[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 org.xenproject.lists.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