]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
vmspawn: split out swtpm-setup logic, and beef it up a bit
authorLennart Poettering <lennart@amutable.com>
Thu, 19 Mar 2026 13:20:02 +0000 (14:20 +0100)
committerLennart Poettering <lennart@amutable.com>
Tue, 24 Mar 2026 13:42:39 +0000 (14:42 +0100)
src/shared/meson.build
src/shared/swtpm-util.c [new file with mode: 0644]
src/shared/swtpm-util.h [new file with mode: 0644]
src/vmspawn/vmspawn.c

index 3d4808dafee104a04c44b432f56faf8600fae7e5..3088b419a5de3975f54f3c5ce306ca66d4d6be70 100644 (file)
@@ -187,6 +187,7 @@ shared_sources = files(
         'socket-netlink.c',
         'specifier.c',
         'switch-root.c',
+        'swtpm-util.c',
         'tar-util.c',
         'tmpfile-util-label.c',
         'tomoyo-util.c',
diff --git a/src/shared/swtpm-util.c b/src/shared/swtpm-util.c
new file mode 100644 (file)
index 0000000..836f877
--- /dev/null
@@ -0,0 +1,158 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <unistd.h>
+
+#include "sd-json.h"
+
+#include "alloc-util.h"
+#include "escape.h"
+#include "fd-util.h"
+#include "json-util.h"
+#include "log.h"
+#include "memfd-util.h"
+#include "path-util.h"
+#include "pidref.h"
+#include "process-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "swtpm-util.h"
+
+int manufacture_swtpm(const char *state_dir, const char *secret) {
+        int r;
+
+        assert(state_dir);
+
+        _cleanup_free_ char *swtpm_setup = NULL;
+        r = find_executable("swtpm_setup", &swtpm_setup);
+        if (r < 0)
+                return log_error_errno(r, "Failed to find 'swtpm_setup' binary: %m");
+
+        _cleanup_strv_free_ char **args = strv_new(
+                        swtpm_setup,
+                         "--tpm2",
+                         "--print-profiles");
+        if (!args)
+                return log_oom();
+
+        _cleanup_close_ int mfd = memfd_new("swtpm-profiles");
+        if (mfd < 0)
+                return log_error_errno(mfd, "Failed to allocate memfd: %m");
+
+        if (DEBUG_LOGGING) {
+                _cleanup_free_ char *cmdline = quote_command_line(args, SHELL_ESCAPE_EMPTY);
+                log_debug("About to spawn: %s", strnull(cmdline));
+        }
+
+        _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
+        r = pidref_safe_fork_full(
+                        "(swtpm-lprof)",
+                        (int[]) { -EBADF, mfd, STDERR_FILENO },
+                        /* except_fds= */ NULL,
+                        /* n_except_fds= */ 0,
+                        FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL|FORK_REARRANGE_STDIO|FORK_LOG,
+                        &pidref);
+        if (r < 0)
+                return log_error_errno(r, "Failed to run swtpm_setup: %m");
+        if (r == 0) {
+                /* Child */
+                execvp(args[0], args);
+                log_error_errno(errno, "Failed to execute '%s': %m", args[0]);
+                _exit(EXIT_FAILURE);
+        }
+
+        r = pidref_wait_for_terminate_and_check("(swtpm-lprof)", &pidref, WAIT_LOG_ABNORMAL);
+        if (r < 0)
+                return r;
+
+        /* NB: we ignore the exit status of --print-profiles, it's broken. Instead we check if we have
+         * received a valid JSON object via STDOUT. */
+        (void) r;
+
+        _cleanup_free_ char *text = NULL;
+        r = read_full_file_full(
+                        mfd,
+                        /* filename= */ NULL,
+                        /* offset= */ 0,
+                        /* size= */ SIZE_MAX,
+                        /* flags= */ 0,
+                        /* bind_name= */ NULL,
+                        &text,
+                        /* ret_size= */ NULL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to read memory fd: %m");
+
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL;
+        const char *best_profile = NULL;
+        if (isempty(text))
+                log_notice("No list of supported profiles could be acquired from swtpm, assuming the implementation is too old to know the concept of profiles.");
+        else {
+                r = sd_json_parse(text, SD_JSON_PARSE_MUST_BE_OBJECT, &j, /* reterr_line= */ NULL, /* reterr_column= */ NULL);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to parse swtpm's --print-profiles output: %m");
+
+                sd_json_variant *v = sd_json_variant_by_key(j, "builtin");
+                if (v) {
+                        if (!sd_json_variant_is_array(v))
+                                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "'builtin' field is not an array: %m");
+
+                        sd_json_variant *i;
+                        JSON_VARIANT_ARRAY_FOREACH(i, v) {
+                                if (!sd_json_variant_is_object(i))
+                                        return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Profile object is not a JSON object.");
+
+                                sd_json_variant *n = sd_json_variant_by_key(i, "Name");
+                                if (!n) {
+                                        log_debug("Object in profiles array does not have a 'Name', skipping.");
+                                        continue;
+                                }
+
+                                if (!sd_json_variant_is_string(n))
+                                        return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Profile's 'Name' field is not a string.");
+
+                                const char *s = sd_json_variant_string(n);
+
+                                /* Pick the best of the default-v1, default-v2, … profiles */
+                                if (!startswith(s, "default-v"))
+                                        continue;
+                                if (!best_profile || strverscmp_improved(s, best_profile) > 0)
+                                        best_profile = s;
+                        }
+                }
+        }
+
+        strv_free(args);
+        args = strv_new(swtpm_setup,
+                        "--tpm-state", state_dir,
+                        "--tpm2",
+                        "--pcr-banks", "sha256",
+                        "--ecc",
+                        "--createek",
+                        "--create-ek-cert",
+                        "--create-platform-cert",
+                        "--not-overwrite");
+        if (!args)
+                return log_oom();
+
+        if (secret && strv_extendf(&args, "--keyfile=%s", secret) < 0)
+                return log_oom();
+
+        if (best_profile && strv_extendf(&args, "--profile-name=%s", best_profile) < 0)
+                return log_oom();
+
+        if (DEBUG_LOGGING) {
+                _cleanup_free_ char *cmdline = quote_command_line(args, SHELL_ESCAPE_EMPTY);
+                log_debug("About to spawn: %s", strnull(cmdline));
+        }
+
+        r = pidref_safe_fork("(swtpm-setup)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL|FORK_LOG|FORK_WAIT, /* ret= */ NULL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to run swtpm_setup: %m");
+        if (r == 0) {
+                /* Child */
+                execvp(args[0], args);
+                log_error_errno(errno, "Failed to execute '%s': %m", args[0]);
+                _exit(EXIT_FAILURE);
+        }
+
+        return 0;
+}
diff --git a/src/shared/swtpm-util.h b/src/shared/swtpm-util.h
new file mode 100644 (file)
index 0000000..9c1c737
--- /dev/null
@@ -0,0 +1,4 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+int manufacture_swtpm(const char *state_dir, const char *secret);
index fa433ba504b4f2d6bc432d0c2facc3ab43d1a917..a65f879449e6aa128223408492f8e2dab585917f 100644 (file)
@@ -72,6 +72,7 @@
 #include "stdio-util.h"
 #include "string-util.h"
 #include "strv.h"
+#include "swtpm-util.h"
 #include "sync-util.h"
 #include "terminal-util.h"
 #include "tmpfile-util.h"
@@ -1354,48 +1355,11 @@ static int start_tpm(
         if (r < 0)
                 return log_error_errno(r, "Failed to create TPM state directory '%s': %m", state_dir);
 
-        _cleanup_free_ char *swtpm_setup = NULL;
-        r = find_executable("swtpm_setup", &swtpm_setup);
+        r = manufacture_swtpm(state_dir, /* secret= */ NULL);
         if (r < 0)
-                return log_error_errno(r, "Failed to find swtpm_setup binary: %m");
-
-        /* Try passing --profile-name default-v2 first, in order to support RSA4096 pcrsig keys, which was
-         * added in 0.11. */
-        _cleanup_strv_free_ char **argv = strv_new(
-                        swtpm_setup,
-                        "--tpm-state", state_dir,
-                        "--tpm2",
-                        "--pcr-banks", "sha256",
-                        "--not-overwrite",
-                        "--profile-name", "default-v2");
-        if (!argv)
-                return log_oom();
-
-        r = pidref_safe_fork("(swtpm-setup)", FORK_CLOSE_ALL_FDS|FORK_LOG|FORK_WAIT, /* ret= */ NULL);
-        if (r == 0) {
-                /* Child */
-                execvp(argv[0], argv);
-                log_error_errno(errno, "Failed to execute '%s': %m", argv[0]);
-                _exit(EXIT_FAILURE);
-        }
-        if (r == -EPROTO) {
-                /* If swtpm_setup fails, try again removing the default-v2 profile, as it might be an older
-                 * version. */
-                strv_remove(argv, "--profile-name");
-                strv_remove(argv, "default-v2");
-
-                r = pidref_safe_fork("(swtpm-setup)", FORK_CLOSE_ALL_FDS|FORK_LOG|FORK_WAIT, /* ret= */ NULL);
-                if (r == 0) {
-                        /* Child */
-                        execvp(argv[0], argv);
-                        log_error_errno(errno, "Failed to execute '%s': %m", argv[0]);
-                        _exit(EXIT_FAILURE);
-                }
-        }
-        if (r < 0)
-                return log_error_errno(r, "Failed to run swtpm_setup: %m");
+                return r;
 
-        strv_free(argv);
+        _cleanup_strv_free_ char **argv = NULL;
         argv = strv_new(sd_socket_activate, "--listen", listen_address, swtpm, "socket", "--tpm2", "--tpmstate");
         if (!argv)
                 return log_oom();