[PATCH v4 14/23] x86/slaunch: update TPM event log (TPM1.2 or TPM2.0)
Sergii Dmytruk <[email protected]> Sun, 2 Aug 2026 16:09:30 +0300
| Newsgroups | gmane.comp.emulators.xen.devel |
|---|---|
| Message-ID | <76a5865c29f948838db879e60d3c0bc3dfc58de1.1785668458.git.sergii.dmytruk@3mdeb.com> |
Instead of storing hashing result to stack variables, a TPM event log is discovered in an Slaunch-specific way, extended with an additional entry and that entry is filled with digests. Signed-off-by: Krystian Hebel <[email protected]> Signed-off-by: Szymon AcedaĆski <[email protected]> Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Sergii Dmytruk <[email protected]> --- Notes: v4: was "x86/tpm.c: implement event log for TPM2.0" v4: part of it as in "x86/tpm.c: code for early hashing and extending PCRs (for TPM1.2)" v4: TPM event log code was part of tpm.c changes, now in slaunch-tpm.c v4: fixed comment on txt_ext_data_element::size and finding log element (worked because it was first) v4: provide list of hashes even in the absence of event log to extend PCRs xen/arch/x86/include/asm/intel-txt.h | 69 ++++++++++ xen/arch/x86/slaunch-tpm.c | 188 +++++++++++++++++++++++---- 2 files changed, 232 insertions(+), 25 deletions(-) diff --git a/xen/arch/x86/include/asm/intel-txt.h b/xen/arch/x86/include/asm/intel-txt.h index 406929fac2..8bcca20d6e 100644 --- a/xen/arch/x86/include/asm/intel-txt.h +++ b/xen/arch/x86/include/asm/intel-txt.h @@ -71,6 +71,8 @@ #include <xen/multiboot2.h> #include <xen/slr-table.h> +#include <asm/tpm1.h> + /* Need to differentiate between pre- and post paging enabled. */ #ifdef __EARLY_SLAUNCH__ #include <xen/macros.h> @@ -200,6 +202,52 @@ struct txt_sinit_mle_data { /* Ext Data Elements */ } __packed; +struct txt_ev_log_container_12 { + char Signature[20]; /* "TXT Event Container", null-terminated */ + uint8_t Reserved[12]; + uint8_t ContainerVerMajor; + uint8_t ContainerVerMinor; + uint8_t PCREventVerMajor; + uint8_t PCREventVerMinor; + uint32_t ContainerSize; /* Allocated size */ + uint32_t PCREventsOffset; + uint32_t NextEventOffset; + struct TPM12_PCREvent PCREvents[]; +}; + +/* Types of extended data. */ +#define TXT_HEAP_EXTDATA_TYPE_END 0 +#define TXT_HEAP_EXTDATA_TYPE_BIOS_SPEC_VER 1 +#define TXT_HEAP_EXTDATA_TYPE_ACM 2 +#define TXT_HEAP_EXTDATA_TYPE_STM 3 +#define TXT_HEAP_EXTDATA_TYPE_CUSTOM 4 +#define TXT_HEAP_EXTDATA_TYPE_MADT 6 +#define TXT_HEAP_EXTDATA_TYPE_EVENT_LOG_POINTER2_1 8 +#define TXT_HEAP_EXTDATA_TYPE_MCFG 9 +#define TXT_HEAP_EXTDATA_TYPE_TPR_REQ 13 +#define TXT_HEAP_EXTDATA_TYPE_DTPR 14 +#define TXT_HEAP_EXTDATA_TYPE_CEDT 15 + +/* + * Self-describing data structure that is used for extensions to TXT heap + * tables. + */ +struct txt_ext_data_element { + uint32_t type; /* One of TXT_HEAP_EXTDATA_TYPE_*. */ + uint32_t size; /* Size of the whole element (header + data), in bytes. */ + uint8_t data[0]; +} __packed; + +/* + * Extended data describing TPM 2.0 log. + */ +struct heap_event_log_pointer_element2_1 { + uint64_t physical_address; + uint32_t allocated_event_container_size; + uint32_t first_record_offset; + uint32_t next_record_offset; +} __packed; + /* * Functions to extract data from the Intel TXT Heap Memory. * @@ -268,6 +316,27 @@ static inline void *txt_init(void) return txt_heap; } +/* + * Find the given element in the TXT heap extended data. + */ +static inline struct txt_ext_data_element * +txt_find_ext_data_element(struct txt_os_sinit_data *os_sinit, uint32_t type) +{ + struct txt_ext_data_element *ext_elem; + + ext_elem = (void *)os_sinit + sizeof(struct txt_os_sinit_data); + + while ( ext_elem->type != TXT_HEAP_EXTDATA_TYPE_END ) + { + if ( ext_elem->type == type ) + return ext_elem; + + ext_elem = (void *)ext_elem + ext_elem->size; + } + + return NULL; +} + static inline bool is_in_pmr(const struct txt_os_sinit_data *os_sinit, uint64_t base, uint32_t size, bool check_high) { diff --git a/xen/arch/x86/slaunch-tpm.c b/xen/arch/x86/slaunch-tpm.c index 21dec67dca..e3b7341cc5 100644 --- a/xen/arch/x86/slaunch-tpm.c +++ b/xen/arch/x86/slaunch-tpm.c @@ -67,6 +67,136 @@ void asmlinkage slaunch_measure_mbi(const multiboot2_fixed_t *mbi, #endif /* __EARLY_SLAUNCH__ */ +static struct tpm_log_hashes +create_log_event12(struct txt_ev_log_container_12 *evt_log, + uint32_t evt_log_size, uint32_t pcr, uint32_t type, + const uint8_t *data, unsigned data_size) +{ + struct tpm_log_hashes log_hashes = {0}; + + struct TPM12_PCREvent *new_entry; + + if (evt_log == NULL) + return log_hashes; + + new_entry = (void *)evt_log + evt_log->NextEventOffset; + + /* + * Check if there is enough space left for new entry. + * Note: it is possible to introduce a gap in event log if entry with big + * data_size is followed by another entry with smaller data. Maybe we should + * cap the event log size in such case? + */ + if ( evt_log->NextEventOffset + sizeof(struct TPM12_PCREvent) + data_size > + evt_log_size ) + return log_hashes; + + evt_log->NextEventOffset += sizeof(struct TPM12_PCREvent) + data_size; + + new_entry->PCRIndex = pcr; + new_entry->Type = type; + new_entry->Size = data_size; + + if ( data != NULL && data_size > 0 ) + memcpy(new_entry->Data, data, data_size); + + log_hashes.count = 1; + log_hashes.hashes[0].alg = TPM_ALG_SHA1; + log_hashes.hashes[0].size = SHA1_DIGEST_SIZE; + log_hashes.hashes[0].data = new_entry->Digest; + + return log_hashes; +} + +static struct heap_event_log_pointer_element2_1 * +find_evt_log_ext_data(struct tpm2_spec_id_event *evt_log) +{ + struct txt_os_sinit_data *os_sinit; + struct txt_ext_data_element *ext_data; + + os_sinit = txt_start(__va(txt_read(TXTCR_HEAP_BASE)), TXT_OS2SINIT); + ext_data = txt_find_ext_data_element(os_sinit, + TXT_HEAP_EXTDATA_TYPE_EVENT_LOG_POINTER2_1); + if ( ext_data == NULL ) + return NULL; + + return (struct heap_event_log_pointer_element2_1 *)ext_data->data; +} + +static struct tpm_log_hashes +create_log_event20(struct tpm2_spec_id_event *evt_log, uint32_t evt_log_size, + uint32_t pcr, uint32_t type, const uint8_t *data, + unsigned data_size) +{ + struct tpm_log_hashes log_hashes = {0}; + + struct heap_event_log_pointer_element2_1 *log_ext_data; + struct tpm2_pcr_event_header *new_entry; + uint32_t entry_size; + unsigned i; + uint8_t *p; + + if (evt_log == NULL) + return log_hashes; + + log_ext_data = find_evt_log_ext_data(evt_log); + if ( log_ext_data == NULL ) + return log_hashes; + + entry_size = sizeof(*new_entry); + for ( i = 0; i < evt_log->digestCount; ++i ) + { + entry_size += sizeof(uint16_t); /* hash type */ + entry_size += evt_log->digestSizes[i].digestSize; + } + entry_size += sizeof(uint32_t); /* data size field */ + entry_size += data_size; + + /* + * Check if there is enough space left for new entry. + * Note: it is possible to introduce a gap in event log if entry with big + * data_size is followed by another entry with smaller data. Maybe we should + * cap the event log size in such case? + */ + if ( log_ext_data->next_record_offset + entry_size > evt_log_size ) + return log_hashes; + + new_entry = (void *)evt_log + log_ext_data->next_record_offset; + log_ext_data->next_record_offset += entry_size; + + new_entry->pcrIndex = pcr; + new_entry->eventType = type; + new_entry->digestCount = evt_log->digestCount; + + p = &new_entry->digests[0]; + for ( i = 0; i < evt_log->digestCount; ++i ) + { + uint16_t alg = evt_log->digestSizes[i].algId; + uint16_t size = evt_log->digestSizes[i].digestSize; + + *(uint16_t *)p = alg; + p += sizeof(uint16_t); + + log_hashes.hashes[i].alg = alg; + log_hashes.hashes[i].size = size; + log_hashes.hashes[i].data = p; + p += size; + + /* This is called "OneDigest" in TXT Software Development Guide. */ + memset(log_hashes.hashes[i].data, 0, size); + log_hashes.hashes[i].data[0] = 1; + } + log_hashes.count = evt_log->digestCount; + + *(uint32_t *)p = data_size; + p += sizeof(uint32_t); + + if ( data != NULL && data_size > 0 ) + memcpy(p, data, data_size); + + return log_hashes; +} + void slaunch_find_log(const struct slr_table *slrt, paddr_t *evt_log, uint32_t *evt_log_size) { @@ -99,42 +229,50 @@ void slaunch_hash_extend(unsigned int loc, unsigned int pcr, const uint8_t *buf, { paddr_t evt_log_paddr; uint32_t evt_log_size; - struct tpm_log_hashes log_hashes; uint8_t discarded_digests[SHA2_256_DIGEST_SIZE]; + struct tpm_log_hashes log_hashes; uint32_t rc; slaunch_find_log(slaunch_get_slrt(), &evt_log_paddr, &evt_log_size); if ( tpm_is_tpm1() ) { - log_hashes = (struct tpm_log_hashes) { - .count = 1, - .hashes = { - { - .alg = TPM_ALG_SHA1, - .size = SHA1_DIGEST_SIZE, - .data = discarded_digests, - }, - }, - }; + struct txt_ev_log_container_12 *evt_log = __va(evt_log_paddr); + + log_hashes = create_log_event12(evt_log, evt_log_size, pcr, type, + log_data, log_data_size); } else { - log_hashes = (struct tpm_log_hashes) { - .count = 2, - .hashes = { - { - .alg = TPM_ALG_SHA1, - .size = SHA1_DIGEST_SIZE, - .data = discarded_digests, - }, - { - .alg = TPM_ALG_SHA256, - .size = SHA2_256_DIGEST_SIZE, - .data = discarded_digests, + struct tpm2_spec_id_event *evt_log = __va(evt_log_paddr); + + log_hashes = create_log_event20(evt_log, evt_log_size, pcr, type, + log_data, log_data_size); + + if ( log_hashes.count == 0 ) + { + /* + * Because TPM2 supports multiple PCR banks, the list of digests is + * also used to indicate which banks to extend. Thus avoid passing + * an empty list of digests to have a chance of something being + * extended even without event log. + */ + log_hashes = (struct tpm_log_hashes) { + .count = 2, + .hashes = { + { + .alg = TPM_ALG_SHA1, + .size = SHA1_DIGEST_SIZE, + .data = discarded_digests, + }, + { + .alg = TPM_ALG_SHA256, + .size = SHA2_256_DIGEST_SIZE, + .data = discarded_digests, + }, }, - }, - }; + }; + } } rc = tpm_hash_extend(loc, pcr, buf, size, &log_hashes); -- 2.55.0