]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
tpm2-setup: measure information about NvPCR initialization to PCR 9
authorLennart Poettering <lennart@poettering.net>
Wed, 12 Nov 2025 21:35:30 +0000 (22:35 +0100)
committerLennart Poettering <lennart@poettering.net>
Fri, 14 Nov 2025 21:04:58 +0000 (22:04 +0100)
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.

docs/TPM2_PCR_MEASUREMENTS.md
src/shared/tpm2-util.c
src/shared/tpm2-util.h

index abd280c004884767f609bc82df8fa35657fd3a40..7b29069a7ea2a5dcabead27192d2f69aa74a19f7 100644 (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
index 356c3e4909e2d6c7de3f28bbc64e859681449dda..ad61fd1d8bdaa2e9248a7ce37c73a3c368f71c64 100644 (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.");
index abf8adfdaf2a3e195cbae55b95c3df72cbb56dfa..8dfe87af0706f6dc9eab7656b6cb0ecc635ca01b 100644 (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;