From 5617f43aae5c49da97217632ce8d0ae94f0ee3bb Mon Sep 17 00:00:00 2001 From: aidan garske Date: Tue, 2 Jun 2026 13:06:36 -0700 Subject: [PATCH] F-5277 F-5122 - Store and enforce policy cpHashA command binding --- src/fwtpm/fwtpm_command.c | 58 ++++++++++++++++++++++++++++++++---- tests/fwtpm_unit_tests.c | 62 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 6 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 0ebc0a64..53068a6f 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -8856,6 +8856,8 @@ static TPM_RC FwCmd_PolicySecret(FWTPM_CTX* ctx, TPM2_Packet* cmd, UINT16 nonceTpmSz, cpHashASz, policyRefSz = 0; INT32 expiration; byte policyRef[64]; + byte nonceTpmBuf[TPM_MAX_DIGEST_SIZE]; + byte cpHashBuf[TPM_MAX_DIGEST_SIZE]; byte entityName[sizeof(TPM2B_NAME)]; int entityNameSz = 0; FWTPM_Session* sess; @@ -8868,19 +8870,25 @@ static TPM_RC FwCmd_PolicySecret(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (rc == 0) { TPM2_Packet_ParseU16(cmd, &nonceTpmSz); - if (cmd->pos + nonceTpmSz > cmdSize) { - rc = TPM_RC_COMMAND_SIZE; + if (nonceTpmSz > (UINT16)sizeof(nonceTpmBuf) || + cmd->pos + nonceTpmSz > cmdSize) { + rc = TPM_RC_SIZE; } } if (rc == 0) { - cmd->pos += nonceTpmSz; + if (nonceTpmSz > 0) { + TPM2_Packet_ParseBytes(cmd, nonceTpmBuf, nonceTpmSz); + } TPM2_Packet_ParseU16(cmd, &cpHashASz); - if (cmd->pos + cpHashASz > cmdSize) { - rc = TPM_RC_COMMAND_SIZE; + if (cpHashASz > (UINT16)sizeof(cpHashBuf) || + cmd->pos + cpHashASz > cmdSize) { + rc = TPM_RC_SIZE; } } if (rc == 0) { - cmd->pos += cpHashASz; + if (cpHashASz > 0) { + TPM2_Packet_ParseBytes(cmd, cpHashBuf, cpHashASz); + } TPM2_Packet_ParseU16(cmd, &policyRefSz); if (policyRefSz > (UINT16)sizeof(policyRef)) { rc = TPM_RC_SIZE; @@ -8909,6 +8917,16 @@ static TPM_RC FwCmd_PolicySecret(FWTPM_CTX* ctx, TPM2_Packet* cmd, rc = TPM_RC_AUTH_TYPE; } + /* A supplied nonceTPM must match the session nonce (Part 3 Sec.23.4), + * preventing replay of a PolicySecret authorization to another session. */ + if (rc == 0 && nonceTpmSz > 0) { + if (nonceTpmSz != sess->nonceTPM.size || + TPM2_ConstantCompare(nonceTpmBuf, sess->nonceTPM.buffer, + nonceTpmSz) != 0) { + rc = TPM_RC_VALUE; + } + } + if (rc == 0) { /* Auth verification for authHandle is handled by the command dispatch * framework (FWTPM_ProcessCommand) via the authorization area, not @@ -8926,6 +8944,12 @@ static TPM_RC FwCmd_PolicySecret(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } + /* Bind the command to cpHashA so policy enforcement can verify it */ + if (rc == 0 && cpHashASz > 0) { + sess->cpHashA.size = cpHashASz; + XMEMCPY(sess->cpHashA.buffer, cpHashBuf, cpHashASz); + } + if (rc == 0) { /* Response: timeout(TPM2B size=0) + ticket(TPMT_TK_AUTH) */ paramStart = FwRspParamsBegin(rsp, cmdTag, ¶mSzPos); @@ -9431,6 +9455,12 @@ static TPM_RC FwCmd_PolicySigned(FWTPM_CTX* ctx, TPM2_Packet* cmd, } } + /* Bind the command to cpHashA so policy enforcement can verify it */ + if (rc == 0 && cpHashASz > 0) { + sess->cpHashA.size = cpHashASz; + XMEMCPY(sess->cpHashA.buffer, cpHashBuf, cpHashASz); + } + if (rc == 0) { /* Response: timeout(TPM2B size=0) + ticket(TPMT_TK_AUTH) */ paramStart = FwRspParamsBegin(rsp, cmdTag, ¶mSzPos); @@ -15775,6 +15805,22 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, TPM_ST_NO_SESSIONS, TPM_RC_LOCALITY); return TPM_RC_SUCCESS; } + /* Enforce any PolicyCpHash command binding: the command's + * cpHash must equal the value the policy committed to. */ + if (pSess->cpHashA.size > 0) { + byte ccpHash[TPM_MAX_DIGEST_SIZE]; + int ccpHashSz = 0; + if (FwComputeCpHash(pSess->authHash, cmdCode, + cmdBuf, cmdSize, cmdHandles, cmdHandleCnt, + ctx, cpStart, ccpHash, &ccpHashSz) != 0 || + (int)pSess->cpHashA.size != ccpHashSz || + TPM2_ConstantCompare(pSess->cpHashA.buffer, + ccpHash, (word32)ccpHashSz) != 0) { + *rspSize = FwBuildErrorResponse(rspBuf, + TPM_ST_NO_SESSIONS, TPM_RC_POLICY_FAIL); + return TPM_RC_SUCCESS; + } + } } else if (authPolicy != NULL && authPolicy->size == 0 && cmdAuths[pj].cmdHmacSize == 0) { diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index 0104c858..90485c53 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -7170,6 +7170,67 @@ static void test_fwtpm_policy_locality_enforced(void) FWTPM_Cleanup(&ctx); printf("Test fwTPM:\tPolicyLocality enforced:\tPassed\n"); } + +/* PolicyCpHash binds a session to a specific command; a command whose real + * cpHash differs must be rejected even when the policyDigest matches. */ +static void test_fwtpm_policy_cphash_enforced(void) +{ + FWTPM_CTX ctx; + int pos, cmdSz, rspSize = 0; + UINT32 sessH; + UINT16 dSz; + byte digest[64]; + byte cph[32]; + UINT32 nvIdx = 0x01500062; + UINT32 nvAttrs = TPMA_NV_OWNERWRITE | TPMA_NV_OWNERREAD | TPMA_NV_NO_DA; + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + memset(cph, 0xCC, sizeof(cph)); + + sessH = StartSessionHelper(&ctx, TPM_SE_POLICY); + AssertIntNE(sessH, 0); + + /* Bind the session to a cpHash no real command will produce */ + pos = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_PolicyCpHash); + PutU32BE(gCmd + pos, sessH); pos += 4; + PutU16BE(gCmd + pos, (UINT16)sizeof(cph)); pos += 2; + memcpy(gCmd + pos, cph, sizeof(cph)); pos += sizeof(cph); + PutU32BE(gCmd + 2, (UINT32)pos); + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + AssertIntEQ(SendPolicyCmd(&ctx, TPM_CC_PolicyGetDigest, sessH), + TPM_RC_SUCCESS); + dSz = GetU16BE(gRsp + TPM2_HEADER_SIZE + 4); + AssertIntEQ(dSz, 32); + memcpy(digest, gRsp + TPM2_HEADER_SIZE + 6, dSz); + + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_SetPrimaryPolicy); pos += 4; + PutU32BE(gCmd + pos, TPM_RH_OWNER); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU16BE(gCmd + pos, dSz); pos += 2; + memcpy(gCmd + pos, digest, dSz); pos += dSz; + 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); + + /* The NV_DefineSpace cpHash will not match the bound cpHashA */ + cmdSz = BuildNvDefineCmd(gCmd, nvIdx, 8, nvAttrs); + PutU32BE(gCmd + 18, sessH); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_POLICY_FAIL); + + FlushHandle(&ctx, sessH); + FWTPM_Cleanup(&ctx); + printf("Test fwTPM:\tPolicyCpHash enforced:\tPassed\n"); +} #endif /* !FWTPM_NO_NV */ #endif /* !FWTPM_NO_POLICY */ @@ -9255,6 +9316,7 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_policynv_owner_read_denied(); test_fwtpm_policyauthorizenv_owner_read_denied(); test_fwtpm_policy_locality_enforced(); + test_fwtpm_policy_cphash_enforced(); #endif #endif