diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml
index 98673e0a51..1292bbfbee 100644
--- a/man/kernel-command-line.xml
+++ b/man/kernel-command-line.xml
@@ -782,6 +782,16 @@
+
+ systemd.tpm2_software_fallback=
+
+ Controls whether to start a fallback software TPM service in case a hardware TPM is
+ not available, implemented by
+ systemd-tpm2-generator8.
+
+
+
+
systemd.factory_reset=
diff --git a/man/systemd-tpm2-generator.xml b/man/systemd-tpm2-generator.xml
index 2e22b99b5a..b45cf29be8 100644
--- a/man/systemd-tpm2-generator.xml
+++ b/man/systemd-tpm2-generator.xml
@@ -45,6 +45,14 @@
for it yet. The latter might be useful in environments where a suitable TPM2 driver for the available
hardware is not available.
+ The kernel command line option (which takes a
+ boolean argument, defaulting to false) may be used to enable an automatic software TPM fallback in case a
+ hardware TPM is not detected and
+ swtpm8 is
+ available. This pulls in the
+ systemd-tpm2-swtpm.service8
+ service.
+
systemd-tpm2-generator implements
systemd.generator7.
@@ -55,6 +63,7 @@
systemd1systemd.special7kernel-command-line7
+ systemd-tpm2-swtpm.service8
diff --git a/src/tpm2-setup/tpm2-generator.c b/src/tpm2-setup/tpm2-generator.c
index 043e0fd4b7..65f450daaf 100644
--- a/src/tpm2-setup/tpm2-generator.c
+++ b/src/tpm2-setup/tpm2-generator.c
@@ -1,8 +1,14 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include
+
+#include "dropin.h"
+#include "efivars.h"
#include "generator.h"
+#include "initrd-util.h"
#include "log.h"
#include "parse-util.h"
+#include "path-util.h"
#include "proc-cmdline.h"
#include "special.h"
#include "tpm2-util.h"
@@ -15,6 +21,7 @@
static const char *arg_dest = NULL;
static int arg_tpm2_wait = -1; /* tri-state: negative → don't know */
+static bool arg_tpm2_software_fallback = false;
static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
int r;
@@ -27,12 +34,19 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
log_warning_errno(r, "Failed to parse 'systemd.tpm2_wait=' kernel command line argument, ignoring: %s", value);
else
arg_tpm2_wait = r;
+
+ } else if (proc_cmdline_key_streq(key, "systemd.tpm2_software_fallback")) {
+ r = value ? parse_boolean(value) : 1;
+ if (r < 0)
+ log_warning_errno(r, "Failed to parse 'systemd.tpm2_software_fallback=' kernel command line argument, ignoring: %s", value);
+ else
+ arg_tpm2_software_fallback = r;
}
return 0;
}
-static int generate_tpm_target_symlink(void) {
+static int generate_tpm_target_symlink(Tpm2Support support, bool software_fallback_enabled) {
int r;
if (arg_tpm2_wait == 0) {
@@ -40,9 +54,7 @@ static int generate_tpm_target_symlink(void) {
return 0;
}
- if (arg_tpm2_wait < 0) {
- Tpm2Support support = tpm2_support();
-
+ if (arg_tpm2_wait < 0 && !software_fallback_enabled) {
if (FLAGS_SET(support, TPM2_SUPPORT_DRIVER)) {
log_debug("Not generating tpm2.target synchronization point, as TPM2 device is already present.");
return 0;
@@ -52,11 +64,6 @@ static int generate_tpm_target_symlink(void) {
log_debug("Not generating tpm2.target synchronization point, as firmware reports no TPM2 present.");
return 0;
}
-
- if (!FLAGS_SET(support, TPM2_SUPPORT_SYSTEM|TPM2_SUPPORT_SUBSYSTEM|TPM2_SUPPORT_LIBRARIES)) {
- log_debug("Not generating tpm2.target synchronization point, as userspace support for TPM2 is not complete.");
- return 0;
- }
}
r = generator_add_symlink(arg_dest, SPECIAL_SYSINIT_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_TPM2_TARGET);
@@ -66,6 +73,93 @@ static int generate_tpm_target_symlink(void) {
return 0;
}
+static int generate_swtpm_symlink(Tpm2Support support) {
+ int r;
+
+ if (!arg_tpm2_software_fallback)
+ return 0;
+
+ if (FLAGS_SET(support, TPM2_SUPPORT_DRIVER) || FLAGS_SET(support, TPM2_SUPPORT_FIRMWARE)) {
+ log_debug("Not generating software TPM units, as a TPM2 device is otherwise available.");
+ return 0;
+ }
+
+ if (!is_efi_boot()) { /* We need the ESP to store the TPM state. */
+ log_warning("TPM software fallback requested but not booted in EFI mode, not pulling in software TPM unit.");
+ return 0;
+ }
+
+ r = find_executable("swtpm", /* ret_filename= */ NULL);
+ if (r == -ENOENT) {
+ log_warning("TPM software fallback requested but swtpm not available, not pulling in software TPM unit.");
+ return 0;
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to determine if 'swtpm' is available: %m");
+
+ r = generator_add_symlink(arg_dest, SPECIAL_SYSINIT_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/systemd-tpm2-swtpm.service");
+ if (r < 0)
+ return log_error_errno(r, "Failed to hook in systemd-tpm2-swtpm.service: %m");
+
+ if (in_initrd())
+ /* Order + pull in the early ESP mount so that swtpm has a place to store its data. */
+ r = write_drop_in(
+ arg_dest,
+ "systemd-tpm2-swtpm.service",
+ 50, "esp",
+ "# Automatically generated by systemd-tpm2-generator\n\n"
+ "[Unit]\n"
+ "Wants=sysefi.mount\n"
+ "After=sysefi.mount\n");
+ else
+ /* Order (but not pull in) the regular ESP automount so that swtpm has a place to store its
+ * data. Note that it might be mounted to two different places depending on the existence of
+ * XBOOTLDR, hence order after both. */
+ r = write_drop_in(
+ arg_dest,
+ "systemd-tpm2-swtpm.service",
+ 50, "esp",
+ "# Automatically generated by systemd-tpm2-generator\n\n"
+ "[Unit]\n"
+ "After=boot.automount efi.automount\n");
+ if (r < 0)
+ return log_error_errno(r, "Failed to hook ESP mount before systemd-tpm2-swtpm.service: %m");
+
+ return 1; /* Tell caller we now created swtpm units */
+}
+
+static int generate_now(void) {
+ int r;
+
+ /* Let's shortcut things before we check for TPM2 support if no one cares anyway */
+ if (arg_tpm2_wait == 0 && !arg_tpm2_software_fallback) {
+ log_debug("Not generating tpm2.target synchronization point or activating software TPM, as turned off via kernel command line.");
+ return 0;
+ }
+
+ /* We are supposed to sync on TPM or do a software fallback, let's first and unconditionally validate this makes sense at
+ * all, i.e. if we have a suitable kernel+userspace. */
+ Tpm2Support support = tpm2_support();
+ if (!FLAGS_SET(support, TPM2_SUPPORT_SYSTEM|TPM2_SUPPORT_SUBSYSTEM|TPM2_SUPPORT_LIBRARIES)) {
+
+ /* Raise log level if things were explicitly configured */
+ log_full((arg_tpm2_wait > 0 ||
+ arg_tpm2_software_fallback) ? LOG_NOTICE : LOG_DEBUG,
+ "Not generating tpm2.target synchronization point or activating software TPM, as userspace support for TPM2 is not complete.");
+ return 0;
+ }
+
+ r = generate_swtpm_symlink(support);
+ if (r < 0)
+ return r;
+
+ r = generate_tpm_target_symlink(support, /* software_fallback_enabled= */ r > 0);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
static int run(const char *dest, const char *dest_early, const char *dest_late) {
int r;
@@ -75,7 +169,7 @@ static int run(const char *dest, const char *dest_early, const char *dest_late)
if (r < 0)
log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m");
- return generate_tpm_target_symlink();
+ return generate_now();
}
DEFINE_MAIN_GENERATOR_FUNCTION(run);