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 @@ systemd1 systemd.special7 kernel-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);