From 6f2f5b96f116f4bea31b86f85288c4cb83429d5a Mon Sep 17 00:00:00 2001 From: Paul Meyer Date: Fri, 3 Jul 2026 15:52:10 +0200 Subject: [PATCH] vmspawn: allow opt-in Secure Boot firmware for coco CoCo firmware is stateless, so Secure Boot keys cannot be enrolled at runtime: it only enforces Secure Boot with keys baked in at build time, refusing unsigned images from the first boot. The default exclusion of the enrolled-keys firmware feature hence keeps unsigned images bootable, but it also makes firmware discovery fail on distros that only ship SNP/TDX firmware with pre-enrolled keys (e.g. Fedora's TDVF). Drop the rejection of --secure-boot=yes with --coco= and instead treat it as an opt-in to such firmware, by lifting the enrolled-keys exclusion. While at it, reject --efi-nvram-template= and an explicit --efi-nvram-state= path with --coco=, which were silently ignored, as stateless firmware has no NVRAM to instantiate or persist. Signed-off-by: Paul Meyer --- man/systemd-vmspawn.xml | 19 +++++++++++++++---- src/vmspawn/vmspawn.c | 32 +++++++++++++++++++++++--------- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml index 4ef1b18f576..2475a0464f9 100644 --- a/man/systemd-vmspawn.xml +++ b/man/systemd-vmspawn.xml @@ -269,7 +269,8 @@ Takes an absolute path, or a relative path beginning with ./. Specifies the path to an EFI NVRAM template file to copy and use as the initial EFI variable NVRAM state. If not specified, the default NVRAM template from the firmware - definition is copied and used. + definition is copied and used. Cannot be used together with , as + confidential computing firmware is stateless. @@ -283,7 +284,8 @@ automatically derived from the VM image path or directory path, with the .efinvramstate suffix appended. If set to the special string off the EFI variable NVRAM state is only maintained transiently and flushed out - when the VM shuts down. Defaults to auto. + when the VM shuts down. Defaults to auto. An explicit path cannot be used + together with , as confidential computing firmware is stateless. If is specified, auto behaves like off. @@ -300,6 +302,13 @@ . Setting this to auto removes secure-boot from both the included and excluded feature lists. + With , setting this to yes additionally + requires the enrolled-keys firmware feature, overriding its default + exclusion: confidential computing firmware is stateless, so keys cannot be enrolled at runtime + and Secure Boot is only operative with keys baked into the image at build time, which in turn + makes the firmware refuse to boot unsigned images from the very first boot. The default + exclusion of enrolled-keys hence keeps unsigned images bootable. + @@ -353,7 +362,8 @@ SNP-capable hardware and firmware. A suitable SNP-built OVMF firmware is picked automatically from the installed QEMU firmware descriptors, by requiring the amd-sev-snp firmware feature; use with a path to a firmware descriptor file to - select a specific one. Secure Boot is unavailable. Direct kernel boot via + select a specific one. Secure Boot is effectively off by default (see + ). Direct kernel boot via is required so that the kernel, initrd and command line are hashed into the launch measurement (kernel-hashes=on); booting the kernel off the disk image via the firmware would leave it outside the measurement. Credentials passed via @@ -376,7 +386,8 @@ with a path to a firmware descriptor file to select a specific one. 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 selected TDVF image and is part of the measured firmware. + is fixed by the selected TDVF image and is part of the measured firmware (see + ). 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 diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 2121fd29d0b..32fd6908c99 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -2574,6 +2574,14 @@ static int discover_ovmf_config(OvmfConfig **ret, sd_json_variant **ret_firmware r = set_put_strdup(&arg_firmware_features_include, coco_feature); if (r < 0) return log_oom(); + + /* CoCo firmware is stateless, so Secure Boot keys cannot be enrolled at runtime + * but must have baked-in, enrolled keys, which we avoid in other cases. */ + if (set_contains(arg_firmware_features_include, "secure-boot")) { + r = set_put_strdup(&arg_firmware_features_include, "enrolled-keys"); + if (r < 0) + return log_oom(); + } } FindOvmfConfigFlags flags = @@ -2584,7 +2592,8 @@ static int discover_ovmf_config(OvmfConfig **ret, sd_json_variant **ret_firmware if (r == -ENOENT && coco_feature) return log_error_errno(r, "No suitable firmware descriptor found for --coco=%s " "(requires stateless firmware in raw format with the '%s' firmware feature). " - "Install a suitable firmware or select a firmware descriptor with --firmware=.", + "Install a suitable firmware, select a firmware descriptor with --firmware=, or " + "opt in with --secure-boot=yes if the installed firmware enforces Secure Boot.", confidential_computing_to_string(arg_confidential_computing), coco_feature); if (r < 0) return log_error_errno(r, "Failed to find OVMF config: %m"); @@ -4244,6 +4253,19 @@ static int verify_arguments(void) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--grow-image is not supported for qcow2 images, use 'qemu-img resize FILE SIZE'."); + if (arg_confidential_computing != COCO_NO) { + /* Confidential computing firmware is stateless, there is no NVRAM to instantiate from a + * template or to persist. */ + if (arg_efi_nvram_template) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--efi-nvram-template= cannot be used with --coco=, " + "confidential computing firmware is stateless."); + if (arg_efi_nvram_state_mode == STATE_PATH) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "An explicit --efi-nvram-state= path cannot be used with --coco=, " + "confidential computing firmware is stateless. Use 'off' or 'auto'."); + } + if (arg_confidential_computing == COCO_AMD_SEV_SNP) { if (native_architecture() != ARCHITECTURE_X86_64) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), @@ -4255,9 +4277,6 @@ static int verify_arguments(void) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--coco=sev-snp can't be used with %s firmware", firmware_to_string(arg_firmware_type)); - if (set_contains(arg_firmware_features_include, "secure-boot")) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "--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. @@ -4281,11 +4300,6 @@ static int verify_arguments(void) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--coco=tdx can't be used with %s firmware", firmware_to_string(arg_firmware_type)); - /* 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"); } -- 2.47.3