From a78afc16168bdded6c8376d6bde9121719b24537 Mon Sep 17 00:00:00 2001 From: Paul Meyer Date: Fri, 26 Jun 2026 11:57:29 +0200 Subject: [PATCH] vmspawn: add Intel TDX confidential VM support Wire up --coco=tdx alongside the existing SEV-SNP path. TDX requires KVM on x86_64, a raw TDVF firmware loaded via -bios (no pflash/NVRAM split), kernel-irqchip=split, and the "host" CPU model since QEMU rejects named models. Sets up the tdx-guest object and confidential-guest-support=tdx0. TDX measurement is different from QEMU's kernel-hashes injection: TDX provides runtime measurements via RTMRs, so the initial measurement only covers the firmware, which then measures the rest of the boot chain into those RTMRs (done by OVMF today). Therefore a restriction to direct kernel boot isn't required either. Signed-off-by: Paul Meyer --- man/systemd-vmspawn.xml | 17 +++++++++- src/vmspawn/vmspawn-settings.c | 1 + src/vmspawn/vmspawn-settings.h | 1 + src/vmspawn/vmspawn.c | 59 ++++++++++++++++++++++++++++------ 4 files changed, 67 insertions(+), 11 deletions(-) diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index fd73e007707..66c49352a9a 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -346,7 +346,8 @@ its current form) in a future version of systemd. Configures whether to run the guest as a confidential VM. Takes one of - no or sev-snp. Defaults to no. + no, sev-snp or tdx. Defaults to + no. sev-snp enables AMD SEV-SNP. This requires KVM on an x86_64 host with SNP-capable hardware and firmware. must point to a raw SNP-built @@ -367,6 +368,20 @@ recent version of systemd (supporting /.extra/system_credentials/). A vTPM, if attached via , must be treated as untrusted by the guest. + tdx enables Intel TDX. This requires KVM on an x86_64 host with + TDX-capable hardware and a TDX-enabled host kernel. As with sev-snp, + must point to a raw TDX-built OVMF (TDVF) .fd + image, which is loaded via QEMU's (pflash + NVRAM split is not + supported), and the CPU model is fixed to host. Firmware is measured into + MRTD when the TD is built. Secure Boot cannot be enrolled at runtime (there is no writable + NVRAM); its state is fixed by the supplied TDVF image and is part of the measured firmware. + When booting a UKI, the whole UKI PE is measured into RTMR 1, and the loaded sections are + measured individually by systemd-stub into RTMR 2. For direct linux boot, + firmware measures the kernel PE into RTMR 1, and the Linux EFI stub measures initrd and + command line into RTMR 2. A vTPM, if attached via , must be treated + as untrusted by the guest. + + diff --git a/src/vmspawn/vmspawn-settings.c b/src/vmspawn/vmspawn-settings.c index d05c4e1a9e1..043920c36ca 100644 --- a/src/vmspawn/vmspawn-settings.c +++ b/src/vmspawn/vmspawn-settings.c @@ -40,6 +40,7 @@ DEFINE_STRING_TABLE_LOOKUP(firmware, Firmware); static const char *const confidential_computing_table[_COCO_MAX] = { [COCO_NO] = "no", [COCO_AMD_SEV_SNP] = "sev-snp", + [COCO_INTEL_TDX] = "tdx", }; DEFINE_STRING_TABLE_LOOKUP(confidential_computing, ConfidentialComputing); diff --git a/src/vmspawn/vmspawn-settings.h b/src/vmspawn/vmspawn-settings.h index 9be3afdef4c..5f488337f93 100644 --- a/src/vmspawn/vmspawn-settings.h +++ b/src/vmspawn/vmspawn-settings.h @@ -45,6 +45,7 @@ typedef enum Firmware { typedef enum ConfidentialComputing { COCO_NO, COCO_AMD_SEV_SNP, + COCO_INTEL_TDX, _COCO_MAX, _COCO_INVALID = -EINVAL, } ConfidentialComputing; diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 6e2329f7f9c..41cf611aa93 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -634,7 +634,7 @@ static int parse_argv(int argc, char *argv[]) { break; } - OPTION_LONG("coco", "no|sev-snp", "Run the guest as a confidential VM"): { + OPTION_LONG("coco", "no|sev-snp|tdx", "Run the guest as a confidential VM"): { ConfidentialComputing cc = confidential_computing_from_string(opts.arg); if (cc < 0) return log_error_errno(cc, "Unknown --coco= value: %s", opts.arg); @@ -2607,11 +2607,11 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { use_kvm = r; } - if (arg_confidential_computing == COCO_AMD_SEV_SNP && !use_kvm) + if (arg_confidential_computing != COCO_NO && !use_kvm) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "--coco=sev-snp requires KVM, but KVM is not available."); + "--coco= requires KVM, but KVM is not available."); - if (arg_firmware_type == FIRMWARE_UEFI && arg_confidential_computing != COCO_AMD_SEV_SNP) { + if (arg_firmware_type == FIRMWARE_UEFI && arg_confidential_computing == COCO_NO) { if (arg_firmware) r = load_ovmf_config(arg_firmware, &ovmf_config); else @@ -2685,7 +2685,7 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { if (r < 0) return r; - if (arg_confidential_computing == COCO_AMD_SEV_SNP) { + if (arg_confidential_computing != COCO_NO) { r = qemu_config_key(config_file, "kernel-irqchip", "split"); if (r < 0) return r; @@ -2719,6 +2719,10 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { r = qemu_config_key(config_file, "confidential-guest-support", "snp0"); if (r < 0) return r; + } else if (arg_confidential_computing == COCO_INTEL_TDX) { + r = qemu_config_key(config_file, "confidential-guest-support", "tdx0"); + if (r < 0) + return r; } r = qemu_config_section(config_file, "smp-opts", /* id= */ NULL, @@ -2927,6 +2931,11 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { "kernel-hashes", "on"); if (r < 0) return r; + } else if (arg_confidential_computing == COCO_INTEL_TDX) { + r = qemu_config_section(config_file, "object", "tdx0", + "qom-type", "tdx-guest"); + if (r < 0) + return r; } unsigned child_cid = arg_vsock_cid; @@ -2948,11 +2957,13 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { /* -cpu stays on cmdline since not all flags are supported in config. SNP needs a stable, * named CPU model so the launch measurement is reproducible across hosts; EPYC-v4 is the - * baseline that covers all SNP-capable processors (Milan and later). */ + * baseline that covers all SNP-capable processors (Milan and later). TDX requires host + * CPU model. */ const char *cpu_model = #ifdef __x86_64__ - arg_confidential_computing == COCO_AMD_SEV_SNP ? "EPYC-v4" - : "max,hv_relaxed,hv-vapic,hv-time"; + arg_confidential_computing == COCO_AMD_SEV_SNP ? "EPYC-v4" : + arg_confidential_computing == COCO_INTEL_TDX ? "host" : + "max,hv_relaxed,hv-vapic,hv-time"; #else "max"; #endif @@ -4128,7 +4139,7 @@ static int verify_arguments(void) { "--coco=sev-snp requires KVM, remove --kvm=no."); if (arg_firmware_type != FIRMWARE_UEFI) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "--coco can't be used with %s firmware", + "--coco=sev-snp can't be used with %s firmware", firmware_to_string(arg_firmware_type)); /* SNP can't use pflash + NVRAM split, so the firmware-descriptor * machinery doesn't apply. Require an explicit raw .fd path and @@ -4140,7 +4151,7 @@ static int verify_arguments(void) { log_debug("Using raw SNP firmware at %s (no NVRAM, no Secure Boot).", arg_firmware); if (set_contains(arg_firmware_features_include, "secure-boot")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "--secure-boot=yes cannot be combined with --coco."); + "--coco=sev-snp cannot be combined with --secure-boot=yes."); if (arg_tpm > 0) log_warning("TPM can't be trusted by the confidential computing guest"); /* kernel-hashes=on only covers what QEMU itself loads via -kernel/-initrd/-append. @@ -4153,6 +4164,34 @@ static int verify_arguments(void) { "so kernel, initrd and cmdline are covered by the launch measurement."); } + if (arg_confidential_computing == COCO_INTEL_TDX) { + if (native_architecture() != ARCHITECTURE_X86_64) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "--coco=tdx is only supported on x86_64."); + if (arg_kvm == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--coco=tdx requires KVM, remove --kvm=no."); + if (arg_firmware_type != FIRMWARE_UEFI) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--coco=tdx can't be used with %s firmware", + firmware_to_string(arg_firmware_type)); + /* TDX can't use pflash + NVRAM split, so the firmware-descriptor + * machinery doesn't apply. Require an explicit raw .fd path and + * use it verbatim with -bios later. */ + if (!arg_firmware) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--coco=tdx requires --firmware=PATH " + "pointing at a raw TDX-built OVMF (TDVF) .fd binary."); + log_debug("Using raw TDX firmware at %s (no NVRAM, no Secure Boot).", arg_firmware); + /* Secure Boot state is baked into the supplied TDVF image and can't be enrolled at + * runtime (no writable NVRAM), so --secure-boot=yes would silently have no effect. */ + if (set_contains(arg_firmware_features_include, "secure-boot")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--coco=tdx cannot be combined with --secure-boot=yes."); + if (arg_tpm > 0) + log_warning("TPM can't be trusted by the confidential computing guest"); + } + return 0; } -- 2.47.3