Index | Thread | Search

From:
Hans-Jörg Höxer <Hans-Joerg_Hoexer@genua.de>
Subject:
Re: [EXT] Re: AMD SEV 5/5: Add support to vmd(8)
To:
Mike Larkin <mlarkin@nested.page>
Cc:
Hans-Jörg Höxer <Hans-Joerg_Hoexer@genua.de>, <tech@openbsd.org>
Date:
Wed, 4 Sep 2024 15:37:45 +0200

Download raw body.

Thread
Hi,

thanks for reviewing!  Updated diff addressing your remarks is attached.

On Thu, Aug 29, 2024 at 10:25:36PM -0700, Mike Larkin wrote:
> ...
> > @@ -193,7 +195,7 @@ push_gdt(void)
> >  	setsegment(&sd[1], 0, 0xffffffff, SDT_MEMERA, SEL_KPL, 1, 1);
> >  	setsegment(&sd[2], 0, 0xffffffff, SDT_MEMRWA, SEL_KPL, 1, 1);
> >
> > -	write_mem(GDT_PAGE, gdtpage, PAGE_SIZE);
> > +	write_mem_enc(GDT_PAGE, gdtpage, PAGE_SIZE);
> 
> The majority of the cases will be non-encrypted; IMO we should leave the name
> as write_mem and have that function determine whether or not to encrypt based
> on the setting. There are a bunch of these.

good point.  

First of all, I renamed sev_add_memsegmet() to sev_register_encryption().
For now I would keep the "sev_" prefix.  In the long run this needs to
be generealized, I agree.

The use of write_mem_enc() was restricted to loadfile_elf.c.  So I think,
the simplest approach is to add calls to sev_register_encryption() to
the relevant write_mem() calls.  It's onla a few instances where those
are needed.  And write_mem_enc() is gone and write_mem() is unmodified.

> ...
> > +	sev_add_memsegment(addr, sz);
> > +
> 
> I would prefer this be named something else to account for other potential
> implementations later. Also, at first glance, I was mistakenly thinking that
> this really is adding a new memory range to the VM. It's not, it's just
> informing ccp(4) that there is a new area of guest memory that needs to be
> encrypted. That's fine, and needed, but maybe a better name can be invented.

true.  I choose sev_register_encryption() now and I would keep the "sev_"
prefix for now.

> > +++ b/usr.sbin/vmd/vm.conf.5
> > @@ -323,6 +323,8 @@ If only
> >  .Pf : Ar group
> >  is given,
> >  only the group is set.
> > +.It Ic sev
> > +Enables SEV for guest.
> >  .El
> >  .Sh VM INSTANCES
> >  It is possible to use configured or running VMs as a template for
> 
> Do we want a .Xr for ccp(4) here? Shrug. Your call.

we are about to flesh out the man page for psp(4).  We will then add and .Xr.
And maybe some general notes about SEV etc.

> ...
> > +	if (enc && sev_add_memsegment((vaddr_t)dst, len) < 0)
> > +		return (EINVAL);
> > +
> 
> With the bulk of the changes above (all write_mem becoming write_mem_enc),
> this unfairly penalizes any write_mem on non-SEV hardware as we need to
> now route through sev_add_memsegment every time. Sure, it short-circuits
> immediately when it checks for SEV, but the default path should be the
> fastest for the non-SEV case, IMO.

I agree.  See above, write_mem() is now unmodified again.

Take care,
HJ.

-------------------------------------------------------------------------
commit a96e700ae246d1c72222f2d6816521808de549b3
Author: Hans-Joerg Hoexer <hshoexer@genua.de>
Date:   Thu Jul 11 16:20:59 2024 +0200

    vmd(8): initial AMD SEV support
    
    To launch a guest with AMD SEV enabled, vmd needs to do a few things:
    
    - retrieve ASID used by guest on VM creation
    - provide ASID to ccp(4)
    - let ccp(4) encrypt memory used intially by guest
    - run guest
    - release resources held by ccp(4) on guest shutdown
    
    To enable SEV for a guest use the parameter "sev" in the guest's vm
    section in vm.conf.

diff --git a/usr.sbin/vmd/Makefile b/usr.sbin/vmd/Makefile
index 22c1e887823..a0d5ae00e62 100644
--- a/usr.sbin/vmd/Makefile
+++ b/usr.sbin/vmd/Makefile
@@ -11,6 +11,7 @@ SRCS+=		vionet.c
 .if ${MACHINE} == "amd64"
 SRCS+=		i8253.c i8259.c fw_cfg.c loadfile_elf.c mc146818.c ns8250.c
 SRCS+=		x86_vm.c x86_mmio.c
+SRCS+=		psp.c sev.c
 .endif # amd64
 .if ${MACHINE} == "arm64"
 SRCS+=		arm64_vm.c
diff --git a/usr.sbin/vmd/arm64_vm.c b/usr.sbin/vmd/arm64_vm.c
index 282dbcb4985..6d176ac4cfc 100644
--- a/usr.sbin/vmd/arm64_vm.c
+++ b/usr.sbin/vmd/arm64_vm.c
@@ -160,3 +160,51 @@ vcpu_exit_pci(struct vm_run_params *vrp)
 	/* NOTREACHED */
 	return (0xff);
 }
+
+void
+set_return_data(struct vm_exit *vei, uint32_t data)
+{
+	fatalx("%s: unimplemented", __func__);
+	/* NOTREACHED */
+	return;
+}
+
+void
+get_input_data(struct vm_exit *vei, uint32_t *data)
+{
+	fatalx("%s: unimplemented", __func__);
+	/* NOTREACHED */
+	return;
+}
+
+int
+sev_init(struct vmd_vm *vm)
+{
+	fatalx("%s: unimplemented", __func__);
+	/* NOTREACHED */
+	return (-1);
+}
+
+int
+sev_shutdown(struct vmd_vm *vm)
+{
+	fatalx("%s: unimplemented", __func__);
+	/* NOTREACHED */
+	return (-1);
+}
+
+int
+sev_activate(struct vmd_vm *vm, int vcpu_id)
+{
+	fatalx("%s: unimplemented", __func__);
+	/* NOTREACHED */
+	return (-1);
+}
+
+int
+sev_encrypt_memory(struct vmd_vm *vm)
+{
+	fatalx("%s: unimplemented", __func__);
+	/* NOTREACHED */
+	return (-1);
+}
diff --git a/usr.sbin/vmd/loadfile_elf.c b/usr.sbin/vmd/loadfile_elf.c
index 166aa04c5e1..85123961bfe 100644
--- a/usr.sbin/vmd/loadfile_elf.c
+++ b/usr.sbin/vmd/loadfile_elf.c
@@ -130,6 +130,8 @@ static void mbcopy(void *, paddr_t, int);
 extern char *__progname;
 extern int vm_id;
 
+uint64_t pg_crypt = 0;
+
 /*
  * setsegment
  *
@@ -194,6 +196,7 @@ push_gdt(void)
 	setsegment(&sd[2], 0, 0xffffffff, SDT_MEMRWA, SEL_KPL, 1, 1);
 
 	write_mem(GDT_PAGE, gdtpage, PAGE_SIZE);
+	sev_register_encryption(GDT_PAGE, PAGE_SIZE);
 }
 
 /*
@@ -229,20 +232,24 @@ push_pt_64(void)
 
 	/* PDPDE0 - first 1GB */
 	memset(ptes, 0, sizeof(ptes));
-	ptes[0] = PG_V | PML3_PAGE;
+	ptes[0] = pg_crypt | PG_V | PML3_PAGE;
 	write_mem(PML4_PAGE, ptes, PAGE_SIZE);
+	sev_register_encryption(PML4_PAGE, PAGE_SIZE);
 
 	/* PDE0 - first 1GB */
 	memset(ptes, 0, sizeof(ptes));
-	ptes[0] = PG_V | PG_RW | PG_u | PML2_PAGE;
+	ptes[0] = pg_crypt | PG_V | PG_RW | PG_u | PML2_PAGE;
 	write_mem(PML3_PAGE, ptes, PAGE_SIZE);
+	sev_register_encryption(PML3_PAGE, PAGE_SIZE);
 
 	/* First 1GB (in 2MB pages) */
 	memset(ptes, 0, sizeof(ptes));
 	for (i = 0 ; i < 512; i++) {
-		ptes[i] = PG_V | PG_RW | PG_u | PG_PS | ((2048 * 1024) * i);
+		ptes[i] = pg_crypt | PG_V | PG_RW | PG_u | PG_PS |
+		    ((2048 * 1024) * i);
 	}
 	write_mem(PML2_PAGE, ptes, PAGE_SIZE);
+	sev_register_encryption(PML2_PAGE, PAGE_SIZE);
 }
 
 /*
@@ -300,8 +307,18 @@ loadfile_elf(gzFile fp, struct vmd_vm *vm, struct vcpu_reg_state *vrs,
 		vrs->vrs_crs[VCPU_REGS_CR4] = CR4_PSE;
 		vrs->vrs_msrs[VCPU_REGS_EFER] = 0ULL;
 	}
-	else
+	else {
+		if (vcp->vcp_sev) {
+			if (vcp->vcp_poscbit == 0) {
+				log_warnx("SEV enabled but no C-bit reported");
+				return 1;
+			}
+			pg_crypt = (1ULL << vcp->vcp_poscbit);
+			log_debug("%s: poscbit %d pg_crypt 0x%016llx",
+			    __func__, vcp->vcp_poscbit, pg_crypt);
+		}
 		push_pt_64();
+	}
 
 	if (bootdevice == VMBOOTDEV_NET) {
 		bootmac = &bm;
@@ -413,6 +430,7 @@ push_bootargs(bios_memmap_t *memmap, size_t n, bios_bootmac_t *bootmac)
 	ba[i++] = 0xFFFFFFFF; /* BOOTARG_END */
 
 	write_mem(BOOTARGS_PAGE, ba, PAGE_SIZE);
+	sev_register_encryption(BOOTARGS_PAGE, PAGE_SIZE);
 
 	return (i * sizeof(uint32_t));
 }
@@ -463,6 +481,7 @@ push_stack(uint32_t bootargsz, uint32_t end)
 	stack[--loc] = 0;
 
 	write_mem(STACK_PAGE, &stack, PAGE_SIZE);
+	sev_register_encryption(STACK_PAGE, PAGE_SIZE);
 
 	return (1024 - (loc - 1)) * sizeof(uint32_t);
 }
@@ -490,6 +509,8 @@ mread(gzFile fp, paddr_t addr, size_t sz)
 	size_t i, osz;
 	char buf[PAGE_SIZE];
 
+	sev_register_encryption(addr, sz);
+
 	/*
 	 * break up the 'sz' bytes into PAGE_SIZE chunks for use with
 	 * write_mem
@@ -565,6 +586,8 @@ marc4random_buf(paddr_t addr, int sz)
 	int i, ct;
 	char buf[PAGE_SIZE];
 
+	sev_register_encryption(addr, sz);
+
 	/*
 	 * break up the 'sz' bytes into PAGE_SIZE chunks for use with
 	 * write_mem
@@ -614,6 +637,7 @@ mbzero(paddr_t addr, int sz)
 {
 	if (write_mem(addr, NULL, sz))
 		return;
+	sev_register_encryption(addr, sz);
 }
 
 /*
@@ -633,6 +657,7 @@ static void
 mbcopy(void *src, paddr_t dst, int sz)
 {
 	write_mem(dst, src, sz);
+	sev_register_encryption(dst, sz);
 }
 
 /*
diff --git a/usr.sbin/vmd/parse.y b/usr.sbin/vmd/parse.y
index aacfd635100..24a60c1b924 100644
--- a/usr.sbin/vmd/parse.y
+++ b/usr.sbin/vmd/parse.y
@@ -126,7 +126,7 @@ typedef struct {
 %token	FORMAT GROUP
 %token	INET6 INSTANCE INTERFACE LLADDR LOCAL LOCKED MEMORY NET NIFS OWNER
 %token	PATH PREFIX RDOMAIN SIZE SOCKET SWITCH UP VM VMID STAGGERED START
-%token  PARALLEL DELAY
+%token  PARALLEL DELAY SEV
 %token	<v.number>	NUMBER
 %token	<v.string>	STRING
 %type	<v.lladdr>	lladdr
@@ -140,6 +140,7 @@ typedef struct {
 %type	<v.string>	optstring
 %type	<v.string>	string
 %type	<v.string>	vm_instance
+%type	<v.number>	sev;
 
 %%
 
@@ -414,6 +415,9 @@ vm_opts_l	: vm_opts_l vm_opts nl
 vm_opts		: disable			{
 			vmc_disable = $1;
 		}
+		| sev				{
+			vcp->vcp_sev = 1;
+		}
 		| DISK string image_format	{
 			if (parse_disk($2, $3) != 0) {
 				yyerror("failed to parse disks: %s", $2);
@@ -757,6 +761,9 @@ disable		: ENABLE			{ $$ = 0; }
 		| DISABLE			{ $$ = 1; }
 		;
 
+sev		: SEV				{ $$ = 1; }
+		;
+
 bootdevice	: CDROM				{ $$ = VMBOOTDEV_CDROM; }
 		| DISK				{ $$ = VMBOOTDEV_DISK; }
 		| NET				{ $$ = VMBOOTDEV_NET; }
@@ -841,6 +848,7 @@ lookup(char *s)
 		{ "path",		PATH },
 		{ "prefix",		PREFIX },
 		{ "rdomain",		RDOMAIN },
+		{ "sev",		SEV },
 		{ "size",		SIZE },
 		{ "socket",		SOCKET },
 		{ "staggered",		STAGGERED },
diff --git a/usr.sbin/vmd/psp.c b/usr.sbin/vmd/psp.c
new file mode 100644
index 00000000000..4e13d5f8788
--- /dev/null
+++ b/usr.sbin/vmd/psp.c
@@ -0,0 +1,272 @@
+/*	$OpenBSD: $	*/
+
+/*
+ * Copyright (c) 2023, 2024 Hans-Joerg Hoexer <hshoexer@genua.de>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/device.h>
+#include <sys/ioctl.h>
+#include <sys/rwlock.h>
+
+#include <machine/bus.h>
+#include <dev/ic/pspvar.h>
+
+#include <string.h>
+
+#include "vmd.h"
+
+extern struct vmd	*env;
+
+/* Guest policy */
+#define GPOL_NODBG	(1ULL << 0)	/* no debuggin */
+#define GPOL_NOKS	(1ULL << 1)	/* no key sharing */
+#define GPOL_ES		(1ULL << 2)	/* SEV-ES required */
+#define GPOL_NOSEND	(1ULL << 3)	/* no guest migration */
+#define GPOL_DOMAIN	(1ULL << 4)	/* no migration to other domain */
+#define GPOL_SEV	(1ULL << 5)	/* no migration to non-SEV platform */
+
+
+/*
+ * Retrieve platform state.
+ */
+int
+psp_get_pstate(uint16_t *state)
+{
+	struct psp_platform_status pst;
+
+	if (ioctl(env->vmd_ccp_fd, PSP_IOC_GET_PSTATUS, &pst) < 0) {
+		log_warn("%s: ioctl", __func__);
+		return (-1);
+	}
+
+	if (state)
+		*state = pst.state;
+
+	return (0);
+}
+
+
+/*
+ * Flush data fabrics of all cores.
+ *
+ * This ensures all data of a SEV enabled guest is committed to
+ * memory.  This needs to be done before an ASID is assigend to
+ * guest using psp_activate().
+ */
+int
+psp_df_flush(void)
+{
+	if (ioctl(env->vmd_ccp_fd, PSP_IOC_DF_FLUSH) < 0) {
+		log_warn("%s: ioctl", __func__);
+		return (-1);
+	}
+
+	return (0);
+}
+
+
+/*
+ * Retrieve guest state.
+ */
+int
+psp_get_gstate(uint32_t handle, uint32_t *policy, uint32_t *asid,
+    uint8_t *state)
+{
+	struct psp_guest_status gst;
+
+	memset(&gst, 0, sizeof(gst));
+	gst.handle = handle;
+
+	if (ioctl(env->vmd_ccp_fd, PSP_IOC_GET_GSTATUS, &gst) < 0) {
+		log_warn("%s: ioctl", __func__);
+		return (-1);
+	}
+
+	if (policy)
+		*policy = gst.policy;
+	if (asid)
+		*asid = gst.asid;
+	if (state)
+		*state = gst.state;
+
+	return (0);
+}
+
+
+/*
+ * Start the launch sequence of a guest.
+ */
+int
+psp_launch_start(uint32_t *handle)
+{
+	struct psp_launch_start ls;
+
+	memset(&ls, 0, sizeof(ls));
+
+	/* Set guest policy. */
+	ls.policy = (GPOL_NODBG | GPOL_NOKS | GPOL_NOSEND | GPOL_DOMAIN |
+	    GPOL_SEV);
+
+	if (ioctl(env->vmd_ccp_fd, PSP_IOC_LAUNCH_START, &ls) < 0) {
+		log_warn("%s: ioctl", __func__);
+		return (-1);
+	}
+
+	if (handle)
+		*handle = ls.handle;
+
+	return (0);
+}
+
+
+/*
+ * Encrypt and measure a memory range.
+ */
+int
+psp_launch_update(uint32_t handle, vaddr_t v, size_t len)
+{
+	struct psp_launch_update_data lud;
+
+	memset(&lud, 0, sizeof(lud));
+	lud.handle = handle;
+	lud.paddr = v;			/* will be converted to paddr */
+	lud.length = len;
+
+	if (ioctl(env->vmd_ccp_fd, PSP_IOC_LAUNCH_UPDATE_DATA, &lud) < 0) {
+		log_warn("%s: ioctl", __func__);
+		return (-1);
+	}
+
+	return (0);
+}
+
+
+/*
+ * Finalize and return memory measurement.
+ *
+ * We ask the PSP to provide a measurement (HMAC) over the encrypted
+ * memory.  As we do not yet negotiate a shared integrity key with
+ * the PSP, the measurement is not really meaningful.  Thus we just
+ * log it for now.
+ */
+int
+psp_launch_measure(uint32_t handle)
+{
+	struct psp_launch_measure lm;
+	char *p, buf[256];
+	size_t len;
+	unsigned int i;
+
+	memset(&lm, 0, sizeof(lm));
+	lm.handle = handle;
+	lm.measure_len = sizeof(lm.psp_measure);
+	memset(lm.measure, 0, sizeof(lm.measure));
+	memset(lm.measure_nonce, 0, sizeof(lm.measure_nonce));
+
+	if (ioctl(env->vmd_ccp_fd, PSP_IOC_LAUNCH_MEASURE, &lm) < 0) {
+		log_warn("%s: ioctl", __func__);
+		return (-1);
+	}
+
+	/*
+	 * We can not verify the measurement, yet. Therefore just
+	 * log it.
+	 */
+	len = sizeof(buf);
+	memset(buf, 0, len);
+	p = buf;
+	for (i = 0; i < sizeof(lm.measure) && len >= 2;
+	    i++, p += 2, len -= 2) {
+		snprintf(p, len, "%02x", lm.measure[i]);
+	}
+	log_info("%s: measurement\t0x%s", __func__, buf);
+
+	len = sizeof(buf);
+	memset(buf, 0, len);
+	p = buf;
+	for (i = 0; i < sizeof(lm.measure_nonce) && len >= 2;
+	    i++, p += 2, len -= 2) {
+		snprintf(p, len, "%02x", lm.measure_nonce[i]);
+	}
+	log_info("%s: nonce\t0x%s", __func__, buf);
+
+	return (0);
+}
+
+
+/*
+ * Finalize launch sequence.
+ */
+int
+psp_launch_finish(uint32_t handle)
+{
+	struct psp_launch_finish lf;
+
+	lf.handle = handle;
+
+	if (ioctl(env->vmd_ccp_fd, PSP_IOC_LAUNCH_FINISH, &lf) < 0) {
+		log_warn("%s: ioctl", __func__);
+		return (-1);
+	}
+
+	return (0);
+}
+
+
+/*
+ * Activate a guest.
+ *
+ * This associates the guest's ASID with the handle used to identify
+ * crypto contexts managed by the PSP.
+ */
+int
+psp_activate(uint32_t handle, uint32_t asid)
+{
+	struct psp_activate act;
+
+	act.handle = handle;
+	act.asid = asid;
+
+	if (ioctl(env->vmd_ccp_fd, PSP_IOC_ACTIVATE, &act) < 0) {
+		log_warn("%s: ioctl", __func__);
+		return (-1);
+	}
+
+	return (0);
+}
+
+
+/*
+ * Deactivate and decommission a guest.
+ *
+ * This deassociates the guest's ASID from the crypto contexts in
+ * the PSP.  Then the PSP releases the crypto contexts (i.e. deletes
+ * keys).
+ */
+int
+psp_guest_shutdown(uint32_t handle)
+{
+	struct psp_guest_shutdown gshutdown;
+
+	gshutdown.handle = handle;
+
+	if (ioctl(env->vmd_ccp_fd, PSP_IOC_GUEST_SHUTDOWN, &gshutdown) < 0) {
+		log_warn("%s: ioctl", __func__);
+		return (-1);
+	}
+
+	return (0);
+}
diff --git a/usr.sbin/vmd/sev.c b/usr.sbin/vmd/sev.c
new file mode 100644
index 00000000000..3cb339aa843
--- /dev/null
+++ b/usr.sbin/vmd/sev.c
@@ -0,0 +1,247 @@
+/*	$OpenBSD: $	*/
+
+/*
+ * Copyright (c) 2023, 2024 Hans-Joerg Hoexer <hshoexer@genua.de>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/device.h>
+#include <sys/param.h>
+#include <sys/rwlock.h>
+
+#include <machine/bus.h>
+#include <crypto/xform.h>
+#include <dev/ic/pspvar.h>
+
+#include <string.h>
+
+#include "vmd.h"
+
+extern struct vmd_vm	*current_vm;
+
+/*
+ * Prepare guest to use SEV.
+ *
+ * This asks the PSP to create a new crypto context including a
+ * memory encryption key and assign a handle to the context.
+ *
+ * When the PSP driver ccp(4) attaches, it initializes the platform.
+ * If this fails for whatever reason we can not run a guest using SEV.
+ */
+int
+sev_init(struct vmd_vm *vm)
+{
+	struct vmop_create_params *vmc = &vm->vm_params;
+	struct vm_create_params	*vcp = &vmc->vmc_params;
+	uint32_t		 handle;
+	uint16_t		 pstate;
+	uint8_t			 gstate;
+
+	if (!vcp->vcp_sev)
+		return (0);
+
+	if (psp_get_pstate(&pstate)) {
+		log_warnx("%s: failed to get platform state", __func__);
+		return (-1);
+	}
+	if (pstate == PSP_PSTATE_UNINIT) {
+		log_warnx("%s: platform uninitialized", __func__);
+		return (-1);
+	}
+
+	if (psp_launch_start(&handle) < 0) {
+		log_warnx("%s: launch failed", __func__);
+		return (-1);
+	};
+	vm->vm_sev_handle = handle;
+
+	if (psp_get_gstate(vm->vm_sev_handle, NULL, NULL, &gstate)) {
+		log_warnx("%s: failed to get guest state", __func__);
+		return (-1);
+	}
+	if (gstate != PSP_GSTATE_LUPDATE) {
+		log_warnx("%s: invalid guest state: 0x%hx", __func__, gstate);
+		return (-1);
+	}
+
+	return (0);
+}
+
+/*
+ * Record memory segments to be encrypted for SEV.
+ */
+int
+sev_register_encryption(vaddr_t addr, size_t size)
+{
+	struct vmop_create_params *vmc;
+	struct vm_create_params *vcp;
+	struct vm_mem_range	*vmr;
+	size_t			 off;
+	int			 i;
+
+	vmc = &current_vm->vm_params;
+	vcp = &vmc->vmc_params;
+
+	if (!vcp->vcp_sev)
+		return (0);
+
+	if (size == 0)
+		return (0);
+
+	/* Adjust address and size to be aligend to AES_XTS_BLOCKSIZE. */
+	if (addr & (AES_XTS_BLOCKSIZE - 1)) {
+		size += (addr & (AES_XTS_BLOCKSIZE - 1));
+		addr &= ~(AES_XTS_BLOCKSIZE - 1);
+	}
+
+	vmr = find_gpa_range(&current_vm->vm_params.vmc_params, addr, size);
+	if (vmr == NULL) {
+		log_warnx("%s: failed - invalid memory range addr = 0x%lx, "
+		    "len = 0x%zx", __func__, addr, size);
+		return (-1);
+	}
+	if (current_vm->vm_sev_nmemsegments ==
+	    nitems(current_vm->vm_sev_memsegments)) {
+		log_warnx("%s: failed - out of SEV memory segments", __func__);
+		return (-1);
+	}
+	i = current_vm->vm_sev_nmemsegments++;
+
+	off = addr - vmr->vmr_gpa;
+
+	current_vm->vm_sev_memsegments[i].vmr_va = vmr->vmr_va + off;
+	current_vm->vm_sev_memsegments[i].vmr_size = size;
+	current_vm->vm_sev_memsegments[i].vmr_gpa = vmr->vmr_gpa + off;
+
+	log_debug("%s: i %d addr 0x%lx size 0x%lx vmr_va 0x%lx vmr_gpa 0x%lx "
+	    "vmr_size 0x%lx", __func__, i, addr, size,
+	    current_vm->vm_sev_memsegments[i].vmr_va,
+	    current_vm->vm_sev_memsegments[i].vmr_gpa,
+	    current_vm->vm_sev_memsegments[i].vmr_size);
+
+	return (0);
+}
+
+/*
+ * Encrypt and measure previously recorded memroy segments.
+ *
+ * This encrypts the memory initially used by the guest.  This
+ * includes the kernel or BIOS image, initial stack, boot arguments
+ * and page tables.
+ *
+ * We also ask the PSP to provide a measurement.  However, right
+ * now we can not really verify it.
+ */
+int
+sev_encrypt_memory(struct vmd_vm *vm)
+{
+	struct vmop_create_params *vmc = &vm->vm_params;
+	struct vm_create_params *vcp = &vmc->vmc_params;
+	struct vm_mem_range	*vmr;
+	size_t			 i;
+	uint8_t			 gstate;
+
+	if (!vcp->vcp_sev)
+		return (0);
+
+	for (i = 0; i < vm->vm_sev_nmemsegments; i++) {
+		vmr = &vm->vm_sev_memsegments[i];
+
+		/* tell PSP to encrypt this range */
+		if (psp_launch_update(vm->vm_sev_handle, vmr->vmr_va,
+		    roundup(vmr->vmr_size, AES_XTS_BLOCKSIZE))) {
+			log_warnx("%s: failed to launch update page "
+			    "%zu:0x%lx", __func__, i, vmr->vmr_va);
+			return (-1);
+		}
+
+		log_debug("%s: encrypted %zu:0x%lx size 0x%lx", __func__, i,
+		    vmr->vmr_va, vmr->vmr_size);
+	}
+	if (psp_launch_measure(vm->vm_sev_handle)) {
+		log_warnx("%s: failed to launch measure", __func__);
+		return (-1);
+	}
+	if (psp_launch_finish(vm->vm_sev_handle)) {
+		log_warnx("%s: failed to launch finish", __func__);
+		return (-1);
+	}
+
+	if (psp_get_gstate(vm->vm_sev_handle, NULL, NULL, &gstate)) {
+		log_warnx("%s: failed to get guest state", __func__);
+		return (-1);
+	}
+	if (gstate != PSP_GSTATE_RUNNING) {
+		log_warnx("%s: invalid guest state: 0x%hx", __func__, gstate);
+		return (-1);
+	}
+
+	return (0);
+}
+
+
+/*
+ * Activate a guest's SEV crypto state.
+ */
+int
+sev_activate(struct vmd_vm *vm, int vcpu_id)
+{
+	struct vmop_create_params *vmc = &vm->vm_params;
+	struct vm_create_params *vcp = &vmc->vmc_params;
+	uint8_t			 gstate;
+
+	if (!vcp->vcp_sev)
+		return (0);
+
+	if (psp_df_flush() ||
+	    psp_activate(vm->vm_sev_handle, vm->vm_sev_asid[vcpu_id])) {
+		log_warnx("%s: failed to activate guest: 0x%x:0x%x", __func__,
+		    vm->vm_sev_handle, vm->vm_sev_asid[vcpu_id]);
+		return (-1);
+	}
+
+	if (psp_get_gstate(vm->vm_sev_handle, NULL, NULL, &gstate)) {
+		log_warnx("%s: failed to get guest state", __func__);
+		return (-1);
+	}
+	if (gstate != PSP_GSTATE_LUPDATE) {
+		log_warnx("%s: invalid guest state: 0x%hx", __func__, gstate);
+		return (-1);
+	}
+
+	return (0);
+}
+
+
+/*
+ * Deactivate and decommission a guest's SEV crypto state.
+ */
+int
+sev_shutdown(struct vmd_vm *vm)
+{
+	struct vmop_create_params *vmc = &vm->vm_params;
+	struct vm_create_params *vcp = &vmc->vmc_params;
+
+	if (!vcp->vcp_sev)
+		return (0);
+
+	if (psp_guest_shutdown(vm->vm_sev_handle)) {
+		log_warnx("failed to deactivate guest");
+		return (-1);
+	}
+	vm->vm_sev_handle = 0;
+
+	return (0);
+}
diff --git a/usr.sbin/vmd/vm.c b/usr.sbin/vmd/vm.c
index e8c73b0e053..c46f46a17cf 100644
--- a/usr.sbin/vmd/vm.c
+++ b/usr.sbin/vmd/vm.c
@@ -48,6 +48,7 @@
 #include <util.h>
 
 #include "atomicio.h"
+#include "loadfile.h"
 #include "mmio.h"
 #include "pci.h"
 #include "virtio.h"
@@ -163,6 +164,11 @@ vm_main(int fd, int fd_vmm)
 		}
 	}
 
+	if (vcp->vcp_sev && env->vmd_ccp_fd < 0) {
+		log_warnx("%s not available", CCP_NODE);
+		_exit(EINVAL);
+	}
+
 	ret = start_vm(&vm, fd);
 	_exit(ret);
 }
@@ -230,6 +236,13 @@ start_vm(struct vmd_vm *vm, int fd)
 		return (ret);
 	}
 
+	/* Setup SEV. */
+	ret = sev_init(vm);
+	if (ret) {
+		log_warnx("could not initialize SEV");
+		return (ret);
+	}
+
 	/*
 	 * Some of vmd currently relies on global state (current_vm, con_fd).
 	 */
@@ -318,6 +331,10 @@ start_vm(struct vmd_vm *vm, int fd)
 	 */
 	ret = run_vm(&vm->vm_params, &vrs);
 
+	/* Shutdown SEV. */
+	if (sev_shutdown(vm))
+		log_warnx("%s: could not shutdown SEV", __func__);
+
 	/* Ensure that any in-flight data is written back */
 	virtio_shutdown(vm);
 
@@ -456,6 +473,9 @@ vm_shutdown(unsigned int cmd)
 	}
 	imsg_flush(&current_vm->vm_iev.ibuf);
 
+	if (sev_shutdown(current_vm))
+		log_warnx("%s: could not shutdown SEV", __func__);
+
 	_exit(0);
 }
 
@@ -820,6 +840,7 @@ static int
 vmm_create_vm(struct vmd_vm *vm)
 {
 	struct vm_create_params *vcp = &vm->vm_params.vmc_params;
+	size_t i;
 
 	/* Sanity check arguments */
 	if (vcp->vcp_ncpus > VMM_MAX_VCPUS_PER_VM)
@@ -838,6 +859,9 @@ vmm_create_vm(struct vmd_vm *vm)
 	if (ioctl(env->vmd_fd, VMM_IOC_CREATE, vcp) == -1)
 		return (errno);
 
+	for (i = 0; i < vcp->vcp_ncpus; i++)
+		vm->vm_sev_asid[i] = vcp->vcp_asid[i];
+
 	return (0);
 }
 
@@ -920,6 +944,18 @@ run_vm(struct vmop_create_params *vmc, struct vcpu_reg_state *vrs)
 			return (EIO);
 		}
 
+		if (sev_activate(current_vm, i)) {
+			log_warnx("%s: SEV activatation failed for VCPU "
+			    "%zu failed - exiting.", __progname, i);
+			return (EIO);
+		}
+
+		if (sev_encrypt_memory(current_vm)) {
+			log_warnx("%s: memory encryption failed for VCPU "
+			    "%zu failed - exiting.", __progname, i);
+			return (EIO);
+		}
+
 		/* once more because reset_cpu changes regs */
 		if (current_vm->vm_state & VM_STATE_RECEIVED) {
 			vregsp.vrwp_vm_id = vcp->vcp_id;
diff --git a/usr.sbin/vmd/vm.conf.5 b/usr.sbin/vmd/vm.conf.5
index ed6cd41df64..e07ba35103b 100644
--- a/usr.sbin/vmd/vm.conf.5
+++ b/usr.sbin/vmd/vm.conf.5
@@ -323,6 +323,8 @@ If only
 .Pf : Ar group
 is given,
 only the group is set.
+.It Ic sev
+Enables SEV for guest.
 .El
 .Sh VM INSTANCES
 It is possible to use configured or running VMs as a template for
diff --git a/usr.sbin/vmd/vmd.c b/usr.sbin/vmd/vmd.c
index 232bc82d8d2..d37abf2364f 100644
--- a/usr.sbin/vmd/vmd.c
+++ b/usr.sbin/vmd/vmd.c
@@ -661,7 +661,7 @@ main(int argc, char **argv)
 	int			 ch;
 	enum privsep_procid	 proc_id = PROC_PARENT;
 	int			 proc_instance = 0, vm_launch = 0;
-	int			 vmm_fd = -1, vm_fd = -1;
+	int			 vmm_fd = -1, vm_fd = -1, ccp_fd = -1;
 	const char		*errp, *title = NULL;
 	int			 argc0 = argc;
 	char			 dev_type = '\0';
@@ -673,7 +673,7 @@ main(int argc, char **argv)
 	env->vmd_fd = -1;
 	env->vmd_fd6 = -1;
 
-	while ((ch = getopt(argc, argv, "D:P:I:V:X:df:i:nt:vp:")) != -1) {
+	while ((ch = getopt(argc, argv, "D:P:I:V:X:df:i:j:nt:vp:")) != -1) {
 		switch (ch) {
 		case 'D':
 			if (cmdline_symset(optarg) < 0)
@@ -735,6 +735,12 @@ main(int argc, char **argv)
 			if (errp)
 				fatalx("invalid vmm fd");
 			break;
+		case 'j':
+			/* -1 means no PSP available */
+			ccp_fd = strtonum(optarg, -1, 128, &errp);
+			if (errp)
+				fatalx("invalid psp fd");
+			break;
 		default:
 			usage();
 		}
@@ -763,6 +769,7 @@ main(int argc, char **argv)
 
 	ps = &env->vmd_ps;
 	ps->ps_env = env;
+	env->vmd_ccp_fd = ccp_fd;
 
 	if (config_init(env) == -1)
 		fatal("failed to initialize configuration");
@@ -837,6 +844,12 @@ main(int argc, char **argv)
 	if (!env->vmd_noaction)
 		proc_connect(ps);
 
+	if (env->vmd_noaction == 0 && proc_id == PROC_PARENT) {
+		env->vmd_ccp_fd = open(CCP_NODE, O_RDWR);
+		if (env->vmd_ccp_fd == -1)
+			log_debug("%s: failed to open %s", __func__, CCP_NODE);
+	}
+
 	if (vmd_configure() == -1)
 		fatalx("configuration failed");
 
@@ -917,6 +930,12 @@ vmd_configure(void)
 	proc_compose_imsg(&env->vmd_ps, PROC_VMM, -1,
 	    IMSG_VMDOP_RECEIVE_VMM_FD, -1, env->vmd_fd, NULL, 0);
 
+	/* Send PSP device fd to vmm proc. */
+	if (env->vmd_ccp_fd != -1) {
+		proc_compose_imsg(&env->vmd_ps, PROC_VMM, -1,
+		    IMSG_VMDOP_RECEIVE_CCP_FD, -1, env->vmd_ccp_fd, NULL, 0);
+	}
+
 	/* Send shared global configuration to all children */
 	if (config_setconfig(env) == -1)
 		return (-1);
diff --git a/usr.sbin/vmd/vmd.h b/usr.sbin/vmd/vmd.h
index 2f2056541c8..f44dc80332a 100644
--- a/usr.sbin/vmd/vmd.h
+++ b/usr.sbin/vmd/vmd.h
@@ -50,6 +50,7 @@
 #define VMD_CONF		"/etc/vm.conf"
 #define SOCKET_NAME		"/var/run/vmd.sock"
 #define VMM_NODE		"/dev/vmm"
+#define CCP_NODE		"/dev/psp"
 #define VM_DEFAULT_BIOS		"/etc/firmware/vmm-bios"
 #define VM_DEFAULT_KERNEL	"/bsd"
 #define VM_DEFAULT_DEVICE	"hd0a"
@@ -131,6 +132,7 @@ enum imsg_type {
 	IMSG_VMDOP_GET_INFO_VM_END_DATA,
 	IMSG_VMDOP_LOAD,
 	IMSG_VMDOP_RECEIVE_VMM_FD,
+	IMSG_VMDOP_RECEIVE_CCP_FD,
 	IMSG_VMDOP_RELOAD,
 	IMSG_VMDOP_PRIV_IFDESCR,
 	IMSG_VMDOP_PRIV_IFADD,
@@ -305,6 +307,12 @@ struct vmd_vm {
 	struct vmop_create_params vm_params;
 	pid_t			 vm_pid;
 	uint32_t		 vm_vmid;
+	uint32_t		 vm_sev_handle;
+	uint32_t		 vm_sev_asid[VMM_MAX_VCPUS_PER_VM];
+
+#define VM_SEV_NSEGMENTS	128
+	size_t			 vm_sev_nmemsegments;
+	struct vm_mem_range	 vm_sev_memsegments[VM_SEV_NSEGMENTS];
 
 	int			 vm_kernel;
 	char			*vm_kernel_path; /* Used by vm.conf. */
@@ -398,6 +406,7 @@ struct vmd {
 	int			 vmd_fd;
 	int			 vmd_fd6;
 	int			 vmd_ptmfd;
+	int			 vmd_ccp_fd;
 };
 
 struct vm_dev_pipe {
@@ -508,6 +517,8 @@ void	 unpause_vm_md(struct vmd_vm *);
 int	 dump_devs(int);
 int	 dump_send_header(int);
 void	*hvaddr_mem(paddr_t, size_t);
+struct vm_mem_range *
+	 find_gpa_range(struct vm_create_params *, paddr_t, size_t);
 int	 write_mem(paddr_t, const void *, size_t);
 int	 read_mem(paddr_t, void *, size_t);
 int	 intr_ack(struct vmd_vm *);
@@ -538,6 +549,7 @@ void	 vm_pipe_init2(struct vm_dev_pipe *, void (*)(int, short, void *),
 	    void *);
 void	 vm_pipe_send(struct vm_dev_pipe *, enum pipe_msg_type);
 enum pipe_msg_type vm_pipe_recv(struct vm_dev_pipe *);
+int	 write_mem(paddr_t, const void *buf, size_t);
 int	 remap_guest_mem(struct vmd_vm *, int);
 __dead void vm_shutdown(unsigned int);
 
@@ -573,4 +585,22 @@ __dead void vionet_main(int, int);
 /* vioblk.c */
 __dead void vioblk_main(int, int);
 
+/* psp.c */
+int	 psp_get_pstate(uint16_t *);
+int	 psp_df_flush(void);
+int	 psp_get_gstate(uint32_t, uint32_t *, uint32_t *, uint8_t *);
+int	 psp_launch_start(uint32_t *);
+int	 psp_launch_update(uint32_t, vaddr_t, size_t);
+int	 psp_launch_measure(uint32_t);
+int	 psp_launch_finish(uint32_t);
+int	 psp_activate(uint32_t, uint32_t);
+int	 psp_guest_shutdown(uint32_t);
+
+/* sev.c */
+int	sev_init(struct vmd_vm *);
+int	sev_register_encryption(vaddr_t, size_t);
+int	sev_encrypt_memory(struct vmd_vm *);
+int	sev_activate(struct vmd_vm *, int);
+int	sev_shutdown(struct vmd_vm *);
+
 #endif /* VMD_H */
diff --git a/usr.sbin/vmd/vmm.c b/usr.sbin/vmd/vmm.c
index 6a98e43f751..e4dd6f7b6bb 100644
--- a/usr.sbin/vmd/vmm.c
+++ b/usr.sbin/vmd/vmm.c
@@ -325,6 +325,11 @@ vmm_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg)
 		/* Get and terminate all running VMs */
 		get_info_vm(ps, NULL, 1);
 		break;
+	case IMSG_VMDOP_RECEIVE_CCP_FD:
+		if (env->vmd_ccp_fd > -1)
+			fatalx("already received psp fd");
+		env->vmd_ccp_fd = imsg->fd;
+		break;
 	default:
 		return (-1);
 	}
@@ -645,7 +650,7 @@ vmm_start_vm(struct imsg *imsg, uint32_t *id, pid_t *pid)
 {
 	struct vm_create_params	*vcp;
 	struct vmd_vm		*vm;
-	char			*nargv[8], num[32], vmm_fd[32];
+	char			*nargv[10], num[32], vmm_fd[32], ccp_fd[32];
 	int			 fd, ret = EINVAL;
 	int			 fds[2];
 	pid_t			 vm_pid;
@@ -760,6 +765,9 @@ vmm_start_vm(struct imsg *imsg, uint32_t *id, pid_t *pid)
 				close(fd);
 		}
 
+		if (env->vmd_ccp_fd > 0)
+			fcntl(env->vmd_ccp_fd, F_SETFD, 0); /* psp device fd */
+
 		/*
 		 * Prepare our new argv for execvp(2) with the fd of our open
 		 * pipe to the parent/vmm process as an argument.
@@ -769,6 +777,8 @@ vmm_start_vm(struct imsg *imsg, uint32_t *id, pid_t *pid)
 		snprintf(num, sizeof(num), "%d", fds[1]);
 		memset(vmm_fd, 0, sizeof(vmm_fd));
 		snprintf(vmm_fd, sizeof(vmm_fd), "%d", env->vmd_fd);
+		memset(ccp_fd, 0, sizeof(ccp_fd));
+		snprintf(ccp_fd, sizeof(ccp_fd), "%d", env->vmd_ccp_fd);
 
 		nargv[0] = env->argv0;
 		nargv[1] = "-V";
@@ -776,14 +786,16 @@ vmm_start_vm(struct imsg *imsg, uint32_t *id, pid_t *pid)
 		nargv[3] = "-n";
 		nargv[4] = "-i";
 		nargv[5] = vmm_fd;
-		nargv[6] = NULL;
+		nargv[6] = "-j";
+		nargv[7] = ccp_fd;
+		nargv[8] = NULL;
 
 		if (env->vmd_verbose == 1) {
-			nargv[6] = VMD_VERBOSE_1;
-			nargv[7] = NULL;
+			nargv[8] = VMD_VERBOSE_1;
+			nargv[9] = NULL;
 		} else if (env->vmd_verbose > 1) {
-			nargv[6] = VMD_VERBOSE_2;
-			nargv[7] = NULL;
+			nargv[8] = VMD_VERBOSE_2;
+			nargv[9] = NULL;
 		}
 
 		/* Control resumes in vmd main(). */
diff --git a/usr.sbin/vmd/x86_vm.c b/usr.sbin/vmd/x86_vm.c
index d0caf98dd12..1fee9475f6f 100644
--- a/usr.sbin/vmd/x86_vm.c
+++ b/usr.sbin/vmd/x86_vm.c
@@ -52,8 +52,6 @@ extern char *__progname;
 void	 create_memory_map(struct vm_create_params *);
 int	 translate_gva(struct vm_exit*, uint64_t, uint64_t *, int);
 
-static struct vm_mem_range *find_gpa_range(struct vm_create_params *, paddr_t,
-    size_t);
 static int	loadfile_bios(gzFile, off_t, struct vcpu_reg_state *);
 static int	vcpu_exit_eptviolation(struct vm_run_params *);
 static void	vcpu_exit_inout(struct vm_run_params *);
@@ -792,7 +790,7 @@ vcpu_exit_pci(struct vm_run_params *vrp)
  *  NULL: on failure if there is no memory range as described by the parameters
  *  Pointer to vm_mem_range that contains the start of the range otherwise.
  */
-static struct vm_mem_range *
+struct vm_mem_range *
 find_gpa_range(struct vm_create_params *vcp, paddr_t gpa, size_t len)
 {
 	size_t i, n;