pcrextend: measure another separator at boot

This has been requested previously for PCR 7 (#40567), but let's do that
for all firmware owned PCRs, since some firmwares forget to measure
their own separator. Let's hence measure our own guranteed one.

Fixes: #40567
This commit is contained in:
Lennart Poettering
2026-03-26 16:11:34 +01:00
parent 3b20cc4526
commit 7c27f9f594
12 changed files with 93 additions and 27 deletions
+1
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'],
+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>
+33 -24
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;
@@ -548,7 +557,7 @@ static int run(int argc, char *argv[]) {
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)
File diff suppressed because one or more lines are too long
+1
View File
@@ -6680,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);
+1
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;
-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
+5
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'],
+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
+1 -1
View File
@@ -12,7 +12,7 @@ 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-os
ConditionPathExists=!/run/systemd/tpm2-srk-public-key.pem