]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
vmspawn: deliver credentials via initrd cpio under SEV-SNP
authorPaul Meyer <katexochen0@gmail.com>
Sat, 23 May 2026 15:05:56 +0000 (17:05 +0200)
committerPaul Meyer <katexochen0@gmail.com>
Wed, 24 Jun 2026 10:47:42 +0000 (12:47 +0200)
Previously, --load-credential / --set-credential were rejected outright
under --coco=sev-snp because the SMBIOS type-11 transport isn't covered
by the launch measurement. PID1 wouldn't have accepted those credentials
anyway (import_credentials_smbios() refuses any SMBIOS-sourced credentials
under a confidential VM).

Instead, when SNP is in use and credentials are present, synthesize a
newc cpio archive containing each credential at
.extra/system_credentials/<id>.cred and append it to the initrd list.
The existing merge_initrds() path then concatenates it into the single
initrd file QEMU loads, which kernel-hashes=on covers in the SEV-SNP
launch digest. PID1's import_credentials_boot() picks them up from the
trusted /.extra/system_credentials/ path and routes them to the @system
bucket, so units can consume them via LoadCredential= unchanged.

Direct kernel boot (--linux=) is already required under SNP, so the
initrd is always under our control here. The cpio synthesis happens
after all internal machine_credential_add()/machine_credential_load()
call sites so the archive captures the complete credential set (journal
forwarding, vmm.notify_socket, ssh ephemeral keys, etc.).

The cpio path is intentionally scoped to SNP: it requires a guest PID1
that knows about /.extra/system_credentials/, and we don't want to
regress credential delivery for non-CoCo guests running older systemd
versions in the guest. Consider switching when the new path is widely
available.

Signed-off-by: Paul Meyer <katexochen0@gmail.com>
src/vmspawn/vmspawn.c

index 36966122f4a5420aa79af0d22aa64b90442705d8..b8d42c55873acc2b19cea8f221006d9e2c62d773 100644 (file)
@@ -46,6 +46,7 @@
 #include "hostname-setup.h"
 #include "hostname-util.h"
 #include "id128-util.h"
+#include "initrd-cpio.h"
 #include "kernel-image.h"
 #include "log.h"
 #include "machine-bind-user.h"
@@ -57,8 +58,8 @@
 #include "namespace-util.h"
 #include "netif-util.h"
 #include "nsresource.h"
-#include "osc-context.h"
 #include "options.h"
+#include "osc-context.h"
 #include "pager.h"
 #include "parse-argument.h"
 #include "parse-util.h"
@@ -3424,26 +3425,6 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
                 }
         }
 
-        char *initrd = NULL;
-        _cleanup_(rm_rf_physical_and_freep) char *merged_initrd = NULL;
-        size_t n_initrds = strv_length(arg_initrds);
-
-        if (n_initrds == 1)
-                initrd = arg_initrds[0];
-        else if (n_initrds > 1) {
-                r = merge_initrds(&merged_initrd);
-                if (r < 0)
-                        return r;
-
-                initrd = merged_initrd;
-        }
-
-        if (initrd) {
-                r = strv_extend_many(&cmdline, "-initrd", initrd);
-                if (r < 0)
-                        return log_oom();
-        }
-
         if (arg_forward_journal) {
                 _cleanup_free_ char *listen_address = NULL;
                 if (asprintf(&listen_address, "vsock:2:%u", child_cid) < 0)
@@ -3545,9 +3526,54 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
                         return log_error_errno(r, "Failed to add VSOCK credential: %m");
         }
 
-        r = cmdline_add_credentials(&cmdline, smbios_dir_fd, smbios_dir);
-        if (r < 0)
-                return r;
+        /* Under --coco=sev-snp the SMBIOS and fw_cfg channels normally used to deliver credentials are
+         * not covered by the launch measurement and are silently discarded by the guest PID1 in
+         * confidential VMs. Instead, package credentials into a cpio archive appended to the initrd
+         * (mirroring what systemd-stub does for ESP credentials) so they enter the launch measurement
+         * via QEMU's "kernel-hashes=on". The new initrd path requires a guest PID1 that knows about
+         * /.extra/system_credentials/, so we keep this scoped to SNP for now. Non-CoCo guests
+         * continue to use the SMBIOS path below, which works with older systemd versions too.
+         * Must run after all credential-mutating calls above so the cpio captures the complete set. */
+        bool use_initrd_cpio = arg_confidential_computing == COCO_AMD_SEV_SNP &&
+                               arg_credentials.n_credentials > 0;
+
+        _cleanup_(unlink_and_freep) char *credentials_cpio_path = NULL;
+        if (use_initrd_cpio) {
+                r = initrd_cpio_credentials_to_tempfile(&arg_credentials, &credentials_cpio_path);
+                if (r < 0)
+                        return r;
+                r = strv_extend(&arg_initrds, credentials_cpio_path);
+                if (r < 0)
+                        return log_oom();
+        }
+
+        char *initrd = NULL;
+        _cleanup_(rm_rf_physical_and_freep) char *merged_initrd = NULL;
+        size_t n_initrds = strv_length(arg_initrds);
+
+        if (n_initrds == 1)
+                initrd = arg_initrds[0];
+        else if (n_initrds > 1) {
+                r = merge_initrds(&merged_initrd);
+                if (r < 0)
+                        return r;
+
+                initrd = merged_initrd;
+        }
+
+        if (initrd) {
+                r = strv_extend_many(&cmdline, "-initrd", initrd);
+                if (r < 0)
+                        return log_oom();
+        }
+
+        /* Under SNP, credentials flow via the initrd cpio above. For everyone else, use the
+         * SMBIOS/fw_cfg/cmdline path. */
+        if (!use_initrd_cpio) {
+                r = cmdline_add_credentials(&cmdline, smbios_dir_fd, smbios_dir);
+                if (r < 0)
+                        return r;
+        }
 
         r = cmdline_add_kernel_cmdline(&cmdline, smbios_dir_fd, smbios_dir);
         if (r < 0)
@@ -4115,9 +4141,6 @@ static int verify_arguments(void) {
                 if (set_contains(arg_firmware_features_include, "secure-boot"))
                         return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
                                                "--secure-boot=yes cannot be combined with --coco.");
-                if (arg_credentials.n_credentials != 0)
-                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                               "SMBIOS credentials aren't trusted by the confidential computing guest and will be rejected.");
                 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.