From 21be8d1687041064f2303bc5f147f7f7ff4e50e7 Mon Sep 17 00:00:00 2001 From: iwanicki92 Date: Wed, 28 Oct 2026 19:29:22 +0100 Subject: [PATCH 1/4] 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 2/4] 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 3/4] 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 4/4] 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)