[PATCH v4 04/23] x86/tpm.c: support extending PCRs of TPM2.0 via TIS

Sergii Dmytruk <[email protected]> Sun, 2 Aug 2026 16:09:20 +0300
Newsgroups org.xenproject.lists.xen-devel
Message-ID <037a0134f174f76a28754848446430eca1ad0e36.1785668458.git.sergii.dmytruk@3mdeb.com>
SHA1 and SHA256 are hard-coded here, but their support by the TPM is
checked.

CRB isn't supported, only TPM1.2-like TIS interface is (data is passed
via writes and reads to/from a single-byte FIFO register).

Signed-off-by: Sergii Dmytruk <[email protected]>
Signed-off-by: Szymon AcedaƄski <[email protected]>
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Sergii Dmytruk <[email protected]>
---

Notes:
    v4: was called "x86/tpm.c: support extending PCRs of TPM2.0"
    v4: adds xen/arch/x86/include/asm/tpm2.h with TPM2.0 declarations by TCG
    v4: detect CRB and do nothing (implemented separately)
    v4: put SPDX license comment on its own line
    v4: `unsigned` => `unsigned int`
    v4: replace uses of `swap16()` and `swap32()`
    v4: more error checks when parsing TPM responses
    v4: style fixes for `goto` labels and spacing

 xen/arch/x86/include/asm/tpm.h  |   5 +
 xen/arch/x86/include/asm/tpm2.h | 150 ++++++++++++++
 xen/arch/x86/tpm.c              | 340 +++++++++++++++++++++++++++++++-
 3 files changed, 489 insertions(+), 6 deletions(-)
 create mode 100644 xen/arch/x86/include/asm/tpm2.h

diff --git a/xen/arch/x86/include/asm/tpm.h b/xen/arch/x86/include/asm/tpm.h
index 06b54fb786..5fa883dad8 100644
--- a/xen/arch/x86/include/asm/tpm.h
+++ b/xen/arch/x86/include/asm/tpm.h
@@ -57,6 +57,11 @@ bool tpm_is_tpm1(void);
  * The list of hashes must either be empty or contain nothing but SHA1 hash when
  * tpm_is_tpm1() returns true.
  *
+ * When tpm_is_tpm1() returns false, the list of digests can also be used to
+ * determine which hashes to extend.  The only hashes that are guaranteed to be
+ * supported are SHA1 and SHA256, all other digests need to be pre-filled by
+ * the caller with some placeholder value.
+ *
  * Returns:
  *  - TPM error code when < 4096 (0 means success)
  *  - TPM_INTERNAL_ERROR on invalid invocation or a failure to communicate with
diff --git a/xen/arch/x86/include/asm/tpm2.h b/xen/arch/x86/include/asm/tpm2.h
new file mode 100644
index 0000000000..4565d302f3
--- /dev/null
+++ b/xen/arch/x86/include/asm/tpm2.h
@@ -0,0 +1,150 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * TPM2.0-related definitions defined by Trusted Computing Group (TCG).
+ *
+ * Copyright (c) 2022-2026 3mdeb Sp. z o.o.  All rights reserved.
+ */
+
+#ifndef X86_TPM2_H
+#define X86_TPM2_H
+
+#include <xen/inttypes.h>
+
+#include <asm/tpm.h>
+
+/*
+ * These constants are for TPM2.0 but don't have a distinct prefix to match
+ * names in the specification.
+ */
+
+#define TPM_HT_PCR   0x00
+
+#define TPM_RH_NULL  0x40000007
+#define TPM_RS_PW    0x40000009
+
+#define HR_SHIFT     24
+#define HR_PCR       (TPM_HT_PCR << HR_SHIFT)
+
+#define TPM_ST_NO_SESSIONS  0x8001
+#define TPM_ST_SESSIONS     0x8002
+
+#define TPM2_PCR_Extend                 0x00000182
+#define TPM2_PCR_HashSequenceStart      0x00000186
+#define TPM2_PCR_SequenceUpdate         0x0000015C
+#define TPM2_PCR_EventSequenceComplete  0x00000185
+
+/* All fields of the following structs are big endian. */
+
+struct tpm2_session_header {
+    uint32_t handle;
+    uint16_t nonceSize;
+    uint8_t nonce[0];
+    uint8_t attrs;
+    uint16_t hmacSize;
+    uint8_t hmac[0];
+} __packed;
+
+struct tpm2_extend_cmd {
+    struct tpm_cmd_hdr h;
+    uint32_t pcrHandle;
+    uint32_t sessionHdrSize;
+    struct tpm2_session_header pcrSession;
+    uint32_t hashCount;
+    uint8_t hashes[0];
+} __packed;
+
+struct tpm2_extend_rsp {
+    struct tpm_rsp_hdr h;
+} __packed;
+
+struct tpm2_sequence_start_cmd {
+    struct tpm_cmd_hdr h;
+    uint16_t hmacSize;
+    uint8_t hmac[0];
+    uint16_t hashAlg;
+} __packed;
+
+struct tpm2_sequence_start_rsp {
+    struct tpm_rsp_hdr h;
+    uint32_t sequenceHandle;
+} __packed;
+
+struct tpm2_sequence_update_cmd {
+    struct tpm_cmd_hdr h;
+    uint32_t sequenceHandle;
+    uint32_t sessionHdrSize;
+    struct tpm2_session_header session;
+    uint16_t dataSize;
+    uint8_t data[0];
+} __packed;
+
+struct tpm2_sequence_update_rsp {
+    struct tpm_rsp_hdr h;
+} __packed;
+
+struct tpm2_sequence_complete_cmd {
+    struct tpm_cmd_hdr h;
+    uint32_t pcrHandle;
+    uint32_t sequenceHandle;
+    uint32_t sessionHdrSize;
+    struct tpm2_session_header pcrSession;
+    struct tpm2_session_header sequenceSession;
+    uint16_t dataSize;
+    uint8_t data[0];
+} __packed;
+
+struct tpm2_sequence_complete_rsp {
+    struct tpm_rsp_hdr h;
+    uint32_t paramSize;
+    uint32_t hashCount;
+    uint8_t hashes[0];
+    /*
+     * Each hash is represented as:
+     * struct {
+     *     uint16_t hashAlg;
+     *     uint8_t hash[size of hashAlg];
+     * };
+     */
+} __packed;
+
+/* The structures below are for TPM event log and these are in little-endian. */
+
+struct tpm2_pcr_event_header {
+    uint32_t pcrIndex;
+    uint32_t eventType;
+    uint32_t digestCount;
+    uint8_t digests[0];
+    /*
+     * Each hash is represented as:
+     * struct {
+     *     uint16_t hashAlg;
+     *     uint8_t hash[size of hashAlg];
+     * };
+     */
+    /* uint32_t eventSize; */
+    /* uint8_t event[0]; */
+} __packed;
+
+struct tpm2_digest_sizes {
+    uint16_t algId;
+    uint16_t digestSize;
+} __packed;
+
+struct tpm2_spec_id_event {
+    uint32_t pcrIndex;
+    uint32_t eventType;
+    uint8_t digest[20];
+    uint32_t eventSize;
+    uint8_t signature[16];
+    uint32_t platformClass;
+    uint8_t specVersionMinor;
+    uint8_t specVersionMajor;
+    uint8_t specErrata;
+    uint8_t uintnSize;
+    uint32_t digestCount;
+    struct tpm2_digest_sizes digestSizes[0]; /* variable number of members */
+    /* uint8_t vendorInfoSize; */
+    /* uint8_t vendorInfo[vendorInfoSize]; */
+} __packed;
+
+#endif /* X86_TPM2_H */
diff --git a/xen/arch/x86/tpm.c b/xen/arch/x86/tpm.c
index 9efaf75440..59bb1ff2c4 100644
--- a/xen/arch/x86/tpm.c
+++ b/xen/arch/x86/tpm.c
@@ -12,11 +12,13 @@
 
 #include <xen/byteorder.h>
 #include <xen/sha1.h>
+#include <xen/sha2.h>
 #include <xen/string.h>
 #include <xen/types.h>
 
 #include <asm/tpm.h>
 #include <asm/tpm1.h>
+#include <asm/tpm2.h>
 
 #ifdef __EARLY_TPM__
 
@@ -75,6 +77,22 @@ static void tpm_write8(unsigned int reg, uint8_t val)
     *(volatile uint8_t *)__va(TPM_MMIO_BASE + reg) = val;
 }
 
+/************************** Interface detection *******************************/
+
+#define TPM_INTF_ID_(x)         TPM_LOC_REG(x, 0x30)
+#define INTF_TYPE_MASK           0x0000000fU
+#define INTF_TYPE_TIS            0x00
+#define INTF_TYPE_CRB            0x01
+
+/*
+ * No static caching: the early 32-bit binary (tpm_early.bin) is built with
+ * "objcopy -j .text", which omits .bss/.data.
+ */
+static bool tpm_is_crb(void)
+{
+    return (tpm_read32(TPM_INTF_ID_(0)) & INTF_TYPE_MASK) == INTF_TYPE_CRB;
+}
+
 /************************** TIS register definitions **************************/
 
 #define TIS_ACCESS_(x)          TPM_LOC_REG(x, 0x00)
@@ -181,24 +199,37 @@ static void tis_send_cmd(unsigned int loc, uint8_t *buf, unsigned int i_size,
 
 static void request_locality(unsigned int loc)
 {
-    tis_request_locality(loc);
+    if ( tpm_is_crb() )
+        return;
+    else
+        tis_request_locality(loc);
 }
 
 static void relinquish_locality(unsigned int loc)
 {
-    tis_relinquish_locality(loc);
+    if ( tpm_is_crb() )
+        return;
+    else
+        tis_relinquish_locality(loc);
 }
 
 static void send_cmd(unsigned int loc, uint8_t *buf, unsigned int i_size,
                      unsigned int *o_size)
 {
-    tis_send_cmd(loc, buf, i_size, o_size);
+    if ( tpm_is_crb() )
+        *o_size = 0;
+    else
+        tis_send_cmd(loc, buf, i_size, o_size);
 }
 
 bool tpm_is_tpm1(void)
 {
     uint32_t intf_version;
 
+    /* CRB interface is always TPM 2.0. */
+    if ( tpm_is_crb() )
+        return false;
+
     /*
      * If one of these conditions is true:
      *  - INTF_CAPABILITY_x.interfaceVersion is 0 (TIS <= 1.21)
@@ -211,14 +242,19 @@ bool tpm_is_tpm1(void)
             !(tpm_read32(TIS_STS_(0)) & STS_FAMILY_MASK));
 }
 
-/****************************** TPM1.2 specific *******************************/
+/****************************** TPM1.2 & TPM2.0 *******************************/
 
-#ifdef __EARLY_TPM__
 /*
  * TPM1.2 is required to support commands of up to 1101 bytes, vendors rarely
  * go above that. Limit maximum size of block of data to be hashed to 1024.
+ *
+ * TPM2.0 should support hashing of at least 1024 bytes.
  */
 #define MAX_HASH_BLOCK      1024
+
+/****************************** TPM1.2 specific *******************************/
+
+#ifdef __EARLY_TPM__
 #define CMD_RSP_BUF_SIZE    (sizeof(struct sha1_update_cmd) + MAX_HASH_BLOCK)
 
 union cmd_rsp {
@@ -382,6 +418,298 @@ static uint32_t tpm12_hash_extend(unsigned int loc, const uint8_t *buf,
 
 /************************** end of TPM1.2 specific ****************************/
 
+/****************************** TPM2.0 specific *******************************/
+
+#ifdef __EARLY_TPM__
+
+union tpm2_cmd_rsp {
+    uint8_t b[sizeof(struct tpm2_sequence_update_cmd) + MAX_HASH_BLOCK];
+    struct tpm_cmd_hdr c;
+    struct tpm_rsp_hdr r;
+    struct tpm2_sequence_start_cmd start_c;
+    struct tpm2_sequence_start_rsp start_r;
+    struct tpm2_sequence_update_cmd update_c;
+    struct tpm2_sequence_update_rsp update_r;
+    struct tpm2_sequence_complete_cmd finish_c;
+    struct tpm2_sequence_complete_rsp finish_r;
+};
+
+static uint32_t tpm2_hash_extend(unsigned int loc, const uint8_t *buf,
+                                 unsigned int size, unsigned int pcr,
+                                 const struct tpm_log_hashes *log_hashes)
+{
+    uint32_t seq_handle;
+    unsigned int max_bytes = MAX_HASH_BLOCK;
+
+    union tpm2_cmd_rsp cmd_rsp;
+    unsigned int o_size;
+    unsigned int i;
+    uint8_t *p;
+    uint32_t rc;
+
+    cmd_rsp.start_c = (struct tpm2_sequence_start_cmd) {
+        .h.tag = cpu_to_be16(TPM_ST_NO_SESSIONS),
+        .h.paramSize = cpu_to_be32(sizeof(cmd_rsp.start_c)),
+        .h.ordinal = cpu_to_be32(TPM2_PCR_HashSequenceStart),
+        /* Compute all supported hashes. */
+        .hashAlg = cpu_to_be16(TPM_ALG_NULL),
+    };
+
+    request_locality(loc);
+
+    o_size = sizeof(cmd_rsp);
+    send_cmd(loc, cmd_rsp.b, be32_to_cpu(cmd_rsp.c.paramSize), &o_size);
+
+    if ( o_size < sizeof(struct tpm_rsp_hdr) )
+    {
+        rc = TPM_INTERNAL_ERROR;
+        goto error;
+    }
+    rc = be32_to_cpu(cmd_rsp.r.returnCode);
+    if ( rc != 0 )
+        goto error;
+
+    seq_handle = be32_to_cpu(cmd_rsp.start_r.sequenceHandle);
+
+    while ( size > 64 )
+    {
+        if ( size < max_bytes )
+            max_bytes = ROUNDDOWN(size, 64);
+
+        cmd_rsp.update_c = (struct tpm2_sequence_update_cmd) {
+            .h.tag = cpu_to_be16(TPM_ST_SESSIONS),
+            .h.paramSize = cpu_to_be32(sizeof(cmd_rsp.update_c) + max_bytes),
+            .h.ordinal = cpu_to_be32(TPM2_PCR_SequenceUpdate),
+            .sequenceHandle = cpu_to_be32(seq_handle),
+            .sessionHdrSize = cpu_to_be32(sizeof(struct tpm2_session_header)),
+            .session.handle = cpu_to_be32(TPM_RS_PW),
+            .dataSize = cpu_to_be16(max_bytes),
+        };
+
+        memcpy(cmd_rsp.update_c.data, buf, max_bytes);
+
+        o_size = sizeof(cmd_rsp);
+        send_cmd(loc, cmd_rsp.b, be32_to_cpu(cmd_rsp.c.paramSize), &o_size);
+
+        if ( o_size < sizeof(struct tpm_rsp_hdr) )
+        {
+            rc = TPM_INTERNAL_ERROR;
+            goto error;
+        }
+        rc = be32_to_cpu(cmd_rsp.r.returnCode);
+        if ( rc != 0 )
+            goto error;
+
+        size -= max_bytes;
+        buf += max_bytes;
+    }
+
+    cmd_rsp.finish_c = (struct tpm2_sequence_complete_cmd) {
+        .h.tag = cpu_to_be16(TPM_ST_SESSIONS),
+        .h.paramSize = cpu_to_be32(sizeof(cmd_rsp.finish_c) + size),
+        .h.ordinal = cpu_to_be32(TPM2_PCR_EventSequenceComplete),
+        .pcrHandle = cpu_to_be32(HR_PCR + pcr),
+        .sequenceHandle = cpu_to_be32(seq_handle),
+        .sessionHdrSize = cpu_to_be32(sizeof(struct tpm2_session_header) * 2),
+        .pcrSession.handle = cpu_to_be32(TPM_RS_PW),
+        .sequenceSession.handle = cpu_to_be32(TPM_RS_PW),
+        .dataSize = cpu_to_be16(size),
+    };
+
+    memcpy(cmd_rsp.finish_c.data, buf, size);
+
+    o_size = sizeof(cmd_rsp);
+    send_cmd(loc, cmd_rsp.b, be32_to_cpu(cmd_rsp.c.paramSize), &o_size);
+
+    if ( o_size < sizeof(struct tpm_rsp_hdr) )
+    {
+        rc = TPM_INTERNAL_ERROR;
+        goto error;
+    }
+    rc = be32_to_cpu(cmd_rsp.r.returnCode);
+    if ( rc != 0 )
+        goto error;
+
+    if ( o_size < sizeof(cmd_rsp.finish_r) )
+    {
+        rc = TPM_INTERNAL_ERROR;
+        goto error;
+    }
+
+    p = cmd_rsp.finish_r.hashes;
+    for ( i = 0; i < be32_to_cpu(cmd_rsp.finish_r.hashCount); ++i )
+    {
+        unsigned int j;
+        uint16_t hash_type;
+
+        if ( p + sizeof(uint16_t) > cmd_rsp.b + o_size )
+        {
+            rc = TPM_INTERNAL_ERROR;
+            goto error;
+        }
+        hash_type = be16_to_cpu(*(uint16_t *)p);
+        p += sizeof(uint16_t);
+
+        for ( j = 0; j < log_hashes->count; ++j )
+        {
+            const struct tpm_log_hash *hash = &log_hashes->hashes[j];
+            if ( hash->alg == hash_type )
+            {
+                if ( p + hash->size > cmd_rsp.b + o_size )
+                {
+                    rc = TPM_INTERNAL_ERROR;
+                    goto error;
+                }
+                memcpy(hash->data, p, hash->size);
+                p += hash->size;
+                break;
+            }
+        }
+
+        if ( j == log_hashes->count )
+            /* Can't continue parsing without knowing hash size. */
+            break;
+    }
+
+    rc = 0;
+
+ error:
+    relinquish_locality(loc);
+    return rc;
+}
+
+#else
+
+union tpm2_cmd_rsp {
+    /* Enough space for multiple hashes. */
+    uint8_t b[sizeof(struct tpm2_extend_cmd) + 1024];
+    struct tpm_cmd_hdr c;
+    struct tpm_rsp_hdr r;
+    struct tpm2_extend_cmd extend_c;
+    struct tpm2_extend_rsp extend_r;
+};
+
+static uint32_t tpm20_pcr_extend(unsigned int loc, uint32_t pcr_handle,
+                                 const struct tpm_log_hashes *log_hashes)
+{
+    union tpm2_cmd_rsp cmd_rsp;
+    unsigned int o_size;
+    unsigned int i;
+    uint8_t *p;
+
+    cmd_rsp.extend_c = (struct tpm2_extend_cmd) {
+        .h.tag = cpu_to_be16(TPM_ST_SESSIONS),
+        .h.ordinal = cpu_to_be32(TPM2_PCR_Extend),
+        .pcrHandle = cpu_to_be32(pcr_handle),
+        .sessionHdrSize = cpu_to_be32(sizeof(struct tpm2_session_header)),
+        .pcrSession.handle = cpu_to_be32(TPM_RS_PW),
+        .hashCount = cpu_to_be32(log_hashes->count),
+    };
+
+    p = cmd_rsp.extend_c.hashes;
+    for ( i = 0; i < log_hashes->count; ++i )
+    {
+        const struct tpm_log_hash *hash = &log_hashes->hashes[i];
+
+        if ( p + sizeof(uint16_t) + hash->size > &cmd_rsp.b[sizeof(cmd_rsp)] )
+        {
+            printk(XENLOG_ERR "Hit TPM message size implementation limit: %ld\n",
+                   sizeof(cmd_rsp));
+            return TPM_INTERNAL_ERROR;
+        }
+
+        *(uint16_t *)p = cpu_to_be16(hash->alg);
+        p += sizeof(uint16_t);
+
+        memcpy(p, hash->data, hash->size);
+        p += hash->size;
+    }
+
+    /* Fill in command size (size of the whole buffer). */
+    cmd_rsp.c.paramSize = cpu_to_be32(sizeof(cmd_rsp.extend_c) +
+                                      (p - cmd_rsp.extend_c.hashes));
+
+    o_size = sizeof(cmd_rsp);
+    send_cmd(loc, cmd_rsp.b, be32_to_cpu(cmd_rsp.c.paramSize), &o_size);
+
+    return be32_to_cpu(cmd_rsp.r.returnCode);
+}
+
+static bool tpm2_supports_hash(unsigned int loc,
+                               const struct tpm_log_hash *hash)
+{
+    uint32_t rc;
+    struct tpm_log_hashes hashes = {
+        .count = 1,
+        .hashes[0] = *hash,
+    };
+
+    /*
+     * This is a valid way of checking hash support, using it to not implement
+     * TPM2_GetCapability().
+     */
+    rc = tpm20_pcr_extend(loc, /*pcr_handle=*/TPM_RH_NULL, &hashes);
+
+    return rc == 0;
+}
+
+static uint32_t tpm2_hash_extend(unsigned int loc, const uint8_t *buf,
+                                 unsigned int size, unsigned int pcr,
+                                 const struct tpm_log_hashes *log_hashes)
+{
+    uint32_t rc;
+    unsigned int i;
+    struct tpm_log_hashes supported_hashes = {0};
+
+    request_locality(loc);
+
+    for ( i = 0; i < log_hashes->count; ++i )
+    {
+        const struct tpm_log_hash *hash = &log_hashes->hashes[i];
+        if ( !tpm2_supports_hash(loc, hash) )
+        {
+            printk(XENLOG_WARNING "Skipped hash unsupported by TPM: %d\n",
+                   hash->alg);
+            continue;
+        }
+
+        if ( hash->alg == TPM_ALG_SHA1 )
+        {
+            sha1(hash->data, buf, size);
+        }
+        else if ( hash->alg == TPM_ALG_SHA256 )
+        {
+            sha2_256(hash->data, buf, size);
+        }
+        else
+        {
+            /*
+             * Assuming the caller has initialized the digest with some
+             * pattern.
+             */
+        }
+
+        if ( supported_hashes.count == MAX_TPM_HASH_COUNT )
+        {
+            printk(XENLOG_ERR "Hit hash count implementation limit: %d\n",
+                   MAX_TPM_HASH_COUNT);
+            return TPM_INTERNAL_ERROR;
+        }
+
+        supported_hashes.hashes[supported_hashes.count] = *hash;
+        ++supported_hashes.count;
+    }
+
+    rc = tpm20_pcr_extend(loc, HR_PCR + pcr, &supported_hashes);
+    relinquish_locality(loc);
+
+    return rc;
+}
+
+#endif /* __EARLY_TPM__ */
+
+/************************** end of TPM2.0 specific ****************************/
+
 uint32_t tpm_hash_extend(unsigned int loc, unsigned int pcr, const uint8_t *buf,
                          unsigned int size,
                          const struct tpm_log_hashes *log_hashes)
@@ -402,5 +730,5 @@ uint32_t tpm_hash_extend(unsigned int loc, unsigned int pcr, const uint8_t *buf,
         return tpm12_hash_extend(loc, buf, size, pcr, log_hashes);
     }
 
-    return TPM_INTERNAL_ERROR;
+    return tpm2_hash_extend(loc, buf, size, pcr, log_hashes);
 }
-- 
2.55.0