diff --git a/examples/firmware/st33_fw_update.c b/examples/firmware/st33_fw_update.c index 55a53c5e..2120df3d 100644 --- a/examples/firmware/st33_fw_update.c +++ b/examples/firmware/st33_fw_update.c @@ -97,6 +97,13 @@ static int TPM2_ST33_SendFirmwareData(fw_info_t* fwinfo) blob_len = ((uint32_t)blob_header[1] << 8) | blob_header[2]; blob_total = blob_len + 3; + if (blob_len > 2048) { + printf("Error: Blob length %u exceeds maximum 2048 at offset %u\n", + blob_len, offset); + rc = BUFFER_E; + break; + } + if (offset + blob_total > fwinfo->firmware_bufSz) { printf("Error: Incomplete blob at offset %u\n", offset); rc = BUFFER_E; diff --git a/examples/native/native_test.c b/examples/native/native_test.c index 79eec0e9..41a43df0 100644 --- a/examples/native/native_test.c +++ b/examples/native/native_test.c @@ -963,7 +963,7 @@ int TPM2_Native_TestArgs(void* userCtx, int argc, char *argv[]) TPM2_GetRCString(rc)); goto exit; } - if (cmdOut.seqComp.result.size != TPM_SHA256_DIGEST_SIZE && + if (cmdOut.seqComp.result.size != TPM_SHA256_DIGEST_SIZE || XMEMCMP(cmdOut.seqComp.result.buffer, hashTestDig, TPM_SHA256_DIGEST_SIZE) != 0) { printf("Hash SHA256 test failed, result not as expected!\n"); diff --git a/examples/pcr/policy.c b/examples/pcr/policy.c index ad2600c7..d79d0cb1 100644 --- a/examples/pcr/policy.c +++ b/examples/pcr/policy.c @@ -104,12 +104,13 @@ int TPM2_PCR_Policy_Test(void* userCtx, int argc, char *argv[]) else { digestLen = (word32)XSTRLEN(digestStr); } - if (digestLen > sizeof(digest)*2) { + if (digestLen == 0 || (digestLen % 2) != 0 || + digestLen > sizeof(digest)*2) { printf("Invalid digest! Must be 16 or 32 bytes of hex like 01020304050607080910111213141516\n"); usage(); return 0; } - hexRet = hexToByte(digestStr, digest, digestLen / 2); + hexRet = hexToByte(digestStr, digest, digestLen); if (hexRet < 0) { printf("Invalid hex digest string\n"); usage(); diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 0a81c5ac..fd9d6af1 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -416,6 +416,16 @@ static void FwLookupEntityAuth(FWTPM_CTX* ctx, TPM_HANDLE handle, } } #endif /* !FWTPM_NO_NV */ + else if (handle <= PCR_LAST) { + /* PCR handles carry per-index authValue set by PCR_SetAuthValue. + * Without this case the password verifier resolves them to + * authSz=0 and any empty-password caller passes the compare. + * PCR_FIRST is 0, so the lower bound is implicit in the + * unsigned type. */ + int pcrIdx = (int)(handle - PCR_FIRST); + *authVal = ctx->pcrAuth[pcrIdx].buffer; + *authValSz = (int)ctx->pcrAuth[pcrIdx].size; + } else { FWTPM_Object* objEnt = FwFindObject(ctx, handle); if (objEnt != NULL) { @@ -1731,11 +1741,30 @@ static TPM_RC FwCmd_PCR_Reset(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, if (rc == 0) { pcrIndex = pcrHandle - PCR_FIRST; + /* PCR 0-15 (SRTM) are not user-resettable per TPM 2.0 Part 2 + * Table 3-8; they reset only via TPM2_Startup(CLEAR). */ if (pcrIndex < 16) { rc = TPM_RC_LOCALITY; } } + /* Per TCG PC Client TPM Profile Table 5, PCR_Reset locality rules + * for indices 16..23 are: + * 16, 23 — any locality + * 17 — locality 4 only (DRTM MLE) + * 18..22 — locality 3 or 4 (DRTM ACM/OS) + * Without this check any caller at locality 0 can wipe DRTM PCRs + * and defeat attestation policies sealed to them. */ + if (rc == 0) { + if (pcrIndex == 17 && ctx->activeLocality != 4) { + rc = TPM_RC_LOCALITY; + } + else if (pcrIndex >= 18 && pcrIndex <= 22 && + ctx->activeLocality != 3 && ctx->activeLocality != 4) { + rc = TPM_RC_LOCALITY; + } + } + if (rc == 0 && cmdTag == TPM_ST_SESSIONS) { rc = FwSkipAuthArea(cmd, cmdSize); } @@ -3586,6 +3615,14 @@ static TPM_RC FwCmd_SetPrimaryPolicy(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (policySz > 0 && TPM2_GetHashDigestSize(hashAlg) <= 0) { rc = TPM_RC_HASH; } + /* Per TPM 2.0 Part 3 Sec.23.1, authPolicy.size must equal the + * digest size of hashAlg. A mismatched length installs a policy + * whose size never matches any legitimate session policyDigest, + * permanently locking the hierarchy out of policy-based access. */ + if (rc == 0 && policySz > 0 && + (int)policySz != TPM2_GetHashDigestSize(hashAlg)) { + rc = TPM_RC_SIZE; + } if (policySz == 0) { hashAlg = TPM_ALG_NULL; } @@ -3667,6 +3704,16 @@ static TPM_RC FwCmd_EvictControl(FWTPM_CTX* ctx, TPM2_Packet* cmd, TPM2_Packet_ParseU32(cmd, &persistentHandle); } + /* Per TPM 2.0 Part 2 Sec.7.4, persistent handles MUST fall in the + * 0x81000000..0x81FFFFFF range. Storing an out-of-range value would + * let a later FwFindObject lookup mistakenly resolve to a transient + * slot and serve attacker-controlled key material. */ + if (rc == 0 && + (persistentHandle < PERSISTENT_FIRST || + persistentHandle > PERSISTENT_LAST)) { + rc = TPM_RC_VALUE; + } + /* Validate auth handle: owner or platform required by spec, * endorsement also accepted for EH-created objects */ if (rc == 0 && authHandle != TPM_RH_OWNER && @@ -3701,10 +3748,20 @@ static TPM_RC FwCmd_EvictControl(FWTPM_CTX* ctx, TPM2_Packet* cmd, } /* objectHandle is transient -> make persistent */ else if (rc == 0) { - obj = FwFindObject(ctx, objectHandle); - if (obj == NULL) { + /* Per TPM 2.0 Part 3 Sec.28, objectHandle for the make-persistent + * form MUST be a loaded transient. Accepting a persistent handle + * here lets a caller clone an existing persistent record into a + * new slot, exhaust the persistent table, and serve attacker- + * controlled key material at a fresh handle. */ + if ((objectHandle & 0xFF000000) != 0x80000000) { rc = TPM_RC_HANDLE; } + if (rc == 0) { + obj = FwFindObject(ctx, objectHandle); + if (obj == NULL) { + rc = TPM_RC_HANDLE; + } + } /* Check if persistent handle already in use */ if (rc == 0) { @@ -4554,10 +4611,23 @@ static TPM_RC FwCmd_LoadExternal(FWTPM_CTX* ctx, TPM2_Packet* cmd, rc = TPM_RC_FAILURE; } - /* p = n / q */ + /* p = n / q, capturing the remainder so we can reject a caller- + * supplied q that does not evenly divide n. Without this check + * FwRsaComputeCRT would succeed on a mathematically inconsistent + * key whose CRT components do not match the public modulus, + * mirroring the existing guard in FwReconstructRsaPrivateKey. */ if (rc == 0) { - rc = mp_div(&rsaKey->n, &rsaKey->q, &rsaKey->p, NULL); - if (rc != 0) { + mp_int rem; + rc = mp_init(&rem); + if (rc == 0) { + rc = mp_div(&rsaKey->n, &rsaKey->q, &rsaKey->p, &rem); + if (rc == 0 && !mp_iszero(&rem)) { + rc = TPM_RC_BINDING; + } + mp_forcezero(&rem); + mp_clear(&rem); + } + if (rc != 0 && rc != TPM_RC_BINDING) { rc = TPM_RC_FAILURE; } } @@ -11999,7 +12069,14 @@ static TPM_RC FwCmd_CertifyCreation(FWTPM_CTX* ctx, TPM2_Packet* cmd, if (rc == 0 && tag != TPM_ST_CREATION) { rc = TPM_RC_TICKET; } - if (rc == 0 && tickDSz > 0) { + /* A zero-length ticket digest cannot bind the creationHash to the + * object name. Without this guard, the attestation embeds the + * caller-supplied creationHash verbatim with no cryptographic + * proof of provenance. */ + if (rc == 0 && tickDSz == 0) { + rc = TPM_RC_TICKET; + } + if (rc == 0) { byte ticketData[TPM_MAX_DIGEST_SIZE + sizeof(TPM2B_NAME)]; int ticketDataSz = 0; byte expectedHmac[TPM_MAX_DIGEST_SIZE]; @@ -15434,6 +15511,22 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, return TPM_RC_SUCCESS; } } + else if (authPolicy != NULL && authPolicy->size == 0 && + cmdAuths[pj].cmdHmacSize == 0) { + /* Per TPM 2.0 Part 1 Sec.19.7, a policy session can only + * authorize an entity whose authPolicy is non-empty. + * When the entity has no authPolicy AND the session + * supplied no HMAC, every downstream auth check would + * be skipped — reject up front. */ + #ifdef DEBUG_WOLFTPM + printf("fwTPM: Policy session empty-HMAC rejected for " + "handle 0x%x without authPolicy (CC=0x%x)\n", + entityH, cmdCode); + #endif + *rspSize = FwBuildErrorResponse(rspBuf, + TPM_ST_NO_SESSIONS, TPM_RC_POLICY_FAIL); + return TPM_RC_SUCCESS; + } } } diff --git a/src/fwtpm/fwtpm_nv.c b/src/fwtpm/fwtpm_nv.c index 66314a0f..2d63bf8d 100644 --- a/src/fwtpm/fwtpm_nv.c +++ b/src/fwtpm/fwtpm_nv.c @@ -407,9 +407,25 @@ static int FwNvUnmarshalNvPublic(const byte* buf, word32* pos, word32 maxSz, if (rc == 0) { rc = FwNvUnmarshalDigest(buf, pos, maxSz, &nvPub->authPolicy); } + /* Per TPM 2.0 Part 3 Sec.31.3, authPolicy.size must equal the digest + * size of nameAlg. A mismatched length installs a policy whose size + * never matches any legitimate session policyDigest, permanently + * denying policy-based access to this NV index. */ + if (rc == 0 && nvPub->authPolicy.size > 0 && + (int)nvPub->authPolicy.size != + TPM2_GetHashDigestSize(nvPub->nameAlg)) { + rc = TPM_RC_FAILURE; + } if (rc == 0) { rc = FwNvUnmarshalU16(buf, pos, maxSz, &nvPub->dataSize); } + /* nv->data[] is sized to FWTPM_MAX_NV_DATA; rejecting an inflated + * dataSize here prevents the later NV_Write bounds check from + * comparing offset+size against an attacker-chosen ceiling and + * overrunning the fixed destination buffer. */ + if (rc == 0 && nvPub->dataSize > FWTPM_MAX_NV_DATA) { + rc = TPM_RC_FAILURE; + } return rc; } @@ -832,6 +848,16 @@ static int FwNvProcessEntry(FWTPM_CTX* ctx, UINT16 tag, FwNvUnmarshalU32(value, &vPos, vMax, &hier); FwNvUnmarshalU16(value, &vPos, vMax, &alg); FwNvUnmarshalDigest(value, &vPos, vMax, &policy); + /* Per TPM 2.0 Part 3 Sec.23.1, policy.size must equal the + * digest size of alg. Discard a journal entry where the + * sizes diverge so it cannot lock the hierarchy out by + * forcing every legitimate policy session to fail the + * size check at policyDigest enforcement time. */ + if (policy.size > 0 && + (int)policy.size != TPM2_GetHashDigestSize(alg)) { + XMEMSET(&policy, 0, sizeof(policy)); + break; + } switch (hier) { case TPM_RH_OWNER: XMEMCPY(&ctx->ownerPolicy, &policy, diff --git a/src/tpm2_asn.c b/src/tpm2_asn.c index 1abe5aa9..6e4ec27b 100644 --- a/src/tpm2_asn.c +++ b/src/tpm2_asn.c @@ -317,6 +317,15 @@ int TPM2_ASN_DecodeRsaPubKey(uint8_t* input, int inputSz, if (rc == 0) { rc = TPM2_ASN_DecodeTag(input, inputSz, &idx, &mod_len, TPM2_ASN_INTEGER); } + if (rc == 0) { + /* Validate mod_len and idx before accessing input buffer. Without + * this guard a length-0 INTEGER followed by no bytes would let + * mod_len underflow from 0 to -1 below, bypassing the size check + * via signed comparison and passing SIZE_MAX to XMEMCPY. */ + if (mod_len <= 0 || idx >= inputSz) { + rc = -1; + } + } if (rc == 0) { if (input[idx] == 0x00) { idx++; diff --git a/src/tpm2_packet.c b/src/tpm2_packet.c index cb2d4b01..df016816 100644 --- a/src/tpm2_packet.c +++ b/src/tpm2_packet.c @@ -1588,6 +1588,14 @@ TPM_RC TPM2_Packet_Parse(TPM_RC rc, TPM2_Packet* packet) TPM2_Packet_ParseU16(packet, NULL); /* tag */ TPM2_Packet_ParseU32(packet, &respSz); /* response size */ TPM2_Packet_ParseU32(packet, &tmpRc); /* response code */ + /* Reject a wire respSz that exceeds the physical buffer size + * captured in packet->size at entry. Without this guard a + * malicious or MITM responder could inflate respSz and cause + * downstream parsers (bounded only by packet->size) to read + * past the physical allocation. */ + if (respSz > (UINT32)packet->size) { + return TPM_RC_SIZE; + } packet->size = respSz; rc = tmpRc; } diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index f36817ee..659c47e0 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -9048,7 +9048,7 @@ int wolfTPM2_CreateKeySeal_ex(WOLFTPM2_DEV* dev, WOLFTPM2_KEYBLOB* keyBlob, if (auth) { TPM2B_AUTH* pAuth = &createIn.inSensitive.sensitive.userAuth; if (authSz > (int)sizeof(pAuth->buffer)) { - authSz = (int)sizeof(pAuth->buffer); /* truncate */ + return BUFFER_E; } pAuth->size = authSz; XMEMCPY(pAuth->buffer, auth, authSz); diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index 2175009c..6072eccf 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -792,6 +792,86 @@ static void test_fwtpm_pcr_extend_and_read(void) fwtpm_pass("PCR_Extend + Read(16):", 0); } +/* Per TPM 2.0 Part 3 Sec.22.3, PCR_Extend takes Auth Role USER on the + * PCR handle. When PCR_SetAuthValue has installed a non-empty + * authValue, a subsequent password session with hmacSize=0 must be + * rejected — the password lookup must consult ctx->pcrAuth[idx] not + * fall through to an empty default. */ +static void test_fwtpm_pcr_extend_empty_pw_rejected_after_setauth(void) +{ + FWTPM_CTX ctx; + int rc, rspSize, cmdSz; + static const byte pcrAuth[] = "pcr-auth-secret-32-bytes-aaaaaaa"; + const int pcrAuthSz = (int)sizeof(pcrAuth) - 1; + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + + /* PCR_SetAuthValue(PCR 16, authValue). Auth area is empty-PW + * (PCR 16 starts with no authValue). */ + cmdSz = 0; + PutU16BE(gCmd + cmdSz, TPM_ST_SESSIONS); cmdSz += 2; + PutU32BE(gCmd + cmdSz, 0); cmdSz += 4; + PutU32BE(gCmd + cmdSz, TPM_CC_PCR_SetAuthValue); cmdSz += 4; + PutU32BE(gCmd + cmdSz, 16); cmdSz += 4; + PutU32BE(gCmd + cmdSz, 9); cmdSz += 4; + PutU32BE(gCmd + cmdSz, TPM_RS_PW); cmdSz += 4; + PutU16BE(gCmd + cmdSz, 0); cmdSz += 2; + gCmd[cmdSz++] = 0; + PutU16BE(gCmd + cmdSz, 0); cmdSz += 2; + PutU16BE(gCmd + cmdSz, (UINT16)pcrAuthSz); cmdSz += 2; + memcpy(gCmd + cmdSz, pcrAuth, pcrAuthSz); + cmdSz += pcrAuthSz; + PutU32BE(gCmd + 2, (UINT32)cmdSz); + rspSize = 0; + rc = FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + /* PCR_Extend(PCR 16) with empty password — must be rejected. */ + cmdSz = 0; + PutU16BE(gCmd + cmdSz, TPM_ST_SESSIONS); cmdSz += 2; + PutU32BE(gCmd + cmdSz, 0); cmdSz += 4; + PutU32BE(gCmd + cmdSz, TPM_CC_PCR_Extend); cmdSz += 4; + PutU32BE(gCmd + cmdSz, 16); cmdSz += 4; + PutU32BE(gCmd + cmdSz, 9); cmdSz += 4; + PutU32BE(gCmd + cmdSz, TPM_RS_PW); cmdSz += 4; + PutU16BE(gCmd + cmdSz, 0); cmdSz += 2; + gCmd[cmdSz++] = 0; + PutU16BE(gCmd + cmdSz, 0); cmdSz += 2; + PutU32BE(gCmd + cmdSz, 1); cmdSz += 4; + PutU16BE(gCmd + cmdSz, TPM_ALG_SHA256); cmdSz += 2; + memset(gCmd + cmdSz, 0x42, 32); cmdSz += 32; + PutU32BE(gCmd + 2, (UINT32)cmdSz); + rspSize = 0; + rc = FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_AUTH_FAIL); + + /* Restore PCR 16 to empty auth so the NV journal doesn't contaminate + * later tests that share FWTPM_NV_FILE. */ + cmdSz = 0; + PutU16BE(gCmd + cmdSz, TPM_ST_SESSIONS); cmdSz += 2; + PutU32BE(gCmd + cmdSz, 0); cmdSz += 4; + PutU32BE(gCmd + cmdSz, TPM_CC_PCR_SetAuthValue); cmdSz += 4; + PutU32BE(gCmd + cmdSz, 16); cmdSz += 4; + PutU32BE(gCmd + cmdSz, 9 + pcrAuthSz); cmdSz += 4; + PutU32BE(gCmd + cmdSz, TPM_RS_PW); cmdSz += 4; + PutU16BE(gCmd + cmdSz, 0); cmdSz += 2; + gCmd[cmdSz++] = 0; + PutU16BE(gCmd + cmdSz, (UINT16)pcrAuthSz); cmdSz += 2; + memcpy(gCmd + cmdSz, pcrAuth, pcrAuthSz); cmdSz += pcrAuthSz; + PutU16BE(gCmd + cmdSz, 0); cmdSz += 2; /* new auth.size = 0 */ + PutU32BE(gCmd + 2, (UINT32)cmdSz); + rspSize = 0; + rc = FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + FWTPM_Cleanup(&ctx); + fwtpm_pass("PCR_Extend empty-pw after SetAuth (AUTH_FAIL):", 0); +} + /* ================================================================== */ /* 7. ReadClock */ /* ================================================================== */ @@ -6776,6 +6856,7 @@ static void test_fwtpm_policy_pcr(void) FWTPM_Cleanup(&ctx); fwtpm_pass("PolicyPCR:", 0); } + #endif /* !FWTPM_NO_POLICY */ /* ================================================================== */ @@ -7025,8 +7106,11 @@ static void test_fwtpm_certify_creation_ecdaa_scheme(void) /* Build TPM2_CertifyCreation with ECDAA inScheme. Use the same key as * both signHandle and objectHandle to avoid additional setup. The - * ticket carries hier=TPM_RH_NULL with a zero digest so HMAC - * validation is skipped (only the tag is checked). */ + * ticket carries hier=TPM_RH_NULL with a zero digest, which must be + * rejected with TPM_RC_TICKET — reaching that reject still proves + * the ECDAA inScheme parser consumed the extra UINT16 count, since + * a wrong-shape scheme parse would have failed earlier with a + * different code. */ pos = 0; PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; PutU32BE(gCmd + pos, 0); pos += 4; @@ -7049,7 +7133,7 @@ static void test_fwtpm_certify_creation_ecdaa_scheme(void) PutU32BE(gCmd + 2, (UINT32)pos); rspSize = 0; FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); - AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_TICKET); FlushHandle(&ctx, keyH); FWTPM_Cleanup(&ctx); @@ -7282,6 +7366,43 @@ static void test_fwtpm_pcr_reset(void) fwtpm_pass("PCR_Reset(16):", 0); } +/* Per TCG PC Client TPM Profile Table 5, PCR 17 (DRTM MLE) may only be + * reset from locality 4. The default test locality is 0, so the reset + * must be rejected with TPM_RC_LOCALITY. Same expectation for PCR 22 + * which requires locality 3 or 4. */ +static void test_fwtpm_pcr_reset_locality_enforced(void) +{ + FWTPM_CTX ctx; + int pos, rspSize; + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_PCR_Reset); pos += 4; + PutU32BE(gCmd + pos, 17); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_LOCALITY); + + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_PCR_Reset); pos += 4; + PutU32BE(gCmd + pos, 22); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_LOCALITY); + + FWTPM_Cleanup(&ctx); + fwtpm_pass("PCR_Reset locality enforced (LOCALITY):", 0); +} + static void test_fwtpm_pcr_event(void) { FWTPM_CTX ctx; @@ -7346,6 +7467,40 @@ static void test_fwtpm_hierarchy_change_auth(void) fwtpm_pass("HierarchyChangeAuth:", 0); } +/* Per TPM 2.0 Part 3 Sec.23.1, authPolicy.size in SetPrimaryPolicy must + * equal the digest size of hashAlg. A mismatched length installs a + * policy whose size never matches any legitimate session policyDigest, + * permanently denying policy-based access to the hierarchy. */ +static void test_fwtpm_set_primary_policy_bad_size_rejected(void) +{ + FWTPM_CTX ctx; + int pos, rspSize; + byte badPolicy[64]; + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + memset(badPolicy, 0xAB, sizeof(badPolicy)); + + 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); + /* authPolicy: 64 bytes, but hashAlg = SHA256 (expects 32) */ + PutU16BE(gCmd + pos, sizeof(badPolicy)); pos += 2; + memcpy(gCmd + pos, badPolicy, sizeof(badPolicy)); + pos += sizeof(badPolicy); + 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_SIZE); + + FWTPM_Cleanup(&ctx); + fwtpm_pass("SetPrimaryPolicy size mismatch (SIZE):", 0); +} + static void test_fwtpm_clear(void) { FWTPM_CTX ctx; @@ -7602,6 +7757,115 @@ static void test_fwtpm_evict_control(void) fwtpm_pass("EvictControl (persist/remove):", 0); } +/* Per TPM 2.0 Part 2 Sec.7.4, persistent handles must fall in + * 0x81000000..0x81FFFFFF. A persistentHandle outside this range must + * be rejected so an attacker cannot plant a persistent record at a + * transient-range handle that FwFindObject would later resolve. */ +static void test_fwtpm_evict_control_bad_persistent_handle_rejected(void) +{ + FWTPM_CTX ctx; + int pos, rspSize; + UINT32 keyH; + UINT32 badPersH = 0x80000010; /* transient range */ + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + +#ifdef HAVE_ECC + keyH = CreatePrimaryHelper(&ctx, TPM_ALG_ECC); +#else + keyH = CreatePrimaryHelper(&ctx, TPM_ALG_RSA); +#endif + AssertIntNE(keyH, 0); + + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_EvictControl); pos += 4; + PutU32BE(gCmd + pos, TPM_RH_OWNER); pos += 4; + PutU32BE(gCmd + pos, keyH); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU32BE(gCmd + pos, badPersH); pos += 4; + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_VALUE); + + FlushHandle(&ctx, keyH); + FWTPM_Cleanup(&ctx); + fwtpm_pass("EvictControl bad persistentHandle (VALUE):", 0); +} + +/* Per TPM 2.0 Part 3 Sec.28, the make-persistent form of EvictControl + * requires objectHandle to be a loaded transient. Passing a persistent + * objectHandle that differs from persistentHandle would otherwise let + * a caller clone a persistent record into a fresh persistent slot. */ +static void test_fwtpm_evict_control_persistent_object_rejected(void) +{ + FWTPM_CTX ctx; + int pos, rspSize; + UINT32 keyH; + UINT32 persH = 0x81000002; + UINT32 cloneH = 0x81000003; + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + +#ifdef HAVE_ECC + keyH = CreatePrimaryHelper(&ctx, TPM_ALG_ECC); +#else + keyH = CreatePrimaryHelper(&ctx, TPM_ALG_RSA); +#endif + AssertIntNE(keyH, 0); + + /* First make the transient persistent at persH. */ + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_EvictControl); pos += 4; + PutU32BE(gCmd + pos, TPM_RH_OWNER); pos += 4; + PutU32BE(gCmd + pos, keyH); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU32BE(gCmd + pos, persH); pos += 4; + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + FlushHandle(&ctx, keyH); + + /* Now attempt to clone the persistent record into a new persistent + * slot by passing the persistent handle as objectHandle (which is + * not the same as persistentHandle, so the evict branch is not + * taken). Must be rejected with TPM_RC_HANDLE. */ + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_EvictControl); pos += 4; + PutU32BE(gCmd + pos, TPM_RH_OWNER); pos += 4; + PutU32BE(gCmd + pos, persH); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU32BE(gCmd + pos, cloneH); pos += 4; + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_HANDLE); + + /* Clean up persH so the NV journal doesn't leak. */ + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_EvictControl); pos += 4; + PutU32BE(gCmd + pos, TPM_RH_OWNER); pos += 4; + PutU32BE(gCmd + pos, persH); pos += 4; + pos = AppendPwAuth(gCmd, pos, NULL, 0); + PutU32BE(gCmd + pos, persH); pos += 4; + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + FWTPM_Cleanup(&ctx); + fwtpm_pass("EvictControl persistent object reject (HANDLE):", 0); +} + static void test_fwtpm_clock_set(void) { FWTPM_CTX ctx; @@ -7834,7 +8098,9 @@ int fwtpm_unit_tests(int argc, char *argv[]) /* PCR operations */ test_fwtpm_pcr_read(); test_fwtpm_pcr_extend_and_read(); + test_fwtpm_pcr_extend_empty_pw_rejected_after_setauth(); test_fwtpm_pcr_reset(); + test_fwtpm_pcr_reset_locality_enforced(); test_fwtpm_pcr_event(); /* Clock */ @@ -7934,6 +8200,8 @@ int fwtpm_unit_tests(int argc, char *argv[]) #endif test_fwtpm_read_public(); test_fwtpm_evict_control(); + test_fwtpm_evict_control_bad_persistent_handle_rejected(); + test_fwtpm_evict_control_persistent_object_rejected(); test_fwtpm_context_save(); /* Crypto */ @@ -7950,6 +8218,7 @@ int fwtpm_unit_tests(int argc, char *argv[]) /* Auth */ test_fwtpm_hierarchy_change_auth(); + test_fwtpm_set_primary_policy_bad_size_rejected(); #ifndef FWTPM_NO_DA test_fwtpm_da_parameters_and_reset(); #endif