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)) {
