From f4b47ae8c166ad93c181c94348498561bc3a6653 Mon Sep 17 00:00:00 2001 From: bluhm Date: Wed, 11 Sep 2024 15:42:52 +0000 Subject: [PATCH] Implement AMD SEV support in vmd(8). 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@ --- usr.sbin/vmd/Makefile | 3 +- usr.sbin/vmd/arm64_vm.c | 50 ++++++- usr.sbin/vmd/loadfile_elf.c | 35 ++++- usr.sbin/vmd/parse.y | 12 +- usr.sbin/vmd/psp.c | 272 ++++++++++++++++++++++++++++++++++++ usr.sbin/vmd/sev.c | 247 ++++++++++++++++++++++++++++++++ usr.sbin/vmd/vm.c | 38 ++++- usr.sbin/vmd/vm.conf.5 | 6 +- usr.sbin/vmd/vmd.c | 25 +++- usr.sbin/vmd/vmd.h | 32 ++++- usr.sbin/vmd/vmm.c | 26 +++- usr.sbin/vmd/x86_vm.c | 6 +- 12 files changed, 725 insertions(+), 27 deletions(-) create mode 100644 usr.sbin/vmd/psp.c create mode 100644 usr.sbin/vmd/sev.c diff --git a/usr.sbin/vmd/Makefile b/usr.sbin/vmd/Makefile index 22c1e887823..bc7a159defc 100644 --- a/usr.sbin/vmd/Makefile +++ b/usr.sbin/vmd/Makefile @@ -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 diff --git a/usr.sbin/vmd/arm64_vm.c b/usr.sbin/vmd/arm64_vm.c index 282dbcb4985..3515ed1c905 100644 --- a/usr.sbin/vmd/arm64_vm.c +++ b/usr.sbin/vmd/arm64_vm.c @@ -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 * @@ -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..efe1e974b5c 100644 --- a/usr.sbin/vmd/loadfile_elf.c +++ b/usr.sbin/vmd/loadfile_elf.c @@ -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); } /* diff --git a/usr.sbin/vmd/parse.y b/usr.sbin/vmd/parse.y index aacfd635100..18211393eba 100644 --- a/usr.sbin/vmd/parse.y +++ b/usr.sbin/vmd/parse.y @@ -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 @@ -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 NUMBER %token STRING %type lladdr @@ -140,6 +140,7 @@ typedef struct { %type optstring %type string %type vm_instance +%type 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..3e69a1c1293 --- /dev/null +++ b/usr.sbin/vmd/psp.c @@ -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 + * + * 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 +#include +#include +#include + +#include +#include + +#include + +#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 index 00000000000..b61034b33a2 --- /dev/null +++ b/usr.sbin/vmd/sev.c @@ -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 + * + * 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 +#include +#include +#include + +#include +#include +#include + +#include + +#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 = ¤t_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(¤t_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..01c7c4a8d95 100644 --- a/usr.sbin/vmd/vm.c +++ b/usr.sbin/vmd/vm.c @@ -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 @@ -48,6 +48,7 @@ #include #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(¤t_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..aaeed8fac79 100644 --- a/usr.sbin/vmd/vm.conf.5 +++ b/usr.sbin/vmd/vm.conf.5 @@ -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 .\" Copyright (c) 2015 Reyk Floeter @@ -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 diff --git a/usr.sbin/vmd/vmd.c b/usr.sbin/vmd/vmd.c index 232bc82d8d2..3409b529ff8 100644 --- a/usr.sbin/vmd/vmd.c +++ b/usr.sbin/vmd/vmd.c @@ -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 @@ -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); diff --git a/usr.sbin/vmd/vmd.h b/usr.sbin/vmd/vmd.h index 2f2056541c8..8d53530198f 100644 --- a/usr.sbin/vmd/vmd.h +++ b/usr.sbin/vmd/vmd.h @@ -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 @@ -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 */ diff --git a/usr.sbin/vmd/vmm.c b/usr.sbin/vmd/vmm.c index 6a98e43f751..282716481cb 100644 --- a/usr.sbin/vmd/vmm.c +++ b/usr.sbin/vmd/vmm.c @@ -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 @@ -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(). */ diff --git a/usr.sbin/vmd/x86_vm.c b/usr.sbin/vmd/x86_vm.c index d0caf98dd12..de8efccd5e1 100644 --- a/usr.sbin/vmd/x86_vm.c +++ b/usr.sbin/vmd/x86_vm.c @@ -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 * @@ -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; -- 2.20.1