core: introduce ConditionSecurity=measured-os

So far we always conditioned our TPM magic on the UKI having detected
TPM support in the firmware. This is a bit limiting when we want to
support a software TPM that is not visible to the firmware. Hence let's
split this up, and add a separate control that can be set via the kernel
command line.  However, as before, let's by default inherit the firmare
TPM discovery state into it, to retain the current behaviour unless
overriden.

With this in place, boot with "systemd.tpm2_measured_os=1
systemd.tpm2_software_fallback=1" on the kernel cmdline to get the swtpm
fallback and then a measured OS based on it.
This commit is contained in:
Lennart Poettering
2026-03-09 18:53:09 +01:00
parent ca03d178a0
commit cd911bec6e
25 changed files with 86 additions and 30 deletions

View File

@@ -792,6 +792,18 @@
<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>

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>

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());

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;
}

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)

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(

View File

@@ -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;

View File

@@ -536,12 +536,12 @@ static int run(int argc, char *argv[]) {
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;
}

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;
}

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;

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);

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

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]

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]

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]

View File

@@ -14,7 +14,7 @@ DefaultDependencies=no
Conflicts=shutdown.target
After=systemd-tpm2-setup-early.service systemd-tpm2-setup.service
Before=sysinit.target shutdown.target
ConditionSecurity=measured-uki
ConditionSecurity=measured-os
ConditionPathExists=!/etc/initrd-release
FailureAction=reboot-force

View File

@@ -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]

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]

View File

@@ -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]

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]

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]

View File

@@ -16,7 +16,7 @@ After=tpm2.target
Before=sysinit.target shutdown.target
RequiresMountsFor=/var/lib/systemd/nvpcr
ConditionPathExists=!/etc/initrd-release
ConditionSecurity=measured-uki
ConditionSecurity=measured-os
[Service]
Type=oneshot

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

View File

@@ -14,7 +14,7 @@ DefaultDependencies=no
Conflicts=shutdown.target
After=tpm2.target systemd-pcrphase-initrd.service
Before=sysinit.target shutdown.target
ConditionSecurity=measured-uki
ConditionSecurity=measured-os
ConditionPathExists=!/run/systemd/tpm2-srk-public-key.pem
[Service]

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]