Overview

In a TPM 2.0 environment, each PCR index exists in multiple parallel PCR banks, one per hash algorithm:

Bank Algorithm Digest Size Note
0 SHA1 20 legacy compatibility
1 SHA256 32 modern default
2 SHA384 48 high assurance
3 SHA512 64 extended security

A single PCR index “N” therefore looks conceptually like PCR[N].<alg>:

PCR[N].sha1
PCR[N].sha256
PCR[N].sha384
PCR[N].sha512

Each bank is extended independently.

This dual-axis model is exactly where configuration mistakes occur.

This post is going to analyze why the misconfiguration happens by using SWTPM as an example and introduce my solution in the upstream.

Where the Misconfiguration Comes From

Different components determine which hash algorithm(s) to use by their own way.

Component Determines by Determines when Default
TF-A MBOOT_EL_HASH_ALG compile-time option Typically SHA256 only
U-Boot CONFIG_SHAxxx compile-time kconfig Can enable multiple banks.
SWTPM --pcr-banks runtime configuration All banks enabled by default without arguments.

So, see an example of real-world situation:

  • TF-A: Logs only SHA256 digests in Event Log by MBOOT_EL_HASH_ALG=sha256

  • U-Boot: Select SHA1 and SHA256 via kconfig

  • SWTPM: Enable SHA1 and SHA256 by --pcr-banks=sha1,sha256

This mismatch is subtle and easy to overlook.

Why This Causes a Problem

The Event Log initialized with only one digest per event (measurement of EL3 firmware / OP-TEE parts) - the digest TF-A generated (SHA256 in our case).

...
- EventNum: 2
  PCRIndex: 0
  EventType: EV_POST_CODE
  DigestCount: 1
  Digests:
  - AlgorithmId: sha256
    Digest: "e4f7a3566cf36c9ed38823f407a279b7f9d1e034fa672232f449914d036fefa4"
  EventSize: 14
  Event: |-
    SECURE_RT_EL3
- EventNum: 3
  PCRIndex: 0
  EventType: EV_POST_CODE
  DigestCount: 1
  Digests:
  - AlgorithmId: sha256
    Digest: "61addc1b8436aaeb58fa75d3bf8ae09ad6b91c305c8416cce40f6061ddbda2cd"
  EventSize: 20
  Event: |-
    SECURE_RT_EL1_OPTEE
- EventNum: 4
  PCRIndex: 0
  EventType: EV_POST_CODE
  DigestCount: 1
  Digests:
  - AlgorithmId: sha256
    Digest: "864086492c2f52c81a8dfb6036db1950834b87b93c0c3849f1aca492de265638"
  EventSize: 27
  Event: |-
    SECURE_RT_EL1_OPTEE_EXTRA1
- EventNum: 5
  PCRIndex: 0
  EventType: EV_POST_CODE
  DigestCount: 1
  Digests:
  - AlgorithmId: sha256
    Digest: "538fb3ce3e70482202d98eb9602e9cb762278ab0aeece651d88894ab5faf094f"
  EventSize: 6
  Event: |-
    BL_33
...

But later U-Boot will measure other components (e.g., kernel, EFIvar, EFIBoot) with both SHA1 and SHA256.

...
- EventNum: 8
  PCRIndex: 7
  EventType: EV_EFI_VARIABLE_DRIVER_CONFIG
  DigestCount: 2
  Digests:
  - AlgorithmId: sha1
    Digest: "85d64b3c1d4eb49f591158589311f426ab186e83"
  - AlgorithmId: sha256
    Digest: "b70b7c1b92209af66d79d12dec1f14f4b8c71c0c69be22a1a04f5c5804e26ec3"
  EventSize: 51
  Event:
    VariableName: 8be4df61-93ca-11d2-aa0d-00e098032b8c
    UnicodeNameLength: 9
    VariableDataLength: 1
    UnicodeName: AuditMode
    VariableData: "00"
- EventNum: 9
  PCRIndex: 4
  EventType: EV_EFI_BOOT_SERVICES_APPLICATION
  DigestCount: 2
  Digests:
  - AlgorithmId: sha1
    Digest: "3d7770341cef967f51006670d70b6b0262dec248"
  - AlgorithmId: sha256
    Digest: "eec05efb0afb7a32969295e1388119055423cd8bc6ace145f554340556634bfb"
  EventSize: 130
  Event:
    ImageLocationInMemory: 0x13d549000
    ImageLengthInMemory: 856064
    ImageLinkTimeAddress: 0x0
    LengthOfDevicePath: 98
    DevicePath: '04012a0001000000000800000000000000001000000000001056aebd31334d4e9466acb5caf0b4a60202040434004500460049005c00640065006200690061006e005c007300680069006d0061006100360034002e0065006600690000007fff0400'
...

Further U-Boot extends both SHA1 / SHA256 PCR banks:

PCR[N].sha1 <- extend(digest_sha1)
PCR[N].sha256 <- extend(digest_sha256)

Obviously, early-stage measurements done by TF-A lack SHA1 records. As a result, those measurements are never extended to the corresponding PCR SHA1 bank (PCR[0].sha1 in our example)

So, when using tpm2_eventlog_validate tool to replay and verify the Event Log (Replay(event_log, PCR_index, hash_bank)):

Replay Operation Expected PCR value Verification Result PCR Index Range
Replay(event_log, N, SHA256) PCR[N].sha256 ✅ matches N = [0..14]
Replay(event_log, 0, SHA1) PCR[0].sha1 ❌ mismatch  
Replay(event_log, M, SHA1) PCR[M].sha1 ✅ matches M = [1..14]

It prompts errors like:

WARN: Event #2 (PCR[0]) missing digest for SHA1 bank
ERROR: Replay of SHA1 bank for PCR[0] failed: expected 000000… but actual f3ab4f…

For an attestation tool, inconsistent PCR values across banks means:

Measured Boot becomes untrustworthy.

And depending on configuration, this leads to different breakages.

Observed Consequences

PCR bank misconfigurations cause impacts not limited to:

Layer Symptom
tpm2_pcrread, /sys/class/tpm/.../pcrs PCR banks disagree / unexpected values appear
tpm2_eventlog replay “PCR mismatch” errors
SystemReady-ES / BBSR certification Fails on firmware integrity verification
Remote attestation services (Keylime / Verifier / Intel Trust Authority) Node is rejected as “Integrity compromised”
IMA appraisal with policy binding to PCR 0/4/7 Filesystem validation fails, causing boot failure

So even though “everything boots fine”, the platform becomes unattestable, which defeats the point of Measured Boot.

Root Cause Summary and How to Fix It

A measured boot system is only valid if all firmware stages and the TPM device agree on the active PCR bank set.

The solution to fix it is to develop a logic to:

  • detect the hash algorithms from among the current Event Log, SWTPM active PCR banks and the ones supported by firmware in run-time;

  • if any misconfigurations exist, determine the new PCR banks configuration we need (a subset of PCR banks which all components can agree on);

  • send PCR Allocate command followed by a TPM Shutdown command to reconfigure the new PCR active banks and reset the system;

  • in the next power cycle, the system will run with the new PCR active banks without any algorithm mismatches.

Patches Links

I’ve developed two U-Boot patch series to address this issue by two steps.

The first series is to report potential PCR banks misconfiguration and exit tpm_tcg2 with errors logged.

Tpm exit with error when algorithm dismatches [v2]

It includes refactoring to simplify the logics in tpm and tpm_tcg2 and logic to report errors when:

  • an Event Log is handed over from the previous boot stage but TPM device was configured with an algorithm that does not exist in the Event Log;

  • TPM device was configured with an algorithm that is not supported by U-Boot;

  • failures observed when parsing the Event Log.

The second series is to implement PCR Allocate command to handle the PCR misconfiguration among TPM device, Event Log from previous boot stage and what U-Boot supports.

Reconfigure TPM when active hash algorithms dismatch [v3]

It re-configures TPM device if any active PCR banks are not supported by U-Boot, or does not exist in the Event Log passed in.

  • Determine the new PCR banks configuration (a subset of PCR banks which all components can agree on).
  • A PCR Allocate command will be sent with the determined PCR banks configurations, followed by a TPM shutdown command and a hardware reset to activate those new configurations.
  • If any of the algorithms from the Event Log is not supported by U-Boot, or TPM device does not support all U-Boot algorithms, exit with error.
  • This feature can be enabled / disable via kconfig TPM_PCR_ALLOCATE.

Test Instructions

Follow below instructions to test all patches on QEMU with SWTPM on a Debian distro image (in qcow2 format, and the kernel should be built with CONFIG_TCG_TIS to support TPM driver).

Get the makefile for building from my Github project: https://github.com/raymo200915/firmware_handoff_build_with_measured_boot

$ git clone git@github.com:raymo200915/firmware_handoff_build_with_measured_boot.git

Rename your distro image file to “debian.qcow2” and place it into the project’s root directory.

Follow README (use case B or C) for build and run instructions.

When launch U-Boot, below logs prompt:

TCG_EfiSpecIDEvent:
PCRIndex : 0
EventType : 3
Digest : 00
: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
: 00 00 00
EventSize : 33
Signature : Spec ID Event03
PlatformClass : 0
SpecVersion : 2.0.2
UintnSize : 1
NumberOfAlgorithms : 1
DigestSizes :
#0 AlgorithmId : SHA256
DigestSize : 32
VendorInfoSize : 0
PCR_Event2:
PCRIndex : 0
EventType : 3
Digests Count : 1
#0 AlgorithmId : SHA256
Digest : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
       : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
EventSize : 17
Signature : StartupLocality
StartupLocality : 0
PCR_Event2:
PCRIndex : 0
EventType : 1
Digests Count : 1
#0 AlgorithmId : SHA256
Digest : de 4f d1 f3 0c a9 78 47 3f 90 7b d0 a9 da d6 31
       : f0 62 e8 23 22 7d 82 3e 5f df 2f 6b 6d e5 90 69
EventSize : 14
Event : SECURE_RT_EL3
PCR_Event2:
PCRIndex : 0
EventType : 1
Digests Count : 1
#0 AlgorithmId : SHA256
Digest : 8c f0 5c 18 da 06 32 ba 88 6c 98 32 c6 e4 75 5a
       : ad 16 42 58 e0 8a 3b 94 c0 50 31 af 2a 34 38 a4
EventSize : 6
Event : BL_33

This is the initial Event Log generated by TF-A. Debian image should be loaded automatically by Boot Manager, if not, set the BootOrder in U-Boot console, and run Boot Manager manually:

=>eficonfig

Description: debian
File: virtio 0:1/EFI\debian\shimaa64.efi
Initrd File:
Fdt File:
Optional Data:
Save
Quit

=>booteif bootmgr

When SWTPM is configured with more than SHA256, for example, by export PCR_BANKS=sha1,sha256,sha384,sha512.

Below logs indicate PCR Allocate, TPM Shutdown and system reset happened and SWTPM is reconfigured:

TPM active algorithm(s) not exist in eventlog  
sha1  
algo_mask: algo_mask  
set bank[0] sha1 off  
set bank[1] sha256 on  
set bank[2] sha384 off  
set bank[3] sha512 off  
PCR allocate done, shutdown TPM and reboot  
resetting ...

After launching kernel, use tpm2_eventlog to explore the Event Log:

sudo apt install tpm2-tools
sudo tpm2_eventlog /sys/kernel/security/tpm0/binary_bios_measurements

Debug Tips

If tpm2_eventlog prompts errors, first check if the Event Log passed from U-Boot correctly. Log should prompt with dmesg:

[ 0.000000] efi: EFI v2.100 by Das U-Boot

[ 0.000000] efi: TPMFinalLog=0x13d622040 RTPROP=0x13d620040 SMBIOS
3.0=0x13e696000 MOKvar=0x13d606000 TPMEventLog=0x13d5f1040
RNG=0x13d5f0040 MEMRESERVE=0x13d5ef040

If not, search for the TPM related log to make sure TPM is working:

dmesg | grep -i tpm

Below log exists if the kernel does not enable CONFIG_TCG_TIS:

[ 0.000000] efi: TPMFinalLog=0x13d622040 RTPROP=0x13d620040 SMBIOS
3.0=0x13e696000 MOKvar=0x13d606000 TPMEventLog=0x13d5f1040
RNG=0x13d5f0040 MEMRESERVE=0x13d5ef040

[ 4.901756] ima: No TPM chip found, activating TPM-bypass!

Double check the kconfig to make sure CONFIG_TCG_TIS is selected:

zgrep CONFIG_TCG_TIS /proc/config.gz

Or:

grep CONFIG_TCG_TIS /boot/config-$(uname -r)

If CONFIG_TCG_TIS is disabled, you have to rebuild and install the kernel via below steps:

  • Get the kernel version:

    uname -r
    
  • Get the kernel source code by the version.

  • Enable CONFIG_TCG_TIS via menuconfig:

    cp /boot/config-$(uname -r) .config
    make menuconfig
    -> Device Drivers
      -> Character devices
        -> TPM Hardware Support (TCG_TPM [=y])
          -> TPM Interface Specification 1.2 Interface / TPM 2.0 FIFO Interface
    
  • Rebuild and install:

    make clean
    make -j$(nproc)
    sudo make modules_install
    sudo make install