tpm2-generator: if requested run things with an swtpm

We want to start the software TPM fallback only if no real hw is
evailable and if the user opts-in to this behaviour. Add a generator
that drives all this, based on kernel command line configuration.
This commit is contained in:
Lennart Poettering
2026-03-09 13:08:28 +01:00
parent 056c21aaeb
commit 750795d103
3 changed files with 123 additions and 10 deletions

View File

@@ -782,6 +782,16 @@
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>systemd.tpm2_software_fallback=</varname></term>
<listitem><para>Controls whether to start a fallback software TPM service in case a hardware TPM is
not available, implemented by
<citerefentry><refentrytitle>systemd-tpm2-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>systemd.factory_reset=</varname></term>

View File

@@ -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.</para>
<para>The <option>systemd.tpm2_software_fallback=</option> 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
<citerefentry><refentrytitle>swtpm</refentrytitle><manvolnum>8</manvolnum></citerefentry> is
available. This pulls in the
<citerefentry><refentrytitle>systemd-tpm2-swtpm.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
service.</para>
<para><command>systemd-tpm2-generator</command> implements
<citerefentry><refentrytitle>systemd.generator</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
</refsect1>
@@ -55,6 +63,7 @@
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd.special</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>kernel-command-line</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-tpm2-swtpm.service</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
</simplelist></para>
</refsect1>
</refentry>

View File

@@ -1,8 +1,14 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <unistd.h>
#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);