From d1ac90a1508b68a88a58cebfaf2ff26f6a758c4b Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 08:51:00 -0700 Subject: [PATCH 01/57] F-14018 - Make Clear seed replacement atomic --- src/fwtpm/fwtpm_command.c | 50 ++++++++++++++++++++++++++------------- tests/fwtpm_unit_tests.c | 49 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 17 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 03449e78c..bc13eb83e 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -5593,12 +5593,33 @@ static TPM_RC FwCmd_ReadPublic(FWTPM_CTX* ctx, TPM2_Packet* cmd, return rc; } +#ifdef WOLFTPM_FWTPM_UNIT_TEST +static int gFwTestClearRngFailure; + +void FWTPM_TestSetClearRngFailure(int enabled); + +void FWTPM_TestSetClearRngFailure(int enabled) +{ + gFwTestClearRngFailure = enabled; +} +#endif + +static int FwGenerateClearSeed(WC_RNG* rng, byte* seed, word32 seedSz) +{ +#ifdef WOLFTPM_FWTPM_UNIT_TEST + if (gFwTestClearRngFailure) + return TPM_RC_FAILURE; +#endif + return wc_RNG_GenerateBlock(rng, seed, seedSz); +} + /* --- TPM2_Clear (CC 0x0126) --- */ static TPM_RC FwCmd_Clear(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, TPM2_Packet* rsp, UINT16 cmdTag) { TPM_RC rc = TPM_RC_SUCCESS; UINT32 authHandle = 0; + byte newOwnerSeed[FWTPM_SEED_SIZE]; int ci; if (cmdSize < TPM2_HEADER_SIZE + 4) { @@ -5628,22 +5649,21 @@ static TPM_RC FwCmd_Clear(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, printf("fwTPM: Clear(authHandle=0x%x)\n", authHandle); #endif - /* Defer object flush until after response auth is computed. - * Sessions must remain valid for response HMAC generation. - * Per TPM spec, Clear flushes transient objects but not sessions. */ - ctx->pendingClear = 1; - - /* Reset owner and endorsement auth */ - XMEMSET(&ctx->ownerAuth, 0, sizeof(ctx->ownerAuth)); - XMEMSET(&ctx->endorsementAuth, 0, sizeof(ctx->endorsementAuth)); - /* Generate new storage primary seed */ - rc = wc_RNG_GenerateBlock(&ctx->rng, ctx->ownerSeed, FWTPM_SEED_SIZE); + rc = FwGenerateClearSeed(&ctx->rng, newOwnerSeed, + sizeof(newOwnerSeed)); if (rc != 0) rc = TPM_RC_FAILURE; - /* Only commit state changes if seed generation succeeded — - * avoid partial mutation on RNG failure */ + /* Only commit state changes if seed generation succeeded. */ if (rc == 0) { + /* Defer object flush until after response auth is computed. + * Sessions must remain valid for response HMAC generation. + * Per TPM spec, Clear flushes transient objects but not sessions. */ + ctx->pendingClear = 1; + + XMEMSET(&ctx->ownerAuth, 0, sizeof(ctx->ownerAuth)); + XMEMSET(&ctx->endorsementAuth, 0, sizeof(ctx->endorsementAuth)); + XMEMCPY(ctx->ownerSeed, newOwnerSeed, sizeof(ctx->ownerSeed)); ctx->endorsementProofVersion = 1; /* Flush owner and endorsement primary cache entries after * clearing hierarchy state. */ @@ -5723,15 +5743,11 @@ static TPM_RC FwCmd_Clear(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, (void)FWTPM_NV_SaveAuth(ctx, TPM_RH_LOCKOUT); (void)FWTPM_NV_SaveFlags(ctx); #endif - FwRspNoParams(rsp, cmdTag); } - else { - /* RNG failure: undo pendingClear so state is not corrupted */ - ctx->pendingClear = 0; - } } + TPM2_ForceZero(newOwnerSeed, sizeof(newOwnerSeed)); return rc; } diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index 47c81fab9..560d51c0d 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -13928,6 +13928,46 @@ static void test_fwtpm_clear(void) fwtpm_pass("Clear preserves endorsement seed:", 0); } +#ifdef WOLFTPM_FWTPM_UNIT_TEST +void FWTPM_TestSetClearRngFailure(int enabled); + +/* A failed seed replacement must leave the live hierarchy state unchanged. */ +static void test_fwtpm_clear_rng_failure_atomic(void) +{ + FWTPM_CTX ctx; + TPM2B_AUTH ownerAuth; + TPM2B_AUTH endorsementAuth; + byte ownerSeed[FWTPM_SEED_SIZE]; + + XMEMSET(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), TPM_RC_SUCCESS); + ctx.ownerAuth.size = 4; + XMEMSET(ctx.ownerAuth.buffer, 0xA5, ctx.ownerAuth.size); + ctx.endorsementAuth.size = 4; + XMEMSET(ctx.endorsementAuth.buffer, 0x5A, ctx.endorsementAuth.size); + XMEMCPY(&ownerAuth, &ctx.ownerAuth, sizeof(ownerAuth)); + XMEMCPY(&endorsementAuth, &ctx.endorsementAuth, + sizeof(endorsementAuth)); + XMEMCPY(ownerSeed, ctx.ownerSeed, sizeof(ownerSeed)); + + FWTPM_TestSetClearRngFailure(1); + AssertIntEQ(SendSimpleSessionCmd(&ctx, TPM_CC_Clear, TPM_RH_LOCKOUT), + TPM_RC_FAILURE); + FWTPM_TestSetClearRngFailure(0); + AssertIntEQ(ctx.pendingClear, 0); + AssertIntEQ(XMEMCMP(&ctx.ownerAuth, &ownerAuth, sizeof(ownerAuth)), 0); + AssertIntEQ(XMEMCMP(&ctx.endorsementAuth, &endorsementAuth, + sizeof(endorsementAuth)), 0); + AssertIntEQ(XMEMCMP(ctx.ownerSeed, ownerSeed, sizeof(ownerSeed)), 0); + + TPM2_ForceZero(&ownerAuth, sizeof(ownerAuth)); + TPM2_ForceZero(&endorsementAuth, sizeof(endorsementAuth)); + TPM2_ForceZero(ownerSeed, sizeof(ownerSeed)); + FWTPM_Cleanup(&ctx); + fwtpm_pass("Clear RNG failure is atomic:", 0); +} +#endif /* WOLFTPM_FWTPM_UNIT_TEST */ + #ifndef FWTPM_NO_NV static int fail_nv_write(void* ctx, word32 offset, const byte* buf, word32 size) @@ -17572,6 +17612,12 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_clear(); return 0; } + #ifdef WOLFTPM_FWTPM_UNIT_TEST + if (argc == 2 && XSTRCMP(argv[1], "clear-rng-failure") == 0) { + test_fwtpm_clear_rng_failure_atomic(); + return 0; + } + #endif /* WOLFTPM_FWTPM_UNIT_TEST */ #if !defined(FWTPM_NO_POLICY) && !defined(NO_RSA) && \ defined(WOLFSSL_KEY_GEN) if (argc == 2 && XSTRCMP(argv[1], "clear-child") == 0) { @@ -18030,6 +18076,9 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_nv_delete_at_capacity(); test_fwtpm_nv_stclear_startup(); #endif /* !FWTPM_NO_NV */ + #ifdef WOLFTPM_FWTPM_UNIT_TEST + test_fwtpm_clear_rng_failure_atomic(); + #endif /* WOLFTPM_FWTPM_UNIT_TEST */ test_fwtpm_clear(); #if defined(WOLFTPM_FWTPM_UNIT_TEST) && !defined(_WIN32) From 92f42c754639f8d9f208e10ca04d480fa8c47fa4 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 08:55:49 -0700 Subject: [PATCH 02/57] F-14020 - Roll back failed global write lock --- src/fwtpm/fwtpm_command.c | 3 +++ tests/fwtpm_unit_tests.c | 29 +++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index bc13eb83e..7c22f4ca6 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -14744,6 +14744,7 @@ static TPM_RC FwCmd_NV_GlobalWriteLock(FWTPM_CTX* ctx, TPM2_Packet* cmd, } if (rc == 0) { + int oldGlobalNvWriteLock = ctx->globalNvWriteLock; #ifdef DEBUG_WOLFTPM printf("fwTPM: NV_GlobalWriteLock(auth=0x%x)\n", authHandle); #endif @@ -14752,6 +14753,8 @@ static TPM_RC FwCmd_NV_GlobalWriteLock(FWTPM_CTX* ctx, TPM2_Packet* cmd, rc = FWTPM_NV_SaveFlags(ctx); if (rc == 0) FwRspNoParams(rsp, cmdTag); + else + ctx->globalNvWriteLock = oldGlobalNvWriteLock; } return rc; diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index 560d51c0d..0b351ffe5 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -13979,6 +13979,30 @@ static int fail_nv_write(void* ctx, word32 offset, const byte* buf, return TPM_RC_FAILURE; } +/* A failed global-lock persist must leave the live lock state unchanged. */ +static void test_fwtpm_global_write_lock_nv_failure(void) +{ + FWTPM_CTX ctx; + FWTPM_NV_HAL oldHal, failHal; + + XMEMSET(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), TPM_RC_SUCCESS); + AssertIntEQ(ctx.globalNvWriteLock, 0); + + oldHal = ctx.nvHal; + failHal = oldHal; + failHal.write = fail_nv_write; + AssertIntEQ(FWTPM_NV_SetHAL(&ctx, &failHal), TPM_RC_SUCCESS); + + AssertIntEQ(SendSimpleSessionCmd(&ctx, TPM_CC_NV_GlobalWriteLock, + TPM_RH_OWNER), TPM_RC_FAILURE); + AssertIntEQ(ctx.globalNvWriteLock, 0); + + AssertIntEQ(FWTPM_NV_SetHAL(&ctx, &oldHal), TPM_RC_SUCCESS); + FWTPM_Cleanup(&ctx); + fwtpm_pass("GlobalWriteLock NV failure rollback:", 0); +} + /* ClearControl must not change disableClear or return success when its NV * update fails. */ static void test_fwtpm_clear_control_nv_failure(void) @@ -17632,6 +17656,10 @@ int fwtpm_unit_tests(int argc, char *argv[]) #endif #endif #ifndef FWTPM_NO_NV + if (argc == 2 && XSTRCMP(argv[1], "global-write-lock-failure") == 0) { + test_fwtpm_global_write_lock_nv_failure(); + return 0; + } if (argc == 2 && XSTRCMP(argv[1], "nv-stclear") == 0) { test_fwtpm_nv_stclear_startup(); return 0; @@ -18069,6 +18097,7 @@ int fwtpm_unit_tests(int argc, char *argv[]) #endif /* !FWTPM_NO_HASH_CMDS */ test_fwtpm_sessions_missing_authsize_command_size(); #ifndef FWTPM_NO_NV + test_fwtpm_global_write_lock_nv_failure(); test_fwtpm_clear_control_nv_failure(); test_fwtpm_state_change_nv_failure(); test_fwtpm_nv_append_atomic(); From e42d9d45af5e9870f8b05ef0b8bfc1a301160d52 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 09:05:59 -0700 Subject: [PATCH 03/57] F-14021 - Propagate session crypto failures --- src/fwtpm/fwtpm_command.c | 78 +++++++++++++++++++++++++++------------ tests/fwtpm_unit_tests.c | 2 +- 2 files changed, 56 insertions(+), 24 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 7c22f4ca6..f2d9be2b9 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -118,6 +118,7 @@ static FWTPM_HashSeq* FwFindHashSeq(FWTPM_CTX* ctx, TPM_HANDLE handle); static int FwGetCmdCount(void); static TPM_CC FwGetCmdCcAt(int idx); static UINT32 FwGetCmdAttrsAt(int idx); + /* --- Response helpers using TPM2_Packet --- */ /* Initialize a response packet on the given buffer */ @@ -19564,6 +19565,7 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, int authFail; /* Password comparison result */ int rspCap; /* Caller's response buffer capacity */ int rspTruncated = 0; /* Response did not fit the buffer */ + int rspCryptoFailure = 0; /* No valid session response was built */ FWTPM_Session* pendingFlushSess = NULL; if (ctx == NULL || cmdBuf == NULL || rspBuf == NULL || rspSize == NULL) { @@ -20457,11 +20459,19 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, } /* Compute expected command HMAC (nonceCaller first, then nonceTPM) */ - FwComputeSessionHmac(hSess, cpHash, cpHashSz, + rc = (TPM_RC)FwComputeSessionHmac(hSess, cpHash, cpHashSz, cmdAuths[hj].attributes, authVal, authValSz, nonceDecrypt, nonceEncrypt, 0, /* isResponse=0 for command HMAC */ expectedHmac, &expectedSz); + if (rc != TPM_RC_SUCCESS) { + wc_ForceZero(cpHash, sizeof(cpHash)); + wc_ForceZero(expectedHmac, sizeof(expectedHmac)); + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, + TPM_ST_NO_SESSIONS, TPM_RC_FAILURE); + wc_ForceZero(cmdAuths, sizeof(cmdAuths)); + return TPM_RC_SUCCESS; + } /* Always run TPM2_ConstantCompare so timing doesn't leak size */ sizeMismatch = (cmdAuths[hj].cmdHmacSize != (UINT16)expectedSz); @@ -20586,10 +20596,6 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, int rpHashSz = 0; const byte* rpBytes = NULL; int rpBytesSz = 0; - /* previous nonceTPM per session, restored if the response overflows - * (66 bytes each, 3 sessions max) */ - TPM2B_NONCE savedNonce[FWTPM_MAX_CMD_AUTHS]; - /* Read parameterSize from response buffer */ if (rspHandleEnd + 4 <= rspPkt.pos) { rspParamSzVal = (UINT32)( @@ -20603,13 +20609,11 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, rspParamEnd = rspParamStart + (int)rspParamSzVal; #endif - /* New nonceTPM before response encryption, saving the old one */ - for (j = 0; j < cmdAuthCnt; j++) { + /* Generate the new nonceTPM values before response encryption. */ + for (j = 0; j < cmdAuthCnt && !rspCryptoFailure; j++) { if (cmdAuths[j].sess != NULL) { FWTPM_Session* sess = cmdAuths[j].sess; int digestSz = TPM2_GetHashDigestSize(sess->authHash); - XMEMCPY(&savedNonce[j], &sess->nonceTPM, - sizeof(savedNonce[j])); if (digestSz > 0) { rngRc = wc_RNG_GenerateBlock(&ctx->rng, sess->nonceTPM.buffer, digestSz); @@ -20617,15 +20621,19 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, sess->nonceTPM.size = digestSz; } else { - sess->nonceTPM.size = 0; + rspCryptoFailure = 1; } } + else { + rspCryptoFailure = 1; + } } } #ifndef FWTPM_NO_PARAM_ENC /* Response parameter encryption (encrypt first TPM2B param) */ - if (doEncRsp && encSess != NULL && rspParamSzVal > 2) { + if (!rspCryptoFailure && doEncRsp && encSess != NULL && + rspParamSzVal > 2) { UINT16 firstParamSz; firstParamSz = FwLoadU16BE(rspBuf + rspParamStart); if (firstParamSz > 0 && @@ -20636,6 +20644,7 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, #ifdef DEBUG_WOLFTPM printf("fwTPM: ParamEncrypt failed %d\n", encRc); #endif + rspCryptoFailure = 1; } } } @@ -20649,7 +20658,7 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, /* Append auth area: one entry per command auth session. * rpHash is computed per-session using each session's authHash, * per TPM 2.0 Part 1 Section 19.6.5. */ - for (j = 0; j < cmdAuthCnt; j++) { + for (j = 0; j < cmdAuthCnt && !rspCryptoFailure; j++) { UINT8 rspAttribs = cmdAuths[j].attributes & TPMA_SESSION_continueSession; #ifndef FWTPM_NO_PARAM_ENC @@ -20674,6 +20683,9 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, int digestSz = TPM2_GetHashDigestSize(sess->authHash); byte hmacBuf[TPM_MAX_DIGEST_SIZE]; int hmacSz = 0; + int cryptoRc; + + XMEMSET(hmacBuf, 0, sizeof(hmacBuf)); /* Append nonceTPM */ TPM2_Packet_AppendU16(&rspPkt, sess->nonceTPM.size); @@ -20693,8 +20705,14 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, /* Compute rpHash for this session's hash algorithm */ rpHashSz = 0; - FwComputeRpHash(sess->authHash, (TPM_CC)cmdCode, + cryptoRc = FwComputeRpHash(sess->authHash, + (TPM_CC)cmdCode, rpBytes, rpBytesSz, rpHash, &rpHashSz); + if (cryptoRc != 0) { + rspCryptoFailure = 1; + wc_ForceZero(hmacBuf, sizeof(hmacBuf)); + break; + } /* Compute and append response HMAC. * authValue is the auth of the j-th command entity handle, @@ -20716,29 +20734,43 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, authVal = NULL; authValSz = 0; } - FwComputeSessionHmac(sess, rpHash, rpHashSz, rspAttribs, + cryptoRc = FwComputeSessionHmac(sess, rpHash, rpHashSz, + rspAttribs, authVal, authValSz, NULL, NULL, 1 /* isResponse */, hmacBuf, &hmacSz); + if (cryptoRc != 0) { + rspCryptoFailure = 1; + wc_ForceZero(hmacBuf, sizeof(hmacBuf)); + break; + } } TPM2_Packet_AppendU16(&rspPkt, (UINT16)hmacSz); if (hmacSz > 0) { TPM2_Packet_AppendBytes(&rspPkt, hmacBuf, hmacSz); } + wc_ForceZero(hmacBuf, sizeof(hmacBuf)); } } + wc_ForceZero(rpHash, sizeof(rpHash)); - /* Auth area is appended after the handler ran, so it can overrun a - * buffer the parameters alone fit in. Restore the nonces: the client - * never received the new ones. */ - if (rspPkt.overflow) { + /* The handler has already committed its state. If the authenticated + * response cannot be delivered, invalidate its sessions so the + * command cannot be replayed with an old nonce. */ + if (rspCryptoFailure || rspPkt.overflow) { + rspCryptoFailure = 1; for (j = 0; j < cmdAuthCnt; j++) { if (cmdAuths[j].sess != NULL) { - XMEMCPY(&cmdAuths[j].sess->nonceTPM, &savedNonce[j], - sizeof(savedNonce[j])); + if (cmdAuths[j].sess == pendingFlushSess) { + pendingFlushSess = NULL; + } + FwFreeSession(cmdAuths[j].sess); + cmdAuths[j].sess = NULL; } } - *rspSize = FwBuildErrorResponse(rspBuf, rspCap, - TPM_ST_NO_SESSIONS, TPM_RC_SIZE); + if (rspPkt.pos > 0 && rspPkt.pos <= rspCap) { + wc_ForceZero(rspBuf, (word32)rspPkt.pos); + } + *rspSize = 0; } else { /* Finalize response header */ @@ -20777,7 +20809,7 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, } TPM2_ForceZero(cmdAuths, sizeof(cmdAuths)); - return TPM_RC_SUCCESS; + return rspCryptoFailure ? TPM_RC_FAILURE : TPM_RC_SUCCESS; } #endif /* WOLFTPM_FWTPM */ diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index 0b351ffe5..e0af4b019 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -15714,6 +15714,7 @@ static void test_fwtpm_param_enc_session_bound_to_auth(void) FWTPM_Cleanup(&ctx); fwtpm_pass("Param-enc session bound to auth HMAC:", 0); } + #endif /* !FWTPM_NO_PARAM_ENC && FWTPM_MAX_SESSIONS >= 2 */ #endif /* !FWTPM_NO_HASH_CMDS */ @@ -17671,7 +17672,6 @@ int fwtpm_unit_tests(int argc, char *argv[]) return 0; } #endif - if (argc == 2 && XSTRCMP(argv[1], "evict-auth") == 0) { test_fwtpm_evict_control(); return 0; From baf23de8de334e3ff4edb3cb1db7a9040936c6ff Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 09:17:05 -0700 Subject: [PATCH 04/57] F-14022 - Preserve small-stack allocation errors --- src/fwtpm/fwtpm_command.c | 46 ++++++++---- tests/fwtpm_unit_tests.c | 137 ++++++++++++++++++++++++++++++++++ wolftpm/fwtpm/fwtpm_command.h | 21 ++++++ 3 files changed, 189 insertions(+), 15 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index f2d9be2b9..ce29ac84a 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -511,7 +511,7 @@ static int FwParamEncryptRsp(FWTPM_CTX* ctx, FWTPM_Session* sess, * when wc_HashInit succeeded, so a failing Init cannot corrupt an * uninitialized context. Either input pointer may be NULL when its length * is zero. */ -static int FwHashOneShot(TPMI_ALG_HASH hashAlg, +WOLFTPM_TEST_VIS int FwHashOneShot(TPMI_ALG_HASH hashAlg, const byte* d1, word32 d1Sz, const byte* d2, word32 d2Sz, byte* digestOut) @@ -519,7 +519,7 @@ static int FwHashOneShot(TPMI_ALG_HASH hashAlg, FWTPM_DECLARE_VAR(hashCtx, wc_HashAlg); enum wc_HashType wcHash = FwGetWcHashType(hashAlg); int dSize = TPM2_GetHashDigestSize(hashAlg); - int rc; + int rc = TPM_RC_SUCCESS; int initOk = 0; if (dSize <= 0) @@ -527,7 +527,8 @@ static int FwHashOneShot(TPMI_ALG_HASH hashAlg, FWTPM_ALLOC_VAR(hashCtx, wc_HashAlg); - rc = wc_HashInit(hashCtx, wcHash); + if (rc == 0) + rc = wc_HashInit(hashCtx, wcHash); if (rc == 0) { initOk = 1; if (d1 != NULL && d1Sz > 0) @@ -548,7 +549,7 @@ static int FwHashOneShot(TPMI_ALG_HASH hashAlg, /* Compute rpHash = H(responseCode=0 || commandCode || responseParameters) * Per TPM 2.0 spec Part 1 Section 18.8 */ -static int FwComputeRpHash(TPMI_ALG_HASH hashAlg, TPM_CC cmdCode, +WOLFTPM_TEST_VIS int FwComputeRpHash(TPMI_ALG_HASH hashAlg, TPM_CC cmdCode, const byte* rpBytes, int rpSize, byte* hashOut, int* hashOutSz) { FWTPM_DECLARE_VAR(hashCtx, wc_HashAlg); @@ -556,14 +557,18 @@ static int FwComputeRpHash(TPMI_ALG_HASH hashAlg, TPM_CC cmdCode, int dSize = TPM2_GetHashDigestSize(hashAlg); UINT32 rcZero = 0; /* responseCode is always 0 for success HMAC */ UINT32 ccSwap; - int rc; + int rc = TPM_RC_SUCCESS; + int initOk = 0; if (dSize <= 0) return TPM_RC_FAILURE; FWTPM_ALLOC_VAR(hashCtx, wc_HashAlg); - rc = wc_HashInit(hashCtx, wcHash); + if (rc == 0) { + rc = wc_HashInit(hashCtx, wcHash); + initOk = (rc == 0); + } /* responseCode = 0 (success) - in native byte order like client */ if (rc == 0) @@ -585,7 +590,8 @@ static int FwComputeRpHash(TPMI_ALG_HASH hashAlg, TPM_CC cmdCode, if (rc == 0) *hashOutSz = dSize; - wc_HashFree(hashCtx, wcHash); + if (initOk) + wc_HashFree(hashCtx, wcHash); FWTPM_FREE_VAR(hashCtx); return rc; } @@ -785,7 +791,7 @@ static int FwCtRelCompare(const byte* a, const byte* b, int len) /* 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, +WOLFTPM_TEST_VIS int FwComputeCpHash(TPMI_ALG_HASH hashAlg, TPM_CC cmdCode, const byte* cmdBuf, int cmdSize, const TPM_HANDLE* handles, int handleCnt, FWTPM_CTX* ctx, int cpStart, @@ -795,15 +801,19 @@ static int FwComputeCpHash(TPMI_ALG_HASH hashAlg, TPM_CC cmdCode, enum wc_HashType wcHash = FwGetWcHashType(hashAlg); int dSize = TPM2_GetHashDigestSize(hashAlg); UINT32 ccSwap; - int rc; + int rc = TPM_RC_SUCCESS; int i; + int initOk = 0; if (dSize <= 0) return TPM_RC_FAILURE; FWTPM_ALLOC_VAR(hashCtx, wc_HashAlg); - rc = wc_HashInit(hashCtx, wcHash); + if (rc == 0) { + rc = wc_HashInit(hashCtx, wcHash); + initOk = (rc == 0); + } /* commandCode in big-endian */ if (rc == 0) { @@ -832,7 +842,8 @@ static int FwComputeCpHash(TPMI_ALG_HASH hashAlg, TPM_CC cmdCode, if (rc == 0) *hashOutSz = dSize; - wc_HashFree(hashCtx, wcHash); + if (initOk) + wc_HashFree(hashCtx, wcHash); FWTPM_FREE_VAR(hashCtx); return rc; } @@ -841,7 +852,7 @@ static int FwComputeCpHash(TPMI_ALG_HASH hashAlg, TPM_CC cmdCode, * Command: HMAC(key, cpHash || nonceCaller || nonceTPM || attrs) * Response: HMAC(key, rpHash || nonceTPM || nonceCaller || attrs) * The isResponse flag controls nonce order. */ -static int FwComputeSessionHmac(FWTPM_Session* sess, +WOLFTPM_TEST_VIS int FwComputeSessionHmac(FWTPM_Session* sess, const byte* pHash, int pHashSz, UINT8 sessionAttributes, const byte* authValue, int authValueSz, const TPM2B_NONCE* nonceDecrypt, const TPM2B_NONCE* nonceEncrypt, @@ -853,7 +864,8 @@ static int FwComputeSessionHmac(FWTPM_Session* sess, int dSize = TPM2_GetHashDigestSize(sess->authHash); byte hmacKey[TPM_MAX_DIGEST_SIZE * 2]; /* sessionKey || authValue */ int hmacKeySz = 0; - int rc; + int rc = TPM_RC_SUCCESS; + int initOk = 0; if (dSize <= 0) return TPM_RC_FAILURE; @@ -872,7 +884,10 @@ static int FwComputeSessionHmac(FWTPM_Session* sess, FWTPM_CALLOC_VAR(hmac, Hmac); - rc = wc_HmacInit(hmac, NULL, INVALID_DEVID); + if (rc == 0) { + rc = wc_HmacInit(hmac, NULL, INVALID_DEVID); + initOk = (rc == 0); + } if (rc == 0) { rc = wc_HmacSetKey(hmac, (int)hashType, @@ -920,7 +935,8 @@ static int FwComputeSessionHmac(FWTPM_Session* sess, *hmacOutSz = dSize; TPM2_ForceZero(hmacKey, sizeof(hmacKey)); - wc_HmacFree(hmac); + if (initOk) + wc_HmacFree(hmac); FWTPM_FREE_VAR(hmac); return rc; } diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index e0af4b019..bbbc60d90 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -47,6 +47,10 @@ #include +#if defined(WOLFTPM_FWTPM_UNIT_TEST) && defined(WOLFTPM_SMALL_STACK) +#include +#endif + #include #include @@ -15718,6 +15722,129 @@ static void test_fwtpm_param_enc_session_bound_to_auth(void) #endif /* !FWTPM_NO_PARAM_ENC && FWTPM_MAX_SESSIONS >= 2 */ #endif /* !FWTPM_NO_HASH_CMDS */ +#if defined(WOLFTPM_FWTPM_UNIT_TEST) && defined(WOLFTPM_SMALL_STACK) +static wolfSSL_Malloc_cb gFwtpmTestMalloc; +static int gFwtpmTestAllocCount; +static int gFwtpmTestFailAllocAt; + +static void* test_fwtpm_fail_malloc(size_t size +#ifdef WOLFSSL_STATIC_MEMORY + , void* heap, int type +#endif +#ifdef WOLFSSL_DEBUG_MEMORY + , const char* func, unsigned int line +#endif +) +{ + gFwtpmTestAllocCount++; + if (gFwtpmTestFailAllocAt > 0 && + gFwtpmTestAllocCount >= gFwtpmTestFailAllocAt) { + return NULL; + } +#ifdef WOLFSSL_STATIC_MEMORY + #ifdef WOLFSSL_DEBUG_MEMORY + return gFwtpmTestMalloc(size, heap, type, func, line); + #else + return gFwtpmTestMalloc(size, heap, type); + #endif +#else + #ifdef WOLFSSL_DEBUG_MEMORY + return gFwtpmTestMalloc(size, func, line); + #else + return gFwtpmTestMalloc(size); + #endif +#endif +} + +/* Small-stack helper allocations must preserve the documented memory error. */ +static void test_fwtpm_command_crypto_allocation_failure(void) +{ + wolfSSL_Malloc_cb oldMalloc; + wolfSSL_Free_cb oldFree; + wolfSSL_Realloc_cb oldRealloc; + FWTPM_CTX ctx; + FWTPM_Session sess; + TPM_HANDLE handles[1]; + byte data[TPM2_HEADER_SIZE]; + byte digest[TPM_MAX_DIGEST_SIZE]; + int digestSz = 0; +#ifndef FWTPM_NO_POLICY + UINT32 sessH; + int authStart; + int pos; + int rspSize; + int rc; + int i; +#endif + + XMEMSET(&ctx, 0, sizeof(ctx)); + XMEMSET(&sess, 0, sizeof(sess)); + XMEMSET(handles, 0, sizeof(handles)); + XMEMSET(data, 0, sizeof(data)); + XMEMSET(digest, 0, sizeof(digest)); + sess.authHash = TPM_ALG_SHA256; + + AssertIntEQ(wolfSSL_GetAllocators(&oldMalloc, &oldFree, &oldRealloc), 0); + gFwtpmTestMalloc = oldMalloc; + gFwtpmTestAllocCount = 0; + gFwtpmTestFailAllocAt = 1; + AssertIntEQ(wolfSSL_SetAllocators(test_fwtpm_fail_malloc, oldFree, + oldRealloc), 0); + AssertIntEQ(FwHashOneShot(TPM_ALG_SHA256, data, sizeof(data), NULL, + 0, digest), TPM_RC_MEMORY); + AssertIntEQ(FwComputeRpHash(TPM_ALG_SHA256, TPM_CC_GetRandom, + data, sizeof(data), digest, &digestSz), TPM_RC_MEMORY); + AssertIntEQ(FwComputeCpHash(TPM_ALG_SHA256, TPM_CC_GetRandom, + data, sizeof(data), handles, 0, &ctx, 0, digest, &digestSz), + TPM_RC_MEMORY); + AssertIntEQ(FwComputeSessionHmac(&sess, data, sizeof(data), 0, NULL, + 0, NULL, NULL, 0, digest, &digestSz), TPM_RC_MEMORY); + AssertIntEQ(wolfSSL_SetAllocators(oldMalloc, oldFree, oldRealloc), 0); + +#ifndef FWTPM_NO_POLICY + XMEMSET(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), TPM_RC_SUCCESS); + sessH = StartSessionHelper(&ctx, TPM_SE_POLICY); + AssertIntNE(sessH, 0); + + pos = BuildCmdHeader(gCmd, TPM_ST_SESSIONS, 0, + TPM_CC_PolicyGetDigest); + PutU32BE(gCmd + pos, sessH); pos += 4; + authStart = pos; + pos += 4; + PutU32BE(gCmd + pos, sessH); pos += 4; + PutU16BE(gCmd + pos, (UINT16)sizeof(data)); pos += 2; + XMEMCPY(gCmd + pos, data, sizeof(data)); pos += sizeof(data); + gCmd[pos++] = TPMA_SESSION_continueSession; + PutU16BE(gCmd + pos, 0); pos += 2; + PutU32BE(gCmd + authStart, (UINT32)(pos - authStart - 4)); + PutU32BE(gCmd + 2, (UINT32)pos); + + XMEMSET(gRsp, 0xA5, 64); + gFwtpmTestAllocCount = 0; + gFwtpmTestFailAllocAt = 1; + AssertIntEQ(wolfSSL_SetAllocators(test_fwtpm_fail_malloc, oldFree, + oldRealloc), 0); + rspSize = FWTPM_MAX_COMMAND_SIZE; + rc = FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(wolfSSL_SetAllocators(oldMalloc, oldFree, oldRealloc), 0); + AssertIntEQ(rc, TPM_RC_FAILURE); + AssertIntEQ(rspSize, 0); + for (i = 0; i < TPM2_HEADER_SIZE + 4 + 2 + WC_SHA256_DIGEST_SIZE; i++) { + AssertIntEQ(gRsp[i], 0); + } + + rspSize = FWTPM_MAX_COMMAND_SIZE; + AssertIntEQ(FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0), + TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_REFERENCE_S0); + FWTPM_Cleanup(&ctx); +#endif + + fwtpm_pass("Command crypto allocation failures:", 0); +} +#endif /* WOLFTPM_FWTPM_UNIT_TEST && WOLFTPM_SMALL_STACK */ + #if defined(HAVE_ECC) && !defined(FWTPM_NO_ECDH) static void test_fwtpm_ecc_parameters(void) { @@ -17672,6 +17799,13 @@ int fwtpm_unit_tests(int argc, char *argv[]) return 0; } #endif +#if defined(WOLFTPM_FWTPM_UNIT_TEST) && defined(WOLFTPM_SMALL_STACK) + if (argc == 2 && XSTRCMP(argv[1], "command-crypto-allocation") == 0) { + test_fwtpm_command_crypto_allocation_failure(); + return 0; + } +#endif + if (argc == 2 && XSTRCMP(argv[1], "evict-auth") == 0) { test_fwtpm_evict_control(); return 0; @@ -17960,6 +18094,9 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_param_enc_session_bound_to_auth(); #endif #endif /* !FWTPM_NO_HASH_CMDS */ +#if defined(WOLFTPM_FWTPM_UNIT_TEST) && defined(WOLFTPM_SMALL_STACK) + test_fwtpm_command_crypto_allocation_failure(); +#endif #if defined(HAVE_ECC) && !defined(FWTPM_NO_ECDH) test_fwtpm_ecc_parameters(); #endif diff --git a/wolftpm/fwtpm/fwtpm_command.h b/wolftpm/fwtpm/fwtpm_command.h index f9e38d95a..ffecf895c 100644 --- a/wolftpm/fwtpm/fwtpm_command.h +++ b/wolftpm/fwtpm/fwtpm_command.h @@ -66,6 +66,27 @@ WOLFTPM_API int FWTPM_ProcessCommand(FWTPM_CTX* ctx, */ WOLFTPM_API void FWTPM_ResetCommandClient(FWTPM_CTX* ctx); +/* Internal crypto helpers exposed only to the fwTPM unit-test build. */ +#ifdef WOLFTPM_FWTPM_UNIT_TEST + #define WOLFTPM_TEST_VIS + int FwHashOneShot(TPMI_ALG_HASH hashAlg, + const byte* d1, word32 d1Sz, const byte* d2, word32 d2Sz, + byte* digestOut); + int FwComputeRpHash(TPMI_ALG_HASH hashAlg, TPM_CC cmdCode, + const byte* rpBytes, int rpSize, byte* hashOut, int* hashOutSz); + int FwComputeCpHash(TPMI_ALG_HASH hashAlg, TPM_CC cmdCode, + const byte* cmdBuf, int cmdSize, const TPM_HANDLE* handles, + int handleCnt, FWTPM_CTX* ctx, int cpStart, + byte* hashOut, int* hashOutSz); + int FwComputeSessionHmac(FWTPM_Session* sess, + const byte* pHash, int pHashSz, UINT8 sessionAttributes, + const byte* authValue, int authValueSz, + const TPM2B_NONCE* nonceDecrypt, const TPM2B_NONCE* nonceEncrypt, + int isResponse, byte* hmacOut, int* hmacOutSz); +#else + #define WOLFTPM_TEST_VIS static +#endif + #ifdef __cplusplus } /* extern "C" */ #endif From cb446bd189a9af0c8fdb2fcc4232f9015df863b2 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 09:23:53 -0700 Subject: [PATCH 05/57] F-14025 - Reject truncated NV SetBits commands --- src/fwtpm/fwtpm_command.c | 8 ++++- tests/fwtpm_unit_tests.c | 69 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index ce29ac84a..dd8cff08c 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -14608,7 +14608,12 @@ static TPM_RC FwCmd_NV_SetBits(FWTPM_CTX* ctx, TPM2_Packet* cmd, TPM2_Packet_ParseU32(cmd, &nvHandle); if (cmdTag == TPM_ST_SESSIONS) rc = FwSkipAuthArea(cmd, cmdSize); - TPM2_Packet_ParseU64(cmd, &bits); + if (rc == 0) { + TPM2_Packet_ParseU64(cmd, &bits); + if (cmd->overflow) { + rc = TPM_RC_COMMAND_SIZE; + } + } nv = FwFindNvIndex(ctx, nvHandle); if (nv == NULL) { @@ -19611,6 +19616,7 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, cmdPkt.buf = (byte*)(size_t)cmdBuf; cmdPkt.pos = 0; cmdPkt.size = cmdSize; + cmdPkt.overflow = 0; /* Parse header */ TPM2_Packet_ParseU16(&cmdPkt, &cmdTag); diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index bbbc60d90..df1d27fe9 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -14007,6 +14007,70 @@ static void test_fwtpm_global_write_lock_nv_failure(void) fwtpm_pass("GlobalWriteLock NV failure rollback:", 0); } +/* A SetBits command missing its mandatory U64 must not write the index. */ +static void test_fwtpm_nv_setbits_truncated(void) +{ + FWTPM_CTX ctx; + FWTPM_NvIndex* nv = NULL; + UINT32 nvIdx = 0x01500008; + UINT32 attrs = TPMA_NV_OWNERWRITE | TPMA_NV_OWNERREAD | TPMA_NV_NO_DA | + ((UINT32)TPM_NT_BITS << 4); + UINT32 oldAttrs; + int oldWritten; + byte oldData[8]; + int i, pos, rspSize, cmdSz; + + XMEMSET(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), TPM_RC_SUCCESS); + cmdSz = BuildNvDefineCmd(gCmd, nvIdx, 8, attrs); + rspSize = 0; + AssertIntEQ(FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0), + TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + for (i = 0; i < FWTPM_MAX_NV_INDICES; i++) { + if (ctx.nvIndices[i].nvPublic.nvIndex == nvIdx) { + nv = &ctx.nvIndices[i]; + break; + } + } + AssertNotNull(nv); + oldWritten = nv->written; + oldAttrs = nv->nvPublic.attributes; + XMEMCPY(oldData, nv->data, sizeof(oldData)); + + pos = BuildCmdHeader(gCmd, TPM_ST_SESSIONS, 0, TPM_CC_NV_SetBits); + PutU32BE(gCmd + pos, TPM_RH_OWNER); pos += 4; + PutU32BE(gCmd + pos, nvIdx); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + AssertIntEQ(FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0), + TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_COMMAND_SIZE); + AssertIntEQ(nv->written, oldWritten); + AssertIntEQ(nv->nvPublic.attributes, oldAttrs); + AssertIntEQ(XMEMCMP(nv->data, oldData, sizeof(oldData)), 0); + + pos = BuildCmdHeader(gCmd, TPM_ST_SESSIONS, 0, TPM_CC_NV_SetBits); + PutU32BE(gCmd + pos, TPM_RH_OWNER); pos += 4; + PutU32BE(gCmd + pos, nvIdx); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, 1); pos += 4; + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + AssertIntEQ(FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0), + TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntNE(nv->nvPublic.attributes & TPMA_NV_WRITTEN, 0); + AssertIntEQ(nv->data[7], 1); + + TPM2_ForceZero(oldData, sizeof(oldData)); + FWTPM_Cleanup(&ctx); + fwtpm_pass("NV_SetBits truncated command rejected:", 0); +} + /* ClearControl must not change disableClear or return success when its NV * update fails. */ static void test_fwtpm_clear_control_nv_failure(void) @@ -17788,6 +17852,10 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_global_write_lock_nv_failure(); return 0; } + if (argc == 2 && XSTRCMP(argv[1], "nv-setbits-truncated") == 0) { + test_fwtpm_nv_setbits_truncated(); + return 0; + } if (argc == 2 && XSTRCMP(argv[1], "nv-stclear") == 0) { test_fwtpm_nv_stclear_startup(); return 0; @@ -18235,6 +18303,7 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_sessions_missing_authsize_command_size(); #ifndef FWTPM_NO_NV test_fwtpm_global_write_lock_nv_failure(); + test_fwtpm_nv_setbits_truncated(); test_fwtpm_clear_control_nv_failure(); test_fwtpm_state_change_nv_failure(); test_fwtpm_nv_append_atomic(); From e37a8a739c71ca0e6204689c1b054f9c38d7dec0 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 09:27:23 -0700 Subject: [PATCH 06/57] F-14069 - Validate special NV index sizes --- src/fwtpm/fwtpm_command.c | 11 +++++++ tests/fwtpm_unit_tests.c | 62 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index dd8cff08c..d8d399cdb 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -13924,6 +13924,17 @@ static TPM_RC FwCmd_NV_DefineSpace(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (nt > TPM_NT_PIN_PASS && nt != TPM_NT_PIN_FAIL) { rc = TPM_RC_ATTRIBUTES; } + else if ((nt == TPM_NT_COUNTER || nt == TPM_NT_BITS || + nt == TPM_NT_PIN_FAIL || nt == TPM_NT_PIN_PASS) && + publicInfo.nvPublic.dataSize != 8) { + rc = TPM_RC_SIZE; + } + else if (nt == TPM_NT_EXTEND && + publicInfo.nvPublic.dataSize != + (UINT16)TPM2_GetHashDigestSize( + publicInfo.nvPublic.nameAlg)) { + rc = TPM_RC_SIZE; + } } /* TPMA_NV_PLATFORMCREATE must be set under platform auth and clear under diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index df1d27fe9..26a3eff0d 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -14071,6 +14071,63 @@ static void test_fwtpm_nv_setbits_truncated(void) fwtpm_pass("NV_SetBits truncated command rejected:", 0); } +/* Special NV types have fixed sizes mandated by the TPM specification. */ +static void test_fwtpm_nv_define_type_sizes(void) +{ + static const UINT32 nvTypes[] = { + TPM_NT_COUNTER, + TPM_NT_BITS, + TPM_NT_EXTEND, + TPM_NT_PIN_FAIL, + TPM_NT_PIN_PASS + }; + static const UINT16 invalidSizes[] = {7, 7, 31, 7, 7}; + static const TPM_RC expectedRc[] = { + TPM_RC_SIZE, + TPM_RC_SIZE, + TPM_RC_SIZE, + TPM_RC_ATTRIBUTES, + TPM_RC_ATTRIBUTES + }; + FWTPM_CTX ctx; + UINT32 attrs; + UINT32 nvIdx = 0x01500010; + int i, cmdSz, rspSize; + + XMEMSET(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), TPM_RC_SUCCESS); + + for (i = 0; i < (int)(sizeof(nvTypes) / sizeof(nvTypes[0])); i++) { + attrs = TPMA_NV_OWNERWRITE | TPMA_NV_OWNERREAD | TPMA_NV_NO_DA | + (nvTypes[i] << 4); + cmdSz = BuildNvDefineCmd(gCmd, nvIdx + (UINT32)i, + invalidSizes[i], attrs); + rspSize = 0; + AssertIntEQ(FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, + 0), TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), expectedRc[i]); + } + + attrs = TPMA_NV_OWNERWRITE | TPMA_NV_OWNERREAD | TPMA_NV_NO_DA | + ((UINT32)TPM_NT_COUNTER << 4); + cmdSz = BuildNvDefineCmd(gCmd, nvIdx, 8, attrs); + rspSize = 0; + AssertIntEQ(FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0), + TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + attrs = TPMA_NV_OWNERWRITE | TPMA_NV_OWNERREAD | TPMA_NV_NO_DA | + ((UINT32)TPM_NT_EXTEND << 4); + cmdSz = BuildNvDefineCmd(gCmd, nvIdx + 1, WC_SHA256_DIGEST_SIZE, attrs); + rspSize = 0; + AssertIntEQ(FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0), + TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + FWTPM_Cleanup(&ctx); + fwtpm_pass("NV_DefineSpace type sizes:", 0); +} + /* ClearControl must not change disableClear or return success when its NV * update fails. */ static void test_fwtpm_clear_control_nv_failure(void) @@ -17856,6 +17913,10 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_nv_setbits_truncated(); return 0; } + if (argc == 2 && XSTRCMP(argv[1], "nv-define-type-sizes") == 0) { + test_fwtpm_nv_define_type_sizes(); + return 0; + } if (argc == 2 && XSTRCMP(argv[1], "nv-stclear") == 0) { test_fwtpm_nv_stclear_startup(); return 0; @@ -18304,6 +18365,7 @@ int fwtpm_unit_tests(int argc, char *argv[]) #ifndef FWTPM_NO_NV test_fwtpm_global_write_lock_nv_failure(); test_fwtpm_nv_setbits_truncated(); + test_fwtpm_nv_define_type_sizes(); test_fwtpm_clear_control_nv_failure(); test_fwtpm_state_change_nv_failure(); test_fwtpm_nv_append_atomic(); From 7a931087f346f1990bdf18f85f574bf284a8cd3a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 12:26:59 -0700 Subject: [PATCH 07/57] F-14075 - Reject truncated policy buffers --- src/fwtpm/fwtpm_command.c | 127 ++++++++++++++++++++++---------------- 1 file changed, 74 insertions(+), 53 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index d8d399cdb..f3f99be29 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -211,6 +211,33 @@ static TPM_RC FwSkipAuthArea(TPM2_Packet* cmd, int cmdSize) return TPM_RC_SUCCESS; } +#ifndef FWTPM_NO_POLICY +/* Parse a policy TPM2B only when its complete wire value is present. */ +static TPM_RC FwPolicyParseTpm2b(TPM2_Packet* cmd, int cmdSize, byte* out, + UINT16 outSz, UINT16* size) +{ + if (cmd == NULL || out == NULL || size == NULL || cmd->overflow || + cmd->pos < 0 || cmd->pos > cmdSize || + cmdSize - cmd->pos < (int)sizeof(UINT16)) { + return TPM_RC_COMMAND_SIZE; + } + + *size = 0; + XMEMSET(out, 0, outSz); + TPM2_Packet_ParseU16(cmd, size); + if (*size > outSz) { + return TPM_RC_SIZE; + } + if ((int)*size > cmdSize - cmd->pos) { + return TPM_RC_COMMAND_SIZE; + } + if (*size > 0) { + TPM2_Packet_ParseBytes(cmd, out, *size); + } + return cmd->overflow ? TPM_RC_COMMAND_SIZE : TPM_RC_SUCCESS; +} +#endif + #ifdef WOLFTPM_SPDM #ifdef FWTPM_SPDM_USE_RESPONDER #define FWTPM_SPDM_P384_SZ 48 @@ -11118,10 +11145,10 @@ static TPM_RC FwCmd_PolicyPCR(FWTPM_CTX* ctx, TPM2_Packet* cmd, int i, j; int bankIdx, pcrDSz; - (void)cmdSize; - FWTPM_ALLOC_VAR(hashCtx, wc_HashAlg); + XMEMSET(&pcrs, 0, sizeof(pcrs)); + /* Parse handle */ TPM2_Packet_ParseU32(cmd, &sessHandle); @@ -11129,17 +11156,17 @@ static TPM_RC FwCmd_PolicyPCR(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (cmdTag == TPM_ST_SESSIONS) rc = FwSkipAuthArea(cmd, cmdSize); /* Parse pcrDigest (TPM2B) */ - TPM2_Packet_ParseU16(cmd, &pcrDigestSize); - if (pcrDigestSize > sizeof(pcrDigest)) { - rc = TPM_RC_SIZE; - } - if (rc == 0 && pcrDigestSize > 0) { - TPM2_Packet_ParseBytes(cmd, pcrDigest, pcrDigestSize); + if (rc == 0) { + rc = FwPolicyParseTpm2b(cmd, cmdSize, pcrDigest, + (UINT16)sizeof(pcrDigest), &pcrDigestSize); } /* Parse PCR selection */ if (rc == 0) { TPM2_Packet_ParsePCR(cmd, &pcrs); + if (cmd->overflow) { + rc = TPM_RC_COMMAND_SIZE; + } } #ifdef DEBUG_WOLFTPM @@ -11562,16 +11589,20 @@ static TPM_RC FwCmd_PolicyOR(FWTPM_CTX* ctx, TPM2_Packet* cmd, byte ccBuf[4]; byte zeroBuf[TPM_MAX_DIGEST_SIZE]; UINT32 ccPolicyOR = TPM_CC_PolicyOR; - (void)cmdSize; FWTPM_ALLOC_VAR(hashCtx, wc_HashAlg); TPM2_Packet_ParseU32(cmd, &sessHandle); if (cmdTag == TPM_ST_SESSIONS) rc = FwSkipAuthArea(cmd, cmdSize); - TPM2_Packet_ParseU32(cmd, &digestCount); - if (digestCount < 2 || digestCount > 8) { - rc = TPM_RC_VALUE; + if (rc == 0) { + TPM2_Packet_ParseU32(cmd, &digestCount); + if (cmd->overflow) { + rc = TPM_RC_COMMAND_SIZE; + } + else if (digestCount < 2 || digestCount > 8) { + rc = TPM_RC_VALUE; + } } if (rc == 0) { sess = FwFindSession(ctx, sessHandle); @@ -11590,12 +11621,14 @@ static TPM_RC FwCmd_PolicyOR(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (rc == 0) { dSz = TPM2_GetHashDigestSize(sess->authHash); for (i = 0; i < (int)digestCount; i++) { - TPM2_Packet_ParseU16(cmd, &digestSizes[i]); - if (digestSizes[i] != (UINT16)dSz) { + rc = FwPolicyParseTpm2b(cmd, cmdSize, digests[i], + (UINT16)sizeof(digests[i]), &digestSizes[i]); + if (rc == 0 && digestSizes[i] != (UINT16)dSz) { rc = TPM_RC_SIZE; + } + if (rc != 0) { break; } - TPM2_Packet_ParseBytes(cmd, digests[i], digestSizes[i]); } } @@ -12868,7 +12901,6 @@ static TPM_RC FwCmd_PolicyTemplate(FWTPM_CTX* ctx, TPM2_Packet* cmd, FWTPM_Session* sess; UINT16 templateHashSz = 0; byte templateHash[TPM_MAX_DIGEST_SIZE]; - (void)cmdSize; sess = FwPolicyParseSession(ctx, cmd, cmdSize, cmdTag); if (sess == NULL) { @@ -12881,13 +12913,14 @@ static TPM_RC FwCmd_PolicyTemplate(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (rc == 0) { int expectedSz = TPM2_GetHashDigestSize(sess->authHash); - TPM2_Packet_ParseU16(cmd, &templateHashSz); - if (expectedSz <= 0 || templateHashSz != (UINT16)expectedSz) { + rc = FwPolicyParseTpm2b(cmd, cmdSize, templateHash, + (UINT16)sizeof(templateHash), &templateHashSz); + if (rc == 0 && (expectedSz <= 0 || + templateHashSz != (UINT16)expectedSz)) { rc = TPM_RC_SIZE; } } if (rc == 0) { - TPM2_Packet_ParseBytes(cmd, templateHash, templateHashSz); /* cpHash, nameHash and templateHash share one session slot * (Part 3 Sec.23.19), so only a repeat of the same template is * accepted */ @@ -12939,7 +12972,6 @@ static TPM_RC FwCmd_PolicyCpHash(FWTPM_CTX* ctx, TPM2_Packet* cmd, int sizeMismatch; int cpDiff; word32 cmpSz; - (void)cmdSize; sess = FwPolicyParseSession(ctx, cmd, cmdSize, cmdTag); if (sess == NULL) { @@ -12952,14 +12984,14 @@ static TPM_RC FwCmd_PolicyCpHash(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (rc == 0) { int expectedSz = TPM2_GetHashDigestSize(sess->authHash); - TPM2_Packet_ParseU16(cmd, &cpHashSz); - if (expectedSz <= 0 || cpHashSz != (UINT16)expectedSz) { + rc = FwPolicyParseTpm2b(cmd, cmdSize, cpHashBuf, + (UINT16)sizeof(cpHashBuf), &cpHashSz); + if (rc == 0 && (expectedSz <= 0 || + cpHashSz != (UINT16)expectedSz)) { rc = TPM_RC_SIZE; } } if (rc == 0) { - TPM2_Packet_ParseBytes(cmd, cpHashBuf, cpHashSz); - /* cpHash, nameHash and templateHash share one session slot */ if (sess->nameHash.size > 0 || sess->templateHash.size > 0) { rc = TPM_RC_CPHASH; @@ -13010,7 +13042,6 @@ static TPM_RC FwCmd_PolicyNameHash(FWTPM_CTX* ctx, TPM2_Packet* cmd, int sizeMismatch; int nameDiff; word32 cmpSz; - (void)cmdSize; sess = FwPolicyParseSession(ctx, cmd, cmdSize, cmdTag); if (sess == NULL) { @@ -13023,14 +13054,14 @@ static TPM_RC FwCmd_PolicyNameHash(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (rc == 0) { int expectedSz = TPM2_GetHashDigestSize(sess->authHash); - TPM2_Packet_ParseU16(cmd, &nameHashSz); - if (expectedSz <= 0 || nameHashSz != (UINT16)expectedSz) { + rc = FwPolicyParseTpm2b(cmd, cmdSize, nameHashBuf, + (UINT16)sizeof(nameHashBuf), &nameHashSz); + if (rc == 0 && (expectedSz <= 0 || + nameHashSz != (UINT16)expectedSz)) { rc = TPM_RC_SIZE; } } if (rc == 0) { - TPM2_Packet_ParseBytes(cmd, nameHashBuf, nameHashSz); - /* cpHash, nameHash and templateHash share one session slot */ if (sess->cpHashA.size > 0 || sess->templateHash.size > 0) { rc = TPM_RC_CPHASH; @@ -13083,7 +13114,6 @@ static TPM_RC FwCmd_PolicyDuplicationSelect(FWTPM_CTX* ctx, TPM2_Packet* cmd, byte objectName[2 + TPM_MAX_DIGEST_SIZE]; byte newParentName[2 + TPM_MAX_DIGEST_SIZE]; UINT8 includeObject = 0; - (void)cmdSize; sess = FwPolicyParseSession(ctx, cmd, cmdSize, cmdTag); if (sess == NULL) { @@ -13096,30 +13126,24 @@ static TPM_RC FwCmd_PolicyDuplicationSelect(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* Parse TPM2B_NAME objectName */ if (rc == 0) { - TPM2_Packet_ParseU16(cmd, &objectNameSz); - if (objectNameSz > sizeof(objectName)) { - rc = TPM_RC_SIZE; - } - } - if (rc == 0 && objectNameSz > 0) { - TPM2_Packet_ParseBytes(cmd, objectName, objectNameSz); + rc = FwPolicyParseTpm2b(cmd, cmdSize, objectName, + (UINT16)sizeof(objectName), &objectNameSz); } /* Parse TPM2B_NAME newParentName */ if (rc == 0) { - TPM2_Packet_ParseU16(cmd, &newParentNameSz); - if (newParentNameSz > sizeof(newParentName)) { - rc = TPM_RC_SIZE; - } - } - if (rc == 0 && newParentNameSz > 0) { - TPM2_Packet_ParseBytes(cmd, newParentName, newParentNameSz); + rc = FwPolicyParseTpm2b(cmd, cmdSize, newParentName, + (UINT16)sizeof(newParentName), + &newParentNameSz); } /* Parse includeObject (TPMI_YES_NO = UINT8) */ if (rc == 0) { TPM2_Packet_ParseU8(cmd, &includeObject); - if (includeObject > 1) { + if (cmd->overflow) { + rc = TPM_RC_COMMAND_SIZE; + } + else if (includeObject > 1) { rc = TPM_RC_VALUE; } } @@ -13245,8 +13269,6 @@ static TPM_RC FwCmd_PolicyCounterTimer(FWTPM_CTX* ctx, TPM2_Packet* cmd, UINT32 cc = TPM_CC_PolicyCounterTimer; int hashCtxInit = 0; - (void)cmdSize; - FWTPM_ALLOC_VAR(hashCtx, wc_HashAlg); sess = FwPolicyParseSession(ctx, cmd, cmdSize, cmdTag); @@ -13260,16 +13282,15 @@ static TPM_RC FwCmd_PolicyCounterTimer(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* Parse operandB (TPM2B) */ if (rc == 0) { - TPM2_Packet_ParseU16(cmd, &operandBSz); - if (operandBSz > sizeof(operandB)) - rc = TPM_RC_SIZE; - } - if (rc == 0 && operandBSz > 0) { - TPM2_Packet_ParseBytes(cmd, operandB, operandBSz); + rc = FwPolicyParseTpm2b(cmd, cmdSize, operandB, + (UINT16)sizeof(operandB), &operandBSz); } if (rc == 0) { TPM2_Packet_ParseU16(cmd, &offset); TPM2_Packet_ParseU16(cmd, &operation); + if (cmd->overflow) { + rc = TPM_RC_COMMAND_SIZE; + } } /* Marshal TPMS_TIME_INFO into timeInfo buffer */ From 7871436c2c65c5cd56de2ee33c9ef5acc0932c8a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 12:31:18 -0700 Subject: [PATCH 08/57] F-14407 - Preserve WRITE_STCLEAR write access --- src/fwtpm/fwtpm_command.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index f3f99be29..ad6b9ed70 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -14265,12 +14265,7 @@ static TPM_RC FwCmd_NV_Write(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* Set WRITTEN attribute */ nv->nvPublic.attributes |= 0x20000000UL; /* TPMA_NV_WRITTEN */ - /* Note: TPMA_NV_WRITEDEFINE means the index CAN be write-locked via - * explicit NV_WriteLock command. It does NOT auto-lock on each write. - * TPMA_NV_WRITE_STCLEAR auto-locks on write (cleared on SU_CLEAR). */ - if (nv->nvPublic.attributes & TPMA_NV_WRITE_STCLEAR) { - nv->nvPublic.attributes |= TPMA_NV_WRITELOCKED; - } + /* WRITE_STCLEAR only enables explicit NV_WriteLock until reset. */ rc = FWTPM_NV_SaveNvIndex(ctx, (int)(nv - ctx->nvIndices)); From e1efef9e4f866c991aa3819c7a31f7cfffd96ac3 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 12:36:04 -0700 Subject: [PATCH 09/57] F-14408 - Validate PolicySigned nonce input --- src/fwtpm/fwtpm_command.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index ad6b9ed70..ee30c345c 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -12423,6 +12423,14 @@ static TPM_RC FwCmd_PolicySigned(FWTPM_CTX* ctx, TPM2_Packet* cmd, } #endif + if (rc == 0 && sess->sessionType == TPM_SE_POLICY && nonceTpmSz > 0) { + if (nonceTpmSz != sess->nonceTPM.size || + TPM2_ConstantCompare(nonceBuf, sess->nonceTPM.buffer, + nonceTpmSz) != 0) { + rc = TPM_RC_VALUE; + } + } + /* For policy sessions (not trial): verify signature per TPM 2.0 Part 3 * Section 23.3. Compute aHash = H(nonceTPM || expiration || cpHashA || * policyRef), then verify signature against aHash using authObject's @@ -12439,9 +12447,8 @@ static TPM_RC FwCmd_PolicySigned(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* Compute aHash */ hrc = wc_HashInit(hashCtx, wcHash); - if (hrc == 0 && sess->nonceTPM.size > 0) { - hrc = wc_HashUpdate(hashCtx, wcHash, - sess->nonceTPM.buffer, sess->nonceTPM.size); + if (hrc == 0 && nonceTpmSz > 0) { + hrc = wc_HashUpdate(hashCtx, wcHash, nonceBuf, nonceTpmSz); } if (hrc == 0) { FwStoreU32BE(expBuf, (UINT32)expiration); From 22512060b4d7eca3db8d82523be3c9b578404bb7 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 12:38:04 -0700 Subject: [PATCH 10/57] F-14409 - Reject unknown PolicySigned handles --- src/fwtpm/fwtpm_command.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index ee30c345c..cc46e612e 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -12474,7 +12474,7 @@ static TPM_RC FwCmd_PolicySigned(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (rc == 0) { authObj = FwFindObject(ctx, authObjHandle); if (authObj == NULL) { - rc = TPM_RC_HANDLE; + rc = TPM_RC_HANDLE | TPM_RC_1; } } if (rc == 0) { @@ -12488,12 +12488,13 @@ static TPM_RC FwCmd_PolicySigned(FWTPM_CTX* ctx, TPM2_Packet* cmd, byte entityName[sizeof(TPM2B_NAME)]; int entityNameSz = FwGetEntityName(ctx, authObjHandle, entityName, (int)sizeof(entityName)); - if (entityNameSz > 0) { - if (FwPolicyExtend(sess, TPM_CC_PolicySigned, - entityName, entityNameSz, - policyRef, policyRefSz, 1) != 0) { - rc = TPM_RC_FAILURE; - } + if (entityNameSz == 0) { + rc = TPM_RC_HANDLE | TPM_RC_1; + } + else if (FwPolicyExtend(sess, TPM_CC_PolicySigned, + entityName, entityNameSz, + policyRef, policyRefSz, 1) != 0) { + rc = TPM_RC_FAILURE; } } From dac8d7cb89c2327094f89512d1b792e2fce396a2 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 12:40:01 -0700 Subject: [PATCH 11/57] F-14410 - Preserve PCR values across Clear --- src/fwtpm/fwtpm_command.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index cc46e612e..7c47ecd3e 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -5749,9 +5749,8 @@ static TPM_RC FwCmd_Clear(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, } #endif - /* Reset PCRs */ - XMEMSET(ctx->pcrDigest, 0, sizeof(ctx->pcrDigest)); - ctx->pcrUpdateCounter = 0; + /* Preserve PCR values and invalidate saved PCR policy state. */ + ctx->pcrUpdateCounter++; /* Reset owner and endorsement hierarchy policies * (not platform/lockout) */ From b03dab2d1e1d446e97dc5795059c6a4b59f14bb8 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 12:43:03 -0700 Subject: [PATCH 12/57] F-14411 - Reset Clear-owned TPM state --- src/fwtpm/fwtpm_command.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 7c47ecd3e..1bb239d15 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -5752,12 +5752,24 @@ static TPM_RC FwCmd_Clear(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, /* Preserve PCR values and invalidate saved PCR policy state. */ ctx->pcrUpdateCounter++; - /* Reset owner and endorsement hierarchy policies - * (not platform/lockout) */ + /* Reset owner, endorsement, and lockout hierarchy policies. */ XMEMSET(&ctx->ownerPolicy, 0, sizeof(ctx->ownerPolicy)); ctx->ownerPolicyAlg = TPM_ALG_NULL; XMEMSET(&ctx->endorsementPolicy, 0, sizeof(ctx->endorsementPolicy)); ctx->endorsementPolicyAlg = TPM_ALG_NULL; + XMEMSET(&ctx->lockoutPolicy, 0, sizeof(ctx->lockoutPolicy)); + ctx->lockoutPolicyAlg = TPM_ALG_NULL; + + TPM2_ForceZero(ctx->pcrAuth, sizeof(ctx->pcrAuth)); + if (ctx->clockHal.get_ms != NULL) { + ctx->clockOffset = 0 - + ctx->clockHal.get_ms(ctx->clockHal.ctx); + } + else { + ctx->clockOffset = 0; + } + ctx->resetCount = 0; + ctx->restartCount = 0; /* Reset disableClear per spec */ ctx->disableClear = 0; From 1d9132bfe95cb27f7edd4802abbfbd4e56079c76 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:56:44 -0700 Subject: [PATCH 13/57] F-14412 - Track orderly startup state --- src/fwtpm/fwtpm_command.c | 59 ++++++++++++++++++++++++++++++++------- src/fwtpm/fwtpm_nv.c | 56 ++++++++++++++++++++++++++----------- tests/fwtpm_unit_tests.c | 35 +++++++++++++++++++++-- wolftpm/fwtpm/fwtpm.h | 3 +- wolftpm/fwtpm/fwtpm_nv.h | 1 + 5 files changed, 125 insertions(+), 29 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 1bb239d15..847c1a861 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -991,6 +991,9 @@ static TPM_RC FwCmd_Startup(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, TPM_RC rc = TPM_RC_SUCCESS; UINT16 startupType = 0; int i, b; + int stateShutdown = 0; + UINT32 oldResetCount = 0; + UINT32 oldRestartCount = 0; #ifndef FWTPM_NO_NV FWTPM_DECLARE_BUF(oldNvData, FWTPM_MAX_NV_DATA); int oldNvDataAllocated = 0; @@ -1017,6 +1020,14 @@ static TPM_RC FwCmd_Startup(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, rc = TPM_RC_FAILURE; } + if (rc == 0) { + stateShutdown = ctx->shutdownStateSaved; + /* Startup(STATE) is only valid after an orderly Shutdown(STATE). */ + if (startupType == TPM_SU_STATE && !stateShutdown) { + rc = TPM_RC_VALUE; + } + } + if (rc == 0) { #ifndef FWTPM_NO_DA /* Non-orderly shutdown penalty (Part 1 Sec.19.8.5): if the prior @@ -1081,13 +1092,13 @@ static TPM_RC FwCmd_Startup(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, (void)FWTPM_NV_Save(ctx); } ctx->globalNvWriteLock = 0; - /* shEnable/ehEnable/phEnableNV re-enable on TPM Reset only; + /* shEnable/ehEnable/phEnableNV re-enable on Reset or Restart; * phEnable re-enables on every startup (handled below). */ ctx->shDisabled = 0; ctx->ehDisabled = 0; ctx->phNvDisabled = 0; #ifndef FWTPM_NO_CONTEXT - /* Saved contexts are invalidated by TPM Reset */ + /* Saved contexts are invalidated by Startup(CLEAR). */ ctx->contextLiveCount = 0; ctx->ctxProtectKeyValid = 0; TPM2_ForceZero(ctx->ctxProtectKey, @@ -1106,7 +1117,7 @@ static TPM_RC FwCmd_Startup(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, ctx->ecEphemeralKeySz = 0; #endif - /* Null seed: re-randomize on every Startup(CLEAR) per spec */ + /* Regenerate the null seed on Startup(CLEAR). */ if (rc == 0) { rc = wc_RNG_GenerateBlock(&ctx->rng, ctx->nullSeed, FWTPM_SEED_SIZE); @@ -1172,18 +1183,26 @@ static TPM_RC FwCmd_Startup(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, FWTPM_FREE_BUF(oldNvData); } #endif + } - /* TPM Reset: bump persisted resetCount, clear restartCount */ - if (rc == 0) { + if (rc == 0) { + oldResetCount = ctx->resetCount; + oldRestartCount = ctx->restartCount; + /* CLEAR after Shutdown(STATE) is Restart for clock counters. */ + if (startupType == TPM_SU_CLEAR && !stateShutdown) { ctx->resetCount++; ctx->restartCount = 0; - rc = FWTPM_NV_SaveFlags(ctx); } - } - else { - /* TPM Restart/Resume: bump volatile restartCount */ - ctx->restartCount++; + else { + ctx->restartCount++; + } + ctx->shutdownStateSaved = 0; rc = FWTPM_NV_SaveFlags(ctx); + if (rc != TPM_RC_SUCCESS) { + ctx->shutdownStateSaved = stateShutdown; + ctx->resetCount = oldResetCount; + ctx->restartCount = oldRestartCount; + } } /* phEnable is SET on every _TPM_Init/Startup (TPM 2.0 Part 2 @@ -1211,6 +1230,7 @@ static TPM_RC FwCmd_Shutdown(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, { TPM_RC rc = TPM_RC_SUCCESS; UINT16 shutdownType = 0; + int oldShutdownState; (void)cmdTag; @@ -1244,8 +1264,11 @@ static TPM_RC FwCmd_Shutdown(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, * non-orderly DA penalty. */ ctx->orderly = 1; #endif + oldShutdownState = ctx->shutdownStateSaved; + ctx->shutdownStateSaved = (shutdownType == TPM_SU_STATE); rc = FWTPM_NV_Save(ctx); if (rc != TPM_RC_SUCCESS) { + ctx->shutdownStateSaved = oldShutdownState; return rc; } @@ -19765,6 +19788,22 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, XMEMSET(cmdAuths, 0, sizeof(cmdAuths)); XMEMSET(cmdHandles, 0, sizeof(cmdHandles)); + /* Any structurally valid command after Shutdown(STATE), except a + * pre-startup capability query, invalidates the saved state before + * session parsing or authorization can mutate TPM state. */ + if (ctx->shutdownStateSaved && cmdCode != TPM_CC_Startup && + cmdCode != TPM_CC_GetCapability) { + ctx->shutdownStateSaved = 0; + rc = FWTPM_NV_SaveFlags(ctx); + if (rc != TPM_RC_SUCCESS) { + ctx->shutdownStateSaved = 1; + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, + TPM_ST_NO_SESSIONS, rc); + TPM2_ForceZero(cmdAuths, sizeof(cmdAuths)); + return TPM_RC_SUCCESS; + } + } + /* For TPM_ST_SESSIONS commands, parse auth area to detect param encryption * and track all auth sessions for response auth generation. * We peek without advancing cmdPkt.pos so handler re-parses normally. */ diff --git a/src/fwtpm/fwtpm_nv.c b/src/fwtpm/fwtpm_nv.c index d8da9ca0d..4eb6ab454 100644 --- a/src/fwtpm/fwtpm_nv.c +++ b/src/fwtpm/fwtpm_nv.c @@ -1356,6 +1356,7 @@ static int FwNvProcessEntry(FWTPM_CTX* ctx, UINT16 tag, case FWTPM_NV_TAG_FLAGS: { UINT8 flags8 = 0; + UINT8 shutdownState = 0; FwNvUnmarshalU8(value, &vPos, vMax, &flags8); ctx->disableClear = (flags8 & 0x01) ? 1 : 0; ctx->globalNvWriteLock = (flags8 & 0x02) ? 1 : 0; @@ -1407,9 +1408,24 @@ static int FwNvProcessEntry(FWTPM_CTX* ctx, UINT16 tag, if (ctx->endorsementProofVersion != 1) ctx->endorsementProofVersion = 0; } + ctx->restartCount = 0; + if (vPos + 4 <= vMax) { + FwNvUnmarshalU32(value, &vPos, vMax, &ctx->restartCount); + } + ctx->shutdownStateSaved = 0; + if (vPos < vMax) { + FwNvUnmarshalU8(value, &vPos, vMax, &shutdownState); + ctx->shutdownStateSaved = shutdownState ? 1 : 0; + } break; } + case FWTPM_NV_TAG_SHUTDOWN_STATE: + if (valueLen >= 1) { + ctx->shutdownStateSaved = value[0] ? 1 : 0; + } + break; + case FWTPM_NV_TAG_CLOCK: { UINT32 lo = 0, hi = 0; FwNvUnmarshalU32(value, &vPos, vMax, &lo); @@ -2190,8 +2206,11 @@ int FWTPM_NV_Save(FWTPM_CTX* ctx) #ifndef FWTPM_NO_DA FwNvMarshalU32(buf, &pos, bufSz, ctx->daFailedTries); #endif - if (ctx->endorsementProofVersion != 0) - FwNvMarshalU8(buf, &pos, bufSz, 1); + FwNvMarshalU8(buf, &pos, bufSz, + ctx->endorsementProofVersion != 0 ? 1 : 0); + FwNvMarshalU32(buf, &pos, bufSz, ctx->restartCount); + /* A full snapshot authorizes resume only with its final marker. */ + FwNvMarshalU8(buf, &pos, bufSz, 0); rc = FwNvAppendEntry(ctx, FWTPM_NV_TAG_FLAGS, buf, (UINT16)pos); } @@ -2335,19 +2354,21 @@ int FWTPM_NV_Save(FWTPM_CTX* ctx) } } - /* Seal the journal: append-only adds one trailing checkpoint (header was - * written after the erase); byte-addressable updates the header in place. */ - if (rc == 0) { + /* Commit the orderly-state marker after every snapshot entry. */ + if (rc == 0 && ctx->shutdownStateSaved) { + const byte stateSaved = 1; + rc = FwNvAppendEntry(ctx, FWTPM_NV_TAG_SHUTDOWN_STATE, + &stateSaved, sizeof(stateSaved)); + } + + /* Append-only compaction defers its checkpoint until every entry is + * written. Byte-addressable appends already update the header and MAC; + * rewriting them here could report failure after a completed commit. */ #ifdef WOLFTPM_FWTPM_NV_APPEND_ONLY - if (FW_NV_APPEND_ONLY(hal)) { - rc = FwNvAppendCheckpoint(ctx); - } - else -#endif - { - rc = FwNvWriteHeader(ctx); - } + if (rc == 0 && FW_NV_APPEND_ONLY(hal)) { + rc = FwNvAppendCheckpoint(ctx); } +#endif #ifdef WOLFTPM_FWTPM_NV_APPEND_ONLY /* A compaction that did not reach its checkpoint leaves the rewritten * snapshot unsealed on the log, so it must be rebuilt before any append. */ @@ -2498,7 +2519,7 @@ int FWTPM_NV_SaveFlags(FWTPM_CTX* ctx) return TPM_RC_SUCCESS; #else int rc; - byte buf[1 + 12 + 4 + 4 + 1]; /* flags, DA, counters, proof version */ + byte buf[1 + 12 + 4 + 4 + 1 + 4 + 1]; word32 pos = 0; if (ctx == NULL) { @@ -2526,8 +2547,11 @@ int FWTPM_NV_SaveFlags(FWTPM_CTX* ctx) #ifndef FWTPM_NO_DA FwNvMarshalU32(buf, &pos, sizeof(buf), ctx->daFailedTries); #endif - if (ctx->endorsementProofVersion != 0) - FwNvMarshalU8(buf, &pos, sizeof(buf), 1); + FwNvMarshalU8(buf, &pos, sizeof(buf), + ctx->endorsementProofVersion != 0 ? 1 : 0); + FwNvMarshalU32(buf, &pos, sizeof(buf), ctx->restartCount); + FwNvMarshalU8(buf, &pos, sizeof(buf), + ctx->shutdownStateSaved ? 1 : 0); rc = FwNvAppendEntry(ctx, FWTPM_NV_TAG_FLAGS, buf, (UINT16)pos); return rc; diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index 26a3eff0d..12bb2ec97 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -243,6 +243,25 @@ static int fwtpm_test_startup(FWTPM_CTX* ctx) return (int)GetRspRC(gRsp); } +#if !defined(FWTPM_NO_NV) && \ + (!defined(FWTPM_NO_CLOCK) || !defined(FWTPM_NO_DA)) +static int fwtpm_test_shutdown_state(FWTPM_CTX* ctx) +{ + int rc; + int rspSize = 0; + int cmdSz; + + cmdSz = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 12, TPM_CC_Shutdown); + PutU16BE(gCmd + cmdSz, TPM_SU_STATE); + cmdSz += 2; + PutU32BE(gCmd + 2, (UINT32)cmdSz); + rc = FWTPM_ProcessCommand(ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + if (rc != TPM_RC_SUCCESS) + return rc; + return (int)GetRspRC(gRsp); +} +#endif /* !FWTPM_NO_NV && (!FWTPM_NO_CLOCK || !FWTPM_NO_DA) */ + /* ================================================================== */ /* 1. Core Lifecycle Tests */ /* ================================================================== */ @@ -2512,13 +2531,17 @@ static void AssertReadClockCounters(FWTPM_CTX* ctx) static void test_fwtpm_readclock_counters(void) { FWTPM_CTX ctx; +#ifndef FWTPM_NO_NV int rc, rspSize, cmdSz; +#endif (void)remove(FWTPM_NV_FILE); XMEMSET(&ctx, 0, sizeof(ctx)); AssertIntEQ(fwtpm_test_startup(&ctx), 0); AssertIntGT((int)ctx.resetCount, 0); AssertReadClockCounters(&ctx); +#ifndef FWTPM_NO_NV + AssertIntEQ(fwtpm_test_shutdown_state(&ctx), TPM_RC_SUCCESS); FWTPM_Cleanup(&ctx); XMEMSET(&ctx, 0, sizeof(ctx)); @@ -2534,6 +2557,7 @@ static void test_fwtpm_readclock_counters(void) AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); AssertIntGT((int)ctx.restartCount, 0); AssertReadClockCounters(&ctx); +#endif FWTPM_Cleanup(&ctx); (void)remove(FWTPM_NV_FILE); @@ -14603,6 +14627,7 @@ static void test_fwtpm_da_platform_exempt(void) fwtpm_pass("DA platform hierarchy exempt:", 0); } +#ifndef FWTPM_NO_NV /* Startup(SU_STATE) exercises the Restart/Resume DA-flag persist branch. */ static void test_fwtpm_da_startup_state(void) { @@ -14611,7 +14636,12 @@ static void test_fwtpm_da_startup_state(void) (void)remove(FWTPM_NV_FILE); memset(&ctx, 0, sizeof(ctx)); - AssertIntEQ(FWTPM_Init(&ctx), 0); + AssertIntEQ(fwtpm_test_startup(&ctx), TPM_RC_SUCCESS); + AssertIntEQ(fwtpm_test_shutdown_state(&ctx), TPM_RC_SUCCESS); + FWTPM_Cleanup(&ctx); + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(FWTPM_Init(&ctx), TPM_RC_SUCCESS); cmdSz = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 12, TPM_CC_Startup); PutU16BE(gCmd + cmdSz, TPM_SU_STATE); cmdSz += 2; PutU32BE(gCmd + 2, (UINT32)cmdSz); @@ -14623,6 +14653,7 @@ static void test_fwtpm_da_startup_state(void) (void)remove(FWTPM_NV_FILE); fwtpm_pass("DA Startup(SU_STATE) persist:", 0); } +#endif /* !FWTPM_NO_NV */ #ifdef HAVE_ECC /* Create an ECC-P256 sign primary in the owner hierarchy with empty auth and @@ -18252,8 +18283,8 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_da_parameters_zero_maxtries_rejected(); test_fwtpm_da_getcap_properties(); test_fwtpm_da_platform_exempt(); - test_fwtpm_da_startup_state(); #ifndef FWTPM_NO_NV + test_fwtpm_da_startup_state(); test_fwtpm_da_oldformat_journal(); test_fwtpm_da_failedtries_clamp(); test_fwtpm_da_no_selfheal_wipe_on_reload(); diff --git a/wolftpm/fwtpm/fwtpm.h b/wolftpm/fwtpm/fwtpm.h index b983b2efa..0f353373c 100644 --- a/wolftpm/fwtpm/fwtpm.h +++ b/wolftpm/fwtpm/fwtpm.h @@ -761,6 +761,7 @@ typedef struct FWTPM_CTX { /* TPM state */ int powerOn; int wasStarted; /* Has TPM2_Startup been called */ + int shutdownStateSaved; /* Previous Shutdown(STATE), persisted */ int pendingClear; /* Deferred clear (after response auth) */ int disableClear; /* ClearControl: 1 = Clear is disabled */ int globalNvWriteLock; /* NV_GlobalWriteLock (reset on Startup CLEAR) */ @@ -794,7 +795,7 @@ typedef struct FWTPM_CTX { #endif UINT64 clockOffset; /* Clock offset set by ClockSet */ UINT32 resetCount; /* TPM Reset count, persisted across boots */ - UINT32 restartCount; /* TPM Restart/Resume count, volatile */ + UINT32 restartCount; /* Restart/Resume count, reset by TPM Reset */ /* PCR state: [pcrIndex][bank][digest bytes] */ byte pcrDigest[IMPLEMENTATION_PCR][FWTPM_PCR_BANKS][TPM_MAX_DIGEST_SIZE]; diff --git a/wolftpm/fwtpm/fwtpm_nv.h b/wolftpm/fwtpm/fwtpm_nv.h index d8c2307d7..7f012f40e 100644 --- a/wolftpm/fwtpm/fwtpm_nv.h +++ b/wolftpm/fwtpm/fwtpm_nv.h @@ -111,6 +111,7 @@ typedef struct FWTPM_NV_HEADER { /* Flags (disableClear, DA params, etc.) */ #define FWTPM_NV_TAG_FLAGS 0x0030 +#define FWTPM_NV_TAG_SHUTDOWN_STATE 0x0031 /* Hierarchy policies (value: UINT32 hierarchy + UINT16 alg + digest) */ #define FWTPM_NV_TAG_HIERARCHY_POLICY 0x0035 From 4bcdacba5004682b0ff90e853a44e08e01632a41 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 13:58:05 -0700 Subject: [PATCH 14/57] F-14413 - Reject duplicate restored session handles --- src/fwtpm/fwtpm_command.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 847c1a861..4eef3930d 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -5487,6 +5487,9 @@ static TPM_RC FwCmd_ContextLoad(FWTPM_CTX* ctx, TPM2_Packet* cmd, rc = TPM_RC_VALUE; } } + if (rc == 0 && FwFindSession(ctx, restored.handle) != NULL) { + rc = TPM_RC_SESSION_HANDLES; + } if (rc == 0) { /* Find a free session slot */ for (si = 0; si < FWTPM_MAX_SESSIONS; si++) { From 54b68ea6d1a5594f0f321a7e50888124ecb8a87f Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 14:03:36 -0700 Subject: [PATCH 15/57] F-14414 - Honor keyed hash HMAC schemes --- src/fwtpm/fwtpm_command.c | 47 +++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 4eef3930d..c6a5ebcfb 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -9756,8 +9756,13 @@ static TPM_RC FwCmd_HMAC(FWTPM_CTX* ctx, TPM2_Packet* cmd, * non-NULL wire hash must match it (Part 3 Sec.17.4). A key with an * unbound (NULL) scheme lets the caller choose, defaulting to the * key's nameAlg. */ - if (obj->pub.parameters.keyedHashDetail.scheme.scheme - != TPM_ALG_NULL) { + if (obj->pub.parameters.keyedHashDetail.scheme.scheme != TPM_ALG_NULL && + obj->pub.parameters.keyedHashDetail.scheme.scheme != + TPM_ALG_HMAC) { + rc = TPM_RC_TYPE; + } + else if (obj->pub.parameters.keyedHashDetail.scheme.scheme + == TPM_ALG_HMAC) { if (hashAlg == TPM_ALG_NULL) { hashAlg = obj->pub.parameters.keyedHashDetail.scheme .details.hmac.hashAlg; @@ -9771,10 +9776,12 @@ static TPM_RC FwCmd_HMAC(FWTPM_CTX* ctx, TPM2_Packet* cmd, hashAlg = obj->pub.nameAlg; } - ht = FwGetWcHashType(hashAlg); - wcHashType = (int)ht; - if (wcHashType == WC_HASH_TYPE_NONE) { - rc = TPM_RC_HASH; + if (rc == 0) { + ht = FwGetWcHashType(hashAlg); + wcHashType = (int)ht; + if (wcHashType == WC_HASH_TYPE_NONE) { + rc = TPM_RC_HASH; + } } } @@ -9926,9 +9933,31 @@ static TPM_RC FwCmd_HMAC_Start(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (rc == 0) { TPM2_Packet_ParseU16(cmd, (UINT16*)&hashAlg); - wcHashType = FwGetWcHashType(hashAlg); - if (wcHashType == WC_HASH_TYPE_NONE) { - rc = TPM_RC_HASH; + if (obj->pub.parameters.keyedHashDetail.scheme.scheme != TPM_ALG_NULL && + obj->pub.parameters.keyedHashDetail.scheme.scheme != + TPM_ALG_HMAC) { + rc = TPM_RC_TYPE; + } + else if (obj->pub.parameters.keyedHashDetail.scheme.scheme + == TPM_ALG_HMAC) { + if (hashAlg == TPM_ALG_NULL) { + hashAlg = obj->pub.parameters.keyedHashDetail.scheme + .details.hmac.hashAlg; + } + else if (hashAlg != obj->pub.parameters.keyedHashDetail.scheme + .details.hmac.hashAlg) { + rc = TPM_RC_VALUE; + } + } + else if (hashAlg == TPM_ALG_NULL) { + rc = TPM_RC_VALUE; + } + + if (rc == 0) { + wcHashType = FwGetWcHashType(hashAlg); + if (wcHashType == WC_HASH_TYPE_NONE) { + rc = TPM_RC_HASH; + } } } From 8af0661b11285c48619442b40630baef3a68330e Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 14:39:42 -0700 Subject: [PATCH 16/57] F-14415 - Implement multi-bank event sequences --- src/fwtpm/fwtpm_command.c | 494 +++++++++++++++++++++++++++++++------- wolftpm/fwtpm/fwtpm.h | 2 + 2 files changed, 409 insertions(+), 87 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index c6a5ebcfb..9e6e8a5f9 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -431,6 +431,30 @@ static int FwGetPcrBankIndex(UINT16 hashAlg) } } +#ifndef FWTPM_NO_HASH_CMDS +static UINT16 FwGetPcrBankAlg(int bank) +{ + if (bank == FWTPM_PCR_BANK_SHA256) { + return TPM_ALG_SHA256; + } +#ifdef WOLFSSL_SHA384 + if (bank == FWTPM_PCR_BANK_SHA384) { + return TPM_ALG_SHA384; + } +#endif +#ifndef NO_SHA + if (bank == FWTPM_PCR_BANK_SHA1) { + return TPM_ALG_SHA1; + } +#endif + return TPM_ALG_NULL; +} + +typedef struct FWTPM_EventHashCopies { + wc_HashAlg hash[FWTPM_PCR_BANKS]; +} FWTPM_EventHashCopies; +#endif + /* KDFa, KDFe, and param encryption/decryption are now shared via * tpm2_param_enc.c (TPM2_KDFa, TPM2_KDFe, TPM2_ParamEnc_XOR, * TPM2_ParamEnc_AESCFB). See wolftpm/tpm2_param_enc.h */ @@ -4363,15 +4387,8 @@ static TPM_RC FwCmd_FlushContext(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* Protects a transient object, sequence, or session context for later reload. * Ordinary objects remain referenced by handle. Sequence state is serialized * without process-local pointers into an encrypted external blob. */ -#ifndef FWTPM_NO_CONTEXT -#define FWTPM_CTX_MAGIC 0x4657544Du /* 'FWTM' */ -#define FWTPM_CTX_VER 1u -#define FWTPM_CTX_SAVED_SEQUENCE (TRANSIENT_FIRST + 1u) -#define FWTPM_SEQ_CTX_PLAIN_MAX \ - (MAX_CONTEXT_SIZE - 8 - AES_BLOCK_SIZE - WC_SHA256_DIGEST_SIZE) -#define FWTPM_SEQ_CTX_WRAPPED_MAX (MAX_CONTEXT_SIZE - 8) - -#if !defined(FWTPM_NO_HASH_CMDS) || defined(WOLFTPM_MLDSA) +#if !defined(FWTPM_NO_HASH_CMDS) || \ + (defined(WOLFTPM_MLDSA) && !defined(FWTPM_NO_CONTEXT)) static int FwCopyHashContext(wc_HashAlg* dst, wc_HashAlg* src, enum wc_HashType hashType) { @@ -4440,8 +4457,79 @@ static int FwCopyHashContext(wc_HashAlg* dst, wc_HashAlg* src, } return rc; } + +#ifndef FWTPM_NO_HASH_CMDS +static int FwCopyHmacContext(Hmac* src, Hmac* dst) +{ +#if LIBWOLFSSL_VERSION_HEX >= 0x05009000 + return wc_HmacCopy(src, dst); +#else + int rc = 0; + + XMEMCPY(dst, src, sizeof(*dst)); + XMEMSET(&dst->hash, 0, sizeof(dst->hash)); + switch (src->macType) { + #ifndef NO_SHA + case WC_SHA: + rc = wc_ShaCopy(&src->hash.sha, &dst->hash.sha); + break; + #endif + #ifndef NO_SHA256 + case WC_SHA256: + rc = wc_Sha256Copy(&src->hash.sha256, &dst->hash.sha256); + break; + #endif + #ifdef WOLFSSL_SHA384 + case WC_SHA384: + rc = wc_Sha384Copy(&src->hash.sha384, &dst->hash.sha384); + break; + #endif + #ifdef WOLFSSL_SHA512 + case WC_SHA512: + rc = wc_Sha512Copy(&src->hash.sha512, &dst->hash.sha512); + break; + #endif + #if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_256) + case WC_SHA3_256: + rc = wc_Sha3_256_Copy(&src->hash.sha3, &dst->hash.sha3); + break; + #endif + #if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_384) + case WC_SHA3_384: + rc = wc_Sha3_384_Copy(&src->hash.sha3, &dst->hash.sha3); + break; + #endif + #if defined(WOLFSSL_SHA3) && !defined(WOLFSSL_NOSHA3_512) + case WC_SHA3_512: + rc = wc_Sha3_512_Copy(&src->hash.sha3, &dst->hash.sha3); + break; + #endif + #ifdef WOLFSSL_SM3 + case WC_SM3: + rc = wc_Sm3Copy(&src->hash.sm3, &dst->hash.sm3); + break; + #endif + default: + rc = BAD_FUNC_ARG; + break; + } + if (rc != 0) { + XMEMSET(dst, 0, sizeof(*dst)); + } + return rc; +#endif +} +#endif #endif +#ifndef FWTPM_NO_CONTEXT +#define FWTPM_CTX_MAGIC 0x4657544Du /* 'FWTM' */ +#define FWTPM_CTX_VER 1u +#define FWTPM_CTX_SAVED_SEQUENCE (TRANSIENT_FIRST + 1u) +#define FWTPM_SEQ_CTX_PLAIN_MAX \ + (MAX_CONTEXT_SIZE - 8 - AES_BLOCK_SIZE - WC_SHA256_DIGEST_SIZE) +#define FWTPM_SEQ_CTX_WRAPPED_MAX (MAX_CONTEXT_SIZE - 8) + static int FwIsSequenceHandle(FWTPM_CTX* ctx, TPM_HANDLE handle) { (void)ctx; @@ -4890,6 +4978,7 @@ static TPM_RC FwSerializeSequence(FWTPM_CTX* ctx, TPM_HANDLE handle, #ifndef FWTPM_NO_HASH_CMDS FWTPM_HashSeq* hashSeq; enum wc_HashType hashSeqType; + int bank; #endif packet.buf = plain; @@ -4913,7 +5002,18 @@ static TPM_RC FwSerializeSequence(FWTPM_CTX* ctx, TPM_HANDLE handle, TPM2_Packet_AppendU16(&packet, hashSeq->authValue.size); TPM2_Packet_AppendBytes(&packet, hashSeq->authValue.buffer, hashSeq->authValue.size); - if (hashSeq->isHmac) { + if (!hashSeq->isHmac && hashSeq->hashAlg == TPM_ALG_NULL) { + TPM2_Packet_AppendU8(&packet, hashSeq->eventBankMask); + rc = packet.overflow ? BUFFER_E : 0; + for (bank = 0; bank < FWTPM_PCR_BANKS && rc == 0; bank++) { + if ((hashSeq->eventBankMask & (1u << bank)) != 0u) { + rc = FwAppendHashState(&packet, + &hashSeq->ctx.eventHash[bank], + FwGetWcHashType(FwGetPcrBankAlg(bank))); + } + } + } + else if (hashSeq->isHmac) { rc = FwAppendHmacState(&packet, &hashSeq->ctx.hmac, hashSeqType, hashSeq->hmacKey, hashSeq->hmacKeySz); } @@ -5005,8 +5105,11 @@ static TPM_RC FwRestoreSequence(FWTPM_CTX* ctx, byte* plain, int plainSz, if (kind == FWTPM_SEQ_CTX_HASH) { FWTPM_HashSeq* seq = FwAllocHashSeq(ctx, loadedHandle); UINT8 isHmac = 0; + UINT8 eventMask = 0; UINT16 authSz = 0; enum wc_HashType hashType; + int isEvent; + int bank; if (seq == NULL) { return TPM_RC_OBJECT_MEMORY; @@ -5015,16 +5118,40 @@ static TPM_RC FwRestoreSequence(FWTPM_CTX* ctx, byte* plain, int plainSz, TPM2_Packet_ParseU8(&packet, &isHmac); TPM2_Packet_ParseU16(&packet, &authSz); hashType = FwGetWcHashType(seq->hashAlg); + isEvent = (!isHmac && seq->hashAlg == TPM_ALG_NULL); if (isHmac > 1u || authSz > sizeof(seq->authValue.buffer) || packet.pos + authSz > packet.size || - hashType == WC_HASH_TYPE_NONE) { + (!isEvent && hashType == WC_HASH_TYPE_NONE)) { rc = TPM_RC_INTEGRITY; } if (rc == 0) { seq->isHmac = isHmac; seq->authValue.size = authSz; TPM2_Packet_ParseBytes(&packet, seq->authValue.buffer, authSz); - if (seq->isHmac) { + if (isEvent) { + TPM2_Packet_ParseU8(&packet, &eventMask); + if (eventMask == 0 || + (eventMask & ~((1u << FWTPM_PCR_BANKS) - 1u)) != 0u) { + rc = TPM_RC_INTEGRITY; + } + for (bank = 0; bank < FWTPM_PCR_BANKS && rc == 0; bank++) { + if ((eventMask & (1u << bank)) != 0u) { + wc_HashAlg raw; + hashType = FwGetWcHashType(FwGetPcrBankAlg(bank)); + XMEMSET(&raw, 0, sizeof(raw)); + rc = FwParseRawHashState(&packet, hashType, &raw); + if (rc == 0) { + rc = FwCopyHashContext( + &seq->ctx.eventHash[bank], &raw, hashType); + } + TPM2_ForceZero(&raw, sizeof(raw)); + if (rc == 0) { + seq->eventBankMask |= (UINT8)(1u << bank); + } + } + } + } + else if (seq->isHmac) { rc = FwParseHmacState(&packet, &seq->ctx.hmac, hashType, NULL, 0, seq->hmacKey, &seq->hmacKeySz); } @@ -9862,9 +9989,19 @@ static FWTPM_HashSeq* FwFindHashSeq(FWTPM_CTX* ctx, TPM_HANDLE handle) static void FwFreeHashSeq(FWTPM_HashSeq* seq) { + int bank; + if (seq->isHmac) { wc_HmacFree(&seq->ctx.hmac); } + else if (seq->hashAlg == TPM_ALG_NULL) { + for (bank = 0; bank < FWTPM_PCR_BANKS; bank++) { + if ((seq->eventBankMask & (1u << bank)) != 0u) { + wc_HashFree(&seq->ctx.eventHash[bank], + FwGetWcHashType(FwGetPcrBankAlg(bank))); + } + } + } else { wc_HashFree(&seq->ctx.hash, FwGetWcHashType(seq->hashAlg)); } @@ -10021,7 +10158,9 @@ static TPM_RC FwCmd_HashSequenceStart(FWTPM_CTX* ctx, TPM2_Packet* cmd, TPMI_ALG_HASH hashAlg; FWTPM_HashSeq* seq = NULL; TPM_HANDLE seqHandle = 0; - enum wc_HashType wcHash; + enum wc_HashType wcHash = WC_HASH_TYPE_NONE; + UINT8 eventBanks = 0; + int bank; (void)cmdSize; @@ -10040,9 +10179,14 @@ static TPM_RC FwCmd_HashSequenceStart(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* Parse hashAlg */ TPM2_Packet_ParseU16(cmd, (UINT16*)&hashAlg); - wcHash = FwGetWcHashType(hashAlg); - if (wcHash == WC_HASH_TYPE_NONE) { - rc = TPM_RC_HASH; + if (hashAlg == TPM_ALG_NULL) { + eventBanks = (UINT8)((1u << FWTPM_PCR_BANKS) - 1u); + } + else { + wcHash = FwGetWcHashType(hashAlg); + if (wcHash == WC_HASH_TYPE_NONE) { + rc = TPM_RC_HASH; + } } } @@ -10058,7 +10202,20 @@ static TPM_RC FwCmd_HashSequenceStart(FWTPM_CTX* ctx, TPM2_Packet* cmd, seq->isHmac = 0; XMEMCPY(&seq->authValue, &auth, sizeof(TPM2B_AUTH)); - rc = wc_HashInit(&seq->ctx.hash, wcHash); + if (hashAlg == TPM_ALG_NULL) { + for (bank = 0; bank < FWTPM_PCR_BANKS && rc == 0; bank++) { + if ((eventBanks & (1u << bank)) != 0u) { + wcHash = FwGetWcHashType(FwGetPcrBankAlg(bank)); + rc = wc_HashInit(&seq->ctx.eventHash[bank], wcHash); + if (rc == 0) { + seq->eventBankMask |= (UINT8)(1u << bank); + } + } + } + } + else { + rc = wc_HashInit(&seq->ctx.hash, wcHash); + } if (rc != 0) { rc = TPM_RC_FAILURE; } @@ -10095,12 +10252,18 @@ static TPM_RC FwCmd_SequenceUpdate(FWTPM_CTX* ctx, TPM2_Packet* cmd, FWTPM_DECLARE_BUF(dataBuf, FWTPM_MAX_DATA_BUF); #ifndef FWTPM_NO_HASH_CMDS FWTPM_HashSeq* seq; + FWTPM_DECLARE_VAR(eventCopies, FWTPM_EventHashCopies); + UINT8 eventCopyMask = 0; + int bank; #endif #ifdef WOLFTPM_MLDSA FWTPM_SignSeq* signSeq = NULL; #endif FWTPM_ALLOC_BUF(dataBuf, FWTPM_MAX_DATA_BUF); +#ifndef FWTPM_NO_HASH_CMDS + FWTPM_CALLOC_VAR(eventCopies, FWTPM_EventHashCopies); +#endif if (cmdSize < TPM2_HEADER_SIZE + 4) { rc = TPM_RC_COMMAND_SIZE; @@ -10220,6 +10383,41 @@ static TPM_RC FwCmd_SequenceUpdate(FWTPM_CTX* ctx, TPM2_Packet* cmd, rc = TPM_RC_FAILURE; } } + else if (seq->hashAlg == TPM_ALG_NULL) { + for (bank = 0; bank < FWTPM_PCR_BANKS && rc == 0; bank++) { + if ((seq->eventBankMask & (1u << bank)) != 0u) { + rc = FwCopyHashContext(&eventCopies->hash[bank], + &seq->ctx.eventHash[bank], + FwGetWcHashType(FwGetPcrBankAlg(bank))); + if (rc == 0) { + eventCopyMask |= (UINT8)(1u << bank); + } + } + } + for (bank = 0; bank < FWTPM_PCR_BANKS && rc == 0; bank++) { + if ((eventCopyMask & (1u << bank)) != 0u) { + rc = wc_HashUpdate(&eventCopies->hash[bank], + FwGetWcHashType(FwGetPcrBankAlg(bank)), + dataBuf, dataSize); + } + } + if (rc == 0) { + for (bank = 0; bank < FWTPM_PCR_BANKS; bank++) { + if ((eventCopyMask & (1u << bank)) != 0u) { + wc_HashFree(&seq->ctx.eventHash[bank], + FwGetWcHashType(FwGetPcrBankAlg(bank))); + XMEMCPY(&seq->ctx.eventHash[bank], + &eventCopies->hash[bank], sizeof(wc_HashAlg)); + XMEMSET(&eventCopies->hash[bank], 0, + sizeof(wc_HashAlg)); + eventCopyMask &= (UINT8)~(1u << bank); + } + } + } + else { + rc = TPM_RC_FAILURE; + } + } else { rc = wc_HashUpdate(&seq->ctx.hash, FwGetWcHashType(seq->hashAlg), dataBuf, dataSize); @@ -10235,6 +10433,21 @@ static TPM_RC FwCmd_SequenceUpdate(FWTPM_CTX* ctx, TPM2_Packet* cmd, FwRspNoParams(rsp, cmdTag); } +#ifndef FWTPM_NO_HASH_CMDS + for (bank = 0; bank < FWTPM_PCR_BANKS; bank++) { + if ((eventCopyMask & (1u << bank)) != 0u) { + wc_HashFree(&eventCopies->hash[bank], + FwGetWcHashType(FwGetPcrBankAlg(bank))); + } + } + #ifdef WOLFTPM_SMALL_STACK + if (eventCopies != NULL) + #endif + { + TPM2_ForceZero(eventCopies, sizeof(*eventCopies)); + } + FWTPM_FREE_VAR(eventCopies); +#endif FWTPM_FREE_BUF(dataBuf); return rc; } @@ -10250,14 +10463,20 @@ static TPM_RC FwCmd_SequenceComplete(FWTPM_CTX* ctx, TPM2_Packet* cmd, UINT16 dataSize = 0; UINT32 hierarchy; FWTPM_DECLARE_BUF(dataBuf, FWTPM_MAX_DATA_BUF); + FWTPM_DECLARE_VAR(hashCopy, wc_HashAlg); + FWTPM_DECLARE_VAR(hmacCopy, Hmac); byte digest[TPM_MAX_DIGEST_SIZE]; int digestSz = 0; FWTPM_HashSeq* seq = NULL; TPMI_ALG_HASH hashAlg = TPM_ALG_NULL; int paramSzPos, paramStart; int trc; + int hashCopyInit = 0; + int hmacCopyInit = 0; FWTPM_ALLOC_BUF(dataBuf, FWTPM_MAX_DATA_BUF); + FWTPM_CALLOC_VAR(hashCopy, wc_HashAlg); + FWTPM_CALLOC_VAR(hmacCopy, Hmac); if (cmdSize < TPM2_HEADER_SIZE + 4) { rc = TPM_RC_COMMAND_SIZE; @@ -10274,6 +10493,9 @@ static TPM_RC FwCmd_SequenceComplete(FWTPM_CTX* ctx, TPM2_Packet* cmd, * by guessing its handle. FlushContext releases those slots. */ rc = TPM_RC_HANDLE; } + else if (!seq->isHmac && seq->hashAlg == TPM_ALG_NULL) { + rc = TPM_RC_MODE; + } else { hashAlg = seq->hashAlg; } @@ -10303,34 +10525,52 @@ static TPM_RC FwCmd_SequenceComplete(FWTPM_CTX* ctx, TPM2_Packet* cmd, TPM2_Packet_ParseU32(cmd, &hierarchy); digestSz = TPM2_GetHashDigestSize(seq->hashAlg); + if (FwGetHierarchySeed(ctx, hierarchy) == NULL) { + rc = TPM_RC_HIERARCHY; + } } /* Feed final data and finalize */ if (rc == 0) { if (seq->isHmac) { - if (dataSize > 0) { - rc = wc_HmacUpdate(&seq->ctx.hmac, dataBuf, dataSize); + rc = FwCopyHmacContext(&seq->ctx.hmac, hmacCopy); + if (rc == 0) { + hmacCopyInit = 1; + } + else { + rc = TPM_RC_FAILURE; + } + if (rc == 0 && dataSize > 0) { + rc = wc_HmacUpdate(hmacCopy, dataBuf, dataSize); if (rc != 0) { rc = TPM_RC_FAILURE; } } if (rc == 0) { - rc = wc_HmacFinal(&seq->ctx.hmac, digest); + rc = wc_HmacFinal(hmacCopy, digest); if (rc != 0) { rc = TPM_RC_FAILURE; } } } else { - if (dataSize > 0) { - rc = wc_HashUpdate(&seq->ctx.hash, + rc = FwCopyHashContext(hashCopy, &seq->ctx.hash, + FwGetWcHashType(seq->hashAlg)); + if (rc == 0) { + hashCopyInit = 1; + } + else { + rc = TPM_RC_FAILURE; + } + if (rc == 0 && dataSize > 0) { + rc = wc_HashUpdate(hashCopy, FwGetWcHashType(seq->hashAlg), dataBuf, dataSize); if (rc != 0) { rc = TPM_RC_FAILURE; } } if (rc == 0) { - rc = wc_HashFinal(&seq->ctx.hash, + rc = wc_HashFinal(hashCopy, FwGetWcHashType(seq->hashAlg), digest); if (rc != 0) { rc = TPM_RC_FAILURE; @@ -10339,10 +10579,6 @@ static TPM_RC FwCmd_SequenceComplete(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } - if (seq != NULL) { - FwFreeHashSeq(seq); - } - if (rc == 0) { /* Build response */ paramStart = FwRspParamsBegin(rsp, cmdTag, ¶mSzPos); @@ -10359,6 +10595,26 @@ static TPM_RC FwCmd_SequenceComplete(FWTPM_CTX* ctx, TPM2_Packet* cmd, FwRspParamsEnd(rsp, cmdTag, paramSzPos, paramStart); } + if (hmacCopyInit) { + wc_HmacFree(hmacCopy); + } + if (hashCopyInit) { + wc_HashFree(hashCopy, FwGetWcHashType(seq->hashAlg)); + } + #ifdef WOLFTPM_SMALL_STACK + if (hmacCopy != NULL) + #endif + { + TPM2_ForceZero(hmacCopy, sizeof(*hmacCopy)); + } + #ifdef WOLFTPM_SMALL_STACK + if (hashCopy != NULL) + #endif + { + TPM2_ForceZero(hashCopy, sizeof(*hashCopy)); + } + FWTPM_FREE_VAR(hmacCopy); + FWTPM_FREE_VAR(hashCopy); FWTPM_FREE_BUF(dataBuf); return rc; } @@ -10375,18 +10631,29 @@ static TPM_RC FwCmd_EventSequenceComplete(FWTPM_CTX* ctx, TPM2_Packet* cmd, UINT32 seqHandle; UINT16 dataSize = 0; FWTPM_DECLARE_BUF(dataBuf, FWTPM_MAX_DATA_BUF); - byte digest[TPM_MAX_DIGEST_SIZE]; - int digestSz = 0; - UINT16 seqHashAlg = 0; + FWTPM_DECLARE_BUF(digestBuf, + FWTPM_PCR_BANKS * TPM_MAX_DIGEST_SIZE); + FWTPM_DECLARE_BUF(newPcrBuf, + FWTPM_PCR_BANKS * TPM_MAX_DIGEST_SIZE); + FWTPM_DECLARE_VAR(eventCopies, FWTPM_EventHashCopies); + int digestSz[FWTPM_PCR_BANKS]; FWTPM_HashSeq* seq = NULL; int paramSzPos, paramStart; - int pcrIndex; + int pcrIndex = 0; int bank; + int bankCount = 0; + UINT8 extendBanks = 0; + UINT8 eventCopyMask = 0; enum wc_HashType wcHash; byte concat[TPM_MAX_DIGEST_SIZE * 2]; - byte newPcrDigest[TPM_MAX_DIGEST_SIZE]; FWTPM_ALLOC_BUF(dataBuf, FWTPM_MAX_DATA_BUF); + FWTPM_ALLOC_BUF(digestBuf, + FWTPM_PCR_BANKS * TPM_MAX_DIGEST_SIZE); + FWTPM_ALLOC_BUF(newPcrBuf, + FWTPM_PCR_BANKS * TPM_MAX_DIGEST_SIZE); + FWTPM_CALLOC_VAR(eventCopies, FWTPM_EventHashCopies); + XMEMSET(digestSz, 0, sizeof(digestSz)); if (cmdSize < TPM2_HEADER_SIZE + 8) { rc = TPM_RC_COMMAND_SIZE; @@ -10400,6 +10667,9 @@ static TPM_RC FwCmd_EventSequenceComplete(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (seq == NULL) { rc = TPM_RC_HANDLE; } + else if (seq->isHmac || seq->hashAlg != TPM_ALG_NULL) { + rc = TPM_RC_MODE; + } } /* Skip auth area */ @@ -10422,75 +10692,86 @@ static TPM_RC FwCmd_EventSequenceComplete(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (dataSize > 0) { TPM2_Packet_ParseBytes(cmd, dataBuf, dataSize); } + } - digestSz = TPM2_GetHashDigestSize(seq->hashAlg); + if (rc == 0 && pcrHandle <= PCR_LAST) { + pcrIndex = pcrHandle - PCR_FIRST; + if (!FwPcrLocalityAllowed(pcrIndex, ctx->activeLocality, 0)) { + rc = TPM_RC_LOCALITY; + } + else { + extendBanks = (UINT8)(seq->eventBankMask & + ctx->pcrAllocatedBanks); + } } - /* Feed final data and finalize the hash */ - if (rc == 0) { - if (seq->isHmac) { - if (dataSize > 0) { - rc = wc_HmacUpdate(&seq->ctx.hmac, dataBuf, dataSize); - if (rc != 0) { - rc = TPM_RC_FAILURE; - } - } + for (bank = 0; bank < FWTPM_PCR_BANKS && rc == 0; bank++) { + if ((seq->eventBankMask & (1u << bank)) != 0u) { + rc = FwCopyHashContext(&eventCopies->hash[bank], + &seq->ctx.eventHash[bank], + FwGetWcHashType(FwGetPcrBankAlg(bank))); if (rc == 0) { - rc = wc_HmacFinal(&seq->ctx.hmac, digest); - if (rc != 0) { - rc = TPM_RC_FAILURE; - } + eventCopyMask |= (UINT8)(1u << bank); + } + else { + rc = TPM_RC_FAILURE; } } - else { + } + + /* Feed and finalize copies so errors leave the sequence retryable. */ + for (bank = 0; bank < FWTPM_PCR_BANKS && rc == 0; bank++) { + if ((eventCopyMask & (1u << bank)) != 0u) { + UINT16 hashAlg = FwGetPcrBankAlg(bank); + byte* digest = digestBuf + bank * TPM_MAX_DIGEST_SIZE; + + wcHash = FwGetWcHashType(hashAlg); + digestSz[bank] = TPM2_GetHashDigestSize(hashAlg); if (dataSize > 0) { - rc = wc_HashUpdate(&seq->ctx.hash, - FwGetWcHashType(seq->hashAlg), dataBuf, dataSize); - if (rc != 0) { - rc = TPM_RC_FAILURE; - } + rc = wc_HashUpdate(&eventCopies->hash[bank], wcHash, + dataBuf, dataSize); } if (rc == 0) { - rc = wc_HashFinal(&seq->ctx.hash, - FwGetWcHashType(seq->hashAlg), digest); - if (rc != 0) { - rc = TPM_RC_FAILURE; - } + rc = wc_HashFinal(&eventCopies->hash[bank], wcHash, + digest); + } + if (rc != 0 || digestSz[bank] <= 0) { + rc = TPM_RC_FAILURE; + } + else { + bankCount++; } } } - /* Save hash alg before freeing sequence */ - if (rc == 0) { - seqHashAlg = seq->hashAlg; - } - - /* Extend the result into the PCR */ + /* Compute every new bank value before modifying the PCR. */ if (rc == 0 && pcrHandle <= PCR_LAST) { - pcrIndex = pcrHandle - PCR_FIRST; - /* This extends the target PCR, so enforce the extend locality from the - * source-of-truth table (DRTM PCRs 17-22 are restricted). */ - if (!FwPcrLocalityAllowed(pcrIndex, ctx->activeLocality, 0)) { - rc = TPM_RC_LOCALITY; + for (bank = 0; bank < FWTPM_PCR_BANKS && rc == 0; bank++) { + if ((extendBanks & (1u << bank)) != 0u) { + byte* digest = digestBuf + bank * TPM_MAX_DIGEST_SIZE; + byte* newPcr = newPcrBuf + bank * TPM_MAX_DIGEST_SIZE; + wcHash = FwGetWcHashType(FwGetPcrBankAlg(bank)); + XMEMCPY(concat, ctx->pcrDigest[pcrIndex][bank], + digestSz[bank]); + XMEMCPY(concat + digestSz[bank], digest, digestSz[bank]); + if (wc_Hash(wcHash, concat, digestSz[bank] * 2, + newPcr, digestSz[bank]) != 0) { + rc = TPM_RC_FAILURE; + } + } } - bank = FwGetPcrBankIndex(seqHashAlg); - if (rc == 0 && bank >= 0 && digestSz > 0) { - wcHash = FwGetWcHashType(seqHashAlg); - XMEMCPY(concat, ctx->pcrDigest[pcrIndex][bank], digestSz); - XMEMCPY(concat + digestSz, digest, digestSz); - if (wc_Hash(wcHash, concat, digestSz * 2, - newPcrDigest, digestSz) == 0) { - XMEMCPY(ctx->pcrDigest[pcrIndex][bank], - newPcrDigest, digestSz); - ctx->pcrUpdateCounter++; + if (rc == 0 && extendBanks != 0) { + for (bank = 0; bank < FWTPM_PCR_BANKS; bank++) { + if ((extendBanks & (1u << bank)) != 0u) { + XMEMCPY(ctx->pcrDigest[pcrIndex][bank], + newPcrBuf + bank * TPM_MAX_DIGEST_SIZE, + digestSz[bank]); + } } + ctx->pcrUpdateCounter++; } } - if (seq != NULL) { - FwFreeHashSeq(seq); - } - if (rc == 0) { #ifdef DEBUG_WOLFTPM printf("fwTPM: EventSequenceComplete(pcr=%d, seq=0x%x)\n", @@ -10500,15 +10781,34 @@ static TPM_RC FwCmd_EventSequenceComplete(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* Build response: TPML_DIGEST_VALUES */ paramStart = FwRspParamsBegin(rsp, cmdTag, ¶mSzPos); - /* count = 1 (only the hash alg used by the sequence) */ - TPM2_Packet_AppendU32(rsp, 1); - /* hashAlg + digest */ - TPM2_Packet_AppendU16(rsp, seqHashAlg); - TPM2_Packet_AppendBytes(rsp, digest, digestSz); + TPM2_Packet_AppendU32(rsp, (UINT32)bankCount); + for (bank = 0; bank < FWTPM_PCR_BANKS; bank++) { + if ((seq->eventBankMask & (1u << bank)) != 0u) { + TPM2_Packet_AppendU16(rsp, FwGetPcrBankAlg(bank)); + TPM2_Packet_AppendBytes(rsp, + digestBuf + bank * TPM_MAX_DIGEST_SIZE, + digestSz[bank]); + } + } FwRspParamsEnd(rsp, cmdTag, paramSzPos, paramStart); } + for (bank = 0; bank < FWTPM_PCR_BANKS; bank++) { + if ((eventCopyMask & (1u << bank)) != 0u) { + wc_HashFree(&eventCopies->hash[bank], + FwGetWcHashType(FwGetPcrBankAlg(bank))); + } + } + #ifdef WOLFTPM_SMALL_STACK + if (eventCopies != NULL) + #endif + { + TPM2_ForceZero(eventCopies, sizeof(*eventCopies)); + } + FWTPM_FREE_VAR(eventCopies); + FWTPM_FREE_BUF(newPcrBuf); + FWTPM_FREE_BUF(digestBuf); FWTPM_FREE_BUF(dataBuf); return rc; } @@ -19689,6 +19989,9 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, int rspTruncated = 0; /* Response did not fit the buffer */ int rspCryptoFailure = 0; /* No valid session response was built */ FWTPM_Session* pendingFlushSess = NULL; +#ifndef FWTPM_NO_HASH_CMDS + FWTPM_HashSeq* pendingFlushHashSeq = NULL; +#endif if (ctx == NULL || cmdBuf == NULL || rspBuf == NULL || rspSize == NULL) { return BAD_FUNC_ARG; @@ -20687,6 +20990,17 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, #ifndef FWTPM_NO_NV XMEMSET(ctx->activeCmdAuthIsPolicy, 0, sizeof(ctx->activeCmdAuthIsPolicy)); +#endif +#ifndef FWTPM_NO_HASH_CMDS + if (rc == TPM_RC_SUCCESS && cmdCode == TPM_CC_SequenceComplete) { + pendingFlushHashSeq = FwFindHashSeq(ctx, + FwLoadU32BE(cmdBuf + TPM2_HEADER_SIZE)); + } + else if (rc == TPM_RC_SUCCESS && + cmdCode == TPM_CC_EventSequenceComplete) { + pendingFlushHashSeq = FwFindHashSeq(ctx, + FwLoadU32BE(cmdBuf + TPM2_HEADER_SIZE + 4)); + } #endif /* A sessions-tagged FlushContext leaves its target session alive so the * dispatcher can use it to generate the response authorization area. */ @@ -20935,6 +21249,12 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, * its session target must not be released until that work is complete. */ FwFreeSession(pendingFlushSess); +#ifndef FWTPM_NO_HASH_CMDS + if (pendingFlushHashSeq != NULL) { + FwFreeHashSeq(pendingFlushHashSeq); + } +#endif + /* Deferred clear: flush transient objects after response auth is built. */ if (ctx->pendingClear) { int nvRc; diff --git a/wolftpm/fwtpm/fwtpm.h b/wolftpm/fwtpm/fwtpm.h index 0f353373c..81e4b9ad5 100644 --- a/wolftpm/fwtpm/fwtpm.h +++ b/wolftpm/fwtpm/fwtpm.h @@ -527,6 +527,7 @@ typedef struct FWTPM_HashSeq { TPM_HANDLE handle; /* Sequence handle (0x80xxxxxx) */ TPMI_ALG_HASH hashAlg; /* Hash algorithm for this sequence */ int isHmac; /* 1 if HMAC sequence, 0 if plain hash */ + UINT8 eventBankMask; /* Active banks when hashAlg is NULL */ TPM2B_AUTH authValue; /* Sequence auth (from HashSequenceStart) */ UINT16 hmacKeySz; byte hmacKey[MAX_SYM_DATA]; @@ -534,6 +535,7 @@ typedef struct FWTPM_HashSeq { union { wc_HashAlg hash; /* wolfCrypt hash context (isHmac == 0) */ Hmac hmac; /* wolfCrypt HMAC context (isHmac == 1) */ + wc_HashAlg eventHash[FWTPM_PCR_BANKS]; } ctx; #endif } FWTPM_HashSeq; From 9b263086ec9e6b8460e45af2fae8c1d5cda3b412 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 14:43:29 -0700 Subject: [PATCH 17/57] F-14416 - Preserve RSA OAEP labels --- src/fwtpm/fwtpm_command.c | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 9e6e8a5f9..cffb557e5 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -9398,6 +9398,8 @@ static TPM_RC FwCmd_RSA_Encrypt(FWTPM_CTX* ctx, TPM2_Packet* cmd, FWTPM_DECLARE_VAR(message, TPM2B_PUBLIC_KEY_RSA); UINT16 encScheme, encHashAlg; UINT16 labelSize; + byte label[sizeof(TPMT_HA) + 1]; + word32 oaepLabelSz = 0; FWTPM_Object* obj = NULL; FWTPM_DECLARE_VAR(rsaKey, RsaKey); int rsaKeyInit = 0; @@ -9408,6 +9410,7 @@ static TPM_RC FwCmd_RSA_Encrypt(FWTPM_CTX* ctx, TPM2_Packet* cmd, FWTPM_ALLOC_VAR(rsaKey, RsaKey); FWTPM_CALLOC_VAR(message, TPM2B_PUBLIC_KEY_RSA); FWTPM_ALLOC_BUF(outBuf, FWTPM_MAX_PUB_BUF); + XMEMSET(label, 0, sizeof(label)); if (cmdSize < TPM2_HEADER_SIZE + 4) { rc = TPM_RC_COMMAND_SIZE; @@ -9459,11 +9462,18 @@ static TPM_RC FwCmd_RSA_Encrypt(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* Parse label */ TPM2_Packet_ParseU16(cmd, &labelSize); - if (cmd->pos + labelSize > cmdSize) { + if (labelSize > sizeof(label) - 1) { + rc = TPM_RC_SIZE; + } + else if (cmd->pos + labelSize > cmdSize) { rc = TPM_RC_COMMAND_SIZE; } else { - cmd->pos += labelSize; + TPM2_Packet_ParseBytes(cmd, label, labelSize); + oaepLabelSz = labelSize; + if (oaepLabelSz > 0 && label[oaepLabelSz - 1] != 0) { + rc = TPM_RC_VALUE; + } } if (rc == 0 && encScheme == TPM_ALG_NULL) { @@ -9514,7 +9524,7 @@ static TPM_RC FwCmd_RSA_Encrypt(FWTPM_CTX* ctx, TPM2_Packet* cmd, outBuf, (word32)FWTPM_MAX_PUB_BUF, rsaKey, &ctx->rng, WC_RSA_OAEP_PAD, (enum wc_HashType)wcHashType, FwGetMgfType(oaepHash), - NULL, 0); + label, oaepLabelSz); } else if (encScheme == TPM_ALG_NULL) { /* Raw RSA (no padding) */ @@ -9565,6 +9575,8 @@ static TPM_RC FwCmd_RSA_Decrypt(FWTPM_CTX* ctx, TPM2_Packet* cmd, FWTPM_DECLARE_VAR(cipherText, TPM2B_PUBLIC_KEY_RSA); UINT16 decScheme, decHashAlg; UINT16 labelSize; + byte label[sizeof(TPMT_HA) + 1]; + word32 oaepLabelSz = 0; FWTPM_Object* obj = NULL; FWTPM_DECLARE_VAR(rsaKey, RsaKey); int rsaKeyInit = 0; @@ -9575,6 +9587,7 @@ static TPM_RC FwCmd_RSA_Decrypt(FWTPM_CTX* ctx, TPM2_Packet* cmd, FWTPM_ALLOC_VAR(rsaKey, RsaKey); FWTPM_CALLOC_VAR(cipherText, TPM2B_PUBLIC_KEY_RSA); FWTPM_ALLOC_BUF(outBuf, FWTPM_MAX_PUB_BUF); + XMEMSET(label, 0, sizeof(label)); if (cmdSize < TPM2_HEADER_SIZE + 4) { rc = TPM_RC_COMMAND_SIZE; @@ -9633,11 +9646,18 @@ static TPM_RC FwCmd_RSA_Decrypt(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* Parse label */ TPM2_Packet_ParseU16(cmd, &labelSize); - if (cmd->pos + labelSize > cmdSize) { + if (labelSize > sizeof(label) - 1) { + rc = TPM_RC_SIZE; + } + else if (cmd->pos + labelSize > cmdSize) { rc = TPM_RC_COMMAND_SIZE; } else { - cmd->pos += labelSize; + TPM2_Packet_ParseBytes(cmd, label, labelSize); + oaepLabelSz = labelSize; + if (oaepLabelSz > 0 && label[oaepLabelSz - 1] != 0) { + rc = TPM_RC_VALUE; + } } if (rc == 0 && decScheme == TPM_ALG_NULL) { @@ -9687,7 +9707,7 @@ static TPM_RC FwCmd_RSA_Decrypt(FWTPM_CTX* ctx, TPM2_Packet* cmd, outBuf, (word32)FWTPM_MAX_PUB_BUF, rsaKey, WC_RSA_OAEP_PAD, (enum wc_HashType)wcHashType, FwGetMgfType(oaepHash), - NULL, 0); + label, oaepLabelSz); } else if (decScheme == TPM_ALG_NULL) { /* Raw RSA (no padding) */ From 908d0fb436a6de757e9139f6c7a529f43d583681 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 14:46:58 -0700 Subject: [PATCH 18/57] F-14417 - Preserve loaded object authorization --- src/fwtpm/fwtpm_command.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index cffb557e5..8b86ac5ec 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -7114,16 +7114,13 @@ static TPM_RC FwCmd_ObjectChangeAuth(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } - /* Re-wrap private key with new auth, then update the live object */ + /* Re-wrap the private key; the loaded object keeps its current auth. */ if (rc == 0) { rc = FwWrapPrivate(ctx, parent, &ctx->rng, &obj->name, obj->pub.type, &newAuth, obj->privKey, obj->privKeySize, &outPrivate); if (rc != 0) { rc = TPM_RC_FAILURE; } - else { - XMEMCPY(&obj->authValue, &newAuth, sizeof(newAuth)); - } } #ifdef DEBUG_WOLFTPM From 5aac3cccb512c152c8c326a10f06deda46fe00bf Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 14:52:07 -0700 Subject: [PATCH 19/57] F-14432 - Reject unsupported policy expiration --- docs/fwtpm/overview.md | 2 +- src/fwtpm/fwtpm_command.c | 22 ++++++++++++++++------ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/docs/fwtpm/overview.md b/docs/fwtpm/overview.md index e9a63b312..4de30998d 100644 --- a/docs/fwtpm/overview.md +++ b/docs/fwtpm/overview.md @@ -341,7 +341,7 @@ The eight Version 185 PQC commands (`TPM2_Encapsulate`, `TPM2_Decapsulate`, `TPM Implemented counts assume RSA, ECC, AES, and all feature groups, with SPDM, PQC (except in the Version 185 row), and the vendor test command disabled. Enabling `WOLFTPM_SPDM` adds `PolicyTransportSPDM` (one more implemented code). The Version 185 row includes the eight PQC commands. -Known limitations: `TPM2_SelfTest` is a minimal smoke test and `TPM2_IncrementalSelfTest` is a stub, as noted above, and `SU_STATE` resume is not supported (see the lifecycle section). The command coverage above should not be read as full conformance with the TCG specification. +Known limitations: `TPM2_SelfTest` is a minimal smoke test and `TPM2_IncrementalSelfTest` is a stub, as noted above, `SU_STATE` resume is not supported (see the lifecycle section), and `TPM2_PolicySecret` rejects nonzero expiration because expiring policy tickets are not supported. The command coverage above should not be read as full conformance with the TCG specification. ## Startup and Shutdown Lifecycle diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 8b86ac5ec..2e738f1c0 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -12136,16 +12136,26 @@ static TPM_RC FwCmd_PolicySecret(FWTPM_CTX* ctx, TPM2_Packet* cmd, TPM2_Packet_ParseBytes(cmd, policyRef, policyRefSz); } TPM2_Packet_ParseU32(cmd, (UINT32*)&expiration); - (void)expiration; + + /* Expiring policy authorizations require session timeout state and + * a TPMT_TK_AUTH that PolicyTicket can validate. Do not silently + * accept the restriction until that support is available. */ + if (expiration != 0) { + rc = TPM_RC_VALUE; + } #ifdef DEBUG_WOLFTPM - printf("fwTPM: PolicySecret(authHandle=0x%x, session=0x%x)\n", - authHandle, sessHandle); + if (rc == 0) { + printf("fwTPM: PolicySecret(authHandle=0x%x, session=0x%x)\n", + authHandle, sessHandle); + } #endif - sess = FwFindSession(ctx, sessHandle); - if (sess == NULL) { - rc = TPM_RC_VALUE; + if (rc == 0) { + sess = FwFindSession(ctx, sessHandle); + if (sess == NULL) { + rc = TPM_RC_VALUE; + } } } From 18cbe970eea55d0c19e2779e34248da95907c74b Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 14:56:19 -0700 Subject: [PATCH 20/57] F-14682 - Release saved sessions on flush --- src/fwtpm/fwtpm_command.c | 47 ++++++++++++++++++++++++++++++++------- wolftpm/fwtpm/fwtpm.h | 1 + 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 2e738f1c0..6e97eff5f 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -3757,6 +3757,35 @@ static void FwFlushAllSessions(FWTPM_CTX* ctx) } } +#ifndef FWTPM_NO_CONTEXT +static void FwRemoveSavedContext(FWTPM_CTX* ctx, int idx) +{ + int i; + + if (idx < 0 || idx >= ctx->contextLiveCount) { + return; + } + for (i = idx; i < ctx->contextLiveCount - 1; i++) { + ctx->contextLive[i] = ctx->contextLive[i + 1]; + ctx->contextLiveHandle[i] = ctx->contextLiveHandle[i + 1]; + } + ctx->contextLiveCount--; +} + +static int FwFlushSavedSession(FWTPM_CTX* ctx, TPM_HANDLE handle) +{ + int i; + + for (i = 0; i < ctx->contextLiveCount; i++) { + if (ctx->contextLiveHandle[i] == handle) { + FwRemoveSavedContext(ctx, i); + return 1; + } + } + return 0; +} +#endif + void FWTPM_ResetCommandClient(FWTPM_CTX* ctx) { #if !defined(FWTPM_NO_HASH_CMDS) || defined(WOLFTPM_MLDSA) @@ -4326,7 +4355,13 @@ static TPM_RC FwCmd_FlushContext(FWTPM_CTX* ctx, TPM2_Packet* cmd, (flushHandle & 0xFF000000) == POLICY_SESSION_FIRST) { FWTPM_Session* sess = FwFindSession(ctx, flushHandle); if (sess == NULL) { + #ifndef FWTPM_NO_CONTEXT + if (!FwFlushSavedSession(ctx, flushHandle)) { + rc = TPM_RC_HANDLE; + } + #else rc = TPM_RC_HANDLE; + #endif } else if (cmdTag != TPM_ST_SESSIONS) { FwFreeSession(sess); @@ -5379,7 +5414,9 @@ static TPM_RC FwCmd_ContextSave(FWTPM_CTX* ctx, TPM2_Packet* cmd, ctx->contextSeqCounter++; if (isSession) { /* Record so this session context loads at most once, any order */ - ctx->contextLive[ctx->contextLiveCount++] = ctx->contextSeqCounter; + ctx->contextLive[ctx->contextLiveCount] = ctx->contextSeqCounter; + ctx->contextLiveHandle[ctx->contextLiveCount] = saveHandle; + ctx->contextLiveCount++; } seqHi = (UINT32)(ctx->contextSeqCounter >> 32); seqLo = (UINT32)(ctx->contextSeqCounter & 0xFFFFFFFFu); @@ -5727,13 +5764,7 @@ static TPM_RC FwCmd_ContextLoad(FWTPM_CTX* ctx, TPM2_Packet* cmd, printf("fwTPM: ContextLoad(handle=0x%x)\n", savedHandle); #endif /* Consume a loaded session context so it cannot be replayed. */ - if (liveIdx >= 0 && liveIdx < ctx->contextLiveCount) { - for (liveScan = liveIdx; liveScan < ctx->contextLiveCount - 1; - liveScan++) { - ctx->contextLive[liveScan] = ctx->contextLive[liveScan + 1]; - } - ctx->contextLiveCount--; - } + FwRemoveSavedContext(ctx, liveIdx); TPM2_Packet_AppendU32(rsp, savedHandle); FwRspFinalize(rsp, TPM_ST_NO_SESSIONS, TPM_RC_SUCCESS); } diff --git a/wolftpm/fwtpm/fwtpm.h b/wolftpm/fwtpm/fwtpm.h index 81e4b9ad5..4c799c550 100644 --- a/wolftpm/fwtpm/fwtpm.h +++ b/wolftpm/fwtpm/fwtpm.h @@ -913,6 +913,7 @@ typedef struct FWTPM_CTX { /* Live (saved-but-not-yet-loaded) context sequences. A context loads at * most once and saved contexts may load in any order. */ UINT64 contextLive[FWTPM_MAX_OBJECTS + FWTPM_MAX_SESSIONS]; + TPM_HANDLE contextLiveHandle[FWTPM_MAX_OBJECTS + FWTPM_MAX_SESSIONS]; int contextLiveCount; #endif From 72f76533541eb39edec88043475b58c5042727ad Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 15:05:40 -0700 Subject: [PATCH 21/57] F-14683 - Preserve the NV counter high-water mark --- src/fwtpm/fwtpm_command.c | 64 +++++++++++++++++++++++++++++++++------ src/fwtpm/fwtpm_nv.c | 31 ++++++++++++++++++- tests/fwtpm_unit_tests.c | 7 ++--- wolftpm/fwtpm/fwtpm.h | 1 + 4 files changed, 87 insertions(+), 16 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 6e97eff5f..0590b9936 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -14284,6 +14284,29 @@ static TPM_RC FwNvCheckAccess(TPM_HANDLE authHandle, return TPM_RC_SUCCESS; } +static TPM_RC FwPreserveNvCounterMax(FWTPM_CTX* ctx, + const FWTPM_NvIndex* nv) +{ + UINT32 nt = (nv->nvPublic.attributes & TPMA_NV_TPM_NT) >> 4; + UINT64 counter; + UINT64 oldMax; + TPM_RC rc = TPM_RC_SUCCESS; + + if (nt != TPM_NT_COUNTER || !nv->written) { + return TPM_RC_SUCCESS; + } + counter = FwLoadU64BE(nv->data); + oldMax = ctx->nvCounterMax; + if (counter > ctx->nvCounterMax) { + ctx->nvCounterMax = counter; + } + rc = FWTPM_NV_SaveFlags(ctx); + if (rc != 0) { + ctx->nvCounterMax = oldMax; + } + return rc; +} + /* --- TPM2_NV_DefineSpace (CC 0x012A) --- */ static TPM_RC FwCmd_NV_DefineSpace(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, TPM2_Packet* rsp, UINT16 cmdTag) @@ -14439,9 +14462,12 @@ static TPM_RC FwCmd_NV_DefineSpace(FWTPM_CTX* ctx, TPM2_Packet* cmd, } slot->written = 0; - /* Initialize NV_COUNTER/NV_BITS to zero */ + /* Counter is not written until its first increment. */ nt = (int)((slot->nvPublic.attributes & TPMA_NV_TPM_NT) >> 4); - if (nt == TPM_NT_COUNTER || nt == TPM_NT_BITS) { + if (nt == TPM_NT_COUNTER) { + XMEMSET(slot->data, 0, 8); + } + else if (nt == TPM_NT_BITS) { XMEMSET(slot->data, 0, 8); slot->written = 1; } @@ -14503,6 +14529,9 @@ static TPM_RC FwCmd_NV_UndefineSpace(FWTPM_CTX* ctx, TPM2_Packet* cmd, rc = TPM_RC_NV_AUTHORIZATION; } + if (rc == 0) { + rc = FwPreserveNvCounterMax(ctx, nv); + } if (rc == 0) { rc = FWTPM_NV_DeleteNvIndex(ctx, nvHandle); if (rc == 0) { @@ -14551,7 +14580,10 @@ static TPM_RC FwCmd_NV_UndefineSpaceSpecial(FWTPM_CTX* ctx, TPM2_Packet* cmd, #ifdef DEBUG_WOLFTPM printf("fwTPM: NV_UndefineSpaceSpecial(nv=0x%x)\n", nvHandle); #endif - rc = FWTPM_NV_DeleteNvIndex(ctx, nvHandle); + rc = FwPreserveNvCounterMax(ctx, nv); + if (rc == 0) { + rc = FWTPM_NV_DeleteNvIndex(ctx, nvHandle); + } if (rc == 0) { XMEMSET(nv, 0, sizeof(FWTPM_NvIndex)); FwRspNoParams(rsp, cmdTag); @@ -14927,15 +14959,26 @@ static TPM_RC FwCmd_NV_Increment(FWTPM_CTX* ctx, TPM2_Packet* cmd, byte oldData[8]; int oldWritten = nv->written; UINT32 oldAttrs = nv->nvPublic.attributes; + UINT64 oldMax = ctx->nvCounterMax; XMEMCPY(oldData, nv->data, 8); - /* Read big-endian counter, increment, write back */ - counter = FwLoadU64BE(nv->data); - counter++; - FwStoreU64BE(nv->data, counter); - nv->written = 1; - nv->nvPublic.attributes |= 0x20000000UL; /* TPMA_NV_WRITTEN */ + /* A new counter starts above the TPM-wide high-water mark. */ + counter = nv->written ? FwLoadU64BE(nv->data) : ctx->nvCounterMax; + if (counter == ~(UINT64)0) { + rc = TPM_RC_NV_RANGE; + } + else { + counter++; + FwStoreU64BE(nv->data, counter); + nv->written = 1; + nv->nvPublic.attributes |= 0x20000000UL; /* TPMA_NV_WRITTEN */ + if (counter > ctx->nvCounterMax) { + ctx->nvCounterMax = counter; + } + } - rc = FWTPM_NV_SaveNvIndex(ctx, (int)(nv - ctx->nvIndices)); + if (rc == 0) { + rc = FWTPM_NV_SaveNvIndex(ctx, (int)(nv - ctx->nvIndices)); + } if (rc == 0) { FwRspNoParams(rsp, cmdTag); @@ -14944,6 +14987,7 @@ static TPM_RC FwCmd_NV_Increment(FWTPM_CTX* ctx, TPM2_Packet* cmd, XMEMCPY(nv->data, oldData, 8); nv->written = oldWritten; nv->nvPublic.attributes = oldAttrs; + ctx->nvCounterMax = oldMax; } TPM2_ForceZero(oldData, sizeof(oldData)); } diff --git a/src/fwtpm/fwtpm_nv.c b/src/fwtpm/fwtpm_nv.c index 4eb6ab454..f1c447afc 100644 --- a/src/fwtpm/fwtpm_nv.c +++ b/src/fwtpm/fwtpm_nv.c @@ -1417,6 +1417,18 @@ static int FwNvProcessEntry(FWTPM_CTX* ctx, UINT16 tag, FwNvUnmarshalU8(value, &vPos, vMax, &shutdownState); ctx->shutdownStateSaved = shutdownState ? 1 : 0; } + #ifndef FWTPM_NO_NV + if (vPos + 8 <= vMax) { + UINT32 lo = 0, hi = 0; + UINT64 counterMax; + FwNvUnmarshalU32(value, &vPos, vMax, &lo); + FwNvUnmarshalU32(value, &vPos, vMax, &hi); + counterMax = (UINT64)lo | ((UINT64)hi << 32); + if (counterMax > ctx->nvCounterMax) { + ctx->nvCounterMax = counterMax; + } + } + #endif break; } @@ -1482,6 +1494,13 @@ static int FwNvProcessEntry(FWTPM_CTX* ctx, UINT16 tag, FWTPM_NvIndex nv; int slot; if (FwNvUnmarshalNvIndex(value, &vPos, vMax, &nv) == 0) { + UINT32 nt = (nv.nvPublic.attributes & TPMA_NV_TPM_NT) >> 4; + if (nt == TPM_NT_COUNTER && nv.written) { + UINT64 counter = FwLoadU64BE(nv.data); + if (counter > ctx->nvCounterMax) { + ctx->nvCounterMax = counter; + } + } slot = FwNvFindOrAllocNvSlot(ctx, nv.nvPublic.nvIndex); if (slot >= 0) { XMEMCPY(&ctx->nvIndices[slot], &nv, @@ -2211,6 +2230,12 @@ int FWTPM_NV_Save(FWTPM_CTX* ctx) FwNvMarshalU32(buf, &pos, bufSz, ctx->restartCount); /* A full snapshot authorizes resume only with its final marker. */ FwNvMarshalU8(buf, &pos, bufSz, 0); + #ifndef FWTPM_NO_NV + FwNvMarshalU32(buf, &pos, bufSz, + (UINT32)(ctx->nvCounterMax & 0xFFFFFFFF)); + FwNvMarshalU32(buf, &pos, bufSz, + (UINT32)(ctx->nvCounterMax >> 32)); + #endif rc = FwNvAppendEntry(ctx, FWTPM_NV_TAG_FLAGS, buf, (UINT16)pos); } @@ -2519,7 +2544,7 @@ int FWTPM_NV_SaveFlags(FWTPM_CTX* ctx) return TPM_RC_SUCCESS; #else int rc; - byte buf[1 + 12 + 4 + 4 + 1 + 4 + 1]; + byte buf[1 + 12 + 4 + 4 + 1 + 4 + 1 + 8]; word32 pos = 0; if (ctx == NULL) { @@ -2552,6 +2577,10 @@ int FWTPM_NV_SaveFlags(FWTPM_CTX* ctx) FwNvMarshalU32(buf, &pos, sizeof(buf), ctx->restartCount); FwNvMarshalU8(buf, &pos, sizeof(buf), ctx->shutdownStateSaved ? 1 : 0); + FwNvMarshalU32(buf, &pos, sizeof(buf), + (UINT32)(ctx->nvCounterMax & 0xFFFFFFFF)); + FwNvMarshalU32(buf, &pos, sizeof(buf), + (UINT32)(ctx->nvCounterMax >> 32)); rc = FwNvAppendEntry(ctx, FWTPM_NV_TAG_FLAGS, buf, (UINT16)pos); return rc; diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index 12bb2ec97..bcd4a3dd2 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -14249,7 +14249,7 @@ static void test_fwtpm_state_change_nv_failure(void) FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); AssertIntEQ(GetRspRC(gRsp), TPM_RC_FAILURE); - /* Restore persistence and confirm the counter was rolled back (== 0). */ + /* Restore persistence and confirm the counter remains unwritten. */ AssertIntEQ(FWTPM_NV_SetHAL(&ctx, &oldHal), TPM_RC_SUCCESS); pos = 0; PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; @@ -14263,10 +14263,7 @@ static void test_fwtpm_state_change_nv_failure(void) PutU32BE(gCmd + 2, (UINT32)pos); rspSize = 0; FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); - AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); - AssertIntEQ(GetU16BE(gRsp + 14), 8); - AssertIntEQ((int)GetU32BE(gRsp + 16), 0); - AssertIntEQ((int)GetU32BE(gRsp + 20), 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_NV_UNINITIALIZED); /* Remove the counter while owner auth is still empty. */ pos = 0; diff --git a/wolftpm/fwtpm/fwtpm.h b/wolftpm/fwtpm/fwtpm.h index 4c799c550..b091643b7 100644 --- a/wolftpm/fwtpm/fwtpm.h +++ b/wolftpm/fwtpm/fwtpm.h @@ -829,6 +829,7 @@ typedef struct FWTPM_CTX { * cost of the NV command group, so omit them when that group is disabled. */ #ifndef FWTPM_NO_NV FWTPM_NvIndex nvIndices[FWTPM_MAX_NV_INDICES]; + UINT64 nvCounterMax; /* Highest NV counter value ever assigned */ #endif /* Hash sequence slots */ From 4b6b564ef1428c1a585ec82a388b87d61ef53bcf Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 15:07:54 -0700 Subject: [PATCH 22/57] F-14685 - Validate objects before persistence --- src/fwtpm/fwtpm_command.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 0590b9936..803a8a4e8 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -6636,6 +6636,16 @@ static TPM_RC FwCmd_EvictControl(FWTPM_CTX* ctx, TPM2_Packet* cmd, rc = TPM_RC_HANDLE; } } + if (rc == 0 && (obj->hierarchy == TPM_RH_NULL || + obj->privKeySize == 0 || + (obj->pub.objectAttributes & TPMA_OBJECT_stClear) != 0)) { + rc = TPM_RC_ATTRIBUTES; + } + if (rc == 0 && + ((authHandle == TPM_RH_PLATFORM) != + (obj->hierarchy == TPM_RH_PLATFORM))) { + rc = TPM_RC_HIERARCHY; + } /* Check if persistent handle already in use */ if (rc == 0) { From 63ef0d2cb9ba34cfaf43f0d4162e17956e79074e Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 15:09:37 -0700 Subject: [PATCH 23/57] F-14696 - Require CertifyCreation tickets --- src/fwtpm/fwtpm_command.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 803a8a4e8..4bb02deff 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -16170,7 +16170,10 @@ static TPM_RC FwCmd_CertifyCreation(FWTPM_CTX* ctx, TPM2_Packet* cmd, } /* creationTicket verification per TPM 2.0 Part 3 Section 18.3 */ - if (rc == 0 && cmd->pos + 8 <= cmdSize) { + if (rc == 0 && cmd->pos + 8 > cmdSize) { + rc = TPM_RC_COMMAND_SIZE; + } + if (rc == 0) { byte ticketDigest[TPM_MAX_DIGEST_SIZE]; TPM2_Packet_ParseU16(cmd, &tag); From ce2388e6e3a478a3d07a4bfdcbb1b246fc5e43ba Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 15:12:29 -0700 Subject: [PATCH 24/57] F-14697 - Reject fixed imported objects --- src/fwtpm/fwtpm_command.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 4bb02deff..c15af193e 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -7773,6 +7773,10 @@ static TPM_RC FwCmd_Import(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (rc == 0) { rc = FwValidateMlTemplate(&objectPublic->publicArea, 0); } + if (rc == 0 && (objectPublic->publicArea.objectAttributes & + (TPMA_OBJECT_fixedTPM | TPMA_OBJECT_fixedParent)) != 0) { + rc = TPM_RC_ATTRIBUTES; + } #ifdef WOLFTPM_V185 if (rc == 0) { rc = FwValidateLimitedAttributes(&objectPublic->publicArea); @@ -7843,6 +7847,12 @@ static TPM_RC FwCmd_Import(FWTPM_CTX* ctx, TPM2_Packet* cmd, } (void)symMode; + if (rc == 0 && (objectPublic->publicArea.objectAttributes & + TPMA_OBJECT_encryptedDuplication) != 0 && + (encKeySz == 0 || symSeedSz == 0 || symAlg == TPM_ALG_NULL)) { + rc = TPM_RC_ATTRIBUTES; + } + #ifdef DEBUG_WOLFTPM if (rc == 0) { printf("fwTPM: Import(parent=0x%x, objectType=%d, symAlg=0x%x, " From 4ecda22bc25f1c4651a255e977613eeaa12c5386 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 15:14:26 -0700 Subject: [PATCH 25/57] F-14698 - Reject unsupported PIN NV indices --- src/fwtpm/fwtpm_command.c | 15 ++++++--------- src/fwtpm/fwtpm_nv.c | 7 +++++++ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index c15af193e..6f0db2471 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -14417,11 +14417,11 @@ static TPM_RC FwCmd_NV_DefineSpace(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* Validate NV type (TPM_NT) — reject reserved values */ if (rc == 0) { UINT32 nt = (publicInfo.nvPublic.attributes & TPMA_NV_TPM_NT) >> 4; - if (nt > TPM_NT_PIN_PASS && nt != TPM_NT_PIN_FAIL) { + if (nt != TPM_NT_ORDINARY && nt != TPM_NT_COUNTER && + nt != TPM_NT_BITS && nt != TPM_NT_EXTEND) { rc = TPM_RC_ATTRIBUTES; } - else if ((nt == TPM_NT_COUNTER || nt == TPM_NT_BITS || - nt == TPM_NT_PIN_FAIL || nt == TPM_NT_PIN_PASS) && + else if ((nt == TPM_NT_COUNTER || nt == TPM_NT_BITS) && publicInfo.nvPublic.dataSize != 8) { rc = TPM_RC_SIZE; } @@ -14694,14 +14694,11 @@ static TPM_RC FwCmd_NV_Write(FWTPM_CTX* ctx, TPM2_Packet* cmd, nv->nvPublic.attributes, 1, ctx->activeCmdAuthIsPolicy[0]); } - /* Per TPM 2.0 Part 3 Section 31.3, NV_Write only valid for ordinary and PIN - * indices. Counter, Bits, and Extend types must use their dedicated - * commands (NV_Increment, NV_SetBits, NV_Extend). Allowing NV_Write on - * counters would let an attacker reset a monotonic counter to zero. */ + /* Counter, Bits, and Extend use dedicated commands. PIN indices are not + * accepted until their counter/limit authorization semantics exist. */ if (rc == 0) { UINT32 nt = (nv->nvPublic.attributes & TPMA_NV_TPM_NT) >> 4; - if (nt != TPM_NT_ORDINARY && nt != TPM_NT_PIN_FAIL && - nt != TPM_NT_PIN_PASS) { + if (nt != TPM_NT_ORDINARY) { rc = TPM_RC_ATTRIBUTES; } } diff --git a/src/fwtpm/fwtpm_nv.c b/src/fwtpm/fwtpm_nv.c index f1c447afc..8f78eb2b6 100644 --- a/src/fwtpm/fwtpm_nv.c +++ b/src/fwtpm/fwtpm_nv.c @@ -509,6 +509,13 @@ static int FwNvUnmarshalNvPublic(const byte* buf, word32* pos, word32 maxSz, if (rc == 0) { rc = FwNvUnmarshalU32(buf, pos, maxSz, &nvPub->attributes); } + if (rc == 0) { + UINT32 nt = (nvPub->attributes & TPMA_NV_TPM_NT) >> 4; + if (nt != TPM_NT_ORDINARY && nt != TPM_NT_COUNTER && + nt != TPM_NT_BITS && nt != TPM_NT_EXTEND) { + rc = TPM_RC_FAILURE; + } + } /* Reserved TPMA_NV bits [9:8] and [24:20] must be clear (Part 2 * Sec.13.4); a crafted journal entry could otherwise install an index * with undefined access-control semantics. */ From 52e157b6c55d39f488a61636482defa88633ef4a Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 15:16:11 -0700 Subject: [PATCH 26/57] F-15704 - Exclude bound auth from session HMACs --- src/fwtpm/fwtpm_command.c | 91 +++++++++++++++++---- tests/fwtpm_unit_tests.c | 165 ++++++++++++++++++++++++++++++++++++-- wolftpm/fwtpm/fwtpm.h | 2 + 3 files changed, 236 insertions(+), 22 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 6f0db2471..d0d298566 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -459,20 +459,27 @@ typedef struct FWTPM_EventHashCopies { * tpm2_param_enc.c (TPM2_KDFa, TPM2_KDFe, TPM2_ParamEnc_XOR, * TPM2_ParamEnc_AESCFB). See wolftpm/tpm2_param_enc.h */ +static int FwSessionIsBoundToEntity(FWTPM_CTX* ctx, + const FWTPM_Session* sess, TPM_HANDLE handle); +static void FwLookupEntityAuth(FWTPM_CTX* ctx, TPM_HANDLE handle, + const byte** authVal, int* authValSz); +static int FwCtAuthCompare(const byte* password, int pwSz, + const byte* authVal, int avSz); + #ifndef FWTPM_NO_PARAM_ENC /* Build the parameter-encryption key = sessionKey || authValue, where authValue * is the bind authValue when the encrypt session authorizes its own bind entity * (TPM 2.0 Part 1 "Symmetric Encrypt" - the encrypt key keeps the authorized * entity's authValue even when bound). authHandle is the handle the session * authorizes (0 for a secondary encrypt-only session). */ -static int FwBuildParamEncKey(FWTPM_Session* sess, TPM_HANDLE authHandle, - byte* keyBuf, int keyBufMax, int* keyBufSz) +static int FwBuildParamEncKey(FWTPM_CTX* ctx, FWTPM_Session* sess, + TPM_HANDLE authHandle, byte* keyBuf, int keyBufMax, int* keyBufSz) { int sz = sess->sessionKey.size; if (sz < 0 || sz > keyBufMax) return TPM_RC_FAILURE; XMEMCPY(keyBuf, sess->sessionKey.buffer, sz); - if (sess->bindHandle != 0 && sess->bindHandle == authHandle && + if (FwSessionIsBoundToEntity(ctx, sess, authHandle) && sess->bindAuth.size > 0 && sz + sess->bindAuth.size <= keyBufMax) { XMEMCPY(keyBuf + sz, sess->bindAuth.buffer, sess->bindAuth.size); sz += sess->bindAuth.size; @@ -489,8 +496,8 @@ static int FwParamDecryptCmd(FWTPM_CTX* ctx, FWTPM_Session* sess, byte keyBuf[TPM_MAX_DIGEST_SIZE * 2]; int keyBufSz = 0; - rc = FwBuildParamEncKey(sess, authHandle, keyBuf, (int)sizeof(keyBuf), - &keyBufSz); + rc = FwBuildParamEncKey(ctx, sess, authHandle, keyBuf, + (int)sizeof(keyBuf), &keyBufSz); if (rc != 0) return rc; @@ -513,7 +520,6 @@ static int FwParamDecryptCmd(FWTPM_CTX* ctx, FWTPM_Session* sess, } TPM2_ForceZero(keyBuf, sizeof(keyBuf)); - (void)ctx; return rc; } @@ -525,8 +531,8 @@ static int FwParamEncryptRsp(FWTPM_CTX* ctx, FWTPM_Session* sess, byte keyBuf[TPM_MAX_DIGEST_SIZE * 2]; int keyBufSz = 0; - rc = FwBuildParamEncKey(sess, authHandle, keyBuf, (int)sizeof(keyBuf), - &keyBufSz); + rc = FwBuildParamEncKey(ctx, sess, authHandle, keyBuf, + (int)sizeof(keyBuf), &keyBufSz); if (rc != 0) return rc; @@ -550,7 +556,6 @@ static int FwParamEncryptRsp(FWTPM_CTX* ctx, FWTPM_Session* sess, } TPM2_ForceZero(keyBuf, sizeof(keyBuf)); - (void)ctx; return rc; } #endif /* !FWTPM_NO_PARAM_ENC */ @@ -693,6 +698,40 @@ static int FwGetEntityName(FWTPM_CTX* ctx, TPM_HANDLE handle, return 0; } +static int FwSessionIsBoundToEntity(FWTPM_CTX* ctx, + const FWTPM_Session* sess, TPM_HANDLE handle) +{ + byte entityName[sizeof(sess->bindName.name)]; + const byte* entityAuth = NULL; + int entityNameSz; + int entityAuthSz = 0; + + if (!sess->isBound) + return 0; + + entityNameSz = FwGetEntityName(ctx, handle, entityName, + (int)sizeof(entityName)); + if (entityNameSz != (int)sess->bindName.size || + XMEMCMP(entityName, sess->bindName.name, entityNameSz) != 0) { + return 0; + } + + FwLookupEntityAuth(ctx, handle, &entityAuth, &entityAuthSz); + return FwCtAuthCompare(sess->bindAuth.buffer, sess->bindAuth.size, + entityAuth, entityAuthSz) == 0; +} + +#ifdef WOLFTPM_FWTPM_UNIT_TEST +int FWTPM_TestSessionIsBoundToEntity(FWTPM_CTX* ctx, + const FWTPM_Session* sess, TPM_HANDLE handle); + +int FWTPM_TestSessionIsBoundToEntity(FWTPM_CTX* ctx, + const FWTPM_Session* sess, TPM_HANDLE handle) +{ + return FwSessionIsBoundToEntity(ctx, sess, handle); +} +#endif + /** \brief Look up the authValue for an entity by its TPM handle. * Searches hierarchies, NV indices, loaded objects, and hash sequences. */ static void FwLookupEntityAuth(FWTPM_CTX* ctx, TPM_HANDLE handle, @@ -11338,6 +11377,7 @@ static TPM_RC FwCmd_StartAuthSession(FWTPM_CTX* ctx, TPM2_Packet* cmd, TPM2B_AUTH bindAuth; const byte* bindAuthVal = NULL; int bindAuthSz = 0; + int bindNameSz; XMEMSET(&bindAuth, 0, sizeof(bindAuth)); FwLookupEntityAuth(ctx, bind, &bindAuthVal, &bindAuthSz); @@ -11353,10 +11393,22 @@ static TPM_RC FwCmd_StartAuthSession(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (bindAuthSz > 0) { XMEMCPY(bindAuth.buffer, bindAuthVal, bindAuthSz); } - sess->bindHandle = bind; + bindNameSz = FwGetEntityName(ctx, bind, + sess->bindName.name, + (int)sizeof(sess->bindName.name)); + if (bindNameSz <= 0) { + rc = TPM_RC_HANDLE; + } + else { + sess->bindName.size = (UINT16)bindNameSz; + sess->bindHandle = bind; + sess->isBound = 1; + } #ifndef FWTPM_NO_DA - sess->isLockoutBound = bind == TPM_RH_LOCKOUT; - sess->isDaBound = !FwHandleIsNoDA(ctx, bind); + if (rc == 0) { + sess->isLockoutBound = bind == TPM_RH_LOCKOUT; + sess->isDaBound = !FwHandleIsNoDA(ctx, bind); + } #endif } if (rc == 0 && bindAuth.size > 0) { @@ -19787,8 +19839,7 @@ static int FwAuthSlotDAUse(FWTPM_CTX* ctx, TPM_HANDLE entityH, (sess->isPasswordPolicy || sess->isAuthValuePolicy)))) { usesEntityAuth = 1; } - usesBindAuth = sess != NULL && cmdHmacSize > 0 && - sess->bindHandle != 0; + usesBindAuth = sess != NULL && cmdHmacSize > 0 && sess->isBound; if (usesEntityAuth) { if (entityH == TPM_RH_LOCKOUT) { daUse |= FW_AUTH_USES_LOCKOUT; @@ -20237,6 +20288,7 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, UINT16 passwordSize; byte cmdHmac[TPM_MAX_DIGEST_SIZE]; /* HMAC from command */ UINT16 cmdHmacSize; + int omitAuthValue; /* Request HMAC used the bound-session key */ } cmdAuths[FWTPM_MAX_CMD_AUTHS]; int cmdAuthCnt = 0; int cpStart = 0; /* Start of command parameters (after auth area) */ @@ -21021,6 +21073,13 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, authVal = NULL; authValSz = 0; } + cmdAuths[hj].omitAuthValue = + hSess->sessionType == TPM_SE_HMAC && + FwSessionIsBoundToEntity(ctx, hSess, entityH); + if (cmdAuths[hj].omitAuthValue) { + authVal = NULL; + authValSz = 0; + } /* Compute expected command HMAC (nonceCaller first, then nonceTPM) */ rc = (TPM_RC)FwComputeSessionHmac(hSess, cpHash, cpHashSz, @@ -21309,6 +21368,10 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, authVal = NULL; authValSz = 0; } + if (cmdAuths[j].omitAuthValue) { + authVal = NULL; + authValSz = 0; + } cryptoRc = FwComputeSessionHmac(sess, rpHash, rpHashSz, rspAttribs, authVal, authValSz, NULL, NULL, 1 /* isResponse */, diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index bcd4a3dd2..a4fba19b5 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -10332,6 +10332,39 @@ static UINT32 StartSessionHelper(FWTPM_CTX* ctx, UINT8 sessType) return GetU32BE(gRsp + TPM2_HEADER_SIZE); } +#ifdef WOLFTPM_FWTPM_UNIT_TEST +int FWTPM_TestSessionIsBoundToEntity(FWTPM_CTX* ctx, + const FWTPM_Session* sess, TPM_HANDLE handle); + +static void test_fwtpm_bound_session_auth_change(void) +{ + FWTPM_CTX ctx; + FWTPM_Session sess; + static const byte oldAuth[] = {0x11, 0x22, 0x33, 0x44}; + static const byte newAuth[] = {0x55, 0x66, 0x77, 0x88}; + + XMEMSET(&ctx, 0, sizeof(ctx)); + XMEMSET(&sess, 0, sizeof(sess)); + ctx.ownerAuth.size = (UINT16)sizeof(oldAuth); + XMEMCPY(ctx.ownerAuth.buffer, oldAuth, sizeof(oldAuth)); + sess.isBound = 1; + sess.bindName.size = 4; + PutU32BE(sess.bindName.name, TPM_RH_OWNER); + sess.bindAuth.size = (UINT16)sizeof(oldAuth); + XMEMCPY(sess.bindAuth.buffer, oldAuth, sizeof(oldAuth)); + + AssertIntEQ(FWTPM_TestSessionIsBoundToEntity(&ctx, &sess, + TPM_RH_OWNER), 1); + XMEMCPY(ctx.ownerAuth.buffer, newAuth, sizeof(newAuth)); + AssertIntEQ(FWTPM_TestSessionIsBoundToEntity(&ctx, &sess, + TPM_RH_OWNER), 0); + + TPM2_ForceZero(&ctx, sizeof(ctx)); + TPM2_ForceZero(&sess, sizeof(sess)); + fwtpm_pass("Bound session invalidated by auth change:", 0); +} +#endif + #ifndef FWTPM_NO_POLICY /* Helper: send a policy command that takes only sessionHandle */ static TPM_RC SendPolicyCmd(FWTPM_CTX* ctx, UINT32 cc, UINT32 sessHandle) @@ -15702,17 +15735,18 @@ static void test_fwtpm_param_enc_session_selection(void) fwtpm_pass("Param-enc follows decrypt/encrypt session:", 0); } -/* HMAC-SHA256 with an empty key over cpHash, nonces and attributes */ -static void ComputeAuthHmac(const byte* cpHash, const byte* nonceCaller, - const byte* nonceTPM, UINT16 nonceTPMSz, const byte* nonceExtra, +/* HMAC-SHA256 over a parameter hash, nonces and session attributes. */ +static void ComputeAuthHmac(const byte* key, UINT16 keySz, + const byte* pHash, const byte* nonceFirst, UINT16 nonceFirstSz, + const byte* nonceSecond, UINT16 nonceSecondSz, const byte* nonceExtra, UINT16 nonceExtraSz, UINT8 attribs, byte* out) { Hmac hmac; AssertIntEQ(wc_HmacInit(&hmac, NULL, INVALID_DEVID), 0); - AssertIntEQ(wc_HmacSetKey(&hmac, WC_SHA256, NULL, 0), 0); - AssertIntEQ(wc_HmacUpdate(&hmac, cpHash, WC_SHA256_DIGEST_SIZE), 0); - AssertIntEQ(wc_HmacUpdate(&hmac, nonceCaller, 16), 0); - AssertIntEQ(wc_HmacUpdate(&hmac, nonceTPM, nonceTPMSz), 0); + AssertIntEQ(wc_HmacSetKey(&hmac, WC_SHA256, key, keySz), 0); + AssertIntEQ(wc_HmacUpdate(&hmac, pHash, WC_SHA256_DIGEST_SIZE), 0); + AssertIntEQ(wc_HmacUpdate(&hmac, nonceFirst, nonceFirstSz), 0); + AssertIntEQ(wc_HmacUpdate(&hmac, nonceSecond, nonceSecondSz), 0); if (nonceExtraSz > 0) { AssertIntEQ(wc_HmacUpdate(&hmac, nonceExtra, nonceExtraSz), 0); } @@ -15721,6 +15755,117 @@ static void ComputeAuthHmac(const byte* cpHash, const byte* nonceCaller, wc_HmacFree(&hmac); } +/* A command that changes the bound entity's authValue must use the same + * request-time bound-session key decision for its response HMAC. */ +static void test_fwtpm_bound_auth_change_response_hmac(void) +{ + FWTPM_CTX ctx; + FWTPM_Session* sess = NULL; + static const byte sessionKey[] = {0x10, 0x20, 0x30, 0x40}; + static const byte newAuth[] = {0x51, 0x52, 0x53, 0x54}; + byte nonceCaller[16]; + byte nonceTPM[TPM_MAX_DIGEST_SIZE]; + byte cpHash[WC_SHA256_DIGEST_SIZE]; + byte rpHash[WC_SHA256_DIGEST_SIZE]; + byte authHmac[WC_SHA256_DIGEST_SIZE]; + byte expected[WC_SHA256_DIGEST_SIZE]; + byte hashInput[8]; + UINT16 nonceTPMSz = 0; + UINT16 rspNonceSz; + UINT16 rspHmacSz; + UINT8 attrs = TPMA_SESSION_continueSession; + UINT8 rspAttrs; + UINT32 sessH; + int authStart; + int off; + int pos; + int rspSize = 0; + int i; + wc_Sha256 sha; + + XMEMSET(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), TPM_RC_SUCCESS); + sessH = StartParamEncSessionHelper(&ctx, TPM_SE_HMAC, TPM_ALG_NULL, + nonceTPM, &nonceTPMSz); + AssertIntNE(sessH, 0); + for (i = 0; i < FWTPM_MAX_SESSIONS; i++) { + if (ctx.sessions[i].used && ctx.sessions[i].handle == sessH) { + sess = &ctx.sessions[i]; + break; + } + } + AssertNotNull(sess); + if (sess == NULL) { + FWTPM_Cleanup(&ctx); + return; + } + sess->isBound = 1; + sess->bindName.size = 4; + PutU32BE(sess->bindName.name, TPM_RH_OWNER); + sess->bindAuth.size = 0; + sess->sessionKey.size = (UINT16)sizeof(sessionKey); + XMEMCPY(sess->sessionKey.buffer, sessionKey, sizeof(sessionKey)); + + XMEMSET(nonceCaller, 0x6A, sizeof(nonceCaller)); + PutU32BE(hashInput, TPM_CC_HierarchyChangeAuth); + PutU32BE(hashInput + 4, TPM_RH_OWNER); + AssertIntEQ(wc_InitSha256(&sha), 0); + AssertIntEQ(wc_Sha256Update(&sha, hashInput, sizeof(hashInput)), 0); + PutU16BE(hashInput, (UINT16)sizeof(newAuth)); + XMEMCPY(hashInput + 2, newAuth, sizeof(newAuth)); + AssertIntEQ(wc_Sha256Update(&sha, hashInput, 2 + sizeof(newAuth)), 0); + AssertIntEQ(wc_Sha256Final(&sha, cpHash), 0); + wc_Sha256Free(&sha); + ComputeAuthHmac(sessionKey, (UINT16)sizeof(sessionKey), cpHash, + nonceCaller, (UINT16)sizeof(nonceCaller), nonceTPM, nonceTPMSz, + NULL, 0, attrs, authHmac); + + pos = BuildCmdHeader(gCmd, TPM_ST_SESSIONS, 0, + TPM_CC_HierarchyChangeAuth); + PutU32BE(gCmd + pos, TPM_RH_OWNER); pos += 4; + authStart = pos; + pos += 4; + PutU32BE(gCmd + pos, sessH); pos += 4; + PutU16BE(gCmd + pos, (UINT16)sizeof(nonceCaller)); pos += 2; + XMEMCPY(gCmd + pos, nonceCaller, sizeof(nonceCaller)); + pos += sizeof(nonceCaller); + gCmd[pos++] = attrs; + PutU16BE(gCmd + pos, (UINT16)sizeof(authHmac)); pos += 2; + XMEMCPY(gCmd + pos, authHmac, sizeof(authHmac)); pos += sizeof(authHmac); + PutU32BE(gCmd + authStart, (UINT32)(pos - authStart - 4)); + PutU16BE(gCmd + pos, (UINT16)sizeof(newAuth)); pos += 2; + XMEMCPY(gCmd + pos, newAuth, sizeof(newAuth)); pos += sizeof(newAuth); + PutU32BE(gCmd + 2, (UINT32)pos); + + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(ctx.ownerAuth.size, sizeof(newAuth)); + AssertIntEQ(XMEMCMP(ctx.ownerAuth.buffer, newAuth, sizeof(newAuth)), 0); + + XMEMSET(hashInput, 0, sizeof(hashInput)); + PutU32BE(hashInput + 4, TPM_CC_HierarchyChangeAuth); + AssertIntEQ(wc_InitSha256(&sha), 0); + AssertIntEQ(wc_Sha256Update(&sha, hashInput, sizeof(hashInput)), 0); + AssertIntEQ(wc_Sha256Final(&sha, rpHash), 0); + wc_Sha256Free(&sha); + off = RspSessOffset(gRsp, 0); + rspNonceSz = GetU16BE(gRsp + off); + AssertIntLE(rspNonceSz, TPM_MAX_DIGEST_SIZE); + rspAttrs = gRsp[off + 2 + rspNonceSz]; + rspHmacSz = GetU16BE(gRsp + off + 3 + rspNonceSz); + AssertIntEQ(rspHmacSz, WC_SHA256_DIGEST_SIZE); + ComputeAuthHmac(sessionKey, (UINT16)sizeof(sessionKey), rpHash, + gRsp + off + 2, rspNonceSz, nonceCaller, + (UINT16)sizeof(nonceCaller), NULL, 0, rspAttrs, expected); + AssertIntEQ(XMEMCMP(expected, gRsp + off + 5 + rspNonceSz, + sizeof(expected)), 0); + + FlushHandle(&ctx, sessH); + FWTPM_Cleanup(&ctx); + (void)remove(FWTPM_NV_FILE); + fwtpm_pass("Bound auth change response HMAC:", 0); +} + /* TPM2_HMAC authorized by an HMAC session, plus a param-enc session. With * bind clear the auth HMAC omits that session's nonce, which must fail. */ static int BuildBoundHmacCmd(byte* buf, UINT32 keyH, const byte* name, @@ -15747,7 +15892,7 @@ static int BuildBoundHmacCmd(byte* buf, UINT32 keyH, const byte* name, AssertIntEQ(wc_Sha256Update(&sha, params, 2 + dataSz + 2), 0); AssertIntEQ(wc_Sha256Final(&sha, cpHash), 0); wc_Sha256Free(&sha); - ComputeAuthHmac(cpHash, nonceCaller, nonceH, nonceHSz, + ComputeAuthHmac(NULL, 0, cpHash, nonceCaller, 16, nonceH, nonceHSz, nonceE, bind ? nonceESz : 0, cont, authHmac); pos = BuildCmdHeader(buf, TPM_ST_SESSIONS, 0, TPM_CC_HMAC); @@ -18249,6 +18394,7 @@ int fwtpm_unit_tests(int argc, char *argv[]) #if !defined(FWTPM_NO_PARAM_ENC) && FWTPM_MAX_SESSIONS >= 2 test_fwtpm_param_enc_session_selection(); test_fwtpm_param_enc_session_bound_to_auth(); + test_fwtpm_bound_auth_change_response_hmac(); #endif #endif /* !FWTPM_NO_HASH_CMDS */ #if defined(WOLFTPM_FWTPM_UNIT_TEST) && defined(WOLFTPM_SMALL_STACK) @@ -18271,6 +18417,9 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_start_hmac_session(); test_fwtpm_start_policy_session(); test_fwtpm_start_trial_session(); +#ifdef WOLFTPM_FWTPM_UNIT_TEST + test_fwtpm_bound_session_auth_change(); +#endif /* Auth */ test_fwtpm_hierarchy_change_auth(); diff --git a/wolftpm/fwtpm/fwtpm.h b/wolftpm/fwtpm/fwtpm.h index b091643b7..00ee4b580 100644 --- a/wolftpm/fwtpm/fwtpm.h +++ b/wolftpm/fwtpm/fwtpm.h @@ -603,7 +603,9 @@ typedef struct FWTPM_Session { TPM2B_NONCE nonceCaller; /* Last caller nonce received */ TPM2B_AUTH sessionKey; /* Session HMAC key (from KDFa) */ TPM2B_AUTH bindAuth; /* Auth of bound entity */ + TPM2B_NAME bindName; /* Name of bound entity */ TPM_HANDLE bindHandle; /* Bound entity handle (0 if unbound) */ + int isBound; /* Session has a bound entity */ #ifndef FWTPM_NO_DA int isDaBound; /* Bound authValue has DA protection */ int isLockoutBound; /* Bound to the lockout hierarchy */ From 00cbd877e79c13da9bb95a609123146e9d716578 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 15:18:04 -0700 Subject: [PATCH 27/57] F-15705 - Limit PCR read digest lists --- src/fwtpm/fwtpm_command.c | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index d0d298566..12abac172 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -2838,12 +2838,6 @@ static TPM_RC FwCmd_PCR_Read(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, } if (rc == 0) { - /* pcrUpdateCounter */ - TPM2_Packet_AppendU32(rsp, ctx->pcrUpdateCounter); - - /* Echo TPML_PCR_SELECTION */ - TPM2_Packet_AppendU32(rsp, pcrSelCount); - numSel = pcrSelCount; for (s = 0; s < numSel && rc == 0; s++) { @@ -2869,18 +2863,45 @@ static TPM_RC FwCmd_PCR_Read(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, if (rc != 0) { break; } + } + } + + if (rc == 0) { + /* TPML_DIGEST holds at most eight entries. Trim the returned + * selection first so it describes exactly the emitted digests. */ + for (s = 0; s < numSel; s++) { + int bank = FwGetPcrBankIndex(selections[s].hashAlg); + int j, pcr; + + for (j = 0; j < selections[s].sizeOfSelect; j++) { + for (pcr = 0; pcr < 8; pcr++) { + if (selections[s].pcrSelect[j] & (1 << pcr)) { + int pcrIndex = j * 8 + pcr; + if (bank < 0 || pcrIndex >= IMPLEMENTATION_PCR || + totalDigests >= 8) { + selections[s].pcrSelect[j] &= (byte)~(1 << pcr); + } + else { + totalDigests++; + } + } + } + } + } - /* Echo selection to response */ + TPM2_Packet_AppendU32(rsp, ctx->pcrUpdateCounter); + TPM2_Packet_AppendU32(rsp, pcrSelCount); + for (s = 0; s < numSel; s++) { + int j; TPM2_Packet_AppendU16(rsp, selections[s].hashAlg); TPM2_Packet_AppendU8(rsp, selections[s].sizeOfSelect); for (j = 0; j < selections[s].sizeOfSelect; j++) { TPM2_Packet_AppendU8(rsp, selections[s].pcrSelect[j]); } } - } - if (rc == 0) { /* TPML_DIGEST: placeholder for count, then digests */ + totalDigests = 0; digestCountPos = rsp->pos; TPM2_Packet_AppendU32(rsp, 0); From 7e35a9871698196e1fd70cac1614a70900caa5b6 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 15:19:54 -0700 Subject: [PATCH 28/57] F-15706 - Honor allocated PCR event banks --- src/fwtpm/fwtpm_command.c | 94 ++++++++++++++------------------------- 1 file changed, 34 insertions(+), 60 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 12abac172..51086bfcc 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -431,7 +431,6 @@ static int FwGetPcrBankIndex(UINT16 hashAlg) } } -#ifndef FWTPM_NO_HASH_CMDS static UINT16 FwGetPcrBankAlg(int bank) { if (bank == FWTPM_PCR_BANK_SHA256) { @@ -450,6 +449,7 @@ static UINT16 FwGetPcrBankAlg(int bank) return TPM_ALG_NULL; } +#ifndef FWTPM_NO_HASH_CMDS typedef struct FWTPM_EventHashCopies { wc_HashAlg hash[FWTPM_PCR_BANKS]; } FWTPM_EventHashCopies; @@ -3229,68 +3229,42 @@ static TPM_RC FwCmd_PCR_Event(FWTPM_CTX* ctx, TPM2_Packet* cmd, TPM2_Packet_ParseBytes(cmd, eventData, eventSize); } - if (rc == 0) { - /* SHA-256 bank */ - bankAlgs[0] = TPM_ALG_SHA256; - digestSz[0] = TPM2_GetHashDigestSize(TPM_ALG_SHA256); - if (digestSz[0] > 0) { - byte* dRow = digestBuf + 0 * TPM_MAX_DIGEST_SIZE; - enum wc_HashType wc0 = FwGetWcHashType(TPM_ALG_SHA256); - rc = wc_Hash(wc0, eventData, eventSize, dRow, digestSz[0]); - if (rc != 0) { - rc = TPM_RC_FAILURE; - } - if (rc == 0) { - XMEMCPY(concat, - ctx->pcrDigest[pcrIndex][FWTPM_PCR_BANK_SHA256], - digestSz[0]); - XMEMCPY(concat + digestSz[0], dRow, digestSz[0]); - rc = wc_Hash(wc0, concat, digestSz[0] * 2, - newPcr, digestSz[0]); - if (rc != 0) { - rc = TPM_RC_FAILURE; - } - } - if (rc == 0) { - XMEMCPY(ctx->pcrDigest[pcrIndex][FWTPM_PCR_BANK_SHA256], - newPcr, digestSz[0]); - bankCount++; - } - } + for (b = 0; b < FWTPM_PCR_BANKS && rc == 0; b++) { + UINT16 hashAlg; + enum wc_HashType wcHash; + byte* eventDigest; - #ifdef WOLFSSL_SHA384 - /* SHA-384 bank */ + if ((ctx->pcrAllocatedBanks & (1u << b)) == 0) { + continue; + } + hashAlg = FwGetPcrBankAlg(b); + wcHash = FwGetWcHashType(hashAlg); + bankAlgs[bankCount] = hashAlg; + digestSz[bankCount] = TPM2_GetHashDigestSize(hashAlg); + eventDigest = digestBuf + bankCount * TPM_MAX_DIGEST_SIZE; + if (hashAlg == TPM_ALG_NULL || digestSz[bankCount] <= 0 || + wcHash == WC_HASH_TYPE_NONE) { + rc = TPM_RC_FAILURE; + break; + } + rc = wc_Hash(wcHash, eventData, eventSize, eventDigest, + digestSz[bankCount]); if (rc == 0) { - bankAlgs[1] = TPM_ALG_SHA384; - digestSz[1] = TPM2_GetHashDigestSize(TPM_ALG_SHA384); - if (digestSz[1] > 0) { - byte* dRow = digestBuf + 1 * TPM_MAX_DIGEST_SIZE; - enum wc_HashType wc1 = FwGetWcHashType(TPM_ALG_SHA384); - rc = wc_Hash(wc1, eventData, eventSize, - dRow, digestSz[1]); - if (rc != 0) { - rc = TPM_RC_FAILURE; - } - if (rc == 0) { - XMEMCPY(concat, - ctx->pcrDigest[pcrIndex][FWTPM_PCR_BANK_SHA384], - digestSz[1]); - XMEMCPY(concat + digestSz[1], dRow, digestSz[1]); - rc = wc_Hash(wc1, concat, digestSz[1] * 2, - newPcr, digestSz[1]); - if (rc != 0) { - rc = TPM_RC_FAILURE; - } - } - if (rc == 0) { - XMEMCPY( - ctx->pcrDigest[pcrIndex][FWTPM_PCR_BANK_SHA384], - newPcr, digestSz[1]); - bankCount++; - } - } + XMEMCPY(concat, ctx->pcrDigest[pcrIndex][b], + digestSz[bankCount]); + XMEMCPY(concat + digestSz[bankCount], eventDigest, + digestSz[bankCount]); + rc = wc_Hash(wcHash, concat, digestSz[bankCount] * 2, + newPcr, digestSz[bankCount]); + } + if (rc == 0) { + XMEMCPY(ctx->pcrDigest[pcrIndex][b], newPcr, + digestSz[bankCount]); + bankCount++; + } + else { + rc = TPM_RC_FAILURE; } - #endif } if (rc == 0) { From 6d5b55c439211f1efed4b1aecc5a286268cc46ee Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 15:22:03 -0700 Subject: [PATCH 29/57] F-15707 - Bound dictionary attack thresholds --- docs/fwtpm/overview.md | 2 +- src/fwtpm/fwtpm_command.c | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/fwtpm/overview.md b/docs/fwtpm/overview.md index 4de30998d..49fd8eb26 100644 --- a/docs/fwtpm/overview.md +++ b/docs/fwtpm/overview.md @@ -341,7 +341,7 @@ The eight Version 185 PQC commands (`TPM2_Encapsulate`, `TPM2_Decapsulate`, `TPM Implemented counts assume RSA, ECC, AES, and all feature groups, with SPDM, PQC (except in the Version 185 row), and the vendor test command disabled. Enabling `WOLFTPM_SPDM` adds `PolicyTransportSPDM` (one more implemented code). The Version 185 row includes the eight PQC commands. -Known limitations: `TPM2_SelfTest` is a minimal smoke test and `TPM2_IncrementalSelfTest` is a stub, as noted above, `SU_STATE` resume is not supported (see the lifecycle section), and `TPM2_PolicySecret` rejects nonzero expiration because expiring policy tickets are not supported. The command coverage above should not be read as full conformance with the TCG specification. +Known limitations: `TPM2_SelfTest` is a minimal smoke test and `TPM2_IncrementalSelfTest` is a stub, as noted above, `SU_STATE` resume is not supported (see the lifecycle section), `TPM2_PolicySecret` rejects nonzero expiration because expiring policy tickets are not supported, and `TPM2_DictionaryAttackParameters` limits `newMaxTries` to 65535. The command coverage above should not be read as full conformance with the TCG specification. ## Startup and Shutdown Lifecycle diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 51086bfcc..18a79d247 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -15419,8 +15419,9 @@ static TPM_RC FwCmd_DictionaryAttackParameters(FWTPM_CTX* ctx, TPM2_Packet_ParseU32(cmd, &lockoutRecovery); } - /* newMaxTries of 0 would permanently disable lockout enforcement */ - if (rc == 0 && newMaxTries == 0) { + /* Reject values the NV loader cannot preserve across restart. */ + if (rc == 0 && (newMaxTries == 0 || + newMaxTries > FWTPM_DA_MAX_TRIES_LIMIT)) { rc = TPM_RC_VALUE; } From 865b8ae13c874bb6ce01c1ed84f63e5501349e63 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 15:23:24 -0700 Subject: [PATCH 30/57] F-15708 - Propagate NV extend hash failures --- src/fwtpm/fwtpm_command.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 18a79d247..ad82aefbf 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -14953,16 +14953,26 @@ static TPM_RC FwCmd_NV_Extend(FWTPM_CTX* ctx, TPM2_Packet* cmd, byte oldData[TPM_MAX_DIGEST_SIZE]; int oldWritten = nv->written; UINT32 oldAttrs = nv->nvPublic.attributes; + int hashRc; XMEMCPY(oldData, nv->data, hSz); - wc_HashUpdate(hashCtx, wcHash, nv->data, hSz); - wc_HashUpdate(hashCtx, wcHash, dataBuf, dataSize); - wc_HashFinal(hashCtx, wcHash, newVal); - XMEMCPY(nv->data, newVal, hSz); + hashRc = wc_HashUpdate(hashCtx, wcHash, nv->data, hSz); + if (hashRc == 0) { + hashRc = wc_HashUpdate(hashCtx, wcHash, dataBuf, dataSize); + } + if (hashRc == 0) { + hashRc = wc_HashFinal(hashCtx, wcHash, newVal); + } + if (hashRc != 0) { + rc = TPM_RC_FAILURE; + } - nv->written = 1; - nv->nvPublic.attributes |= 0x20000000UL; /* TPMA_NV_WRITTEN */ + if (rc == 0) { + XMEMCPY(nv->data, newVal, hSz); + nv->written = 1; + nv->nvPublic.attributes |= 0x20000000UL; /* TPMA_NV_WRITTEN */ - rc = FWTPM_NV_SaveNvIndex(ctx, (int)(nv - ctx->nvIndices)); + rc = FWTPM_NV_SaveNvIndex(ctx, (int)(nv - ctx->nvIndices)); + } if (rc == 0) { FwRspNoParams(rsp, cmdTag); From 51d5014cf83d01b3ca3abbdc812d3f1edbe557ee Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 15:27:49 -0700 Subject: [PATCH 31/57] F-15814 - Bound StartAuthSession caller nonces --- src/fwtpm/fwtpm_command.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index ad82aefbf..1af2abd7a 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -11294,9 +11294,9 @@ static TPM_RC FwCmd_StartAuthSession(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } - /* nonceCaller must carry at least the 16-octet minimum (Part 1 - * Sec.19.6.8); a tiny/zero nonce weakens the param-encryption IV. */ - if (rc == 0 && nonceCallerSize < 16) { + /* nonceCaller must be 16..digest(authHash) octets (Part 3 Sec.11.1.1). */ + if (rc == 0 && (nonceCallerSize < 16 || + nonceCallerSize > (UINT16)nonceSize)) { rc = TPM_RC_SIZE; } From 0e80f4a3694923a9824323b67fc26dbf8c201865 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 15:30:07 -0700 Subject: [PATCH 32/57] F-15815 - Validate session symmetric definitions --- src/fwtpm/fwtpm_command.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 1af2abd7a..93af7ab05 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -11286,6 +11286,26 @@ static TPM_RC FwCmd_StartAuthSession(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } + /* Validate the parameter-encryption algorithm. */ + if (rc == 0) { +#ifdef FWTPM_NO_PARAM_ENC + if (symmetric.algorithm != TPM_ALG_NULL) { + rc = TPM_RC_SYMMETRIC; + } +#else + if (symmetric.algorithm == TPM_ALG_XOR) { + rc = FwTestHashAlg(symmetric.keyBits.xorr); + } + else { + rc = FwTestSymDef(&symmetric); + if (rc == 0 && symmetric.algorithm != TPM_ALG_NULL && + symmetric.mode.sym != TPM_ALG_CFB) { + rc = TPM_RC_MODE; + } + } +#endif + } + /* Validate hash algorithm */ if (rc == 0) { nonceSize = TPM2_GetHashDigestSize(authHash); From 31be0d071631921cdbea36b0f2793a0f7f844765 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 15:34:22 -0700 Subject: [PATCH 33/57] F-15816 - Normalize salted session errors --- src/fwtpm/fwtpm_command.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 93af7ab05..8d7758edc 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -11361,23 +11361,30 @@ static TPM_RC FwCmd_StartAuthSession(FWTPM_CTX* ctx, TPM2_Packet* cmd, } /* If tpmKey is specified, decrypt the encrypted salt */ - if (rc == 0 && tpmKey != TPM_RH_NULL && encSaltSize > 0) { + if (rc == 0 && tpmKey != TPM_RH_NULL) { FWTPM_Object* keyObj = FwFindObject(ctx, tpmKey); if (keyObj == NULL) { rc = TPM_RC_HANDLE; } - /* tpmKey must be a restricted decryption key (Part 1 Sec.11.2.2); - * a signing-only key would let an attacker derive the session key. */ - if (rc == 0 && - (!(keyObj->pub.objectAttributes & TPMA_OBJECT_decrypt) || - !(keyObj->pub.objectAttributes & TPMA_OBJECT_restricted))) { - rc = TPM_RC_KEY; + else if (keyObj->privKeySize == 0) { + rc = TPM_RC_HANDLE; + } + /* Keep salt recovery bound to a restricted decryption key. */ + else if (!(keyObj->pub.objectAttributes & TPMA_OBJECT_decrypt) || + !(keyObj->pub.objectAttributes & TPMA_OBJECT_restricted)) { + rc = TPM_RC_ATTRIBUTES; + } + else if (encSaltSize == 0) { + rc = TPM_RC_VALUE; } if (rc == 0) { rc = FwDecryptSeed(ctx, keyObj, encSalt, encSaltSize, (const byte*)"SECRET", 7, "SECRET", salt, (int)sizeof(salt), &saltSize); + if (rc != 0 && rc != TPM_RC_KEY) { + rc = TPM_RC_VALUE; + } } } From 517d1637369a8d9bcd045963e945ccc30b14f8e2 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 15:38:25 -0700 Subject: [PATCH 34/57] F-15817 - Validate HMAC key selection --- src/fwtpm/fwtpm_command.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 8d7758edc..7330f400b 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -9959,6 +9959,13 @@ static TPM_RC FwCmd_HMAC(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (!(obj->pub.objectAttributes & TPMA_OBJECT_sign)) rc = TPM_RC_KEY; } + if (rc == 0 && obj->pub.type != TPM_ALG_KEYEDHASH) { + rc = TPM_RC_TYPE; + } + if (rc == 0 && + (obj->pub.objectAttributes & TPMA_OBJECT_restricted)) { + rc = TPM_RC_ATTRIBUTES; + } /* Skip auth area */ if (rc == 0 && cmdTag == TPM_ST_SESSIONS) { @@ -9982,9 +9989,7 @@ static TPM_RC FwCmd_HMAC(FWTPM_CTX* ctx, TPM2_Packet* cmd, TPM2_Packet_ParseU16(cmd, (UINT16*)&hashAlg); /* If the key binds an HMAC scheme, a NULL wire hash uses it and a - * non-NULL wire hash must match it (Part 3 Sec.17.4). A key with an - * unbound (NULL) scheme lets the caller choose, defaulting to the - * key's nameAlg. */ + * non-NULL wire hash must match it (Part 3 Sec.15.5). */ if (obj->pub.parameters.keyedHashDetail.scheme.scheme != TPM_ALG_NULL && obj->pub.parameters.keyedHashDetail.scheme.scheme != TPM_ALG_HMAC) { @@ -10002,7 +10007,7 @@ static TPM_RC FwCmd_HMAC(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } else if (hashAlg == TPM_ALG_NULL) { - hashAlg = obj->pub.nameAlg; + rc = TPM_RC_VALUE; } if (rc == 0) { @@ -10021,17 +10026,10 @@ static TPM_RC FwCmd_HMAC(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } - /* For KEYEDHASH objects the HMAC key is in privKey. - * For other object types fall back to authValue. */ + /* For KEYEDHASH objects the HMAC key is in privKey. */ if (rc == 0) { - if (obj->pub.type == TPM_ALG_KEYEDHASH && obj->privKeySize > 0) { - rc = wc_HmacSetKey(hmac, wcHashType, - obj->privKey, (word32)obj->privKeySize); - } - else { - rc = wc_HmacSetKey(hmac, wcHashType, - obj->authValue.buffer, obj->authValue.size); - } + rc = wc_HmacSetKey(hmac, wcHashType, + obj->privKey, (word32)obj->privKeySize); } if (rc == 0) { rc = wc_HmacUpdate(hmac, dataBuf, dataSize); @@ -10040,7 +10038,9 @@ static TPM_RC FwCmd_HMAC(FWTPM_CTX* ctx, TPM2_Packet* cmd, rc = wc_HmacFinal(hmac, digest); } if (rc != 0 && rc != TPM_RC_COMMAND_SIZE && rc != TPM_RC_HANDLE && - rc != TPM_RC_SIZE && rc != TPM_RC_HASH) { + rc != TPM_RC_SIZE && rc != TPM_RC_HASH && rc != TPM_RC_KEY && + rc != TPM_RC_TYPE && rc != TPM_RC_ATTRIBUTES && + rc != TPM_RC_VALUE) { rc = TPM_RC_FAILURE; } From 6b1cd4918e6d837e3f2352c369be30b33340ce6b Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 15:40:07 -0700 Subject: [PATCH 35/57] F-15818 - Reject restricted HMAC sequences --- src/fwtpm/fwtpm_command.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 7330f400b..515df405f 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -10142,6 +10142,10 @@ static TPM_RC FwCmd_HMAC_Start(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (!(obj->pub.objectAttributes & TPMA_OBJECT_sign)) rc = TPM_RC_KEY; } + if (rc == 0 && + (obj->pub.objectAttributes & TPMA_OBJECT_restricted)) { + rc = TPM_RC_ATTRIBUTES; + } /* Skip auth area */ if (rc == 0 && cmdTag == TPM_ST_SESSIONS) { From a4de8c1ebee8e5a16baf422fdf7620dbdd695e26 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 15:42:49 -0700 Subject: [PATCH 36/57] F-15819 - Reject unsupported cipher modes --- examples/bench/bench.c | 3 ++- src/fwtpm/fwtpm_command.c | 27 ++++++++++++++++++--------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/examples/bench/bench.c b/examples/bench/bench.c index dc31a30ec..b67203491 100644 --- a/examples/bench/bench.c +++ b/examples/bench/bench.c @@ -251,7 +251,8 @@ static int bench_sym_aes(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* storageKey, XMEMSET(iv, 0, sizeof(iv)); rc = wolfTPM2_EncryptDecrypt(dev, &aesKey, in, out, inOutSz, iv, sizeof(iv), isDecrypt); - if (bench_unsupported(rc)) { + if (bench_unsupported(rc) || + (rc & RC_MAX_FMT1) == TPM_RC_MODE) { printf("%-16s Skipped (not supported)\n", desc); rc = 0; goto exit; diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 515df405f..fa962f920 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -15645,14 +15645,21 @@ static TPM_RC FwEncryptDecryptCore(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } - /* If mode is NULL, use the key's default mode */ - if (rc == 0 && mode == TPM_ALG_NULL) { - mode = obj->pub.parameters.symDetail.sym.mode.sym; - } - /* A non-NULL wire mode must match the key's configured mode; otherwise - * e.g. ECB could be applied to a CFB key (Part 3 Sec.12.6.1). */ - else if (rc == 0 && mode != obj->pub.parameters.symDetail.sym.mode.sym) { - rc = TPM_RC_MODE; + /* Use a fixed key mode when present. A key without one requires the + * caller to select an explicit supported mode. */ + if (rc == 0) { + UINT16 keyMode = obj->pub.parameters.symDetail.sym.mode.sym; + if (keyMode == TPM_ALG_NULL) { + if (mode == TPM_ALG_NULL) { + rc = TPM_RC_MODE; + } + } + else if (mode == TPM_ALG_NULL) { + mode = keyMode; + } + else if (mode != keyMode) { + rc = TPM_RC_MODE; + } } #ifdef DEBUG_WOLFTPM @@ -15677,7 +15684,6 @@ static TPM_RC FwEncryptDecryptCore(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (rc == 0) { switch (mode) { case TPM_ALG_CFB: - default: /* CFB always uses the forward AES cipher (AES_ENCRYPTION key * schedule) for both encrypt and decrypt operations */ ret = wc_AesSetKey(aes, obj->privKey, @@ -15754,6 +15760,9 @@ static TPM_RC FwEncryptDecryptCore(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } break; + default: + rc = TPM_RC_MODE; + break; } } From 4424cd26f339c129431520cb43a3be3f1212c49d Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 15:47:47 -0700 Subject: [PATCH 37/57] F-15820 - Enforce symmetric command constraints --- src/fwtpm/fwtpm_command.c | 40 ++++++++++++++++++++++++++------- src/tpm2_wrap.c | 3 ++- wrapper/rust/wolftpm/src/key.rs | 2 +- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index fa962f920..b289f8ebb 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -15564,15 +15564,13 @@ static TPM_RC FwEncryptDecryptCore(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* Only SYMCIPHER keys supported */ if (rc == 0) { if (obj->pub.type != TPM_ALG_SYMCIPHER) { - rc = TPM_RC_TYPE; - } - } - /* Verify key has decrypt attribute */ - if (rc == 0) { - if (!(obj->pub.objectAttributes & TPMA_OBJECT_decrypt)) { rc = TPM_RC_KEY; } } + if (rc == 0 && + (obj->pub.objectAttributes & TPMA_OBJECT_restricted)) { + rc = TPM_RC_ATTRIBUTES; + } if (rc == 0) { if (obj->privKeySize <= 0 || obj->privKeySize > 32) { rc = TPM_RC_KEY; @@ -15645,6 +15643,15 @@ static TPM_RC FwEncryptDecryptCore(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } + if (rc == 0 && decrypt != YES && decrypt != NO) { + rc = TPM_RC_VALUE; + } + if (rc == 0 && + ((decrypt && !(obj->pub.objectAttributes & TPMA_OBJECT_decrypt)) || + (!decrypt && !(obj->pub.objectAttributes & TPMA_OBJECT_sign)))) { + rc = TPM_RC_ATTRIBUTES; + } + /* Use a fixed key mode when present. A key without one requires the * caller to select an explicit supported mode. */ if (rc == 0) { @@ -15662,6 +15669,18 @@ static TPM_RC FwEncryptDecryptCore(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } + if (rc == 0) { + if (mode == TPM_ALG_ECB) { + if (ivSize != 0) { + rc = TPM_RC_SIZE; + } + } + else if ((mode == TPM_ALG_CFB || mode == TPM_ALG_CBC) && + ivSize != AES_BLOCK_SIZE) { + rc = TPM_RC_SIZE; + } + } + #ifdef DEBUG_WOLFTPM if (rc == 0) { printf("fwTPM: EncryptDecrypt%s(key=0x%x, decrypt=%d, mode=0x%x, " @@ -15795,8 +15814,13 @@ static TPM_RC FwEncryptDecryptCore(FWTPM_CTX* ctx, TPM2_Packet* cmd, TPM2_Packet_AppendBytes(rsp, inData, inDataSize); /* ivOut (TPM2B_IV) */ - TPM2_Packet_AppendU16(rsp, (UINT16)sizeof(ivBuf)); - TPM2_Packet_AppendBytes(rsp, ivBuf, sizeof(ivBuf)); + if (mode == TPM_ALG_ECB) { + TPM2_Packet_AppendU16(rsp, 0); + } + else { + TPM2_Packet_AppendU16(rsp, (UINT16)sizeof(ivBuf)); + TPM2_Packet_AppendBytes(rsp, ivBuf, sizeof(ivBuf)); + } FwRspParamsEnd(rsp, cmdTag, paramSzPos, paramStart); } diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index 0940087b4..94520bef0 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -8937,7 +8937,8 @@ int wolfTPM2_EncryptDecryptBlock(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, encDecIn.keyHandle = key->handle.hndl; if (iv == NULL || ivSz == 0) { - encDecIn.ivIn.size = MAX_AES_BLOCK_SIZE_BYTES; /* zeros */ + encDecIn.ivIn.size = encDecIn.mode == TPM_ALG_ECB ? 0 : + MAX_AES_BLOCK_SIZE_BYTES; /* zeros */ } else { encDecIn.ivIn.size = ivSz; diff --git a/wrapper/rust/wolftpm/src/key.rs b/wrapper/rust/wolftpm/src/key.rs index aa5aab0fd..21155a2fe 100644 --- a/wrapper/rust/wolftpm/src/key.rs +++ b/wrapper/rust/wolftpm/src/key.rs @@ -225,7 +225,7 @@ impl Template { &mut t, bits as c_int, sys::TPM_ALG_ID_T_TPM_ALG_CFB as sys::TPM_ALG_ID, - 0, + 1, 1, ) }; From 0b0c6c5336f4d6514ca858a1a630a176bff881cc Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 15:49:47 -0700 Subject: [PATCH 38/57] F-15821 - Bound TPM property groups --- src/fwtpm/fwtpm_command.c | 19 +++++++++++++------ tests/fwtpm_unit_tests.c | 5 +++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index b289f8ebb..048719dca 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -2064,20 +2064,27 @@ static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd, }; int totalProps = (int)(sizeof(allProps) / sizeof(allProps[0])); int startIdx = 0; + int avail = 0; int numOut; + UINT32 propertyGroup = property / (UINT32)PT_GROUP; for (startIdx = 0; startIdx < totalProps; startIdx++) { - if (allProps[startIdx].prop >= property) + if (allProps[startIdx].prop >= property && + allProps[startIdx].prop / (UINT32)PT_GROUP == + propertyGroup) { break; + } } - numOut = totalProps - startIdx; - if (numOut < 0) - numOut = 0; + while (startIdx + avail < totalProps && + allProps[startIdx + avail].prop / (UINT32)PT_GROUP == + propertyGroup) { + avail++; + } + numOut = avail; if ((UINT32)numOut > propertyCount) numOut = (int)propertyCount; - /* Truncated (start offset or propertyCount cap): flag moreData=YES. */ - if (startIdx + numOut < totalProps) + if (numOut < avail) FwPatchMoreData(rsp, moreDataPos, 1); /* YES */ TPM2_Packet_AppendU32(rsp, (UINT32)numOut); diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index a4fba19b5..db19e4525 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -1261,12 +1261,13 @@ static void getcap_paging_check(FWTPM_CTX* ctx, UINT32 cap, int idIs16) { int rc, rspSize, cmdSz, total, iters; UINT32 property, count, id, prev, fullCount; + UINT32 firstProperty = (cap == TPM_CAP_TPM_PROPERTIES) ? PT_FIXED : 0; byte moreData; /* Full list in one shot: must fit, so moreData=NO. */ cmdSz = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_GetCapability); PutU32BE(gCmd + cmdSz, cap); cmdSz += 4; - PutU32BE(gCmd + cmdSz, 0); cmdSz += 4; + PutU32BE(gCmd + cmdSz, firstProperty); cmdSz += 4; PutU32BE(gCmd + cmdSz, 1024); cmdSz += 4; PutU32BE(gCmd + 2, (UINT32)cmdSz); rspSize = 0; @@ -1277,7 +1278,7 @@ static void getcap_paging_check(FWTPM_CTX* ctx, UINT32 cap, int idIs16) AssertIntGT((int)fullCount, 1); /* Walk the cursor one entry per page. */ - property = 0; prev = 0; total = 0; + property = firstProperty; prev = 0; total = 0; for (iters = 0; iters < 1024; iters++) { cmdSz = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_GetCapability); PutU32BE(gCmd + cmdSz, cap); cmdSz += 4; From 9b0c0154be411a77af72b66a523a29e93808874c Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 15:52:58 -0700 Subject: [PATCH 39/57] F-15822 - Report command NV attributes --- src/fwtpm/fwtpm_command.c | 78 +++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 048719dca..703d242d7 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -19583,6 +19583,8 @@ typedef struct { /* Bit 2: first auth handle has DUP role */ /* Bit 3: command flushes its handle (TPMA_CC.F) */ /* Bit 4: first auth handle has ADMIN role */ + /* Bit 5: command may update NV (TPMA_CC.nv) */ + /* Bit 6: command is extensive (TPMA_CC.extensive) */ } FWTPM_CMD_ENTRY; #ifndef FWTPM_NO_PARAM_ENC @@ -19605,28 +19607,31 @@ typedef struct { * matching PolicyCommandCode is required. */ #define FW_CMD_FLAG_AUTH_ADMIN 0x10 +#define FW_CMD_MOD_NV 0x20 +#define FW_CMD_MOD_EXTENSIVE 0x40 + /* inH aH oH flags */ static const FWTPM_CMD_ENTRY fwCmdTable[] = { /* --- Basic (always enabled) --- */ - { TPM_CC_Startup, FwCmd_Startup, 0, 0, 0, 0 }, - { TPM_CC_Shutdown, FwCmd_Shutdown, 0, 0, 0, 0 }, - { TPM_CC_SelfTest, FwCmd_SelfTest, 0, 0, 0, 0 }, - { TPM_CC_IncrementalSelfTest, FwCmd_IncrementalSelfTest, 0, 0, 0, 0 }, + { TPM_CC_Startup, FwCmd_Startup, 0, 0, 0, FW_CMD_MOD_NV }, + { TPM_CC_Shutdown, FwCmd_Shutdown, 0, 0, 0, FW_CMD_MOD_NV }, + { TPM_CC_SelfTest, FwCmd_SelfTest, 0, 0, 0, FW_CMD_MOD_NV }, + { TPM_CC_IncrementalSelfTest, FwCmd_IncrementalSelfTest, 0, 0, 0, FW_CMD_MOD_NV }, { TPM_CC_GetTestResult, FwCmd_GetTestResult, 0, 0, 0, 0 }, { TPM_CC_GetRandom, FwCmd_GetRandom, 0, 0, 0, FW_CMD_FLAG_DEC }, - { TPM_CC_StirRandom, FwCmd_StirRandom, 0, 0, 0, FW_CMD_FLAG_ENC }, + { TPM_CC_StirRandom, FwCmd_StirRandom, 0, 0, 0, FW_CMD_FLAG_ENC | FW_CMD_MOD_NV }, { TPM_CC_GetCapability, FwCmd_GetCapability, 0, 0, 0, 0 }, { TPM_CC_TestParms, FwCmd_TestParms, 0, 0, 0, 0 }, { TPM_CC_PCR_Read, FwCmd_PCR_Read, 0, 0, 0, 0 }, - { TPM_CC_PCR_Extend, FwCmd_PCR_Extend, 1, 1, 0, 0 }, - { TPM_CC_PCR_Reset, FwCmd_PCR_Reset, 1, 1, 0, 0 }, - { TPM_CC_PCR_Event, FwCmd_PCR_Event, 1, 1, 0, FW_CMD_FLAG_ENC }, - { TPM_CC_PCR_Allocate, FwCmd_PCR_Allocate, 1, 1, 0, 0 }, - { TPM_CC_PCR_SetAuthPolicy, FwCmd_PCR_SetAuthPolicy, 1, 1, 0, FW_CMD_FLAG_ENC }, + { TPM_CC_PCR_Extend, FwCmd_PCR_Extend, 1, 1, 0, FW_CMD_MOD_NV }, + { TPM_CC_PCR_Reset, FwCmd_PCR_Reset, 1, 1, 0, FW_CMD_MOD_NV }, + { TPM_CC_PCR_Event, FwCmd_PCR_Event, 1, 1, 0, FW_CMD_FLAG_ENC | FW_CMD_MOD_NV }, + { TPM_CC_PCR_Allocate, FwCmd_PCR_Allocate, 1, 1, 0, FW_CMD_MOD_NV }, + { TPM_CC_PCR_SetAuthPolicy, FwCmd_PCR_SetAuthPolicy, 1, 1, 0, FW_CMD_FLAG_ENC | FW_CMD_MOD_NV }, { TPM_CC_PCR_SetAuthValue, FwCmd_PCR_SetAuthValue, 1, 1, 0, FW_CMD_FLAG_ENC }, #ifndef FWTPM_NO_CLOCK { TPM_CC_ReadClock, FwCmd_ReadClock, 0, 0, 0, 0 }, - { TPM_CC_ClockSet, FwCmd_ClockSet, 1, 1, 0, 0 }, + { TPM_CC_ClockSet, FwCmd_ClockSet, 1, 1, 0, FW_CMD_MOD_NV }, { TPM_CC_ClockRateAdjust, FwCmd_ClockRateAdjust, 1, 1, 0, 0 }, #endif /* !FWTPM_NO_CLOCK */ /* --- Key management (always enabled, algorithm checks inside) --- */ @@ -19639,14 +19644,14 @@ static const FWTPM_CMD_ENTRY fwCmdTable[] = { { TPM_CC_ContextLoad, FwCmd_ContextLoad, 0, 0, 1, 0 }, #endif /* !FWTPM_NO_CONTEXT */ { TPM_CC_ReadPublic, FwCmd_ReadPublic, 1, 0, 0, FW_CMD_FLAG_DEC }, - { TPM_CC_Clear, FwCmd_Clear, 1, 1, 0, 0 }, - { TPM_CC_ClearControl, FwCmd_ClearControl, 1, 1, 0, 0 }, - { TPM_CC_ChangeEPS, FwCmd_ChangeEPS, 1, 1, 0, 0 }, - { TPM_CC_ChangePPS, FwCmd_ChangePPS, 1, 1, 0, 0 }, - { TPM_CC_HierarchyControl, FwCmd_HierarchyControl, 1, 1, 0, 0 }, - { TPM_CC_HierarchyChangeAuth, FwCmd_HierarchyChangeAuth, 1, 1, 0, FW_CMD_FLAG_ENC }, - { TPM_CC_SetPrimaryPolicy, FwCmd_SetPrimaryPolicy, 1, 1, 0, FW_CMD_FLAG_ENC }, - { TPM_CC_EvictControl, FwCmd_EvictControl, 2, 1, 0, 0 }, + { TPM_CC_Clear, FwCmd_Clear, 1, 1, 0, FW_CMD_MOD_NV | FW_CMD_MOD_EXTENSIVE }, + { TPM_CC_ClearControl, FwCmd_ClearControl, 1, 1, 0, FW_CMD_MOD_NV }, + { TPM_CC_ChangeEPS, FwCmd_ChangeEPS, 1, 1, 0, FW_CMD_MOD_NV | FW_CMD_MOD_EXTENSIVE }, + { TPM_CC_ChangePPS, FwCmd_ChangePPS, 1, 1, 0, FW_CMD_MOD_NV | FW_CMD_MOD_EXTENSIVE }, + { TPM_CC_HierarchyControl, FwCmd_HierarchyControl, 1, 1, 0, FW_CMD_MOD_NV | FW_CMD_MOD_EXTENSIVE }, + { TPM_CC_HierarchyChangeAuth, FwCmd_HierarchyChangeAuth, 1, 1, 0, FW_CMD_FLAG_ENC | FW_CMD_MOD_NV }, + { TPM_CC_SetPrimaryPolicy, FwCmd_SetPrimaryPolicy, 1, 1, 0, FW_CMD_FLAG_ENC | FW_CMD_MOD_NV }, + { TPM_CC_EvictControl, FwCmd_EvictControl, 2, 1, 0, FW_CMD_MOD_NV }, { TPM_CC_Create, FwCmd_Create, 1, 1, 0, FW_CMD_FLAG_ENC | FW_CMD_FLAG_DEC }, { TPM_CC_ObjectChangeAuth, FwCmd_ObjectChangeAuth, 2, 1, 0, FW_CMD_FLAG_ENC | FW_CMD_FLAG_DEC | FW_CMD_FLAG_AUTH_ADMIN }, { TPM_CC_Load, FwCmd_Load, 1, 1, 1, FW_CMD_FLAG_ENC | FW_CMD_FLAG_DEC }, @@ -19673,7 +19678,7 @@ static const FWTPM_CMD_ENTRY fwCmdTable[] = { * once the hash commands (and their sequence producers) are gated out. */ #ifndef FWTPM_NO_HASH_CMDS { TPM_CC_SequenceComplete, FwCmd_SequenceComplete, 1, 1, 0, FW_CMD_FLAG_ENC | FW_CMD_FLAG_DEC | FW_CMD_MOD_FLUSHED }, - { TPM_CC_EventSequenceComplete, FwCmd_EventSequenceComplete, 2, 2, 0, FW_CMD_FLAG_ENC | FW_CMD_MOD_FLUSHED }, + { TPM_CC_EventSequenceComplete, FwCmd_EventSequenceComplete, 2, 2, 0, FW_CMD_FLAG_ENC | FW_CMD_MOD_FLUSHED | FW_CMD_MOD_NV }, #endif /* !FWTPM_NO_HASH_CMDS */ /* --- ECC --- */ #ifdef HAVE_ECC @@ -19737,19 +19742,19 @@ static const FWTPM_CMD_ENTRY fwCmdTable[] = { #endif /* --- NV RAM --- */ #ifndef FWTPM_NO_NV - { TPM_CC_NV_DefineSpace, FwCmd_NV_DefineSpace, 1, 1, 0, FW_CMD_FLAG_ENC }, - { TPM_CC_NV_UndefineSpace, FwCmd_NV_UndefineSpace, 2, 1, 0, 0 }, - { TPM_CC_NV_UndefineSpaceSpecial, FwCmd_NV_UndefineSpaceSpecial, 2, 2, 0, FW_CMD_FLAG_AUTH_ADMIN }, + { TPM_CC_NV_DefineSpace, FwCmd_NV_DefineSpace, 1, 1, 0, FW_CMD_FLAG_ENC | FW_CMD_MOD_NV }, + { TPM_CC_NV_UndefineSpace, FwCmd_NV_UndefineSpace, 2, 1, 0, FW_CMD_MOD_NV }, + { TPM_CC_NV_UndefineSpaceSpecial, FwCmd_NV_UndefineSpaceSpecial, 2, 2, 0, FW_CMD_FLAG_AUTH_ADMIN | FW_CMD_MOD_NV }, { TPM_CC_NV_ReadPublic, FwCmd_NV_ReadPublic, 1, 0, 0, FW_CMD_FLAG_DEC }, - { TPM_CC_NV_Write, FwCmd_NV_Write, 2, 1, 0, FW_CMD_FLAG_ENC }, + { TPM_CC_NV_Write, FwCmd_NV_Write, 2, 1, 0, FW_CMD_FLAG_ENC | FW_CMD_MOD_NV }, { TPM_CC_NV_Read, FwCmd_NV_Read, 2, 1, 0, FW_CMD_FLAG_DEC }, - { TPM_CC_NV_Extend, FwCmd_NV_Extend, 2, 1, 0, FW_CMD_FLAG_ENC }, - { TPM_CC_NV_Increment, FwCmd_NV_Increment, 2, 1, 0, 0 }, - { TPM_CC_NV_WriteLock, FwCmd_NV_WriteLock, 2, 1, 0, 0 }, - { TPM_CC_NV_ReadLock, FwCmd_NV_ReadLock, 2, 1, 0, 0 }, - { TPM_CC_NV_SetBits, FwCmd_NV_SetBits, 2, 1, 0, 0 }, - { TPM_CC_NV_ChangeAuth, FwCmd_NV_ChangeAuth, 1, 1, 0, FW_CMD_FLAG_ENC | FW_CMD_FLAG_AUTH_ADMIN }, - { TPM_CC_NV_GlobalWriteLock, FwCmd_NV_GlobalWriteLock, 1, 1, 0, 0 }, + { TPM_CC_NV_Extend, FwCmd_NV_Extend, 2, 1, 0, FW_CMD_FLAG_ENC | FW_CMD_MOD_NV }, + { TPM_CC_NV_Increment, FwCmd_NV_Increment, 2, 1, 0, FW_CMD_MOD_NV }, + { TPM_CC_NV_WriteLock, FwCmd_NV_WriteLock, 2, 1, 0, FW_CMD_MOD_NV }, + { TPM_CC_NV_ReadLock, FwCmd_NV_ReadLock, 2, 1, 0, FW_CMD_MOD_NV }, + { TPM_CC_NV_SetBits, FwCmd_NV_SetBits, 2, 1, 0, FW_CMD_MOD_NV }, + { TPM_CC_NV_ChangeAuth, FwCmd_NV_ChangeAuth, 1, 1, 0, FW_CMD_FLAG_ENC | FW_CMD_FLAG_AUTH_ADMIN | FW_CMD_MOD_NV }, + { TPM_CC_NV_GlobalWriteLock, FwCmd_NV_GlobalWriteLock, 1, 1, 0, FW_CMD_MOD_NV }, #endif /* !FWTPM_NO_NV */ /* --- ECC Parameters --- */ #ifdef HAVE_ECC @@ -19774,8 +19779,8 @@ static const FWTPM_CMD_ENTRY fwCmdTable[] = { #endif /* !FWTPM_NO_CREDENTIAL */ /* --- Dictionary Attack --- */ #ifndef FWTPM_NO_DA - { TPM_CC_DictionaryAttackLockReset, FwCmd_DictionaryAttackLockReset, 1, 1, 0, 0 }, - { TPM_CC_DictionaryAttackParameters, FwCmd_DictionaryAttackParameters, 1, 1, 0, 0 }, + { TPM_CC_DictionaryAttackLockReset, FwCmd_DictionaryAttackLockReset, 1, 1, 0, FW_CMD_MOD_NV }, + { TPM_CC_DictionaryAttackParameters, FwCmd_DictionaryAttackParameters, 1, 1, 0, FW_CMD_MOD_NV }, #endif /* --- Vendor (optional; off by default, see WOLFTPM_FWTPM_TCG_TEST) --- */ #ifdef WOLFTPM_FWTPM_TCG_TEST @@ -19815,8 +19820,7 @@ static TPM_CC FwGetCmdCcAt(int idx) return fwCmdTable[idx].cc; } -/* TPMA_CC for the command at table index idx: commandIndex (15:0), cHandles - * (27:25), rHandle (28), and vendor V bit (29). Used for TPM_CAP_COMMANDS. */ +/* TPMA_CC for the command at table index idx. Used for TPM_CAP_COMMANDS. */ static UINT32 FwGetCmdAttrsAt(int idx) { const FWTPM_CMD_ENTRY* e; @@ -19831,6 +19835,10 @@ static UINT32 FwGetCmdAttrsAt(int idx) attrs |= ((UINT32)1 << 28); /* rHandle */ if (e->encDecFlags & FW_CMD_MOD_FLUSHED) attrs |= ((UINT32)1 << 24); /* flushed */ + if (e->encDecFlags & FW_CMD_MOD_NV) + attrs |= TPMA_CC_nv; + if (e->encDecFlags & FW_CMD_MOD_EXTENSIVE) + attrs |= TPMA_CC_extensive; if ((UINT32)e->cc & (UINT32)CC_VEND) attrs |= (UINT32)CC_VEND; /* V */ return attrs; From 781cba58b337ccd1d29c46472167ddf5ed8ca505 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 16:06:48 -0700 Subject: [PATCH 40/57] F-15823 - Reset platform authorization on startup --- src/fwtpm/fwtpm_command.c | 22 ++++++++++++++++++++++ src/fwtpm/fwtpm_nv.c | 28 ++++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 703d242d7..137e6098a 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -1057,6 +1057,9 @@ static TPM_RC FwCmd_Startup(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, int stateShutdown = 0; UINT32 oldResetCount = 0; UINT32 oldRestartCount = 0; + TPM2B_AUTH oldPlatformAuth; + TPM2B_DIGEST oldPlatformPolicy; + TPMI_ALG_HASH oldPlatformPolicyAlg; #ifndef FWTPM_NO_NV FWTPM_DECLARE_BUF(oldNvData, FWTPM_MAX_NV_DATA); int oldNvDataAllocated = 0; @@ -1268,6 +1271,25 @@ static TPM_RC FwCmd_Startup(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, } } + if (rc == 0 && startupType == TPM_SU_CLEAR) { + oldPlatformAuth = ctx->platformAuth; + oldPlatformPolicy = ctx->platformPolicy; + oldPlatformPolicyAlg = ctx->platformPolicyAlg; + TPM2_ForceZero(&ctx->platformAuth, + sizeof(ctx->platformAuth)); + TPM2_ForceZero(&ctx->platformPolicy, + sizeof(ctx->platformPolicy)); + ctx->platformPolicyAlg = TPM_ALG_NULL; + rc = FWTPM_NV_SaveAuth(ctx, TPM_RH_PLATFORM); + if (rc != 0) { + ctx->platformAuth = oldPlatformAuth; + ctx->platformPolicy = oldPlatformPolicy; + ctx->platformPolicyAlg = oldPlatformPolicyAlg; + } + TPM2_ForceZero(&oldPlatformAuth, sizeof(oldPlatformAuth)); + TPM2_ForceZero(&oldPlatformPolicy, sizeof(oldPlatformPolicy)); + } + /* phEnable is SET on every _TPM_Init/Startup (TPM 2.0 Part 2 * TPMS_STARTUP_CLEAR), regardless of CLEAR vs STATE. */ ctx->phDisabled = 0; diff --git a/src/fwtpm/fwtpm_nv.c b/src/fwtpm/fwtpm_nv.c index 8f78eb2b6..61b26d72d 100644 --- a/src/fwtpm/fwtpm_nv.c +++ b/src/fwtpm/fwtpm_nv.c @@ -1324,7 +1324,21 @@ static int FwNvProcessEntry(FWTPM_CTX* ctx, UINT16 tag, break; case FWTPM_NV_TAG_PLATFORM_AUTH: - FwNvUnmarshalAuth(value, &vPos, vMax, &ctx->platformAuth); + if (FwNvUnmarshalAuth(value, &vPos, vMax, + &ctx->platformAuth) == 0 && vPos < vMax) { + TPM2B_DIGEST policy; + UINT16 alg = TPM_ALG_NULL; + + XMEMSET(&policy, 0, sizeof(policy)); + if (FwNvUnmarshalU16(value, &vPos, vMax, &alg) == 0 && + FwNvUnmarshalDigest(value, &vPos, vMax, + &policy) == 0 && + (policy.size == 0 || + (int)policy.size == TPM2_GetHashDigestSize(alg))) { + ctx->platformPolicy = policy; + ctx->platformPolicyAlg = alg; + } + } break; case FWTPM_NV_TAG_LOCKOUT_AUTH: @@ -2443,7 +2457,7 @@ int FWTPM_NV_SaveSeeds(FWTPM_CTX* ctx) int FWTPM_NV_SaveAuth(FWTPM_CTX* ctx, UINT32 hierarchy) { int rc; - byte buf[2 + TPM_SHA384_DIGEST_SIZE]; + byte buf[2 + TPM_MAX_DIGEST_SIZE + 2 + 2 + TPM_MAX_DIGEST_SIZE]; word32 pos = 0; UINT16 tag; TPM2B_AUTH* auth; @@ -2474,6 +2488,16 @@ int FWTPM_NV_SaveAuth(FWTPM_CTX* ctx, UINT32 hierarchy) } rc = FwNvMarshalAuth(buf, &pos, sizeof(buf), auth); + /* Keep platform authorization and policy in one journal entry so + * Startup(CLEAR) can persist their reset with a single checkpoint. */ + if (rc == 0 && hierarchy == TPM_RH_PLATFORM) { + rc = FwNvMarshalU16(buf, &pos, sizeof(buf), + ctx->platformPolicyAlg); + if (rc == 0) { + rc = FwNvMarshalDigest(buf, &pos, sizeof(buf), + &ctx->platformPolicy); + } + } if (rc == 0) { rc = FwNvAppendEntry(ctx, tag, buf, (UINT16)pos); } From 176404edc428e19e7bca04928b913a69f2b2ded1 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 16:08:43 -0700 Subject: [PATCH 41/57] F-15824 - Flush transient state on every startup --- src/fwtpm/fwtpm_command.c | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 137e6098a..7791cbf85 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -1122,25 +1122,27 @@ static TPM_RC FwCmd_Startup(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, ctx->daLockoutHealMs = FwDaNowMs(ctx); #endif - if (startupType == TPM_SU_CLEAR) { - /* Flush all transient objects, sessions, hash sequences, - * primary cache, and reset PCRs */ - FwFlushAllObjects(ctx); - FwFlushAllSessions(ctx); - #ifndef FWTPM_NO_HASH_CMDS - for (i = 0; i < FWTPM_MAX_HASH_SEQ; i++) { - if (ctx->hashSeq[i].used) { - FwFreeHashSeq(&ctx->hashSeq[i]); - } + /* Every Startup invalidates transient objects, sessions, and + * in-progress sequences. */ + FwFlushAllObjects(ctx); + FwFlushAllSessions(ctx); + #ifndef FWTPM_NO_HASH_CMDS + for (i = 0; i < FWTPM_MAX_HASH_SEQ; i++) { + if (ctx->hashSeq[i].used) { + FwFreeHashSeq(&ctx->hashSeq[i]); } - #endif - #ifdef WOLFTPM_MLDSA - for (i = 0; i < FWTPM_MAX_SIGN_SEQ; i++) { - if (ctx->signSeq[i].used) { - FwFreeSignSeq(&ctx->signSeq[i]); - } + } + #endif + #ifdef WOLFTPM_MLDSA + for (i = 0; i < FWTPM_MAX_SIGN_SEQ; i++) { + if (ctx->signSeq[i].used) { + FwFreeSignSeq(&ctx->signSeq[i]); } - #endif + } + #endif + + if (startupType == TPM_SU_CLEAR) { + /* Flush the primary cache and reset PCRs. */ for (i = 0; i < FWTPM_MAX_PRIMARY_CACHE; i++) { XMEMSET(&ctx->primaryCache[i], 0, sizeof(ctx->primaryCache[i])); From ba0180995d1a4387b888be427d3607d2e3c50323 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 16:10:23 -0700 Subject: [PATCH 42/57] F-15825 - Correct ClockSet upper bound --- src/fwtpm/fwtpm_command.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 7791cbf85..05276921f 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -3636,10 +3636,10 @@ static TPM_RC FwCmd_ClockSet(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* New time must be >= current (can only advance) */ if (rc == 0) { UINT64 currentTime = FWTPM_Clock_GetMs(ctx); - /* Clock may move forward but at most 2^32-1 ms per call (Part 1 - * Sec.17.5.3); reject a far-future value that would freeze it. */ + /* The clock may only move forward and may not exceed the TPM's + * absolute clock limit. */ if (newTime < currentTime || - newTime > currentTime + 0xFFFFFFFFULL) { + newTime > 0xFFFF000000000000ULL) { rc = TPM_RC_VALUE; } } From 0e0ab3300cd32c7fe5ec7f03b8818f91b7d5c687 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 16:11:43 -0700 Subject: [PATCH 43/57] F-15826 - Correct invalid provision handle errors --- src/fwtpm/fwtpm_command.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 05276921f..0381712b6 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -3630,7 +3630,7 @@ static TPM_RC FwCmd_ClockSet(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* Only owner or platform hierarchy can set clock */ if (authHandle != TPM_RH_OWNER && authHandle != TPM_RH_PLATFORM) { - rc = TPM_RC_AUTH_TYPE; + rc = TPM_RC_VALUE; } /* New time must be >= current (can only advance) */ @@ -3686,7 +3686,7 @@ static TPM_RC FwCmd_ClockRateAdjust(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* Only owner or platform can adjust clock rate */ if (rc == 0 && authHandle != TPM_RH_OWNER && authHandle != TPM_RH_PLATFORM) { - rc = TPM_RC_AUTH_TYPE; + rc = TPM_RC_VALUE; } if (rc == 0) { From 3b01d4460c5c6aabb48f4f78edbc636afbe6987e Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 16:13:25 -0700 Subject: [PATCH 44/57] F-15827 - Validate hash ticket hierarchies --- src/fwtpm/fwtpm_command.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 0381712b6..1fb48ff4f 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -9908,8 +9908,13 @@ static TPM_RC FwCmd_Hash(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* Parse hashAlg */ TPM2_Packet_ParseU16(cmd, (UINT16*)&hashAlg); - /* Parse hierarchy (ignored for simple hash) */ + /* Parse hierarchy used for the validation ticket. */ TPM2_Packet_ParseU32(cmd, &hierarchy); + if (hierarchy != TPM_RH_OWNER && + hierarchy != TPM_RH_ENDORSEMENT && + hierarchy != TPM_RH_PLATFORM && hierarchy != TPM_RH_NULL) { + rc = TPM_RC_VALUE; + } wcHash = FwGetWcHashType(hashAlg); if (wcHash == WC_HASH_TYPE_NONE) { @@ -10663,7 +10668,7 @@ static TPM_RC FwCmd_SequenceComplete(FWTPM_CTX* ctx, TPM2_Packet* cmd, digestSz = TPM2_GetHashDigestSize(seq->hashAlg); if (FwGetHierarchySeed(ctx, hierarchy) == NULL) { - rc = TPM_RC_HIERARCHY; + rc = TPM_RC_VALUE; } } From 00464e4f600bb25e9b9f109d6791c39d523456f4 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 16:14:59 -0700 Subject: [PATCH 45/57] F-15828 - Preserve sequences after failed completion --- tests/fwtpm_unit_tests.c | 106 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index db19e4525..c9ff068c6 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -15913,6 +15913,111 @@ static int BuildBoundHmacCmd(byte* buf, UINT32 keyH, const byte* name, return pos; } +/* SequenceComplete must retain the sequence authValue until after the response + * HMAC is generated, then flush the sequence. */ +static void test_fwtpm_sequence_complete_response_hmac(void) +{ + FWTPM_CTX ctx; + static const byte seqAuth[] = {0x11, 0x22, 0x33, 0x44}; + byte nonceCaller[16]; + byte nonceTPM[TPM_MAX_DIGEST_SIZE]; + byte cpHash[WC_SHA256_DIGEST_SIZE]; + byte rpHash[WC_SHA256_DIGEST_SIZE]; + byte authHmac[WC_SHA256_DIGEST_SIZE]; + byte expected[WC_SHA256_DIGEST_SIZE]; + byte ccBuf[4]; + byte params[6]; + UINT16 nonceTPMSz = 0; + UINT16 rspNonceSz; + UINT16 rspHmacSz; + UINT8 attrs = TPMA_SESSION_continueSession; + UINT8 rspAttrs; + UINT32 seqH; + UINT32 sessH; + UINT32 rspParamSz; + int authStart; + int off; + int pos; + int rspSize = 0; + wc_Sha256 sha; + + XMEMSET(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), TPM_RC_SUCCESS); + + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, + TPM_CC_HashSequenceStart); + PutU16BE(gCmd + pos, (UINT16)sizeof(seqAuth)); pos += 2; + XMEMCPY(gCmd + pos, seqAuth, sizeof(seqAuth)); pos += sizeof(seqAuth); + PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; + PutU32BE(gCmd + 2, (UINT32)pos); + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + seqH = GetU32BE(gRsp + TPM2_HEADER_SIZE); + + sessH = StartParamEncSessionHelper(&ctx, TPM_SE_HMAC, TPM_ALG_NULL, + nonceTPM, &nonceTPMSz); + AssertIntNE(sessH, 0); + XMEMSET(nonceCaller, 0x5A, sizeof(nonceCaller)); + + PutU16BE(params, 0); + PutU32BE(params + 2, TPM_RH_OWNER); + PutU32BE(ccBuf, TPM_CC_SequenceComplete); + AssertIntEQ(wc_InitSha256(&sha), 0); + AssertIntEQ(wc_Sha256Update(&sha, ccBuf, sizeof(ccBuf)), 0); + AssertIntEQ(wc_Sha256Update(&sha, params, sizeof(params)), 0); + AssertIntEQ(wc_Sha256Final(&sha, cpHash), 0); + wc_Sha256Free(&sha); + ComputeAuthHmac(seqAuth, (UINT16)sizeof(seqAuth), cpHash, nonceCaller, + (UINT16)sizeof(nonceCaller), nonceTPM, nonceTPMSz, NULL, 0, attrs, + authHmac); + + pos = BuildCmdHeader(gCmd, TPM_ST_SESSIONS, 0, + TPM_CC_SequenceComplete); + PutU32BE(gCmd + pos, seqH); pos += 4; + authStart = pos; + pos += 4; + PutU32BE(gCmd + pos, sessH); pos += 4; + PutU16BE(gCmd + pos, (UINT16)sizeof(nonceCaller)); pos += 2; + XMEMCPY(gCmd + pos, nonceCaller, sizeof(nonceCaller)); + pos += sizeof(nonceCaller); + gCmd[pos++] = attrs; + PutU16BE(gCmd + pos, (UINT16)sizeof(authHmac)); pos += 2; + XMEMCPY(gCmd + pos, authHmac, sizeof(authHmac)); pos += sizeof(authHmac); + PutU32BE(gCmd + authStart, (UINT32)(pos - authStart - 4)); + XMEMCPY(gCmd + pos, params, sizeof(params)); pos += sizeof(params); + PutU32BE(gCmd + 2, (UINT32)pos); + + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + rspParamSz = GetU32BE(gRsp + TPM2_HEADER_SIZE); + AssertIntEQ(wc_InitSha256(&sha), 0); + XMEMSET(ccBuf, 0, sizeof(ccBuf)); + AssertIntEQ(wc_Sha256Update(&sha, ccBuf, sizeof(ccBuf)), 0); + PutU32BE(ccBuf, TPM_CC_SequenceComplete); + AssertIntEQ(wc_Sha256Update(&sha, ccBuf, sizeof(ccBuf)), 0); + AssertIntEQ(wc_Sha256Update(&sha, gRsp + TPM2_HEADER_SIZE + 4, + rspParamSz), 0); + AssertIntEQ(wc_Sha256Final(&sha, rpHash), 0); + wc_Sha256Free(&sha); + + off = RspSessOffset(gRsp, 0); + rspNonceSz = GetU16BE(gRsp + off); + AssertIntLE(rspNonceSz, TPM_MAX_DIGEST_SIZE); + rspAttrs = gRsp[off + 2 + rspNonceSz]; + rspHmacSz = GetU16BE(gRsp + off + 3 + rspNonceSz); + AssertIntEQ(rspHmacSz, WC_SHA256_DIGEST_SIZE); + ComputeAuthHmac(seqAuth, (UINT16)sizeof(seqAuth), rpHash, + gRsp + off + 2, rspNonceSz, nonceCaller, + (UINT16)sizeof(nonceCaller), NULL, 0, rspAttrs, expected); + AssertIntEQ(XMEMCMP(expected, gRsp + off + 5 + rspNonceSz, + sizeof(expected)), 0); + + FlushHandle(&ctx, sessH); + FWTPM_Cleanup(&ctx); + fwtpm_pass("SequenceComplete response HMAC uses sequence auth:", 0); +} + /* With an HMAC authorization first, its HMAC must also cover the nonceTPM * of a separate encrypt or decrypt session (Part 1 Sec.19.6.5). */ static void test_fwtpm_param_enc_session_bound_to_auth(void) @@ -18396,6 +18501,7 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_param_enc_session_selection(); test_fwtpm_param_enc_session_bound_to_auth(); test_fwtpm_bound_auth_change_response_hmac(); + test_fwtpm_sequence_complete_response_hmac(); #endif #endif /* !FWTPM_NO_HASH_CMDS */ #if defined(WOLFTPM_FWTPM_UNIT_TEST) && defined(WOLFTPM_SMALL_STACK) From 0293f9b03ab2238d221f6cc1476822ffd5f579a6 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 16:20:32 -0700 Subject: [PATCH 46/57] F-15829 - Reject sessions on context commands --- src/fwtpm/fwtpm_command.c | 17 +++++++++--- tests/fwtpm_unit_tests.c | 54 ++++++++++++--------------------------- 2 files changed, 30 insertions(+), 41 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 1fb48ff4f..2b46decae 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -19614,6 +19614,7 @@ typedef struct { /* Bit 4: first auth handle has ADMIN role */ /* Bit 5: command may update NV (TPMA_CC.nv) */ /* Bit 6: command is extensive (TPMA_CC.extensive) */ + /* Bit 7: command requires TPM_ST_NO_SESSIONS */ } FWTPM_CMD_ENTRY; #ifndef FWTPM_NO_PARAM_ENC @@ -19638,11 +19639,12 @@ typedef struct { #define FW_CMD_MOD_NV 0x20 #define FW_CMD_MOD_EXTENSIVE 0x40 +#define FW_CMD_FLAG_NO_SESSIONS 0x80 /* inH aH oH flags */ static const FWTPM_CMD_ENTRY fwCmdTable[] = { /* --- Basic (always enabled) --- */ - { TPM_CC_Startup, FwCmd_Startup, 0, 0, 0, FW_CMD_MOD_NV }, + { TPM_CC_Startup, FwCmd_Startup, 0, 0, 0, FW_CMD_MOD_NV | FW_CMD_FLAG_NO_SESSIONS }, { TPM_CC_Shutdown, FwCmd_Shutdown, 0, 0, 0, FW_CMD_MOD_NV }, { TPM_CC_SelfTest, FwCmd_SelfTest, 0, 0, 0, FW_CMD_MOD_NV }, { TPM_CC_IncrementalSelfTest, FwCmd_IncrementalSelfTest, 0, 0, 0, FW_CMD_MOD_NV }, @@ -19667,10 +19669,10 @@ static const FWTPM_CMD_ENTRY fwCmdTable[] = { { TPM_CC_CreatePrimary, FwCmd_CreatePrimary, 1, 1, 1, FW_CMD_FLAG_ENC | FW_CMD_FLAG_DEC }, /* flushHandle is a parameter, not a handle-area handle (Part 3): zero * command handles so the dispatcher locates the auth area correctly. */ - { TPM_CC_FlushContext, FwCmd_FlushContext, 0, 0, 0, 0 }, + { TPM_CC_FlushContext, FwCmd_FlushContext, 0, 0, 0, FW_CMD_FLAG_NO_SESSIONS }, #ifndef FWTPM_NO_CONTEXT - { TPM_CC_ContextSave, FwCmd_ContextSave, 1, 0, 0, 0 }, - { TPM_CC_ContextLoad, FwCmd_ContextLoad, 0, 0, 1, 0 }, + { TPM_CC_ContextSave, FwCmd_ContextSave, 1, 0, 0, FW_CMD_FLAG_NO_SESSIONS }, + { TPM_CC_ContextLoad, FwCmd_ContextLoad, 0, 0, 1, FW_CMD_FLAG_NO_SESSIONS }, #endif /* !FWTPM_NO_CONTEXT */ { TPM_CC_ReadPublic, FwCmd_ReadPublic, 1, 0, 0, FW_CMD_FLAG_DEC }, { TPM_CC_Clear, FwCmd_Clear, 1, 1, 0, FW_CMD_MOD_NV | FW_CMD_MOD_EXTENSIVE }, @@ -20362,6 +20364,13 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, return TPM_RC_SUCCESS; } + if (cmdTag == TPM_ST_SESSIONS && + (entry->encDecFlags & FW_CMD_FLAG_NO_SESSIONS)) { + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, + TPM_RC_AUTH_CONTEXT); + return TPM_RC_SUCCESS; + } + /* Per TPM 2.0 Part 3, any command with an @auth role on a handle must * carry tag = TPM_ST_SESSIONS. NO_SESSIONS leaves the auth area unparsed * and bypasses every downstream auth/HMAC/policy enforcement loop, so diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index c9ff068c6..4962591be 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -10470,17 +10470,14 @@ static TPM_RC SendNvAccessCmd(FWTPM_CTX* ctx, UINT32 nvIndex, /* Group E: Sessions */ /* ================================================================== */ -/* TPM2_FlushContext with a sessions tag carries flushHandle in the parameter - * area after the authorization area, so the handler must skip that area to - * find it (Part 3). */ +/* TPM2_FlushContext requires TPM_ST_NO_SESSIONS. */ static void test_fwtpm_flushcontext_sessions_tag(void) { FWTPM_CTX ctx; UINT32 sessH; int pos, rspSize, rc; #ifndef FWTPM_NO_POLICY - int authStart, rspPos; - UINT16 nonceSz, hmacSz; + int authStart; #endif memset(&ctx, 0, sizeof(ctx)); @@ -10499,16 +10496,15 @@ static void test_fwtpm_flushcontext_sessions_tag(void) rspSize = 0; rc = FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); AssertIntEQ(rc, TPM_RC_SUCCESS); - AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); - /* The response tag must match the command tag. */ - AssertIntEQ(GetU16BE(gRsp), TPM_ST_SESSIONS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_AUTH_CONTEXT); + AssertIntEQ(GetU16BE(gRsp), TPM_ST_NO_SESSIONS); - /* The session is gone: flushing it again fails with TPM_RC_HANDLE. */ + /* Rejection leaves the target session loaded. */ PutU32BE(gCmd + 2, (UINT32)pos); rspSize = 0; rc = FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); AssertIntEQ(rc, TPM_RC_SUCCESS); - AssertIntEQ(GetRspRC(gRsp), TPM_RC_HANDLE); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_AUTH_CONTEXT); /* Auth area present but no flushHandle bytes after it: COMMAND_SIZE. */ pos = 0; @@ -10520,7 +10516,7 @@ static void test_fwtpm_flushcontext_sessions_tag(void) rspSize = 0; rc = FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); AssertIntEQ(rc, TPM_RC_SUCCESS); - AssertIntEQ(GetRspRC(gRsp), TPM_RC_COMMAND_SIZE); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_AUTH_CONTEXT); /* authSize larger than the remaining bytes is rejected up front. */ pos = 0; @@ -10533,10 +10529,9 @@ static void test_fwtpm_flushcontext_sessions_tag(void) rspSize = 0; rc = FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); AssertIntEQ(rc, TPM_RC_SUCCESS); - AssertIntEQ(GetRspRC(gRsp), TPM_RC_AUTHSIZE); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_AUTH_CONTEXT); - /* A zero-length authorization area still has a parameter area. The - * deferred flush must use its offset and release the target session. */ + /* A zero-length authorization area is rejected the same way. */ sessH = StartSessionHelper(&ctx, TPM_SE_HMAC); AssertIntNE(sessH, 0); pos = 0; @@ -10549,8 +10544,8 @@ static void test_fwtpm_flushcontext_sessions_tag(void) rspSize = 0; rc = FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); AssertIntEQ(rc, TPM_RC_SUCCESS); - AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); - AssertIntEQ(GetU16BE(gRsp), TPM_ST_SESSIONS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_AUTH_CONTEXT); + AssertIntEQ(GetU16BE(gRsp), TPM_ST_NO_SESSIONS); pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_FlushContext); @@ -10559,11 +10554,11 @@ static void test_fwtpm_flushcontext_sessions_tag(void) rspSize = 0; rc = FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); AssertIntEQ(rc, TPM_RC_SUCCESS); - AssertIntEQ(GetRspRC(gRsp), TPM_RC_HANDLE); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); #ifndef FWTPM_NO_POLICY - /* The flushed session may also be the response authorization session. - * It must remain live until the response auth area has been generated. */ + /* A target that is also named in the rejected authorization area remains + * live and can be flushed by a conforming command. */ sessH = StartSessionHelper(&ctx, TPM_SE_POLICY); AssertIntNE(sessH, 0); @@ -10583,24 +10578,9 @@ static void test_fwtpm_flushcontext_sessions_tag(void) rspSize = 0; rc = FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); AssertIntEQ(rc, TPM_RC_SUCCESS); - AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); - AssertIntEQ(GetU16BE(gRsp), TPM_ST_SESSIONS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_AUTH_CONTEXT); + AssertIntEQ(GetU16BE(gRsp), TPM_ST_NO_SESSIONS); - rspPos = TPM2_HEADER_SIZE; - AssertTrue(rspPos + 4 <= rspSize); - AssertIntEQ(GetU32BE(gRsp + rspPos), 0); /* parameterSize */ - rspPos += 4; - AssertTrue(rspPos + 2 <= rspSize); - nonceSz = GetU16BE(gRsp + rspPos); rspPos += 2; - AssertIntEQ((int)nonceSz, TPM2_GetHashDigestSize(TPM_ALG_SHA256)); - AssertTrue(rspPos + nonceSz + 3 <= rspSize); - rspPos += nonceSz; - AssertIntEQ(gRsp[rspPos++], TPMA_SESSION_continueSession); - hmacSz = GetU16BE(gRsp + rspPos); rspPos += 2; - AssertIntEQ((int)hmacSz, TPM2_GetHashDigestSize(TPM_ALG_SHA256)); - AssertIntEQ(rspPos + hmacSz, rspSize); - - /* FlushContext overrides continueSession: the target is now gone. */ pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_FlushContext); PutU32BE(gCmd + pos, sessH); pos += 4; @@ -10608,7 +10588,7 @@ static void test_fwtpm_flushcontext_sessions_tag(void) rspSize = 0; rc = FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); AssertIntEQ(rc, TPM_RC_SUCCESS); - AssertIntEQ(GetRspRC(gRsp), TPM_RC_HANDLE); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); #endif /* !FWTPM_NO_POLICY */ FWTPM_Cleanup(&ctx); From bc1cb4634b1329c84ee92a097fee543874fd12b5 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 16:22:41 -0700 Subject: [PATCH 47/57] F-15830 - Return indexed handle reference errors --- src/fwtpm/fwtpm_command.c | 36 +++++++++++++++++++++++++++++++++++- tests/fwtpm_unit_tests.c | 31 +++++++++++++++++++++---------- 2 files changed, 56 insertions(+), 11 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 2b46decae..a4b9e08f3 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -19886,6 +19886,24 @@ static const FWTPM_CMD_ENTRY* FwFindCmdEntry(TPM_CC cc) return NULL; } +static int FwTransientHandleLoaded(FWTPM_CTX* ctx, TPM_HANDLE handle) +{ + if (FwFindObject(ctx, handle) != NULL) { + return 1; + } +#ifndef FWTPM_NO_HASH_CMDS + if (FwFindHashSeq(ctx, handle) != NULL) { + return 1; + } +#endif +#ifdef WOLFTPM_MLDSA + if (FwFindSignSeq(ctx, handle) != NULL) { + return 1; + } +#endif + return 0; +} + #ifndef FWTPM_NO_DA /* Return 1 if the handle names an entity exempt from dictionary-attack * lockout: a permanent hierarchy other than lockout, a sequence, an NV index @@ -20273,6 +20291,8 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, int doEncRsp = 0; /* Encrypt outgoing response param */ #endif int pj, hj; /* Loop indices for auth validation */ + int transientIdx; + TPM_HANDLE transientHandle; int authFail; /* Password comparison result */ int rspCap; /* Caller's response buffer capacity */ int rspTruncated = 0; /* Response did not fit the buffer */ @@ -20381,6 +20401,20 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, return TPM_RC_SUCCESS; } + /* Handle-area transient references are unmarshaled before dispatch. */ + for (transientIdx = 0; + transientIdx < (int)entry->inHandleCnt && transientIdx < 4; + transientIdx++) { + transientHandle = FwLoadU32BE(cmdBuf + TPM2_HEADER_SIZE + + transientIdx * 4); + if ((transientHandle & HR_RANGE_MASK) == TRANSIENT_FIRST && + !FwTransientHandleLoaded(ctx, transientHandle)) { + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, + TPM_ST_NO_SESSIONS, TPM_RC_REFERENCE_H0 + transientIdx); + return TPM_RC_SUCCESS; + } + } + /* A disabled hierarchy makes its objects, NV indices and hierarchy handle * unusable (TPM 2.0 Part 1 Sec.14.2). Reject any command whose input * handles reference a disabled hierarchy. FlushContext is exempt so stale @@ -20582,7 +20616,7 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, /* Non-existent session handle — reject. * Without this, sess remains NULL and all * downstream HMAC/auth checks are skipped. */ - rc = TPM_RC_VALUE; + rc = TPM_RC_REFERENCE_S0 + cmdAuthCnt; break; } } diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index 4962591be..c95a9ccaf 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -6088,13 +6088,13 @@ static void test_fwtpm_encapsulate_neg(void) memset(&ctx, 0, sizeof(ctx)); AssertIntEQ(fwtpm_test_startup(&ctx), 0); - /* TPM_RC_HANDLE: no object at handle 0x80FFFFFF. */ + /* TPM_RC_REFERENCE_H0: no object at handle 0x80FFFFFF. */ BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 14, TPM_CC_Encapsulate); PutU32BE(gCmd + 10, 0x80FFFFFFu); rspSize = 0; rc = FWTPM_ProcessCommand(&ctx, gCmd, 14, gRsp, &rspSize, 0); AssertIntEQ(rc, TPM_RC_SUCCESS); - AssertIntEQ(GetRspRC(gRsp), TPM_RC_HANDLE); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_REFERENCE_H0); /* TPM_RC_KEY: key is not an MLKEM object (MLDSA is signing). */ mldsaHandle = fwtpm_neg_mk_mldsa_primary(&ctx); @@ -6192,7 +6192,7 @@ static void test_fwtpm_signseqstart_neg(void) AssertIntEQ(rc, TPM_RC_SUCCESS); AssertIntEQ(GetRspRC(gRsp), TPM_RC_KEY); - /* TPM_RC_HANDLE: invalid handle. */ + /* TPM_RC_REFERENCE_H0: invalid handle. */ pos = 0; PutU16BE(gCmd + pos, TPM_ST_NO_SESSIONS); pos += 2; PutU32BE(gCmd + pos, 0); pos += 4; @@ -6204,7 +6204,7 @@ static void test_fwtpm_signseqstart_neg(void) rspSize = 0; rc = FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); AssertIntEQ(rc, TPM_RC_SUCCESS); - AssertIntEQ(GetRspRC(gRsp), TPM_RC_HANDLE); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_REFERENCE_H0); FWTPM_Cleanup(&ctx); fwtpm_pass("SignSeqStart negatives (KEY/HANDLE):", 1); @@ -6303,7 +6303,7 @@ static void test_fwtpm_signseqcomplete_neg(void) mldsaHandle2 = mldsaHandle ^ 0x1u; /* different but likely unfindable */ (void)mldsaHandle2; - /* Instead: trigger TPM_RC_HANDLE by passing bogus sequence handle. */ + /* Instead: trigger TPM_RC_REFERENCE_H0 with a bogus sequence handle. */ pos = 0; PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; PutU32BE(gCmd + pos, 0); pos += 4; @@ -6323,7 +6323,7 @@ static void test_fwtpm_signseqcomplete_neg(void) rspSize = 0; rc = FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); AssertIntEQ(rc, TPM_RC_SUCCESS); - AssertIntEQ(GetRspRC(gRsp), TPM_RC_HANDLE); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_REFERENCE_H0); /* Clean up the allocated sequence slot. */ (void)seqHandle; @@ -6394,7 +6394,7 @@ static void test_fwtpm_verifyseqcomplete_neg(void) memset(&ctx, 0, sizeof(ctx)); AssertIntEQ(fwtpm_test_startup(&ctx), 0); - /* TPM_RC_HANDLE: unknown sequence handle. */ + /* TPM_RC_REFERENCE_H0: unknown sequence handle. */ pos = 0; PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; PutU32BE(gCmd + pos, 0); pos += 4; @@ -6405,12 +6405,12 @@ static void test_fwtpm_verifyseqcomplete_neg(void) PutU32BE(gCmd + pos, TPM_RS_PW); pos += 4; PutU16BE(gCmd + pos, 0); pos += 2; gCmd[pos++] = 0; PutU16BE(gCmd + pos, 0); pos += 2; - /* Empty signature area fine — handler returns RC_HANDLE before parsing. */ + /* Empty signature area is fine: handle validation runs before parsing. */ PutU32BE(gCmd + 2, (UINT32)pos); rspSize = 0; rc = FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); AssertIntEQ(rc, TPM_RC_SUCCESS); - AssertIntEQ(GetRspRC(gRsp), TPM_RC_HANDLE); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_REFERENCE_H0); FWTPM_Cleanup(&ctx); fwtpm_pass("VerifySeqComplete negatives (HANDLE):", 1); @@ -14407,15 +14407,26 @@ static void test_fwtpm_sessions_short_authcount_rejected(void) { FWTPM_CTX ctx; int rspSize, pos; + UINT32 seqH; memset(&ctx, 0, sizeof(ctx)); AssertIntEQ(fwtpm_test_startup(&ctx), 0); + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, + TPM_CC_HashSequenceStart); + PutU16BE(gCmd + pos, 0); pos += 2; + PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + seqH = GetU32BE(gRsp + TPM2_HEADER_SIZE); + pos = 0; PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; PutU32BE(gCmd + pos, 0); pos += 4; PutU32BE(gCmd + pos, TPM_CC_EventSequenceComplete); pos += 4; - PutU32BE(gCmd + pos, 0x80000000); pos += 4; /* handle 1 */ + PutU32BE(gCmd + pos, seqH); pos += 4; /* handle 1 */ PutU32BE(gCmd + pos, TPM_RH_PLATFORM); pos += 4; /* handle 2 */ pos = AppendPwAuth(gCmd, pos, NULL, 0); /* single auth entry */ PutU32BE(gCmd + 2, (UINT32)pos); From ad83c3a83277b2f4dc3cb10275b5279ba8ca80e6 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 16:27:45 -0700 Subject: [PATCH 48/57] F-15831 - Gate pre-startup capability queries --- src/fwtpm/fwtpm_command.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index a4b9e08f3..2125a56a9 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -20272,6 +20272,23 @@ static TPM_RC FwCheckPolicyAssertions(FWTPM_CTX* ctx, /* Public API: FWTPM_ProcessCommand */ /* ================================================================== */ +/* Standard TPM command processing permits only Startup before Startup has + * completed. The locked SPDM plaintext capability probe is a vendor transport + * exception used to discover SPDM support before establishing a session. */ +static int FwAllowPreStartupSpdmProbe(FWTPM_CTX* ctx, TPM_CC cmdCode) +{ +#ifdef FWTPM_SPDM_USE_RESPONDER + return cmdCode == TPM_CC_GetCapability && + ctx->spdmMode != FWTPM_SPDM_MODE_OFF && + !ctx->activeCmdOverSpdm && ctx->spdmRespCtx != NULL && + wolfSPDM_RespIsLocked(ctx->spdmRespCtx); +#else + (void)ctx; + (void)cmdCode; + return 0; +#endif +} + int FWTPM_ProcessCommand(FWTPM_CTX* ctx, const byte* cmdBuf, int cmdSize, byte* rspBuf, int* rspSize, int locality) @@ -20357,7 +20374,7 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, } if (!ctx->wasStarted && cmdCode != TPM_CC_Startup && - cmdCode != TPM_CC_GetCapability) { + !FwAllowPreStartupSpdmProbe(ctx, cmdCode)) { *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, TPM_RC_INITIALIZE); return TPM_RC_SUCCESS; From 8c1b7f0b4279cc8d2d9bde46d4be27aabcbf8604 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 16:36:57 -0700 Subject: [PATCH 49/57] F-15832 - Enforce PolicySigned expiration --- src/fwtpm/fwtpm.c | 7 + src/fwtpm/fwtpm_command.c | 402 +++++++++++++++++++++++++++----------- tests/fwtpm_unit_tests.c | 3 +- wolftpm/fwtpm/fwtpm.h | 3 + 4 files changed, 297 insertions(+), 118 deletions(-) diff --git a/src/fwtpm/fwtpm.c b/src/fwtpm/fwtpm.c index 893d930ed..4c33737ea 100644 --- a/src/fwtpm/fwtpm.c +++ b/src/fwtpm/fwtpm.c @@ -112,6 +112,13 @@ int FWTPM_Init(FWTPM_CTX* ctx) } } + /* timeEpoch is an opaque random nonce, not a wall-clock value. It is + * authenticated into authorization tickets and regenerated by Clear. */ + if (rc == 0) { + rc = wc_RNG_GenerateBlock(&ctx->rng, ctx->timeEpoch, + sizeof(ctx->timeEpoch)); + } + #ifndef FWTPM_NO_CONTEXT /* Generate per-boot context protection key (volatile only) for * ContextSave/ContextLoad HMAC + AES-CFB session blob protection. */ diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 2125a56a9..c9e6b5ebf 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -5913,6 +5913,7 @@ static TPM_RC FwCmd_Clear(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, TPM_RC rc = TPM_RC_SUCCESS; UINT32 authHandle = 0; byte newOwnerSeed[FWTPM_SEED_SIZE]; + byte newTimeEpoch[FWTPM_SEED_SIZE]; int ci; if (cmdSize < TPM2_HEADER_SIZE + 4) { @@ -5945,6 +5946,10 @@ static TPM_RC FwCmd_Clear(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, /* Generate new storage primary seed */ rc = FwGenerateClearSeed(&ctx->rng, newOwnerSeed, sizeof(newOwnerSeed)); + if (rc == 0) { + rc = FwGenerateClearSeed(&ctx->rng, newTimeEpoch, + sizeof(newTimeEpoch)); + } if (rc != 0) rc = TPM_RC_FAILURE; /* Only commit state changes if seed generation succeeded. */ @@ -5957,6 +5962,7 @@ static TPM_RC FwCmd_Clear(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, XMEMSET(&ctx->ownerAuth, 0, sizeof(ctx->ownerAuth)); XMEMSET(&ctx->endorsementAuth, 0, sizeof(ctx->endorsementAuth)); XMEMCPY(ctx->ownerSeed, newOwnerSeed, sizeof(ctx->ownerSeed)); + XMEMCPY(ctx->timeEpoch, newTimeEpoch, sizeof(ctx->timeEpoch)); ctx->endorsementProofVersion = 1; /* Flush owner and endorsement primary cache entries after * clearing hierarchy state. */ @@ -6052,6 +6058,7 @@ static TPM_RC FwCmd_Clear(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, } TPM2_ForceZero(newOwnerSeed, sizeof(newOwnerSeed)); + TPM2_ForceZero(newTimeEpoch, sizeof(newTimeEpoch)); return rc; } @@ -11531,6 +11538,7 @@ static TPM_RC FwCmd_StartAuthSession(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (sessionType == TPM_SE_POLICY || sessionType == TPM_SE_TRIAL) { sess->policyDigest.size = nonceSize; XMEMSET(sess->policyDigest.buffer, 0, nonceSize); + sess->startTime = FWTPM_Clock_GetMs(ctx); } } @@ -11608,6 +11616,31 @@ static TPM_RC FwCmd_PolicyGetDigest(FWTPM_CTX* ctx, TPM2_Packet* cmd, return rc; } +static void FwResetPolicyData(FWTPM_Session* sess) +{ + XMEMSET(sess->policyDigest.buffer, 0, sess->policyDigest.size); + sess->isPasswordPolicy = 0; + sess->isAuthValuePolicy = 0; + sess->isPPRequired = 0; + sess->timeout = 0; + sess->cpHashA.size = 0; + sess->nameHash.size = 0; + sess->requiredLocality = 0; + sess->hasRequiredLocality = 0; + sess->commandCode = 0; + sess->templateHash.size = 0; + sess->checkNvWritten = 0; + sess->nvWrittenState = 0; + sess->pcrUpdateCounter = 0; + sess->hasPcrUpdateCounter = 0; + #ifdef WOLFTPM_SPDM + sess->checkSecureChannel = 0; + sess->checkReqKey = 0; + sess->checkTpmKey = 0; + sess->scKeyNameHash.size = 0; + #endif +} + /* --- TPM2_PolicyRestart (CC 0x0180) --- */ static TPM_RC FwCmd_PolicyRestart(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, TPM2_Packet* rsp, UINT16 cmdTag) @@ -11638,31 +11671,7 @@ static TPM_RC FwCmd_PolicyRestart(FWTPM_CTX* ctx, TPM2_Packet* cmd, } if (rc == 0) { - /* Reset policy digest to all zeros */ - XMEMSET(sess->policyDigest.buffer, 0, sess->policyDigest.size); - - /* Reset all policy-related session flags so stale assertions from - * prior policy evaluations don't affect future HMAC computation - * or authorization checks. */ - sess->isPasswordPolicy = 0; - sess->isAuthValuePolicy = 0; - sess->isPPRequired = 0; - sess->cpHashA.size = 0; - sess->nameHash.size = 0; - sess->requiredLocality = 0; - sess->hasRequiredLocality = 0; - sess->commandCode = 0; - sess->templateHash.size = 0; - sess->checkNvWritten = 0; - sess->nvWrittenState = 0; - sess->pcrUpdateCounter = 0; - sess->hasPcrUpdateCounter = 0; - #ifdef WOLFTPM_SPDM - sess->checkSecureChannel = 0; - sess->checkReqKey = 0; - sess->checkTpmKey = 0; - sess->scKeyNameHash.size = 0; - #endif + FwResetPolicyData(sess); FwRspFinalize(rsp, TPM_ST_NO_SESSIONS, TPM_RC_SUCCESS); } @@ -12892,8 +12901,55 @@ static TPM_RC FwCmd_PolicyTransportSPDM(FWTPM_CTX* ctx, TPM2_Packet* cmd, } #endif /* WOLFTPM_SPDM */ +#define FWTPM_POLICY_TICKET_RESET_BIT (((UINT64)1) << 63) + +/* Build the authenticated data following TPM_ST_AUTH_* for an authorization + * ticket. */ +static int FwBuildPolicyTicketData(FWTPM_CTX* ctx, UINT64 timeout, + int expiresOnReset, const byte* cpHash, UINT16 cpHashSz, + const byte* policyRef, UINT16 policyRefSz, + const byte* authName, UINT16 authNameSz, + byte* out, int outCap, int* outSz) +{ + int pos = 0; + int needed = cpHashSz + policyRefSz + authNameSz + 8 + 8 + + (int)sizeof(ctx->timeEpoch); + UINT64 epoch = ((UINT64)ctx->resetCount << 32) | ctx->restartCount; + + if (expiresOnReset) { + needed += 4; + } + if (needed > outCap) { + return BUFFER_E; + } + if (cpHashSz > 0) { + XMEMCPY(out + pos, cpHash, cpHashSz); + pos += cpHashSz; + } + if (policyRefSz > 0) { + XMEMCPY(out + pos, policyRef, policyRefSz); + pos += policyRefSz; + } + if (authNameSz > 0) { + XMEMCPY(out + pos, authName, authNameSz); + pos += authNameSz; + } + FwStoreU64BE(out + pos, timeout); + pos += 8; + FwStoreU64BE(out + pos, epoch); + pos += 8; + XMEMCPY(out + pos, ctx->timeEpoch, sizeof(ctx->timeEpoch)); + pos += (int)sizeof(ctx->timeEpoch); + if (expiresOnReset) { + FwStoreU32BE(out + pos, ctx->resetCount); + pos += 4; + } + *outSz = pos; + return 0; +} + /* --- TPM2_PolicySigned (CC 0x0160) --- */ -/* Simplified: verify signature and extend policyDigest with +/* Verify the signature and extend policyDigest with * H(policyDigest || TPM_CC_PolicySigned || authObject.name). * Wire: authObject (U32) → policySession (U32) → [auth area] → * nonceTPM (TPM2B) → cpHashA (TPM2B) → policyRef (TPM2B) → @@ -12909,9 +12965,21 @@ static TPM_RC FwCmd_PolicySigned(FWTPM_CTX* ctx, TPM2_Packet* cmd, byte nonceBuf[TPM_MAX_DIGEST_SIZE]; byte cpHashBuf[TPM_MAX_DIGEST_SIZE]; byte policyRef[64]; + byte entityName[sizeof(TPM2B_NAME)]; + byte ticketData[TPM_MAX_DIGEST_SIZE + 64 + sizeof(TPM2B_NAME) + 20 + + FWTPM_SEED_SIZE]; + byte ticketHmac[TPM_MAX_DIGEST_SIZE]; TPMT_SIGNATURE sig; FWTPM_Session* sess = NULL; FWTPM_Object* authObj = NULL; + UINT64 authTimeout = 0; + UINT64 wireTimeout = 0; + UINT64 now = 0; + UINT32 ticketHierarchy = TPM_RH_NULL; + int entityNameSz = 0; + int ticketDataSz = 0; + int ticketHmacSz = 0; + int expiresOnReset = 0; int paramSzPos, paramStart; TPM2_Packet_ParseU32(cmd, &authObjHandle); @@ -12989,6 +13057,32 @@ static TPM_RC FwCmd_PolicySigned(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } + /* expiration is signed seconds. With nonceTPM it is relative to the + * session start; without nonceTPM it is an absolute TPM time in seconds. */ + if (rc == 0 && sess->sessionType == TPM_SE_POLICY && expiration != 0) { + UINT64 seconds = (expiration < 0) ? + (UINT64)(0u - (UINT32)expiration) : (UINT64)(UINT32)expiration; + + now = FWTPM_Clock_GetMs(ctx); + if (nonceTpmSz > 0) { + authTimeout = sess->startTime + (seconds * 1000u); + } + else { + /* Part 3 23.2.2 defines this as absolute seconds, not a + * duration from now. Keep the current millisecond remainder so + * conversion does not make the authorization expire early. */ + authTimeout = (seconds * 1000u) + (now % 1000u); + } + if (authTimeout < now) { + rc = TPM_RC_EXPIRED; + } + } + + if (rc == 0 && sess->sessionType == TPM_SE_POLICY && cpHashASz > 0 && + cpHashASz != sess->policyDigest.size) { + rc = TPM_RC_SIZE; + } + /* For policy sessions (not trial): verify signature per TPM 2.0 Part 3 * Section 23.3. Compute aHash = H(nonceTPM || expiration || cpHashA || * policyRef), then verify signature against aHash using authObject's @@ -13043,22 +13137,14 @@ static TPM_RC FwCmd_PolicySigned(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (rc == 0) { /* Build entity name for the auth object */ - byte entityName[sizeof(TPM2B_NAME)]; - int entityNameSz = FwGetEntityName(ctx, authObjHandle, + entityNameSz = FwGetEntityName(ctx, authObjHandle, entityName, (int)sizeof(entityName)); if (entityNameSz == 0) { rc = TPM_RC_HANDLE | TPM_RC_1; } - else if (FwPolicyExtend(sess, TPM_CC_PolicySigned, - entityName, entityNameSz, - policyRef, policyRefSz, 1) != 0) { - rc = TPM_RC_FAILURE; - } } - /* Bind the command to cpHashA so policy enforcement can verify it. - * Lock-once: if already bound, a different value must be rejected - * rather than silently replacing the earlier binding. */ + /* Validate the lock-once cpHash constraint before changing the policy. */ if (rc == 0 && cpHashASz > 0) { if (sess->cpHashA.size > 0) { if (sess->cpHashA.size != cpHashASz || @@ -13067,22 +13153,72 @@ static TPM_RC FwCmd_PolicySigned(FWTPM_CTX* ctx, TPM2_Packet* cmd, rc = TPM_RC_CPHASH; } } + } + + /* A negative expiration returns a reusable authorization ticket. */ + if (rc == 0 && sess->sessionType == TPM_SE_POLICY && expiration < 0) { + authObj = FwFindObject(ctx, authObjHandle); + if (authObj == NULL) { + rc = TPM_RC_HANDLE | TPM_RC_1; + } else { - sess->cpHashA.size = cpHashASz; - XMEMCPY(sess->cpHashA.buffer, cpHashBuf, cpHashASz); + ticketHierarchy = authObj->hierarchy; + } + expiresOnReset = (nonceTpmSz == 0); + if (rc == 0 && FwBuildPolicyTicketData(ctx, authTimeout, + expiresOnReset, cpHashBuf, cpHashASz, + policyRef, policyRefSz, entityName, (UINT16)entityNameSz, + ticketData, (int)sizeof(ticketData), &ticketDataSz) != 0) { + rc = TPM_RC_FAILURE; + } + if (rc == 0) { + rc = FwComputeTicketHmac(ctx, ticketHierarchy, + CONTEXT_INTEGRITY_HASH_ALG, TPM_ST_AUTH_SIGNED, + ticketData, ticketDataSz, NULL, 0, + ticketHmac, &ticketHmacSz); + } + wireTimeout = authTimeout; + if (expiresOnReset) { + wireTimeout |= FWTPM_POLICY_TICKET_RESET_BIT; } } + if (rc == 0 && FwPolicyExtend(sess, TPM_CC_PolicySigned, + entityName, entityNameSz, + policyRef, policyRefSz, 1) != 0) { + rc = TPM_RC_FAILURE; + } + + if (rc == 0 && cpHashASz > 0) { + sess->cpHashA.size = cpHashASz; + XMEMCPY(sess->cpHashA.buffer, cpHashBuf, cpHashASz); + } + if (rc == 0 && authTimeout != 0 && + (sess->timeout == 0 || authTimeout < sess->timeout)) { + sess->timeout = authTimeout; + } + if (rc == 0) { - /* Response: timeout(TPM2B size=0) + ticket(TPMT_TK_AUTH) */ paramStart = FwRspParamsBegin(rsp, cmdTag, ¶mSzPos); - TPM2_Packet_AppendU16(rsp, 0); /* timeout size = 0 */ - TPM2_Packet_AppendU16(rsp, TPM_ST_AUTH_SIGNED); - TPM2_Packet_AppendU32(rsp, TPM_RH_NULL); - TPM2_Packet_AppendU16(rsp, 0); /* ticket digest size = 0 */ + if (expiration < 0 && sess->sessionType == TPM_SE_POLICY) { + TPM2_Packet_AppendU16(rsp, 8); + TPM2_Packet_AppendU64(rsp, wireTimeout); + TPM2_Packet_AppendU16(rsp, TPM_ST_AUTH_SIGNED); + TPM2_Packet_AppendU32(rsp, ticketHierarchy); + TPM2_Packet_AppendU16(rsp, (UINT16)ticketHmacSz); + TPM2_Packet_AppendBytes(rsp, ticketHmac, ticketHmacSz); + } + else { + TPM2_Packet_AppendU16(rsp, 0); + TPM2_Packet_AppendU16(rsp, TPM_ST_AUTH_SIGNED); + TPM2_Packet_AppendU32(rsp, TPM_RH_NULL); + TPM2_Packet_AppendU16(rsp, 0); + } FwRspParamsEnd(rsp, cmdTag, paramSzPos, paramStart); } + TPM2_ForceZero(ticketHmac, sizeof(ticketHmac)); + return rc; } @@ -14005,6 +14141,7 @@ static TPM_RC FwCmd_PolicyTicket(FWTPM_CTX* ctx, TPM2_Packet* cmd, TPM_RC rc = TPM_RC_SUCCESS; UINT32 sessHandle; UINT16 timeoutSz, cpHashASz, policyRefSz, authNameSz; + byte timeoutBuf[8]; byte cpHashABuf[TPM_MAX_DIGEST_SIZE]; byte policyRefBuf[64]; byte authNameBuf[sizeof(TPM2B_NAME)]; @@ -14012,25 +14149,44 @@ static TPM_RC FwCmd_PolicyTicket(FWTPM_CTX* ctx, TPM2_Packet* cmd, UINT32 ticketHier; byte ticketDigest[TPM_MAX_DIGEST_SIZE]; FWTPM_Session* sess; - INT32 expiration = 0; + UINT64 wireTimeout = 0; + UINT64 authTimeout = 0; + int expiresOnReset = 0; + int isTrial = 0; UINT32 extendCC; - int cpaSizeMismatch; - int cpaDiff; - word32 cpaCmpSz; - (void)cmdSize; TPM2_Packet_ParseU32(cmd, &sessHandle); if (cmdTag == TPM_ST_SESSIONS) rc = FwSkipAuthArea(cmd, cmdSize); - /* Parse timeout (TPM2B) - skip, we use expiration=0 for ticket verify */ + sess = FwFindSession(ctx, sessHandle); + if (sess == NULL) { + rc = TPM_RC_VALUE; + } + if (rc == 0 && sess->sessionType != TPM_SE_POLICY && + sess->sessionType != TPM_SE_TRIAL) { + rc = TPM_RC_AUTH_TYPE; + } + if (rc == 0) { + isTrial = (sess->sessionType == TPM_SE_TRIAL); + } + + /* Parse the implementation-specific timeout returned by PolicySigned. */ if (rc == 0) { TPM2_Packet_ParseU16(cmd, &timeoutSz); - if (timeoutSz > 8) + if ((!isTrial && timeoutSz != sizeof(timeoutBuf)) || + (isTrial && timeoutSz != 0 && + timeoutSz != sizeof(timeoutBuf)) || + cmd->pos + timeoutSz > cmdSize) { rc = TPM_RC_SIZE; + } } - if (rc == 0) { - cmd->pos += timeoutSz; + if (rc == 0 && timeoutSz > 0) { + TPM2_Packet_ParseBytes(cmd, timeoutBuf, timeoutSz); + wireTimeout = FwLoadU64BE(timeoutBuf); + expiresOnReset = + ((wireTimeout & FWTPM_POLICY_TICKET_RESET_BIT) != 0); + authTimeout = wireTimeout & ~FWTPM_POLICY_TICKET_RESET_BIT; } /* Parse cpHashA (TPM2B) */ @@ -14038,6 +14194,8 @@ static TPM_RC FwCmd_PolicyTicket(FWTPM_CTX* ctx, TPM2_Packet* cmd, TPM2_Packet_ParseU16(cmd, &cpHashASz); if (cpHashASz > sizeof(cpHashABuf)) rc = TPM_RC_SIZE; + else if (cmd->pos + cpHashASz > cmdSize) + rc = TPM_RC_COMMAND_SIZE; } if (rc == 0 && cpHashASz > 0) { TPM2_Packet_ParseBytes(cmd, cpHashABuf, cpHashASz); @@ -14048,6 +14206,8 @@ static TPM_RC FwCmd_PolicyTicket(FWTPM_CTX* ctx, TPM2_Packet* cmd, TPM2_Packet_ParseU16(cmd, &policyRefSz); if (policyRefSz > sizeof(policyRefBuf)) rc = TPM_RC_SIZE; + else if (cmd->pos + policyRefSz > cmdSize) + rc = TPM_RC_COMMAND_SIZE; } if (rc == 0 && policyRefSz > 0) { TPM2_Packet_ParseBytes(cmd, policyRefBuf, policyRefSz); @@ -14058,18 +14218,25 @@ static TPM_RC FwCmd_PolicyTicket(FWTPM_CTX* ctx, TPM2_Packet* cmd, TPM2_Packet_ParseU16(cmd, &authNameSz); if (authNameSz > sizeof(authNameBuf)) rc = TPM_RC_SIZE; + else if (cmd->pos + authNameSz > cmdSize) + rc = TPM_RC_COMMAND_SIZE; } if (rc == 0 && authNameSz > 0) { TPM2_Packet_ParseBytes(cmd, authNameBuf, authNameSz); } /* Parse ticket (TPMT_TK_AUTH): tag(2) + hierarchy(4) + digest(TPM2B) */ + if (rc == 0 && cmd->pos + 8 > cmdSize) { + rc = TPM_RC_COMMAND_SIZE; + } if (rc == 0) { TPM2_Packet_ParseU16(cmd, &ticketTag); TPM2_Packet_ParseU32(cmd, &ticketHier); TPM2_Packet_ParseU16(cmd, &ticketDigestSz); if (ticketDigestSz > sizeof(ticketDigest)) rc = TPM_RC_SIZE; + else if (cmd->pos + ticketDigestSz > cmdSize) + rc = TPM_RC_COMMAND_SIZE; } if (rc == 0 && ticketDigestSz > 0) { TPM2_Packet_ParseBytes(cmd, ticketDigest, ticketDigestSz); @@ -14080,73 +14247,57 @@ static TPM_RC FwCmd_PolicyTicket(FWTPM_CTX* ctx, TPM2_Packet* cmd, ticketTag != TPM_ST_AUTH_SECRET) { rc = TPM_RC_TICKET; } + if (rc == 0 && FwGetHierarchySeed(ctx, ticketHier) == NULL) { + rc = TPM_RC_VALUE; + } - /* A zero-length ticket digest cannot bind any HMAC and would let the - * caller skip FwComputeTicketHmac below, then forge an arbitrary - * PolicySigned/PolicySecret extension via FwPolicyExtend. */ - if (rc == 0 && ticketDigestSz == 0) { + /* Trial sessions calculate the policy without validating the ticket. */ + if (rc == 0 && !isTrial && ticketDigestSz == 0) { rc = TPM_RC_TICKET; } - sess = FwFindSession(ctx, sessHandle); - if (sess == NULL) { - rc = TPM_RC_VALUE; + if (rc == 0 && !isTrial && authTimeout < FWTPM_Clock_GetMs(ctx)) { + rc = TPM_RC_EXPIRED; } - if (rc == 0 && sess->sessionType != TPM_SE_POLICY && - sess->sessionType != TPM_SE_TRIAL) { - rc = TPM_RC_AUTH_TYPE; + + if (rc == 0 && !isTrial && cpHashASz > 0 && + cpHashASz != sess->policyDigest.size) { + rc = TPM_RC_SIZE; } - /* Verify ticket HMAC: - * aHash = H(nonceTPM || expiration || cpHashA || policyRef) - * ticket = HMAC(proofValue, ticketTag || aHash || authName) */ - if (rc == 0) { - byte aHash[TPM_MAX_DIGEST_SIZE]; - byte ticketInput[2 + TPM_MAX_DIGEST_SIZE + sizeof(TPM2B_NAME)]; + if (rc == 0 && !isTrial && cpHashASz > 0 && + sess->cpHashA.size > 0 && + (sess->cpHashA.size != cpHashASz || + TPM2_ConstantCompare(sess->cpHashA.buffer, cpHashABuf, + cpHashASz) != 0)) { + rc = TPM_RC_CPHASH; + } + + /* Verify the authorization ticket over its policy qualifiers, timeout, + * time epoch, and reset count when applicable. */ + if (rc == 0 && !isTrial) { + byte ticketInput[TPM_MAX_DIGEST_SIZE + 64 + + sizeof(TPM2B_NAME) + 20 + FWTPM_SEED_SIZE]; int ticketInputSz = 0; byte expectedHmac[TPM_MAX_DIGEST_SIZE]; int expectedSz = 0; - wc_HashAlg aCtx; - enum wc_HashType aWcHash; - int aHashSz; - byte expBuf[4]; int hmacRc; int sizeMismatch; int ticketDiff; word32 cmpSz; - aWcHash = FwGetWcHashType(sess->authHash); - aHashSz = TPM2_GetHashDigestSize(sess->authHash); - - /* aHash = H(nonceTPM || expiration || cpHashA || policyRef) */ - if (wc_HashInit(&aCtx, aWcHash) == 0) { - wc_HashUpdate(&aCtx, aWcHash, - sess->nonceTPM.buffer, sess->nonceTPM.size); - FwStoreU32BE(expBuf, (UINT32)expiration); - wc_HashUpdate(&aCtx, aWcHash, expBuf, 4); - if (cpHashASz > 0) - wc_HashUpdate(&aCtx, aWcHash, cpHashABuf, cpHashASz); - if (policyRefSz > 0) - wc_HashUpdate(&aCtx, aWcHash, policyRefBuf, policyRefSz); - wc_HashFinal(&aCtx, aWcHash, aHash); - wc_HashFree(&aCtx, aWcHash); - } - else { + if (FwBuildPolicyTicketData(ctx, authTimeout, expiresOnReset, + cpHashABuf, cpHashASz, policyRefBuf, policyRefSz, + authNameBuf, authNameSz, ticketInput, + (int)sizeof(ticketInput), &ticketInputSz) != 0) { rc = TPM_RC_FAILURE; } - /* ticketInput = aHash || authName */ - if (rc == 0) { - XMEMCPY(ticketInput, aHash, aHashSz); - ticketInputSz = aHashSz; - XMEMCPY(ticketInput + ticketInputSz, authNameBuf, authNameSz); - ticketInputSz += authNameSz; - } - /* Verify HMAC — always run TPM2_ConstantCompare so timing doesn't * leak whether size matched */ if (rc == 0) { - hmacRc = FwComputeTicketHmac(ctx, ticketHier, sess->authHash, + hmacRc = FwComputeTicketHmac(ctx, ticketHier, + CONTEXT_INTEGRITY_HASH_ALG, ticketTag, ticketInput, ticketInputSz, NULL, 0, @@ -14157,10 +14308,9 @@ static TPM_RC FwCmd_PolicyTicket(FWTPM_CTX* ctx, TPM2_Packet* cmd, ticketDiff = TPM2_ConstantCompare(ticketDigest, expectedHmac, cmpSz); if (hmacRc != 0 || (sizeMismatch | ticketDiff)) { - rc = TPM_RC_POLICY_FAIL; + rc = TPM_RC_TICKET; } } - TPM2_ForceZero(aHash, sizeof(aHash)); TPM2_ForceZero(expectedHmac, sizeof(expectedHmac)); } @@ -14181,23 +14331,15 @@ static TPM_RC FwCmd_PolicyTicket(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } - /* Store cpHashA constraint if provided — always run TPM2_ConstantCompare - * so timing doesn't leak size match */ - if (rc == 0 && cpHashASz > 0) { - if (sess->cpHashA.size > 0) { - cpaSizeMismatch = (sess->cpHashA.size != cpHashASz); - cpaCmpSz = (sess->cpHashA.size < cpHashASz) ? - sess->cpHashA.size : cpHashASz; - cpaDiff = TPM2_ConstantCompare(sess->cpHashA.buffer, cpHashABuf, - cpaCmpSz); - if (cpaSizeMismatch | cpaDiff) { - rc = TPM_RC_CPHASH; - } - } - if (rc == 0) { - sess->cpHashA.size = cpHashASz; - XMEMCPY(sess->cpHashA.buffer, cpHashABuf, cpHashASz); - } + /* Store the already-validated cpHashA constraint if provided. */ + if (rc == 0 && !isTrial && cpHashASz > 0) { + sess->cpHashA.size = cpHashASz; + XMEMCPY(sess->cpHashA.buffer, cpHashABuf, cpHashASz); + } + + if (rc == 0 && !isTrial && + (sess->timeout == 0 || authTimeout < sess->timeout)) { + sess->timeout = authTimeout; } if (rc == 0) { @@ -20719,6 +20861,14 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, return TPM_RC_SUCCESS; } + if (pSess->timeout != 0 && + pSess->timeout < FWTPM_Clock_GetMs(ctx)) { + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, + TPM_ST_NO_SESSIONS, TPM_RC_EXPIRED); + TPM2_ForceZero(cmdAuths, sizeof(cmdAuths)); + return TPM_RC_SUCCESS; + } + /* Find entity's authPolicy by handle type */ #ifndef FWTPM_NO_NV if ((entityH & 0xFF000000) == (NV_INDEX_FIRST & 0xFF000000)) { @@ -21588,6 +21738,24 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, /* Finalize response header */ FwRspFinalize(&rspPkt, TPM_ST_SESSIONS, TPM_RC_SUCCESS); *rspSize = rspPkt.pos; + + /* TCG Part 4 UpdateInternalSession records a new startTime when + * nonceTPM rolls and resets policy data after authorizing use. */ + for (j = 0; j < cmdAuthCnt; j++) { + FWTPM_Session* sess = cmdAuths[j].sess; + if (sess != NULL && + (cmdAuths[j].attributes & + TPMA_SESSION_continueSession) && + (sess->sessionType == TPM_SE_POLICY || + sess->sessionType == TPM_SE_TRIAL)) { + #ifndef FWTPM_NO_POLICY + if (j < entry->authHandleCnt) { + FwResetPolicyData(sess); + } + #endif + sess->startTime = FWTPM_Clock_GetMs(ctx); + } + } } } diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index c95a9ccaf..fa7611b60 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -10992,7 +10992,8 @@ static void test_fwtpm_policy_ticket_zero_digest_rejected(void) PutU32BE(gCmd + pos, TPM_CC_PolicyTicket); pos += 4; PutU32BE(gCmd + pos, sessH); pos += 4; pos = AppendPwAuth(gCmd, pos, NULL, 0); - PutU16BE(gCmd + pos, 0); pos += 2; /* timeout TPM2B size = 0 */ + PutU16BE(gCmd + pos, 8); pos += 2; /* timeout TPM2B */ + memset(gCmd + pos, 0, 8); pos += 8; PutU16BE(gCmd + pos, 0); pos += 2; /* cpHashA TPM2B size = 0 */ PutU16BE(gCmd + pos, 0); pos += 2; /* policyRef TPM2B size = 0 */ PutU16BE(gCmd + pos, sizeof(fakeAuthName)); pos += 2; diff --git a/wolftpm/fwtpm/fwtpm.h b/wolftpm/fwtpm/fwtpm.h index 00ee4b580..c39bf4d15 100644 --- a/wolftpm/fwtpm/fwtpm.h +++ b/wolftpm/fwtpm/fwtpm.h @@ -611,6 +611,8 @@ typedef struct FWTPM_Session { int isLockoutBound; /* Bound to the lockout hierarchy */ #endif TPM2B_DIGEST policyDigest; /* Running policy digest (policy sessions) */ + UINT64 startTime; /* TPM time when nonceTPM last changed */ + UINT64 timeout; /* Policy expiration in TPM milliseconds */ int isPasswordPolicy; /* 1 if PolicyPassword was called */ int isAuthValuePolicy; /* 1 if PolicyAuthValue was called */ TPM2B_DIGEST cpHashA; /* PolicyCpHash: locked once set */ @@ -800,6 +802,7 @@ typedef struct FWTPM_CTX { UINT64 clockOffset; /* Clock offset set by ClockSet */ UINT32 resetCount; /* TPM Reset count, persisted across boots */ UINT32 restartCount; /* Restart/Resume count, reset by TPM Reset */ + byte timeEpoch[TPM_SHA384_DIGEST_SIZE]; /* Opaque ticket epoch nonce */ /* PCR state: [pcrIndex][bank][digest bytes] */ byte pcrDigest[IMPLEMENTATION_PCR][FWTPM_PCR_BANKS][TPM_MAX_DIGEST_SIZE]; From 8fae4f210ecfc11cfb5f7b7d6ff0cee68fcaa87d Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 16:39:38 -0700 Subject: [PATCH 50/57] F-15833 - Validate policy locality ranges --- src/fwtpm/fwtpm_command.c | 41 ++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index c9e6b5ebf..bb83b3b87 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -12749,6 +12749,7 @@ static TPM_RC FwCmd_PolicyLocality(FWTPM_CTX* ctx, TPM2_Packet* cmd, TPM_RC rc = TPM_RC_SUCCESS; UINT32 sessHandle; UINT8 locality = 0; + UINT8 allowedLocality = 0; FWTPM_Session* sess; byte locBuf[1]; (void)cmdSize; @@ -12767,6 +12768,30 @@ static TPM_RC FwCmd_PolicyLocality(FWTPM_CTX* ctx, TPM2_Packet* cmd, rc = TPM_RC_AUTH_TYPE; } + if (rc == 0 && locality == 0) { + rc = TPM_RC_RANGE; + } + if (rc == 0 && sess->hasRequiredLocality) { + if ((sess->requiredLocality < 32) != (locality < 32)) { + rc = TPM_RC_RANGE; + } + else if (locality < 32) { + allowedLocality = sess->requiredLocality & locality; + if (allowedLocality == 0) { + rc = TPM_RC_RANGE; + } + } + else if (sess->requiredLocality != locality) { + rc = TPM_RC_RANGE; + } + } + else { + allowedLocality = locality; + } + if (rc == 0 && locality >= 32) { + allowedLocality = locality; + } + if (rc == 0) { #ifdef DEBUG_WOLFTPM printf("fwTPM: PolicyLocality(session=0x%x, locality=%d)\n", @@ -12778,14 +12803,8 @@ static TPM_RC FwCmd_PolicyLocality(FWTPM_CTX* ctx, TPM2_Packet* cmd, NULL, 0, 0) != 0) { rc = TPM_RC_FAILURE; } - /* Bind the locality constraint (intersect across calls). A flag - * marks it present so a bitmap of 0 stays an unsatisfiable - * constraint rather than reading as "unset". */ if (rc == 0) { - if (!sess->hasRequiredLocality) - sess->requiredLocality = locality; - else - sess->requiredLocality &= locality; + sess->requiredLocality = allowedLocality; sess->hasRequiredLocality = 1; } } @@ -20934,8 +20953,12 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, } /* Enforce any PolicyLocality constraint bound to the session */ if (pSess->hasRequiredLocality && - (ctx->activeLocality > WOLFTPM_LOCALITY_MAX || - !((1u << ctx->activeLocality) & pSess->requiredLocality))) { + ((pSess->requiredLocality < 32 && + (ctx->activeLocality > WOLFTPM_LOCALITY_MAX || + !((1u << ctx->activeLocality) & + pSess->requiredLocality))) || + (pSess->requiredLocality >= 32 && + ctx->activeLocality != pSess->requiredLocality))) { *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, TPM_RC_LOCALITY); TPM2_ForceZero(cmdAuths, sizeof(cmdAuths)); From 4496410b52ded313dba9870686046c28239b2337 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 16:41:28 -0700 Subject: [PATCH 51/57] F-15834 - Reject unsupported policy command codes --- src/fwtpm/fwtpm_command.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index bb83b3b87..ec653cd60 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -118,6 +118,9 @@ static FWTPM_HashSeq* FwFindHashSeq(FWTPM_CTX* ctx, TPM_HANDLE handle); static int FwGetCmdCount(void); static TPM_CC FwGetCmdCcAt(int idx); static UINT32 FwGetCmdAttrsAt(int idx); +#ifndef FWTPM_NO_POLICY +static int FwCommandIsImplemented(TPM_CC cc); +#endif /* --- Response helpers using TPM2_Packet --- */ @@ -12107,6 +12110,9 @@ static TPM_RC FwCmd_PolicyCommandCode(FWTPM_CTX* ctx, TPM2_Packet* cmd, sess->commandCode != commandCode) { rc = TPM_RC_VALUE; } + if (rc == 0 && !FwCommandIsImplemented(commandCode)) { + rc = TPM_RC_POLICY_CC; + } if (rc == 0) { #ifdef DEBUG_WOLFTPM @@ -20047,6 +20053,13 @@ static const FWTPM_CMD_ENTRY* FwFindCmdEntry(TPM_CC cc) return NULL; } +#ifndef FWTPM_NO_POLICY +static int FwCommandIsImplemented(TPM_CC cc) +{ + return FwFindCmdEntry(cc) != NULL; +} +#endif + static int FwTransientHandleLoaded(FWTPM_CTX* ctx, TPM_HANDLE handle) { if (FwFindObject(ctx, handle) != NULL) { From 837c1b0f5f408da56102c409bdb26a84cd7c2747 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 16:44:30 -0700 Subject: [PATCH 52/57] F-15836 - Validate created object attributes --- src/fwtpm/fwtpm_command.c | 102 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index ec653cd60..80d7cab31 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -2676,6 +2676,95 @@ static TPM_RC FwValidateMlTemplate(const TPMT_PUBLIC* pub, int checkPubSize) return rc; } +static TPM_RC FwValidateCreateAttributes(const TPMT_PUBLIC* pub, + const FWTPM_Object* parent, UINT16 sensitiveDataSz) +{ + UINT32 attrs = pub->objectAttributes; + UINT32 parentAttrs = (parent != NULL) ? parent->pub.objectAttributes : 0; + int fixedTpm = (attrs & TPMA_OBJECT_fixedTPM) != 0; + int fixedParent = (attrs & TPMA_OBJECT_fixedParent) != 0; + int sensitiveOrigin = + (attrs & TPMA_OBJECT_sensitiveDataOrigin) != 0; + int restricted = (attrs & TPMA_OBJECT_restricted) != 0; + int decrypt = (attrs & TPMA_OBJECT_decrypt) != 0; + int sign = (attrs & TPMA_OBJECT_sign) != 0; + int asymmetric = 0; + + switch (pub->type) { + #ifndef NO_RSA + case TPM_ALG_RSA: + asymmetric = 1; + break; + #endif + #ifdef HAVE_ECC + case TPM_ALG_ECC: + asymmetric = 1; + break; + #endif + #ifdef WOLFTPM_MLDSA + case TPM_ALG_MLDSA: + case TPM_ALG_HASH_MLDSA: + asymmetric = 1; + break; + #endif + #ifdef WOLFTPM_MLKEM + case TPM_ALG_MLKEM: + asymmetric = 1; + break; + #endif + default: + break; + } + + if ((sensitiveDataSz == 0) != sensitiveOrigin) { + return TPM_RC_ATTRIBUTES; + } + if (asymmetric && (!sensitiveOrigin || sensitiveDataSz != 0)) { + return TPM_RC_ATTRIBUTES; + } + if (pub->type == TPM_ALG_KEYEDHASH && !sign && !decrypt && + sensitiveOrigin) { + return TPM_RC_ATTRIBUTES; + } + if ((pub->type == TPM_ALG_KEYEDHASH || + pub->type == TPM_ALG_SYMCIPHER) && restricted && + !sensitiveOrigin && (fixedParent || fixedTpm)) { + return TPM_RC_ATTRIBUTES; + } + + if ((parent == NULL || + (parentAttrs & TPMA_OBJECT_fixedTPM) != 0) && + fixedTpm != fixedParent) { + return TPM_RC_ATTRIBUTES; + } + if (parent != NULL && + (parentAttrs & TPMA_OBJECT_fixedTPM) == 0 && fixedTpm) { + return TPM_RC_ATTRIBUTES; + } + if (restricted && sign == decrypt) { + return TPM_RC_ATTRIBUTES; + } + if (pub->type != TPM_ALG_KEYEDHASH && !sign && !decrypt) { + return TPM_RC_ATTRIBUTES; + } + if (fixedTpm && (attrs & TPMA_OBJECT_encryptedDuplication) != 0) { + return TPM_RC_ATTRIBUTES; + } + if (parent != NULL && + (parentAttrs & TPMA_OBJECT_fixedTPM) == 0 && + ((attrs ^ parentAttrs) & + TPMA_OBJECT_encryptedDuplication) != 0) { + return TPM_RC_ATTRIBUTES; + } +#ifdef WOLFTPM_V185 + if ((attrs & TPMA_OBJECT_x509sign) != 0 && (!asymmetric || !sign)) { + return TPM_RC_ATTRIBUTES; + } +#endif + + return TPM_RC_SUCCESS; +} + #ifdef WOLFTPM_V185 /* fwTPM does not implement the firmware/SVN-bound hierarchy seeds and * proofs required by limited objects. */ @@ -3950,6 +4039,10 @@ static TPM_RC FwCmd_CreatePrimary(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (rc == 0) { rc = FwValidateMlTemplate(&inPublic->publicArea, 0); } + if (rc == 0) { + rc = FwValidateCreateAttributes(&inPublic->publicArea, NULL, + sensDataSize); + } #ifdef WOLFTPM_V185 if (rc == 0) { rc = FwValidateLimitedAttributes(&inPublic->publicArea); @@ -6848,6 +6941,10 @@ static TPM_RC FwCmd_Create(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (rc == 0) { rc = FwValidateMlTemplate(&inPublic->publicArea, 0); } + if (rc == 0) { + rc = FwValidateCreateAttributes(&inPublic->publicArea, parent, + sensDataSize); + } #ifdef WOLFTPM_V185 if (rc == 0) { rc = FwValidateLimitedAttributes(&inPublic->publicArea); @@ -8958,6 +9055,11 @@ static TPM_RC FwCmd_CreateLoaded(FWTPM_CTX* ctx, TPM2_Packet* cmd, TPM2_Packet_ParsePublic(cmd, inPublic); } + if (rc == 0) { + rc = FwValidateCreateAttributes(&inPublic->publicArea, parent, + sensDataSize); + } + #ifdef WOLFTPM_V185 if (rc == 0) { rc = FwValidateLimitedAttributes(&inPublic->publicArea); From 1782ced2f85ec57ddb5a501c3df3254e5e69959d Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 16:47:04 -0700 Subject: [PATCH 53/57] F-15837 - Obfuscate attestation metadata --- src/fwtpm/fwtpm_command.c | 100 ++++++++++++++++++++++++++++---------- 1 file changed, 75 insertions(+), 25 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 80d7cab31..7e28a6089 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -16164,18 +16164,57 @@ static TPM_RC FwCmd_EncryptDecrypt2(FWTPM_CTX* ctx, TPM2_Packet* cmd, * Returns: number of bytes written (up to serialized position in pkt). */ #ifndef FWTPM_NO_ATTESTATION /* Append a TPMS_CLOCK_INFO from live TPM state (clock + reset/restart). */ -static void FwAppendClockInfo(FWTPM_CTX* ctx, TPM2_Packet* pkt) +static void FwAppendClockInfo(FWTPM_CTX* ctx, TPM2_Packet* pkt, + UINT32 resetObfuscation, UINT32 restartObfuscation) { TPM2_Packet_AppendU64(pkt, FWTPM_Clock_GetMs(ctx)); - TPM2_Packet_AppendU32(pkt, ctx->resetCount); - TPM2_Packet_AppendU32(pkt, ctx->restartCount); + TPM2_Packet_AppendU32(pkt, ctx->resetCount + resetObfuscation); + TPM2_Packet_AppendU32(pkt, ctx->restartCount + restartObfuscation); TPM2_Packet_AppendU8(pkt, YES); /* safe */ } -static void FwAppendAttestCommonHeader(FWTPM_CTX* ctx, TPM2_Packet* pkt, - UINT16 type, const TPM2B_NAME* qualifiedSigner, +static TPM_RC FwAppendAttestCommonHeader(FWTPM_CTX* ctx, TPM2_Packet* pkt, + UINT16 type, const FWTPM_Object* signer, + const TPM2B_NAME* qualifiedSigner, const TPM2B_DATA* extraData) { + TPM_RC rc = TPM_RC_SUCCESS; + UINT64 firmwareObfuscation = 0; + UINT32 resetObfuscation = 0; + UINT32 restartObfuscation = 0; + byte proof[TPM_MAX_DIGEST_SIZE]; + byte obfuscation[16]; + + if (signer->hierarchy != TPM_RH_ENDORSEMENT && + signer->hierarchy != TPM_RH_PLATFORM) { + int proofSz = TPM2_GetHashDigestSize(CONTEXT_INTEGRITY_HASH_ALG); + + if (proofSz <= 0) { + rc = TPM_RC_HASH; + } + if (rc == 0) { + rc = FwComputeProofValue(ctx, TPM_RH_OWNER, + CONTEXT_INTEGRITY_HASH_ALG, proof, proofSz); + } + if (rc == 0 && TPM2_KDFa_ex(signer->pub.nameAlg, + proof, (UINT32)proofSz, "OBFUSCATE", + qualifiedSigner->name, qualifiedSigner->size, + NULL, 0, obfuscation, sizeof(obfuscation)) != + (int)sizeof(obfuscation)) { + rc = TPM_RC_FAILURE; + } + if (rc == 0) { + firmwareObfuscation = FwLoadU64BE(obfuscation); + resetObfuscation = FwLoadU32BE(obfuscation + 8); + restartObfuscation = FwLoadU32BE(obfuscation + 12); + } + } + if (rc != 0) { + TPM2_ForceZero(proof, sizeof(proof)); + TPM2_ForceZero(obfuscation, sizeof(obfuscation)); + return rc; + } + /* magic */ TPM2_Packet_AppendU32(pkt, TPM_GENERATED_VALUE); /* type */ @@ -16188,10 +16227,15 @@ static void FwAppendAttestCommonHeader(FWTPM_CTX* ctx, TPM2_Packet* pkt, TPM2_Packet_AppendU16(pkt, extraData->size); TPM2_Packet_AppendBytes(pkt, (byte*)extraData->buffer, extraData->size); /* clockInfo: clock(8) + resetCount(4) + restartCount(4) + safe(1) */ - FwAppendClockInfo(ctx, pkt); + FwAppendClockInfo(ctx, pkt, resetObfuscation, restartObfuscation); /* firmwareVersion */ - TPM2_Packet_AppendU64(pkt, ((UINT64)FWTPM_VERSION_MAJOR << 32) | - FWTPM_VERSION_MINOR); + TPM2_Packet_AppendU64(pkt, + (((UINT64)FWTPM_VERSION_MAJOR << 32) | FWTPM_VERSION_MINOR) + + firmwareObfuscation); + + TPM2_ForceZero(proof, sizeof(proof)); + TPM2_ForceZero(obfuscation, sizeof(obfuscation)); + return TPM_RC_SUCCESS; } #endif /* !FWTPM_NO_ATTESTATION */ @@ -16352,8 +16396,8 @@ static TPM_RC FwCmd_Quote(FWTPM_CTX* ctx, TPM2_Packet* cmd, attestPkt.pos = 0; attestPkt.size = (int)FWTPM_MAX_ATTEST_BUF; - FwAppendAttestCommonHeader(ctx, &attestPkt, TPM_ST_ATTEST_QUOTE, - &sigObj->name, &qualifyingData); + rc = FwAppendAttestCommonHeader(ctx, &attestPkt, + TPM_ST_ATTEST_QUOTE, sigObj, &sigObj->name, &qualifyingData); /* attested.quote: pcrSelect (TPML_PCR_SELECTION) + * pcrDigest (TPM2B_DIGEST) */ @@ -16486,8 +16530,8 @@ static TPM_RC FwCmd_Certify(FWTPM_CTX* ctx, TPM2_Packet* cmd, attestPkt.pos = 0; attestPkt.size = (int)FWTPM_MAX_ATTEST_BUF; - FwAppendAttestCommonHeader(ctx, &attestPkt, TPM_ST_ATTEST_CERTIFY, - &sigObj->name, &qualifyingData); + rc = FwAppendAttestCommonHeader(ctx, &attestPkt, + TPM_ST_ATTEST_CERTIFY, sigObj, &sigObj->name, &qualifyingData); /* attested.certify: name + qualifiedName */ TPM2_Packet_AppendU16(&attestPkt, objToSign->name.size); @@ -16499,8 +16543,10 @@ static TPM_RC FwCmd_Certify(FWTPM_CTX* ctx, TPM2_Packet* cmd, objToSign->name.size); /* Build response */ - rc = FwBuildAttestResponse(ctx, rsp, cmdTag, sigObj, - sigScheme, sigHashAlg, attestBuf, attestPkt.pos); + if (rc == 0) { + rc = FwBuildAttestResponse(ctx, rsp, cmdTag, sigObj, + sigScheme, sigHashAlg, attestBuf, attestPkt.pos); + } } FWTPM_FREE_BUF(attestBuf); return rc; @@ -16686,8 +16732,8 @@ static TPM_RC FwCmd_CertifyCreation(FWTPM_CTX* ctx, TPM2_Packet* cmd, attestPkt.pos = 0; attestPkt.size = (int)FWTPM_MAX_ATTEST_BUF; - FwAppendAttestCommonHeader(ctx, &attestPkt, TPM_ST_ATTEST_CREATION, - &sigObj->name, &qualifyingData); + rc = FwAppendAttestCommonHeader(ctx, &attestPkt, + TPM_ST_ATTEST_CREATION, sigObj, &sigObj->name, &qualifyingData); /* attested.creation: objectName (TPM2B_NAME) + * creationHash (TPM2B_DIGEST) */ @@ -16699,8 +16745,10 @@ static TPM_RC FwCmd_CertifyCreation(FWTPM_CTX* ctx, TPM2_Packet* cmd, creationHash.size); /* Build response */ - rc = FwBuildAttestResponse(ctx, rsp, cmdTag, sigObj, - sigScheme, sigHashAlg, attestBuf, attestPkt.pos); + if (rc == 0) { + rc = FwBuildAttestResponse(ctx, rsp, cmdTag, sigObj, + sigScheme, sigHashAlg, attestBuf, attestPkt.pos); + } } FWTPM_FREE_BUF(attestBuf); return rc; @@ -16749,20 +16797,22 @@ static TPM_RC FwCmd_GetTime(FWTPM_CTX* ctx, TPM2_Packet* cmd, attestPkt.pos = 0; attestPkt.size = (int)FWTPM_MAX_PUB_BUF; - FwAppendAttestCommonHeader(ctx, &attestPkt, TPM_ST_ATTEST_TIME, - &sigObj->name, &qualifyingData); + rc = FwAppendAttestCommonHeader(ctx, &attestPkt, + TPM_ST_ATTEST_TIME, sigObj, &sigObj->name, &qualifyingData); /* attested.time: TPMS_TIME_INFO (time + clockInfo) + * firmwareVersion */ TPM2_Packet_AppendU64(&attestPkt, FWTPM_Clock_GetMs(ctx)); /* time */ - FwAppendClockInfo(ctx, &attestPkt); + FwAppendClockInfo(ctx, &attestPkt, 0, 0); TPM2_Packet_AppendU64(&attestPkt, ((UINT64)FWTPM_VERSION_MAJOR << 32) | FWTPM_VERSION_MINOR); /* firmwareVersion */ /* Build response */ - rc = FwBuildAttestResponse(ctx, rsp, cmdTag, sigObj, - sigScheme, sigHashAlg, attestBuf, attestPkt.pos); + if (rc == 0) { + rc = FwBuildAttestResponse(ctx, rsp, cmdTag, sigObj, + sigScheme, sigHashAlg, attestBuf, attestPkt.pos); + } } FWTPM_FREE_BUF(attestBuf); return rc; @@ -16895,8 +16945,8 @@ static TPM_RC FwCmd_NV_Certify(FWTPM_CTX* ctx, TPM2_Packet* cmd, attestPkt.pos = 0; attestPkt.size = (int)FWTPM_MAX_ATTEST_BUF; - FwAppendAttestCommonHeader(ctx, &attestPkt, attestType, - &sigObj->name, &qualifyingData); + rc = FwAppendAttestCommonHeader(ctx, &attestPkt, attestType, + sigObj, &sigObj->name, &qualifyingData); if (digestMode) { /* attested.nvDigest: indexName + nvDigest From d3c5187f210ccb9569098b3209491e980044f851 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 16:48:43 -0700 Subject: [PATCH 54/57] F-15838 - Limit StirRandom input size --- src/fwtpm/fwtpm_command.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 7e28a6089..473d117ec 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -1559,7 +1559,10 @@ static TPM_RC FwCmd_StirRandom(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, if (rc == 0) { TPM2_Packet_ParseU16(cmd, &inDataSize); - if (cmd->pos + inDataSize > cmdSize) { + if (inDataSize > MAX_SYM_DATA) { + rc = TPM_RC_SIZE; + } + else if (cmd->pos + inDataSize > cmdSize) { rc = TPM_RC_COMMAND_SIZE; } } From f12078f90e0cf1dfac2aa69bafab4a0c92bc1856 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Tue, 13 Oct 2026 20:06:42 -0700 Subject: [PATCH 55/57] F-15835 - Validate PCR selection sizes --- src/fwtpm/fwtpm_command.c | 49 +++++++++++++++++++++++++++++++++++++++ tests/fwtpm_unit_tests.c | 44 +++++++++++++++++++++-------------- 2 files changed, 76 insertions(+), 17 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 473d117ec..cc31438e3 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -434,6 +434,43 @@ static int FwGetPcrBankIndex(UINT16 hashAlg) } } +static TPM_RC FwValidatePcrSelection(TPM2_Packet* cmd, int cmdSize) +{ + TPM2_Packet check; + UINT32 count; + UINT32 i; + + if (cmd == NULL || cmd->pos > cmdSize || cmdSize - cmd->pos < 4) { + return TPM_RC_COMMAND_SIZE; + } + + check = *cmd; + TPM2_Packet_ParseU32(&check, &count); + if (count > (UINT32)(cmdSize - check.pos) / 3U) { + return TPM_RC_COMMAND_SIZE; + } + + for (i = 0; i < count; i++) { + UINT16 hashAlg; + UINT8 sizeofSelect; + + TPM2_Packet_ParseU16(&check, &hashAlg); + TPM2_Packet_ParseU8(&check, &sizeofSelect); + (void)hashAlg; + + if (sizeofSelect < PCR_SELECT_MIN || + sizeofSelect > PCR_SELECT_MAX) { + return TPM_RC_VALUE; + } + if ((int)sizeofSelect > cmdSize - check.pos) { + return TPM_RC_COMMAND_SIZE; + } + TPM2_Packet_ParseBytes(&check, NULL, sizeofSelect); + } + + return TPM_RC_SUCCESS; +} + static UINT16 FwGetPcrBankAlg(int bank) { if (bank == FWTPM_PCR_BANK_SHA256) { @@ -2948,6 +2985,9 @@ static TPM_RC FwCmd_PCR_Read(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, if (cmdSize < TPM2_HEADER_SIZE + 4) { rc = TPM_RC_COMMAND_SIZE; } + if (rc == 0) { + rc = FwValidatePcrSelection(cmd, cmdSize); + } if (rc == 0) { TPM2_Packet_ParseU32(cmd, &pcrSelCount); @@ -3446,6 +3486,9 @@ static TPM_RC FwCmd_PCR_Allocate(FWTPM_CTX* ctx, TPM2_Packet* cmd, } /* Parse TPML_PCR_SELECTION */ + if (rc == 0) { + rc = FwValidatePcrSelection(cmd, cmdSize); + } if (rc == 0) { TPM2_Packet_ParseU32(cmd, &count); if (count > FWTPM_PCR_BANKS * 4) { @@ -11827,6 +11870,9 @@ static TPM_RC FwCmd_PolicyPCR(FWTPM_CTX* ctx, TPM2_Packet* cmd, } /* Parse PCR selection */ + if (rc == 0) { + rc = FwValidatePcrSelection(cmd, cmdSize); + } if (rc == 0) { TPM2_Packet_ParsePCR(cmd, &pcrs); if (cmd->overflow) { @@ -16376,6 +16422,9 @@ static TPM_RC FwCmd_Quote(FWTPM_CTX* ctx, TPM2_Packet* cmd, } /* PCRselect */ + if (rc == 0) { + rc = FwValidatePcrSelection(cmd, cmdSize); + } if (rc == 0) { TPM2_Packet_ParseU32(cmd, &pcrSelCount); numSel = pcrSelCount; diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index fa7611b60..08d4cb2ed 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -2152,13 +2152,13 @@ static void test_fwtpm_pcr_allocate(void) AssertIntEQ(rc, TPM_RC_SUCCESS); AssertIntEQ(GetRspRC(gRsp), TPM_RC_AUTH_TYPE); - /* Oversized selection count is rejected before parsing */ + /* A selection count that exceeds the remaining packet is truncated */ cmdSz = BuildPcrAllocateCmd(gCmd, TPM_RH_PLATFORM, algs, 1, 1); PutU32BE(gCmd + TPM2_HEADER_SIZE + 4 + 13, FWTPM_PCR_BANKS * 4 + 1); rspSize = 0; rc = FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); AssertIntEQ(rc, TPM_RC_SUCCESS); - AssertIntEQ(GetRspRC(gRsp), TPM_RC_SIZE); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_COMMAND_SIZE); /* Both banks selected. Per spec 22.5 the change is staged, so the live * allocation must not move until the next Startup(CLEAR). */ @@ -3794,7 +3794,8 @@ static void test_fwtpm_mldsa_quote_sign_and_verify(void) PutU16BE(gCmd + pos, TPM_ALG_NULL); pos += 2; /* inScheme */ PutU32BE(gCmd + pos, 1); pos += 4; /* PCRselect count */ PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; - gCmd[pos++] = 3; gCmd[pos++] = 0; gCmd[pos++] = 0; gCmd[pos++] = 0; + gCmd[pos++] = (byte)PCR_SELECT_MAX; + XMEMSET(gCmd + pos, 0, PCR_SELECT_MAX); pos += PCR_SELECT_MAX; PutU32BE(gCmd + 2, (UINT32)pos); AssertIntEQ(FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0), TPM_RC_SUCCESS); @@ -3835,7 +3836,8 @@ static void test_fwtpm_mldsa_quote_scheme_mismatch_rejected(void) PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; PutU32BE(gCmd + pos, 1); pos += 4; PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; - gCmd[pos++] = 3; gCmd[pos++] = 0; gCmd[pos++] = 0; gCmd[pos++] = 0; + gCmd[pos++] = (byte)PCR_SELECT_MAX; + XMEMSET(gCmd + pos, 0, PCR_SELECT_MAX); pos += PCR_SELECT_MAX; PutU32BE(gCmd + 2, (UINT32)pos); AssertIntEQ(FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0), TPM_RC_SUCCESS); @@ -3874,7 +3876,8 @@ static void test_fwtpm_mldsa_quote_explicit_empty_scheme(void) PutU16BE(gCmd + pos, TPM_ALG_MLDSA); pos += 2; /* explicit, no hashAlg */ PutU32BE(gCmd + pos, 1); pos += 4; /* PCRselect count */ PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; - gCmd[pos++] = 3; gCmd[pos++] = 0; gCmd[pos++] = 0; gCmd[pos++] = 0; + gCmd[pos++] = (byte)PCR_SELECT_MAX; + XMEMSET(gCmd + pos, 0, PCR_SELECT_MAX); pos += PCR_SELECT_MAX; PutU32BE(gCmd + 2, (UINT32)pos); AssertIntEQ(FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0), TPM_RC_SUCCESS); @@ -3950,7 +3953,8 @@ static void test_fwtpm_quote_mldsa_scheme_on_ecc_rejected(void) PutU16BE(gCmd + pos, TPM_ALG_MLDSA); pos += 2; /* ML-DSA selector */ PutU32BE(gCmd + pos, 1); pos += 4; PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; - gCmd[pos++] = 1; gCmd[pos++] = 0; gCmd[pos++] = 0; gCmd[pos++] = 0; + gCmd[pos++] = (byte)PCR_SELECT_MAX; + XMEMSET(gCmd + pos, 0, PCR_SELECT_MAX); pos += PCR_SELECT_MAX; PutU32BE(gCmd + 2, (UINT32)pos); AssertIntEQ(FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0), TPM_RC_SUCCESS); @@ -4069,7 +4073,8 @@ static void test_fwtpm_hash_mldsa_quote_sign_and_verify(void) PutU16BE(gCmd + pos, TPM_ALG_NULL); pos += 2; /* inScheme */ PutU32BE(gCmd + pos, 1); pos += 4; /* PCRselect count */ PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; - gCmd[pos++] = 3; gCmd[pos++] = 0; gCmd[pos++] = 0; gCmd[pos++] = 0; + gCmd[pos++] = (byte)PCR_SELECT_MAX; + XMEMSET(gCmd + pos, 0, PCR_SELECT_MAX); pos += PCR_SELECT_MAX; PutU32BE(gCmd + 2, (UINT32)pos); AssertIntEQ(FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0), TPM_RC_SUCCESS); @@ -4196,7 +4201,10 @@ static void test_fwtpm_hash_mldsa_quote_namealg_digest(void) PutU16BE(gCmd + pos, TPM_ALG_NULL); pos += 2; /* inScheme */ PutU32BE(gCmd + pos, 1); pos += 4; PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; - gCmd[pos++] = 1; gCmd[pos++] = 0; gCmd[pos++] = 0; gCmd[pos++] = 0; + gCmd[pos++] = (byte)PCR_SELECT_MAX; + XMEMSET(gCmd + pos, 0, PCR_SELECT_MAX); + gCmd[pos] = 0x01; + pos += PCR_SELECT_MAX; PutU32BE(gCmd + 2, (UINT32)pos); AssertIntEQ(FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0), TPM_RC_SUCCESS); @@ -5895,7 +5903,8 @@ static void test_fwtpm_mldsa_quote_public_only_returns_key(void) PutU16BE(gCmd + pos, TPM_ALG_NULL); pos += 2; /* inScheme */ PutU32BE(gCmd + pos, 1); pos += 4; /* PCRselect count */ PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; - gCmd[pos++] = 3; gCmd[pos++] = 0; gCmd[pos++] = 0; gCmd[pos++] = 0; + gCmd[pos++] = (byte)PCR_SELECT_MAX; + XMEMSET(gCmd + pos, 0, PCR_SELECT_MAX); pos += PCR_SELECT_MAX; PutU32BE(gCmd + 2, (UINT32)pos); rspSize = 0; AssertIntEQ(FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0), @@ -11901,8 +11910,10 @@ static TPM_RC SendPolicyPcrCmd(FWTPM_CTX* ctx, UINT32 sessH, } PutU32BE(gCmd + pos, 1); pos += 4; /* count */ PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; /* hash */ - gCmd[pos++] = 3; /* sizeofSelect */ - gCmd[pos++] = 0x01; gCmd[pos++] = 0x00; gCmd[pos++] = 0x00; /* PCR 0 */ + gCmd[pos++] = (byte)PCR_SELECT_MAX; /* sizeofSelect */ + XMEMSET(gCmd + pos, 0, PCR_SELECT_MAX); + gCmd[pos] = 0x01; /* PCR 0 */ + pos += PCR_SELECT_MAX; PutU32BE(gCmd + 2, (UINT32)pos); FWTPM_ProcessCommand(ctx, gCmd, pos, gRsp, &rspSize, 0); return GetRspRC(gRsp); @@ -12664,13 +12675,11 @@ static void test_fwtpm_quote_ecdaa_scheme(void) PutU16BE(gCmd + pos, TPM_ALG_ECDAA); pos += 2; PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; PutU16BE(gCmd + pos, 0); pos += 2; /* ECDAA count */ - /* PCRselect: count=1, hashAlg=SHA256, sizeOfSelect=3, no PCRs */ + /* PCRselect: count=1, hashAlg=SHA256, no PCRs */ PutU32BE(gCmd + pos, 1); pos += 4; PutU16BE(gCmd + pos, TPM_ALG_SHA256); pos += 2; - gCmd[pos++] = 3; - gCmd[pos++] = 0; - gCmd[pos++] = 0; - gCmd[pos++] = 0; + gCmd[pos++] = (byte)PCR_SELECT_MAX; + XMEMSET(gCmd + pos, 0, PCR_SELECT_MAX); pos += PCR_SELECT_MAX; PutU32BE(gCmd + 2, (UINT32)pos); rspSize = 0; FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); @@ -12907,7 +12916,8 @@ static int BuildQuoteCmd(byte* buf, UINT32 signHandle) PutU16BE(buf + pos, TPM_ALG_NULL); pos += 2; /* inScheme */ PutU32BE(buf + pos, 1); pos += 4; /* PCRselect count */ PutU16BE(buf + pos, TPM_ALG_SHA256); pos += 2; - buf[pos++] = 3; buf[pos++] = 0; buf[pos++] = 0; buf[pos++] = 0; + buf[pos++] = (byte)PCR_SELECT_MAX; + XMEMSET(buf + pos, 0, PCR_SELECT_MAX); pos += PCR_SELECT_MAX; PutU32BE(buf + 2, (UINT32)pos); return pos; } From 2272ac66531afb1fc2c5e3f27dd532168f60e800 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Thu, 15 Oct 2026 09:10:18 -0700 Subject: [PATCH 56/57] Rebase policy timeouts across Clear --- src/fwtpm/fwtpm_command.c | 32 ++++++++++++++++++++++++++++++++ tests/fwtpm_unit_tests.c | 31 +++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index cc31438e3..f22e60a21 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -505,6 +505,9 @@ static void FwLookupEntityAuth(FWTPM_CTX* ctx, TPM_HANDLE handle, const byte** authVal, int* authValSz); static int FwCtAuthCompare(const byte* password, int pwSz, const byte* authVal, int avSz); +#ifndef FWTPM_NO_POLICY +static void FwResetPolicyData(FWTPM_Session* sess); +#endif #ifndef FWTPM_NO_PARAM_ENC /* Build the parameter-encryption key = sessionKey || authValue, where authValue @@ -6057,6 +6060,11 @@ static TPM_RC FwCmd_Clear(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, byte newOwnerSeed[FWTPM_SEED_SIZE]; byte newTimeEpoch[FWTPM_SEED_SIZE]; int ci; +#ifndef FWTPM_NO_POLICY + UINT64 oldClock; + UINT64 newClock; + int si; +#endif if (cmdSize < TPM2_HEADER_SIZE + 4) { rc = TPM_RC_COMMAND_SIZE; @@ -6158,6 +6166,9 @@ static TPM_RC FwCmd_Clear(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, ctx->lockoutPolicyAlg = TPM_ALG_NULL; TPM2_ForceZero(ctx->pcrAuth, sizeof(ctx->pcrAuth)); + #ifndef FWTPM_NO_POLICY + oldClock = FWTPM_Clock_GetMs(ctx); + #endif if (ctx->clockHal.get_ms != NULL) { ctx->clockOffset = 0 - ctx->clockHal.get_ms(ctx->clockHal.ctx); @@ -6167,6 +6178,27 @@ static TPM_RC FwCmd_Clear(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, } ctx->resetCount = 0; ctx->restartCount = 0; + #ifndef FWTPM_NO_POLICY + newClock = FWTPM_Clock_GetMs(ctx); + for (si = 0; si < FWTPM_MAX_SESSIONS; si++) { + FWTPM_Session* sess = &ctx->sessions[si]; + if (!sess->used || + (sess->sessionType != TPM_SE_POLICY && + sess->sessionType != TPM_SE_TRIAL)) { + continue; + } + if (sess->timeout != 0) { + if (sess->timeout < oldClock) { + FwResetPolicyData(sess); + } + else { + sess->timeout = newClock + + (sess->timeout - oldClock); + } + } + sess->startTime = newClock; + } + #endif /* Reset disableClear per spec */ ctx->disableClear = 0; diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index 08d4cb2ed..e5754bb25 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -13950,11 +13950,34 @@ static void test_fwtpm_clear(void) byte ticketBefore[TPM_MAX_DIGEST_SIZE]; byte ticketAfter[TPM_MAX_DIGEST_SIZE]; byte digest[WC_SHA256_DIGEST_SIZE]; +#ifndef FWTPM_NO_POLICY + FWTPM_Session* policySess = NULL; + UINT32 policySessHandle; + int i; +#endif int ticketBeforeSz = 0; int ticketAfterSz = 0; memset(&ctx, 0, sizeof(ctx)); AssertIntEQ(fwtpm_test_startup(&ctx), 0); +#ifndef FWTPM_NO_POLICY + policySessHandle = StartSessionHelper(&ctx, TPM_SE_POLICY); + AssertIntNE(policySessHandle, 0); + for (i = 0; i < FWTPM_MAX_SESSIONS; i++) { + if (ctx.sessions[i].used && + ctx.sessions[i].handle == policySessHandle) { + policySess = &ctx.sessions[i]; + break; + } + } + AssertNotNull(policySess); + policySess->startTime = 1000; + policySess->timeout = 2000; + policySess->commandCode = TPM_CC_Clear; + /* Clear must not make an already-expired policy usable again when it + * resets Clock. */ + ctx.clockOffset = 10000; +#endif XMEMCPY(ownerSeed, ctx.ownerSeed, sizeof(ownerSeed)); XMEMCPY(endorsementSeed, ctx.endorsementSeed, sizeof(endorsementSeed)); XMEMSET(digest, 0x5A, sizeof(digest)); @@ -13964,6 +13987,14 @@ static void test_fwtpm_clear(void) AssertIntEQ(SendSimpleSessionCmd(&ctx, TPM_CC_Clear, TPM_RH_LOCKOUT), TPM_RC_SUCCESS); +#ifndef FWTPM_NO_POLICY + AssertIntEQ(policySess->used, 1); + AssertIntEQ(policySess->handle, policySessHandle); + AssertIntEQ((int)policySess->timeout, 0); + AssertIntEQ(policySess->commandCode, 0); + AssertIntEQ(SendPolicyCmd(&ctx, TPM_CC_PolicyGetDigest, + policySessHandle), TPM_RC_SUCCESS); +#endif AssertIntNE(XMEMCMP(ctx.ownerSeed, ownerSeed, sizeof(ownerSeed)), 0); AssertIntEQ(XMEMCMP(ctx.endorsementSeed, endorsementSeed, sizeof(endorsementSeed)), 0); From 334c6d08505ddcecc6be99f32ba349e615bb3d82 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Fri, 16 Oct 2026 11:51:10 -0700 Subject: [PATCH 57/57] Address PR 629 review --- .github/workflows/sanitizer.yml | 6 ++++ src/fwtpm/fwtpm_command.c | 50 ++++----------------------------- src/fwtpm/fwtpm_io.c | 11 -------- tests/fwtpm_check.sh | 13 ++++++++- tests/fwtpm_io_test_adapter.c | 9 ++++++ tests/fwtpm_unit_tests.c | 48 ++++++++++++++++++------------- tests/unit_tests.c | 31 +++++++++++++++++--- wolftpm/fwtpm/fwtpm_command.h | 5 +++- 8 files changed, 91 insertions(+), 82 deletions(-) diff --git a/.github/workflows/sanitizer.yml b/.github/workflows/sanitizer.yml index f611630bc..a2405cd28 100644 --- a/.github/workflows/sanitizer.yml +++ b/.github/workflows/sanitizer.yml @@ -49,6 +49,12 @@ jobs: - name: "LeakSan" cflags: "-fsanitize=leak -fno-omit-frame-pointer -g" ldflags: "-fsanitize=leak" + - name: "ASan (small stack)" + cflags: "-fsanitize=address -fno-omit-frame-pointer -g -O1" + ldflags: "-fsanitize=address" + asan_options: "detect_leaks=0" + wolftpm_extra_config: "--enable-smallstack" + wolfssl_extra_config: "--enable-smallstack" # Same base sanitizers, but linked against the latest wolfSSL -stable # release instead of master. diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index f22e60a21..e9f1dd497 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -499,7 +499,7 @@ typedef struct FWTPM_EventHashCopies { * tpm2_param_enc.c (TPM2_KDFa, TPM2_KDFe, TPM2_ParamEnc_XOR, * TPM2_ParamEnc_AESCFB). See wolftpm/tpm2_param_enc.h */ -static int FwSessionIsBoundToEntity(FWTPM_CTX* ctx, +WOLFTPM_TEST_VIS int FwSessionIsBoundToEntity(FWTPM_CTX* ctx, const FWTPM_Session* sess, TPM_HANDLE handle); static void FwLookupEntityAuth(FWTPM_CTX* ctx, TPM_HANDLE handle, const byte** authVal, int* authValSz); @@ -741,7 +741,7 @@ static int FwGetEntityName(FWTPM_CTX* ctx, TPM_HANDLE handle, return 0; } -static int FwSessionIsBoundToEntity(FWTPM_CTX* ctx, +WOLFTPM_TEST_VIS int FwSessionIsBoundToEntity(FWTPM_CTX* ctx, const FWTPM_Session* sess, TPM_HANDLE handle) { byte entityName[sizeof(sess->bindName.name)]; @@ -764,17 +764,6 @@ static int FwSessionIsBoundToEntity(FWTPM_CTX* ctx, entityAuth, entityAuthSz) == 0; } -#ifdef WOLFTPM_FWTPM_UNIT_TEST -int FWTPM_TestSessionIsBoundToEntity(FWTPM_CTX* ctx, - const FWTPM_Session* sess, TPM_HANDLE handle); - -int FWTPM_TestSessionIsBoundToEntity(FWTPM_CTX* ctx, - const FWTPM_Session* sess, TPM_HANDLE handle) -{ - return FwSessionIsBoundToEntity(ctx, sess, handle); -} -#endif - /** \brief Look up the authValue for an entity by its TPM handle. * Searches hierarchies, NV indices, loaded objects, and hash sequences. */ static void FwLookupEntityAuth(FWTPM_CTX* ctx, TPM_HANDLE handle, @@ -6031,26 +6020,6 @@ static TPM_RC FwCmd_ReadPublic(FWTPM_CTX* ctx, TPM2_Packet* cmd, return rc; } -#ifdef WOLFTPM_FWTPM_UNIT_TEST -static int gFwTestClearRngFailure; - -void FWTPM_TestSetClearRngFailure(int enabled); - -void FWTPM_TestSetClearRngFailure(int enabled) -{ - gFwTestClearRngFailure = enabled; -} -#endif - -static int FwGenerateClearSeed(WC_RNG* rng, byte* seed, word32 seedSz) -{ -#ifdef WOLFTPM_FWTPM_UNIT_TEST - if (gFwTestClearRngFailure) - return TPM_RC_FAILURE; -#endif - return wc_RNG_GenerateBlock(rng, seed, seedSz); -} - /* --- TPM2_Clear (CC 0x0126) --- */ static TPM_RC FwCmd_Clear(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, TPM2_Packet* rsp, UINT16 cmdTag) @@ -6094,10 +6063,10 @@ static TPM_RC FwCmd_Clear(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, #endif /* Generate new storage primary seed */ - rc = FwGenerateClearSeed(&ctx->rng, newOwnerSeed, + rc = wc_RNG_GenerateBlock(&ctx->rng, newOwnerSeed, sizeof(newOwnerSeed)); if (rc == 0) { - rc = FwGenerateClearSeed(&ctx->rng, newTimeEpoch, + rc = wc_RNG_GenerateBlock(&ctx->rng, newTimeEpoch, sizeof(newTimeEpoch)); } if (rc != 0) rc = TPM_RC_FAILURE; @@ -17556,7 +17525,7 @@ static int FwHexNibble(char c) /* Convert a big-endian hex value to binary. An odd leading digit is the low * nibble of the first output byte. Returns byte count, or -1 on error. */ -static int FwHexToBin(const char* hex, byte* out, int outSz) +WOLFTPM_TEST_VIS int FwHexToBin(const char* hex, byte* out, int outSz) { int i, len, decodedLen, outPos = 0; int hi, lo; @@ -17586,15 +17555,6 @@ static int FwHexToBin(const char* hex, byte* out, int outSz) return outPos; } -#ifdef WOLFTPM_FWTPM_UNIT_TEST -int FWTPM_TestHexToBin(const char* hex, byte* out, int outSz); - -int FWTPM_TestHexToBin(const char* hex, byte* out, int outSz) -{ - return FwHexToBin(hex, out, outSz); -} -#endif - /* --- TPM2_ECC_Parameters (CC 0x0178) --- * Returns curve parameters from wolfCrypt's ecc_set_type via * wc_ecc_get_curve_params(). Automatically supports P-256, P-384, diff --git a/src/fwtpm/fwtpm_io.c b/src/fwtpm/fwtpm_io.c index 2137dd999..dc3fdf989 100644 --- a/src/fwtpm/fwtpm_io.c +++ b/src/fwtpm/fwtpm_io.c @@ -375,17 +375,6 @@ static int HandleMssimSignal(FWTPM_CTX* ctx, SOCKET_T clientFd, UINT32 tssCmd) return TransportSend(ctx, FWTPM_IO_TYPE_COMMAND, clientFd, &netVal, 4); } -#ifdef WOLFTPM_FWTPM_UNIT_TEST -int FWTPM_IO_TestHandleMssimSignal(FWTPM_CTX* ctx, SOCKET_T clientFd, - UINT32 tssCmd); - -int FWTPM_IO_TestHandleMssimSignal(FWTPM_CTX* ctx, SOCKET_T clientFd, - UINT32 tssCmd) -{ - return HandleMssimSignal(ctx, clientFd, tssCmd); -} -#endif - /* --- Process and send TPM command response --- */ static int DispatchAndRespond(FWTPM_CTX* ctx, UINT32 cmdSize, int locality, SOCKET_T clientFd, int isSwtpm) diff --git a/tests/fwtpm_check.sh b/tests/fwtpm_check.sh index 581f0f7db..4df6b40f2 100755 --- a/tests/fwtpm_check.sh +++ b/tests/fwtpm_check.sh @@ -25,6 +25,7 @@ FAIL=0 SKIP=0 STARTED_SERVER=0 SKIP_EXAMPLES=0 +SKIP_EXAMPLES_REASON="" # --- Helpers --- @@ -250,6 +251,15 @@ if [ -f "$WOLFTPM_OPTIONS" ]; then if grep -q "^#define NO_GETENV" "$WOLFTPM_OPTIONS"; then HAS_GETENV=0 fi + if [ $IS_FWTPM_MODE -eq 1 ] && \ + grep -q "^#define WOLFTPM_SMALL_STACK" "$WOLFTPM_OPTIONS"; then + # fwTPM serializes private RSA state into Create responses that exceed + # the supported 1,350-byte small-stack client response buffer. The + # unit tests use ECC for those cases; skip the general example suite, + # which intentionally exercises RSA child-key creation. + SKIP_EXAMPLES=1 + SKIP_EXAMPLES_REASON="fwTPM RSA responses exceed small-stack buffer" + fi fi # Determine mode @@ -293,6 +303,7 @@ if [ $HAS_RSA_NO_PAD -eq 0 ]; then echo " Rebuild wolfSSL with: CFLAGS=\"-DWC_RSA_NO_PADDING\"" echo " fwTPM requires WC_RSA_NO_PADDING for RSA encrypt/decrypt" SKIP_EXAMPLES=1 + SKIP_EXAMPLES_REASON="missing WC_RSA_NO_PADDING" fi # --- Auto-detect feature flags for run_examples.sh --- @@ -502,7 +513,7 @@ fi # --- Run examples --- if [ $SKIP_EXAMPLES -eq 1 ]; then - echo "SKIP: run_examples.sh (missing WC_RSA_NO_PADDING)" + echo "SKIP: run_examples.sh ($SKIP_EXAMPLES_REASON)" SKIP=$((SKIP + 1)) elif [ -x "$RUN_EXAMPLES" ]; then echo "" diff --git a/tests/fwtpm_io_test_adapter.c b/tests/fwtpm_io_test_adapter.c index d9a3c838d..397d90e45 100644 --- a/tests/fwtpm_io_test_adapter.c +++ b/tests/fwtpm_io_test_adapter.c @@ -14,3 +14,12 @@ #endif #include "../src/fwtpm/fwtpm_io.c" + +int FWTPM_IO_TestHandleMssimSignal(FWTPM_CTX* ctx, SOCKET_T clientFd, + UINT32 tssCmd); + +int FWTPM_IO_TestHandleMssimSignal(FWTPM_CTX* ctx, SOCKET_T clientFd, + UINT32 tssCmd) +{ + return HandleMssimSignal(ctx, clientFd, tssCmd); +} diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index e5754bb25..ccbb4f8b8 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -67,6 +67,15 @@ int FWTPM_IO_TestHandleMssimSignal(FWTPM_CTX* ctx, SOCKET_T clientFd, UINT32 tssCmd); #endif +#if defined(WOLFTPM_FWTPM_UNIT_TEST) && defined(HAVE_HASHDRBG) + /* wolfSSL 5.8 and older keep this Hash-DRBG state value private. */ + #ifdef WC_DRBG_FAILED + #define FWTPM_TEST_DRBG_FAILED WC_DRBG_FAILED + #else + #define FWTPM_TEST_DRBG_FAILED 2 + #endif +#endif + /* Test Fail Helpers (same as unit_tests.c) */ #ifndef NO_ABORT #ifndef XABORT @@ -1518,21 +1527,19 @@ static void test_fwtpm_getcap_ecc_curves(void) #if defined(HAVE_ECC) && !defined(FWTPM_NO_ECDH) && \ defined(WOLFTPM_FWTPM_UNIT_TEST) -int FWTPM_TestHexToBin(const char* hex, byte* out, int outSz); - static void test_fwtpm_hex_to_bin(void) { byte out[2]; XMEMSET(out, 0, sizeof(out)); - AssertIntEQ(FWTPM_TestHexToBin("abc", out, sizeof(out)), 2); + AssertIntEQ(FwHexToBin("abc", out, sizeof(out)), 2); AssertIntEQ(out[0], 0x0a); AssertIntEQ(out[1], 0xbc); - AssertIntEQ(FWTPM_TestHexToBin("f", out, sizeof(out)), 1); + AssertIntEQ(FwHexToBin("f", out, sizeof(out)), 1); AssertIntEQ(out[0], 0x0f); - AssertIntEQ(FWTPM_TestHexToBin("0g", out, sizeof(out)), -1); - AssertIntEQ(FWTPM_TestHexToBin("1234", out, 1), -1); - AssertIntEQ(FWTPM_TestHexToBin(NULL, out, sizeof(out)), -1); + AssertIntEQ(FwHexToBin("0g", out, sizeof(out)), -1); + AssertIntEQ(FwHexToBin("1234", out, 1), -1); + AssertIntEQ(FwHexToBin(NULL, out, sizeof(out)), -1); fwtpm_pass("ECC parameter hex decoding:", 0); } @@ -10343,7 +10350,7 @@ static UINT32 StartSessionHelper(FWTPM_CTX* ctx, UINT8 sessType) } #ifdef WOLFTPM_FWTPM_UNIT_TEST -int FWTPM_TestSessionIsBoundToEntity(FWTPM_CTX* ctx, +int FwSessionIsBoundToEntity(FWTPM_CTX* ctx, const FWTPM_Session* sess, TPM_HANDLE handle); static void test_fwtpm_bound_session_auth_change(void) @@ -10363,10 +10370,10 @@ static void test_fwtpm_bound_session_auth_change(void) sess.bindAuth.size = (UINT16)sizeof(oldAuth); XMEMCPY(sess.bindAuth.buffer, oldAuth, sizeof(oldAuth)); - AssertIntEQ(FWTPM_TestSessionIsBoundToEntity(&ctx, &sess, + AssertIntEQ(FwSessionIsBoundToEntity(&ctx, &sess, TPM_RH_OWNER), 1); XMEMCPY(ctx.ownerAuth.buffer, newAuth, sizeof(newAuth)); - AssertIntEQ(FWTPM_TestSessionIsBoundToEntity(&ctx, &sess, + AssertIntEQ(FwSessionIsBoundToEntity(&ctx, &sess, TPM_RH_OWNER), 0); TPM2_ForceZero(&ctx, sizeof(ctx)); @@ -14012,9 +14019,7 @@ static void test_fwtpm_clear(void) fwtpm_pass("Clear preserves endorsement seed:", 0); } -#ifdef WOLFTPM_FWTPM_UNIT_TEST -void FWTPM_TestSetClearRngFailure(int enabled); - +#if defined(WOLFTPM_FWTPM_UNIT_TEST) && defined(HAVE_HASHDRBG) /* A failed seed replacement must leave the live hierarchy state unchanged. */ static void test_fwtpm_clear_rng_failure_atomic(void) { @@ -14022,6 +14027,7 @@ static void test_fwtpm_clear_rng_failure_atomic(void) TPM2B_AUTH ownerAuth; TPM2B_AUTH endorsementAuth; byte ownerSeed[FWTPM_SEED_SIZE]; + byte rngStatus; XMEMSET(&ctx, 0, sizeof(ctx)); AssertIntEQ(fwtpm_test_startup(&ctx), TPM_RC_SUCCESS); @@ -14034,10 +14040,12 @@ static void test_fwtpm_clear_rng_failure_atomic(void) sizeof(endorsementAuth)); XMEMCPY(ownerSeed, ctx.ownerSeed, sizeof(ownerSeed)); - FWTPM_TestSetClearRngFailure(1); + /* Use wolfCrypt's real failed-DRBG path instead of a library test hook. */ + rngStatus = ctx.rng.status; + ctx.rng.status = FWTPM_TEST_DRBG_FAILED; AssertIntEQ(SendSimpleSessionCmd(&ctx, TPM_CC_Clear, TPM_RH_LOCKOUT), TPM_RC_FAILURE); - FWTPM_TestSetClearRngFailure(0); + ctx.rng.status = rngStatus; AssertIntEQ(ctx.pendingClear, 0); AssertIntEQ(XMEMCMP(&ctx.ownerAuth, &ownerAuth, sizeof(ownerAuth)), 0); AssertIntEQ(XMEMCMP(&ctx.endorsementAuth, &endorsementAuth, @@ -14050,7 +14058,7 @@ static void test_fwtpm_clear_rng_failure_atomic(void) FWTPM_Cleanup(&ctx); fwtpm_pass("Clear RNG failure is atomic:", 0); } -#endif /* WOLFTPM_FWTPM_UNIT_TEST */ +#endif /* WOLFTPM_FWTPM_UNIT_TEST && HAVE_HASHDRBG */ #ifndef FWTPM_NO_NV static int fail_nv_write(void* ctx, word32 offset, const byte* buf, @@ -18197,12 +18205,12 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_clear(); return 0; } - #ifdef WOLFTPM_FWTPM_UNIT_TEST + #if defined(WOLFTPM_FWTPM_UNIT_TEST) && defined(HAVE_HASHDRBG) if (argc == 2 && XSTRCMP(argv[1], "clear-rng-failure") == 0) { test_fwtpm_clear_rng_failure_atomic(); return 0; } - #endif /* WOLFTPM_FWTPM_UNIT_TEST */ + #endif /* WOLFTPM_FWTPM_UNIT_TEST && HAVE_HASHDRBG */ #if !defined(FWTPM_NO_POLICY) && !defined(NO_RSA) && \ defined(WOLFSSL_KEY_GEN) if (argc == 2 && XSTRCMP(argv[1], "clear-child") == 0) { @@ -18690,9 +18698,9 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_nv_delete_at_capacity(); test_fwtpm_nv_stclear_startup(); #endif /* !FWTPM_NO_NV */ - #ifdef WOLFTPM_FWTPM_UNIT_TEST + #if defined(WOLFTPM_FWTPM_UNIT_TEST) && defined(HAVE_HASHDRBG) test_fwtpm_clear_rng_failure_atomic(); - #endif /* WOLFTPM_FWTPM_UNIT_TEST */ + #endif /* WOLFTPM_FWTPM_UNIT_TEST && HAVE_HASHDRBG */ test_fwtpm_clear(); #if defined(WOLFTPM_FWTPM_UNIT_TEST) && !defined(_WIN32) diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 957cb47f1..ab1844dd6 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -2288,7 +2288,11 @@ static void test_wolfTPM2_BoundSession_EmptyAuth_ParamEnc(void) WOLFTPM2_KEY child; WOLFTPM2_SESSION session; TPMT_PUBLIC publicTemplate; -#if !defined(NO_RSA) +#if defined(WOLFTPM_SMALL_STACK) && defined(WOLFTPM_FWTPM_BUILD) && \ + defined(HAVE_ECC) + /* RSA Create responses exceed the reduced small-stack response buffer. */ + TPM_ALG_ID srkAlg = TPM_ALG_ECC; +#elif !defined(NO_RSA) TPM_ALG_ID srkAlg = TPM_ALG_RSA; #else TPM_ALG_ID srkAlg = TPM_ALG_ECC; @@ -2329,7 +2333,13 @@ static void test_wolfTPM2_BoundSession_EmptyAuth_ParamEnc(void) AssertIntEQ(rc, TPM_RC_SUCCESS); /* Create+load a child under the EmptyAuth SRK; fails pre-fix. */ -#if !defined(NO_RSA) +#if defined(WOLFTPM_SMALL_STACK) && defined(WOLFTPM_FWTPM_BUILD) && \ + defined(HAVE_ECC) + rc = wolfTPM2_GetKeyTemplate_ECC(&publicTemplate, + TPMA_OBJECT_sensitiveDataOrigin | TPMA_OBJECT_userWithAuth | + TPMA_OBJECT_sign | TPMA_OBJECT_noDA, + TPM_ECC_NIST_P256, TPM_ALG_ECDSA); +#elif !defined(NO_RSA) rc = wolfTPM2_GetKeyTemplate_RSA(&publicTemplate, TPMA_OBJECT_sensitiveDataOrigin | TPMA_OBJECT_userWithAuth | TPMA_OBJECT_sign | TPMA_OBJECT_noDA); @@ -2368,7 +2378,11 @@ static void test_wolfTPM2_CreateLoaded_ParamEnc(void) WOLFTPM2_KEYBLOB child; WOLFTPM2_SESSION session; TPMT_PUBLIC publicTemplate; -#if !defined(NO_RSA) +#if defined(WOLFTPM_SMALL_STACK) && defined(WOLFTPM_FWTPM_BUILD) && \ + defined(HAVE_ECC) + /* RSA CreateLoaded responses exceed the small-stack response buffer. */ + TPM_ALG_ID srkAlg = TPM_ALG_ECC; +#elif !defined(NO_RSA) TPM_ALG_ID srkAlg = TPM_ALG_RSA; #else TPM_ALG_ID srkAlg = TPM_ALG_ECC; @@ -2405,7 +2419,13 @@ static void test_wolfTPM2_CreateLoaded_ParamEnc(void) TPMA_SESSION_continueSession)); AssertIntEQ(rc, TPM_RC_SUCCESS); -#if !defined(NO_RSA) +#if defined(WOLFTPM_SMALL_STACK) && defined(WOLFTPM_FWTPM_BUILD) && \ + defined(HAVE_ECC) + rc = wolfTPM2_GetKeyTemplate_ECC(&publicTemplate, + TPMA_OBJECT_sensitiveDataOrigin | TPMA_OBJECT_userWithAuth | + TPMA_OBJECT_sign | TPMA_OBJECT_noDA, + TPM_ECC_NIST_P256, TPM_ALG_ECDSA); +#elif !defined(NO_RSA) rc = wolfTPM2_GetKeyTemplate_RSA(&publicTemplate, TPMA_OBJECT_sensitiveDataOrigin | TPMA_OBJECT_userWithAuth | TPMA_OBJECT_sign | TPMA_OBJECT_noDA); @@ -10306,7 +10326,10 @@ int unit_tests(int argc, char *argv[]) !defined(WOLFTPM2_NO_WOLFCRYPT) && defined(HAVE_ECC) test_readKeyBlob_PrivOverflow(); #endif + /* An RSA Create response does not fit the reduced small-stack buffer. */ + #if !defined(WOLFTPM_SMALL_STACK) || !defined(WOLFTPM_FWTPM_BUILD) test_wolfTPM2_KeyBlob(TPM_ALG_RSA); + #endif test_wolfTPM2_KeyBlob(TPM_ALG_ECC); #if !defined(WOLFTPM2_NO_WOLFCRYPT) && defined(HAVE_ECC) && \ !defined(WOLFTPM2_NO_ASN) diff --git a/wolftpm/fwtpm/fwtpm_command.h b/wolftpm/fwtpm/fwtpm_command.h index ffecf895c..52d1b538d 100644 --- a/wolftpm/fwtpm/fwtpm_command.h +++ b/wolftpm/fwtpm/fwtpm_command.h @@ -66,7 +66,7 @@ WOLFTPM_API int FWTPM_ProcessCommand(FWTPM_CTX* ctx, */ WOLFTPM_API void FWTPM_ResetCommandClient(FWTPM_CTX* ctx); -/* Internal crypto helpers exposed only to the fwTPM unit-test build. */ +/* Internal helpers exposed only to the fwTPM unit-test build. */ #ifdef WOLFTPM_FWTPM_UNIT_TEST #define WOLFTPM_TEST_VIS int FwHashOneShot(TPMI_ALG_HASH hashAlg, @@ -83,6 +83,9 @@ WOLFTPM_API void FWTPM_ResetCommandClient(FWTPM_CTX* ctx); const byte* authValue, int authValueSz, const TPM2B_NONCE* nonceDecrypt, const TPM2B_NONCE* nonceEncrypt, int isResponse, byte* hmacOut, int* hmacOutSz); + #ifndef FWTPM_NO_ECDH + int FwHexToBin(const char* hex, byte* out, int outSz); + #endif #else #define WOLFTPM_TEST_VIS static #endif