From 6e066e771bd052b6558a3c87da597e0cc54fe978 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 1/5] tpmlib: store the last byte of a TIS response at the end of the buffer tis_recv() reads all but the last byte of the response body into the buffer and then reads the last byte into the first byte of that same body. The final byte of every response is therefore whatever the buffer held before, and the first byte is overwritten. Read the last byte into its own place after the bytes already received. Signed-off-by: iwanicki92 --- tpmlib/tis.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tpmlib/tis.c b/tpmlib/tis.c index b405f88..ffe5197 100644 --- a/tpmlib/tis.c +++ b/tpmlib/tis.c @@ -206,8 +206,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 67359ddf0dc98978c31cae0dae45f5084c3a2f18 Mon Sep 17 00:00:00 2001 From: iwanicki92 Date: Wed, 28 Oct 2026 19:29:22 +0100 Subject: [PATCH 2/5] tpmlib: poll for a TIS response for up to 2 s 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 --- tpmlib/tis.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/tpmlib/tis.c b/tpmlib/tis.c index ffe5197..4547735 100644 --- a/tpmlib/tis.c +++ b/tpmlib/tis.c @@ -157,25 +157,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 31d116c7e77e1b86865d4ee778393bd8318c5546 Mon Sep 17 00:00:00 2001 From: iwanicki92 Date: Thu, 29 Oct 2026 20:14:28 +0100 Subject: [PATCH 3/5] tpmlib: count an SM3-256 digest with its own size 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 --- 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 8db050d6678a35fedd133f41cdb02c1771c8e43b Mon Sep 17 00:00:00 2001 From: iwanicki92 Date: Thu, 29 Oct 2026 21:17:56 +0100 Subject: [PATCH 4/5] link.lds: put the small .bss before the page-aligned data .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 --- 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 61599d8..1f8ac41 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 02b17c0..a84f1b4 100644 --- a/head.S +++ b/head.S @@ -319,7 +319,7 @@ GLOBAL(skinit_reserved) #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 1760244..c51f5ea 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 46be0bd..606a252 100644 --- a/link.lds +++ b/link.lds @@ -55,9 +55,18 @@ SECTIONS *(.skinit_reserved) } - .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 @@ -108,6 +117,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 9b4018b3415da36dddbc5ac40033040ac5e56da3 Mon Sep 17 00:00:00 2001 From: iwanicki92 Date: Thu, 29 Oct 2026 19:00:10 +0100 Subject: [PATCH 5/5] Declare the TPM's PCR banks in the event log 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 --- 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 9425d7a..206eae2 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)