:: commit 3600dce820c3f2fbbe4894b0ffad20a3e41deeff

Mintsuki <mintsuki@protonmail.com> — 2026-04-29 19:20

parents: c5e109d7b6

lib/tpm: Implement measured boot via EFI_TCG2_PROTOCOL

diff --git a/common/entry.s3.c b/common/entry.s3.c
index 0b45967f..d6b86ebb 100644
--- a/common/entry.s3.c
+++ b/common/entry.s3.c
@@ -9,6 +9,7 @@
 #include <lib/config.h>
 #include <lib/trace.h>
 #include <lib/bli.h>
+#include <lib/tpm.h>
 #include <sys/e820.h>
 #include <sys/a20.h>
 #include <sys/idt.h>
@@ -107,6 +108,8 @@ defer_error:
         }
     }
 
+    tpm_init();
+
     boot_volume = NULL;
 
     EFI_HANDLE current_handle = ImageHandle;
diff --git a/common/lib/config.c b/common/lib/config.c
index cfd95d2e..af4f56d3 100644
--- a/common/lib/config.c
+++ b/common/lib/config.c
@@ -10,6 +10,7 @@
 #include <lib/print.h>
 #include <pxe/tftp.h>
 #include <crypt/blake2b.h>
+#include <lib/tpm.h>
 #include <sys/cpu.h>
 
 #define CONFIG_B2SUM_SIGNATURE "++CONFIG_B2SUM_SIGNATURE++"
@@ -384,6 +385,12 @@ int init_config(size_t config_size) {
         }
     }
 
+#if defined (UEFI)
+    // Measure the on-disk config bytes before the in-place mutations below.
+    tpm_measure(TPM_PCR_BOOT_AUTH, TPM_EV_IPL,
+                config_addr, config_size - 2, "Limine config");
+#endif
+
     // add trailing newline if not present
     config_addr[config_size - 2] = '\n';
 
diff --git a/common/lib/tpm.c b/common/lib/tpm.c
new file mode 100644
index 00000000..7b602b17
--- /dev/null
+++ b/common/lib/tpm.c
@@ -0,0 +1,69 @@
+#if defined (UEFI)
+
+#include <stdint.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <efi.h>
+#include <efi/protocol/efitcg2.h>
+#include <lib/tpm.h>
+#include <lib/misc.h>
+#include <lib/print.h>
+#include <lib/libc.h>
+#include <mm/pmm.h>
+
+static EFI_TCG2_PROTOCOL *tcg2 = NULL;
+
+void tpm_init(void) {
+    EFI_GUID tcg2_guid = EFI_TCG2_PROTOCOL_GUID;
+    EFI_TCG2_PROTOCOL *proto = NULL;
+    EFI_STATUS status = gBS->LocateProtocol(&tcg2_guid, NULL, (void **)&proto);
+    if (status != EFI_SUCCESS || proto == NULL) {
+        return;
+    }
+
+    EFI_TCG2_BOOT_SERVICE_CAPABILITY cap;
+    memset(&cap, 0, sizeof(cap));
+    cap.Size = sizeof(cap);
+    status = proto->GetCapability(proto, &cap);
+    if (status != EFI_SUCCESS || !cap.TPMPresentFlag) {
+        return;
+    }
+
+    tcg2 = proto;
+    printv("tpm: TCG2 protocol located, TPM present (active PCR banks: %x)\n",
+           (uint32_t)cap.ActivePcrBanks);
+}
+
+void tpm_measure(uint32_t pcr, uint32_t event_type,
+                 const void *data, size_t data_size,
+                 const char *description) {
+    if (tcg2 == NULL || data == NULL) {
+        return;
+    }
+
+    size_t desc_len = description != NULL ? strlen(description) : 0;
+    size_t event_size = offsetof(EFI_TCG2_EVENT, Event) + desc_len;
+
+    EFI_TCG2_EVENT *event = ext_mem_alloc(event_size);
+    event->Size = (UINT32)event_size;
+    event->Header.HeaderSize = sizeof(EFI_TCG2_EVENT_HEADER);
+    event->Header.HeaderVersion = 1;
+    event->Header.PCRIndex = pcr;
+    event->Header.EventType = event_type;
+    if (desc_len > 0) {
+        memcpy(event->Event, description, desc_len);
+    }
+
+    EFI_STATUS status = tcg2->HashLogExtendEvent(
+        tcg2, 0,
+        (EFI_PHYSICAL_ADDRESS)(uintptr_t)data, (UINT64)data_size,
+        event);
+    if (status != EFI_SUCCESS) {
+        printv("tpm: HashLogExtendEvent for PCR %u failed: %X\n",
+               pcr, (uint64_t)status);
+    }
+
+    pmm_free(event, event_size);
+}
+
+#endif
diff --git a/common/lib/tpm.h b/common/lib/tpm.h
new file mode 100644
index 00000000..d31d8b13
--- /dev/null
+++ b/common/lib/tpm.h
@@ -0,0 +1,26 @@
+#ifndef LIB__TPM_H__
+#define LIB__TPM_H__
+
+#include <stdint.h>
+#include <stddef.h>
+#include <stdbool.h>
+
+// PCR allocation, matching systemd-boot conventions:
+//   PCR 8: kernel command line and other authoritative strings
+//   PCR 9: kernel image, initrd, devicetree, and other binary blobs
+#define TPM_PCR_BOOT_AUTH       8
+#define TPM_PCR_LOADED_IMAGES   9
+
+// TCG PC Client Platform Firmware Profile event types
+#define TPM_EV_IPL              0x0000000d
+
+#if defined (UEFI)
+
+void tpm_init(void);
+void tpm_measure(uint32_t pcr, uint32_t event_type,
+                 const void *data, size_t data_size,
+                 const char *description);
+
+#endif
+
+#endif
diff --git a/common/protos/limine.c b/common/protos/limine.c
index 4bfea06c..1f628eca 100644
--- a/common/protos/limine.c
+++ b/common/protos/limine.c
@@ -16,6 +16,7 @@
 #include <lib/fdt.h>
 #include <libfdt.h>
 #include <lib/uri.h>
+#include <lib/tpm.h>
 #include <sys/smp.h>
 #include <sys/cpu.h>
 #include <sys/gdt.h>
@@ -456,6 +457,13 @@ static void *_get_request(uint64_t id[4]) {
 #define FEAT_END } while (0);
 
 noreturn void limine_load(char *config, char *cmdline) {
+#if defined (UEFI)
+    if (cmdline != NULL) {
+        tpm_measure(TPM_PCR_BOOT_AUTH, TPM_EV_IPL,
+                    cmdline, strlen(cmdline), "Limine cmdline");
+    }
+#endif
+
 #if defined (__x86_64__) || defined (__i386__)
     uint32_t eax, ebx, ecx, edx;
 #endif
@@ -515,6 +523,11 @@ noreturn void limine_load(char *config, char *cmdline) {
 
     uint8_t *kernel = kernel_file->fd;
 
+#if defined (UEFI)
+    tpm_measure(TPM_PCR_LOADED_IMAGES, TPM_EV_IPL,
+                kernel, kernel_file->size, "Limine executable");
+#endif
+
     char *kaslr_s = config_get_value(config, 0, "KASLR");
     bool kaslr = false;
     if (kaslr_s != NULL && strcmp(kaslr_s, "yes") == 0) {
@@ -1324,6 +1337,13 @@ FEAT_START
         struct limine_file *l = &modules[final_module_count++];
         *l = get_file(f, module_cmdline);
 
+#if defined (UEFI)
+        if (!f->is_high_mem) {
+            tpm_measure(TPM_PCR_LOADED_IMAGES, TPM_EV_IPL,
+                        f->fd, f->size, "Limine module");
+        }
+#endif
+
         fclose(f);
     }
 
diff --git a/common/protos/linux_risc.c b/common/protos/linux_risc.c
index b927ce49..c1f7a30d 100644
--- a/common/protos/linux_risc.c
+++ b/common/protos/linux_risc.c
@@ -11,6 +11,7 @@
 #include <lib/config.h>
 #include <lib/print.h>
 #include <lib/uri.h>
+#include <lib/tpm.h>
 #include <mm/pmm.h>
 #include <sys/idt.h>
 #include <lib/fb.h>
@@ -155,6 +156,9 @@ static void load_module(struct boot_param *p, char *config) {
         fread(modules[i], p->module_base + offset, 0, module_size);
         fclose(modules[i]);
 
+        tpm_measure(TPM_PCR_LOADED_IMAGES, TPM_EV_IPL,
+                    p->module_base + offset, module_size, "Linux initrd");
+
         char *module_path = config_get_value(config, i, "MODULE_PATH");
         printv("linux: loaded module `%s` at %p, size %U\n", module_path,
                p->module_base + offset, (uint64_t)module_size);
@@ -168,6 +172,11 @@ static void prepare_device_tree_blob(struct boot_param *p) {
     void *dtb = p->dtb;
     int ret;
 
+    // Measure the device tree as loaded, before applying our /chosen and
+    // memory-node fixups, so the resulting PCR is stable across boots.
+    tpm_measure(TPM_PCR_LOADED_IMAGES, TPM_EV_IPL,
+                dtb, fdt_totalsize(dtb), "Linux DTB");
+
     // Delete all /memory@... nodes. Linux will use the given UEFI memory map
     // instead.
     while (true) {
@@ -466,6 +475,11 @@ noreturn void linux_load(char *config, char *cmdline) {
     p.cmdline = cmdline;
     p.dtb = get_device_tree_blob(config, 0x1000);
 
+    if (cmdline != NULL) {
+        tpm_measure(TPM_PCR_BOOT_AUTH, TPM_EV_IPL,
+                    cmdline, strlen(cmdline), "Linux cmdline");
+    }
+
     struct file_handle *kernel_file;
 
     char *kernel_path = config_get_value(config, 0, "PATH");
@@ -515,6 +529,9 @@ 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(TPM_PCR_LOADED_IMAGES, TPM_EV_IPL,
+                p.kernel_base, p.kernel_size, "Linux kernel");
+
     load_module(&p, config);
 
     prepare_device_tree_blob(&p);
diff --git a/common/protos/linux_x86.c b/common/protos/linux_x86.c
index cf67099b..2bc5c0ea 100644
--- a/common/protos/linux_x86.c
+++ b/common/protos/linux_x86.c
@@ -12,6 +12,7 @@
 #include <lib/config.h>
 #include <lib/print.h>
 #include <lib/uri.h>
+#include <lib/tpm.h>
 #include <mm/pmm.h>
 #include <sys/idt.h>
 #include <lib/fb.h>
@@ -292,6 +293,13 @@ struct boot_params {
 noreturn void linux_load(char *config, char *cmdline) {
     struct file_handle *kernel_file;
 
+#if defined (UEFI)
+    if (cmdline != NULL) {
+        tpm_measure(TPM_PCR_BOOT_AUTH, TPM_EV_IPL,
+                    cmdline, strlen(cmdline), "Linux cmdline");
+    }
+#endif
+
     char *kernel_path = config_get_value(config, 0, "PATH");
     if (kernel_path == NULL) {
         kernel_path = config_get_value(config, 0, "KERNEL_PATH");
@@ -415,6 +423,11 @@ 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(TPM_PCR_LOADED_IMAGES, TPM_EV_IPL,
+                (void *)kernel_load_addr, kernel_data_size, "Linux kernel");
+#endif
+
     fclose(kernel_file);
 
     ///////////////////////////////////////
@@ -502,6 +515,11 @@ noreturn void linux_load(char *config, char *cmdline) {
 
         fread(modules[i], (void *)_modules_mem_base, 0, modules[i]->size);
 
+#if defined (UEFI)
+        tpm_measure(TPM_PCR_LOADED_IMAGES, TPM_EV_IPL,
+                    (void *)_modules_mem_base, modules[i]->size, "Linux initrd");
+#endif
+
         _modules_mem_base += modules[i]->size;
 
         fclose(modules[i]);
diff --git a/common/protos/multiboot1.c b/common/protos/multiboot1.c
index 8e3a646b..9d811263 100644
--- a/common/protos/multiboot1.c
+++ b/common/protos/multiboot1.c
@@ -12,6 +12,7 @@
 #include <lib/config.h>
 #include <lib/print.h>
 #include <lib/uri.h>
+#include <lib/tpm.h>
 #include <lib/fb.h>
 #include <lib/term.h>
 #include <lib/elsewhere.h>
@@ -55,6 +56,13 @@ static void *mb1_info_alloc(void **mb1_info_raw, size_t size) {
 noreturn void multiboot1_load(char *config, char *cmdline) {
     struct file_handle *kernel_file;
 
+#if defined (UEFI)
+    if (cmdline != NULL) {
+        tpm_measure(TPM_PCR_BOOT_AUTH, TPM_EV_IPL,
+                    cmdline, strlen(cmdline), "Multiboot1 cmdline");
+    }
+#endif
+
     char *kernel_path = config_get_value(config, 0, "PATH");
     if (kernel_path == NULL) {
         kernel_path = config_get_value(config, 0, "KERNEL_PATH");
@@ -76,6 +84,11 @@ noreturn void multiboot1_load(char *config, char *cmdline) {
 
     size_t kernel_file_size = kernel_file->size;
 
+#if defined (UEFI)
+    tpm_measure(TPM_PCR_LOADED_IMAGES, TPM_EV_IPL,
+                kernel, kernel_file_size, "Multiboot1 kernel");
+#endif
+
     fclose(kernel_file);
 
     struct multiboot1_header header = {0};
@@ -348,6 +361,11 @@ noreturn void multiboot1_load(char *config, char *cmdline) {
             void *module_addr = f->fd;
             uint64_t module_target = (uint64_t)-1; /* no target preference, use top */
 
+#if defined (UEFI)
+            tpm_measure(TPM_PCR_LOADED_IMAGES, TPM_EV_IPL,
+                        module_addr, f->size, "Multiboot1 module");
+#endif
+
             if (!elsewhere_append(true /* flexible target */,
                     ranges, &ranges_count, ranges_max,
                     module_addr, &module_target, f->size)) {
diff --git a/common/protos/multiboot2.c b/common/protos/multiboot2.c
index 77459d0f..4a60f522 100644
--- a/common/protos/multiboot2.c
+++ b/common/protos/multiboot2.c
@@ -12,6 +12,7 @@
 #include <lib/config.h>
 #include <lib/print.h>
 #include <lib/uri.h>
+#include <lib/tpm.h>
 #include <lib/fb.h>
 #include <lib/term.h>
 #include <lib/elsewhere.h>
@@ -75,6 +76,13 @@ static size_t get_multiboot2_info_size(
 noreturn void multiboot2_load(char *config, char* cmdline) {
     struct file_handle *kernel_file;
 
+#if defined (UEFI)
+    if (cmdline != NULL) {
+        tpm_measure(TPM_PCR_BOOT_AUTH, TPM_EV_IPL,
+                    cmdline, strlen(cmdline), "Multiboot2 cmdline");
+    }
+#endif
+
     char *kernel_path = config_get_value(config, 0, "PATH");
     if (kernel_path == NULL) {
         kernel_path = config_get_value(config, 0, "KERNEL_PATH");
@@ -96,6 +104,11 @@ noreturn void multiboot2_load(char *config, char* cmdline) {
 
     size_t kernel_file_size = kernel_file->size;
 
+#if defined (UEFI)
+    tpm_measure(TPM_PCR_LOADED_IMAGES, TPM_EV_IPL,
+                kernel, kernel_file_size, "Multiboot2 kernel");
+#endif
+
     fclose(kernel_file);
 
     struct multiboot_header *header = NULL;
@@ -652,6 +665,11 @@ reloc_fail:
         void *module_addr = f->fd;
         uint64_t module_target = (uint64_t)-1;
 
+#if defined (UEFI)
+        tpm_measure(TPM_PCR_LOADED_IMAGES, TPM_EV_IPL,
+                    module_addr, f->size, "Multiboot2 module");
+#endif
+
         if (!elsewhere_append(true /* flexible target */,
                 ranges, &ranges_count, ranges_max,
                 module_addr, &module_target, f->size)) {
tab: 248 wrap: offon