Skip to content

Declare the TPM's PCR banks in the event log - #28

Open
iwanicki92 wants to merge 5 commits into
TrenchBoot:skl-loader-amdsl-v11from
iwanicki92:up/tpm-banks
Open

iwanicki92 wants to merge 5 commits into
TrenchBoot:skl-loader-amdsl-v11from
iwanicki92:up/tpm-banks

Conversation

@iwanicki92

Copy link
Copy Markdown

With a TPM that has only SHA-256 allocated, or SHA-384 as well, every Linux Secure Launch fails. The SKL's event log declares SHA-1 and SHA-256 whatever banks the TPM has. Linux resets on a log whose banks differ from the TPM's, because its extend needs one digest per allocated bank, in the TPM's order.

The last commit asks the TPM for its PCR allocation with TPM2_GetCapability and declares those banks, in that order. Banks the SKL cannot hash get a placeholder digest (01 followed by zeros), as Linux and Xen log for such banks, and the SKL extends the same placeholder there for its own records. If the query fails, the log falls back to SHA-1 and SHA-256.

The commits before it are what the query needs:

  1. tis_recv() stored the last byte of every response in the first byte of the body. This commit is taken from patch 0012 in zarhus/meta-trenchboot#58 with its message reworded, because the bank query depends on it.
  2. tis_recv() waited one TIMEOUT_D (30 ms on TPM 2.0) for the response. In CI, a software TPM on a busy runner once answered the bank query later than that. The SKL then fell back to SHA-1 and SHA-256 on a TPM with only SHA-256, and the Linux launch failed. A stalled swtpm reproduces it. tis_recv() now polls for up to 2 s, as long as Linux allows for slow commands.
  3. convert_digest_list() counted an SM3-256 digest as 20 bytes, so such an extend was built short.
  4. The bank code grows the 64-bit DEBUG build until bootloader_data leaves GRUB less than the 4 KiB it needs for the SLRT, and GRUB then rejects the SKL. The linker script puts the small .bss before the page-aligned data, and an assertion fails the link when less than 4 KiB is left.

Known limits. None of them is a regression. Before this PR, the log always declared SHA-1 and SHA-256, and in each case below it is no worse than that:

  • On a CRB TPM the query always fails, because crb_recv() does not read the reply, so the log falls back to the old two banks.
  • The AMDSL build starts the log while the PSP keeps locality 2 locked, so the query fails there too, with the same fallback.
  • On a TPM with more than five allocated banks, unknown algorithms included, the query fails, with the same fallback.
  • A bank of an algorithm the SKL does not know is left out of the log, as every bank but SHA-1 and SHA-256 was before.
  • In a bank the SKL cannot hash, PCR 17 does not replay from the log, because SKINIT extends the real digest there. Before this PR, such a bank was not in the log at all. Linux replays only the records after its start marker, so it is not affected.

The first three commits apply unchanged to skl-loader-amdsl-noblob. The link.lds commit needs a rebase there, because that branch changes the linker script too.

Testing:

  • The CI matrix (gcc and clang-14, 32 and 64 bit, LTO on and off, the tests and scan-build) passes in an Ubuntu 22.04 container. make run-test-event_log runs the new hosted test for two, one and three banks and the fallback.
  • The AMDSL build, signed in an Ubuntu 20.04 container, passes and fails in the same configurations as skl-loader-amdsl-v11 without this PR.
  • In QEMU with drtm-tests, which emulates AMD SKINIT, every Linux and Xen launch boots with a TPM that has SHA-1 and SHA-256, only SHA-256, or SHA-1, SHA-256 and SHA-384. Their logs declare the TPM's banks and replay to PCR 18, and to PCR 17 once the SKINIT record carries the real SLB digest (see the last limit above). The Xen launches with only SHA-256 also need x86/tpm.c: skip TPM2 digests the event log does not list xen#35, which the test image had.

m-iwanicki and others added 5 commits September 16, 2026 17:22
tis_recv() reads all but the last byte of the response body into the
buffer and then reads the last byte into the first byte of that same
body. The final byte of every response is therefore whatever the buffer
held before, and the first byte is overwritten. Read the last byte into
its own place after the bytes already received.

Signed-off-by: iwanicki92 <iwanicki92@gmail.com>
tis_recv() waits TIMEOUT_D once after STS_GO, and gives up if no
response is there by then. TIMEOUT_D is 30 ms on TPM 2.0 and 750 ms on
TPM 1.2. It bounds a register access, not a command, and commands take
longer. Linux's TPM driver allows 750 ms for TPM2_GetCapability and
TPM2_PCR_Extend (TPM2_DURATION_MEDIUM), and 2 s for slower commands
such as TPM2_GetRandom (TPM2_DURATION_LONG).

Poll dataAvail every millisecond for up to 2 s instead, which covers
both, on both TPM families. This lengthens the TPM 1.2 wait from 750 ms
to 2 s. It prepares for a PCR bank query with TPM2_GetCapability, whose
response can take longer than 30 ms. The family argument is no longer
used and is marked unused, as in crb_recv().

Signed-off-by: iwanicki92 <iwanicki92@gmail.com>
convert_digest_list() steps over an SM3-256 digest by SM3256_SIZE, but
adds SHA1_SIZE to the size it returns. A TPM2_PCR_Extend with an SM3-256
digest is therefore built 12 bytes short, and the TPM fails it. Add
SM3256_SIZE instead.

Nothing extends an SM3-256 bank yet. The PCR bank support that follows
extends a placeholder digest into such a bank.

Signed-off-by: iwanicki92 <iwanicki92@gmail.com>
.bss starts on the first page boundary after .skinit_reserved. Its
page-aligned data comes first: the page tables, the event log and the
IOMMU device table, 40 KiB in the 64-bit build. The small .bss variables
follow, about 1.2 KiB, and then bootloader_data. GRUB puts a 4 KiB SLRT
at bootloader_data and refuses an SKL that leaves less room before the
64 KiB end.

In a 64-bit DEBUG build, .skinit_reserved ends 0x380 bytes below 0x4000.
Once it crosses 0x4000, .bss starts at 0x5000, and the small variables
push bootloader_data past 0xf000.

Link the small variables into a section of their own before .bss, in the
space that the page alignment of .bss leaves free. The code can then
grow until it and the small variables reach 0x5000, about 2.8 KiB more.
The cost is room after bootloader_data. A 64-bit DEBUG build puts it at
0xf000, with exactly 4 KiB left instead of 6.8 KiB. An assertion fails
the link when less than 4 KiB is left.

clang with LTO puts each variable in a section named after it, such as
.bss.tis_buff. The page-aligned data moves to .bss..page_data, so the new
section can take every .bss.<name> section but that one.

The small .bss would share a LOAD segment with the code, and ld 2.39 and
later warn about the RWX segment, which --fatal-warnings turns into an
error. A maximum page size of 16 bytes puts them in separate segments.
skl.bin is a flat binary, so the segments do not change it.

Signed-off-by: iwanicki92 <iwanicki92@gmail.com>
The event log declares SHA-1 and SHA-256 whatever banks the TPM has.
Linux resets on a log whose banks differ from the TPM's banks, because
its extend needs one digest per allocated bank, in the TPM's order. So a
TPM with only SHA-256, or with SHA-384 as well, fails every launch.

Ask the TPM for its PCR allocation with TPM2_GetCapability, and declare
those banks in that order. Each record carries SHA-1 and SHA-256 where
the TPM has them. Other banks get a placeholder digest (01 followed by
zeros), as Linux and Xen log for such banks. For the records the SKL
measures itself, it extends the same placeholder into those banks, so
those records replay. The SKINIT record does not replay there, because
SKINIT extends PCR 17 with the real digest in every bank.

If the query fails, or finds no bank of an algorithm the SKL knows, the
log declares SHA-1 and SHA-256, the banks most discrete TPMs have
allocated by default. DEBUG builds print the query's return code. The
query fails in these cases:

- on a CRB TPM, because crb_recv() does not read the reply
- in the AMDSL build, which starts the log while the PSP keeps
  locality 2 locked
- on a TPM with more than five banks

A bank of an algorithm the SKL does not know is left out of the log.

test-event_log.c checks the header and records for two, one and three
banks and the fallback. Run it with "make run-test-event_log". It stubs
the GetCapability query, so it does not cover the reply parser.

Signed-off-by: iwanicki92 <iwanicki92@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants