From dc655b6b6f59aa4554c5ffc8ba8c997ebc2c72b5 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 6 Aug 2026 12:59:08 -0700 Subject: [PATCH 01/21] F-7585 - Parse policy_sign -pcrdigest= hex result in a signed temporary --- examples/pcr/policy_sign.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/examples/pcr/policy_sign.c b/examples/pcr/policy_sign.c index 066ee9238..86c6c9c69 100644 --- a/examples/pcr/policy_sign.c +++ b/examples/pcr/policy_sign.c @@ -292,15 +292,17 @@ int TPM2_PCR_PolicySign_Example(void* userCtx, int argc, char *argv[]) else if (XSTRNCMP(argv[argc-1], "-pcrdigest=", XSTRLEN("-pcrdigest=")) == 0) { const char* hashHexStr = argv[argc-1] + XSTRLEN("-pcrdigest="); int hashHexStrLen = (int)XSTRLEN(hashHexStr); - if (hashHexStrLen > (int)sizeof(pcrDigest)*2+1) - pcrDigestSz = -1; + int hexRet; + if (hashHexStrLen > (int)sizeof(pcrDigest)*2) + hexRet = -1; else - pcrDigestSz = hexToByte(hashHexStr, pcrDigest, hashHexStrLen); - if (pcrDigestSz <= 0) { + hexRet = hexToByte(hashHexStr, pcrDigest, hashHexStrLen); + if (hexRet <= 0) { fprintf(stderr, "Invalid PCR hash length\n"); usage(); return -1; } + pcrDigestSz = (word32)hexRet; } else if (XSTRNCMP(argv[argc-1], "-password=", XSTRLEN("-password=")) == 0) { From 6c3c356e1f696e2474ce742e47fee5e6ac070ce6 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 6 Aug 2026 12:59:08 -0700 Subject: [PATCH 02/21] F-7596 - Compare pcrDigestSz against remaining buffer capacity --- src/tpm2_wrap.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index d50044bbe..a4340defa 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -10867,7 +10867,8 @@ int wolfTPM2_PolicyPCRMake(TPM_ALG_ID pcrAlg, byte* pcrArray, word32 pcrArraySz, TPM2_Packet_AppendPCR(&packet, &pcr); /* Copy the pcrDigest to the end of buffer */ - if (pcrDigestSz + packet.pos > sizeof(buf)) { + if (packet.pos < 0 || (word32)packet.pos > (word32)sizeof(buf) || + pcrDigestSz > (word32)sizeof(buf) - (word32)packet.pos) { return BUFFER_E; } XMEMCPY(buf + packet.pos, pcrDigest, pcrDigestSz); From 394dce35451db7a820ee6dfea0257a0879fe4e18 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 6 Aug 2026 12:59:08 -0700 Subject: [PATCH 03/21] F-7586 - Keep EK cert verify result out of the example return code --- examples/endorsement/get_ek_certs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/endorsement/get_ek_certs.c b/examples/endorsement/get_ek_certs.c index c442e5c85..0a2cca076 100644 --- a/examples/endorsement/get_ek_certs.c +++ b/examples/endorsement/get_ek_certs.c @@ -505,10 +505,10 @@ int TPM2_EndorsementCert_Example(void* userCtx, int argc, char *argv[]) #ifndef WOLFCRYPT_ONLY if (rc == 0) { /* Validate EK certificate against trusted certificates */ - rc = wolfSSL_CertManagerVerifyBuffer(cm, certBuf, certSz, + int vfyRc = wolfSSL_CertManagerVerifyBuffer(cm, certBuf, certSz, WOLFSSL_FILETYPE_ASN1); printf("EK Certificate is %s\n", - (rc == WOLFSSL_SUCCESS) ? "VALID" : "INVALID"); + (vfyRc == WOLFSSL_SUCCESS) ? "VALID" : "INVALID"); } #endif From 2e32fcacbf4043151c7c184426058d2eaaacea5c Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 6 Aug 2026 12:59:08 -0700 Subject: [PATCH 04/21] F-7587 - Define TPM_SPI_PIN in the Microchip chip select fallback --- hal/tpm_io_microchip.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hal/tpm_io_microchip.c b/hal/tpm_io_microchip.c index 2c42a8fa5..1d0a1a967 100644 --- a/hal/tpm_io_microchip.c +++ b/hal/tpm_io_microchip.c @@ -279,7 +279,7 @@ /* TPM Chip Select Pin (default PC5) */ #ifndef TPM_SPI_PIN -#define SYS_PORT_PIN_PC5 +#define TPM_SPI_PIN SYS_PORT_PIN_PC5 #endif int TPM2_IoCb_Microchip_SPI(TPM2_CTX* ctx, const byte* txBuf, byte* rxBuf, From db418ebdcfd46e9b1462ad7b26d604a7f5c9fb67 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 6 Aug 2026 12:59:08 -0700 Subject: [PATCH 05/21] F-7602 - Zeroize HAL I2C write staging buffers --- hal/tpm_io_espressif.c | 2 ++ hal/tpm_io_infineon.c | 1 + hal/tpm_io_linux.c | 2 ++ hal/tpm_io_microchip.c | 2 ++ hal/tpm_io_st.c | 1 + hal/tpm_io_zephyr.c | 1 + 6 files changed, 9 insertions(+) diff --git a/hal/tpm_io_espressif.c b/hal/tpm_io_espressif.c index 27a370c45..5599770b7 100644 --- a/hal/tpm_io_espressif.c +++ b/hal/tpm_io_espressif.c @@ -367,6 +367,8 @@ static esp_err_t esp_tpm_register_write(uint32_t reg, } } + TPM2_ForceZero(buf, sizeof(buf)); + return result; } diff --git a/hal/tpm_io_infineon.c b/hal/tpm_io_infineon.c index 8d3eb88c4..3ba116b6d 100644 --- a/hal/tpm_io_infineon.c +++ b/hal/tpm_io_infineon.c @@ -130,6 +130,7 @@ else { printf("CyHAL I2C Write failure %d\n", (int)result); } + TPM2_ForceZero(buf, sizeof(buf)); return ret; } diff --git a/hal/tpm_io_linux.c b/hal/tpm_io_linux.c index 6f969577e..5f798058e 100644 --- a/hal/tpm_io_linux.c +++ b/hal/tpm_io_linux.c @@ -185,6 +185,8 @@ break; } while (--timeout > 0); + TPM2_ForceZero(buf, sizeof(buf)); + return (rc == -1) ? TPM_RC_FAILURE : TPM_RC_SUCCESS; } diff --git a/hal/tpm_io_microchip.c b/hal/tpm_io_microchip.c index 1d0a1a967..fd807450a 100644 --- a/hal/tpm_io_microchip.c +++ b/hal/tpm_io_microchip.c @@ -220,6 +220,7 @@ if (!queued) { printf("error: i2c_write: I2C_BB_Write failed: %d\n", status); + TPM2_ForceZero(buf, sizeof(buf)); return -1; } @@ -242,6 +243,7 @@ else { printf("I2C Write failure %d\n", status); } + TPM2_ForceZero(buf, sizeof(buf)); return ret; } diff --git a/hal/tpm_io_st.c b/hal/tpm_io_st.c index ac9d02550..70eaefca4 100644 --- a/hal/tpm_io_st.c +++ b/hal/tpm_io_st.c @@ -127,6 +127,7 @@ else { printf("I2C Write failure %d\n", status); } + TPM2_ForceZero(buf, sizeof(buf)); return ret; } diff --git a/hal/tpm_io_zephyr.c b/hal/tpm_io_zephyr.c index d07d78131..804a42382 100644 --- a/hal/tpm_io_zephyr.c +++ b/hal/tpm_io_zephyr.c @@ -154,6 +154,7 @@ int TPM2_IoCb_Zephyr_I2C(TPM2_CTX* ctx, int isRead, word32 addr, printf("Failed to write to TPM at register 0x%02X! Error: %d\n", addr, ret); } + TPM2_ForceZero(tempBuf, size + 1); XFREE(tempBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER); } From 6833067ec41f690650e46286809d9825c87d48ca Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 6 Aug 2026 12:59:09 -0700 Subject: [PATCH 06/21] F-7589 - Check I2C busy timeout in Microchip read data and write loops --- hal/tpm_io_microchip.c | 60 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/hal/tpm_io_microchip.c b/hal/tpm_io_microchip.c index fd807450a..9a7299c74 100644 --- a/hal/tpm_io_microchip.c +++ b/hal/tpm_io_microchip.c @@ -67,6 +67,29 @@ static uintptr_t dummy_context; + /* Staging buffers stay at file scope: a busy-timeout bail-out returns + * while the bit-bang engine is still clocking data in or out of them, so + * they must outlive the call that queued the transfer. Reads stage here + * too, so a timeout cannot leave the engine writing into the caller's + * (often stack-local) buffer. */ + static byte i2cRegBuf[1]; + static byte i2cXferBuf[MAX_SPI_FRAMESIZE+1]; + static byte i2cRdBuf[MAX_SPI_FRAMESIZE]; + /* Set when a busy timeout leaves plaintext in a staging buffer that could + * not be scrubbed; cleared by the next call that finds the engine idle. */ + static int i2cXferDirty = 0; + + /* Scrub staging left dirty by a previous busy timeout. Only safe once the + * engine is idle, so callers must check I2C_BB_IsBusy() first. */ + static void i2c_scrub_stale(void) + { + if (i2cXferDirty) { + TPM2_ForceZero(i2cXferBuf, sizeof(i2cXferBuf)); + TPM2_ForceZero(i2cRdBuf, sizeof(i2cRdBuf)); + i2cXferDirty = 0; + } + } + static void dummy_callback(uintptr_t context) { (void) context; @@ -101,12 +124,13 @@ bool queued = false; int timeout = TPM_I2C_TRIES; int busy_retry = TPM_I2C_TRIES; - byte buf[1]; + byte* buf = i2cRegBuf; if (I2C_BB_IsBusy()) { printf("error: i2c_read: already busy\n"); return -1; } + i2c_scrub_stale(); /* TIS layer should never provide a buffer larger than this, but double check for good coding practice */ @@ -119,7 +143,7 @@ do { /* Queue the write with I2C_BB. */ - queued = I2C_BB_Write(TPM2_I2C_ADDR, buf, sizeof(buf)); + queued = I2C_BB_Write(TPM2_I2C_ADDR, buf, sizeof(i2cRegBuf)); if (!queued) { printf("error: i2c_read: I2C_BB_Write failed\n"); @@ -159,7 +183,7 @@ do { /* Queue the read with I2C_BB. */ - queued = I2C_BB_Read(TPM2_I2C_ADDR, data, len); + queued = I2C_BB_Read(TPM2_I2C_ADDR, i2cRdBuf, len); if (!queued) { printf("error: i2c_read: I2C_BB_Read failed\n"); @@ -172,6 +196,14 @@ microchip_wait(250); } + if (I2C_BB_IsBusy()) { + /* Engine still owns i2cRdBuf; it cannot be scrubbed here, so + * flag it for the next call that finds the engine idle */ + i2cXferDirty = 1; + printf("error: i2c_read: busy wait timed out\n"); + return -1; + } + status = I2C_BB_ErrorGet(); if (status == I2CBB_ERROR_NAK) { microchip_wait(250); @@ -179,6 +211,7 @@ } while (status == I2CBB_ERROR_NAK && --timeout > 0); if (status == I2CBB_ERROR_NONE) { + XMEMCPY(data, i2cRdBuf, len); ret = TPM_RC_SUCCESS; } else { @@ -186,6 +219,7 @@ status, TPM_I2C_TRIES - timeout); } + TPM2_ForceZero(i2cRdBuf, sizeof(i2cRdBuf)); return ret; } @@ -196,7 +230,7 @@ bool queued = false; int timeout = TPM_I2C_TRIES; int busy_retry = TPM_I2C_TRIES; - byte buf[MAX_SPI_FRAMESIZE+1]; + byte* buf = i2cXferBuf; /* TIS layer should never provide a buffer larger than this, but double check for good coding practice */ @@ -209,8 +243,11 @@ printf("error: i2c_write: already busy\n"); return -1; } + i2c_scrub_stale(); - /* Build packet with TPM register and data */ + /* Build packet with TPM register and data. The engine is idle here, + * so clear any residue a previous busy-timeout bail-out left */ + TPM2_ForceZero(buf, sizeof(i2cXferBuf)); buf[0] = (reg & 0xFF); /* convert to simple 8-bit address for I2C */ XMEMCPY(buf + 1, data, len); @@ -220,7 +257,7 @@ if (!queued) { printf("error: i2c_write: I2C_BB_Write failed: %d\n", status); - TPM2_ForceZero(buf, sizeof(buf)); + TPM2_ForceZero(buf, sizeof(i2cXferBuf)); return -1; } @@ -230,6 +267,15 @@ microchip_wait(250); } + if (I2C_BB_IsBusy()) { + /* Engine is still clocking out i2cXferBuf, so it cannot be + * scrubbed here; flag it for the next call that finds the + * engine idle */ + i2cXferDirty = 1; + printf("error: i2c_write: busy wait timed out\n"); + return -1; + } + status = I2C_BB_ErrorGet(); if (status == I2CBB_ERROR_NAK) { @@ -243,7 +289,7 @@ else { printf("I2C Write failure %d\n", status); } - TPM2_ForceZero(buf, sizeof(buf)); + TPM2_ForceZero(buf, sizeof(i2cXferBuf)); return ret; } From e5147067440598127327cb8e244c8f70a4c8f7a7 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 6 Aug 2026 12:59:09 -0700 Subject: [PATCH 07/21] F-7594 - Compare PolicyNV operands in constant time --- src/fwtpm/fwtpm_command.c | 68 +++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 049b060c2..eba6212c3 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -541,6 +541,32 @@ static int FwCtAuthCompare(const byte* password, int pwSz, return ((int)diff != 0) ? 1 : 0; } +#ifndef FWTPM_NO_POLICY +/* Constant-time lexicographic comparison of two equal-length big-endian + * byte strings, for the TPM_EO_* relational policy assertions. + * Runs a fixed number of iterations with mask accumulation so the timing + * does not reveal the index of the first differing byte. + * Returns -1 if a < b, 1 if a > b, 0 if equal. */ +static int FwCtRelCompare(const byte* a, const byte* b, int len) +{ + volatile byte gt = 0; + volatile byte eq = 1; + int ci; + + /* Borrow-bit form, not a sign-of-difference shift: the latter is + * canonicalized back into icmp/select, which becomes a real branch on + * cores without conditional execution (ARMv6-M, ARMv8-M Baseline). + * Most significant byte first, so the first difference decides. */ + for (ci = 0; ci < len; ci++) { + gt = (byte)(gt | ((((UINT32)((int)b[ci] - (int)a[ci])) >> 8) & eq)); + eq = (byte)(eq & + (((((UINT32)((int)b[ci] ^ (int)a[ci]))) - 1u) >> 8)); + } + + return ((int)gt + (int)gt + (int)eq) - 1; +} +#endif /* !FWTPM_NO_POLICY */ + /* Compute cpHash = H(commandCode || name1 || ... || cpBuffer) * Per TPM 2.0 Part 1 Section 18.7 */ static int FwComputeCpHash(TPMI_ALG_HASH hashAlg, TPM_CC cmdCode, @@ -10267,28 +10293,20 @@ static TPM_RC FwCmd_PolicyNV(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* Compare operandB with NV data at offset */ byte* nvData = nv->data + offset; int pass = 0; + volatile byte bitDiff = 0; - /* Byte-by-byte comparison for relational operators */ - cmpResult = 0; - for (i = 0; i < (int)operandBSz; i++) { - if (nvData[i] < operandB[i]) { - cmpResult = -1; - break; - } - else if (nvData[i] > operandB[i]) { - cmpResult = 1; - break; - } - } + /* Constant-time comparison for relational operators */ + cmpResult = FwCtRelCompare(nvData, operandB, (int)operandBSz); - /* For signed comparisons, check sign bits (big-endian MSB) */ + /* Signed comparison flips the result when the sign bits differ. + * Selected with masks so the secret MSB is not branched on. */ signedCmpResult = cmpResult; if (operandBSz > 0) { - int nvSign = (nvData[0] & 0x80) ? 1 : 0; - int opSign = (operandB[0] & 0x80) ? 1 : 0; - if (nvSign != opSign) { - signedCmpResult = nvSign ? -1 : 1; - } + int diffSign = (int)(((UINT32)(nvData[0] ^ operandB[0])) >> 7) & 1; + int nvNeg = (int)(((UINT32)nvData[0]) >> 7) & 1; + int signRes = 1 - (2 * nvNeg); /* nv negative => -1, else 1 */ + signedCmpResult = (signRes & -diffSign) | + (cmpResult & ~(-diffSign)); } switch (operation) { @@ -10323,22 +10341,16 @@ static TPM_RC FwCmd_PolicyNV(FWTPM_CTX* ctx, TPM2_Packet* cmd, pass = (cmpResult <= 0); break; case TPM_EO_BITSET: - pass = 1; for (i = 0; i < (int)operandBSz; i++) { - if ((nvData[i] & operandB[i]) != operandB[i]) { - pass = 0; - break; - } + bitDiff |= (byte)((nvData[i] & operandB[i]) ^ operandB[i]); } + pass = ((int)bitDiff == 0); break; case TPM_EO_BITCLEAR: - pass = 1; for (i = 0; i < (int)operandBSz; i++) { - if ((nvData[i] & operandB[i]) != 0) { - pass = 0; - break; - } + bitDiff |= (byte)(nvData[i] & operandB[i]); } + pass = ((int)bitDiff == 0); break; default: rc = TPM_RC_VALUE; From 790a2886675c405bf2180001d4557a2a25392474 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 6 Aug 2026 12:59:09 -0700 Subject: [PATCH 08/21] F-7595 - Compare PolicyCounterTimer operands in constant time --- src/fwtpm/fwtpm_command.c | 37 +++++++++++++------------------------ 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index eba6212c3..157fe6267 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -10892,27 +10892,20 @@ static TPM_RC FwCmd_PolicyCounterTimer(FWTPM_CTX* ctx, TPM2_Packet* cmd, int pass = 0; int cmpResult = 0; int signedCmpResult = 0; + volatile byte bitDiff = 0; int i; - for (i = 0; i < (int)operandBSz; i++) { - if (data[i] < operandB[i]) { - cmpResult = -1; - break; - } - else if (data[i] > operandB[i]) { - cmpResult = 1; - break; - } - } + cmpResult = FwCtRelCompare(data, operandB, (int)operandBSz); - /* For signed comparisons, check sign bits (big-endian MSB) */ + /* Signed comparison flips the result when the sign bits differ. + * Selected with masks so the compared MSB is not branched on. */ signedCmpResult = cmpResult; if (operandBSz > 0) { - int nvSign = (data[0] & 0x80) ? 1 : 0; - int opSign = (operandB[0] & 0x80) ? 1 : 0; - if (nvSign != opSign) { - signedCmpResult = nvSign ? -1 : 1; - } + int diffSign = (int)(((UINT32)(data[0] ^ operandB[0])) >> 7) & 1; + int nvNeg = (int)(((UINT32)data[0]) >> 7) & 1; + int signRes = 1 - (2 * nvNeg); + signedCmpResult = (signRes & -diffSign) | + (cmpResult & ~(-diffSign)); } switch (operation) { @@ -10927,20 +10920,16 @@ static TPM_RC FwCmd_PolicyCounterTimer(FWTPM_CTX* ctx, TPM2_Packet* cmd, case TPM_EO_SIGNED_LE: pass = (signedCmpResult <= 0); break; case TPM_EO_UNSIGNED_LE: pass = (cmpResult <= 0); break; case TPM_EO_BITSET: - pass = 1; for (i = 0; i < (int)operandBSz; i++) { - if ((data[i] & operandB[i]) != operandB[i]) { - pass = 0; break; - } + bitDiff |= (byte)((data[i] & operandB[i]) ^ operandB[i]); } + pass = ((int)bitDiff == 0); break; case TPM_EO_BITCLEAR: - pass = 1; for (i = 0; i < (int)operandBSz; i++) { - if ((data[i] & operandB[i]) != 0) { - pass = 0; break; - } + bitDiff |= (byte)(data[i] & operandB[i]); } + pass = ((int)bitDiff == 0); break; default: rc = TPM_RC_VALUE; From 3ab9027b68d3e9ffab54a9a07d7709affad36397 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 6 Aug 2026 12:59:09 -0700 Subject: [PATCH 09/21] F-7588 - Reject transcript growth without 32-bit addition wraparound --- src/spdm/spdm_transcript.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/spdm/spdm_transcript.c b/src/spdm/spdm_transcript.c index b0215b7e1..05bbd2b2c 100644 --- a/src/spdm/spdm_transcript.c +++ b/src/spdm/spdm_transcript.c @@ -53,7 +53,8 @@ int wolfSPDM_TranscriptAdd(WOLFSPDM_CTX* ctx, const byte* data, word32 len) return WOLFSPDM_E_INVALID_ARG; } - if (ctx->transcriptLen + len > WOLFSPDM_MAX_TRANSCRIPT) { + if (ctx->transcriptLen > WOLFSPDM_MAX_TRANSCRIPT || + len > WOLFSPDM_MAX_TRANSCRIPT - ctx->transcriptLen) { return WOLFSPDM_E_BUFFER_SMALL; } From cc7cb89c11c1bec2f5445aeee38695db06be513f Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 6 Aug 2026 12:59:09 -0700 Subject: [PATCH 10/21] F-7590 - Reserve the NUL terminator when clamping -secrethex= input --- examples/boot/secret_seal.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/boot/secret_seal.c b/examples/boot/secret_seal.c index 94266847a..93d3e3d4d 100644 --- a/examples/boot/secret_seal.c +++ b/examples/boot/secret_seal.c @@ -152,8 +152,8 @@ int TPM2_Boot_SecretSeal_Example(void* userCtx, int argc, char *argv[]) const char* secretStr = argv[argc-1] + XSTRLEN("-secrethex="); word32 secretStrSz = (word32)XSTRLEN(secretStr); int secretHexSz; - if (secretStrSz > (word32)(sizeof(secret)*2-1)) - secretStrSz = (word32)(sizeof(secret)*2-1); + if (secretStrSz > (word32)((sizeof(secret)-1)*2)) + secretStrSz = (word32)((sizeof(secret)-1)*2); secretHexSz = hexToByte(secretStr, secret, secretStrSz); if (secretHexSz < 0) { printf("Invalid secret hex string\n"); From 3f8c84d59582acf638a55ae25deabac913a40e85 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 6 Aug 2026 12:59:09 -0700 Subject: [PATCH 11/21] F-7591 - Cap PCR selection count to the entries actually parsed --- src/tpm2_packet.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/tpm2_packet.c b/src/tpm2_packet.c index b28ffa86f..c4ffe4669 100644 --- a/src/tpm2_packet.c +++ b/src/tpm2_packet.c @@ -571,6 +571,7 @@ void TPM2_Packet_ParsePCR(TPM2_Packet* packet, TPML_PCR_SELECTION* pcr) int i; UINT32 wireCount; UINT32 loopCount; + UINT32 parsedCount; UINT16 hash; UINT8 wireSizeofSelect; TPM2_Packet_ParseU32(packet, &wireCount); @@ -594,7 +595,12 @@ void TPM2_Packet_ParsePCR(TPM2_Packet* packet, TPML_PCR_SELECTION* pcr) else { loopCount = 0; } + /* remaining/3 assumes a zero-length select, so it over-estimates how + * many entries the wire actually carries; count what is really parsed */ + parsedCount = 0; for (i = 0; i < (int)loopCount; i++) { + if (packet == NULL || packet->pos + 3 > packet->size) + break; TPM2_Packet_ParseU16(packet, &hash); TPM2_Packet_ParseU8(packet, &wireSizeofSelect); if (i < (int)pcr->count) { @@ -615,7 +621,10 @@ void TPM2_Packet_ParsePCR(TPM2_Packet* packet, TPML_PCR_SELECTION* pcr) /* Skip entire entry for overflow iterations */ TPM2_Packet_ParseBytes(packet, NULL, wireSizeofSelect); } + parsedCount++; } + if (pcr->count > parsedCount) + pcr->count = parsedCount; /* Skip remaining wire entries beyond the capped loop so packet->pos * stays synchronized with the wire format for subsequent parsing. * Break when the packet is exhausted to avoid spinning on an From 62427d8060d34993af0d90e06b22c80307c18c8f Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 6 Aug 2026 12:59:09 -0700 Subject: [PATCH 12/21] F-7592 - Check BuildPolicyCommandCode status for each policy digest --- examples/nvram/extend.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/examples/nvram/extend.c b/examples/nvram/extend.c index 1601bdd0e..31fba14fd 100644 --- a/examples/nvram/extend.c +++ b/examples/nvram/extend.c @@ -161,7 +161,11 @@ int TPM2_NVRAM_Extend_Example(void* userCtx, int argc, char *argv[]) /* Policy A: TPM2_PolicyCommandCode -> TPM_CC_NV_Read */ /* 47ce3032d8bad1f3089cb0c09088de43501491d460402b90cd1b7fc0b68ca92f */ policy[0] = &policyDigest[policyDigestSz]; - BuildPolicyCommandCode(hashAlg, policy[0], &nvSize, TPM_CC_NV_Read); + rc = BuildPolicyCommandCode(hashAlg, policy[0], &nvSize, TPM_CC_NV_Read); + if (rc != TPM_RC_SUCCESS) { + printf("Building PolicyA failed!\n"); + goto exit; + } printf("PolicyA: %d\n", nvSize); TPM2_PrintBin(policy[0], nvSize); policyDigestSz += nvSize; @@ -169,7 +173,11 @@ int TPM2_NVRAM_Extend_Example(void* userCtx, int argc, char *argv[]) /* Policy B: TPM2_PolicyCommandCode -> TPM_CC_NV_Extend */ /* b6a2e7142ee56fd978047488483daa5b42b8dc4cc7ddcceddfb91793cf1ff1b7 */ policy[1] = &policyDigest[policyDigestSz]; - BuildPolicyCommandCode(hashAlg, policy[1], &nvSize, TPM_CC_NV_Extend); + rc = BuildPolicyCommandCode(hashAlg, policy[1], &nvSize, TPM_CC_NV_Extend); + if (rc != TPM_RC_SUCCESS) { + printf("Building PolicyB failed!\n"); + goto exit; + } printf("PolicyB: %d\n", nvSize); TPM2_PrintBin(policy[1], nvSize); policyDigestSz += nvSize; @@ -177,7 +185,11 @@ int TPM2_NVRAM_Extend_Example(void* userCtx, int argc, char *argv[]) /* Policy C: TPM2_PolicyCommandCode -> TPM_CC_PolicyNV */ /* 203e4bd5d0448c9615cc13fa18e8d39222441cc40204d99a77262068dbd55a43 */ policy[2] = &policyDigest[policyDigestSz]; - BuildPolicyCommandCode(hashAlg, policy[2], &nvSize, TPM_CC_PolicyNV); + rc = BuildPolicyCommandCode(hashAlg, policy[2], &nvSize, TPM_CC_PolicyNV); + if (rc != TPM_RC_SUCCESS) { + printf("Building PolicyC failed!\n"); + goto exit; + } printf("PolicyC: %d\n", nvSize); TPM2_PrintBin(policy[2], nvSize); policyDigestSz += nvSize; From d17fb78806f703abba882b07b30efb15a503f408 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 6 Aug 2026 12:59:09 -0700 Subject: [PATCH 13/21] F-7597 - Clamp shared memory rsp_len to the TIS response FIFO size --- src/fwtpm/fwtpm_tis.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/fwtpm/fwtpm_tis.c b/src/fwtpm/fwtpm_tis.c index 8d26fe2c8..0edfe401f 100644 --- a/src/fwtpm/fwtpm_tis.c +++ b/src/fwtpm/fwtpm_tis.c @@ -314,6 +314,11 @@ static void TisHandleRegAccess(FWTPM_CTX* ctx, FWTPM_TIS_REGS* regs) /* Snapshot read state to locals for TOCTOU safety */ UINT32 rpos = regs->fifo_read_pos; UINT32 rlen = regs->rsp_len; + /* Client-writable shared memory: clamp to FIFO capacity the + * same way the command side clamps localCmdLen */ + if (rlen > (UINT32)sizeof(regs->rsp_buf)) { + rlen = (UINT32)sizeof(regs->rsp_buf); + } /* Zero full reg_data first: client may copy more bytes than * we write here (it uses its originally-requested size, not * our clamped len), so any stale shared-memory bytes would From d3a61f2a61755bbb8d3d837b456a595fe0c4eb89 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 6 Aug 2026 12:59:09 -0700 Subject: [PATCH 14/21] F-7598 - Validate manifest hash size before staging FieldUpgradeStart --- src/tpm2_wrap.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index a4340defa..b5b017095 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -11109,6 +11109,14 @@ static int tpm2_ifx_firmware_start(WOLFTPM2_DEV* dev, TPM_ALG_ID hashAlg, int rc; WOLFTPM2_SESSION tpmSession; + if (dev == NULL || manifest_hash == NULL || manifest_hash_sz == 0 || + manifest_hash_sz > TPM_SHA512_DIGEST_SIZE) { + return BAD_FUNC_ARG; + } + if (manifest_hash_sz != (uint32_t)TPM2_GetHashDigestSize(hashAlg)) { + return BAD_FUNC_ARG; + } + XMEMSET(&tpmSession, 0, sizeof(tpmSession)); rc = wolfTPM2_StartSession(dev, &tpmSession, NULL, NULL, From 28626aaab05c2dae6a7200228543b2e4086d758e Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 6 Aug 2026 12:59:09 -0700 Subject: [PATCH 15/21] F-7601 - Zeroize ML-DSA sign and verify sequence input structs --- src/tpm2_wrap.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index b5b017095..2d870f1d8 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -5547,6 +5547,8 @@ int wolfTPM2_SignSequenceUpdate(WOLFTPM2_DEV* dev, rc = TPM2_SequenceUpdate(&seqUpdateIn); + TPM2_ForceZero(&seqUpdateIn, sizeof(seqUpdateIn)); + return rc; } @@ -5652,6 +5654,8 @@ int wolfTPM2_SignSequenceComplete(WOLFTPM2_DEV* dev, } } + TPM2_ForceZero(&signSeqCompleteIn, sizeof(signSeqCompleteIn)); + return rc; } #endif /* WOLFTPM_MLDSA_SIGN */ @@ -5720,6 +5724,8 @@ int wolfTPM2_VerifySequenceUpdate(WOLFTPM2_DEV* dev, rc = TPM2_SequenceUpdate(&seqUpdateIn); + TPM2_ForceZero(&seqUpdateIn, sizeof(seqUpdateIn)); + return rc; } From edcb84535f628843c0489d01194aa02ea00fd487 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 6 Aug 2026 12:59:09 -0700 Subject: [PATCH 16/21] F-7603 - Zeroize crypto callback hash cache before freeing --- src/tpm2_cryptocb.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tpm2_cryptocb.c b/src/tpm2_cryptocb.c index 0a6500e67..d8cc83804 100644 --- a/src/tpm2_cryptocb.c +++ b/src/tpm2_cryptocb.c @@ -620,6 +620,8 @@ int wolfTPM2_CryptoDevCb(int devId, wc_CryptoInfo* info, void* ctx) hashCtx->handle = 0; /* clear hash handle */ if ((hashFlags & WC_HASH_FLAG_ISCOPY) == 0) { if (hashCtx->cacheBuf) { + TPM2_ForceZero(hashCtx->cacheBuf, + hashCtx->cacheBufSz); XFREE(hashCtx->cacheBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER); hashCtx->cacheBuf = NULL; } @@ -832,6 +834,7 @@ static int wolfTPM2_HashUpdateCache(WOLFTPM2_HASHCTX* hashCtx, return MEMORY_E; } XMEMCPY(hashCtx->cacheBuf, oldIn, hashCtx->cacheSz); + TPM2_ForceZero(oldIn, oldBufSz); XFREE(oldIn, NULL, DYNAMIC_TYPE_TMP_BUFFER); } From 395dc2bbb815aeedf069fea13343ce1fb1f1f306 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 6 Aug 2026 12:59:10 -0700 Subject: [PATCH 17/21] F-7604 - Zeroize response authentication temporaries in TPM2_ResponseProcess --- src/tpm2.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/tpm2.c b/src/tpm2.c index 6c4c8cee5..5b233dbd8 100644 --- a/src/tpm2.c +++ b/src/tpm2.c @@ -378,11 +378,15 @@ int TPM2_ResponseProcess(TPM2_CTX* ctx, TPM2_Packet* packet, int sizeMismatch; int diff; + XMEMSET(&hash, 0, sizeof(hash)); + XMEMSET(&hmac, 0, sizeof(hmac)); + if (expectedHmacSz == 0 || authRsp.hmac.size != expectedHmacSz) { #ifdef DEBUG_WOLFTPM printf("Response HMAC size mismatch! expected=%u got=%u\n", expectedHmacSz, authRsp.hmac.size); #endif + TPM2_ForceZero(&authRsp, sizeof(authRsp)); return TPM_RC_HMAC; } @@ -393,6 +397,8 @@ int TPM2_ResponseProcess(TPM2_CTX* ctx, TPM2_Packet* packet, #ifdef DEBUG_WOLFTPM printf("Error calculating rpHash!\n"); #endif + TPM2_ForceZero(&hash, sizeof(hash)); + TPM2_ForceZero(&authRsp, sizeof(authRsp)); return rc; } @@ -404,6 +410,9 @@ int TPM2_ResponseProcess(TPM2_CTX* ctx, TPM2_Packet* packet, #ifdef DEBUG_WOLFTPM printf("Error calculating response HMAC!\n"); #endif + TPM2_ForceZero(&hmac, sizeof(hmac)); + TPM2_ForceZero(&hash, sizeof(hash)); + TPM2_ForceZero(&authRsp, sizeof(authRsp)); return rc; } @@ -418,8 +427,14 @@ int TPM2_ResponseProcess(TPM2_CTX* ctx, TPM2_Packet* packet, #ifdef DEBUG_WOLFTPM printf("Response HMAC verification failed!\n"); #endif + TPM2_ForceZero(&hmac, sizeof(hmac)); + TPM2_ForceZero(&hash, sizeof(hash)); + TPM2_ForceZero(&authRsp, sizeof(authRsp)); return TPM_RC_HMAC; } + + TPM2_ForceZero(&hmac, sizeof(hmac)); + TPM2_ForceZero(&hash, sizeof(hash)); } /* Save off last known HMAC */ @@ -441,10 +456,13 @@ int TPM2_ResponseProcess(TPM2_CTX* ctx, TPM2_Packet* packet, #ifdef DEBUG_WOLFTPM printf("Response parameter decryption failed\n"); #endif + TPM2_ForceZero(&authRsp, sizeof(authRsp)); return rc; } } } + + TPM2_ForceZero(&authRsp, sizeof(authRsp)); } return rc; From 099de0e64cbff79b79cbb18e41d81f27f8acfacc Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 6 Aug 2026 12:59:10 -0700 Subject: [PATCH 18/21] F-7593 - Check GetNvAttributesTemplate status before modifying attributes --- examples/gpio/gpio_config.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/gpio/gpio_config.c b/examples/gpio/gpio_config.c index 20d791669..d68bcd761 100644 --- a/examples/gpio/gpio_config.c +++ b/examples/gpio/gpio_config.c @@ -404,13 +404,13 @@ int TPM2_GPIO_Config_Example(void* userCtx, int argc, char *argv[]) /* Initial NV attributes */ parent.hndl = TPM_RH_PLATFORM; rc = wolfTPM2_GetNvAttributesTemplate(parent.hndl, &nvAttributes); - /* Add NV attributes required by Nuvoton specification */ - nvAttributes |= (TPMA_NV_PLATFORMCREATE | TPMA_NV_POLICY_DELETE); - nvAttributes |= (TPMA_NV_TPM_NT & (TPM_NT_ORDINARY << 4)); if (rc != TPM_RC_SUCCESS) { printf("Setting NV attributes failed\n"); goto exit; } + /* Add NV attributes required by Nuvoton specification */ + nvAttributes |= (TPMA_NV_PLATFORMCREATE | TPMA_NV_POLICY_DELETE); + nvAttributes |= (TPMA_NV_TPM_NT & (TPM_NT_ORDINARY << 4)); #ifdef DEBUG_WOLFTPM printf("nvAttributes = 0x%8.8X\n", nvAttributes); #endif From f86b78d8363d558bb64f07e1489e9c81207a2494 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 6 Aug 2026 12:59:10 -0700 Subject: [PATCH 19/21] F-7599 - Reject authenticated SPDM records shorter than the length field --- src/spdm/spdm_secured.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/spdm/spdm_secured.c b/src/spdm/spdm_secured.c index 36b8af8af..6544c13b8 100644 --- a/src/spdm/spdm_secured.c +++ b/src/spdm/spdm_secured.c @@ -299,7 +299,11 @@ int wolfSPDM_DecryptInternal(WOLFSPDM_CTX* ctx, /* ----- Parse decrypted payload ----- */ - if (rc == 0) { + if (rc == 0 && cipherLen < 2) { + /* authenticated record too short to hold the application length */ + ret = WOLFSPDM_E_BUFFER_SMALL; + } + else if (rc == 0) { appDataLen = SPDM_Get16LE(decrypted); #ifdef WOLFTPM_SPDM_TCG if (ctx->mode == WOLFSPDM_MODE_NUVOTON || From 954cd33f606afe679aeecae930c0b2dd110121fc Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 6 Aug 2026 12:59:10 -0700 Subject: [PATCH 20/21] F-7600 - Terminate CSR subject components and clear stale bytes --- src/tpm2_wrap.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index 2d870f1d8..24366e481 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -9849,6 +9849,7 @@ static int CSR_Parse_DN(CertName* name, const char* subject) for (i = 0; i < (int)(sizeof(tags) / sizeof(DNTags)); i++) { const char *begin, *end; + char* dst; word32 len = 0; /* find start tag */ begin = XSTRSTR(subject, tags[i].tag); @@ -9865,7 +9866,13 @@ static int CSR_Parse_DN(CertName* name, const char* subject) if (len > CTC_NAME_SIZE-1) { len = CTC_NAME_SIZE-1; /* leave room for null term */ } - XMEMCPY((byte*)name + tags[i].certNameOff, begin, len); + /* Clear only the component being written: SetSubject may be + * called more than once to build a DN, so components absent from + * this subject must survive */ + dst = (char*)name + tags[i].certNameOff; + XMEMSET(dst, 0, CTC_NAME_SIZE); + XMEMCPY(dst, begin, len); + dst[len] = '\0'; } } return rc; From 1401945cd0847262891c39610165324f8083bc9d Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 6 Aug 2026 12:59:10 -0700 Subject: [PATCH 21/21] F-7605 - Zeroize hash context in PCRGetDigest and PolicyHash --- src/tpm2_wrap.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index 24366e481..9200d9c0a 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -10773,6 +10773,7 @@ int wolfTPM2_PCRGetDigest(WOLFTPM2_DEV* dev, TPM_ALG_ID pcrAlg, rc = wc_HashFinal(&hash_ctx, hashType, pcrDigest); } wc_HashFree(&hash_ctx, hashType); + TPM2_ForceZero(&hash_ctx, sizeof(hash_ctx)); #ifdef DEBUG_WOLFTPM if (rc != 0) { @@ -10839,6 +10840,7 @@ int wolfTPM2_PolicyHash(TPM_ALG_ID hashAlg, rc = wc_HashFinal(&hash_ctx, hashType, digest); } wc_HashFree(&hash_ctx, hashType); + TPM2_ForceZero(&hash_ctx, sizeof(hash_ctx)); #ifdef DEBUG_WOLFTPM if (rc != 0) {