]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
vmspawn: allow opt-in Secure Boot firmware for coco 42876/head
authorPaul Meyer <katexochen0@gmail.com>
Fri, 3 Jul 2026 13:52:10 +0000 (15:52 +0200)
committerPaul Meyer <katexochen0@gmail.com>
Mon, 6 Jul 2026 09:04:11 +0000 (11:04 +0200)
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>
man/systemd-vmspawn.xml
src/vmspawn/vmspawn.c

index 4ef1b18f57628200c275fb978111370e3915d732..2475a0464f9ba4d124f1d11e9e856023bef84d6a 100644 (file)
           <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>
           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>
           <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>
 
           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
           <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
index 2121fd29d0b04d9ca6250687efd1e3970c422d65..32fd6908c99a13951c692d9e1734da3a8d58ab33 100644 (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");
         }