diff --git a/tools/include/libxl.h b/tools/include/libxl.h index 63fce15bfd1c..d2c79920498d 100644 --- a/tools/include/libxl.h +++ b/tools/include/libxl.h @@ -3025,6 +3025,27 @@ static inline int libxl_qemu_monitor_command_0x041200(libxl_ctx *ctx, */ int libxl_clear_domid_history(libxl_ctx *ctx); +/* + * Used to retrieve an attestation for a coco domain + */ +int libxl_coco_domain_attestation(libxl_ctx *ctx, uint32_t domid, int file, bool is_mmonce_file, char *mmonce); + +/* + * Used to retrieve platform public keys and relevant information (identification, certificates); + */ +int libxl_coco_platform_certs(libxl_ctx *ctx, char *path); + +/* + * Retrieve a certificate signing request + */ +int libxl_coco_csr(libxl_ctx *ctx, char* path); + +int libxl_coco_update(libxl_ctx *ctx, char* path); + +int libxl_coco_regen_certificate(libxl_ctx *ctx, char* cert); + +int libxl_coco_import_certificate(libxl_ctx *ctx, char* pek, char *crt); + #endif /* LIBXL_H */ /* diff --git a/tools/include/xenctrl.h b/tools/include/xenctrl.h index 6062dc747941..d38a413e858e 100644 --- a/tools/include/xenctrl.h +++ b/tools/include/xenctrl.h @@ -1685,6 +1685,13 @@ int xc_get_hvm_param(xc_interface *handle, uint32_t dom, int param, unsigned lon int xc_coco_platform_status(xc_interface *handle, coco_platform_status_t *status); int xc_coco_prepare_initial_mem(xc_interface *handle, coco_prepare_initial_mem_t *cmd); +int xc_coco_finish_initial_mem(xc_interface *handle, domid_t domid); +int xc_coco_get_attestation(xc_interface *handle, coco_attestation_report_t *report); +int xc_coco_get_platform_certs(xc_interface *handle, coco_platform_certs_t *cmd); +int xc_coco_get_csr(xc_interface *handle, coco_certificate_t *cmd); +int xc_coco_regen_certificate(xc_interface *handle, coco_certificate_name_t cert); +int xc_coco_import_certificate(xc_interface *handle, coco_platform_import_certs_t *cmd); +int xc_coco_update(xc_interface *handle, coco_update_t *cmd); /* HVM guest pass-through */ int xc_assign_device(xc_interface *xch, @@ -2684,4 +2691,4 @@ int xc_dt_overlay_domain(xc_interface *xch, void *overlay_fdt, * tab-width: 4 * indent-tabs-mode: nil * End: - */ + */ \ No newline at end of file diff --git a/tools/libs/ctrl/xc_domain.c b/tools/libs/ctrl/xc_domain.c index 66b6c146f4cf..74fef385ccab 100644 --- a/tools/libs/ctrl/xc_domain.c +++ b/tools/libs/ctrl/xc_domain.c @@ -1532,6 +1532,43 @@ int xc_coco_prepare_initial_mem(xc_interface *handle, coco_prepare_initial_mem_t return rc; } +int xc_coco_finish_initial_mem(xc_interface *handle, domid_t domid) +{ + DECLARE_HYPERCALL_BUFFER(domid_t, arg); + int rc; + + arg = xc_hypercall_buffer_alloc(handle, arg, sizeof(*arg)); + if ( arg == NULL ) + return -1; + *arg = domid; + + rc = xencall2(handle->xcall, __HYPERVISOR_coco_op, XEN_COCO_finish_initial_mem, + HYPERCALL_BUFFER_AS_ARG(arg)); + + xc_hypercall_buffer_free(handle, arg); + return rc; +} + +int xc_coco_get_attestation(xc_interface *handle, coco_attestation_report_t *cmd) +{ + DECLARE_HYPERCALL_BUFFER(coco_attestation_report_t, arg); + int rc; + + arg = xc_hypercall_buffer_alloc(handle, arg, sizeof(*arg)); + if ( arg == NULL ) + return -1; + memcpy(arg, cmd, sizeof(coco_attestation_report_t)); + + rc = xencall2(handle->xcall, __HYPERVISOR_coco_op, XEN_COCO_attestation_report, + HYPERCALL_BUFFER_AS_ARG(arg)); + + if (!rc) { + memcpy(cmd, arg, sizeof(coco_attestation_report_t)); + } + xc_hypercall_buffer_free(handle, arg); + return rc; +} + int xc_domain_setdebugging(xc_interface *xch, uint32_t domid, unsigned int enable) diff --git a/tools/libs/ctrl/xc_version.c b/tools/libs/ctrl/xc_version.c index 54d1b9296696..008a70e36e0e 100644 --- a/tools/libs/ctrl/xc_version.c +++ b/tools/libs/ctrl/xc_version.c @@ -7,6 +7,7 @@ #include "xc_private.h" #include +#include static int do_xen_version(xc_interface *xch, int cmd, xc_hypercall_buffer_t *dest) @@ -204,3 +205,101 @@ char *xc_xenver_buildid(xc_interface *xch) return res; } + + +int xc_coco_get_platform_certs(xc_interface *handle, coco_platform_certs_t *cmd) +{ + DECLARE_HYPERCALL_BUFFER(coco_platform_certs_t, arg); + int rc; + + arg = xc_hypercall_buffer_alloc(handle, arg, sizeof(*arg)); + if ( arg == NULL ) + return -1; + memcpy(arg, cmd, sizeof(coco_platform_certs_t)); + + rc = xencall2(handle->xcall, __HYPERVISOR_coco_op, XEN_COCO_platform_certs, + HYPERCALL_BUFFER_AS_ARG(arg)); + + if (!rc) { + memcpy(cmd, arg, sizeof(coco_platform_certs_t)); + } + xc_hypercall_buffer_free(handle, arg); + return rc; +} + +int xc_coco_get_csr(xc_interface *handle, coco_certificate_t *cmd) +{ + DECLARE_HYPERCALL_BUFFER(coco_certificate_t, arg); + int rc; + + arg = xc_hypercall_buffer_alloc(handle, arg, sizeof(*arg)); + if ( arg == NULL ) + return -1; + memcpy(arg, cmd, sizeof(coco_certificate_t)); + + rc = xencall2(handle->xcall, __HYPERVISOR_coco_op, XEN_COCO_platform_csr, + HYPERCALL_BUFFER_AS_ARG(arg)); + + if (!rc) { + memcpy(cmd, arg, sizeof(coco_certificate_t)); + } + xc_hypercall_buffer_free(handle, arg); + return rc; +} + +int xc_coco_update(xc_interface *handle, coco_update_t *cmd) +{ + DECLARE_HYPERCALL_BUFFER(coco_update_t, arg); + int rc; + + arg = xc_hypercall_buffer_alloc(handle, arg, sizeof(*arg)); + if ( arg == NULL ) + return -1; + memcpy(arg, cmd, sizeof(coco_update_t)); + + rc = xencall2(handle->xcall, __HYPERVISOR_coco_op, XEN_COCO_platform_update, + HYPERCALL_BUFFER_AS_ARG(arg)); + + if (!rc) { + memcpy(cmd, arg, sizeof(coco_update_t)); + } + xc_hypercall_buffer_free(handle, arg); + return rc; +} +int xc_coco_regen_certificate(xc_interface *handle, coco_certificate_name_t cert) +{ + /* Maybe it's not necessary to add another syscall for that */ + DECLARE_HYPERCALL_BUFFER(coco_certificate_name_t, arg); + int rc; + + arg = xc_hypercall_buffer_alloc(handle, arg, sizeof(*arg)); + if ( arg == NULL ) + return -1; + *arg = cert; + + rc = xencall2(handle->xcall, __HYPERVISOR_coco_op, XEN_COCO_platform_regen_cert, + HYPERCALL_BUFFER_AS_ARG(arg)); + + xc_hypercall_buffer_free(handle, arg); + return rc; +} + +int xc_coco_import_certificate(xc_interface *handle, coco_platform_import_certs_t *cmd) +{ + DECLARE_HYPERCALL_BUFFER(coco_platform_import_certs_t, arg); + int rc; + + arg = xc_hypercall_buffer_alloc(handle, arg, sizeof(*arg)); + if ( arg == NULL ) + return -1; + memcpy(arg, cmd, sizeof(coco_platform_import_certs_t)); + + rc = xencall2(handle->xcall, __HYPERVISOR_coco_op, XEN_COCO_platform_cert_import, + HYPERCALL_BUFFER_AS_ARG(arg)); + + if (!rc) { + memcpy(cmd, arg, sizeof(coco_platform_import_certs_t)); + } + xc_hypercall_buffer_free(handle, arg); + return rc; +} diff --git a/tools/libs/guest/xg_dom_boot.c b/tools/libs/guest/xg_dom_boot.c index d663c8ff322d..4ca6c3dbb00f 100644 --- a/tools/libs/guest/xg_dom_boot.c +++ b/tools/libs/guest/xg_dom_boot.c @@ -192,10 +192,10 @@ int xc_dom_boot_image(struct xc_dom_image *dom) (rc = dom->arch_hooks->setup_pgtables(dom)) != 0 ) return rc; - if ( dom->coco ) + if ( dom->coco ) { /* We need to check if we are actually a SEV-ES guest to set SIF_HVM_GHCB */ dom->use_ghcb = info.arch_config.coco.sev.policy & 0x4; - + } /* start info page */ if ( dom->arch_hooks->start_info ) dom->arch_hooks->start_info(dom); @@ -241,6 +241,12 @@ int xc_dom_boot_image(struct xc_dom_image *dom) /* let the vm run */ if ( (rc = dom->arch_hooks->vcpu(dom)) != 0 ) return rc; + + if (dom->coco) { + /* vcpu needs to be initialized */ + if ( (rc = xg_dom_coco_finish_encrypt(dom->xch, dom)) != 0 ) + return rc; + } xc_dom_unmap_all(dom); return rc; diff --git a/tools/libs/guest/xg_dom_coco.c b/tools/libs/guest/xg_dom_coco.c index f47b59fa4915..8df908657a89 100644 --- a/tools/libs/guest/xg_dom_coco.c +++ b/tools/libs/guest/xg_dom_coco.c @@ -32,4 +32,8 @@ int xg_dom_coco_encrypt_seg(xc_interface *xch, struct xc_dom_image *dom, cmd.count = seg.pages; return xc_coco_prepare_initial_mem(xch, &cmd); +} +int xg_dom_coco_finish_encrypt(xc_interface *xch, struct xc_dom_image *dom) +{ + return xc_coco_finish_initial_mem(xch, dom->guest_domid); } \ No newline at end of file diff --git a/tools/libs/guest/xg_dom_coco.h b/tools/libs/guest/xg_dom_coco.h index eac0fa66e363..7df02e7f8158 100644 --- a/tools/libs/guest/xg_dom_coco.h +++ b/tools/libs/guest/xg_dom_coco.h @@ -26,6 +26,8 @@ int xg_dom_coco_encrypt_seg(xc_interface *xch, struct xc_dom_image *dom, struct xc_dom_seg seg, const char *name); +int xg_dom_coco_finish_encrypt(xc_interface *xch, struct xc_dom_image *dom); + #endif /* XC_DOM_COCO_H */ /* diff --git a/tools/libs/light/Makefile b/tools/libs/light/Makefile index b690d921593d..4e9f0b9f39e1 100644 --- a/tools/libs/light/Makefile +++ b/tools/libs/light/Makefile @@ -27,6 +27,8 @@ OBJS-y += libxl_colo_nic.o else OBJS-y += libxl_no_colo.o endif +# TODO: add something like CONFIG_COCO ? +OBJS-y += libxl_coco.o ACPI_PATH = $(XEN_ROOT)/tools/libacpi DSDT_FILES-$(CONFIG_X86) = dsdt_pvh.c diff --git a/tools/libs/light/libxl_coco.c b/tools/libs/light/libxl_coco.c new file mode 100644 index 000000000000..6f0db9f360c9 --- /dev/null +++ b/tools/libs/light/libxl_coco.c @@ -0,0 +1,203 @@ +#include "libxl_internal.h" +#include "xenctrl.h" +#include +#include +#include + +static int hex_char_to_int(char c) { + if ('0' <= c && c <= '9') return c - '0'; + if ('a' <= c && c <= 'f') return c - 'a' + 10; + if ('A' <= c && c <= 'F') return c - 'A' + 10; + return -1; +} + +int libxl_coco_domain_attestation(libxl_ctx *ctx, uint32_t domid, int file, bool is_mmonce_file, char *mmonce) { + coco_attestation_report_t report; + int rc, r; + + if (is_mmonce_file) { + int datalen = 0; + void *data = NULL; + + r = libxl_read_file_contents(ctx, mmonce, &data, &datalen); + + if (datalen != 16) { + fprintf(stderr, "Error: invalid mmonce length\n"); + return ERROR_INVAL; + } + memcpy(&report.mnonce, data, 16); + free(data); + } else { + if (strnlen(mmonce, 33) != 32) { + fprintf(stderr, "Error: invalid mmonce length\n"); + } + for (int i = 0; i < 16; i++) { + int hi = hex_char_to_int(mmonce[2*i]); + int lo = hex_char_to_int(mmonce[2*i + 1]); + + if (hi < 0 || lo < 0) { + fprintf(stderr, "Error: invalid hex character\n"); + return -1; + } + + report.mnonce[i] = (hi << 4) | lo; + } + + } + + report.domid = domid; + report.len = 0; + + rc = xc_coco_get_attestation(ctx->xch, &report); + + if (!rc) { + size_t written = write(file, &report.sev, report.len); + // the union used does not matter, we use the pointer + if (written != report.len) { + perror("write"); + close(file); + return -1; + } + } + + close(file); + return rc; +} + + +int libxl_coco_platform_certs(libxl_ctx *ctx, char* path) { + int rc; + coco_platform_certs_t certs; + + rc = xc_coco_get_platform_certs(ctx->xch, &certs); + + if (!rc) { + int file = open(path, O_WRONLY | O_CREAT, 0644); + if (!file) { + perror("open:"); + return -1; + } + + size_t written = write(file, &certs.sev, sizeof(certs.sev)); + if (written != sizeof(certs.sev)) { + perror("write:"); + close(file); + return -1; + } + + printf("Platform Version: %d.%d.%d\n", + certs.status.version_major, + certs.status.version_minor, + certs.status.version_build); + + printf("Platform owned %s\n", certs.status.flags & COCO_STATUS_FEATURES_PLATFORM_OWNED ? "True" : "False"); + + for (size_t cpu_n = 0; cpu_n < certs.cpu_number; cpu_n++) { + printf("CPU ID %lu: ", cpu_n); + for (size_t i = 0; i < 64; i++) { + printf("%02X", certs.hwid[i + cpu_n * 64]); + } + printf("\n"); + } + } + + return rc; +} + +int libxl_coco_csr(libxl_ctx *ctx, char* path) { + int rc; + coco_certificate_t cert; + + rc = xc_coco_get_csr(ctx->xch, &cert); + + if (!rc) { + int file = open(path, O_WRONLY | O_CREAT, 0644); + if (!file) { + perror("open:"); + return -1; + } + + size_t written = write(file, &cert.sev, sizeof(cert.sev)); + if (written != sizeof(cert.sev)) { + perror("write:"); + close(file); + return -1; + } + } + return rc; +} + +int libxl_coco_update(libxl_ctx *ctx, char* path) { + int rc = 0; + coco_update_t update; + struct stat st; + if (stat(path, &st) != 0) { + perror("can't stat firmware"); + return -1; + } + DECLARE_HYPERCALL_BUFFER(void, buf); + update.size = st.st_size; + buf = xc_hypercall_buffer_alloc(ctx->xch, buf, update.size); + + int fd = open(path, O_RDONLY); + update.size = read(fd, buf,update.size); + if (update.size == -1 ) { + perror("could not open firmware file"); + return -1; + } + set_xen_guest_handle(update.data, buf); + + rc = xc_coco_update(ctx->xch, &update); + + xc_hypercall_buffer_free(ctx->xch, buf); + + return rc; +} + +int libxl_coco_regen_certificate(libxl_ctx *ctx, char* crt) { + coco_certificate_name_t cert = 0; + size_t i = 0; + COCO_CERTIFICATE_NAME_ARRAY_DEF() + for (; i < sizeof(certs_name) / 8; i++) { + if (strcmp(crt, certs_name[i]) == 0) { + cert = i; + break; + } + } + if (i == (sizeof(certs_name) / 8)) { + printf("Invalid certificate name, not in :\n"); + for (i = 0; i < sizeof(certs_name) / 8; i++) { + printf("%s ", certs_name[i]); + } + puts(""); + return -1; + } + + return xc_coco_regen_certificate(ctx->xch, cert); +} + +int libxl_coco_import_certificate(libxl_ctx *ctx, char *pek, char *crt) { + coco_platform_import_certs_t import; + void *data = NULL; + int rc, datalen = 0; + + rc = libxl_read_file_contents(ctx, crt, &data, &datalen); + if (datalen != sizeof(import.sev.oca)) { + fprintf(stderr, "Error: invalid certificate length\n"); + return ERROR_INVAL; + } + memcpy(&import.sev.oca, data, sizeof(import.sev.oca)); + free(data); + + rc = libxl_read_file_contents(ctx, pek, &data, &datalen); + + if (datalen != sizeof(import.sev.pek)) { + fprintf(stderr, "Error: invalid pek certificate length\n"); + return ERROR_INVAL; + } + memcpy(&import.sev.pek, data, sizeof(import.sev.pek)); + free(data); + + + return xc_coco_import_certificate(ctx->xch, &import); +} diff --git a/tools/libs/light/libxl_create.c b/tools/libs/light/libxl_create.c index d814d4151241..09271f8132de 100644 --- a/tools/libs/light/libxl_create.c +++ b/tools/libs/light/libxl_create.c @@ -688,7 +688,6 @@ int libxl__domain_make(libxl__gc *gc, libxl_domain_config *d_config, } else { local_domid = info->domid; /* May not be valid */ } - ret = xc_domain_create(ctx->xch, &local_domid, &create); if (ret < 0) { /* diff --git a/tools/libs/light/libxl_types.idl b/tools/libs/light/libxl_types.idl index 17cd6b9dd158..6d18222fc6c6 100644 --- a/tools/libs/light/libxl_types.idl +++ b/tools/libs/light/libxl_types.idl @@ -741,6 +741,8 @@ libxl_domain_build_info = Struct("domain_build_info",[ ("fixed_mem_layout", libxl_defbool), ("x2apic_force", libxl_defbool), ("sev_policy", uint32, {'init_val': '~0U'}), + ("sev_session_file", string), + ("sev_cert_file", string), ])), # Alternate p2m is not bound to any architecture or guest type, as it is # supported by x86 HVM and ARM support is planned. diff --git a/tools/libs/light/libxl_x86.c b/tools/libs/light/libxl_x86.c index 76fc4243df59..6e33b6f7a80b 100644 --- a/tools/libs/light/libxl_x86.c +++ b/tools/libs/light/libxl_x86.c @@ -2,6 +2,7 @@ #include "libxl_arch.h" #include #include +#include int libxl__arch_domain_prepare_config(libxl__gc *gc, libxl_domain_config *d_config, @@ -36,6 +37,39 @@ int libxl__arch_domain_prepare_config(libxl__gc *gc, config->arch.coco.sev.flags |= XEN_X86_SEV_POLICY_VALID; config->arch.coco.sev.policy = d_config->b_info.arch_x86.sev_policy; } + + if ( d_config->b_info.arch_x86.sev_session_file && d_config->b_info.arch_x86.sev_cert_file ) + { + DECLARE_HYPERCALL_BUFFER(sev_start_parameters_t, buf); + buf = xc_hypercall_buffer_alloc(gc->owner->xch, buf, sizeof(*buf)); + int rc, fd; + + fd = open(d_config->b_info.arch_x86.sev_session_file, O_RDONLY); + if (fd == -1) { + perror("sev_session_file invalid"); + exit(1); + } + rc = read(fd, &buf->session, sizeof(buf->session)); + if (rc != sizeof(buf->session)) { + perror("sev_session_file invalid"); + exit(1); + } + + fd = open(d_config->b_info.arch_x86.sev_cert_file, O_RDONLY); + if (fd == -1) { + perror("sev_session_file invalid"); + exit(1); + } + rc = read(fd, &buf->crt, sizeof(buf->crt)); + if (rc != sizeof(struct sev_certificate)) { + perror("sev_session_file invalid"); + exit(1); + } + + set_xen_guest_handle(config->arch.coco.sev.sp, buf); + } else { + config->arch.coco.sev.sp.p = 0; + } if (libxl_defbool_val(d_config->b_info.trap_unmapped_accesses)) { LOG(ERROR, "trap_unmapped_accesses is not supported on x86\n"); diff --git a/tools/xl/Makefile b/tools/xl/Makefile index ad577cdd702f..419b5139f815 100644 --- a/tools/xl/Makefile +++ b/tools/xl/Makefile @@ -24,6 +24,7 @@ XL_OBJS += xl_sched.o xl_pci.o xl_vcpu.o xl_cdrom.o xl_mem.o XL_OBJS += xl_info.o xl_console.o xl_misc.o XL_OBJS += xl_vmcontrol.o xl_saverestore.o xl_migrate.o XL_OBJS += xl_vdispl.o xl_vsnd.o xl_vkb.o +XL_OBJS += xl_coco.o $(XL_OBJS): CFLAGS += $(CFLAGS_libxentoollog) $(XL_OBJS): CFLAGS += $(CFLAGS_XL) diff --git a/tools/xl/xl.h b/tools/xl/xl.h index 9000df00de19..333ad9ea767c 100644 --- a/tools/xl/xl.h +++ b/tools/xl/xl.h @@ -217,6 +217,7 @@ int main_psr_mba_set(int argc, char **argv); int main_psr_mba_show(int argc, char **argv); #endif int main_qemu_monitor_command(int argc, char **argv); +int main_coco(int argc, char **argv); void help(const char *command); diff --git a/tools/xl/xl_cmdtable.c b/tools/xl/xl_cmdtable.c index 06a00397184c..9b0e95971949 100644 --- a/tools/xl/xl_cmdtable.c +++ b/tools/xl/xl_cmdtable.c @@ -641,6 +641,11 @@ const struct cmd_spec cmd_table[] = { "Issue a qemu monitor command to the device model of a domain", " ", }, + { "coco", + &main_coco, 0, 0, + "All subcommand for coco domains", + " ", + }, #ifdef LIBXL_HAVE_DT_OVERLAY { "dt-overlay", &main_dt_overlay, 0, 1, diff --git a/tools/xl/xl_coco.c b/tools/xl/xl_coco.c new file mode 100644 index 000000000000..31ac2e3a90d3 --- /dev/null +++ b/tools/xl/xl_coco.c @@ -0,0 +1,247 @@ +#include +#include +#include + +#include +#include +#include +#include + +#include "xl.h" +#include "xl_utils.h" + +static int main_coco_attestation(int argc, char **argv); +static int main_coco_get_platform_certs(int argc, char **argv); +static int main_coco_certificate_signing_request(int argc, char **argv); +static int main_coco_certificate_import(int argc, char **argv); +static int main_coco_regen_certificate(int argc, char **argv); +static int main_coco_update_firmware(int argc, char **argv); + +static const struct cmd_spec coco_cmd_table[] = { + { "attestation", + &main_coco_attestation, 0, 0, + "Get an attestation for a domain", + " ", + }, + { "platform", + &main_coco_get_platform_certs, 0, 0, + "Get the platform public key and identification", + " ", + }, + { "csr", + &main_coco_certificate_signing_request, 0, 0, + "Certificate Signing Request", + " ", + }, + { "import", + &main_coco_certificate_import, 0, 0, + "Import signed certificate", + " ", + }, + { "regen", + &main_coco_regen_certificate, 0, 0, + "Regenerate the platform keys", + " ", + }, + { "update", + &main_coco_update_firmware, 0, 0, + "Update the platform firmware", + "", + }, +}; + +static int main_coco_update_firmware(int argc, char **argv) { + int opt, rc; + char *path = NULL; + + SWITCH_FOREACH_OPT(opt, "", NULL, "coco update", 1) { + /* No options */ + } + + path = argv[optind]; + + rc = libxl_coco_update(ctx, path); + + return rc; +} + +static int main_coco_certificate_signing_request(int argc, char **argv) { + int opt, rc; + char *path = "to_sign.bin"; + static struct option opts[] = { + {"file", 1, 0, 'f'}, + COMMON_LONG_OPTS + }; + + SWITCH_FOREACH_OPT(opt, "f:", opts, "coco csr", 0) { + case 'f': + path = optarg; + break; + } + + rc = libxl_coco_csr(ctx, path); + + return rc; +} + +static int main_coco_certificate_import(int argc, char **argv) { + int opt, rc; + char *crt = "crt.bin"; + char *pek = "pek.bin"; + + static struct option opts[] = { + {"crt", 1, 0, 'c'}, + {"pek", 1, 0, 'p'}, + COMMON_LONG_OPTS + }; + + SWITCH_FOREACH_OPT(opt, "c:p:", opts, "coco import", 0) { + case 'c': + crt = optarg; + break; + case 'p': + pek = optarg; + break; + } + + rc = libxl_coco_import_certificate(ctx, pek, crt); + + return rc; + /* platform get status + -> if init && owned + -> ERR : ask do a pek gen + -> if init && !owned + -> Perform cert import + -> else + -> Guest running / init error + */ +} +static int main_coco_regen_certificate(int argc, char **argv) { + int opt, rc; + + SWITCH_FOREACH_OPT(opt, "", NULL, "coco regen", 1) { + /* No options */ + } + + rc = libxl_coco_regen_certificate(ctx, argv[optind]); + + return rc; +} + +static int main_coco_attestation(int argc, char **argv) { + int rc; + int dst_file = 1; + char * mmonce = NULL; + uint32_t domid; + bool is_mmonce_file = false; + + int opt; + static struct option opts[] = { + {"file", 1, 0, 'f'}, + {"print", 0, 0, 'p'}, + {"mmonce", 1, 0, 'm'}, + {"mmonce-file", 1, 0, 'n'}, + COMMON_LONG_OPTS + }; + + SWITCH_FOREACH_OPT(opt, "f:pm:n:", opts, "coco attestation", 1) { + case 'p': + dst_file = 1; + break; + case 'f': + dst_file = open(optarg, O_WRONLY | O_CREAT, 0644); + if (!dst_file) { + perror("open"); + return -1; + } + break; + case 'm': + mmonce = optarg; + is_mmonce_file = false; + break; + case 'n': + mmonce = optarg; + is_mmonce_file = true; + break; + } + + if (mmonce == NULL) { + fprintf(stderr, "Error: no mmonce provided\n"); + return 1; + } + + domid = find_domain(argv[optind]); + + rc = libxl_coco_domain_attestation(ctx, domid, dst_file, is_mmonce_file, mmonce); + + return rc; +} + +static int main_coco_get_platform_certs(int argc, char **argv) { + int opt, rc; + char *path = "pdh.bin"; + static struct option opts[] = { + {"file", 1, 0, 'f'}, + COMMON_LONG_OPTS + }; + + SWITCH_FOREACH_OPT(opt, "f:", opts, "coco platform", 0) { + case 'f': + path = optarg; + break; + } + + rc = libxl_coco_platform_certs(ctx, path); + + return rc; +} + +static const int coco_cmdtable_len = ARRAY_SIZE(coco_cmd_table); + +/* Look up a command in the table, allowing unambiguous truncation */ +static const struct cmd_spec *coco_cmdtable_lookup(const char *s) +{ + const struct cmd_spec *cmd = NULL; + size_t len; + int i, count = 0; + + if (!s) + return NULL; + len = strlen(s); + for (i = 0; i < coco_cmdtable_len; i++) { + if (!strncmp(s, coco_cmd_table[i].cmd_name, len)) { + cmd = &coco_cmd_table[i]; + /* Take an exact match, even if it also prefixes another command */ + if (len == strlen(cmd->cmd_name)) + return cmd; + count++; + } + } + return (count == 1) ? cmd : NULL; +} + +int main_coco(int argc, char **argv) { + int opt, rc; + char *cmd; + const struct cmd_spec *cspec; + + SWITCH_FOREACH_OPT(opt, "", NULL, "coco", 1) { + /* No options */ + } + cmd = argv[optind]; + + /* Reset options for per-command use of getopt. */ + argv += optind; + argc -= optind; + optind = 1; + + cspec = coco_cmdtable_lookup(cmd); + if (cspec) { + rc = cspec->cmd_impl(argc, argv); + } else { + fprintf(stderr, "command not implemented\n"); + rc = EXIT_FAILURE; + } + + return rc; +} diff --git a/tools/xl/xl_parse.c b/tools/xl/xl_parse.c index e0184c46bd7d..b8b1809a65b5 100644 --- a/tools/xl/xl_parse.c +++ b/tools/xl/xl_parse.c @@ -1753,6 +1753,8 @@ void parse_config_data(const char *config_source, if (!xlu_cfg_get_long(config, "max_event_channels", &l, 0)) b_info->event_channels = l; + xlu_cfg_replace_string (config, "sev_cert_file", &b_info->arch_x86.sev_cert_file, 0); + xlu_cfg_replace_string (config, "sev_session_file", &b_info->arch_x86.sev_session_file, 0); xlu_cfg_replace_string (config, "kernel", &b_info->kernel, 0); xlu_cfg_replace_string (config, "ramdisk", &b_info->ramdisk, 0); xlu_cfg_replace_string (config, "device_tree", &b_info->device_tree, 0); diff --git a/xen/arch/x86/coco/sev.c b/xen/arch/x86/coco/sev.c index 6356bff7849c..4718f93de9a4 100644 --- a/xen/arch/x86/coco/sev.c +++ b/xen/arch/x86/coco/sev.c @@ -6,7 +6,9 @@ #include #include +#include #include +#include #include #include @@ -30,35 +32,51 @@ static int sev_domain_initialise(struct domain *d) unsigned int psp_ret = 0; long rc = 0; + if (unlikely(d->arch.hvm.svm.sev.status != SEV_GUEST_UNINIT)) { + /* This should never happen */ + printk(XENLOG_ERR "sev: Trying to init an already init guest\n"); + } + if ( sev_policy.rsvd0 || sev_policy.rsvd1 ) { printk(XENLOG_ERR "sev: Reserved bits set in policy\n"); return -EINVAL; } - + if ( sev_policy.es && !cpu_has_sev_es ) { printk(XENLOG_ERR "sev: SEV-ES is not supported\n"); return -EINVAL; } - + if ( !(d->arch.emulation_flags & XEN_X86_EMU_FORCE_X2APIC) ) { printk(XENLOG_ERR "sev: Guest must have forced x2apic\n"); return -EINVAL; } - + sd_ls.handle = 0; /* generate new one */ sd_ls.policy = sev_policy; - sd_ls.dh_cert_address = 0; /* do not DH stuff */ + if (d->arch.hvm.svm.sev.owner_crt && d->arch.hvm.svm.sev.session) { + sd_ls.dh_cert_address = virt_to_maddr(d->arch.hvm.svm.sev.owner_crt); + sd_ls.dh_cert_len = sizeof(*d->arch.hvm.svm.sev.owner_crt); + sd_ls.session_address = virt_to_maddr(d->arch.hvm.svm.sev.session); + sd_ls.session_len = sizeof(*d->arch.hvm.svm.sev.session); + } else { + sd_ls.dh_cert_address = 0; /* do not DH stuff */ + } rc = sev_do_cmd(SEV_CMD_LAUNCH_START, (void *)(&sd_ls), &psp_ret, true); - if ( rc ) + if ( rc || psp_ret ) { printk(XENLOG_ERR "asp: failed to LAUNCH_START domain(%d): psp_ret %u\n", d->domain_id, psp_ret); return rc; } + xfree(d->arch.hvm.svm.sev.owner_crt); + xfree(d->arch.hvm.svm.sev.session); + d->arch.hvm.svm.sev.owner_crt = NULL; + d->arch.hvm.svm.sev.session = NULL; sd_a.handle = sd_ls.handle; sd_a.asid = d->arch.hvm.asid.asid; @@ -72,6 +90,7 @@ static int sev_domain_initialise(struct domain *d) } d->arch.hvm.svm.sev.asp_handle = sd_ls.handle; + d->arch.hvm.svm.sev.status = SEV_GUEST_LUPDATE; return 0; } @@ -85,6 +104,11 @@ static int sev_domain_prepare_initial_mem(struct domain *d, gfn_t gfn, size_t co mfn_t mfn = INVALID_MFN, mfn_base = INVALID_MFN; size_t segment_size = 0; + if (unlikely(d->arch.hvm.svm.sev.status != SEV_GUEST_LUPDATE)) { + /* This should never happen */ + printk(XENLOG_ERR "sev: Trying to update a guest in wrong state\n"); + } + flush_all(FLUSH_CACHE_WRITEBACK); do { @@ -105,7 +129,7 @@ static int sev_domain_prepare_initial_mem(struct domain *d, gfn_t gfn, size_t co printk(XENLOG_DEBUG "asp: LAUNCH_UPDATE_DATA d%hu: base=%"PRI_xen_pfn", size=%zx\n", d->domain_id, mfn_x(mfn_base), segment_size); - + sd_lud.reserved = 0; sd_lud.handle = d->arch.hvm.svm.sev.asp_handle; sd_lud.address = mfn_x(mfn_base) << PAGE_SHIFT; @@ -123,7 +147,7 @@ static int sev_domain_prepare_initial_mem(struct domain *d, gfn_t gfn, size_t co mfn_base = mfn; segment_size = 0; } - } + } gfn = gfn_add(gfn, 1); segment_size++; @@ -148,13 +172,16 @@ static int sev_domain_prepare_initial_mem(struct domain *d, gfn_t gfn, size_t co return rc; } -static int sev_domain_creation_finished(struct domain *d) -{ +static int sev_domain_finish_memory(struct domain *d) { struct sev_data_launch_measure sd_lm; - struct sev_data_launch_finish sd_lf; - unsigned int psp_ret; + unsigned int psp_ret = 0; long rc = 0; + if (unlikely(d->arch.hvm.svm.sev.status != SEV_GUEST_LUPDATE)) { + /* This should never happen */ + printk(XENLOG_ERR "sev: Trying to measure a guest in wrong state\n"); + } + sd_lm.handle = d->arch.hvm.svm.sev.asp_handle; sd_lm.address = virt_to_maddr(d->arch.hvm.svm.sev.measure); sd_lm.len = sizeof(d->arch.hvm.svm.sev.measure); @@ -164,13 +191,31 @@ static int sev_domain_creation_finished(struct domain *d) if ( rc ) { printk(XENLOG_ERR "asp: failed to LAUNCH_MEASURE for d%hu: psp_ret %u, rc %ld\n", - d->domain_id, psp_ret, rc); - + d->domain_id, psp_ret, rc); + if (psp_ret == SEV_RET_INVALID_LEN) printk(XENLOG_ERR "asp: Expected %"PRIu32" bytes\n", sd_lm.len); return rc; } + printk(XENLOG_DEBUG"asp: LAUNCH_MEASURE for d%hu: \n", d->domain_id); + d->arch.hvm.svm.sev.status = SEV_GUEST_LSECRET; + d->arch.hvm.svm.sev.measure_len = sd_lm.len; + + return 0; +} + +static int sev_domain_creation_finished(struct domain *d) +{ + struct sev_data_launch_finish sd_lf; + unsigned int psp_ret; + long rc = 0; + + if (unlikely(d->arch.hvm.svm.sev.status != SEV_GUEST_LSECRET)) { + /* This should never happen */ + printk(XENLOG_ERR "sev: Trying to finish a guest in wrong state\n"); + } + sd_lf.handle = d->arch.hvm.svm.sev.asp_handle; rc = sev_do_cmd(SEV_CMD_LAUNCH_FINISH, (void *)(&sd_lf), &psp_ret, true); @@ -180,8 +225,7 @@ static int sev_domain_creation_finished(struct domain *d) d->domain_id, psp_ret, rc); return rc; } - - d->arch.hvm.svm.sev.measure_len = sd_lm.len; + d->arch.hvm.svm.sev.status = SEV_GUEST_RUNNING; return 0; } @@ -248,6 +292,7 @@ static void sev_domain_destroy(struct domain *d) } d->arch.hvm.svm.sev.asp_handle = 0; + d->arch.hvm.svm.sev.status = SEV_GUEST_UNINIT; } static int sev_asid_alloc(struct domain *d, struct hvm_asid *asid) @@ -258,10 +303,52 @@ static int sev_asid_alloc(struct domain *d, struct hvm_asid *asid) return hvm_asid_alloc_range(asid, asid_min, asid_max); } +static int sev_attestation_report(struct domain *d, + struct coco_attestation_report *args) { + struct sev_data_attestation_report report; + unsigned int psp_ret = 0; + int rc = 0; + + + if (unlikely(!(d->arch.hvm.svm.sev.status == SEV_GUEST_LSECRET || + d->arch.hvm.svm.sev.status == SEV_GUEST_SENT || + d->arch.hvm.svm.sev.status == SEV_GUEST_SUPDATE || + d->arch.hvm.svm.sev.status == SEV_GUEST_RUNNING))) { + printk(XENLOG_ERR "sev: Trying to get an attestation for a guest in wrong state\n"); + } + + report.handle = d->arch.hvm.svm.sev.asp_handle; + report.len = sizeof(struct sev_attestation_report_response); + args->len = sizeof(struct sev_attestation_report_response); + report.reserved = 0; + report.address = (uint64_t) virt_to_maddr(&args->sev); + for (size_t i =0; i < 16; i++) { // or memset ? + report.mnonce[i] = args->mnonce[i]; + } + + printk(XENLOG_ERR + "asp: ATTESTATION_REPORT d%d: size=%u\n", d->domain_id, args->len); + + rc = sev_do_cmd(SEV_CMD_ATTESTATION_REPORT, (void *)(&report), + &psp_ret, true); + + if (!rc && !psp_ret) { + return 0; + } + printk(XENLOG_ERR "asp: failed to ATTESTATION for d%hu: psp_ret %d\n", + d->domain_id, psp_ret); + + return rc; +} + + + static struct coco_domain_ops sev_domain_ops = { .prepare_initial_mem = sev_domain_prepare_initial_mem, .domain_initialise = sev_domain_initialise, .domain_creation_finished = sev_domain_creation_finished, + .domain_memory_finished = sev_domain_finish_memory, + .domain_attestation_report = sev_attestation_report, .domain_destroy = sev_domain_destroy, .asid_alloc = sev_asid_alloc, }; @@ -274,13 +361,18 @@ static int sev_es_asid_alloc(struct domain *d, struct hvm_asid *asid) return hvm_asid_alloc_range(asid, 1, raw_cpu_policy.extd.min_no_es_asid - 1); } -static int sev_es_domain_creation_finished(struct domain *d) +static int sev_es_domain_vcpu_initialise(struct domain *d) { struct vcpu *v; struct sev_data_launch_update_vmsa sd_luv = {}; sd_luv.handle = d->arch.hvm.svm.sev.asp_handle; sd_luv.reserved = 0; + if (unlikely(d->arch.hvm.svm.sev.status != SEV_GUEST_LUPDATE)) { + /* This should never happen */ + printk(XENLOG_ERR "sev: Trying to update a guest in wrong state. Stage : %d\n", d->arch.hvm.svm.sev.status); + } + for_each_vcpu ( d, v ) { int rc; @@ -288,7 +380,7 @@ static int sev_es_domain_creation_finished(struct domain *d) struct vmcb_struct *vmcb = v->arch.hvm.svm.vmcb; struct cpu_user_regs *regs = &v->arch.user_regs; void *vmsa; - + /* Stash guest CPU registers into VMCB including VMSA fields */ vmcb->rax = regs->rax; vmcb->vmsa_regs.rbx = regs->rbx; @@ -320,30 +412,36 @@ static int sev_es_domain_creation_finished(struct domain *d) sizeof(struct vmcb_struct) - offsetof(struct vmcb_struct, vmsa_start)); cache_flush(vmsa, PAGE_SIZE); unmap_domain_page(vmsa); - + /* Clear VMSA-specific fields from VMCB (marked as reserved). */ memset(&vmcb->vmsa_regs, 0, sizeof(vmcb->vmsa_regs)); sd_luv.address = page_to_maddr(v->arch.hvm.svm.sev.vmsa_page); sd_luv.len = PAGE_SIZE_4K; - + rc = sev_do_cmd(SEV_CMD_LAUNCH_UPDATE_VMSA, (void *)(&sd_luv), &psp_ret, true); if ( rc ) { - printk(XENLOG_ERR "asp: failed to LAUNCH_UPDATE_VMSA d%huv%d: err %u\n", + printk(XENLOG_ERR "asp: failed to LAUNCH_UPDATE_VMSA d%huv%d: psp_ret %u\n", d->domain_id, v->vcpu_id, psp_ret); return rc; } + + printk(XENLOG_DEBUG "asp: LAUNCH_UPDATE_VMSA d%huv%d:\n", + d->domain_id, v->vcpu_id); } - return sev_domain_creation_finished(d); + return 0; } static struct coco_domain_ops sev_es_domain_ops = { .prepare_initial_mem = sev_domain_prepare_initial_mem, .domain_initialise = sev_domain_initialise, - .domain_creation_finished = sev_es_domain_creation_finished, + .domain_vcpu_initialise = sev_es_domain_vcpu_initialise, + .domain_creation_finished = sev_domain_creation_finished, + .domain_memory_finished = sev_domain_finish_memory, .domain_destroy = sev_domain_destroy, + .domain_attestation_report = sev_attestation_report, .asid_alloc = sev_es_asid_alloc, .show_execution_state = sev_vmsa_dump, }; @@ -355,7 +453,7 @@ static int sev_init(void) if ( WARN_ON(!cpu_has_sme || !cpu_has_sev) ) return -ENOSYS; - /* AMD SME and SmmLock are required for SEV. */ + /* AMD SME and SmmLock are required for SEV. */ rdmsrl(MSR_K8_SYSCFG, syscfg); if ( !(syscfg & SYSCFG_MEM_ENCRYPT) ) @@ -365,7 +463,7 @@ static int sev_init(void) } rdmsrl(MSR_K8_HWCR, hwcr); - + if ( !(hwcr & K8_HWCR_SMM_LOCK) ) { printk(XENLOG_ERR "sev: SMM Lock is not enabled\n"); @@ -392,7 +490,8 @@ static int sev_init(void) static int sev_get_platform_status(struct coco_platform_status *status) { status->platform = COCO_PLATFORM_amd_sev; - + /* cannot call do_sev_cmd, platform isn't initialized yet + so the structure is missing information like version... */ if ( cpu_has_sev_es ) status->platform_flags |= COCO_PLATFORM_FLAG_sev_es; @@ -401,23 +500,175 @@ static int sev_get_platform_status(struct coco_platform_status *status) return 0; } +static int sev_get_platform_certs(struct coco_platform_certs *certs) { + int rc; + unsigned int psp_ret = 0; + struct sev_data_pdh_cert_export pdh_cert_export; + struct sev_user_data_status status; + struct sev_data_get_id get_id; + + + rc = sev_do_cmd(SEV_CMD_PLATFORM_STATUS, (void *)(&status), &psp_ret, true); + if ( rc || psp_ret ) + { + printk(XENLOG_ERR "asp: failed to PLATFORM_STATUS: rc %u psp_ret %u\n", rc, psp_ret); + return rc; + } + certs->status = platform_status; // flags + certs->status.version_major = status.api_major; + certs->status.version_minor = status.api_minor; + certs->status.version_build = status.build; + certs->status.flags = certs->status.flags | + (status.flags & 0x1) ? COCO_STATUS_FEATURES_PLATFORM_OWNED : 0; + + if (status.api_major > 1 || status.api_minor > 15) { + // SEV GET_ID is available from SEV API v0.16 and up + get_id.address = (uint64_t) virt_to_maddr(&certs->hwid); + get_id.len = sizeof(certs->hwid); + + rc = sev_do_cmd(SEV_CMD_GET_ID, (void *)(&get_id), &psp_ret, true); + if ( rc || psp_ret ) + { + printk(XENLOG_ERR "asp: failed to GET_ID: rc %u psp_ret %u\n", rc, psp_ret); + return rc; + } + certs->cpu_number = nr_sockets; + } + + pdh_cert_export.pdh_cert_address = (uint64_t) virt_to_maddr(&certs->sev.pdh); + pdh_cert_export.pdh_cert_len = sizeof(certs->sev.pdh); + + /* PSP needs contiguous memory for the 3 certificates */ + pdh_cert_export.cert_chain_address = (uint64_t) virt_to_maddr(&certs->sev.pek); + pdh_cert_export.cert_chain_len = sizeof(certs->sev.pek) * 3; + pdh_cert_export.reserved = 0; + + rc = sev_do_cmd(SEV_CMD_PDH_CERT_EXPORT, (void *)(&pdh_cert_export), &psp_ret, true); + if ( rc || psp_ret ) + { + printk(XENLOG_ERR "asp: failed to PDH_CERT_EXPORT: rc %u psp_ret %u\n", rc, psp_ret); + return rc; + } + return 0; +} + +static int sev_get_csr(coco_certificate_t *cert) { + struct sev_data_pek_csr arg; + struct sev_certificate *c = &(cert->sev); + unsigned int psp_ret = 0; + int rc = 0; + + arg.len = sizeof(cert->sev); + arg.address = (uint64_t) virt_to_maddr(c); + + rc = sev_do_cmd(SEV_CMD_PEK_CSR, (void *)(&arg), + &psp_ret, true); + + if (rc || psp_ret) { + printk(XENLOG_ERR "asp: PEK_CSR: rc %d psp %x size=%u\n",rc, psp_ret, arg.len); + return rc; + } + return 0; +} + +static int sev_regen_certificate(enum coco_certificate_name cert) { + int rc; + unsigned int psp_ret; + + COCO_CERTIFICATE_NAME_ARRAY_DEF() + + switch (cert) { + case sev_pek: { + rc = sev_do_cmd(SEV_CMD_PEK_GEN, NULL, &psp_ret, true); + break; + } + case sev_pdh:{ + rc = sev_do_cmd(SEV_CMD_PDH_GEN, NULL, &psp_ret, true); + break; + } + default: + printk(XENLOG_ERR"sev: Invalid certificate"); + return -EINVAL; + } + if (rc || psp_ret) { + printk(XENLOG_ERR "sev: regen certificate %d failed: rc %d psp %x \n", cert, rc, psp_ret); + switch (psp_ret) { + case SEV_RET_INVALID_PLATFORM_STATE: + printk(XENLOG_ERR "asp: the platform is not in the right state," + "no guest should run and the platform must be init\n"); + break; + } + } else { + printk(XENLOG_ERR "sev: %s certificate regenerate\n", certs_name[cert]); + } + return rc; +} + +static int sev_import_certificate(coco_platform_import_certs_t *certs) { + int rc; + unsigned int psp_ret = 0; + struct sev_data_pek_cert_import arg; + arg.oca_cert_address = virt_to_maddr(&(certs->sev.oca)); + arg.oca_cert_len = sizeof(certs->sev.oca); + arg.pek_cert_address = virt_to_maddr(&(certs->sev.pek)); + arg.pek_cert_len = sizeof(certs->sev.pek); + + rc = sev_do_cmd(SEV_CMD_PEK_CERT_IMPORT, &arg, &psp_ret, true); + + if (rc || psp_ret) { + printk(XENLOG_ERR "asp: SEV_CMD_PEK_CERT_IMPORT: rc %d psp %x \n", rc, psp_ret); + switch (psp_ret) { + case SEV_RET_ALREADY_OWNED: + printk(XENLOG_ERR "asp: the platform is already owned, regenerate the certificate to own it\n"); + break; + } + } + return rc; +} + +static int sev_platform_update(void* firmware, int len) { + int rc; + unsigned int psp_ret = 0; + + rc = sev_do_cmd(SEV_CMD_SHUTDOWN, NULL, &psp_ret, true); + if (rc || psp_ret) { + printk(XENLOG_ERR "asp: SEV_CMD_SHUTDOWN: rc %d psp %x \n", rc, psp_ret); + printk(XENLOG_ERR "sev: can't update, is there running guest ?\n"); + return rc; + } + rc = sp_update_firmware(firmware, len, &psp_ret); + + return rc; +} + static struct coco_domain_ops *sev_get_domain_ops(struct domain *d, const struct xen_domctl_createdomain *config) { /* We need to set a valid policy for the initialization. */ union sev_guest_policy *sev_policy = &d->arch.hvm.svm.sev.asp_policy; - if ( config->arch.coco.sev.flags & XEN_X86_SEV_POLICY_VALID ) - sev_policy->raw = (uint32_t)config->arch.coco.sev.policy; + if (config->arch.coco.sev.flags & XEN_X86_SEV_POLICY_VALID ) { + sev_policy->raw = config->arch.coco.sev.policy; + } else - /* Use a reasonable default policy */ + // Use a reasonable default policy *sev_policy = (union sev_guest_policy){ .no_key_sharing = true, .no_debug = true, .no_send = true, /* To change when SEV live migration is something */ .es = cpu_has_sev_es, /* Use SEV-ES if available */ }; + if (config->arch.coco.sev.sp.p) { + sev_start_parameters_t sp; + if ( copy_from_guest(&sp, config->arch.coco.sev.sp, 1) ) + goto out; + d->arch.hvm.svm.sev.owner_crt = xmalloc(struct sev_certificate); + d->arch.hvm.svm.sev.session = xmalloc(struct sev_session); + memcpy(d->arch.hvm.svm.sev.owner_crt, sp.crt, sizeof(sp.crt)); + memcpy(d->arch.hvm.svm.sev.session, sp.session, sizeof(sp.session)); + } +out: return sev_policy->es ? &sev_es_domain_ops : &sev_domain_ops; } @@ -425,6 +676,11 @@ struct coco_ops sev_coco_ops = { .name = "SEV", .init = sev_init, .get_platform_status = sev_get_platform_status, + .get_platform_certs = sev_get_platform_certs, + .get_certificate_signing_request = sev_get_csr, + .import_certificates = sev_import_certificate, + .update_platform = sev_platform_update, + .regen_platform_cert = sev_regen_certificate, .get_domain_ops = sev_get_domain_ops, }; diff --git a/xen/arch/x86/include/asm/coco.h b/xen/arch/x86/include/asm/coco.h index 93a203f00738..b1b1e6c10aaa 100644 --- a/xen/arch/x86/include/asm/coco.h +++ b/xen/arch/x86/include/asm/coco.h @@ -5,4 +5,4 @@ extern struct coco_ops sev_coco_ops; -#endif /* X86_COCO_H */ \ No newline at end of file +#endif /* X86_COCO_H */ diff --git a/xen/arch/x86/include/asm/hvm/svm/svm.h b/xen/arch/x86/include/asm/hvm/svm/svm.h index fac05226a414..712fda830d25 100644 --- a/xen/arch/x86/include/asm/hvm/svm/svm.h +++ b/xen/arch/x86/include/asm/hvm/svm/svm.h @@ -9,8 +9,9 @@ #ifndef __ASM_X86_HVM_SVM_H__ #define __ASM_X86_HVM_SVM_H__ -#include #include +#include +#include void svm_asid_init(void); void svm_vcpu_assign_asid(struct vcpu *v); @@ -30,11 +31,14 @@ bool svm_load_segs(unsigned int ldt_ents, unsigned long ldt_base, unsigned long gs_shadow); struct sev_state { + enum sev_guest_status status; uint32_t asp_handle; union sev_guest_policy asp_policy; - uint8_t measure[96]; - uint32_t measure_len; /* 96 bytes */ + uint8_t measure[48]; + uint32_t measure_len; /* 48 bytes */ unsigned long flags; + struct sev_certificate *owner_crt; + struct sev_session *session; }; struct svm_domain { diff --git a/xen/arch/x86/include/asm/psp-sev.h b/xen/arch/x86/include/asm/psp-sev.h index 41fe9f1b4cd7..7aaad9968c95 100644 --- a/xen/arch/x86/include/asm/psp-sev.h +++ b/xen/arch/x86/include/asm/psp-sev.h @@ -68,6 +68,19 @@ enum sev_cmd { SEV_CMD_MAX, }; +/** + * Sev platform guest status + */ +enum sev_guest_status { + SEV_GUEST_UNINIT = 0, + SEV_GUEST_LUPDATE, + SEV_GUEST_LSECRET, + SEV_GUEST_RUNNING, + SEV_GUEST_SUPDATE, + SEV_GUEST_RUPDATE, + SEV_GUEST_SENT +}; + /** * struct sev_data_init - INIT command parameters * @@ -676,4 +689,6 @@ struct sev_user_data_get_id2 { extern int sev_do_cmd(int cmd, void *data, unsigned int *psp_ret, bool poll); +extern int sp_update_firmware(void *firmware, int len, unsigned int *psp_ret); + #endif /* __PSP_SEV_H__ */ diff --git a/xen/common/coco.c b/xen/common/coco.c index 99e967c042df..c64bd4ad334f 100644 --- a/xen/common/coco.c +++ b/xen/common/coco.c @@ -2,7 +2,10 @@ /* * General confidential computing functions. */ - + +#include "xen/config.h" +#include "xen/lib.h" +#include "xen/xmalloc.h" #include #include #include @@ -101,6 +104,81 @@ long coco_op_prepare_initial_mem(struct coco_prepare_initial_mem arg) put_domain(d); return rc; } +long coco_op_finish_initial_mem(domid_t domid) +{ + long rc = 0; + struct domain *d = get_domain_by_id(domid); + + if ( !d ) + return -ENOENT; + + if ( !is_coco_domain(d) ) + { + rc = -EOPNOTSUPP; + goto out; + } + + rc = coco_domain_vcpu_initialise(d); + if (rc) + goto out; + rc = coco_domain_memory_finished(d); + +out: + put_domain(d); + return rc; +} + +static long coco_op_get_attestation_report(coco_attestation_report_t *report) { + struct domain *d; + int rc; + + d = get_domain_by_id(report->domid); + + if (!d) + return -ENOENT; + + if (!is_coco_domain(d)) + return -EOPNOTSUPP; + + if (!d->coco_ops || !d->coco_ops->domain_attestation_report) + return -EOPNOTSUPP; + + rc = d->coco_ops->domain_attestation_report(d, report); + + return rc; +} + +static long coco_op_certs(coco_platform_certs_t *certs) { + if (coco_ops && coco_ops->get_platform_certs) { + return coco_ops->get_platform_certs(certs); + } + return -EOPNOTSUPP; +} +static long coco_op_csr(coco_certificate_t *cert) { + if (coco_ops && coco_ops->get_certificate_signing_request) { + return coco_ops->get_certificate_signing_request(cert); + } + return -EOPNOTSUPP; +} +static long coco_op_regen_platform_cert(coco_certificate_name_t cert) { + if (coco_ops && coco_ops->regen_platform_cert) { + return coco_ops->regen_platform_cert(cert); + } + return -EOPNOTSUPP; +} + +static long coco_op_import_certificate(coco_platform_import_certs_t *cert) { + if (coco_ops && coco_ops->import_certificates) { + return coco_ops->import_certificates(cert); + } + return -EOPNOTSUPP; +} +static long coco_op_update(void *firmware, int len) { + if (coco_ops && coco_ops->update_platform) { + return coco_ops->update_platform(firmware, len); + } + return -EOPNOTSUPP; +} long do_coco_op(unsigned int cmd, XEN_GUEST_HANDLE_PARAM(void) arg) { @@ -116,7 +194,6 @@ long do_coco_op(unsigned int cmd, XEN_GUEST_HANDLE_PARAM(void) arg) return 0; } - case XEN_COCO_prepare_initial_mem: { struct coco_prepare_initial_mem prepare_initial_mem; @@ -126,7 +203,104 @@ long do_coco_op(unsigned int cmd, XEN_GUEST_HANDLE_PARAM(void) arg) return coco_op_prepare_initial_mem(prepare_initial_mem); } + case XEN_COCO_finish_initial_mem: + { + domid_t domid; + + if ( copy_from_guest(&domid, arg, 1) ) + return -EFAULT; + + printk(XENLOG_DEBUG"%s: called\n", __func__); + + + return coco_op_finish_initial_mem(domid); + } + case XEN_COCO_attestation_report: + { + coco_attestation_report_t report; + int rc = 0; + + if ( copy_from_guest(&report, arg, 1) ) + return -EFAULT; + + rc = coco_op_get_attestation_report(&report); + if (rc) + return rc; + + if (copy_to_guest(arg, &report, 1)) + return -EFAULT; + + return 0; + } + case XEN_COCO_platform_certs: + { + coco_platform_certs_t *certs = xmalloc(coco_platform_certs_t); + int rc = 0; + + if ( copy_from_guest(certs, arg, 1) ) + return -EFAULT; + + if (!certs){ + printk(XENLOG_ERR"%s: could not malloc\n", __func__); + return -ENOSPC; + } + + rc = coco_op_certs(certs); + + if (copy_to_guest(arg, certs, 1)) + return -EFAULT; + + xfree(certs); + return rc; + } + case XEN_COCO_platform_csr: + { + coco_certificate_t cert; + int rc = 0; + + if ( copy_from_guest(&cert, arg, 1) ) + return -EFAULT; + + rc = coco_op_csr(&cert); + + if (copy_to_guest(arg, &cert, 1)) + return -EFAULT; + + return rc; + } + case XEN_COCO_platform_regen_cert: + { + coco_certificate_name_t cert; + + if ( copy_from_guest(&cert, arg, 1) ) + return -EFAULT; + return coco_op_regen_platform_cert(cert); + } + case XEN_COCO_platform_cert_import: + { + coco_platform_import_certs_t cert; + if ( copy_from_guest(&cert, arg, 1) ) + return -EFAULT; + return coco_op_import_certificate(&cert); + } + case XEN_COCO_platform_update: + { + coco_update_t update; + void *data; + int rc; + + if ( copy_from_guest(&update, arg, 1) ) + return -EFAULT; + data = _xmalloc(update.size, __alignof__(update.size)); + if ( copy_from_guest(data, update.data, update.size) ) + return -EFAULT; + rc = coco_op_update(data, update.size); + + xfree(data); + return rc; + } + default: return -ENOSYS; } @@ -138,4 +312,4 @@ long do_sev_console_op(unsigned long c) return 0; } -__initcall(coco_init); \ No newline at end of file +__initcall(coco_init); diff --git a/xen/common/domain.c b/xen/common/domain.c index 3392fbe3c0c3..3914f82daf5b 100644 --- a/xen/common/domain.c +++ b/xen/common/domain.c @@ -758,7 +758,7 @@ static int sanitise_domain_config(struct xen_domctl_createdomain *config) dprintk(XENLOG_INFO, "COCO is not available\n"); return -EINVAL; } - + if ( !hvm ) { dprintk(XENLOG_INFO, "COCO requested for non-HVM guest\n"); @@ -1312,7 +1312,7 @@ int domain_kill(struct domain *d) unmap_guest_area(v, &v->runstate_guest_area); } d->is_dying = DOMDYING_dead; - /* Mem event cleanup has to go here because the rings + /* Mem event cleanup has to go here because the rings * have to be put before we call put_domain. */ vm_event_cleanup(d); domain_changed_state(d); @@ -1337,7 +1337,7 @@ void __domain_crash(struct domain *d) { printk("Domain %d (vcpu#%d) crashed on cpu#%d:\n", d->domain_id, current->vcpu_id, smp_processor_id()); - + if ( !coco_show_execution_state(current) ) show_execution_state(guest_cpu_user_regs()); } @@ -2348,7 +2348,7 @@ long common_vcpu_fast_op(struct cpu_user_regs *regs, int cmd, struct vcpu *v) switch ( cmd ) { case VCPUOP_initialise: - rc = arch_initialise_vcpu(v, (XEN_GUEST_HANDLE(void)) { + rc = arch_initialise_vcpu(v, (XEN_GUEST_HANDLE(void)) { (void *)fastabi_value_n(regs, 2) }); break; diff --git a/xen/drivers/crypto/asp.c b/xen/drivers/crypto/asp.c index 1e7de34fff0c..82a5af840b20 100644 --- a/xen/drivers/crypto/asp.c +++ b/xen/drivers/crypto/asp.c @@ -278,7 +278,7 @@ int _sev_do_cmd(struct amd_sp_dev *sp, int cmd, void *data, unsigned int *psp_re if ( rc && psp_ret ) *psp_ret = SEV_CMDRESP_STS(cmdresp); - if ( data && (!rc) ) + if ( data ) memcpy(data, sp->cmd_buff, buf_len); } else @@ -335,7 +335,7 @@ static int _sev_do_cmd_sync(struct amd_sp_dev *sp, int cmd, void *data, unsigned if ( rc && psp_ret ) *psp_ret = SEV_CMDRESP_STS(cmdresp); - if ( data && (!rc) ) + if ( data ) //copy on error too, to allow to know space how much the psp needs memcpy(data, sp->cmd_buff, buf_len); return rc; @@ -456,13 +456,6 @@ static int __init sp_get_api_version(struct amd_sp_dev *sp) return 0; } -static int __init sp_update_firmware(struct amd_sp_dev *sp) -{ - /* - * FIXME: nothing to do for now - */ - return 0; -} static int __init sp_alloc_special_regions(struct amd_sp_dev *sp) { @@ -500,6 +493,37 @@ static int __init sp_do_init(struct amd_sp_dev *sp) return 0; } +int sp_update_firmware(void *firmware, int len, unsigned int *psp_ret) +{ + struct sev_data_download_firmware argupdate; + struct sev_data_init arginit; + int rc = 0; + argupdate.address = virt_to_maddr(firmware); + argupdate.len = len; + + rc = sev_do_cmd(SEV_CMD_DOWNLOAD_FIRMWARE, &argupdate, psp_ret, true); + if (rc || *psp_ret) { + printk(XENLOG_ERR "asp: SEV_CMD_DOWNLOAD_FIRMWARE: rc %d psp %x \n", rc, *psp_ret); + printk(XENLOG_ERR "asp: SEV_CMD_DOWNLOAD_FIRMWARE: %d %x %x\n", len, ((uint8_t*)firmware)[0], ((uint8_t*)firmware)[len-1]); + return rc; + } + printk(XENLOG_ERR "asp: SEV_CMD_DOWNLOAD_FIRMWARE: updated sucessfully\n"); + + if ( amd_sp_master->es_tmr_region ) + { + arginit.flags = SEV_INIT_FLAGS_SEV_ES; + arginit.tmr_address = page_to_maddr(amd_sp_master->es_tmr_region); + arginit.tmr_len = SEV_ES_TMR_SIZE; + } + rc = _sev_do_cmd_sync(amd_sp_master, SEV_CMD_INIT, &arginit, psp_ret); + if (rc || *psp_ret) { + printk(XENLOG_ERR "asp: SEV_CMD_INIT: rc %d psp %x \n", rc, *psp_ret); + } + + printk(XENLOG_ERR "asp: SEV_CMD_INIT: init sucessful\n"); + return 0; +} + static int __init sp_df_flush(struct amd_sp_dev *sp) { unsigned int err; @@ -533,14 +557,6 @@ static int __init sp_dev_init(struct amd_sp_dev *sp) return rc; } - rc = sp_update_firmware(sp); - if ( rc ) - { - dprintk(XENLOG_ERR, "asp-%pp: can't update firmware %d\n", - &sp->pdev->sbdf, rc); - return rc; - } - rc = sp_alloc_special_regions(sp); if ( rc ) { diff --git a/xen/include/public/arch-x86/xen.h b/xen/include/public/arch-x86/xen.h index ebb5c177a4b1..4c9bbb0ab8fd 100644 --- a/xen/include/public/arch-x86/xen.h +++ b/xen/include/public/arch-x86/xen.h @@ -266,6 +266,16 @@ struct arch_shared_info { }; typedef struct arch_shared_info arch_shared_info_t; +/* could not properly include the struct from "../hvm/coco.h" due to circular includes */ +/* size of certificate is 2084 bytes */ +struct sev_start_parameters { + uint8_t crt[2084]; + uint8_t session[128]; +}; +typedef struct sev_start_parameters sev_start_parameters_t; +DEFINE_XEN_GUEST_HANDLE(sev_start_parameters_t); + + #if defined(__XEN__) || defined(__XEN_TOOLS__) /* * struct xen_arch_domainconfig's ABI is covered by @@ -316,8 +326,9 @@ struct xen_arch_domainconfig { struct { /* Use provided policy if set. If cleared, use default Xen policy. */ #define XEN_X86_SEV_POLICY_VALID (1u << 0) + XEN_GUEST_HANDLE(sev_start_parameters_t) sp; uint32_t flags; - uint64_t policy; + uint32_t policy; } sev; } coco; }; diff --git a/xen/include/public/hvm/coco.h b/xen/include/public/hvm/coco.h index 2e23d91e1249..c14ee3286a7e 100644 --- a/xen/include/public/hvm/coco.h +++ b/xen/include/public/hvm/coco.h @@ -5,6 +5,14 @@ #include "../xen.h" #define XEN_COCO_platform_status 0 +#define XEN_COCO_prepare_initial_mem 1 +#define XEN_COCO_finish_initial_mem 8 +#define XEN_COCO_attestation_report 2 +#define XEN_COCO_platform_certs 3 +#define XEN_COCO_platform_csr 4 +#define XEN_COCO_platform_regen_cert 5 +#define XEN_COCO_platform_cert_import 6 +#define XEN_COCO_platform_update 7 /** * XEN_COCO_platform_status: Get the status of confidential computing platform. @@ -33,23 +41,23 @@ struct coco_platform_status { #define COCO_STATUS_FLAG_supported (1 << 0) /* Confidential computing is supported and usable */ #define COCO_STATUS_FLAG_unsafe (1 << 1) /* Confidential computing is unsafe (e.g debug mode) */ +#define COCO_STATUS_FEATURES_PLATFORM_OWNED (1 << 31) /* Confidential computing is supported and usable */ uint32_t flags; /* OUT */ - uint32_t features; /* OUT */ uint32_t version_major; /* OUT */ uint32_t version_minor; /* OUT */ + uint32_t version_build; /* OUT */ }; typedef struct coco_platform_status coco_platform_status_t; DEFINE_XEN_GUEST_HANDLE(coco_platform_status_t); -#define XEN_COCO_prepare_initial_mem 1 /** * XEN_COCO_prepare_initial_mem: Prepare early memory pages of a guest - * + * * During guest construction, the confidential computing platform may require memory * to be prepared (e.g., encrypted) before the guest is started. - * + * * After preparation, any further access to these pages is invalid, as they may be * encrypted, sealed, or tracked by the platform. */ @@ -62,4 +70,116 @@ struct coco_prepare_initial_mem { typedef struct coco_prepare_initial_mem coco_prepare_initial_mem_t; DEFINE_XEN_GUEST_HANDLE(coco_prepare_initial_mem_t); + +struct sev_attestation_report_response { + uint8_t mnonce[16]; + uint8_t launch_digest[32]; + uint32_t policy; + uint32_t sig_usage; + uint32_t sig_algo; + uint32_t reserved; + uint8_t sig[144]; +} __attribute__((packed)); + +/** + * len is the size used by the attestation, it can be used to determine the attestation type + * the union is used to make sure the struct is big enough to handle all attestation + */ +struct coco_attestation_report { + domid_t domid; /* IN */ + uint8_t mnonce[16]; /* IN */ + uint32_t len; /* OUT */ + union { + struct sev_attestation_report_response sev; + } /* OUT */; +}; +typedef struct coco_attestation_report coco_attestation_report_t; +DEFINE_XEN_GUEST_HANDLE(coco_attestation_report_t); + +struct sev_session { + uint8_t nonce[16]; + uint8_t wrap_tk[32]; + uint8_t wrap_iv[16]; + uint8_t wrap_mac[32]; + uint8_t policy_mac[32]; +} __attribute__((packed)); + +struct sev_certificate { + uint32_t version; + uint8_t api_major; + uint8_t api_minor; + uint8_t reserved; + uint8_t reserved1; + uint32_t pubkey_usage; + uint32_t pubkey_algo; + uint8_t pubkey[1028]; + uint32_t sig1_usage; + uint32_t sig1_algo; + uint8_t sig1[512]; + uint32_t sig2_usage; + uint32_t sig2_algo; + uint8_t sig2[512]; +} __attribute__((packed)); + +/** + * Note : this cek is not signed by amd, + * you need to use it with the cpuid to get the signed version from amd's server + */ +struct sev_certificate_fullchain { + struct sev_certificate pdh; + struct sev_certificate pek; + struct sev_certificate oca; + struct sev_certificate cek; +} __attribute__((packed)); + +struct coco_platform_certs { + uint8_t hwid[128]; /* OUT */ + uint8_t cpu_number; /* OUT */ + struct coco_platform_status status; /* OUT */ + union { + struct sev_certificate_fullchain sev; /* OUT */ + }; +}; +typedef struct coco_platform_certs coco_platform_certs_t; +DEFINE_XEN_GUEST_HANDLE(coco_platform_certs_t); + +struct coco_certificate { + union { + struct sev_certificate sev; /* OUT */ + }; +}; +typedef struct coco_certificate coco_certificate_t; +DEFINE_XEN_GUEST_HANDLE(coco_certificate_t); + +struct coco_update { + XEN_GUEST_HANDLE(void) data; + uint32_t size; +}; +typedef struct coco_update coco_update_t; +DEFINE_XEN_GUEST_HANDLE(coco_update_t); + +struct coco_platform_import_certs { + union { + struct { + struct sev_certificate pek; + struct sev_certificate oca; + } sev; + }; +}; + +typedef struct coco_platform_import_certs coco_platform_import_certs_t; +DEFINE_XEN_GUEST_HANDLE(coco_platform_import_certs_t); + +enum coco_certificate_name { + sev_pek = 0, + sev_pdh, +}; +typedef enum coco_certificate_name coco_certificate_name_t; + +#define COCO_CERTIFICATE_NAME_ARRAY_DEF() \ + const char *certs_name[] = { \ + [sev_pek] = "sev_pek", \ + [sev_pdh] = "sev_pdh", \ + }; \ + #endif /* __XEN_PUBLIC_HVM_COCO_H__ */ diff --git a/xen/include/xen/coco.h b/xen/include/xen/coco.h index fd4562ddcba7..52fa7ffd163f 100644 --- a/xen/include/xen/coco.h +++ b/xen/include/xen/coco.h @@ -13,15 +13,18 @@ extern __read_mostly struct coco_platform_status platform_status; struct coco_domain_ops { int (*prepare_initial_mem)(struct domain *d, gfn_t gfn, size_t page_count); - /* HVM domain hooks */ int (*domain_initialise)(struct domain *d); + int (*domain_memory_finished)(struct domain *d); + int (*domain_vcpu_initialise)(struct domain *d); int (*domain_creation_finished)(struct domain *d); void (*domain_destroy)(struct domain *d); /* Returns false if the general handler needs to be used. */ bool (*show_execution_state)(struct vcpu *v); + int (*domain_attestation_report)(struct domain *d, + struct coco_attestation_report *report); #ifdef CONFIG_X86 /* COCO-specific ASID allocation logic */ int (*asid_alloc)(struct domain *d, struct hvm_asid *asid); @@ -30,9 +33,13 @@ struct coco_domain_ops { struct coco_ops { const char *name; - int (*init)(void); int (*get_platform_status)(coco_platform_status_t *status); + int (*get_platform_certs)(coco_platform_certs_t *certs); + int (*get_certificate_signing_request)(coco_certificate_t *certs); + int (*import_certificates)(coco_platform_import_certs_t *certs); + int (*regen_platform_cert)(coco_certificate_name_t cert); + int (*update_platform)(void *firmware, int len); struct coco_domain_ops *(*get_domain_ops)(struct domain *d, const struct xen_domctl_createdomain *config); }; @@ -55,6 +62,22 @@ static inline int coco_domain_initialise(struct domain *d) return 0; } +static inline int coco_domain_vcpu_initialise(struct domain *d) +{ + if ( d->coco_ops && d->coco_ops->domain_vcpu_initialise ) + return d->coco_ops->domain_vcpu_initialise(d); + + return 0; +} + +static inline int coco_domain_memory_finished(struct domain *d) +{ + if ( d->coco_ops && d->coco_ops->domain_memory_finished ) + return d->coco_ops->domain_memory_finished(d); + + return 0; +} + static inline int coco_domain_creation_finished(struct domain *d) { if ( d->coco_ops && d->coco_ops->domain_creation_finished ) @@ -89,6 +112,16 @@ static inline int coco_domain_initialise(struct domain *d) return 0; } +static inline int coco_domain_vcpu_initialise(struct domain *d) +{ + return 0; +} + +static inline int coco_domain_memory_finished(struct domain *d) +{ + return 0; +} + static inline int coco_domain_creation_finished(struct domain *d) { return 0; @@ -104,4 +137,4 @@ static inline bool coco_show_execution_state(struct vcpu *v) } #endif -#endif /* _XEN_COCO_H */ \ No newline at end of file +#endif /* _XEN_COCO_H */