Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
fa616ac
F-4157 — Fix native_test hash check using && instead of ||
aidangarske May 26, 2026
5477be2
F-4622 — Pass full hex length to hexToByte in policy.c -digest=
aidangarske May 26, 2026
bcb3f57
F-4623 — Reject st33_fw_update blob_len > 2048 before XMEMCPY
aidangarske May 26, 2026
6c1b690
F-4743 — Guard mod_len in TPM2_ASN_DecodeRsaPubKey to prevent underflow
aidangarske May 26, 2026
17e4cbc
F-4674 — Reject NV journal nvPublic.dataSize > FWTPM_MAX_NV_DATA
aidangarske May 26, 2026
c1f7032
F-4377 — Reject NULL-hierarchy ticket in FwCmd_PolicyAuthorize
aidangarske May 26, 2026
75216a3
F-4744 — Reject zero-digest creation ticket in FwCmd_CertifyCreation
aidangarske May 26, 2026
a8d7c84
F-4745 — Look up ctx->pcrAuth in FwLookupEntityAuth for PCR handles
aidangarske May 26, 2026
566faa5
F-4746 — Reject out-of-range persistentHandle in FwCmd_EvictControl
aidangarske May 26, 2026
f1117a6
F-4747 — Reject persistent objectHandle in EvictControl make-persiste…
aidangarske May 26, 2026
b3ca027
F-4750 — Enforce authPolicy.size matches hashAlg in FwCmd_SetPrimaryP…
aidangarske May 26, 2026
f27c3b1
F-4749 — Reject NV authPolicy.size that does not match nameAlg digest
aidangarske May 26, 2026
eb87236
F-4748 — Discard NV hierarchy policy with size != alg digest
aidangarske May 26, 2026
2bd2f89
F-4376 — Reject policy session with empty HMAC when entity authPolicy…
aidangarske May 26, 2026
f23e794
F-4379 — Check mp_div remainder in FwCmd_LoadExternal RSA path
aidangarske May 26, 2026
6914245
F-4378 — Enforce per-PCR locality table in FwCmd_PCR_Reset
aidangarske May 26, 2026
2ac2f41
F-4160 — Return BUFFER_E for oversized auth in wolfTPM2_CreateKeySeal_ex
aidangarske May 26, 2026
b33569e
F-4680 — Reject wire respSz exceeding physical buffer in TPM2_Packet_…
aidangarske May 26, 2026
48eaf5c
F-4745 — Fix type-limits warning on unsigned handle >= PCR_FIRST
aidangarske May 26, 2026
a53c28b
F-4377 — Revert NULL-hierarchy reject; NULL tickets are spec-compliant
aidangarske May 26, 2026
1cf5b9b
F-4379 — Call mp_clear on rem after mp_forcezero
aidangarske May 26, 2026
90866dd
F-4622 — Reject odd-length -digest= argument before hexToByte
aidangarske May 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions examples/firmware/st33_fw_update.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion examples/native/native_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
5 changes: 3 additions & 2 deletions examples/pcr/policy.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Comment thread
aidangarske marked this conversation as resolved.
printf("Invalid hex digest string\n");
usage();
Expand Down
105 changes: 99 additions & 6 deletions src/fwtpm/fwtpm_command.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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 &&
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Comment thread
aidangarske marked this conversation as resolved.
mp_clear(&rem);
}
if (rc != 0 && rc != TPM_RC_BINDING) {
rc = TPM_RC_FAILURE;
}
}
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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;
}
}
}

Expand Down
26 changes: 26 additions & 0 deletions src/fwtpm/fwtpm_nv.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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,
Expand Down
9 changes: 9 additions & 0 deletions src/tpm2_asn.c
Original file line number Diff line number Diff line change
Expand Up @@ -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++;
Expand Down
8 changes: 8 additions & 0 deletions src/tpm2_packet.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
2 changes: 1 addition & 1 deletion src/tpm2_wrap.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading
Loading