From f127b896f8cae38d3d6c3b1eef2226eac425bc88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Iwanicki?= Date: Thu, 19 Feb 2026 12:26:19 +0100 Subject: [PATCH 01/14] head.S: move skl_info and bootloader_data fields back MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes issue: ``` loader/slaunch/skl.c:117:slaunch: Possible SKL module measures bootloader data: 16424 (measured prefix) > 170 (data offset) error: SKL module isn't correct. ``` Signed-off-by: Michał Iwanicki --- head.S | 4 ++-- sanity_check.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/head.S b/head.S index 70d6e60..93a25c7 100644 --- a/head.S +++ b/head.S @@ -48,8 +48,6 @@ GLOBAL(sl_header) .long 0xffffffff /* Reserved */ .long _end_of_signed + 0x640 /* Total Length including signature and pubkey raw data */ .word soc_flag /* Offset to SOCFLAG Structure, see below */ - .word skl_info /* Offset to SKL info with UUID and version */ - .word bootloader_data /* Offset to SLRT filled by the bootloader */ soc_flag: /* @@ -66,6 +64,8 @@ soc_flag: .long 0x00a10f00 .long 0x00a80f00 .long 0x00aa0f00 + .word skl_info /* Offset to SKL info with UUID and version */ + .word bootloader_data /* Offset to SLRT filled by the bootloader */ ENDDATA(sl_header) .text diff --git a/sanity_check.sh b/sanity_check.sh index 2697005..f993417 100755 --- a/sanity_check.sh +++ b/sanity_check.sh @@ -1,7 +1,7 @@ #!/bin/bash . util.sh -SKL_INFO=`hexdump "$SLB_FILE" -s30 -n2 -e '/2 "%u"'` +SKL_INFO=`hexdump "$SLB_FILE" -s66 -n2 -e '/2 "%u"'` if ! od --format=x8 --skip-bytes=$SKL_INFO --read-bytes=16 $SLB_FILE | grep -q "e91192048e26f178 02ccc4765bc82a83"; then echo "ERROR: SKL UUID missing or misplaced in $SLB_FILE" >&2 From 6a85b3421eff23146314c8b435ceedaf344a62ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Iwanicki?= Date: Fri, 20 Feb 2026 10:36:31 +0100 Subject: [PATCH 02/14] psp.c: Set MSI PRO B850 and MZ33-AR1 PSP version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MSI PRO B850 (1022:15C7) - change from PSP_NONE to PSP_V3 MZ33-AR1 (1022:156E) - add device and set to ASP (and expand psp_version_t enum) Signed-off-by: Michał Iwanicki --- psp.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/psp.c b/psp.c index 8cefa9e..a395e9d 100644 --- a/psp.c +++ b/psp.c @@ -54,7 +54,8 @@ typedef enum { PSP_NONE = 0, PSP_V1, PSP_V2, - PSP_V3 + PSP_V3, + ASP, } psp_version_t; struct pci_psp_device { @@ -71,7 +72,8 @@ const struct pci_psp_device psp_devs_list[] = { {0x1022, 0x15DF, PSP_V3}, {0x1022, 0x1649, PSP_V2}, {0x1022, 0x14CA, PSP_V3}, - {0x1022, 0x15C7, PSP_NONE} + {0x1022, 0x15C7, PSP_V3}, + {0x1022, 0x156E, ASP}, }; static bool init_drtm_interface(u64 base_addr, psp_version_t psp_version); @@ -226,6 +228,7 @@ static bool init_drtm_interface(u64 base_addr, psp_version_t psp_version) switch(psp_version) { case PSP_V3: case PSP_V2: + case ASP: g_psp_drtm.c2pmsg_72 = (volatile u32 *)(u32)(base + 0x10a20); g_psp_drtm.c2pmsg_93 = (volatile u32 *)(u32)(base + 0x10a74); g_psp_drtm.c2pmsg_94 = (volatile u32 *)(u32)(base + 0x10a78); From b65da4822aea8498b8bff04fe96d23cef566018b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Iwanicki?= Date: Tue, 15 Sep 2026 09:31:23 +0200 Subject: [PATCH 03/14] pci.c: use type 1 accesses when MMIO config space lies above 4G MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pci_init() kept only the low 32 bits of MSR C001_0058. On a platform that places the MMIO configuration space above 4 GiB the enable bit is set and bits 31:20 are zero, so SKL selected the MMIO accessor with a base of 0 and every configuration read returned low physical memory. On a Gigabyte MZ33-AR1 (EPYC 9015, firmware R19_F13) the MSR reads 0x00003FFB00000021, which puts the configuration space at 0x3FFB00000000. discover_psp() enumerated device IDs that match no hardware and get_psp_bar_addr() returned 0, so the PSP was never found. GRUB, which uses 0xCF8/0xCFC on the same platform, finds the PSP and its BAR. Read the whole 64-bit MSR value, derive the region size from BusRange, and use the MMIO accessor only when the region lies below 4 GiB. That is the range SKL can address: the 32-bit build runs without paging and the 64-bit build identity-maps the first 4 GiB. Otherwise use type 1 accesses, and print a note so the chosen path shows up in the log. Fixes: https://github.com/TrenchBoot/trenchboot-issues/issues/81 Signed-off-by: Michał Iwanicki --- pci.c | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/pci.c b/pci.c index 0b33bf2..4d33e79 100644 --- a/pci.c +++ b/pci.c @@ -23,6 +23,7 @@ #include #include #include +#include int (*pci_read)(unsigned int seg, unsigned int bus, unsigned int devfn, int reg, int len, u32 *value); @@ -193,20 +194,41 @@ u32 pci_locate(unsigned int bus, unsigned int devfn) return pci_cap_ptr; } +/* MSR C001_0058, MMIO configuration space base address (AMD64 APM vol. 2). */ +#define MSR_MMIO_CFG_BASE_ADDR 0xc0010058 +#define MMIO_CFG_BASE_ENABLE (1ULL << 0) +#define MMIO_CFG_BASE_BUS_RANGE(v) (((v) >> 2) & 0xf) +#define MMIO_CFG_BASE_ADDR_MASK 0x000ffffffff00000ULL /* bits 51:20 */ + void pci_init(void) { u32 eax, edx; - - asm volatile("rdmsr" : "=a"(eax), "=d"(edx) : "c"(0xc0010058)); - - if ( eax & 1 ) /* MMIO configuration space is enabled */ + u64 msr, base, size; + + asm volatile("rdmsr" : "=a"(eax), "=d"(edx) : "c"(MSR_MMIO_CFG_BASE_ADDR)); + msr = ((u64)edx << 32) | eax; + + base = msr & MMIO_CFG_BASE_ADDR_MASK; + /* One 1 MiB segment per bus, 2^BusRange buses. */ + size = 1ULL << (20 + MMIO_CFG_BASE_BUS_RANGE(msr)); + + /* + * SKL can address only the first 4 GiB of physical memory: the 32-bit + * build runs without paging and the 64-bit build identity-maps 4 GiB. + * Use type 1 accesses when the MMIO configuration space lies outside + * that range. + */ + if ( (msr & MMIO_CFG_BASE_ENABLE) && base + size <= 0x100000000ULL ) { - mmio_base_addr = (eax & 0xfff00000); + mmio_base_addr = (u32)base; pci_read = &pci_mmio_read; pci_write = &pci_mmio_write; } else { + if ( msr & MMIO_CFG_BASE_ENABLE ) + print("PCI: MMIO configuration space above 4G, using type 1 accesses\n"); + pci_read = &pci_conf1_read; pci_write = &pci_conf1_write; } From 889275a97182bb32f4e828f8db118145f8fffca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Iwanicki?= Date: Tue, 15 Sep 2026 10:00:00 +0200 Subject: [PATCH 04/14] link.lds: align the measured length to 16 bytes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AMD's SKL signing flow rounds the measured length in the SL header up to a multiple of 16 bytes and places the "$AS1" signature header at that offset. header_tool.py does the rounding in Python, and AmdSlSigningTool repeats it in place before hashing (SignPSS rounds the byte at offset 2 up to the next 0x10 and carries into offset 3). The openssl-signed image on this branch aligns _end_of_measured to 4 bytes only. The length field then ends 8 bytes past a 16-byte boundary (0x4028 in the image tested on the Gigabyte MZ33-AR1, 0x40c8 with the toolchain here) and the signature header sits at an offset a tool-signed image never has. That test ended in DRTM_LAUNCH_ERROR with PCRs 18 to 20 capped, which is what the PSP does when SKL fails authentication. Align .rodata and _end_of_measured to 16 bytes so the signature header follows the measured region at a multiple of 16, as in the tool output. Discard toolchain .note sections at link time. ld places orphan notes right after .rodata, which puts them between the measured region and the signature header. The Makefile already strips them from skl.bin, so the flat binary keeps the same content and loses the gap. Assert both properties so a layout drift fails the build. The other fields the signing tool writes match the header emitted by head.S: "$AS1", signed size equal to length + 0x100, SigOption 1, the key ID, the size with signature at 0x6c, and the total length at offset 0x18 of the SL header. The public key blob matches testPublicKey.bin, and the signature over length + 0x100 bytes verifies with a digest-length PSS salt, which is what the tool uses. Related: https://github.com/TrenchBoot/trenchboot-issues/issues/79 Signed-off-by: Michał Iwanicki --- link.lds | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/link.lds b/link.lds index af49c46..26a6fad 100644 --- a/link.lds +++ b/link.lds @@ -29,21 +29,23 @@ SECTIONS *(.headers) *(.text*) } - .rodata : { + .rodata : ALIGN(16) { *(SORT_BY_ALIGNMENT(.rodata*)) /* * This is unwritten (as of AMD APM revision 3.42) behavior of SKINIT. * PCR 17 is extended with SKL padded with zeros up to a size that is - * aligned up to the multiply of 4B. To keep functions in util.sh - * simple, _end_of_measured is explicitly aligned. This may increase - * skl.bin size by up to 3 bytes, but it doesn't increase the real size - * of data sent to TPM to be measured. + * aligned up to the multiply of 4B. AMD's SKL signing tools round the + * measured length up to a multiple of 16B and place the signature + * header at that offset, so _end_of_measured is explicitly aligned to + * 16B. This keeps functions in util.sh simple and gives the PSP the + * layout it expects, at the cost of up to 15 bytes of zero padding + * that SKINIT measures along with the code. * * Note that this padding must be part of .rodata to be included in * skl.bin, both for 'dd' used in util.sh and for checks performed by * GRUB. */ - . = ALIGN(4); + . = ALIGN(16); } _end_of_measured = .; @@ -111,8 +113,14 @@ SECTIONS *(.data*) } + /* + * Toolchain notes (e.g. .note.gnu.property) are orphan sections that ld + * would otherwise place right after .rodata, between the measured region + * and the signature header. + */ /DISCARD/ : { *(.eh_frame*) + *(.note.*) } } @@ -120,8 +128,11 @@ ASSERT(_end <= 0x10000, "Landing Zone exceeds 64k"); ASSERT(SIZEOF(.got) == 0, ".got section not empty - non-hidden symbols used?"); ASSERT(SIZEOF(.data) == 0, ".data section not empty - initialized data used?"); +ASSERT((_end_of_measured & 0xf) == 0, "_end_of_measured not aligned to 16 bytes"); ASSERT(!SIZEOF(.skl_sig_hdr) || SIZEOF(.skl_sig_hdr) == 0x100, ".skl_sig_hdr size not 256 bytes"); +ASSERT(!SIZEOF(.skl_sig_hdr) || ADDR(.skl_sig_hdr) == _end_of_measured, + ".skl_sig_hdr does not directly follow the measured region"); ASSERT(!SIZEOF(.skl_pubkey_hdr) || SIZEOF(.skl_pubkey_hdr) == 0x240, ".skl_pubkey_hdr size not 0x240 bytes"); From 6970825a874664a731f07de4e716b1614f6265ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Iwanicki?= Date: Tue, 15 Sep 2026 10:25:41 +0200 Subject: [PATCH 05/14] Makefile: sign with a PSS salt of the digest length MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PSP verifies the SKL signature with RSA-PSS, SHA-384 and a salt of 48 bytes, the hash length (AMD publication 58453, DRTM Service Integration Guide, appendix G.1). AmdSlSigningTool signs the same way. openssl dgst picks the salt length from its own default when none is given. OpenSSL 3.1 and newer use the digest length, older releases use the maximum salt the modulus allows, 462 bytes here. An image signed on such a host carries a signature the PSP will not accept. Pass rsa_pss_saltlen:digest so the signature matches the PSP on every OpenSSL version. Signed-off-by: Michał Iwanicki --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index d6c71c9..8bebbc5 100644 --- a/Makefile +++ b/Makefile @@ -65,8 +65,8 @@ skl.bin: skl Makefile objcopy -O binary -S -R '.note.*' -R '.got.plt' -R '.skl_sig' -R '.skl_pubkey_hdr' -R '.skl_pubkey_modulus' $< $@ @./sanity_check.sh ifeq ($(AMDSL), y) - # create signature - openssl dgst -sha384 -sign testPrivateKey.pem -out signature_rev.bin -binary -sigopt rsa_padding_mode:pss $@ + # create signature, RSA-PSS with a salt of the digest length as the PSP expects + openssl dgst -sha384 -sign testPrivateKey.pem -out signature_rev.bin -binary -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:digest $@ # reverse byte order xxd -c1 -p signature_rev.bin | tac | xxd -r -p > signature.bin # get the modulus for pubkey header, reverse byte order From f4c7714c6c57ccfc5015f8d1039a9648a9835978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Iwanicki?= Date: Wed, 16 Sep 2026 16:30:33 +0200 Subject: [PATCH 06/14] head.S: list family 1Ah in the SOC flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SOC flag structure names the CPU families and models the image is built for, as CPUID leaf 1 EAX with the low byte cleared. The list ends at family 19h. The Gigabyte MZ33-AR1 carries an EPYC 9015, family 1Ah model 02h, CPUID 0x00B00F21, and its ASP answers DRTM_CMD_LAUNCH with DRTM_LAUNCH_ERROR for an image whose header, signature and key token otherwise match AMD's signing tool output. The list is the one field left that does not describe that CPU. The structure has to keep eight entries. GRUB reads skl_info_offset and bootloader_data_offset from fixed offsets 0x42 and 0x44, and boot.h declares the same layout, so a ninth entry would move both fields. Family 1Ah model 00h replaces family 17h model 60h, the oldest entry. Signed-off-by: Michał Iwanicki --- head.S | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/head.S b/head.S index 93a25c7..180e55e 100644 --- a/head.S +++ b/head.S @@ -54,9 +54,14 @@ soc_flag: * This structure isn't described in the spec, but it is assumed to be a * number of entries, followed by family and model of supported CPUs, in * form of EAX of CPUID leaf 1 with stepping set to 0. + * + * The structure has a fixed size of eight entries: GRUB and boot.h both + * locate skl_info and bootloader_data at offsets 0x42 and 0x44, right + * after it. Family 1Ah model 00h (EPYC 9005) takes the slot of family + * 17h model 60h. */ .long 0x00000008 - .long 0x00860f00 + .long 0x00b00f00 .long 0x00a50f00 .long 0x00a40f00 .long 0x00a70f00 From fdcd3c190a46dc01c8a2ce392065e84fbd88e07b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Iwanicki?= Date: Wed, 16 Sep 2026 20:29:18 +0200 Subject: [PATCH 07/14] amdsl: hold no TPM locality during DRTM_CMD_LAUNCH MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The DRTM service opens locality 3 to read and extend PCR 17 during the launch command. TIS grants a higher locality only while no lower one is active, so the SKL relinquishes its locality before issuing the command and requests locality 2 again afterwards. The service keeps localities 1 and 2 locked until the launch succeeds, which is why PCR reads at locality 2 returned -EAGAIN both before and after the command. tis_request_locality() treats an all-ones TPM_ACCESS read as not granted. Reserved bit 6 is set in that value, and it is what a locality the chipset does not decode for the host returns. Signed-off-by: Michał Iwanicki --- main.c | 10 ++++++++++ tpmlib/tis.c | 10 ++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/main.c b/main.c index 9425d7a..eb6a91b 100644 --- a/main.c +++ b/main.c @@ -293,12 +293,22 @@ static asm_return_t amdsl_launch() print("AMDSL: PSP NOT found\n"); } + /* + * The DRTM service opens locality 3 to read and extend PCR 17. TIS grants + * a higher locality only when no lower one is active, so the host holds + * no locality while the command runs. Locality 2 is locked by the DRTM + * service until the launch succeeds, so it is requested afterwards. + */ + tpm_relinquish_locality(tpm); + if (!drtm_launch()) { print("AMDSL: skl_linux: DRTM launch failed\n"); } else { print("AMDSL: skl_linux: DRTM launch successful\n"); } + tpm_request_locality(tpm, 2); + if (!drtm_extend_ossl_digest((u64)dl_info->dlme_base, dl_info->dlme_size)) { print("DRTM: skl_linux: failed to extend OSSL digest\n"); } diff --git a/tpmlib/tis.c b/tpmlib/tis.c index b405f88..a8e5b00 100644 --- a/tpmlib/tis.c +++ b/tpmlib/tis.c @@ -58,6 +58,8 @@ void tis_relinquish_locality(void) u8 tis_request_locality(u8 l) { + u8 access; + if (l > TPM_MAX_LOCALITY) return TPM_NO_LOCALITY; @@ -68,8 +70,12 @@ u8 tis_request_locality(u8 l) tpm_write8(ACCESS_REQUEST_USE, ACCESS(l)); - /* wait for locality to be granted */ - if (tpm_read8(ACCESS(l)) & ACCESS_ACTIVE_LOCALITY) + /* + * A locality the chipset does not decode for the host reads as all + * ones, which has the reserved bit 6 set. Treat that as not granted. + */ + access = tpm_read8(ACCESS(l)); + if (access != 0xff && (access & ACCESS_ACTIVE_LOCALITY)) locality = l; return locality; From efdb982f7565b2eef7ea8012a0868657cd875641 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Iwanicki?= Date: Wed, 16 Sep 2026 17:22:23 +0200 Subject: [PATCH 08/14] tpmlib: store the last byte of a TIS response at the end of the buffer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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, so the final byte of every response is whatever the buffer held before. A PCR read on the MZ33-AR1 returned digests whose last byte was zero for that reason. The last byte now lands after the bytes already received. Signed-off-by: Michał Iwanicki --- tpmlib/tis.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tpmlib/tis.c b/tpmlib/tis.c index a8e5b00..fb5bcf6 100644 --- a/tpmlib/tis.c +++ b/tpmlib/tis.c @@ -212,8 +212,8 @@ size_t tis_recv(enum tpm_family f, struct tpmbuff *buf) if (!tis_data_available(locality)) return 0; - /* read last byte */ - if (recv_data(buf_ptr, 1) != 1) + /* read last byte into its own slot after the bytes already received */ + if (recv_data(buf_ptr + expected - 1, 1) != 1) return 0; /* make sure we read everything */ From b4b5d06bb0be70a615be35695131be6313464c16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Iwanicki?= Date: Wed, 16 Sep 2026 18:09:13 +0200 Subject: [PATCH 09/14] psp: wait up to two minutes for DRTM_CMD_EXTEND_OSSL_DIGEST MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mailbox wait gives the PSP 50 retries of 100 ms for every command. Hashing the DLME is the one command whose work scales with the payload, and on the Gigabyte MZ33-AR1 a 7.6 MB Xen image ran past the five seconds after a successful launch, so the SKL reported "command failed to complete" and moved on. The extend now waits up to 1200 retries. The other commands keep the short wait. Signed-off-by: Michał Iwanicki --- psp.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/psp.c b/psp.c index a395e9d..d8eb7b6 100644 --- a/psp.c +++ b/psp.c @@ -246,9 +246,12 @@ static bool init_drtm_interface(u64 base_addr, psp_version_t psp_version) return true; } -static bool drtm_wait_for_psp_ready(u32 *status) +/* 100 ms per retry. Hashing a multi-megabyte DLME takes the PSP seconds. */ +#define DRTM_WAIT_RETRIES 50 +#define DRTM_EXTEND_WAIT_RETRIES 1200 + +static bool drtm_wait_for_psp_ready_retries(u32 *status, int retry) { - int retry = 50; u32 reg_val = 0; while (--retry) { @@ -273,6 +276,11 @@ static bool drtm_wait_for_psp_ready(u32 *status) return true; } +static bool drtm_wait_for_psp_ready(u32 *status) +{ + return drtm_wait_for_psp_ready_retries(status, DRTM_WAIT_RETRIES); +} + #ifndef UINT32_MAX #define UINT32_MAX 0xFFFFFFFF #endif @@ -313,7 +321,7 @@ bool drtm_extend_ossl_digest(u64 addr, u64 size) *g_psp_drtm.c2pmsg_95 = (u32)(reloc_addr >> 32); *g_psp_drtm.c2pmsg_72 = (DRTM_CMD_EXTEND_OSSL_DIGEST << DRTM_MBOX_CMD_SHIFT); - if (!drtm_wait_for_psp_ready(&status)) { + if (!drtm_wait_for_psp_ready_retries(&status, DRTM_EXTEND_WAIT_RETRIES)) { print("DRTM: drtm_extend_ossl_digest: command failed to complete\n"); return false; } From 21be8d1687041064f2303bc5f147f7f7ff4e50e7 Mon Sep 17 00:00:00 2001 From: iwanicki92 Date: Wed, 28 Oct 2026 19:29:22 +0100 Subject: [PATCH 10/14] tpmlib: poll for a TIS response for up to 2 s MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Signed-off-by: Michał Iwanicki --- tpmlib/tis.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/tpmlib/tis.c b/tpmlib/tis.c index fb5bcf6..63ca86c 100644 --- a/tpmlib/tis.c +++ b/tpmlib/tis.c @@ -163,25 +163,26 @@ static size_t recv_data(unsigned char *buf, size_t len) return size; } -size_t tis_recv(enum tpm_family f, struct tpmbuff *buf) +size_t tis_recv(__attribute__((unused)) enum tpm_family f, + struct tpmbuff *buf) { u32 expected; u8 *buf_ptr; struct tpm_header *hdr; + int i; if (locality > TPM_MAX_LOCALITY) return 0; - /* ensure that there is data available */ - if (!tis_data_available(locality)) { - if (f == TPM12) - tpm1_timeout_d(); - else - tpm2_timeout_d(); + /* + * Poll for the response for up to 2 s. TIMEOUT_D bounds a register + * access, and a command can take much longer to complete. + */ + for (i = 0; !tis_data_available(locality) && i < 2000; i++) + tpm_mdelay(1); - if (!tis_data_available(locality)) - return 0; - } + if (!tis_data_available(locality)) + return 0; /* read header */ hdr = (struct tpm_header *)buf->head; From ffb24dd48a84c0851ac0357ab1cb7e4157ce61c1 Mon Sep 17 00:00:00 2001 From: iwanicki92 Date: Thu, 29 Oct 2026 20:14:28 +0100 Subject: [PATCH 11/14] tpmlib: count an SM3-256 digest with its own size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Signed-off-by: Michał Iwanicki --- tpmlib/tpm2_cmds.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tpmlib/tpm2_cmds.c b/tpmlib/tpm2_cmds.c index 73355af..ca9e24e 100644 --- a/tpmlib/tpm2_cmds.c +++ b/tpmlib/tpm2_cmds.c @@ -82,7 +82,7 @@ static u16 convert_digest_list(struct tpml_digest_values *digests) case TPM_ALG_SM3_256: h->alg = cpu_to_be16(h->alg); h = (struct tpmt_ha *)((u8 *)h + SM3256_SIZE); - size += sizeof(u16) + SHA1_SIZE; + size += sizeof(u16) + SM3256_SIZE; break; default: return 0; From cb52b3cc99d5d517e5aa29ad23b1d714f0cbc1ed Mon Sep 17 00:00:00 2001 From: iwanicki92 Date: Thu, 29 Oct 2026 21:17:56 +0100 Subject: [PATCH 12/14] link.lds: put the small .bss before the page-aligned data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit .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. 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 Signed-off-by: Michał Iwanicki --- Makefile | 3 +++ head.S | 2 +- include/defs.h | 2 +- link.lds | 15 +++++++++++++-- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 8bebbc5..06ed91a 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,9 @@ endif CFLAGS += -Os -g -MMD -MP -mno-sse -mno-mmx -fpie -fomit-frame-pointer CFLAGS += -Iinclude -ffreestanding -fno-common -Wall -Werror LDFLAGS += -nostdlib -no-pie -Wl,--build-id=none,--fatal-warnings,--defsym=BITS=$(BITS) +# Keep the code and the small .bss of link.lds in separate LOAD segments, so +# ld 2.39 and later do not warn about an RWX segment. +LDFLAGS += -Wl,-z,max-page-size=0x10 CFLAGS_TPMLIB := -include boot.h -include errno-base.h -include byteswap.h -DEBADRQC=EINVAL diff --git a/head.S b/head.S index 180e55e..3a4847f 100644 --- a/head.S +++ b/head.S @@ -364,7 +364,7 @@ ENDDATA(gdt) #ifdef __x86_64__ /* 64bit Pagetables, identity map of the first 4G of RAM. */ - .section .bss.page_data, "aw", @nobits + .section .bss..page_data, "aw", @nobits .align PAGE_SIZE l1_identmap: /* 1x L1 page, mapping 2M of RAM. */ diff --git a/include/defs.h b/include/defs.h index 3a614d2..fd6d2fb 100644 --- a/include/defs.h +++ b/include/defs.h @@ -64,7 +64,7 @@ /* Due to the 64k total size limit, group all page aligned data together. */ #define __page_data \ - __attribute__ ((__section__(".bss.page_data"), __aligned__(PAGE_SIZE))) + __attribute__ ((__section__(".bss..page_data"), __aligned__(PAGE_SIZE))) #define unreachable() __builtin_unreachable() #define offsetof(a, b) __builtin_offsetof(a, b) diff --git a/link.lds b/link.lds index 26a6fad..4838f9d 100644 --- a/link.lds +++ b/link.lds @@ -68,9 +68,18 @@ SECTIONS *(.skl_pubkey_modulus) } - .bss : { - . = ALIGN(4096); + /* + * The small variables come first, in a section of their own, so they + * fill the space that the page alignment of .bss would otherwise waste. + * Page aligned data is in .bss..page_data, which the second pattern + * leaves out. + */ + .bss_small : { _bss = .; + *(SORT_BY_ALIGNMENT(.bss) SORT_BY_ALIGNMENT(.bss.[!.]*)) + } + + .bss : { /* * Due to the 64k total size constraint, we link all page size/aligned * data together in a single section, to avoid wasting space in the @@ -125,6 +134,8 @@ SECTIONS } ASSERT(_end <= 0x10000, "Landing Zone exceeds 64k"); +/* GRUB puts a 4 KiB SLRT at bootloader_data and refuses an SKL without room. */ +ASSERT(bootloader_data <= 0x10000 - 0x1000, "no room for a 4 KiB SLRT"); ASSERT(SIZEOF(.got) == 0, ".got section not empty - non-hidden symbols used?"); ASSERT(SIZEOF(.data) == 0, ".data section not empty - initialized data used?"); From 95fe314c8a1385db58b5849c0b48b8f7030f1a97 Mon Sep 17 00:00:00 2001 From: iwanicki92 Date: Thu, 29 Oct 2026 19:00:10 +0100 Subject: [PATCH 13/14] Declare the TPM's PCR banks in the event log MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Signed-off-by: Michał Iwanicki --- event_log.c | 265 +++++++++++++++++++++++++----------- include/event_log.h | 6 + main.c | 1 + test-event_log.c | 295 ++++++++++++++++++++++++++++++++++++++++ tpmlib/tpm.c | 11 ++ tpmlib/tpm.h | 1 + tpmlib/tpm2.h | 11 ++ tpmlib/tpm2_cmds.c | 110 +++++++++++++++ tpmlib/tpm2_constants.h | 4 + 9 files changed, 628 insertions(+), 76 deletions(-) create mode 100644 test-event_log.c diff --git a/event_log.c b/event_log.c index 483697c..c65d500 100644 --- a/event_log.c +++ b/event_log.c @@ -17,11 +17,13 @@ */ #include +#include #include #include #include #include #include "tpmlib/tpm.h" +#include "tpmlib/tpm_common.h" #include "tpmlib/tpm2_constants.h" static u8 *evtlog_base; @@ -43,7 +45,38 @@ static int log_write(const void *data, unsigned size) #define EV_NO_ACTION 0x3 #define EV_TYPE_SLAUNCH 0x502 -#define HASH_COUNT 2 +/* + * One bank per algorithm the SKL knows the digest size of: SHA-1, SHA-256, + * SHA-384, SHA-512 and SM3-256. + */ +#define MAX_BANKS 5 + +/* + * The TPM's allocated banks in its order, which the TPM 2.0 log declares + * and every record of it carries. Where the SKL cannot compute a digest, + * the record carries 01 followed by zeros, as Linux and Xen log for such + * a bank. The Intel TXT Software Development Guide calls it OneDigest. + */ +static struct __packed { + u16 alg; + u16 size; +} banks[MAX_BANKS]; +static u32 nbanks; + +static const u8 one_digest[SHA512_SIZE] = { 1 }; + +static u16 digest_size(u16 alg) +{ + switch ( alg ) + { + case TPM_ALG_SHA1: return SHA1_SIZE; + case TPM_ALG_SHA256: return SHA256_SIZE; + case TPM_ALG_SHA384: return SHA384_SIZE; + case TPM_ALG_SHA512: return SHA512_SIZE; + case TPM_ALG_SM3_256: return SM3256_SIZE; + default: return 0; + } +} /* For compatibility with TXT and easier operations */ @@ -69,6 +102,16 @@ typedef struct __packed { u32 next_record_offset; } txt_event_log_pointer2_1_element; +/* Event log entries */ + +typedef struct __packed { + u32 pcr; + u32 event_type; + u8 digest[SHA1_DIGEST_SIZE]; + u32 event_size; + /* u8 event[]; */ +} tpm12_event_t; + /* Event log headers */ typedef struct __packed { @@ -86,48 +129,38 @@ typedef struct __packed { tpm12_event_log_header hdr; /* AKA u8 vendor_info[]; */ } tpm12_spec_id_ev_t; -typedef struct __packed { - u32 number_of_algorithms; - /* Hardcode table size so we can use sizeof */ - struct { - u16 id; - u16 size; - } digest_sizes[HASH_COUNT]; -} tpm20_digest_sizes_t; - -typedef struct __packed { - common_spec_id_ev_t c; - tpm20_digest_sizes_t sizes; - u8 vendor_info_size; - txt_event_log_pointer2_1_element el; /* AKA u8 vendor_info[]; */ -} tpm20_spec_id_ev_t; +/* + * The TPM 2.0 header is common_spec_id_ev_t, the number of algorithms, + * an id and size pair per bank, and the vendor info with its size. Its + * size depends on the number of banks. + */ +static u32 tpm20_header_size(void) +{ + return sizeof(common_spec_id_ev_t) + sizeof(u32) + nbanks * sizeof(banks[0]) + + 1 + sizeof(txt_event_log_pointer2_1_element); +} -/* Event log entries */ +/* The vendor info of the TPM 2.0 header in the log */ +static txt_event_log_pointer2_1_element *tpm20_el(void) +{ + return (txt_event_log_pointer2_1_element *) + (evtlog_base + sizeof(tpm12_event_t) + tpm20_header_size() + - sizeof(txt_event_log_pointer2_1_element)); +} -typedef struct __packed { - u32 pcr; - u32 event_type; - u8 digest[SHA1_DIGEST_SIZE]; - u32 event_size; - /* u8 event[]; */ -} tpm12_event_t; +/* + * A TPM 2.0 record is the PCR, the type, the digest count, an id and + * digest per bank, the event size and the event: TCG_PCR_EVENT2. + */ +static u32 tpm20_record_size(u32 event_size) +{ + u32 i, size = 3 * sizeof(u32) + sizeof(u32) + event_size; -/* The same as TPML_DIGEST_VALUES but little endian, as event log expects it */ -typedef struct __packed { - u32 count; - u16 sha1_id; - u8 sha1_hash[SHA1_DIGEST_SIZE]; - u16 sha256_id; - u8 sha256_hash[SHA256_DIGEST_SIZE]; -} ev_log_hash_t; + for ( i = 0; i < nbanks; i++ ) + size += sizeof(u16) + banks[i].size; -typedef struct __packed { - u32 pcr; - u32 event_type; - ev_log_hash_t digests; - u32 event_size; - /* u8 event[]; */ -} tpm20_event_t; + return size; +} static const tpm12_spec_id_ev_t tpm12_id_struct = { .c.signature = "Spec ID Event00", @@ -150,20 +183,12 @@ static const tpm12_spec_id_ev_t tpm12_id_struct = { .hdr.next_event_offset = sizeof(tpm12_event_log_header) }; -static const tpm20_spec_id_ev_t tpm20_id_struct = { - .c.signature = "Spec ID Event03", - .c.spec_ver_minor = 0, - .c.spec_ver_major = 2, - .c.errata = 0, - .c.uintn_size = 2, - .sizes.number_of_algorithms = HASH_COUNT, - .sizes.digest_sizes[0].id = TPM_ALG_SHA1, - .sizes.digest_sizes[0].size = 20, - .sizes.digest_sizes[1].id = TPM_ALG_SHA256, - .sizes.digest_sizes[1].size = 32, - .vendor_info_size = sizeof(txt_event_log_pointer2_1_element), - .el.first_record_offset = 0, - .el.next_record_offset = sizeof(tpm20_spec_id_ev_t) + sizeof(tpm12_event_t) +static const common_spec_id_ev_t tpm20_id_common = { + .signature = "Spec ID Event03", + .spec_ver_minor = 0, + .spec_ver_major = 2, + .errata = 0, + .uintn_size = 2, }; int log_event_tpm12(u32 pcr, u8 sha1[SHA1_DIGEST_SIZE], char *event) @@ -187,30 +212,104 @@ int log_event_tpm12(u32 pcr, u8 sha1[SHA1_DIGEST_SIZE], char *event) return 1; } +/* + * Writes a TPM 2.0 record with digests[i] in bank i, or the placeholder + * where that is NULL. Returns non-zero when it does not fit. + */ +static int write_event_tpm20(u32 pcr, u32 type, const u8 *const digests[], + const void *event, u32 event_size) +{ + u32 i, head[3] = { pcr, type, nbanks }; + + if ( !HAS_ENOUGH_SPACE(tpm20_record_size(event_size)) ) + return 1; + + log_write(head, sizeof(head)); + for ( i = 0; i < nbanks; i++ ) + { + log_write(&banks[i].alg, sizeof(banks[i].alg)); + log_write(digests[i] ? digests[i] : one_digest, banks[i].size); + } + log_write(&event_size, sizeof(event_size)); + log_write(event, event_size); + tpm20_el()->next_record_offset += tpm20_record_size(event_size); + + return 0; +} + int log_event_tpm20(u32 pcr, u8 sha1[SHA1_DIGEST_SIZE], u8 sha256[SHA256_DIGEST_SIZE], char *event) { - tpm20_event_t ev; - tpm20_spec_id_ev_t *base = (tpm20_spec_id_ev_t *) - (evtlog_base + sizeof(tpm12_event_t)); + const u8 *digests[MAX_BANKS] = { NULL }; + u32 i; - ev.event_size = strlen(event); + for ( i = 0; i < nbanks; i++ ) + digests[i] = banks[i].alg == TPM_ALG_SHA1 ? sha1 + : banks[i].alg == TPM_ALG_SHA256 ? sha256 : NULL; - if ( HAS_ENOUGH_SPACE(sizeof(ev) + ev.event_size) ) + return write_event_tpm20(pcr, EV_TYPE_SLAUNCH, digests, event, + strlen(event)); +} + +void event_log_extend_placeholders(struct tpm *tpm, u32 pcr) +{ + u32 i; + + for ( i = 0; i < nbanks; i++ ) + if ( banks[i].alg != TPM_ALG_SHA1 && banks[i].alg != TPM_ALG_SHA256 ) + tpm_extend_pcr(tpm, pcr, banks[i].alg, (u8 *)one_digest); +} + +/* + * Asks the TPM 2.0 for its allocated banks, leaving out algorithms the SKL + * does not know. If the query fails, or finds no bank the SKL knows, the + * log declares SHA-1 and SHA-256, the banks most discrete TPMs have + * allocated by default. The query fails behind a locality the SKL does + * not hold, and on every CRB TPM, because crb_recv() does not read the + * reply. + */ +static void find_banks(struct tpm *tpm) +{ + u16 algs[MAX_BANKS]; + u32 n = MAX_BANKS, i; + int rc; + + nbanks = 0; + rc = tpm_get_pcr_banks(tpm, algs, &n); + if ( rc == 0 ) { - ev.pcr = pcr; - ev.event_type = EV_TYPE_SLAUNCH; - ev.digests.count = 2; - ev.digests.sha1_id = TPM_ALG_SHA1; - memcpy(ev.digests.sha1_hash, sha1, 20); - ev.digests.sha256_id = TPM_ALG_SHA256; - memcpy(ev.digests.sha256_hash, sha256, 32); - base->el.next_record_offset += sizeof(ev) + ev.event_size; - log_write(&ev, sizeof(ev)); - return log_write(event, ev.event_size); + for ( i = 0; i < n; i++ ) + { + if ( digest_size(algs[i]) == 0 ) + continue; + banks[nbanks].alg = algs[i]; + banks[nbanks].size = digest_size(algs[i]); + nbanks++; + } + } + else + { + print("Bank query failed: "); + print_u64((u32)rc); + print("\n"); } - return 1; + if ( nbanks == 0 ) + { + banks[0].alg = TPM_ALG_SHA1; + banks[0].size = SHA1_SIZE; + banks[1].alg = TPM_ALG_SHA256; + banks[1].size = SHA256_SIZE; + nbanks = 2; + } + + print("Event log banks:"); + for ( i = 0; i < nbanks; i++ ) + { + print(" "); + print_u64(banks[i].alg); + } + print("\n"); } int event_log_init(struct tpm *tpm) @@ -233,8 +332,9 @@ int event_log_init(struct tpm *tpm) } else if ( tpm->family == TPM20 ) { - min_size += sizeof(tpm20_id_struct); - min_size += 2 * sizeof(tpm20_event_t); /* SKL and kernel hashes */ + find_banks(tpm); + min_size += tpm20_header_size(); + min_size += 2 * tpm20_record_size(0); /* SKL and kernel hashes */ } else { @@ -277,7 +377,7 @@ int event_log_init(struct tpm *tpm) if ( tpm->family == TPM12 ) ev.event_size = sizeof(tpm12_id_struct); else - ev.event_size = sizeof(tpm20_id_struct); + ev.event_size = tpm20_header_size(); log_write(&ev, sizeof(ev)); } @@ -288,10 +388,19 @@ int event_log_init(struct tpm *tpm) log_write(&tpm12_id_struct, sizeof(tpm12_id_struct)); id->hdr.container_size = info->size; } else { - tpm20_spec_id_ev_t *id = (tpm20_spec_id_ev_t *)ptr_current; - log_write(&tpm20_id_struct, sizeof(tpm20_id_struct)); - id->el.allocated_event_container_size = info->size; - id->el.phys_addr = _u(evtlog_base); + u8 vendor_info_size = sizeof(txt_event_log_pointer2_1_element); + txt_event_log_pointer2_1_element el = { + .phys_addr = _u(evtlog_base), + .allocated_event_container_size = info->size, + .first_record_offset = 0, + .next_record_offset = sizeof(tpm12_event_t) + tpm20_header_size(), + }; + + log_write(&tpm20_id_common, sizeof(tpm20_id_common)); + log_write(&nbanks, sizeof(nbanks)); + log_write(banks, nbanks * sizeof(banks[0])); + log_write(&vendor_info_size, sizeof(vendor_info_size)); + log_write(&el, sizeof(el)); } /* Log what was done by SKINIT */ @@ -305,6 +414,10 @@ int event_log_init(struct tpm *tpm) u8 sha256_hash[SHA256_DIGEST_SIZE]; sha256sum(sha256_hash, _start, _end_of_measured - _start); + /* + * SKINIT extended PCR 17 with the real digest in every bank, so + * this record does not replay in a bank that gets the placeholder. + */ return log_event_tpm20(17, hash, sha256_hash, "SKINIT"); } diff --git a/include/event_log.h b/include/event_log.h index 2a75f9a..141433d 100644 --- a/include/event_log.h +++ b/include/event_log.h @@ -22,4 +22,10 @@ int event_log_init(struct tpm *tpm); int log_event_tpm12(u32 pcr, u8 sha1[20], char *event); int log_event_tpm20(u32 pcr, u8 sha1[20], u8 sha256[32], char *event); +/* + * Extends the placeholder digest into the banks of the PCR that the SKL + * cannot hash, so that those banks replay. + */ +void event_log_extend_placeholders(struct tpm *tpm, u32 pcr); + #endif /* __EVENT_LOG_H__ */ diff --git a/main.c b/main.c index eb6a91b..c1f5d6e 100644 --- a/main.c +++ b/main.c @@ -61,6 +61,7 @@ static void extend_pcr(struct tpm *tpm, void *data, u32 size, u32 pcr, char *ev) print("shasum calculated:\n"); hexdump(sha256_hash, SHA256_DIGEST_SIZE); tpm_extend_pcr(tpm, pcr, TPM_ALG_SHA256, &sha256_hash[0]); + event_log_extend_placeholders(tpm, pcr); log_event_tpm20(pcr, hash, sha256_hash, ev); } diff --git a/test-event_log.c b/test-event_log.c new file mode 100644 index 0000000..7ea7106 --- /dev/null +++ b/test-event_log.c @@ -0,0 +1,295 @@ +/* + * A hosted test of the TPM 2.0 event log: the header and records follow + * the TPM's banks, with the placeholder where the SKL has no digest. + */ +#include +#include +#include +#include +#include + +/* The host's C runtime owns _start, so the SKL's symbols go by other names */ +#define _start skl_start +#define _end_of_measured skl_end_of_measured + +#include "sha1sum.c" +#include "sha256.c" +#include "event_log.c" + +#ifdef DEBUG +void print(const char *unused) { } +void print_u64(u64 unused) { } +#endif + +/* An SLB-sized image whose first 16 bytes are what SKINIT measured */ +const char skl_start[SLB_SIZE] = "SKINIT measured"; +__asm__(".globl skl_end_of_measured\n.set skl_end_of_measured, skl_start + 16"); + +/* + * The SLRT that gives the SKL its log region: one log info entry, then + * the end tag. Its flexible array is initialised with room for both. + */ +#define SLRT_ENTRIES \ + ((sizeof(struct slr_entry_log_info) + sizeof(struct slr_entry_hdr)) / \ + sizeof(struct slr_entry_hdr)) + +struct slr_table bootloader_data = { + .entries = { [SLRT_ENTRIES - 1] = { 0 } }, +}; +static struct slr_entry_log_info *const slrt_log = + (struct slr_entry_log_info *)bootloader_data.entries; +static struct slr_entry_hdr *const slrt_end = + (struct slr_entry_hdr *)bootloader_data.entries + SLRT_ENTRIES - 1; + +static u8 log_region[4096]; + +/* What the TPM answers for its banks */ +static u16 tpm_algs[MAX_BANKS]; +static u32 tpm_nalgs; +static int tpm_banks_rc; + +int tpm_get_pcr_banks(struct tpm *t, u16 *algs, u32 *count) +{ + if ( tpm_banks_rc ) + return tpm_banks_rc; + memcpy(algs, tpm_algs, tpm_nalgs * sizeof(u16)); + *count = tpm_nalgs; + return 0; +} + +static void tpm_has(u32 n, ...) +{ + va_list ap; + u32 i; + + va_start(ap, n); + for ( i = 0; i < n; i++ ) + tpm_algs[i] = va_arg(ap, int); + va_end(ap); + tpm_nalgs = n; + tpm_banks_rc = 0; +} + +/* The extends made: (pcr, alg, whether the placeholder) */ +static struct { + u32 pcr; + u16 alg; + bool placeholder; +} extends[32]; +static unsigned nextends; + +static bool is_placeholder(const u8 *digest, u32 size) +{ + u32 i; + + if ( digest[0] != 1 ) + return false; + for ( i = 1; i < size; i++ ) + if ( digest[i] != 0 ) + return false; + return true; +} + +int tpm_extend_pcr(struct tpm *t, u32 pcr, u16 algo, u8 *digest) +{ + if ( nextends < 32 ) + { + extends[nextends].pcr = pcr; + extends[nextends].alg = algo; + extends[nextends].placeholder = + is_placeholder(digest, digest_size(algo)); + } + nextends++; + return 0; +} + +static struct tpm tpm20 = { .family = TPM20 }; + +static int failures; + +#define CHECK(cond) do { \ + if ( !(cond) ) { \ + printf("%s:%d: %s\n", __FILE__, __LINE__, #cond); \ + failures++; \ + } \ +} while ( 0 ) + +static void fresh_log(u32 size) +{ + nextends = 0; + bootloader_data.size = sizeof(bootloader_data) + + SLRT_ENTRIES * sizeof(struct slr_entry_hdr); + slrt_log->hdr.tag = SLR_ENTRY_LOG_INFO; + slrt_log->hdr.size = sizeof(*slrt_log); + slrt_log->format = SLR_DRTM_TPM20_LOG; + slrt_log->size = size; + slrt_log->addr = _u(log_region); + slrt_end->tag = SLR_ENTRY_END; + slrt_end->size = sizeof(*slrt_end); + memset(log_region, 0xa5, sizeof(log_region)); + CHECK(event_log_init(&tpm20) == 0); +} + +/* The header as a reader parses it: the algorithm table and the vendor info */ +static struct { + u32 nalgs; + u16 alg[MAX_BANKS]; + u16 size[MAX_BANKS]; + txt_event_log_pointer2_1_element *el; + const u8 *records; +} hdr; + +static void parse_header(void) +{ + const u8 *p = log_region + sizeof(tpm12_event_t); + u32 i; + + CHECK(memcmp(p, "Spec ID Event03", 16) == 0); + p += sizeof(common_spec_id_ev_t); + memcpy(&hdr.nalgs, p, 4); + p += 4; + CHECK(hdr.nalgs >= 1 && hdr.nalgs <= MAX_BANKS); + for ( i = 0; i < hdr.nalgs && i < MAX_BANKS; i++ ) + { + memcpy(&hdr.alg[i], p, 2); + memcpy(&hdr.size[i], p + 2, 2); + p += 4; + } + CHECK(*p == sizeof(*hdr.el)); + hdr.el = (txt_event_log_pointer2_1_element *)(p + 1); + hdr.records = p + 1 + sizeof(*hdr.el); +} + +/* + * The records after the header, parsed as a reader would: each carries + * the header's banks in its order, and for every bank the first digest + * byte and whether it is the placeholder. + */ +struct record { + u32 pcr, type; + u8 first[MAX_BANKS]; + bool placeholder[MAX_BANKS]; +}; + +static unsigned records(struct record *out, unsigned max) +{ + const u8 *p; + unsigned n = 0; + + parse_header(); + p = hdr.records; + while ( n < max ) + { + u32 count, i, len; + + memcpy(&out[n].pcr, p, 4); + memcpy(&out[n].type, p + 4, 4); + memcpy(&count, p + 8, 4); + if ( count == 0 ) + break; + p += 12; + CHECK(count == hdr.nalgs); + for ( i = 0; i < count && i < MAX_BANKS; i++ ) + { + u16 alg; + memcpy(&alg, p, 2); + CHECK(alg == hdr.alg[i]); + out[n].first[i] = p[2]; + out[n].placeholder[i] = is_placeholder(p + 2, hdr.size[i]); + p += 2 + hdr.size[i]; + } + memcpy(&len, p, 4); + p += 4 + len; + n++; + } + return n; +} + +int main(void) +{ + u8 skinit_sha1[20], skinit_sha256[32]; + struct record recs[16]; + unsigned n; + + sha1sum(skinit_sha1, skl_start, 16); + sha256sum(skinit_sha256, skl_start, 16); + + /* A TPM with the two banks a discrete one ships with */ + tpm_has(2, TPM_ALG_SHA1, TPM_ALG_SHA256); + fresh_log(sizeof(log_region)); + n = records(recs, 16); + CHECK(hdr.nalgs == 2 && hdr.alg[0] == TPM_ALG_SHA1 && hdr.size[0] == 20); + CHECK(hdr.alg[1] == TPM_ALG_SHA256 && hdr.size[1] == 32); + CHECK(hdr.el->phys_addr == _u(log_region)); + CHECK(hdr.el->allocated_event_container_size == sizeof(log_region)); + CHECK(hdr.el->first_record_offset == 0); + /* The header event, the header with its two banks, then SKINIT's record */ + CHECK(hdr.records == log_region + 32 + 24 + 4 + 8 + 1 + 20); + CHECK(hdr.el->next_record_offset == 32 + 24 + 4 + 8 + 1 + 20 + 72 + 6); + CHECK(n == 1); + CHECK(recs[0].pcr == 17 && recs[0].type == EV_TYPE_SLAUNCH); + CHECK(recs[0].first[0] == skinit_sha1[0] && !recs[0].placeholder[0]); + CHECK(recs[0].first[1] == skinit_sha256[0] && !recs[0].placeholder[1]); + /* Nothing to extend the placeholder into */ + event_log_extend_placeholders(&tpm20, 17); + CHECK(nextends == 0); + + /* A TPM with only SHA-256: one digest per record and no placeholder */ + tpm_has(1, TPM_ALG_SHA256); + fresh_log(sizeof(log_region)); + n = records(recs, 16); + CHECK(hdr.nalgs == 1 && hdr.alg[0] == TPM_ALG_SHA256); + CHECK(hdr.records == log_region + 32 + 24 + 4 + 4 + 1 + 20); + CHECK(n == 1 && recs[0].first[0] == skinit_sha256[0]); + CHECK(!recs[0].placeholder[0]); + event_log_extend_placeholders(&tpm20, 17); + CHECK(nextends == 0); + + /* + * A TPM with SHA-384 as well, in its own order: the SKL's records get + * the placeholder there, which is extended. + */ + tpm_has(3, TPM_ALG_SHA256, TPM_ALG_SHA1, TPM_ALG_SHA384); + fresh_log(sizeof(log_region)); + n = records(recs, 16); + CHECK(hdr.nalgs == 3 && hdr.alg[0] == TPM_ALG_SHA256); + CHECK(hdr.alg[1] == TPM_ALG_SHA1); + CHECK(hdr.alg[2] == TPM_ALG_SHA384 && hdr.size[2] == 48); + CHECK(n == 1); + CHECK(recs[0].first[0] == skinit_sha256[0] && !recs[0].placeholder[0]); + CHECK(recs[0].first[1] == skinit_sha1[0] && !recs[0].placeholder[1]); + CHECK(recs[0].placeholder[2]); + event_log_extend_placeholders(&tpm20, 17); + CHECK(nextends == 1); + CHECK(extends[0].pcr == 17 && extends[0].alg == TPM_ALG_SHA384); + CHECK(extends[0].placeholder); + + /* A bank of an algorithm the SKL cannot size is left out */ + tpm_has(2, 0x99, TPM_ALG_SHA256); + fresh_log(sizeof(log_region)); + records(recs, 16); + CHECK(hdr.nalgs == 1 && hdr.alg[0] == TPM_ALG_SHA256); + + /* When the TPM does not answer, the log declares SHA-1 and SHA-256 */ + tpm_banks_rc = -1; + fresh_log(sizeof(log_region)); + records(recs, 16); + CHECK(hdr.nalgs == 2 && hdr.alg[0] == TPM_ALG_SHA1); + CHECK(hdr.alg[1] == TPM_ALG_SHA256); + + /* A region too small for the header and two bare records is refused */ + tpm_has(2, TPM_ALG_SHA1, TPM_ALG_SHA256); + nextends = 0; + slrt_log->size = 32 + 57 + 2 * 72 - 1; + CHECK(event_log_init(&tpm20) != 0); + slrt_log->size = 32 + 57 + 2 * 72; + CHECK(event_log_init(&tpm20) == 0); + + if ( failures ) + { + printf("%d failures\n", failures); + return 1; + } + printf("ok\n"); + return 0; +} diff --git a/tpmlib/tpm.c b/tpmlib/tpm.c index 64b3995..e4a5d91 100644 --- a/tpmlib/tpm.c +++ b/tpmlib/tpm.c @@ -152,6 +152,17 @@ int tpm_extend_pcr(struct tpm *t, u32 pcr, u16 algo, return ret; } +int tpm_get_pcr_banks(struct tpm *t, u16 *algs, u32 *count) +{ + if (t->buff == NULL) + return -EINVAL; + + if (t->family != TPM20) + return -EINVAL; + + return tpm2_get_pcr_banks(t, algs, count); +} + void free_tpm(struct tpm *t) { tpm_relinquish_locality(t); diff --git a/tpmlib/tpm.h b/tpmlib/tpm.h index a0b57e1..ca75a83 100644 --- a/tpmlib/tpm.h +++ b/tpmlib/tpm.h @@ -56,5 +56,6 @@ extern u8 tpm_request_locality(struct tpm *t, u8 l); extern void tpm_relinquish_locality(struct tpm *t); extern int tpm_extend_pcr(struct tpm *t, u32 pcr, u16 algo, u8 *digest); +extern int tpm_get_pcr_banks(struct tpm *t, u16 *algs, u32 *count); extern void free_tpm(struct tpm *t); #endif diff --git a/tpmlib/tpm2.h b/tpmlib/tpm2.h index 96ed81c..1d1a3cd 100644 --- a/tpmlib/tpm2.h +++ b/tpmlib/tpm2.h @@ -85,4 +85,15 @@ struct tpm2_resp { int tpm2_extend_pcr(struct tpm *t, u32 pcr, struct tpml_digest_values *digests); +/* + * Reads the banks with PCRs allocated, in the TPM's order. On entry + * *count is the room in algs[], and on success it is the number of banks + * found. Returns 0 on success, or: + * - -ENOSPC when the TPM has more banks than that + * - the TPM response code when the TPM fails the command + * - -EIO when the reply is malformed + * - another negative errno when the command cannot be sent or received + */ +int tpm2_get_pcr_banks(struct tpm *t, u16 *algs, u32 *count); + #endif diff --git a/tpmlib/tpm2_cmds.c b/tpmlib/tpm2_cmds.c index ca9e24e..d76adc7 100644 --- a/tpmlib/tpm2_cmds.c +++ b/tpmlib/tpm2_cmds.c @@ -158,3 +158,113 @@ int tpm2_extend_pcr(struct tpm *t, u32 pcr, out: return ret; } + +int tpm2_get_pcr_banks(struct tpm *t, u16 *algs, u32 *count) +{ + struct tpmbuff *b = t->buff; + struct tpm2_cmd cmd; + struct tpm_header *hdr; + u8 *p, *end; + u32 nsel, i, n = 0; + int ret = 0; + + if (b == NULL || algs == NULL || count == NULL) + return -EINVAL; + + ret = tpm2_alloc_cmd(b, &cmd, TPM_ST_NO_SESSIONS, + TPM_CC_GET_CAPABILITY); + if (ret < 0) + goto out; + + /* capability, property, propertyCount */ + cmd.params = (u8 *)tpmb_put(b, 3 * sizeof(u32)); + if (cmd.params == NULL) { + ret = -ENOMEM; + goto free; + } + + p = cmd.params; + *(u32 *)p = cpu_to_be32(TPM_CAP_PCRS); + p += sizeof(u32); + *(u32 *)p = cpu_to_be32(0); + p += sizeof(u32); + *(u32 *)p = cpu_to_be32(1); + + cmd.header->size = cpu_to_be32(tpmb_size(b)); + + if (t->ops.send(b) != tpmb_size(b)) { + ret = -EAGAIN; + goto free; + } + + tpmb_trim(b, tpmb_size(b)); + hdr = (struct tpm_header *)b->head; + tpmb_put(b, sizeof(struct tpm_header)); + + if (t->ops.recv(t->family, b) != tpmb_size(b)) { + ret = -EAGAIN; + goto free; + } + + if (hdr->code != 0) { + ret = (int)hdr->code; + goto free; + } + + /* + * Response: moreData, TPMS_CAPABILITY_DATA{capability, + * TPML_PCR_SELECTION{count, TPMS_PCR_SELECTION{hash, sizeofSelect, + * pcrSelect[]}[]}}. A bank with any PCR selected is allocated. + */ + p = b->head + sizeof(struct tpm_header); + end = b->head + hdr->size; + + if (end - p < 1 + sizeof(u32) + sizeof(u32)) { + ret = -EIO; + goto free; + } + p += 1; + if (be32_to_cpu(*(u32 *)p) != TPM_CAP_PCRS) { + ret = -EIO; + goto free; + } + p += sizeof(u32); + nsel = be32_to_cpu(*(u32 *)p); + p += sizeof(u32); + + for (i = 0; i < nsel; i++) { + u16 alg; + u8 size, j, selected = 0; + + if (end - p < sizeof(u16) + 1) { + ret = -EIO; + goto free; + } + alg = be16_to_cpu(*(u16 *)p); + size = p[sizeof(u16)]; + p += sizeof(u16) + 1; + if (end - p < size) { + ret = -EIO; + goto free; + } + for (j = 0; j < size; j++) + selected |= p[j]; + p += size; + + if (selected) { + if (n < *count) + algs[n] = alg; + n++; + } + } + + if (n > *count) + ret = -ENOSPC; + else + *count = n; + +free: + tpmb_free(b); +out: + return ret; +} diff --git a/tpmlib/tpm2_constants.h b/tpmlib/tpm2_constants.h index 35bdc1e..2893cde 100644 --- a/tpmlib/tpm2_constants.h +++ b/tpmlib/tpm2_constants.h @@ -58,8 +58,12 @@ #define TPM_ALG_LAST _AT(u16, 0x0044) /* Table 12 Definition of (UINT32) TPM_CC Constants (Numeric Order) */ +#define TPM_CC_GET_CAPABILITY _AT(u32, 0x0000017A) #define TPM_CC_PCR_EXTEND _AT(u32, 0x00000182) +/* Table 22 Definition of (UINT32) TPM_CAP Constants */ +#define TPM_CAP_PCRS _AT(u32, 0x00000005) + /* Table 19 Definition of (UINT16) TPM_ST Constants */ #define TPM_ST_NO_SESSIONS _AT(u16, 0x8001) #define TPM_ST_SESSIONS _AT(u16, 0x8002) From 65fbb8708528ec58948cc6282de0e07e5160f8fb Mon Sep 17 00:00:00 2001 From: iwanicki92 Date: Mon, 21 Sep 2026 23:29:24 +0200 Subject: [PATCH 14/14] amdsl: append the PSP's TCG log to the event log MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Under the PSP DRTM service the extends of PCR 17 and 18 between SKINIT and the DLME are the service's, kept in a log of its own, so the log the DLME found could not replay to the PCRs. The AMDSL SKL now fetches that log with DRTM_CMD_GET_TCG_LOGS after the OSSL extend and appends its records, bar the SKINIT one it logged already, rewritten to the log's banks: a digest the record carries for a bank is kept, a bank it lacks gets the placeholder and the extend. Its log starts after the launch, at the locality the service unlocks with it, so the TPM bank query answers there. This is the PSP half of "Log the TPM's banks and append the PSP's TCG log". The bank half is TrenchBoot/secure-kernel-loader#28. Signed-off-by: iwanicki92 Signed-off-by: Michał Kopeć Signed-off-by: Michał Iwanicki --- event_log.c | 94 +++++++++++++++++++++++++++++++++++++++++++++ include/event_log.h | 3 ++ include/psp.h | 5 +++ main.c | 28 ++++++++++++-- psp.c | 69 +++++++++++++++++++++++++++++++++ 5 files changed, 196 insertions(+), 3 deletions(-) diff --git a/event_log.c b/event_log.c index c65d500..7afae47 100644 --- a/event_log.c +++ b/event_log.c @@ -260,6 +260,100 @@ void event_log_extend_placeholders(struct tpm *tpm, u32 pcr) tpm_extend_pcr(tpm, pcr, banks[i].alg, (u8 *)one_digest); } +/* + * Appends the records of another TCG2 log to this one, bar those of + * skip_type, each rewritten to this log's banks: a digest the record + * carries for a bank is kept, a bank it lacks gets the placeholder and + * the PCR is extended with it at this locality, so the log replays + * there too. Digests of banks this log lacks are dropped. A record that + * does not parse or fit stops the copy with what was copied left in + * place. Returns non-zero when the copy stopped short. + */ +int event_log_append_tcg2(struct tpm *tpm, const void *log, u32 size, + u32 skip_type) +{ + const u8 *p = log; + const u8 *end = p + size; + const tpm12_event_t *hdr = log; + const common_spec_id_ev_t *id; + const u16 *algs; + u32 nalgs, i; + + if ( tpm->family != TPM20 || limit == ptr_current ) + return 1; + + /* The header event, EV_NO_ACTION in PCR 0 carrying Spec ID Event03 */ + if ( size < sizeof(*hdr) + sizeof(*id) + sizeof(u32) + || hdr->pcr != 0 + || hdr->event_type != EV_NO_ACTION + || hdr->event_size < sizeof(*id) + sizeof(u32) + || hdr->event_size > size - sizeof(*hdr) ) + return 1; + + id = (const common_spec_id_ev_t *)(hdr + 1); + for ( i = 0; i < sizeof(id->signature); i++ ) + if ( id->signature[i] != tpm20_id_common.signature[i] ) + return 1; + + /* The algorithms of the log's records, id and size pairs, size its digests */ + nalgs = *(const u32 *)(id + 1); + algs = (const u16 *)((const u32 *)(id + 1) + 1); + if ( nalgs == 0 || nalgs > (hdr->event_size - sizeof(*id) - 4) / 4 ) + return 1; + + p += sizeof(*hdr) + hdr->event_size; + + while ( p + 3 * sizeof(u32) <= end ) + { + const u8 *digests[MAX_BANKS] = { NULL }; + u32 pcr, type, count, len, j; + + pcr = *(const u32 *)p; + type = *(const u32 *)(p + 4); + count = *(const u32 *)(p + 8); + if ( count == 0 ) + break; + p += 3 * sizeof(u32); + + for ( i = 0; i < count; i++ ) + { + u16 alg, dsize = 0; + + if ( p + sizeof(u16) > end ) + return 1; + alg = *(const u16 *)p; + for ( j = 0; j < nalgs; j++ ) + if ( algs[2 * j] == alg ) + dsize = algs[2 * j + 1]; + if ( dsize == 0 || dsize > end - p - sizeof(u16) ) + return 1; + for ( j = 0; j < nbanks; j++ ) + if ( banks[j].alg == alg && banks[j].size == dsize ) + digests[j] = p + sizeof(u16); + p += sizeof(u16) + dsize; + } + + if ( p + sizeof(u32) > end ) + return 1; + len = *(const u32 *)p; + p += sizeof(u32); + if ( len > end - p ) + return 1; + + if ( type != skip_type ) + { + if ( write_event_tpm20(pcr, type, digests, p, len) ) + return 1; + for ( j = 0; j < nbanks; j++ ) + if ( digests[j] == NULL ) + tpm_extend_pcr(tpm, pcr, banks[j].alg, (u8 *)one_digest); + } + p += len; + } + + return 0; +} + /* * Asks the TPM 2.0 for its allocated banks, leaving out algorithms the SKL * does not know. If the query fails, or finds no bank the SKL knows, the diff --git a/include/event_log.h b/include/event_log.h index 141433d..bea7892 100644 --- a/include/event_log.h +++ b/include/event_log.h @@ -28,4 +28,7 @@ int log_event_tpm20(u32 pcr, u8 sha1[20], u8 sha256[32], char *event); */ void event_log_extend_placeholders(struct tpm *tpm, u32 pcr); +int event_log_append_tcg2(struct tpm *tpm, const void *log, u32 size, + u32 skip_type); + #endif /* __EVENT_LOG_H__ */ diff --git a/include/psp.h b/include/psp.h index f6d2b6d..8ce6e83 100644 --- a/include/psp.h +++ b/include/psp.h @@ -91,6 +91,11 @@ bool drtm_launch(void); bool drtm_get_cap(void); +bool drtm_get_tcg_logs(u64 *addr, u64 *size); + +/* The first record of the PSP's TCG log: SKINIT's measurement of the SLB */ +#define DRTM_TCG_EV_SL_IMAGE 0x8001 + #endif /* AMDSL */ #endif /* __SKINIT_PSP_H__ */ diff --git a/main.c b/main.c index c1f5d6e..71b5761 100644 --- a/main.c +++ b/main.c @@ -259,14 +259,12 @@ static asm_return_t amdsl_launch() struct slr_entry_amd_info *amd_info; struct tpm *tpm; asm_return_t ret; + u64 log_addr, log_size; print("Enter amdsl_launch()\n"); tpm = enable_tpm(); tpm_request_locality(tpm, 2); - event_log_init(tpm); - - print("TPM enabled and logging initialized\n"); dl_info = next_entry_with_tag(NULL, SLR_ENTRY_DL_INFO); amd_info = next_entry_with_tag(NULL, SLR_ENTRY_AMD_INFO); @@ -310,10 +308,34 @@ static asm_return_t amdsl_launch() tpm_request_locality(tpm, 2); + /* + * The log declares the TPM's banks, which the TPM tells at a locality + * the DRTM service unlocked with the launch, so the log starts here. + */ + event_log_init(tpm); + if (!drtm_extend_ossl_digest((u64)dl_info->dlme_base, dl_info->dlme_size)) { print("DRTM: skl_linux: failed to extend OSSL digest\n"); } + /* + * The PSP's extends of PCR 17 and 18, at the launch and just now, are + * in a log of its own. Append its records to ours, so the log the + * DLME gets replays to the PCRs. Its first record is SKINIT's + * measurement, which event_log_init() already logged. The banks the + * service did not log get the placeholder extends, at locality 2. + */ + if (!drtm_get_tcg_logs(&log_addr, &log_size)) { + print("DRTM: skl_linux: failed to get the TCG logs\n"); + } else if (log_addr + log_size > 0xffffffffULL) { + print("DRTM: skl_linux: the TCG logs are above 4 GiB, not appended\n"); + } else if (event_log_append_tcg2(tpm, _p((u32)log_addr), (u32)log_size, + DRTM_TCG_EV_SL_IMAGE)) { + print("DRTM: skl_linux: failed to append the TCG logs\n"); + } else { + print("DRTM: skl_linux: the TCG logs are appended to the event log\n"); + } + ret.dlme_entry = _p(dl_info->dlme_base + dl_info->dlme_entry); ret.dlme_arg = _p(amd_info->boot_params_base); diff --git a/psp.c b/psp.c index d8eb7b6..139b3a6 100644 --- a/psp.c +++ b/psp.c @@ -404,4 +404,73 @@ bool drtm_get_cap(void) return true; } +/* + * One command without arguments, its status and c2pmsg_93 to _95 as the + * PSP left them printed, and true on DRTM_NO_ERROR. + */ +static bool drtm_command_no_args(const char *name, u32 cmd) +{ + u32 status = 0; + + if (!(*g_psp_drtm.c2pmsg_72 & DRTM_MBOX_READY_MASK)) { + print("DRTM: "); + print(name); + print(": PSP not ready\n"); + return false; + } + + *g_psp_drtm.c2pmsg_93 = 0; + *g_psp_drtm.c2pmsg_94 = 0; + *g_psp_drtm.c2pmsg_95 = 0; + *g_psp_drtm.c2pmsg_72 = (cmd << DRTM_MBOX_CMD_SHIFT); + if (!drtm_wait_for_psp_ready(&status)) { + print("DRTM: "); + print(name); + print(": command failed to complete\n"); + return false; + } + + print("DRTM: "); + print(name); + print(": status "); + drtm_print_status(status); + print(", c2pmsg_93 = "); + print_u64(*g_psp_drtm.c2pmsg_93); + print(", c2pmsg_94 = "); + print_u64(*g_psp_drtm.c2pmsg_94); + print(", c2pmsg_95 = "); + print_u64(*g_psp_drtm.c2pmsg_95); + print("\n"); + + return status == 0; +} + +/* + * The log of the PSP's extends, as DRTM_CMD_GET_TCG_LOGS names it: the + * size in c2pmsg_93, the address in _94 and _95, as + * DRTM_CMD_EXTEND_OSSL_DIGEST takes one. The guide places + * DRTM_CMD_ALLOCATE_SHARED_MEMORY before the command, so a failed first + * attempt is retried once after it. + */ +bool drtm_get_tcg_logs(u64 *addr, u64 *size) +{ + print("DRTM: drtm_get_tcg_logs: Entering\n"); + + if (!drtm_command_no_args("drtm_get_tcg_logs", DRTM_CMD_GET_TCG_LOGS)) { + print("DRTM: drtm_get_tcg_logs: retrying after " + "DRTM_CMD_ALLOCATE_SHARED_MEMORY\n"); + drtm_command_no_args("drtm_allocate_shared_memory", + DRTM_CMD_ALLOCATE_SHARED_MEMORY); + if (!drtm_command_no_args("drtm_get_tcg_logs", + DRTM_CMD_GET_TCG_LOGS)) { + return false; + } + } + + *size = *g_psp_drtm.c2pmsg_93; + *addr = *g_psp_drtm.c2pmsg_94 | ((u64)*g_psp_drtm.c2pmsg_95 << 32); + + return true; +} + #endif /* AMDSL */