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 <katexochen0@gmail.com>
This commit is contained in:
Paul Meyer
2026-07-06 11:04:11 +02:00
parent 2f95c17a2f
commit 6f2f5b96f1
2 changed files with 38 additions and 13 deletions
+15 -4
View File
@@ -269,7 +269,8 @@
<listitem><para>Takes an absolute path, or a relative path beginning with
<filename>./</filename>. 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.</para>
definition is copied and used. Cannot be used together with <option>--coco=</option>, as
confidential computing firmware is stateless.</para>
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
</varlistentry>
@@ -283,7 +284,8 @@
automatically derived from the VM image path or directory path, with the
<literal>.efinvramstate</literal> suffix appended. If set to the special string
<literal>off</literal> the EFI variable NVRAM state is only maintained transiently and flushed out
when the VM shuts down. Defaults to <literal>auto</literal>.</para>
when the VM shuts down. Defaults to <literal>auto</literal>. An explicit path cannot be used
together with <option>--coco=</option>, as confidential computing firmware is stateless.</para>
<para>If <option>--ephemeral</option> is specified, <literal>auto</literal> behaves like
<literal>off</literal>.</para>
@@ -300,6 +302,13 @@
<option>--firmware-features=~secure-boot</option>. Setting this to <literal>auto</literal>
removes <literal>secure-boot</literal> from both the included and excluded feature lists.</para>
<para>With <option>--coco=</option>, setting this to <literal>yes</literal> additionally
requires the <literal>enrolled-keys</literal> 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 <literal>enrolled-keys</literal> hence keeps unsigned images bootable.</para>
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
</varlistentry>
@@ -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 <literal>amd-sev-snp</literal>
firmware feature; use <option>--firmware=</option> 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
<option>--secure-boot=</option>). Direct kernel boot via
<option>--linux=</option> is required so that the kernel, initrd and command line are hashed
into the launch measurement (<literal>kernel-hashes=on</literal>); booting the kernel off the
disk image via the firmware would leave it outside the measurement. Credentials passed via
@@ -376,7 +386,8 @@
<option>--firmware=</option> with a path to a firmware descriptor file to select a specific
one. The CPU model is fixed to <literal>host</literal>. 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
<option>--secure-boot=</option>).
When booting a UKI, the whole UKI PE is measured into RTMR 1, and the loaded sections are
measured individually by <command>systemd-stub</command> into RTMR 2. For direct linux boot,
firmware measures the kernel PE into RTMR 1, and the Linux EFI stub measures initrd and
+23 -9
View File
@@ -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");
}