From c2d2dedc424d1ed3820737669ee88c4a149daa79 Mon Sep 17 00:00:00 2001 From: David Garske Date: Fri, 13 Mar 2026 07:34:28 +0100 Subject: [PATCH 1/4] Improve measured boot support --- docs/TPM.md | 7 +++++-- docs/measured_boot.md | 15 ++++++++++----- options.mk | 3 +++ src/tpm.c | 41 ++++++++++++++++++++++++++++++----------- 4 files changed, 48 insertions(+), 18 deletions(-) diff --git a/docs/TPM.md b/docs/TPM.md index d936a74013..94fd77f2af 100644 --- a/docs/TPM.md +++ b/docs/TPM.md @@ -11,8 +11,9 @@ In wolfBoot we support TPM based root of trust, sealing/unsealing, cryptographic | `WOLFBOOT_TPM_KEYSTORE=1` | `WOLFBOOT_TPM_KEYSTORE` | Enables TPM based root of trust. NV Index must store a hash of the trusted public key. | | `WOLFBOOT_TPM_KEYSTORE_NV_BASE=0x` | `WOLFBOOT_TPM_KEYSTORE_NV_BASE=0x` | NV index in platform range 0x1400000 - 0x17FFFFF. | | `WOLFBOOT_TPM_KEYSTORE_AUTH=secret` | `WOLFBOOT_TPM_KEYSTORE_AUTH` | Password for NV access | -| `MEASURED_BOOT=1` | `WOLFBOOT_MEASURED_BOOT` | Enable measured boot. Extend PCR with wolfBoot hash. | +| `MEASURED_BOOT=1` | `WOLFBOOT_MEASURED_BOOT` | Enable measured boot. Extends PCR with a hash of the wolfBoot bootloader code. | | `MEASURED_PCR_A=16` | `WOLFBOOT_MEASURED_PCR_A=16` | The PCR index to use. See [docs/measured_boot.md](/docs/measured_boot.md). | +| `MEASURED_BOOT_APP_PARTITION=1` | `WOLFBOOT_MEASURED_BOOT_APP_PARTITION` | Legacy: measure the boot (application) partition instead of wolfBoot code. | | `WOLFBOOT_TPM_SEAL=1` | `WOLFBOOT_TPM_SEAL` | Enables support for sealing/unsealing based on PCR policy signed externally. | | `WOLFBOOT_TPM_SEAL_NV_BASE=0x01400300` | `WOLFBOOT_TPM_SEAL_NV_BASE` | To override the default sealed blob storage location in the platform hierarchy. | | `WOLFBOOT_TPM_SEAL_AUTH=secret` | `WOLFBOOT_TPM_SEAL_AUTH` | Password for sealing/unsealing secrets, if omitted the PCR policy will be used | @@ -30,7 +31,9 @@ NOTE: The TPM's RSA verify requires ASN.1 encoding, so use SIGN=RSA2048ENC ## Measured Boot -The wolfBoot image is hashed and extended to the indicated PCR. This can be used later in the application to prove the boot process was not tampered with. Enabled with `WOLFBOOT_MEASURED_BOOT` and exposes API `wolfBoot_tpm2_extend`. +The wolfBoot bootloader code is hashed and extended to the indicated PCR. This can be used later in the application to prove the boot process was not tampered with. Enabled with `WOLFBOOT_MEASURED_BOOT` and exposes API `wolfBoot_tpm2_extend`. + +By default, the measurement covers wolfBoot's own code region (from `_start_text` to `_stored_data` linker symbols). To use the legacy behavior of measuring the boot (application) partition instead, set `MEASURED_BOOT_APP_PARTITION=1`. ## Sealing and Unsealing a secret diff --git a/docs/measured_boot.md b/docs/measured_boot.md index f02d42a605..78d774bc03 100644 --- a/docs/measured_boot.md +++ b/docs/measured_boot.md @@ -30,8 +30,13 @@ Having TPM measurements provide a way for the firmware or Operating System(OS), like Windows or Linux, to know that the software loaded before it gained control over system, is trustworthy and not modified. -In wolfBoot the concept is simplified to measuring a single component, the main -firmware image. However, this can easily be extended by using more PCR registers. +In wolfBoot the concept is simplified to measuring a single component, the +wolfBoot bootloader code itself. This ensures the bootloader has not been +tampered with before it verifies and loads the application. However, this can +easily be extended by using more PCR registers. + +To use the legacy behavior of measuring the boot (application) partition instead +of wolfBoot's own code, set `MEASURED_BOOT_APP_PARTITION=1` in your config. ## Configuration @@ -81,6 +86,6 @@ MEASURED_PCR_A?=16 ### Code wolfBoot offers out-of-the-box solution. There is zero need of the developer to touch wolfBoot code -in order to use measured boot. If you would want to check the code, then look in `src/image.c` and -more specifically the `measure_boot()` function. There you would find several TPM2 native API calls -to wolfTPM. For more information about wolfTPM you can check its GitHub repository. +in order to use measured boot. If you would want to check the code, then look in `src/tpm.c` and +more specifically the `self_hash()` and `measure_boot()` functions. There you would find several TPM2 +native API calls to wolfTPM. For more information about wolfTPM you can check its GitHub repository. diff --git a/options.mk b/options.mk index be66a7dd2e..fd8091be89 100644 --- a/options.mk +++ b/options.mk @@ -38,6 +38,9 @@ ifeq ($(MEASURED_BOOT),1) WOLFTPM:=1 CFLAGS+=-D"WOLFBOOT_MEASURED_BOOT" CFLAGS+=-D"WOLFBOOT_MEASURED_PCR_A=$(MEASURED_PCR_A)" + ifeq ($(MEASURED_BOOT_APP_PARTITION),1) + CFLAGS+=-D"WOLFBOOT_MEASURED_BOOT_APP_PARTITION" + endif endif ## TPM keystore diff --git a/src/tpm.c b/src/tpm.c index 6a6edc72b5..eb3050ef0b 100644 --- a/src/tpm.c +++ b/src/tpm.c @@ -229,13 +229,29 @@ static int TPM2_IoCb(TPM2_CTX* ctx, const uint8_t* txBuf, uint8_t* rxBuf, #ifdef WOLFBOOT_MEASURED_BOOT -#ifndef WOLFBOOT_NO_PARTITIONS +#ifdef WOLFBOOT_MEASURED_BOOT_APP_PARTITION + /* Legacy: measure the boot (application) partition */ + #ifndef WOLFBOOT_NO_PARTITIONS + #define SELF_HASH_ADDR ((uintptr_t)WOLFBOOT_PARTITION_BOOT_ADDRESS) + #define SELF_HASH_SZ ((uint32_t)WOLFBOOT_PARTITION_SIZE) + #endif +#else + /* Default: measure wolfBoot's own code region + * (from ARCH_FLASH_OFFSET to WOLFBOOT_PARTITION_BOOT_ADDRESS) */ + #if defined(WOLFBOOT_PARTITION_BOOT_ADDRESS) && defined(ARCH_FLASH_OFFSET) + #define SELF_HASH_ADDR ((uintptr_t)ARCH_FLASH_OFFSET) + #define SELF_HASH_SZ ((uint32_t)((uintptr_t)WOLFBOOT_PARTITION_BOOT_ADDRESS - \ + (uintptr_t)ARCH_FLASH_OFFSET)) + #endif +#endif + +#ifdef SELF_HASH_ADDR #ifdef WOLFBOOT_HASH_SHA256 #include static int self_sha256(uint8_t *hash) { - uintptr_t p = (uintptr_t)WOLFBOOT_PARTITION_BOOT_ADDRESS; - uint32_t sz = (uint32_t)WOLFBOOT_PARTITION_SIZE; + uintptr_t p = SELF_HASH_ADDR; + uint32_t sz = SELF_HASH_SZ; uint32_t blksz, position = 0; wc_Sha256 sha256_ctx; @@ -244,7 +260,8 @@ static int self_sha256(uint8_t *hash) blksz = WOLFBOOT_SHA_BLOCK_SIZE; if (position + blksz > sz) blksz = sz - position; - #if defined(EXT_FLASH) && defined(NO_XIP) + #if defined(EXT_FLASH) && defined(NO_XIP) && \ + defined(WOLFBOOT_MEASURED_BOOT_APP_PARTITION) rc = ext_flash_read(p, ext_hash_block, WOLFBOOT_SHA_BLOCK_SIZE); if (rc != WOLFBOOT_SHA_BLOCK_SIZE) return -1; @@ -264,8 +281,8 @@ static int self_sha256(uint8_t *hash) #include static int self_sha384(uint8_t *hash) { - uintptr_t p = (uintptr_t)WOLFBOOT_PARTITION_BOOT_ADDRESS; - uint32_t sz = (uint32_t)WOLFBOOT_PARTITION_SIZE; + uintptr_t p = SELF_HASH_ADDR; + uint32_t sz = SELF_HASH_SZ; uint32_t blksz, position = 0; wc_Sha384 sha384_ctx; @@ -274,7 +291,8 @@ static int self_sha384(uint8_t *hash) blksz = WOLFBOOT_SHA_BLOCK_SIZE; if (position + blksz > sz) blksz = sz - position; - #if defined(EXT_FLASH) && defined(NO_XIP) + #if defined(EXT_FLASH) && defined(NO_XIP) && \ + defined(WOLFBOOT_MEASURED_BOOT_APP_PARTITION) rc = ext_flash_read(p, ext_hash_block, WOLFBOOT_SHA_BLOCK_SIZE); if (rc != WOLFBOOT_SHA_BLOCK_SIZE) return -1; @@ -290,7 +308,7 @@ static int self_sha384(uint8_t *hash) return 0; } #endif /* HASH type */ -#endif /* WOLFBOOT_NO_PARTITIONS */ +#endif /* SELF_HASH_ADDR */ /** * @brief Extends a PCR in the TPM with a hash. @@ -1434,8 +1452,9 @@ int wolfBoot_tpm2_init(void) } #endif /* WOLFBOOT_TPM_KEYSTORE | WOLFBOOT_TPM_SEAL */ -#if defined(WOLFBOOT_MEASURED_BOOT) && !defined(WOLFBOOT_NO_PARTITIONS) - /* hash wolfBoot and extend PCR */ +#if defined(WOLFBOOT_MEASURED_BOOT) && defined(SELF_HASH_ADDR) + /* measured boot: hash wolfBoot code (or boot partition if + * WOLFBOOT_MEASURED_BOOT_APP_PARTITION) and extend PCR */ if (rc == 0) { rc = self_hash(digest); if (rc == 0) { @@ -1445,7 +1464,7 @@ int wolfBoot_tpm2_init(void) wolfBoot_printf("Error %d performing wolfBoot measurement!\n", rc); } } -#endif /* defined(WOLFBOOT_MEASURED_BOOT) && !defined(WOLFBOOT_NO_PARTITIONS) */ +#endif /* WOLFBOOT_MEASURED_BOOT && SELF_HASH_ADDR */ return rc; } From bec5dc267510c55d8f3c4293e9b9b436db468ee6 Mon Sep 17 00:00:00 2001 From: David Garske Date: Fri, 13 Mar 2026 13:29:54 +0100 Subject: [PATCH 2/4] Update to use linker script for bootloader region to measure --- src/tpm.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/tpm.c b/src/tpm.c index eb3050ef0b..3a27ace7b7 100644 --- a/src/tpm.c +++ b/src/tpm.c @@ -235,14 +235,20 @@ static int TPM2_IoCb(TPM2_CTX* ctx, const uint8_t* txBuf, uint8_t* rxBuf, #define SELF_HASH_ADDR ((uintptr_t)WOLFBOOT_PARTITION_BOOT_ADDRESS) #define SELF_HASH_SZ ((uint32_t)WOLFBOOT_PARTITION_SIZE) #endif -#else - /* Default: measure wolfBoot's own code region - * (from ARCH_FLASH_OFFSET to WOLFBOOT_PARTITION_BOOT_ADDRESS) */ +#elif defined(ARCH_SIM) + /* Simulator: no linker script, use bootloader partition region */ #if defined(WOLFBOOT_PARTITION_BOOT_ADDRESS) && defined(ARCH_FLASH_OFFSET) #define SELF_HASH_ADDR ((uintptr_t)ARCH_FLASH_OFFSET) #define SELF_HASH_SZ ((uint32_t)((uintptr_t)WOLFBOOT_PARTITION_BOOT_ADDRESS - \ (uintptr_t)ARCH_FLASH_OFFSET)) #endif +#else + /* Default: measure wolfBoot's own code using linker script symbols */ + extern unsigned int _start_text; + extern unsigned int _stored_data; + #define SELF_HASH_ADDR ((uintptr_t)&_start_text) + #define SELF_HASH_SZ ((uint32_t)((uintptr_t)&_stored_data - \ + (uintptr_t)&_start_text)) #endif #ifdef SELF_HASH_ADDR From 7be56a7d172803dfaf8da0f178facaa3b939a7af Mon Sep 17 00:00:00 2001 From: David Garske Date: Fri, 13 Mar 2026 08:58:48 -0700 Subject: [PATCH 3/4] Updated x86 linker scripts with missing variable --- hal/x86_fsp_qemu.ld.in | 2 ++ hal/x86_fsp_qemu_stage1.ld.in | 1 + hal/x86_fsp_tgl.ld.in | 1 + hal/x86_fsp_tgl_stage1.ld.in | 1 + 4 files changed, 5 insertions(+) diff --git a/hal/x86_fsp_qemu.ld.in b/hal/x86_fsp_qemu.ld.in index cfecbbfcb5..3e08865cb4 100644 --- a/hal/x86_fsp_qemu.ld.in +++ b/hal/x86_fsp_qemu.ld.in @@ -25,6 +25,8 @@ SECTIONS _end_wolfboot = .; } > RAM + _stored_data = _end_text; + _fsp_size = _end_fsp_s - _start_fsp_s; .bss WOLFBOOT_LOAD_BASE + SIZEOF(.text) (NOLOAD): { diff --git a/hal/x86_fsp_qemu_stage1.ld.in b/hal/x86_fsp_qemu_stage1.ld.in index 962cd8f09b..dc43b4c2a9 100644 --- a/hal/x86_fsp_qemu_stage1.ld.in +++ b/hal/x86_fsp_qemu_stage1.ld.in @@ -56,6 +56,7 @@ SECTIONS .bootloader WOLFBOOT_ORIGIN : { + _start_text = .; KEEP(*(.boot*)) *(.text*) *(.rodata*) diff --git a/hal/x86_fsp_tgl.ld.in b/hal/x86_fsp_tgl.ld.in index 1ec85c53f1..9c4c156984 100644 --- a/hal/x86_fsp_tgl.ld.in +++ b/hal/x86_fsp_tgl.ld.in @@ -25,6 +25,7 @@ SECTIONS _end_wolfboot = .; } + _stored_data = _end_text; _fsp_size = _end_fsp_s - _start_fsp_s; .bss WOLFBOOT_LOAD_BASE + SIZEOF(.text) (NOLOAD): { diff --git a/hal/x86_fsp_tgl_stage1.ld.in b/hal/x86_fsp_tgl_stage1.ld.in index 4b7c0ca7bd..923d3bbae8 100644 --- a/hal/x86_fsp_tgl_stage1.ld.in +++ b/hal/x86_fsp_tgl_stage1.ld.in @@ -53,6 +53,7 @@ SECTIONS .bootloader WOLFBOOT_ORIGIN : { + _start_text = .; KEEP(./tgl_fsp.o(.boot)) KEEP(*(.boot*)) KEYSTORE_START = .; From 937c20b29aa0abf4d64a8f18b2184bbd3791223f Mon Sep 17 00:00:00 2001 From: David Garske Date: Tue, 17 Mar 2026 12:21:18 -0700 Subject: [PATCH 4/4] Fixes for X86 FSP QEMU test --- src/tpm.c | 4 ++++ tools/scripts/x86_fsp/compute_pcr.py | 28 +++++++++++++++++++--------- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/tpm.c b/src/tpm.c index 3a27ace7b7..e8e37eca19 100644 --- a/src/tpm.c +++ b/src/tpm.c @@ -242,6 +242,10 @@ static int TPM2_IoCb(TPM2_CTX* ctx, const uint8_t* txBuf, uint8_t* rxBuf, #define SELF_HASH_SZ ((uint32_t)((uintptr_t)WOLFBOOT_PARTITION_BOOT_ADDRESS - \ (uintptr_t)ARCH_FLASH_OFFSET)) #endif +#elif defined(WOLFBOOT_FSP) + /* FSP: stage1 boot_x86_fsp.c handles measurement via self_extend_pcr() + * and wolfBoot_image_measure(). Skip generic self-measurement here since + * stage2 .data is interleaved with .text making the hash non-deterministic */ #else /* Default: measure wolfBoot's own code using linker script symbols */ extern unsigned int _start_text; diff --git a/tools/scripts/x86_fsp/compute_pcr.py b/tools/scripts/x86_fsp/compute_pcr.py index db3af5012b..7e3c1048e9 100644 --- a/tools/scripts/x86_fsp/compute_pcr.py +++ b/tools/scripts/x86_fsp/compute_pcr.py @@ -61,13 +61,21 @@ def get_sha256_hash_of_wolfboot_image(file_path: str): return data[4:4+l] data = data[4+l:] +def get_sym_addr(elf_file: str, sym_name: str) -> int: + """ + get the address of a symbol from ELF file + """ + symbols = subprocess.check_output(['nm', elf_file]).split(b'\n') + matches = list(filter(lambda x: sym_name.encode() in x, symbols)) + if not matches: + return None + return int(matches[0].split(b' ')[0], 16) + def get_keystore_sym_addr() -> int: """ get the address of symbol keystore from ELF file image """ - symbols = subprocess.check_output(['nm', 'stage1/loader_stage1.elf']).split(b'\n') - _start_keystore = int(list(filter(lambda x: b'_start_keystore' in x, symbols))[0].split(b' ')[0], 16) - return _start_keystore + return get_sym_addr('stage1/loader_stage1.elf', '_start_keystore') def pcr_extend(pcr: bytearray, data: bytearray) -> bytearray: """ @@ -95,23 +103,25 @@ def pcr_extend(pcr: bytearray, data: bytearray) -> bytearray: pcr0 = bytearray(b'\x00'*32) if args.target == 'qemu': + # self_extend_pcr() in boot_x86_fsp.c + # Hashes from _start_keystore to end of 4GB (keystore + vectors) keystore_addr = get_keystore_sym_addr() keystore_off = addr_to_off(keystore_addr, image_size = len(image)) ibb = image[keystore_off:] - h = hashlib.sha256() - h.update(ibb) - pcr0_data_hash = h.digest() - pcr0 = pcr_extend(b'\x00'*32, pcr0_data_hash) + pcr0 = pcr_extend(pcr0, get_sha256_hash(ibb)) print(f"Initial PCR0: {pcr0.hex()}") is_stage1_auth_enabled = get_config_value(config, 'STAGE1_AUTH') == '1' print(f"stage1 auth is {'enabled' if is_stage1_auth_enabled else 'disabled'}") - if is_stage1_auth_enabled: + is_measured_boot = get_config_value(config, 'MEASURED_BOOT') == '1' + + # wolfBoot_image_measure() extends PCR with wolfboot image hash + if is_measured_boot: wb_hash = get_sha256_hash_of_wolfboot_image('stage1/wolfboot_raw_v1_signed.bin') pcr0 = pcr_extend(pcr0, wb_hash) - print(f"PCR0 after wolfboot: {pcr0.hex()}") + print(f"PCR0 after wolfboot image measure: {pcr0.hex()}") # the pcrdigest needed by policy_sign tool is the hash of the concatenation of all PCRs involved in the policy. # we have only one PCR here