| /* |
| * AWS nitro-enclave machine |
| * |
| * Copyright (c) 2024 Dorjoy Chowdhury <dorjoychy111@gmail.com> |
| * |
| * This work is licensed under the terms of the GNU GPL, version 2 or |
| * (at your option) any later version. See the COPYING file in the |
| * top-level directory. |
| */ |
| |
| #include "qemu/osdep.h" |
| #include "qemu/error-report.h" |
| #include "qapi/error.h" |
| #include "qom/object_interfaces.h" |
| |
| #include "chardev/char.h" |
| #include "hw/sysbus.h" |
| #include "hw/core/eif.h" |
| #include "hw/i386/x86.h" |
| #include "hw/i386/microvm.h" |
| #include "hw/i386/nitro_enclave.h" |
| #include "hw/virtio/virtio-mmio.h" |
| #include "hw/virtio/virtio-nsm.h" |
| #include "hw/virtio/vhost-user-vsock.h" |
| #include "sysemu/hostmem.h" |
| |
| static BusState *find_free_virtio_mmio_bus(void) |
| { |
| BusChild *kid; |
| BusState *bus = sysbus_get_default(); |
| |
| QTAILQ_FOREACH(kid, &bus->children, sibling) { |
| DeviceState *dev = kid->child; |
| if (object_dynamic_cast(OBJECT(dev), TYPE_VIRTIO_MMIO)) { |
| VirtIOMMIOProxy *mmio = VIRTIO_MMIO(OBJECT(dev)); |
| VirtioBusState *mmio_virtio_bus = &mmio->bus; |
| BusState *mmio_bus = &mmio_virtio_bus->parent_obj; |
| if (QTAILQ_EMPTY(&mmio_bus->children)) { |
| return mmio_bus; |
| } |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static void vhost_user_vsock_init(NitroEnclaveMachineState *nems) |
| { |
| DeviceState *dev = qdev_new(TYPE_VHOST_USER_VSOCK); |
| VHostUserVSock *vsock = VHOST_USER_VSOCK(dev); |
| BusState *bus; |
| |
| if (!nems->vsock) { |
| error_report("A valid chardev id for vhost-user-vsock device must be " |
| "provided using the 'vsock' machine option"); |
| exit(1); |
| } |
| |
| bus = find_free_virtio_mmio_bus(); |
| if (!bus) { |
| error_report("Failed to find bus for vhost-user-vsock device"); |
| exit(1); |
| } |
| |
| Chardev *chardev = qemu_chr_find(nems->vsock); |
| if (!chardev) { |
| error_report("Failed to find chardev with id %s", nems->vsock); |
| exit(1); |
| } |
| |
| vsock->conf.chardev.chr = chardev; |
| |
| qdev_realize_and_unref(dev, bus, &error_fatal); |
| } |
| |
| static void virtio_nsm_init(NitroEnclaveMachineState *nems) |
| { |
| DeviceState *dev = qdev_new(TYPE_VIRTIO_NSM); |
| VirtIONSM *vnsm = VIRTIO_NSM(dev); |
| BusState *bus = find_free_virtio_mmio_bus(); |
| |
| if (!bus) { |
| error_report("Failed to find bus for virtio-nsm device."); |
| exit(1); |
| } |
| |
| qdev_prop_set_string(dev, "module-id", nems->id); |
| |
| qdev_realize_and_unref(dev, bus, &error_fatal); |
| nems->vnsm = vnsm; |
| } |
| |
| static void nitro_enclave_devices_init(NitroEnclaveMachineState *nems) |
| { |
| vhost_user_vsock_init(nems); |
| virtio_nsm_init(nems); |
| } |
| |
| static void nitro_enclave_machine_state_init(MachineState *machine) |
| { |
| NitroEnclaveMachineClass *ne_class = |
| NITRO_ENCLAVE_MACHINE_GET_CLASS(machine); |
| NitroEnclaveMachineState *ne_state = NITRO_ENCLAVE_MACHINE(machine); |
| |
| ne_class->parent_init(machine); |
| nitro_enclave_devices_init(ne_state); |
| } |
| |
| static void nitro_enclave_machine_reset(MachineState *machine, ResetType type) |
| { |
| NitroEnclaveMachineClass *ne_class = |
| NITRO_ENCLAVE_MACHINE_GET_CLASS(machine); |
| NitroEnclaveMachineState *ne_state = NITRO_ENCLAVE_MACHINE(machine); |
| |
| ne_class->parent_reset(machine, type); |
| |
| memset(ne_state->vnsm->pcrs, 0, sizeof(ne_state->vnsm->pcrs)); |
| |
| /* PCR0 */ |
| ne_state->vnsm->extend_pcr(ne_state->vnsm, 0, ne_state->image_sha384, |
| QCRYPTO_HASH_DIGEST_LEN_SHA384); |
| /* PCR1 */ |
| ne_state->vnsm->extend_pcr(ne_state->vnsm, 1, ne_state->bootstrap_sha384, |
| QCRYPTO_HASH_DIGEST_LEN_SHA384); |
| /* PCR2 */ |
| ne_state->vnsm->extend_pcr(ne_state->vnsm, 2, ne_state->app_sha384, |
| QCRYPTO_HASH_DIGEST_LEN_SHA384); |
| /* PCR3 */ |
| if (ne_state->parent_role) { |
| ne_state->vnsm->extend_pcr(ne_state->vnsm, 3, |
| (uint8_t *) ne_state->parent_role, |
| strlen(ne_state->parent_role)); |
| } |
| /* PCR4 */ |
| if (ne_state->parent_id) { |
| ne_state->vnsm->extend_pcr(ne_state->vnsm, 4, |
| (uint8_t *) ne_state->parent_id, |
| strlen(ne_state->parent_id)); |
| } |
| /* PCR8 */ |
| if (ne_state->signature_found) { |
| ne_state->vnsm->extend_pcr(ne_state->vnsm, 8, |
| ne_state->fingerprint_sha384, |
| QCRYPTO_HASH_DIGEST_LEN_SHA384); |
| } |
| |
| /* First 16 PCRs are locked from boot and reserved for nitro enclave */ |
| for (int i = 0; i < 16; ++i) { |
| ne_state->vnsm->lock_pcr(ne_state->vnsm, i); |
| } |
| } |
| |
| static void nitro_enclave_machine_initfn(Object *obj) |
| { |
| MicrovmMachineState *mms = MICROVM_MACHINE(obj); |
| X86MachineState *x86ms = X86_MACHINE(obj); |
| NitroEnclaveMachineState *nems = NITRO_ENCLAVE_MACHINE(obj); |
| |
| nems->id = g_strdup("i-234-enc5678"); |
| |
| /* AWS nitro enclaves have PCIE and ACPI disabled */ |
| mms->pcie = ON_OFF_AUTO_OFF; |
| x86ms->acpi = ON_OFF_AUTO_OFF; |
| } |
| |
| static void x86_load_eif(X86MachineState *x86ms, FWCfgState *fw_cfg, |
| int acpi_data_size, bool pvh_enabled) |
| { |
| Error *err = NULL; |
| char *eif_kernel, *eif_initrd, *eif_cmdline; |
| MachineState *machine = MACHINE(x86ms); |
| NitroEnclaveMachineState *nems = NITRO_ENCLAVE_MACHINE(x86ms); |
| |
| if (!read_eif_file(machine->kernel_filename, machine->initrd_filename, |
| &eif_kernel, &eif_initrd, &eif_cmdline, |
| nems->image_sha384, nems->bootstrap_sha384, |
| nems->app_sha384, nems->fingerprint_sha384, |
| &(nems->signature_found), &err)) { |
| error_report_err(err); |
| exit(1); |
| } |
| |
| g_free(machine->kernel_filename); |
| machine->kernel_filename = eif_kernel; |
| g_free(machine->initrd_filename); |
| machine->initrd_filename = eif_initrd; |
| |
| /* |
| * If kernel cmdline argument was provided, let's concatenate it to the |
| * extracted EIF kernel cmdline. |
| */ |
| if (machine->kernel_cmdline != NULL) { |
| char *cmd = g_strdup_printf("%s %s", eif_cmdline, |
| machine->kernel_cmdline); |
| g_free(eif_cmdline); |
| g_free(machine->kernel_cmdline); |
| machine->kernel_cmdline = cmd; |
| } else { |
| machine->kernel_cmdline = eif_cmdline; |
| } |
| |
| x86_load_linux(x86ms, fw_cfg, 0, true); |
| |
| unlink(machine->kernel_filename); |
| unlink(machine->initrd_filename); |
| return; |
| } |
| |
| static bool create_memfd_backend(MachineState *ms, const char *path, |
| Error **errp) |
| { |
| Object *obj; |
| MachineClass *mc = MACHINE_GET_CLASS(ms); |
| bool r = false; |
| |
| obj = object_new(TYPE_MEMORY_BACKEND_MEMFD); |
| if (!object_property_set_int(obj, "size", ms->ram_size, errp)) { |
| goto out; |
| } |
| object_property_add_child(object_get_objects_root(), mc->default_ram_id, |
| obj); |
| |
| if (!user_creatable_complete(USER_CREATABLE(obj), errp)) { |
| goto out; |
| } |
| r = object_property_set_link(OBJECT(ms), "memory-backend", obj, errp); |
| |
| out: |
| object_unref(obj); |
| return r; |
| } |
| |
| static char *nitro_enclave_get_vsock_chardev_id(Object *obj, Error **errp) |
| { |
| NitroEnclaveMachineState *nems = NITRO_ENCLAVE_MACHINE(obj); |
| |
| return g_strdup(nems->vsock); |
| } |
| |
| static void nitro_enclave_set_vsock_chardev_id(Object *obj, const char *value, |
| Error **errp) |
| { |
| NitroEnclaveMachineState *nems = NITRO_ENCLAVE_MACHINE(obj); |
| |
| g_free(nems->vsock); |
| nems->vsock = g_strdup(value); |
| } |
| |
| static char *nitro_enclave_get_id(Object *obj, Error **errp) |
| { |
| NitroEnclaveMachineState *nems = NITRO_ENCLAVE_MACHINE(obj); |
| |
| return g_strdup(nems->id); |
| } |
| |
| static void nitro_enclave_set_id(Object *obj, const char *value, |
| Error **errp) |
| { |
| NitroEnclaveMachineState *nems = NITRO_ENCLAVE_MACHINE(obj); |
| |
| g_free(nems->id); |
| nems->id = g_strdup(value); |
| } |
| |
| static char *nitro_enclave_get_parent_role(Object *obj, Error **errp) |
| { |
| NitroEnclaveMachineState *nems = NITRO_ENCLAVE_MACHINE(obj); |
| |
| return g_strdup(nems->parent_role); |
| } |
| |
| static void nitro_enclave_set_parent_role(Object *obj, const char *value, |
| Error **errp) |
| { |
| NitroEnclaveMachineState *nems = NITRO_ENCLAVE_MACHINE(obj); |
| |
| g_free(nems->parent_role); |
| nems->parent_role = g_strdup(value); |
| } |
| |
| static char *nitro_enclave_get_parent_id(Object *obj, Error **errp) |
| { |
| NitroEnclaveMachineState *nems = NITRO_ENCLAVE_MACHINE(obj); |
| |
| return g_strdup(nems->parent_id); |
| } |
| |
| static void nitro_enclave_set_parent_id(Object *obj, const char *value, |
| Error **errp) |
| { |
| NitroEnclaveMachineState *nems = NITRO_ENCLAVE_MACHINE(obj); |
| |
| g_free(nems->parent_id); |
| nems->parent_id = g_strdup(value); |
| } |
| |
| static void nitro_enclave_class_init(ObjectClass *oc, void *data) |
| { |
| MachineClass *mc = MACHINE_CLASS(oc); |
| MicrovmMachineClass *mmc = MICROVM_MACHINE_CLASS(oc); |
| NitroEnclaveMachineClass *nemc = NITRO_ENCLAVE_MACHINE_CLASS(oc); |
| |
| mmc->x86_load_linux = x86_load_eif; |
| |
| mc->family = "nitro_enclave_i386"; |
| mc->desc = "AWS Nitro Enclave"; |
| |
| nemc->parent_init = mc->init; |
| mc->init = nitro_enclave_machine_state_init; |
| |
| nemc->parent_reset = mc->reset; |
| mc->reset = nitro_enclave_machine_reset; |
| |
| mc->create_default_memdev = create_memfd_backend; |
| |
| object_class_property_add_str(oc, NITRO_ENCLAVE_VSOCK_CHARDEV_ID, |
| nitro_enclave_get_vsock_chardev_id, |
| nitro_enclave_set_vsock_chardev_id); |
| object_class_property_set_description(oc, NITRO_ENCLAVE_VSOCK_CHARDEV_ID, |
| "Set chardev id for vhost-user-vsock " |
| "device"); |
| |
| object_class_property_add_str(oc, NITRO_ENCLAVE_ID, nitro_enclave_get_id, |
| nitro_enclave_set_id); |
| object_class_property_set_description(oc, NITRO_ENCLAVE_ID, |
| "Set enclave identifier"); |
| |
| object_class_property_add_str(oc, NITRO_ENCLAVE_PARENT_ROLE, |
| nitro_enclave_get_parent_role, |
| nitro_enclave_set_parent_role); |
| object_class_property_set_description(oc, NITRO_ENCLAVE_PARENT_ROLE, |
| "Set parent instance IAM role ARN"); |
| |
| object_class_property_add_str(oc, NITRO_ENCLAVE_PARENT_ID, |
| nitro_enclave_get_parent_id, |
| nitro_enclave_set_parent_id); |
| object_class_property_set_description(oc, NITRO_ENCLAVE_PARENT_ID, |
| "Set parent instance identifier"); |
| } |
| |
| static const TypeInfo nitro_enclave_machine_info = { |
| .name = TYPE_NITRO_ENCLAVE_MACHINE, |
| .parent = TYPE_MICROVM_MACHINE, |
| .instance_size = sizeof(NitroEnclaveMachineState), |
| .instance_init = nitro_enclave_machine_initfn, |
| .class_size = sizeof(NitroEnclaveMachineClass), |
| .class_init = nitro_enclave_class_init, |
| }; |
| |
| static void nitro_enclave_machine_init(void) |
| { |
| type_register_static(&nitro_enclave_machine_info); |
| } |
| type_init(nitro_enclave_machine_init); |