Implement AMD SEV support in vmd(8).
authorbluhm <bluhm@openbsd.org>
Wed, 11 Sep 2024 15:42:52 +0000 (15:42 +0000)
committerbluhm <bluhm@openbsd.org>
Wed, 11 Sep 2024 15:42:52 +0000 (15:42 +0000)
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 psp(4)
- let psp(4) encrypt memory used intially by guest
- run guest
- release resources held by psp(4) on guest shutdown
To enable SEV for a guest use the parameter "sev" in the guest's vm
section in vm.conf.

from hshoexer@; OK mlarkin@

12 files changed:
usr.sbin/vmd/Makefile
usr.sbin/vmd/arm64_vm.c
usr.sbin/vmd/loadfile_elf.c
usr.sbin/vmd/parse.y
usr.sbin/vmd/psp.c [new file with mode: 0644]
usr.sbin/vmd/sev.c [new file with mode: 0644]
usr.sbin/vmd/vm.c
usr.sbin/vmd/vm.conf.5
usr.sbin/vmd/vmd.c
usr.sbin/vmd/vmd.h
usr.sbin/vmd/vmm.c
usr.sbin/vmd/x86_vm.c

index 22c1e88..bc7a159 100644 (file)
@@ -1,4 +1,4 @@
-#      $OpenBSD: Makefile,v 1.30 2024/07/10 09:27:33 dv Exp $
+#      $OpenBSD: Makefile,v 1.31 2024/09/11 15:42:52 bluhm Exp $
 
 .if ${MACHINE} == "amd64" || ${MACHINE} == "arm64"
 
@@ -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
index 282dbcb..3515ed1 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: arm64_vm.c,v 1.1 2024/07/10 10:41:19 dv Exp $ */
+/*     $OpenBSD: arm64_vm.c,v 1.2 2024/09/11 15:42:52 bluhm Exp $      */
 /*
  * Copyright (c) 2024 Dave Voutila <dv@openbsd.org>
  *
@@ -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);
+}
index 166aa04..efe1e97 100644 (file)
@@ -1,5 +1,5 @@
 /* $NetBSD: loadfile.c,v 1.10 2000/12/03 02:53:04 tsutsui Exp $ */
-/* $OpenBSD: loadfile_elf.c,v 1.48 2024/07/09 09:31:37 dv Exp $ */
+/* $OpenBSD: loadfile_elf.c,v 1.49 2024/09/11 15:42:52 bluhm Exp $ */
 
 /*-
  * Copyright (c) 1997 The NetBSD Foundation, Inc.
@@ -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);
 }
 
 /*
index aacfd63..1821139 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: parse.y,v 1.69 2024/07/09 09:31:37 dv Exp $   */
+/*     $OpenBSD: parse.y,v 1.70 2024/09/11 15:42:52 bluhm Exp $        */
 
 /*
  * Copyright (c) 2007-2016 Reyk Floeter <reyk@openbsd.org>
@@ -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 (file)
index 0000000..3e69a1c
--- /dev/null
@@ -0,0 +1,272 @@
+/*     $OpenBSD: psp.c,v 1.1 2024/09/11 15:42:52 bluhm Exp $   */
+
+/*
+ * 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_psp_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_psp_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_psp_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_psp_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_psp_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_psp_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_psp_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_psp_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_psp_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 (file)
index 0000000..b61034b
--- /dev/null
@@ -0,0 +1,247 @@
+/*     $OpenBSD: sev.c,v 1.1 2024/09/11 15:42:52 bluhm Exp $   */
+
+/*
+ * 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 psp(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);
+}
index e8c73b0..01c7c4a 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: vm.c,v 1.104 2024/07/10 09:27:33 dv Exp $     */
+/*     $OpenBSD: vm.c,v 1.105 2024/09/11 15:42:52 bluhm Exp $  */
 
 /*
  * Copyright (c) 2015 Mike Larkin <mlarkin@openbsd.org>
@@ -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_psp_fd < 0) {
+               log_warnx("%s not available", PSP_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;
index ed6cd41..aaeed8f 100644 (file)
@@ -1,4 +1,4 @@
-.\" $OpenBSD: vm.conf.5,v 1.63 2023/05/12 00:06:53 kn Exp $
+.\" $OpenBSD: vm.conf.5,v 1.64 2024/09/11 15:42:52 bluhm Exp $
 .\"
 .\" Copyright (c) 2015 Mike Larkin <mlarkin@openbsd.org>
 .\" Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org>
@@ -15,7 +15,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd $Mdocdate: May 12 2023 $
+.Dd $Mdocdate: September 11 2024 $
 .Dt VM.CONF 5
 .Os
 .Sh NAME
@@ -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
index 232bc82..3409b52 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: vmd.c,v 1.159 2024/07/10 09:27:33 dv Exp $    */
+/*     $OpenBSD: vmd.c,v 1.160 2024/09/11 15:42:52 bluhm Exp $ */
 
 /*
  * Copyright (c) 2015 Reyk Floeter <reyk@openbsd.org>
@@ -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, psp_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 */
+                       psp_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_psp_fd = psp_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_psp_fd = open(PSP_NODE, O_RDWR);
+               if (env->vmd_psp_fd == -1)
+                       log_debug("%s: failed to open %s", __func__, PSP_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_psp_fd != -1) {
+               proc_compose_imsg(&env->vmd_ps, PROC_VMM, -1,
+                   IMSG_VMDOP_RECEIVE_PSP_FD, -1, env->vmd_psp_fd, NULL, 0);
+       }
+
        /* Send shared global configuration to all children */
        if (config_setconfig(env) == -1)
                return (-1);
index 2f20565..8d53530 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: vmd.h,v 1.127 2024/07/10 09:27:33 dv Exp $    */
+/*     $OpenBSD: vmd.h,v 1.128 2024/09/11 15:42:52 bluhm Exp $ */
 
 /*
  * Copyright (c) 2015 Mike Larkin <mlarkin@openbsd.org>
@@ -50,6 +50,7 @@
 #define VMD_CONF               "/etc/vm.conf"
 #define SOCKET_NAME            "/var/run/vmd.sock"
 #define VMM_NODE               "/dev/vmm"
+#define PSP_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_PSP_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_psp_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 */
index 6a98e43..2827164 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: vmm.c,v 1.121 2024/07/10 09:27:33 dv Exp $    */
+/*     $OpenBSD: vmm.c,v 1.122 2024/09/11 15:42:52 bluhm Exp $ */
 
 /*
  * Copyright (c) 2015 Mike Larkin <mlarkin@openbsd.org>
@@ -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_PSP_FD:
+               if (env->vmd_psp_fd > -1)
+                       fatalx("already received psp fd");
+               env->vmd_psp_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], psp_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_psp_fd > 0)
+                       fcntl(env->vmd_psp_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(psp_fd, 0, sizeof(psp_fd));
+               snprintf(psp_fd, sizeof(psp_fd), "%d", env->vmd_psp_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] = psp_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(). */
index d0caf98..de8efcc 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: x86_vm.c,v 1.2 2024/07/12 13:51:12 dv Exp $   */
+/*     $OpenBSD: x86_vm.c,v 1.3 2024/09/11 15:42:52 bluhm Exp $        */
 /*
  * Copyright (c) 2015 Mike Larkin <mlarkin@openbsd.org>
  *
@@ -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;