optionally run a software TPM at boot as fallback for TPM less machines (#41016)

In various scenarios it's useful to be able to run a software TPM as
fallback on a machine if it doesn't natively have a hardware for it, in
order to provide somewhat systematic interfacing for legacy machines.
This adds the infrastructure for it.

Relevant parts:

1. On EFI systems sd-stub will now generate a random secret on first
boot and store it in a persistent NV variable, which is marked
inaccessible to the OS. It then derives a per-OS secret from that which
is passed to the OS via the initrd logic. This is generally useful, but
in particular is intended to secure the software TPM at least a bit: it
provides better security than nothing (i.e. the only protection in place
is that the firmwrae protections work, but this is also what shim relies
on, hence maybe not too bad), and allows swtpm to encrypt its storage
with something.

2. systemd-tpm2-generator is extended to optionally start an swtpm if no
tpm hw is detected. Because this is of course a major downgrade in
security, this has to be requested explicitly at boot via a kernel
cmdline switch.

3. This optionally mounts the ESP from the initrd. This is general
infrastructure, and has been requested before, but is particularly
interesting in the context of software TPMs: the state needs to be
stored somewhere, and that before the rootfs can be unlocked.

4. This introduces a special new separator measurement for PCRs 0-7 that
isolates all measurements from pre-os/uefi world from those done by the
OS. I added this for three reasons:
- in the swtpm case we'll not have any pre-os/uefi measurements, and we
need to be able to determine cleanly that this is the case. this hence
is supposed to play a similar role as the usual firmware separator
measurement, that however cleanly fixates to the PCRs even the case
where the firmware measurements don't exist at all
   - this is a very comprehensive fix for #40567
- not all firmwares generate the firmware separator at all, but it is
essential to seal off firmware variables from OS generated ones. This
can fill the void to some degree.
6. This introduces a new kernel cmdline switch
systemd.tpm2-measured-os=1, which allows force enabling all our
measurement logic, even if UKI TPM measurements are not done. This is
supposed to be used for the swtpm case so that one gets all the
measurements even without having the early boot verified boot chain in
place.

Benefits of all this: systems that care about TPMs have a (lower
security) compat glue in place that allows supporting legacy hw the same
way as modern hw in many ways, so that remote attestation and other uses
can reasonably work with the same codepaths.

Also see: https://github.com/lxc/incus-os/pull/667 regarding prior
similar work.

Fixes: #40567
This commit is contained in:
Lennart Poettering
2026-03-26 16:12:46 +01:00
committed by GitHub
56 changed files with 746 additions and 97 deletions
+9 -8
View File
@@ -133,6 +133,14 @@ Features:
* add service file setting to force the fwmark (a la SO_MARK) to some value, so
that we can allowlist certain services for imds this way.
* lock down swtpm a bit to make it harder to extract keys from it as it is
running. i.e. make ptracing + termination hard from the outside. also run
swtpm as unpriv user (not trivial, probably requires patch swtpm, as it needs
to allocate vtpm device), to lock it down from the inside.
* once swtpm's sd_notify() support has landed in the distributions, remove the
invocation in tpm2-swtpm.c and let swtpm handle it.
* make systemd work nicely without /bin/sh, logins and associated shell tools around
- make sure debug shell service (sushell) has a nice failure mode, prints a message and reboots
- varlink interface for "systemctl start" and friends
@@ -156,13 +164,6 @@ Features:
* on first login of a user, measure its identity to some nvpcr
* optionally spawn an swtpm instance if a system doesn't have a native tpm, do
it via the tpm generator
* add a secret key logic to sd-stub, that uses early-boot efi variables for
storing, that can be used as fallback logic for tpm-less systems for disk
encryption, and swtpm state encryption.
* sd-lldp: pick up 802.3 maximum frame size/mtu, to be able to detect jumbo
frame capable networks
@@ -562,7 +563,7 @@ Features:
service into the early boot, waiting for the DMI and network device to show
up.
* Add UKI profile conditioning so that profles are only available if secure
* Add UKI profile conditioning so that profiles are only available if secure
boot is turned off, or only on. similar, add conditions on TPM availability,
network boot, and other conditions.
+22
View File
@@ -782,6 +782,28 @@
<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.tpm2_measured_os=</varname></term>
<listitem><para>Controls whether to execute various boot and runtime TPM PCR measurements. Takes a
boolean argument. If not specified explicitly this behaviour is enabled automatically in case
<citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> is
used and it succeeded in doing pre-boot measurements of the booted UKI, and otherwise
disabled.</para>
<xi:include href="version-info.xml" xpointer="v261"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>systemd.factory_reset=</varname></term>
+5
View File
@@ -1138,6 +1138,7 @@ manpages = [
'systemd-pcrfs@.service',
'systemd-pcrmachine.service',
'systemd-pcrnvdone.service',
'systemd-pcrosseparator.service',
'systemd-pcrphase-initrd.service',
'systemd-pcrphase-sysinit.service',
'systemd-pcrproduct.service'],
@@ -1237,6 +1238,10 @@ manpages = [
'8',
['systemd-tpm2-setup', 'systemd-tpm2-setup-early.service'],
'ENABLE_BOOTLOADER'],
['systemd-tpm2-swtpm.service',
'8',
['systemd-tpm2-swtpm'],
'ENABLE_BOOTLOADER'],
['systemd-tty-ask-password-agent', '1', [], ''],
['systemd-udev-settle.service', '8', [], ''],
['systemd-udevd.service',
+11 -2
View File
@@ -75,6 +75,13 @@
discovery based on the boot loader reported ESP which is also enabled if no <literal>root=</literal>
parameter is specified at all. (The latter relies on <command>systemd-udevd.service</command>'s
<filename>/dev/gpt-auto-root</filename> block device symlink generation).</para></listitem>
<listitem><para>It will also generate a unit file mounting the EFI System Partition (ESP) to
<filename>/sysefi/</filename>, if applicable. Unlike the file systems mentioned above this mount is not
activated by default however, but can be pulled in by services requiring ESP access from within the
initrd. Note that this mount point is initrd-specific and does not make use of autofs. The ESP is
typically mounted at a different place and via autofs once the system transitions out of the
initrd.</para></listitem>
</itemizedlist>
</para>
@@ -179,7 +186,7 @@
<entry><constant>SD_GPT_ESP</constant></entry>
<entry><constant>c12a7328-f81f-11d2-ba4b-00a0c93ec93b</constant></entry>
<entry>EFI System Partition (ESP)</entry>
<entry><filename>/efi/</filename> or <filename>/boot/</filename></entry>
<entry><filename>/efi/</filename> or <filename>/boot/</filename> once the system transitioned out of the initrd, <filename>/sysefi/</filename> before</entry>
<entry>The first partition with this type UUID located on the same disk as the root partition is mounted to <filename>/boot/</filename> or <filename>/efi/</filename>, see below.</entry>
</row>
<row>
@@ -259,7 +266,9 @@
<para>The ESP is mounted to <filename>/boot/</filename> if that directory exists and is not used for
XBOOTLDR, and otherwise to <filename>/efi/</filename>. Same as for <filename>/boot/</filename>, an
automount unit is used. The mount point will be created if necessary.</para>
automount unit is used. The mount point will be created if necessary. These apply once the system
transitioned out of the initrd phase. Before that, if components in the initrd require ESP access, it
will be mounted to <filename>/sysefi/</filename>.</para>
<para>No configuration is created for mount points that are configured in <citerefentry
project='man-pages'><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry> or when
+9 -1
View File
@@ -21,6 +21,7 @@
<refname>systemd-pcrphase-sysinit.service</refname>
<refname>systemd-pcrphase-initrd.service</refname>
<refname>systemd-pcrmachine.service</refname>
<refname>systemd-pcrosseparator.service</refname>
<refname>systemd-pcrproduct.service</refname>
<refname>systemd-pcrfs-root.service</refname>
<refname>systemd-pcrfs@.service</refname>
@@ -34,6 +35,7 @@
<para><filename>systemd-pcrphase-sysinit.service</filename></para>
<para><filename>systemd-pcrphase-initrd.service</filename></para>
<para><filename>systemd-pcrmachine.service</filename></para>
<para><filename>systemd-pcrosseparator.service</filename></para>
<para><filename>systemd-pcrproduct.service</filename></para>
<para><filename>systemd-pcrfs-root.service</filename></para>
<para><filename>systemd-pcrfs@.service</filename></para>
@@ -53,6 +55,11 @@
(see <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>) into
PCR 15.</para>
<para><filename>systemd-pcrosseparator.service</filename> is a system service that measures the word
<literal>os-separator</literal> into PCRs 0-7, 9, 12-14. This acts as additional separator measurement
separating pre-boot (i.e. firmware + bootloader) measurements from OS measurements, and in particular
"seals" off the firmware PCRs.</para>
<para><filename>systemd-pcrproduct.service</filename> is a system service that measures the firmware
product UUID (as provided by one of SMBIOS, Devicetree, …) into a NvPCR named
<literal>hardware</literal>.</para>
@@ -168,7 +175,8 @@
<listitem><para>Takes the index of the PCR to extend. If <option>--machine-id</option> or
<option>--file-system=</option> are specified defaults to 15, otherwise (and unless
<option>--product-id</option> is specified) defaults to 11. May not be combined with
<option>--product-id</option> is specified) defaults to 11. May be specified multiple times, in order
to measure to multiple PCRs at the same time. May not be combined with
<option>--nvpcr=</option>.</para>
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
+9
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>
+63
View File
@@ -0,0 +1,63 @@
<?xml version="1.0"?>
<!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<!-- SPDX-License-Identifier: LGPL-2.1-or-later -->
<refentry id="systemd-tpm2-swtpm.service" conditional='ENABLE_BOOTLOADER HAVE_TPM2'
xmlns:xi="http://www.w3.org/2001/XInclude">
<refentryinfo>
<title>systemd-tpm2-swtpm.service</title>
<productname>systemd</productname>
</refentryinfo>
<refmeta>
<refentrytitle>systemd-tpm2-swtpm.service</refentrytitle>
<manvolnum>8</manvolnum>
</refmeta>
<refnamediv>
<refname>systemd-tpm2-swtpm.service</refname>
<refname>systemd-tpm2-swtpm</refname>
<refpurpose>Provide a fallback software TPM</refpurpose>
</refnamediv>
<refsynopsisdiv>
<para><filename>systemd-tpm2-swtpm.service</filename></para>
<para><filename>/usr/lib/systemd/systemd-tpm2-swtpm</filename></para>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>The <filename>systemd-tpm2-swtpm.service</filename> provides fallback software TPM functionality,
intended for use in environments where a discrete or firmware TPM ("hardware TPM") is not available. It is
pulled into the boot process by
<citerefentry><refentrytitle>systemd-tpm2-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
if a hardware TPM is not available, and the system is configured to provide a software TPM in that case.</para>
<para>Note that a software TPM provides only very weak security properties compared to a hardware TPM,
and hence should only be used as a fallback mechanism if a hardware TPM is not available but TPM
semantics are desired. This service ultimately wraps
<citerefentry><refentrytitle>swtpm</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
<para>If the boot secret <filename>/.extra/boot-secret</filename> (in the initrd) or
<filename>/run/systemd/stub/boot-secret</filename> (on the host) is available the software TPM NVRAM
storage is encrypted with this key. See
<citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> for
details.</para>
<para>The TPM NVRAM storage is placed on the EFI System Partition as it needs to be accessible during
very early boot-up, in particular before the root file system is decrypted and mounted.</para>
</refsect1>
<refsect1>
<title>See Also</title>
<para><simplelist type="inline">
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-tpm2-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>swtpm</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
</simplelist></para>
</refsect1>
</refentry>
+4
View File
@@ -1605,6 +1605,10 @@
<entry>measured-uki</entry>
<entry>Unified Kernel Image with PCR 11 Measurements, as per <citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry>. <xi:include href="version-info.xml" xpointer="v255"/></entry>
</row>
<row>
<entry>measured-os</entry>
<entry>OS PCR measurements enabled. This is typically equivalent to <varname>measured-uki</varname>, however may also be set explicitly via the <varname>systemd.tpm2_measured_os=</varname> kernel command line switch, see <citerefentry><refentrytitle>kernel-command-line</refentrytitle><manvolnum>7</manvolnum></citerefentry> for details. The various system services doing boot and runtime measurements are conditioned on this flag. <xi:include href="version-info.xml" xpointer="v261"/></entry>
</row>
</tbody>
</tgroup>
</table>
+1
View File
@@ -123,6 +123,7 @@ Packages=
sed
socat
strace
swtpm
tar
tree
util-linux
@@ -63,6 +63,7 @@ Packages=
softhsm
squashfs-tools
stress-ng
swtpm-tools
tpm2-tools
veritysetup
vim-common
@@ -71,6 +71,7 @@ Packages=
softhsm2
squashfs-tools
stress-ng
swtpm-tools
tgt
tpm2-tools
tzdata
+1
View File
@@ -16,4 +16,5 @@ Packages=
findutils
grep
sed
swtpm
tar
@@ -7,6 +7,7 @@ Distribution=|fedora
[Content]
PrepareScripts=%D/mkosi/mkosi.conf.d/centos-fedora/systemd.prepare
Packages=
swtpm-tools
tpm2-tools
VolatilePackages=
@@ -9,6 +9,7 @@ PrepareScripts=%D/mkosi/mkosi.conf.d/debian-ubuntu/systemd.prepare
Packages=
btrfs-progs
tpm2-tools
swtpm-tools
VolatilePackages=
libsystemd-shared
+1 -1
View File
@@ -56,7 +56,7 @@ int verb_nvpcrs(int argc, char *argv[], uintptr_t _data, void *userdata) {
_cleanup_(table_unrefp) Table *table = NULL;
int r;
bool have_tpm2 = tpm2_is_fully_supported();
bool have_tpm2 = tpm2_is_mostly_supported();
if (!have_tpm2)
log_notice("System lacks full TPM2 support, not showing NvPCR state.");
+2 -2
View File
@@ -101,8 +101,8 @@ int verb_pcrs(int argc, char *argv[], uintptr_t _data, void *userdata) {
const char *alg = NULL;
int r;
if (!tpm2_is_fully_supported())
log_notice("System lacks full TPM2 support, not showing PCR state.");
if (!tpm2_is_mostly_supported())
log_notice("System lacks sufficient TPM2 support, not showing PCR state.");
else {
r = get_pcr_alg(&alg);
if (r < 0)
+1 -1
View File
@@ -1706,7 +1706,7 @@ int write_data_file_atomic_at(
return r;
}
r = fchmod_umask(fd, 0644);
r = fchmod_umask(fd, FLAGS_SET(flags, WRITE_DATA_FILE_MODE_0400) ? 0400 : 0644);
if (r < 0)
return r;
+1
View File
@@ -166,6 +166,7 @@ int fopen_mode_to_flags(const char *mode);
typedef enum WriteDataFileFlags {
WRITE_DATA_FILE_MKDIR_0755 = 1 << 0,
WRITE_DATA_FILE_MODE_0400 = 1 << 1,
} WriteDataFileFlags;
int write_data_file_atomic_at(int dir_fd, const char *path, const struct iovec *iovec, WriteDataFileFlags flags);
+10
View File
@@ -477,6 +477,16 @@ int verb_status(int argc, char *argv[], uintptr_t _data, void *userdata) {
printf(" Measured UKI: %sfailed%s (%m)\n", ansi_highlight_red(), ansi_normal());
}
k = efi_measured_os(LOG_DEBUG);
if (k > 0)
printf(" Measured OS: %syes%s\n", ansi_highlight_green(), ansi_normal());
else if (k == 0)
printf(" Measured OS: no\n");
else {
errno = -k;
printf(" Measured OS: %sfailed%s (%m)\n", ansi_highlight_red(), ansi_normal());
}
k = efi_get_reboot_to_firmware();
if (k > 0)
printf(" Boot into FW: %sactive%s\n", ansi_highlight_yellow(), ansi_normal());
+4 -4
View File
@@ -1030,11 +1030,11 @@ static int measure_volume_key(
return 0;
}
r = efi_measured_uki(LOG_WARNING);
r = efi_measured_os(LOG_WARNING);
if (r < 0)
return r;
if (r == 0) {
log_debug("Kernel stub did not measure kernel image into the expected PCR, skipping userspace volume key measurement, too.");
log_debug("OS measurements not explicitly requested and kernel stub did not measure kernel image into the expected PCR, skipping userspace volume key measurement, too.");
return 0;
}
@@ -1109,11 +1109,11 @@ static int measure_keyslot(
}
#if HAVE_TPM2
r = efi_measured_uki(LOG_WARNING);
r = efi_measured_os(LOG_WARNING);
if (r < 0)
return r;
if (r == 0) {
log_debug("Kernel stub did not measure kernel image into the expected PCR, skipping userspace key slot measurement, too.");
log_debug("OS measurements not explicitly requested and kernel stub did not measure kernel image into the expected PCR, skipping userspace key slot measurement, too.");
return 0;
}
+2 -2
View File
@@ -672,9 +672,9 @@ static int add_mount(
}
if (flags & MOUNT_PCRFS) {
r = efi_measured_uki(LOG_WARNING);
r = efi_measured_os(LOG_WARNING);
if (r == 0)
log_debug("Kernel stub did not measure kernel image into PCR, skipping userspace measurement, too.");
log_debug("OS measurements not explicitly requested and kernel stub did not measure kernel image into PCR, skipping userspace measurement, too.");
else if (r > 0) {
r = generator_hook_up_pcrfs(dest, where, target_unit);
if (r < 0)
+71 -16
View File
@@ -115,11 +115,13 @@ static int add_cryptsetup(
return log_oom();
}
r = efi_measured_uki(LOG_WARNING);
if (r > 0)
r = efi_measured_os(LOG_WARNING);
if (r > 0) {
/* Enable TPM2 based unlocking automatically, if we have a TPM. See #30176. */
if (!strextend_with_separator(&options, ",", "tpm2-device=auto"))
return log_oom();
} else if (r == 0)
log_debug("Will not enable TPM based unlocking of volume '%s', OS measurements are not explicitly requested and not booted via systemd-stub with measurements enabled.", id);
if (FLAGS_SET(flags, MOUNT_MEASURE)) {
/* We only measure the root volume key into PCR 15 if we are booted with sd-stub (i.e. in a
@@ -130,7 +132,7 @@ static int add_cryptsetup(
if (!strextend_with_separator(&options, ",", "tpm2-measure-pcr=yes,tpm2-measure-keyslot-nvpcr=yes"))
return log_oom();
if (r == 0)
log_debug("Will not measure volume key of volume '%s', not booted via systemd-stub with measurements enabled.", id);
log_debug("Will not measure volume key of volume '%s', as OS measurements are not explicitly requested and not booted via systemd-stub with measurements enabled.", id);
}
r = generator_write_cryptsetup_service_section(f, id, what, NULL, options);
@@ -240,11 +242,11 @@ static int add_veritysetup(
return log_oom();
if (FLAGS_SET(flags, MOUNT_MEASURE)) {
r = efi_measured_uki(LOG_WARNING);
r = efi_measured_os(LOG_WARNING);
if (r > 0 && !strextend_with_separator(&options, ",", "tpm2-measure-nvpcr=yes"))
return log_oom();
if (r == 0)
log_debug("Will not measure root hash/signature of volume '%s', not booted via systemd-stub with measurements enabled.", id);
else if (r == 0)
log_debug("Will not measure root hash/signature of volume '%s', OS measurements not explicitly requested and not booted via systemd-stub with measurements enabled.", id);
}
r = generator_write_veritysetup_service_section(
@@ -307,7 +309,8 @@ static int add_mount(
MountPointFlags flags,
const char *options,
const char *description,
const char *post) {
const char *post,
const char *conflicts) {
_cleanup_free_ char *unit = NULL, *crypto_what = NULL, *opts_filtered = NULL;
_cleanup_fclose_ FILE *f = NULL;
@@ -377,6 +380,12 @@ static int add_mount(
if (r < 0)
return r;
if (conflicts)
fprintf(f,
"Conflicts=%1$s\n"
"Before=%1$s\n",
conflicts);
fprintf(f,
"\n"
"[Mount]\n"
@@ -493,7 +502,8 @@ static int add_partition_mount(
(STR_IN_SET(id, "root", "var") ? MOUNT_MEASURE : 0), /* by default measure rootfs and /var, since they contain the "identity" of the system */
options,
description,
SPECIAL_LOCAL_FS_TARGET);
SPECIAL_LOCAL_FS_TARGET,
/* conflicts= */ NULL);
}
static int add_partition_swap(DissectedPartition *p) {
@@ -582,7 +592,8 @@ static int add_automount(
flags,
options,
description,
/* post= */ NULL);
/* post= */ NULL,
/* conflicts= */ NULL);
if (r < 0)
return r;
@@ -919,7 +930,8 @@ static int add_root_mount(void) {
MOUNT_MEASURE,
options,
"Root Partition",
in_initrd() ? SPECIAL_INITRD_ROOT_FS_TARGET : SPECIAL_LOCAL_FS_TARGET);
in_initrd() ? SPECIAL_INITRD_ROOT_FS_TARGET : SPECIAL_LOCAL_FS_TARGET,
/* conflicts= */ NULL);
#else
return 0;
#endif
@@ -995,7 +1007,8 @@ static int add_usr_mount(void) {
/* flags= */ 0,
options,
"/usr/ Partition",
in_initrd() ? SPECIAL_INITRD_USR_FS_TARGET : SPECIAL_LOCAL_FS_TARGET);
in_initrd() ? SPECIAL_INITRD_USR_FS_TARGET : SPECIAL_LOCAL_FS_TARGET,
/* conflicts= */ NULL);
if (r < 0)
return r;
@@ -1009,7 +1022,8 @@ static int add_usr_mount(void) {
MOUNT_VALIDATEFS,
"bind",
"/usr/ Partition (Final)",
SPECIAL_INITRD_FS_TARGET);
SPECIAL_INITRD_FS_TARGET,
/* conflicts= */ NULL);
if (r < 0)
return r;
}
@@ -1017,6 +1031,49 @@ static int add_usr_mount(void) {
return 0;
}
static int add_early_esp_mount(void) {
int r;
/* Early ESP discovery is a bit different than the other mounts here: it's purely about the initrd,
* and goes away during the transition to the host (where it might likely be mounted again, but then
* via autofs, hence lazily). Moreover, the location is fixed → /sysefi/, i.e. we do not bother with
* XBOOTLDR vs. ESP for this. Also, the mount is not pulled in by default, but is expected to be
* pulled in by the component that uses it.
*
* The initial usecase for this is software TPM that needs a place to store its state before the root
* file system can be mounted.
*
* Or in other words: this is much simpler, more focussed on a short-lived boot-time operation than
* the regular logic during later boot. */
if (!in_initrd())
return 0;
if (!is_efi_boot())
return 0;
_cleanup_free_ char *options = NULL;
r = partition_pick_mount_options(
PARTITION_ESP,
"vfat",
/* rw= */ true,
/* discard= */ false,
&options,
/* ret_ms_flags= */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to pick ESP mount options: %m");
return add_mount("esp",
"/dev/disk/by-designator/esp",
"/sysefi/",
"vfat",
MOUNT_RW,
options,
"EFI System Partition (Early)",
/* post= */ NULL,
/* conflicts= */ "initrd-switch-root.target");
}
static int process_loader_partitions(DissectedPartition *esp, DissectedPartition *xbootldr) {
sd_id128_t loader_uuid;
int r;
@@ -1190,7 +1247,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
return 0;
/* Disable root disk logic if there's a root= value specified (unless it happens to be
* "gpt-auto" or "gpt-auto-force") */
* "gpt-auto", "gpt-auto-force", "dissect", "dissect-force") */
arg_auto_root = parse_gpt_auto_root("root=", value);
assert(arg_auto_root >= 0);
@@ -1244,9 +1301,6 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
if (proc_cmdline_value_missing(key, value))
return 0;
/* Disable root disk logic if there's a root= value specified (unless it happens to be
* "gpt-auto" or "gpt-auto-force") */
arg_auto_usr = parse_gpt_auto_root("mount.usr=", value);
assert(arg_auto_usr >= 0);
@@ -1325,6 +1379,7 @@ static int run(const char *dest, const char *dest_early, const char *dest_late)
r = 0;
RET_GATHER(r, add_root_mount());
RET_GATHER(r, add_usr_mount());
RET_GATHER(r, add_early_esp_mount());
RET_GATHER(r, add_mounts());
return r;
@@ -86,7 +86,7 @@ static int add_dissected_swap_cryptsetup(void) {
r = generator_write_cryptsetup_service_section(
f, "swap", DISSECTED_SWAP_LUKS_DEVICE,
/* key_file= */ NULL,
efi_measured_uki(LOG_DEBUG) > 0 ? "tpm2-device=auto" : NULL);
efi_measured_os(LOG_DEBUG) > 0 ? "tpm2-device=auto" : NULL);
if (r < 0)
return r;
+37 -28
View File
@@ -29,7 +29,7 @@ static char **arg_banks = NULL;
static char *arg_file_system = NULL;
static bool arg_machine_id = false;
static bool arg_product_id = false;
static unsigned arg_pcr_index = UINT_MAX;
static uint32_t arg_pcr_mask = 0;
static char *arg_nvpcr_name = NULL;
static bool arg_varlink = false;
static bool arg_early = false;
@@ -139,11 +139,16 @@ static int parse_argv(int argc, char *argv[]) {
}
case ARG_PCR:
if (isempty(optarg)) {
arg_pcr_mask = 0;
break;
}
r = tpm2_pcr_index_from_string(optarg);
if (r < 0)
return log_error_errno(r, "Failed to parse PCR index: %s", optarg);
arg_pcr_index = r;
arg_pcr_mask |= INDEX_TO_MASK(uint32_t, r);
break;
case ARG_NVPCR:
@@ -213,7 +218,7 @@ static int parse_argv(int argc, char *argv[]) {
if (!!arg_file_system + arg_machine_id + arg_product_id > 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--file-system=, --machine-id, --product-id may not be combined.");
if (arg_pcr_index != UINT_MAX && arg_nvpcr_name)
if (arg_pcr_mask != 0 && arg_nvpcr_name)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--pcr= and --nvpcr= may not be combined.");
r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT);
@@ -221,11 +226,11 @@ static int parse_argv(int argc, char *argv[]) {
return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
if (r > 0)
arg_varlink = true;
else if (arg_pcr_index == UINT_MAX && !arg_nvpcr_name) {
arg_pcr_index =
(arg_file_system || arg_machine_id) ? TPM2_PCR_SYSTEM_IDENTITY : /* → PCR 15 */
!arg_product_id ? TPM2_PCR_KERNEL_BOOT : /* → PCR 11 */
UINT_MAX;
else if (arg_pcr_mask == 0 && !arg_nvpcr_name) {
arg_pcr_mask =
(arg_file_system || arg_machine_id) ? INDEX_TO_MASK(uint32_t, TPM2_PCR_SYSTEM_IDENTITY) : /* → PCR 15 */
!arg_product_id ? INDEX_TO_MASK(uint32_t, TPM2_PCR_KERNEL_BOOT) : /* → PCR 11 */
0;
r = free_and_strdup_warn(&arg_nvpcr_name, arg_product_id ? "hardware" : NULL);
if (r < 0)
@@ -235,7 +240,7 @@ static int parse_argv(int argc, char *argv[]) {
return 1;
}
static int determine_banks(Tpm2Context *c, unsigned target_pcr_nr) {
static int determine_banks(Tpm2Context *c, uint32_t target_pcr_mask) {
_cleanup_strv_free_ char **l = NULL;
int r;
@@ -244,7 +249,7 @@ static int determine_banks(Tpm2Context *c, unsigned target_pcr_nr) {
if (!strv_isempty(arg_banks)) /* Explicitly configured? Then use that */
return 0;
r = tpm2_get_good_pcr_banks_strv(c, UINT32_C(1) << target_pcr_nr, &l);
r = tpm2_get_good_pcr_banks_strv(c, target_pcr_mask, &l);
if (r < 0)
return log_error_errno(r, "Could not verify pcr banks: %m");
@@ -275,7 +280,7 @@ static int escape_and_truncate_data(const void *data, size_t size, char **ret) {
}
static int extend_pcr_now(
unsigned pcr,
uint32_t pcr_mask,
const void *data,
size_t size,
Tpm2UserspaceEventType event) {
@@ -283,11 +288,13 @@ static int extend_pcr_now(
_cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL;
int r;
assert(pcr_mask != 0);
r = tpm2_context_new_or_warn(arg_tpm2_device, &c);
if (r < 0)
return r;
r = determine_banks(c, pcr);
r = determine_banks(c, pcr_mask);
if (r < 0)
return r;
if (strv_isempty(arg_banks)) /* Still none? */
@@ -302,18 +309,20 @@ static int extend_pcr_now(
if (escape_and_truncate_data(data, size, &safe) < 0)
return log_oom();
log_debug("Measuring '%s' into PCR index %u, banks %s.", safe, pcr, joined_banks);
BIT_FOREACH(pcr, pcr_mask) {
log_debug("Measuring '%s' into PCR index %i, banks %s.", safe, pcr, joined_banks);
r = tpm2_pcr_extend_bytes(c, arg_banks, pcr, &IOVEC_MAKE(data, size), /* secret= */ NULL, event, safe);
if (r < 0)
return log_error_errno(r, "Could not extend PCR: %m");
r = tpm2_pcr_extend_bytes(c, arg_banks, pcr, &IOVEC_MAKE(data, size), /* secret= */ NULL, event, safe);
if (r < 0)
return log_error_errno(r, "Could not extend PCR: %m");
log_struct(LOG_INFO,
LOG_MESSAGE_ID(SD_MESSAGE_TPM_PCR_EXTEND_STR),
LOG_MESSAGE("Extended PCR index %u with '%s' (banks %s).", pcr, safe, joined_banks),
LOG_ITEM("MEASURING=%s", safe),
LOG_ITEM("PCR=%u", pcr),
LOG_ITEM("BANKS=%s", joined_banks));
log_struct(LOG_INFO,
LOG_MESSAGE_ID(SD_MESSAGE_TPM_PCR_EXTEND_STR),
LOG_MESSAGE("Extended PCR index %i with '%s' (banks %s).", pcr, safe, joined_banks),
LOG_ITEM("MEASURING=%s", safe),
LOG_ITEM("PCR=%i", pcr),
LOG_ITEM("BANKS=%s", joined_banks));
}
return 0;
}
@@ -435,7 +444,7 @@ static int vl_method_extend(sd_varlink *link, sd_json_variant *parameters, sd_va
if (r == -ENOENT)
return sd_varlink_error(link, "io.systemd.PCRExtend.NoSuchNvPCR", NULL);
} else
r = extend_pcr_now(p.pcr, extend_iovec->iov_base, extend_iovec->iov_len, p.event_type);
r = extend_pcr_now(INDEX_TO_MASK(uint32_t, p.pcr), extend_iovec->iov_base, extend_iovec->iov_len, p.event_type);
if (r < 0)
return r;
@@ -531,24 +540,24 @@ static int run(int argc, char *argv[]) {
if (arg_event_type >= 0)
event = arg_event_type;
if (arg_graceful && !tpm2_is_fully_supported()) {
if (arg_graceful && !tpm2_is_mostly_supported()) {
log_notice("No complete TPM2 support detected, exiting gracefully.");
return EXIT_SUCCESS;
}
/* Skip logic if sd-stub is not used, after all PCR 11 might have a very different purpose then. */
r = efi_measured_uki(LOG_ERR);
/* Skip logic if measured OS functionality is not enabled. */
r = efi_measured_os(LOG_ERR);
if (r < 0)
return r;
if (r == 0) {
log_info("Kernel stub did not measure kernel image into PCR %i, skipping userspace measurement, too.", TPM2_PCR_KERNEL_BOOT);
log_info("OS measurements not explicitly requested and kernel stub did not measure kernel image into PCR %i, skipping userspace measurement, too.", TPM2_PCR_KERNEL_BOOT);
return EXIT_SUCCESS;
}
if (arg_nvpcr_name)
r = extend_nvpcr_now(arg_nvpcr_name, word, strlen(word), event);
else
r = extend_pcr_now(arg_pcr_index, word, strlen(word), event);
r = extend_pcr_now(arg_pcr_mask, word, strlen(word), event);
if (r < 0)
return r;
+1
View File
@@ -27,6 +27,7 @@ install_data('pcrlock.d/500-separator.pcrlock.d/600-0xffffffff.pcrlock', install
install_data('pcrlock.d/700-action-efi-exit-boot-services.pcrlock.d/300-present.pcrlock', install_dir : pcrlockdir / '700-action-efi-exit-boot-services.pcrlock.d')
install_data('pcrlock.d/700-action-efi-exit-boot-services.pcrlock.d/600-absent.pcrlock', install_dir : pcrlockdir / '700-action-efi-exit-boot-services.pcrlock.d')
install_data('pcrlock.d/750-enter-initrd.pcrlock', install_dir : pcrlockdir)
install_data('pcrlock.d/750-os-separator.pcrlock', install_dir : pcrlockdir)
install_data('pcrlock.d/770-nvpcr-separator.pcrlock', install_dir : pcrlockdir)
install_data('pcrlock.d/800-leave-initrd.pcrlock', install_dir : pcrlockdir)
install_data('pcrlock.d/850-sysinit.pcrlock', install_dir : pcrlockdir)
+4
View File
@@ -899,6 +899,10 @@ static int event_log_load_firmware(EventLog *el) {
path = tpm2_firmware_log_path();
r = read_full_file(path, (char**) &buf, &bufsize);
if (r == -ENOENT) {
log_notice("No '%s' file, assuming TPM without firmware support.", path);
return 0;
}
if (r < 0)
return log_error_errno(r, "Failed to open TPM2 event log '%s': %m", path);
File diff suppressed because one or more lines are too long
+2
View File
@@ -741,6 +741,8 @@ static int condition_test_security(Condition *c, char **env) {
return detect_confidential_virtualization() > 0;
if (streq(c->parameter, "measured-uki"))
return efi_measured_uki(LOG_DEBUG);
if (streq(c->parameter, "measured-os"))
return efi_measured_os(LOG_DEBUG);
return false;
}
+7 -2
View File
@@ -894,14 +894,17 @@ int encrypt_credential_and_warn(
* container tpm2_support will detect this, and will return a different flag combination of
* TPM2_SUPPORT_FULL, effectively skipping the use of TPM2 when inside one. */
try_tpm2 = tpm2_is_fully_supported();
try_tpm2 = tpm2_is_mostly_supported();
if (!try_tpm2)
log_debug("System lacks TPM2 support or running in a container, not attempting to use TPM2.");
} else
try_tpm2 = CRED_KEY_REQUIRES_TPM2(with_key);
if (try_tpm2) {
if (CRED_KEY_WANTS_TPM2_PK(with_key) || CRED_KEY_REQUIRES_TPM2_PK(with_key)) {
/* If the firmware does not support TPMs, then UKI measurements are not going to work, hence
* PCR 11 public key stuff cannot work. Because of that, if PK is only wanted (but not
* required) we won't try it. */
if ((CRED_KEY_WANTS_TPM2_PK(with_key) && tpm2_is_fully_supported()) || CRED_KEY_REQUIRES_TPM2_PK(with_key)) {
/* Load public key for PCR policies, if one is specified, or explicitly requested */
@@ -926,6 +929,8 @@ int encrypt_credential_and_warn(
if (r < 0)
return log_error_errno(r, "Could not find best pcr bank: %m");
log_debug("Selected literal PCR mask: 0x%x, PK PCR mask: 0x%x", tpm2_hash_pcr_mask, tpm2_pubkey_pcr_mask);
TPML_PCR_SELECTION tpm2_hash_pcr_selection;
tpm2_tpml_pcr_selection_from_mask(tpm2_hash_pcr_mask, tpm2_pcr_bank, &tpm2_hash_pcr_selection);
+25
View File
@@ -8,6 +8,7 @@
#include "log.h"
#include "parse-util.h"
#include "path-util.h"
#include "proc-cmdline.h"
#include "stat-util.h"
#include "string-util.h"
#include "strv.h"
@@ -312,6 +313,30 @@ int efi_measured_uki(int log_level) {
#endif
}
int efi_measured_os(int log_level) {
#if ENABLE_EFI
static int cached = -1;
int r;
/* Returns if we shall enable our measurement machinery */
if (cached >= 0)
return cached;
bool b;
r = proc_cmdline_get_bool("systemd.tpm2_measured_os", /* flags= */ 0, &b);
if (r < 0)
log_debug_errno(r, "Failed to parse systemd.tpm2_measured_os= kernel command line argument, ignoring: %m");
else if (r > 0)
return (cached = b);
/* If nothing is explicitly configured, just assume that if we booted with a measured UKI we also want a measured OS */
return (cached = efi_measured_uki(log_level));
#else
return log_full_errno(log_level, SYNTHETIC_ERRNO(EOPNOTSUPP), "Compiled without support for EFI");
#endif
}
int efi_loader_get_config_timeout_one_shot(usec_t *ret) {
#if ENABLE_EFI
_cleanup_free_ char *v = NULL;
+1
View File
@@ -15,6 +15,7 @@ int efi_loader_get_features(uint64_t *ret);
int efi_stub_get_features(uint64_t *ret);
int efi_measured_uki(int log_level);
int efi_measured_os(int log_level);
int efi_loader_get_config_timeout_one_shot(usec_t *ret);
int efi_loader_update_entry_one_shot_cache(char **cache, struct stat *cache_stat);
+5
View File
@@ -2899,6 +2899,8 @@ int tpm2_get_best_pcr_bank(
log_debug("Boot loader didn't set the LoaderTpm2ActivePcrBanks EFI variable or EFI support is unavailable, we have to guess the used PCR banks.");
} else if (efi_banks == UINT32_MAX)
log_debug("Boot loader set the LoaderTpm2ActivePcrBanks EFI variable to indicate that the GetActivePcrBanks() API is not available in the firmware. We have to guess the used PCR banks.");
else if (efi_banks == 0)
log_debug("Boot loader set the LoaderTpm2ActivePcrBanks EFI variable to zero to indicate that TPM support is not available in the firmware. We'll have to guess the used PCR banks.");
else {
if (BIT_SET(efi_banks, TPM2_ALG_SHA256))
*ret = TPM2_ALG_SHA256;
@@ -3008,6 +3010,8 @@ int tpm2_get_good_pcr_banks(
log_debug("Boot loader didn't set the LoaderTpm2ActivePcrBanks EFI variable or EFI support is unavailable, we have to guess the used PCR banks.");
} else if (efi_banks == UINT32_MAX)
log_debug("Boot loader set the LoaderTpm2ActivePcrBanks EFI variable to indicate that the GetActivePcrBanks() API is not available in the firmware. We have to guess the used PCR banks.");
else if (efi_banks == 0)
log_debug("Boot loader set the LoaderTpm2ActivePcrBanks EFI variable to zero to indicate that TPM support is not available in the firmware. We'll have to guess the used PCR banks.");
else {
FOREACH_ARRAY(hash, tpm2_hash_algorithms, TPM2_N_HASH_ALGORITHMS) {
if (!BIT_SET(efi_banks, *hash))
@@ -6676,6 +6680,7 @@ static const char* tpm2_userspace_event_type_table[_TPM2_USERSPACE_EVENT_TYPE_MA
[TPM2_EVENT_NVPCR_SEPARATOR] = "nvpcr-separator",
[TPM2_EVENT_DM_VERITY] = "dm-verity",
[TPM2_EVENT_IMDS_USERDATA] = "imds-userdata",
[TPM2_EVENT_OS_SEPARATOR] = "os-separator",
};
DEFINE_STRING_TABLE_LOOKUP(tpm2_userspace_event_type, Tpm2UserspaceEventType);
+5
View File
@@ -149,6 +149,7 @@ typedef enum Tpm2UserspaceEventType {
TPM2_EVENT_NVPCR_SEPARATOR,
TPM2_EVENT_DM_VERITY,
TPM2_EVENT_IMDS_USERDATA,
TPM2_EVENT_OS_SEPARATOR,
_TPM2_USERSPACE_EVENT_TYPE_MAX,
_TPM2_USERSPACE_EVENT_TYPE_INVALID = -EINVAL,
} Tpm2UserspaceEventType;
@@ -496,6 +497,7 @@ typedef enum Tpm2Support {
/* Combined flags for generic (i.e. not tool-specific) support */
TPM2_SUPPORT_FULL = TPM2_SUPPORT_API|TPM2_SUPPORT_LIBTSS2_ALL,
TPM2_SUPPORT_SOFTWARE = TPM2_SUPPORT_FULL & ~TPM2_SUPPORT_FIRMWARE, /* Same, just without PC firmware support */
} Tpm2Support;
Tpm2Support tpm2_support_full(Tpm2Support mask);
@@ -505,6 +507,9 @@ static inline Tpm2Support tpm2_support(void) {
static inline bool tpm2_is_fully_supported(void) {
return tpm2_support() == TPM2_SUPPORT_FULL;
}
static inline bool tpm2_is_mostly_supported(void) {
return (tpm2_support() & TPM2_SUPPORT_SOFTWARE) == TPM2_SUPPORT_SOFTWARE;
}
int verb_has_tpm2_generic(bool quiet);
+9
View File
@@ -22,6 +22,15 @@ executables += [
'HAVE_TPM2',
],
},
libexec_template + {
'name' : 'systemd-tpm2-swtpm',
'sources' : files('tpm2-swtpm.c'),
'conditions' : [
'ENABLE_BOOTLOADER',
'HAVE_OPENSSL',
'HAVE_TPM2',
],
},
generator_template + {
'name' : 'systemd-tpm2-generator',
'sources' : files('tpm2-generator.c'),
+104 -10
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);
+1 -1
View File
@@ -516,7 +516,7 @@ static int run(int argc, char *argv[]) {
if (r <= 0)
return r;
if (arg_graceful && !tpm2_is_fully_supported()) {
if (arg_graceful && !tpm2_is_mostly_supported()) {
log_notice("No complete TPM2 support detected, exiting gracefully.");
return EXIT_SUCCESS;
}
+221
View File
@@ -0,0 +1,221 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <unistd.h>
#include "sd-daemon.h"
#include "alloc-util.h"
#include "chase.h"
#include "errno-util.h"
#include "escape.h"
#include "fd-util.h"
#include "fileio.h"
#include "find-esp.h"
#include "fs-util.h"
#include "hexdecoct.h"
#include "hmac.h"
#include "initrd-util.h"
#include "iovec-util.h"
#include "log.h"
#include "main-func.h"
#include "path-lookup.h"
#include "path-util.h"
#include "sha256.h"
#include "stat-util.h"
#include "string-util.h"
#include "strv.h"
#include "swtpm-util.h"
#define BOOT_SECRET_SIZE 32U
static int load_boot_secret(struct iovec *ret) {
_cleanup_(iovec_done_erase) struct iovec buf = {};
int r;
const char *bs = in_initrd() ? "/.extra/boot-secret" : "/run/systemd/stub/boot-secret";
r = read_full_file_full(
AT_FDCWD,
bs,
UINT64_MAX,
BOOT_SECRET_SIZE,
READ_FULL_FILE_SECURE|READ_FULL_FILE_VERIFY_REGULAR,
/* bind_name= */ NULL,
(char**) &buf.iov_base,
&buf.iov_len);
if (r == -ENOENT) {
log_warning_errno(r, "Boot secret (%s) not found, not encrypting software TPM state!", bs);
*ret = (struct iovec) {};
return 0;
}
if (r < 0)
return log_error_errno(r, "Failed to read '%s': %m", bs);
if (buf.iov_len < BOOT_SECRET_SIZE)
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Boot secret too short, refusing.");
*ret = TAKE_STRUCT(buf);
return 1;
}
static int prepare_secret(const char *runtime_dir, char **ret) {
int r;
assert(runtime_dir);
assert(ret);
_cleanup_(iovec_done_erase) struct iovec boot_secret = {};
r = load_boot_secret(&boot_secret);
if (r < 0)
return r;
if (r == 0) {
*ret = NULL;
return 0;
}
/* Derive a suitable swtpm specific secret */
static const char tag[] = "systemd swtpm tag v1";
uint8_t secret[SHA256_DIGEST_SIZE];
CLEANUP_ERASE(secret);
hmac_sha256(boot_secret.iov_base,
boot_secret.iov_len,
tag,
strlen(tag),
secret);
_cleanup_free_ char *p = path_join(runtime_dir, "secret");
if (!p)
return log_oom();
assert_cc(sizeof(secret) >= 16); /* swtpm only wants a 16 byte key */
_cleanup_(erase_and_freep) char *h = hexmem(secret, 16);
if (!h)
return log_oom();
r = write_data_file_atomic_at(XAT_FDROOT, p, &IOVEC_MAKE_STRING(h), WRITE_DATA_FILE_MODE_0400);
if (r < 0)
return log_error_errno(r, "Failed to write secret file: %m");
*ret = TAKE_PTR(p);
return 1;
}
static int setup_swtpm(const char *state_dir, int state_fd, const char *secret) {
int r;
assert(state_dir);
assert(state_fd >= 0);
/* Sets up the state directory via swtpm_setup */
if (in_initrd()) {
/* In the initrd remove previous transient state */
r = RET_NERRNO(unlinkat(state_fd, "tpm2-00.volatilestate", /* flags= */ 0));
if (r < 0 && r != -ENOENT)
return log_error_errno(r, "Failed to remove 'tpm2-00.volatilestate': %m");
}
r = dir_is_empty_at(state_fd, /* path= */ NULL, /* ignore_hidden_or_backup= */ false);
if (r < 0)
return log_error_errno(r, "Failed to check if TPM state directory is empty: %m");
if (r == 0) {
log_debug("TPM state directory is already populated, not manufacturing a TPM.");
return 0;
}
if (!in_initrd())
return log_error_errno(SYNTHETIC_ERRNO(ESTALE), "swtpm TPM state directory has not been initialized in the initrd, refusing.");
log_debug("TPM state directory is unpopulated, manufacturing a TPM.");
return manufacture_swtpm(state_dir, secret);
}
static int run(int argc, char *argv[]) {
int r;
log_setup();
_cleanup_free_ char *runtime_dir = NULL;
r = runtime_directory(RUNTIME_SCOPE_SYSTEM, "systemd/swtpm", &runtime_dir);
if (r < 0)
return log_error_errno(r, "Unable to determine runtime directory: %m");
_cleanup_free_ char *swtpm = NULL;
r = find_executable("swtpm", &swtpm);
if (r < 0)
return log_error_errno(r, "Failed to find 'swtpm' binary: %m");
_cleanup_free_ char *_esp = NULL;
const char *esp;
if (in_initrd())
/* The early ESP support uses only a single mount point, we do not need to search for it. */
esp = "/sysefi";
else {
r = find_esp_and_warn(
/* root= */ NULL,
/* path= */ NULL,
/* unprivileged_mode= */ false,
&_esp);
if (r == -ENOKEY) /* This one find_esp_and_warn() doesn't actually log about. */
return log_error_errno(r, "No ESP discovered.");
if (r < 0)
return r;
esp = _esp;
}
_cleanup_free_ char *state_dir = NULL;
_cleanup_close_ int state_fd = -EBADF;
r = chase("/loader/swtpm",
esp, CHASE_PREFIX_ROOT|CHASE_TRIGGER_AUTOFS|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY,
&state_dir,
&state_fd);
if (r < 0)
return log_error_errno(r, "Failed to open swtpm state directory in ESP: %m");
_cleanup_(unlink_and_freep) char *secret = NULL;
r = prepare_secret(runtime_dir, &secret);
if (r < 0)
return r;
r = setup_swtpm(state_dir, state_fd, secret);
if (r < 0)
return r;
_cleanup_strv_free_ char **args =
strv_new(swtpm,
"chardev",
"--vtpm-proxy",
"--tpm2",
/* Make sure that in the initrd swtpm never sends TPM2_Shutdown() for us, we want to
* be able to stop the daemon after all temporarily during the initrd→host
* transition. */
in_initrd() ? "--flags=startup-clear,disable-auto-shutdown" : "--flags=startup-clear");
if (!args)
return log_oom();
if (strv_extendf(&args, "--ctrl=type=unixio,path=%s/socket", runtime_dir) < 0)
return log_oom();
if (secret && strv_extendf(&args, "--key=file=%s,format=hex,mode=aes-cbc,remove=true", secret) < 0)
return log_oom();
if (strv_extendf(&args, "--tpmstate=dir=%s,mode=0600", state_dir) < 0)
return log_oom();
if (DEBUG_LOGGING) {
_cleanup_free_ char *cmd = quote_command_line(args, SHELL_ESCAPE_EMPTY);
log_debug("Chain-loading: %s", strnull(cmd));
}
/* Ideally swtpm could send this itself, but for now let's accept it like this. */
// FIXME: remove this once swtpm 0.11 is released and hit all relevant distros. Then bump version
// requirements.
(void) sd_notify(/* unset_environment= */ true, "READY=1");
/* NB: if the execve() succeeds it's swtpm's job to actually unlink the secret file */
execv(swtpm, args);
return log_error_errno(errno, "Failed to chainload swtpm: %m");
}
DEFINE_MAIN_FUNCTION(run);
-1
View File
@@ -53,7 +53,6 @@ fi
(! "$SD_PCREXTEND" --bank= foo)
(! "$SD_PCREXTEND" --tpm2-device= foo)
(! "$SD_PCREXTEND" --tpm2-device=/dev/null foo)
(! "$SD_PCREXTEND" --pcr= foo)
(! "$SD_PCREXTEND" --pcr=-1 foo)
(! "$SD_PCREXTEND" --pcr=1024 foo)
(! "$SD_PCREXTEND" --foo=bar)
+12
View File
@@ -42,6 +42,18 @@ PCRS="1+2+3+4+5+16"
# (as the PCR values simply won't match the log).
rm -f /run/log/systemd/tpm2-measure.log
# Add the os-separator measurements, they should be the only measurements that touch pcr 0…6 done from userspace.
RS=$'\x1e'
cat >/run/log/systemd/tpm2-measure.log <<EOF
${RS}{"pcr":0,"digests":[{"hashAlg":"sha256","digest":"ff5b9d73dad709633ae76adf444012b57e913a12ed7403c3931145862f35f841"}],"content_type":"systemd","content":{"string":"os-separator","bootId":"5270738c127841a4b592f0fdc2839929","timestamp":2659711,"eventType":"os-separator"}}
${RS}{"pcr":1,"digests":[{"hashAlg":"sha256","digest":"ff5b9d73dad709633ae76adf444012b57e913a12ed7403c3931145862f35f841"}],"content_type":"systemd","content":{"string":"os-separator","bootId":"5270738c127841a4b592f0fdc2839929","timestamp":2659775,"eventType":"os-separator"}}
${RS}{"pcr":2,"digests":[{"hashAlg":"sha256","digest":"ff5b9d73dad709633ae76adf444012b57e913a12ed7403c3931145862f35f841"}],"content_type":"systemd","content":{"string":"os-separator","bootId":"5270738c127841a4b592f0fdc2839929","timestamp":2659805,"eventType":"os-separator"}}
${RS}{"pcr":3,"digests":[{"hashAlg":"sha256","digest":"ff5b9d73dad709633ae76adf444012b57e913a12ed7403c3931145862f35f841"}],"content_type":"systemd","content":{"string":"os-separator","bootId":"5270738c127841a4b592f0fdc2839929","timestamp":2659829,"eventType":"os-separator"}}
${RS}{"pcr":4,"digests":[{"hashAlg":"sha256","digest":"ff5b9d73dad709633ae76adf444012b57e913a12ed7403c3931145862f35f841"}],"content_type":"systemd","content":{"string":"os-separator","bootId":"5270738c127841a4b592f0fdc2839929","timestamp":2659851,"eventType":"os-separator"}}
${RS}{"pcr":5,"digests":[{"hashAlg":"sha256","digest":"ff5b9d73dad709633ae76adf444012b57e913a12ed7403c3931145862f35f841"}],"content_type":"systemd","content":{"string":"os-separator","bootId":"5270738c127841a4b592f0fdc2839929","timestamp":2660099,"eventType":"os-separator"}}
${RS}{"pcr":6,"digests":[{"hashAlg":"sha256","digest":"ff5b9d73dad709633ae76adf444012b57e913a12ed7403c3931145862f35f841"}],"content_type":"systemd","content":{"string":"os-separator","bootId":"5270738c127841a4b592f0fdc2839929","timestamp":2660139,"eventType":"os-separator"}}
EOF
# Reset TPM PCR 16 ("debug") explicitly, so that we can use it in a known good state
tpm2_pcrreset 16
+9
View File
@@ -584,6 +584,11 @@ units = [
'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'],
'symlinks' : ['sysinit.target.wants/'],
},
{
'file' : 'systemd-pcrosseparator.service.in',
'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'],
'symlinks' : ['sysinit.target.wants/'],
},
{
'file' : 'systemd-pcrphase-factory-reset.service.in',
'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'],
@@ -633,6 +638,10 @@ units = [
'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'],
'symlinks' : ['sysinit.target.wants/'],
},
{
'file' : 'systemd-tpm2-swtpm.service.in',
'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'],
},
{
'file' : 'systemd-pcrlock-make-policy.service.in',
'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'],
+1 -1
View File
@@ -13,7 +13,7 @@ Documentation=man:systemd-pcrextend(8)
DefaultDependencies=no
After=tpm2.target
Before=sockets.target
ConditionSecurity=measured-uki
ConditionSecurity=measured-os
[Socket]
ListenStream=/run/systemd/io.systemd.PCRExtend
+1 -1
View File
@@ -15,7 +15,7 @@ Conflicts=shutdown.target
After=tpm2.target systemd-pcrmachine.service
Before=shutdown.target
ConditionPathExists=!/etc/initrd-release
ConditionSecurity=measured-uki
ConditionSecurity=measured-os
FailureAction=reboot-force
[Service]
+1 -1
View File
@@ -16,7 +16,7 @@ Conflicts=shutdown.target
After=%i.mount tpm2.target systemd-pcrfs-root.service
Before=shutdown.target
ConditionPathExists=!/etc/initrd-release
ConditionSecurity=measured-uki
ConditionSecurity=measured-os
FailureAction=reboot-force
[Service]
+1 -1
View File
@@ -15,7 +15,7 @@ Conflicts=shutdown.target
After=tpm2.target
Before=sysinit.target shutdown.target
ConditionPathExists=!/etc/initrd-release
ConditionSecurity=measured-uki
ConditionSecurity=measured-os
FailureAction=reboot-force
[Service]
+2 -2
View File
@@ -13,8 +13,8 @@ Documentation=man:systemd-pcrnvdone.service(8)
DefaultDependencies=no
Conflicts=shutdown.target
After=systemd-tpm2-setup-early.service systemd-tpm2-setup.service
Before=sysinit.target shutdown.target
ConditionSecurity=measured-uki
Before=sysinit.target cryptsetup-pre.target cryptsetup.target shutdown.target
ConditionSecurity=measured-os
ConditionPathExists=!/etc/initrd-release
FailureAction=reboot-force
+28
View File
@@ -0,0 +1,28 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=TPM PCR OS Separator
Documentation=man:systemd-pcrosseparator.service(8)
DefaultDependencies=no
Conflicts=shutdown.target initrd-switch-root.target
After=tpm2.target
Before=sysinit.target shutdown.target initrd-switch-root.target cryptsetup-pre.target cryptsetup.target
ConditionPathExists=/etc/initrd-release
ConditionSecurity=measured-os
FailureAction=reboot-force
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart={{LIBEXECDIR}}/systemd-pcrextend --graceful \
--pcr=0 --pcr=1 --pcr=2 --pcr=3 --pcr=4 --pcr=5 \
--pcr=6 --pcr=7 --pcr=9 --pcr=12 --pcr=13 --pcr=14 \
--event-type=os-separator \
os-separator
@@ -14,7 +14,7 @@ DefaultDependencies=no
Conflicts=shutdown.target
After=tpm2.target
Before=shutdown.target factory-reset.target
ConditionSecurity=measured-uki
ConditionSecurity=measured-os
FailureAction=reboot-force
[Service]
+1 -1
View File
@@ -15,7 +15,7 @@ Conflicts=shutdown.target initrd-switch-root.target
After=tpm2.target
Before=sysinit.target cryptsetup-pre.target cryptsetup.target shutdown.target initrd-switch-root.target systemd-sysext.service
ConditionPathExists=/etc/initrd-release
ConditionSecurity=measured-uki
ConditionSecurity=measured-os
FailureAction=reboot-force
[Service]
@@ -15,7 +15,7 @@ Conflicts=shutdown.target
After=tpm2.target
Before=shutdown.target
ConditionPathExists=/etc/initrd-release
ConditionSecurity=measured-uki
ConditionSecurity=measured-os
FailureAction=reboot-force
[Service]
+1 -1
View File
@@ -15,7 +15,7 @@ Conflicts=shutdown.target
After=sysinit.target tpm2.target
Before=basic.target shutdown.target
ConditionPathExists=!/etc/initrd-release
ConditionSecurity=measured-uki
ConditionSecurity=measured-os
FailureAction=reboot-force
[Service]
+1 -1
View File
@@ -13,7 +13,7 @@ Documentation=man:systemd-pcrphase.service(8)
After=remote-fs.target remote-cryptsetup.target tpm2.target
Before=systemd-user-sessions.service
ConditionPathExists=!/etc/initrd-release
ConditionSecurity=measured-uki
ConditionSecurity=measured-os
FailureAction=reboot-force
[Service]
+2 -2
View File
@@ -12,11 +12,11 @@ Description=TPM NvPCR Product ID Measurement
Documentation=man:systemd-pcrproduct.service(8)
DefaultDependencies=no
Conflicts=shutdown.target
After=tpm2.target
After=tpm2.target systemd-pcrnvdone.service
Before=sysinit.target shutdown.target
RequiresMountsFor=/var/lib/systemd/nvpcr
ConditionPathExists=!/etc/initrd-release
ConditionSecurity=measured-uki
ConditionSecurity=measured-os
[Service]
Type=oneshot
+1 -1
View File
@@ -22,7 +22,7 @@ ConditionPathExists=/sys/class/tpm/tpm0/ppi/request
# derive here from the fact that UKIs are used. Because if they do they are OK
# with our SRK initialization and our PCR measurements, and hence should also
# be OK with our TPM resets.
ConditionSecurity=measured-uki
ConditionSecurity=measured-os
[Service]
Type=oneshot
+2 -2
View File
@@ -12,9 +12,9 @@ Description=Early TPM SRK Setup
Documentation=man:systemd-tpm2-setup.service(8)
DefaultDependencies=no
Conflicts=shutdown.target
After=tpm2.target systemd-pcrphase-initrd.service
After=tpm2.target systemd-pcrphase-initrd.service systemd-pcrosseparator.service
Before=sysinit.target shutdown.target
ConditionSecurity=measured-uki
ConditionSecurity=measured-os
ConditionPathExists=!/run/systemd/tpm2-srk-public-key.pem
[Service]
+1 -1
View File
@@ -15,7 +15,7 @@ Conflicts=shutdown.target
After=tpm2.target systemd-tpm2-setup-early.service systemd-remount-fs.service
Before=sysinit.target shutdown.target
RequiresMountsFor=/var/lib/systemd
ConditionSecurity=measured-uki
ConditionSecurity=measured-os
ConditionPathExists=!/etc/initrd-release
[Service]
+26
View File
@@ -0,0 +1,26 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# This file is part of systemd.
#
# systemd is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
[Unit]
Description=Fallback Software TPM
Documentation=man:systemd-tpm2-swtpm.service(8)
DefaultDependencies=no
After=systemd-sysusers.service
Wants=modprobe@tpm_vtpm_proxy.service
After=modprobe@tpm_vtpm_proxy.service
Before=tpm2.target sysinit.target
[Service]
Type=notify
RuntimeDirectory=systemd/swtpm
ExecStart={{LIBEXECDIR}}/systemd-tpm2-swtpm
# Write out volatile state (so that we can read it back after the initrd transition
ExecStop=swtpm_ioctl --unix %t/systemd/swtpm/socket -v
# Initiate graceful shutdown
ExecStop=swtpm_ioctl --unix %t/systemd/swtpm/socket -s