tpm2-setup: measure information about NvPCR initialization to PCR 9

This locks down NvPCR initilization a bit more: we'll measure each
initialization of an NvPCR into PCR 9, thus chaining the NvPCRs to the
PCR set. After all NvPCRs are initialized we measure a barrier into PCR
9 as well.

This ensures that later additions of NvPCRs are clearly recognizable and
distuingishable from those done at boot.
This commit is contained in:
Lennart Poettering
2025-11-12 22:35:30 +01:00
parent 89bfa9239e
commit d70296bb56
3 changed files with 49 additions and 0 deletions

View File

@@ -199,6 +199,22 @@ initrd" in UTF-16.
**Measured hash** covers the per-UKI sysext cpio archive (which is generated
on-the-fly by `systemd-stub`).
## PCR Measurements Made by `systemd-tpm2-setup` (Userspace)
### PCR 9, NvPCR Initializations
The `systemd-tpm2-setup.service` service initializes any NvPCRs defined via
`*.nvpcr` files. For each initialized NvPCR it will measure an event into PCR
9.
**Measured hash** covers the string `nvpcr-init:`, suffixed by the NvPCR
name, suffixed by `:0x`, suffixed by the NV Index handle (formatted in
hexadecimal), suffixed by a colon, suffixed by the hash function used, in
lowercase (i.e. `sha256` or so), suffixed by a colon, and finally suffixed by
the state of the NvPCR after its initialization with the anchor measurement, in
hexadecimal. Example:
`nvpcr-init:hardware:0x1d10200:sha256:de3857f637c61e82f02e3722e1b207585fe9711045d863238904be8db10683f2`
## PCR/NvPCR Measurements Made by `systemd-pcrextend` (Userspace)
### PCR 11, boot phases

View File

@@ -6426,6 +6426,7 @@ static const char* tpm2_userspace_event_type_table[_TPM2_USERSPACE_EVENT_TYPE_MA
[TPM2_EVENT_MACHINE_ID] = "machine-id",
[TPM2_EVENT_PRODUCT_ID] = "product-id",
[TPM2_EVENT_KEYSLOT] = "keyslot",
[TPM2_EVENT_NVPCR_INIT] = "nvpcr-init",
};
DEFINE_STRING_TABLE_LOOKUP(tpm2_userspace_event_type, Tpm2UserspaceEventType);
@@ -7368,6 +7369,37 @@ int tpm2_nvpcr_initialize(
return log_debug_errno(r, "Failed to write anchor file: %m");
tpm2_userspace_log_clean(log_fd);
log_fd = safe_close(log_fd);
/* Now also measure the initialization into PCR 9, so that there's a trace of it in regular PCRs. You
* might wonder why PCR 9? Well, we have very few PCRs available, and PCR 9 appears to be the least
* bad for this. It typically contains stuff that in our world is hard to predict anyway
* (i.e. possibly some overly verbose Grub stuff, as well as all initrds those generated on-the-fly
* and those prepared beforehand mangled into one), quite differently from all other PCRs we could
* use. Moreover PCR 11 already contains most stuff from PCR 9, as it contains the same data
* (i.e. initrds) in a more sensible fashion, clearly separated from on-the-fly generated ones. Note
* that we only do all this measurement stuff if we are booted as UKI, and hence when PCR 11 is
* available, but PCR 9 is not predictable. */
_cleanup_strv_free_ char **banks = NULL;
r = tpm2_get_good_pcr_banks_strv(c, UINT32_C(1) << TPM2_PCR_KERNEL_INITRD, &banks);
if (r < 0)
return log_error_errno(r, "Could not verify PCR banks: %m");
_cleanup_free_ char *word = NULL;
if (asprintf(&word, "nvpcr-init:%s:0x%x:%s:%s", name, p.nv_index, tpm2_hash_alg_to_string(p.algorithm), h) < 0)
return log_oom();
r = tpm2_pcr_extend_bytes(
c,
banks,
TPM2_PCR_KERNEL_INITRD,
&IOVEC_MAKE_STRING(word),
/* secret= */ NULL,
TPM2_EVENT_NVPCR_INIT,
word);
if (r < 0)
return log_error_errno(r, "Could not extend PCR %i: %m", TPM2_PCR_KERNEL_INITRD);
return 1;
#else /* HAVE_OPENSSL */
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled.");

View File

@@ -144,6 +144,7 @@ typedef enum Tpm2UserspaceEventType {
TPM2_EVENT_MACHINE_ID,
TPM2_EVENT_PRODUCT_ID,
TPM2_EVENT_KEYSLOT,
TPM2_EVENT_NVPCR_INIT,
_TPM2_USERSPACE_EVENT_TYPE_MAX,
_TPM2_USERSPACE_EVENT_TYPE_INVALID = -EINVAL,
} Tpm2UserspaceEventType;