lib, protos, docs: Reshuffle Measured Boot PCR allocation
diff --git a/USAGE.md b/USAGE.md
index 383a3414..bb9b4921 100644
--- a/USAGE.md
+++ b/USAGE.md
@@ -56,11 +56,18 @@ When measured boot is active, Limine extends the platform PCRs with the
artifacts it loads, following the GRUB convention from the
[UAPI Linux TPM PCR Registry](https://uapi-group.org/specifications/specs/linux_tpm_pcr_registry/):
-* **PCR 8** receives, in order, the on-disk `limine.conf` bytes (before any
- in-memory cleanup), and the kernel command line of the booted entry.
-* **PCR 9** receives, in load order, the kernel image as read from disk, each
- module/initrd in the order they appear in the config, and, when the booted
- protocol consumes a device tree blob, the DTB as loaded (taken from
+* **PCR 8** receives, in order, the kernel command line of the booted entry
+ (from its `cmdline` config option, exactly as Limine will hand it to the
+ kernel), the kernel image's path, each module/initrd's path in the order
+ they appear in the config, and the DTB's path when the booted protocol
+ consumes one specified via `dtb_path` or `global_dtb`. Any trailing
+ `#<hash>` integrity suffix is stripped before measurement so PCR 8 stays
+ stable across kernel/module/DTB updates; the `$` decompression prefix is
+ preserved as part of the policy.
+* **PCR 9** receives, in order, the on-disk `limine.conf` bytes (before any
+ in-memory cleanup), the kernel image as read from disk, each module/initrd
+ in the order they appear in the config, and, when the booted protocol
+ consumes a device tree blob, the DTB as loaded (taken from
`dtb_path`/`global_dtb` if set, otherwise from the firmware's
`EFI_DTB_TABLE_GUID` table) before Limine's `/chosen` and memory-node
fixups.
@@ -93,10 +100,21 @@ handoff is consistent across attempts:
### Reproducing the digests
For an external verifier to recompute a PCR extend, hash exactly these bytes:
+For PCR 8:
+
+* `cmdline: <cmdline>`: the kernel command line bytes, without trailing
+ NUL, i.e. `strlen(cmdline)` bytes.
+* `path: <kernel_path>`: the kernel image's path string with any trailing
+ `#<hash>` suffix stripped, without trailing NUL.
+* `module_path: <module_path>`: the module/initrd's path string with any
+ trailing `#<hash>` suffix stripped, without trailing NUL.
+* `dtb_path: <dtb_path>`: the DTB file's path string with any trailing
+ `#<hash>` suffix stripped, without trailing NUL.
+
+For PCR 9:
+
* `limine_cfg`: the on-disk `limine.conf` file bytes verbatim (no trailing
newline added, no NUL appended).
-* `cmdline: <cmdline>`: the kernel command line as a string, without its
- terminating NUL, i.e. `strlen(cmdline)` bytes.
* `path: <kernel_path>`: the full file bytes of the kernel image as opened
by Limine (post-decompression for `$`-prefixed paths, after BLAKE2B hash
verification when Secure Boot is active).
diff --git a/common/lib/misc.c b/common/lib/misc.c
index 00c78489..714b8219 100644
--- a/common/lib/misc.c
+++ b/common/lib/misc.c
@@ -156,6 +156,7 @@ void *get_device_tree_blob(const char *config, size_t extra_size,
#if defined (UEFI)
if (measure) {
+ tpm_measure_path(TPM_PCR_BOOT_AUTH, TPM_EV_IPL, "dtb_path: ", dtb_path);
tpm_measure(TPM_PCR_LOADED_IMAGES, TPM_EV_IPL,
dtb, size, "dtb_path: ", dtb_path);
}
diff --git a/common/lib/tpm.c b/common/lib/tpm.c
index 1d9a6e39..0cd2f731 100644
--- a/common/lib/tpm.c
+++ b/common/lib/tpm.c
@@ -172,6 +172,29 @@ void tpm_measure(uint32_t pcr, uint32_t event_type,
}
}
+void tpm_measure_path(uint32_t pcr, uint32_t event_type,
+ const char *desc_prefix, const char *path) {
+ if (!measured_boot || path == NULL) {
+ return;
+ }
+
+ const char *hash_sep = strchr(path, '#');
+ size_t path_len = hash_sep != NULL
+ ? (size_t)(hash_sep - path)
+ : strlen(path);
+
+ // Static scratch matches uri.c's URI_BUF_SIZE; URIs longer than that
+ // already panic in uri_resolve(), so a too-long path here is a bug.
+ static char stripped[4096];
+ if (path_len >= sizeof(stripped)) {
+ return;
+ }
+ memcpy(stripped, path, path_len);
+ stripped[path_len] = '\0';
+
+ tpm_measure(pcr, event_type, stripped, path_len, desc_prefix, stripped);
+}
+
uint32_t tpm_calc_event_size(const void *event_p, const void *header_p) {
const struct tpm_pcr_event2_head *event = event_p;
const struct tpm_pcr_event_v1_2 *event_header = header_p;
diff --git a/common/lib/tpm.h b/common/lib/tpm.h
index 87b061d7..6c8cb7bd 100644
--- a/common/lib/tpm.h
+++ b/common/lib/tpm.h
@@ -25,6 +25,13 @@ void tpm_measure(uint32_t pcr, uint32_t event_type,
const void *data, size_t data_size,
const char *desc_prefix, const char *desc_value);
+// Measure a config-supplied URI string into the given PCR with any trailing
+// `#<hash>` suffix stripped, so the digest captures only the policy-stable
+// portion (resource, root, path, and any `$` decompression marker). The
+// event description shows the same stripped string.
+void tpm_measure_path(uint32_t pcr, uint32_t event_type,
+ const char *desc_prefix, const char *path);
+
// Capture the firmware TCG2 event log into bootloader-reclaimable memory
// and expose the raw event stream. `format` receives the TCG event log
// format identifier (1 = TCG 1.2, 2 = TCG 2.0 crypto-agile). Returns false
diff --git a/common/menu.c b/common/menu.c
index d11ac1ac..bf86e968 100644
--- a/common/menu.c
+++ b/common/menu.c
@@ -1192,7 +1192,7 @@ noreturn void _menu(bool first_run) {
size_t raw_size;
const char *raw = config_get_raw(&raw_size);
if (raw != NULL) {
- tpm_measure(TPM_PCR_BOOT_AUTH, TPM_EV_IPL,
+ tpm_measure(TPM_PCR_LOADED_IMAGES, TPM_EV_IPL,
raw, raw_size, "limine_cfg", NULL);
}
#endif
diff --git a/common/protos/limine.c b/common/protos/limine.c
index d0888d1f..aa89a3b6 100644
--- a/common/protos/limine.c
+++ b/common/protos/limine.c
@@ -524,6 +524,7 @@ noreturn void limine_load(char *config, char *cmdline) {
uint8_t *kernel = kernel_file->fd;
#if defined (UEFI)
+ tpm_measure_path(TPM_PCR_BOOT_AUTH, TPM_EV_IPL, "path: ", kernel_path);
tpm_measure(TPM_PCR_LOADED_IMAGES, TPM_EV_IPL,
kernel, kernel_file->size, "path: ", kernel_path);
#endif
@@ -1340,6 +1341,7 @@ FEAT_START
*l = get_file(f, module_cmdline);
#if defined (UEFI)
+ tpm_measure_path(TPM_PCR_BOOT_AUTH, TPM_EV_IPL, "module_path: ", module_path);
tpm_measure(TPM_PCR_LOADED_IMAGES, TPM_EV_IPL,
f->fd, f->size, "module_path: ", module_path);
#endif
diff --git a/common/protos/linux_risc.c b/common/protos/linux_risc.c
index 4f483f14..d49e27e3 100644
--- a/common/protos/linux_risc.c
+++ b/common/protos/linux_risc.c
@@ -158,6 +158,7 @@ static void load_module(struct boot_param *p, char *config) {
char *module_path = config_get_value(config, i, "MODULE_PATH");
+ tpm_measure_path(TPM_PCR_BOOT_AUTH, TPM_EV_IPL, "module_path: ", module_path);
tpm_measure(TPM_PCR_LOADED_IMAGES, TPM_EV_IPL,
p->module_base + offset, module_size, "module_path: ", module_path);
@@ -526,6 +527,7 @@ noreturn void linux_load(char *config, char *cmdline) {
fclose(kernel_file);
printv("linux: loaded kernel `%s` at %p, size %U\n", kernel_path, p.kernel_base, (uint64_t)p.kernel_size);
+ tpm_measure_path(TPM_PCR_BOOT_AUTH, TPM_EV_IPL, "path: ", kernel_path);
tpm_measure(TPM_PCR_LOADED_IMAGES, TPM_EV_IPL,
p.kernel_base, p.kernel_size, "path: ", kernel_path);
diff --git a/common/protos/linux_x86.c b/common/protos/linux_x86.c
index b76fbd4d..9fed6999 100644
--- a/common/protos/linux_x86.c
+++ b/common/protos/linux_x86.c
@@ -424,6 +424,7 @@ noreturn void linux_load(char *config, char *cmdline) {
fread(kernel_file, (void *)kernel_load_addr, real_mode_code_size, kernel_file->size - real_mode_code_size);
#if defined (UEFI)
+ tpm_measure_path(TPM_PCR_BOOT_AUTH, TPM_EV_IPL, "path: ", kernel_path);
tpm_measure(TPM_PCR_LOADED_IMAGES, TPM_EV_IPL,
kernel_file->fd, kernel_file->size, "path: ", kernel_path);
#endif
@@ -516,6 +517,7 @@ noreturn void linux_load(char *config, char *cmdline) {
fread(modules[i], (void *)_modules_mem_base, 0, modules[i]->size);
#if defined (UEFI)
+ tpm_measure_path(TPM_PCR_BOOT_AUTH, TPM_EV_IPL, "module_path: ", module_path);
tpm_measure(TPM_PCR_LOADED_IMAGES, TPM_EV_IPL,
(void *)_modules_mem_base, modules[i]->size, "module_path: ", module_path);
#endif
diff --git a/common/protos/multiboot1.c b/common/protos/multiboot1.c
index fdd08afa..a3c79cf0 100644
--- a/common/protos/multiboot1.c
+++ b/common/protos/multiboot1.c
@@ -85,6 +85,7 @@ noreturn void multiboot1_load(char *config, char *cmdline) {
size_t kernel_file_size = kernel_file->size;
#if defined (UEFI)
+ tpm_measure_path(TPM_PCR_BOOT_AUTH, TPM_EV_IPL, "path: ", kernel_path);
tpm_measure(TPM_PCR_LOADED_IMAGES, TPM_EV_IPL,
kernel, kernel_file_size, "path: ", kernel_path);
#endif
@@ -362,6 +363,7 @@ noreturn void multiboot1_load(char *config, char *cmdline) {
uint64_t module_target = (uint64_t)-1; /* no target preference, use top */
#if defined (UEFI)
+ tpm_measure_path(TPM_PCR_BOOT_AUTH, TPM_EV_IPL, "module_path: ", module_path);
tpm_measure(TPM_PCR_LOADED_IMAGES, TPM_EV_IPL,
module_addr, f->size, "module_path: ", module_path);
#endif
diff --git a/common/protos/multiboot2.c b/common/protos/multiboot2.c
index d6677d01..46fb6886 100644
--- a/common/protos/multiboot2.c
+++ b/common/protos/multiboot2.c
@@ -105,6 +105,7 @@ noreturn void multiboot2_load(char *config, char* cmdline) {
size_t kernel_file_size = kernel_file->size;
#if defined (UEFI)
+ tpm_measure_path(TPM_PCR_BOOT_AUTH, TPM_EV_IPL, "path: ", kernel_path);
tpm_measure(TPM_PCR_LOADED_IMAGES, TPM_EV_IPL,
kernel, kernel_file_size, "path: ", kernel_path);
#endif
@@ -666,6 +667,7 @@ reloc_fail:
uint64_t module_target = (uint64_t)-1;
#if defined (UEFI)
+ tpm_measure_path(TPM_PCR_BOOT_AUTH, TPM_EV_IPL, "module_path: ", module_path);
tpm_measure(TPM_PCR_LOADED_IMAGES, TPM_EV_IPL,
module_addr, f->size, "module_path: ", module_path);
#endif
