From 247853b4b0fd28593441a4fbea1c59859a275577 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Mon, 27 Jul 2026 12:12:48 -0700 Subject: [PATCH] F-7272 - Honor caller response buffer capacity in FWTPM_ProcessCommand --- src/fwtpm/fwtpm_command.c | 166 +++++++++++++++++++++++----------- src/fwtpm/fwtpm_io.c | 2 + src/fwtpm/fwtpm_main.c | 45 +++++++-- src/fwtpm/fwtpm_tis.c | 1 + src/tpm2_packet.c | 19 ++++ tests/fwtpm_unit_tests.c | 82 +++++++++++++++++ wolftpm/fwtpm/fwtpm_command.h | 4 +- wolftpm/tpm2_packet.h | 1 + 8 files changed, 259 insertions(+), 61 deletions(-) diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index ce17cda3..f0aa489f 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -93,6 +93,7 @@ static void FwRspInit(TPM2_Packet* pkt, byte* buf, int bufSize) pkt->buf = buf; pkt->pos = TPM2_HEADER_SIZE; /* skip header, filled by Finalize */ pkt->size = bufSize; + pkt->overflow = 0; /* Zero header area so stale data doesn't confuse session detection */ XMEMSET(buf, 0, TPM2_HEADER_SIZE); } @@ -110,10 +111,10 @@ static int FwRspFinalize(TPM2_Packet* pkt, UINT16 tag, TPM_RC rc) } /* Build a minimal error-only response */ -static int FwBuildErrorResponse(byte* rsp, UINT16 tag, TPM_RC rc) +static int FwBuildErrorResponse(byte* rsp, int rspBufSz, UINT16 tag, TPM_RC rc) { TPM2_Packet pkt; - FwRspInit(&pkt, rsp, FWTPM_MAX_COMMAND_SIZE); + FwRspInit(&pkt, rsp, rspBufSz); return FwRspFinalize(&pkt, tag, rc); } @@ -1016,10 +1017,17 @@ static TPM_RC FwCmd_GetRandom(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, /* TPM2B_DIGEST: size + data */ TPM2_Packet_AppendU16(rsp, bytesRequested); - rc = wc_RNG_GenerateBlock(&ctx->rng, - rsp->buf + rsp->pos, bytesRequested); - if (rc != 0) { - rc = TPM_RC_FAILURE; + /* This writes past the packet API, so bound it explicitly. */ + if (rsp->pos + (int)bytesRequested > rsp->size) { + rsp->overflow = 1; + bytesRequested = 0; + } + else { + rc = wc_RNG_GenerateBlock(&ctx->rng, + rsp->buf + rsp->pos, bytesRequested); + if (rc != 0) { + rc = TPM_RC_FAILURE; + } } } @@ -1091,12 +1099,28 @@ static int FwPcrLocalityAllowed(int pcrIndex, int locality, int isReset); /* Overwrite a big-endian UINT32 already appended at buf[pos]. Used to * back-patch a TPML count with the number of entries actually emitted, so a * count/payload mismatch is impossible even if two entries collapse to one. */ -static void FwPatchU32BE(byte* buf, int pos, UINT32 v) +/* Back-patch a reserved field, but only where it actually fits. The + * reservation is dropped silently when the buffer is already full. */ +static void FwPatchU32BE(TPM2_Packet* rsp, int pos, UINT32 v) { - buf[pos + 0] = (byte)(v >> 24); - buf[pos + 1] = (byte)(v >> 16); - buf[pos + 2] = (byte)(v >> 8); - buf[pos + 3] = (byte)(v); + if (pos < 0 || pos + 4 > rsp->size) { + rsp->overflow = 1; + return; + } + rsp->buf[pos + 0] = (byte)(v >> 24); + rsp->buf[pos + 1] = (byte)(v >> 16); + rsp->buf[pos + 2] = (byte)(v >> 8); + rsp->buf[pos + 3] = (byte)(v); +} + +/* Back-patch the one-byte moreData flag with the same guard. */ +static void FwPatchMoreData(TPM2_Packet* rsp, int pos, byte v) +{ + if (pos < 0 || pos + 1 > rsp->size) { + rsp->overflow = 1; + return; + } + rsp->buf[pos] = v; } /* --- TPM2_GetCapability (CC 0x017A) --- */ @@ -1210,7 +1234,7 @@ static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd, if ((UINT32)numOut > propertyCount) numOut = (int)propertyCount; if (avail > numOut) - rsp->buf[moreDataPos] = 1; /* YES - more entries remain */ + FwPatchMoreData(rsp, moreDataPos, 1); /* more entries remain */ /* Reserve the count and back-patch it to entries actually emitted. */ countPos = rsp->pos; @@ -1235,7 +1259,7 @@ static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd, haveLast = 1; emitted++; } - FwPatchU32BE(rsp->buf, countPos, (UINT32)emitted); + FwPatchU32BE(rsp, countPos, (UINT32)emitted); break; } @@ -1261,7 +1285,7 @@ static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd, if ((UINT32)numOut > propertyCount) numOut = (int)propertyCount; if (avail > numOut) - rsp->buf[moreDataPos] = 1; /* YES - more entries remain */ + FwPatchMoreData(rsp, moreDataPos, 1); /* more entries remain */ /* Reserve the count and back-patch it to entries actually emitted. */ countPos = rsp->pos; @@ -1287,7 +1311,7 @@ static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd, haveLast = 1; emitted++; } - FwPatchU32BE(rsp->buf, countPos, (UINT32)emitted); + FwPatchU32BE(rsp, countPos, (UINT32)emitted); break; } @@ -1418,7 +1442,7 @@ static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* Truncated (start offset or propertyCount cap): flag moreData=YES. */ if (startIdx + numOut < totalProps) - rsp->buf[moreDataPos] = 1; /* YES */ + FwPatchMoreData(rsp, moreDataPos, 1); /* YES */ TPM2_Packet_AppendU32(rsp, (UINT32)numOut); for (i = 0; i < (UINT32)numOut; i++) { @@ -1496,7 +1520,7 @@ static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd, /* Truncated (start offset or propertyCount cap): flag moreData=YES. */ if (startIdx + numOut < totalProps) - rsp->buf[moreDataPos] = 1; /* YES */ + FwPatchMoreData(rsp, moreDataPos, 1); /* YES */ TPM2_Packet_AppendU32(rsp, (UINT32)numOut); for (ii = 0; ii < (UINT32)numOut; ii++) { @@ -1589,7 +1613,7 @@ static TPM_RC FwCmd_GetCapability(FWTPM_CTX* ctx, TPM2_Packet* cmd, if ((UINT32)count > propertyCount) { count = (int)propertyCount; - rsp->buf[moreDataPos] = 1; /* YES - more handles available */ + FwPatchMoreData(rsp, moreDataPos, 1); /* more handles available */ } TPM2_Packet_AppendU32(rsp, (UINT32)count); if (count > 0) { @@ -16221,13 +16245,23 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, #endif int pj, hj; /* Loop indices for auth validation */ int authFail; /* Password comparison result */ + int rspCap; /* Caller's response buffer capacity */ + int rspTruncated = 0; /* Response did not fit the buffer */ if (ctx == NULL || cmdBuf == NULL || rspBuf == NULL || rspSize == NULL) { return BAD_FUNC_ARG; } + /* rspSize is in/out: capacity in, bytes written out. Callers that leave + * it unset get the historic FWTPM_MAX_COMMAND_SIZE assumption. */ + /* Handlers commit state before marshalling and some write outside the + * packet API, so they are only ever run with a full-size buffer. A + * caller with a smaller transport buffer must stage through one. + * rspSize is output-only, so its incoming value is never read. */ + rspCap = FWTPM_MAX_COMMAND_SIZE; + if (cmdSize < TPM2_HEADER_SIZE) { - *rspSize = FwBuildErrorResponse(rspBuf, TPM_ST_NO_SESSIONS, + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, TPM_RC_COMMAND_SIZE); return TPM_RC_SUCCESS; } @@ -16243,13 +16277,13 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, TPM2_Packet_ParseU32(&cmdPkt, &cmdCode); if (cmdTag != TPM_ST_NO_SESSIONS && cmdTag != TPM_ST_SESSIONS) { - *rspSize = FwBuildErrorResponse(rspBuf, TPM_ST_NO_SESSIONS, + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, TPM_RC_BAD_TAG); return TPM_RC_SUCCESS; } if ((int)cmdSizeHdr != cmdSize) { - *rspSize = FwBuildErrorResponse(rspBuf, TPM_ST_NO_SESSIONS, + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, TPM_RC_COMMAND_SIZE); return TPM_RC_SUCCESS; } @@ -16257,14 +16291,14 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, /* A valid command code has only the 16-bit index plus the vendor V bit * (CC_VEND); reject any other reserved bit so it cannot alias a command. */ if ((cmdCode & ~((UINT32)CC_VEND | 0xFFFFu)) != 0) { - *rspSize = FwBuildErrorResponse(rspBuf, TPM_ST_NO_SESSIONS, + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, TPM_RC_COMMAND_CODE); return TPM_RC_SUCCESS; } if (!ctx->wasStarted && cmdCode != TPM_CC_Startup && cmdCode != TPM_CC_GetCapability) { - *rspSize = FwBuildErrorResponse(rspBuf, TPM_ST_NO_SESSIONS, + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, TPM_RC_INITIALIZE); return TPM_RC_SUCCESS; } @@ -16278,14 +16312,14 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, entry = FwFindCmdEntry(cmdCode); if (entry == NULL) { - *rspSize = FwBuildErrorResponse(rspBuf, TPM_ST_NO_SESSIONS, + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, TPM_RC_COMMAND_CODE); return TPM_RC_SUCCESS; } /* Validate minimum command size: header + 4 bytes per input handle */ if (cmdSize < TPM2_HEADER_SIZE + (entry->inHandleCnt * 4)) { - *rspSize = FwBuildErrorResponse(rspBuf, TPM_ST_NO_SESSIONS, + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, TPM_RC_COMMAND_SIZE); return TPM_RC_SUCCESS; } @@ -16295,7 +16329,7 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, * and bypasses every downstream auth/HMAC/policy enforcement loop, so * reject up front for any handler that declares authHandleCnt > 0. */ if (cmdTag != TPM_ST_SESSIONS && entry->authHandleCnt > 0) { - *rspSize = FwBuildErrorResponse(rspBuf, TPM_ST_NO_SESSIONS, + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, TPM_RC_AUTH_MISSING); return TPM_RC_SUCCESS; } @@ -16339,7 +16373,7 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, int authEnd; /* Reject if authAreaSz exceeds remaining command bytes */ if (authAreaSz > (UINT32)(cmdSize - cmdPkt.pos)) { - *rspSize = FwBuildErrorResponse(rspBuf, + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, TPM_RC_AUTHSIZE); return TPM_RC_SUCCESS; } @@ -16476,7 +16510,7 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, /* Check if auth area parsing encountered an error */ if (rc != TPM_RC_SUCCESS) { - *rspSize = FwBuildErrorResponse(rspBuf, + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, rc); return TPM_RC_SUCCESS; } @@ -16548,7 +16582,7 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, printf("fwTPM: Policy digest mismatch for handle " "0x%x (CC=0x%x)\n", entityH, cmdCode); #endif - *rspSize = FwBuildErrorResponse(rspBuf, + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, TPM_RC_POLICY_FAIL); return TPM_RC_SUCCESS; } @@ -16556,7 +16590,7 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, if (pSess->hasRequiredLocality && (ctx->activeLocality > WOLFTPM_LOCALITY_MAX || !((1u << ctx->activeLocality) & pSess->requiredLocality))) { - *rspSize = FwBuildErrorResponse(rspBuf, + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, TPM_RC_LOCALITY); return TPM_RC_SUCCESS; } @@ -16571,7 +16605,7 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, (int)pSess->cpHashA.size != ccpHashSz || TPM2_ConstantCompare(pSess->cpHashA.buffer, ccpHash, (word32)ccpHashSz) != 0) { - *rspSize = FwBuildErrorResponse(rspBuf, + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, TPM_RC_POLICY_FAIL); return TPM_RC_SUCCESS; } @@ -16589,7 +16623,7 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, "handle 0x%x without authPolicy (CC=0x%x)\n", entityH, cmdCode); #endif - *rspSize = FwBuildErrorResponse(rspBuf, + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, TPM_RC_POLICY_FAIL); return TPM_RC_SUCCESS; } @@ -16610,7 +16644,7 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, * etc.) — but not Clear via platformAuth, which is the recovery path. */ if (ctx->lockoutAuthFailed && entry->authHandleCnt > 0 && cmdHandleCnt > 0 && cmdHandles[0] == TPM_RH_LOCKOUT) { - *rspSize = FwBuildErrorResponse(rspBuf, + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, TPM_RC_LOCKOUT); return TPM_RC_SUCCESS; } @@ -16633,7 +16667,7 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, cmdCode != TPM_CC_StartAuthSession && cmdCode != TPM_CC_FlushContext && !(cmdHandleCnt > 0 && FwHandleIsNoDA(ctx, cmdHandles[0]))) { - *rspSize = FwBuildErrorResponse(rspBuf, + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, TPM_RC_LOCKOUT); return TPM_RC_SUCCESS; } @@ -16665,7 +16699,7 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, ctx->orderly = 0; (void)FWTPM_NV_SaveFlags(ctx); #ifdef FWTPM_DA_USED_RETRY - *rspSize = FwBuildErrorResponse(rspBuf, + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, TPM_RC_RETRY); return TPM_RC_SUCCESS; #endif @@ -16693,7 +16727,7 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, printf("fwTPM: Password/HMAC auth rejected for handle " "0x%x — policy required (userWithAuth clear)\n", entityH); #endif - *rspSize = FwBuildErrorResponse(rspBuf, + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, TPM_RC_AUTH_UNAVAILABLE); return TPM_RC_SUCCESS; } @@ -16722,12 +16756,12 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, #endif #ifndef FWTPM_NO_DA if (FwDaRegisterFailure(ctx, entityH)) { - *rspSize = FwBuildErrorResponse(rspBuf, + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, TPM_RC_LOCKOUT); return TPM_RC_SUCCESS; } #endif - *rspSize = FwBuildErrorResponse(rspBuf, + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, TPM_RC_AUTH_FAIL); return TPM_RC_SUCCESS; } @@ -16754,7 +16788,7 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, if (FwComputeCpHash(hSess->authHash, cmdCode, cmdBuf, cmdSize, cmdHandles, cmdHandleCnt, ctx, cpStart, cpHash, &cpHashSz) != 0) { - *rspSize = FwBuildErrorResponse(rspBuf, + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, TPM_RC_FAILURE); return TPM_RC_SUCCESS; } @@ -16776,7 +16810,7 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, printf("fwTPM: PolicyPassword auth failed for handle " "0x%x (CC=0x%x)\n", entityH, cmdCode); #endif - *rspSize = FwBuildErrorResponse(rspBuf, + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, TPM_RC_AUTH_FAIL); return TPM_RC_SUCCESS; } @@ -16811,12 +16845,12 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, #endif #ifndef FWTPM_NO_DA if (FwDaRegisterFailure(ctx, entityH)) { - *rspSize = FwBuildErrorResponse(rspBuf, + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, TPM_RC_LOCKOUT); return TPM_RC_SUCCESS; } #endif - *rspSize = FwBuildErrorResponse(rspBuf, + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, TPM_RC_AUTH_FAIL); return TPM_RC_SUCCESS; } @@ -16840,7 +16874,7 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, #ifdef DEBUG_WOLFTPM printf("fwTPM: ParamDecrypt failed %d\n", (int)rc); #endif - *rspSize = FwBuildErrorResponse(rspBuf, + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, TPM_RC_FAILURE); return TPM_RC_SUCCESS; } @@ -16849,11 +16883,22 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, #endif /* !FWTPM_NO_PARAM_ENC */ /* Set up response packet */ - FwRspInit(&rspPkt, rspBuf, FWTPM_MAX_COMMAND_SIZE); + FwRspInit(&rspPkt, rspBuf, rspCap); rc = entry->handler(ctx, &cmdPkt, cmdSize, &rspPkt, cmdTag); - if (rc != TPM_RC_SUCCESS) { - *rspSize = FwBuildErrorResponse(rspBuf, TPM_ST_NO_SESSIONS, rc); + /* The packet layer drops appends that would overrun the buffer, so + * report the truncation instead of returning a malformed packet. The + * session flush and deferred clear below must still run. */ + if (rc == TPM_RC_SUCCESS && rspPkt.overflow) { + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, + TPM_RC_SIZE); + rspTruncated = 1; + } + if (rspTruncated) { + /* response already built above */ + } + else if (rc != TPM_RC_SUCCESS) { + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, TPM_ST_NO_SESSIONS, rc); } else if (cmdTag != TPM_ST_SESSIONS) { /* Non-session: handler already finalized the response */ @@ -16880,6 +16925,9 @@ 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) { @@ -16894,12 +16942,13 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, rspParamEnd = rspParamStart + (int)rspParamSzVal; #endif - /* Generate fresh nonceTPM BEFORE response encryption (encryption - * uses the new nonceTPM, matching what client receives in auth) */ + /* New nonceTPM before response encryption, saving the old one */ for (j = 0; j < cmdAuthCnt; 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); @@ -17017,9 +17066,24 @@ int FWTPM_ProcessCommand(FWTPM_CTX* ctx, } } - /* Finalize response header */ - FwRspFinalize(&rspPkt, TPM_ST_SESSIONS, TPM_RC_SUCCESS); - *rspSize = rspPkt.pos; + /* 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) { + for (j = 0; j < cmdAuthCnt; j++) { + if (cmdAuths[j].sess != NULL) { + XMEMCPY(&cmdAuths[j].sess->nonceTPM, &savedNonce[j], + sizeof(savedNonce[j])); + } + } + *rspSize = FwBuildErrorResponse(rspBuf, rspCap, + TPM_ST_NO_SESSIONS, TPM_RC_SIZE); + } + else { + /* Finalize response header */ + FwRspFinalize(&rspPkt, TPM_ST_SESSIONS, TPM_RC_SUCCESS); + *rspSize = rspPkt.pos; + } } /* Per TPM 2.0 spec Part 1 Section 19.6.4: flush sessions where the caller diff --git a/src/fwtpm/fwtpm_io.c b/src/fwtpm/fwtpm_io.c index b593e590..e2d4fdbe 100644 --- a/src/fwtpm/fwtpm_io.c +++ b/src/fwtpm/fwtpm_io.c @@ -353,6 +353,7 @@ static int DispatchAndRespond(FWTPM_CTX* ctx, UINT32 cmdSize, int locality, * SPDM. Allowlist that one command; reject the rest. */ cc = (cmdSize >= 10) ? FwLoadU32BE(ctx->cmdBuf + 6) : 0; if (cc == TPM_CC_GetCapability) { + rspSize = (int)sizeof(ctx->rspBuf); procRc = FWTPM_ProcessCommand(ctx, ctx->cmdBuf, (int)cmdSize, ctx->rspBuf, &rspSize, locality); if (procRc != TPM_RC_SUCCESS || rspSize == 0) { @@ -369,6 +370,7 @@ static int DispatchAndRespond(FWTPM_CTX* ctx, UINT32 cmdSize, int locality, #endif if (!dispatched) { + rspSize = (int)sizeof(ctx->rspBuf); procRc = FWTPM_ProcessCommand(ctx, ctx->cmdBuf, (int)cmdSize, ctx->rspBuf, &rspSize, locality); if (procRc != TPM_RC_SUCCESS || rspSize == 0) { diff --git a/src/fwtpm/fwtpm_main.c b/src/fwtpm/fwtpm_main.c index ae260df5..6beedde7 100644 --- a/src/fwtpm/fwtpm_main.c +++ b/src/fwtpm/fwtpm_main.c @@ -81,10 +81,28 @@ static void usage(const char* progname) } #ifdef WOLFTPM_SPDM_RESPONDER +/* Build a 10-byte TPM response header carrying an error code. */ +static int FwtpmBuildErrorFrame(byte* resp, TPM_RC rc) +{ + resp[0] = 0x80; resp[1] = 0x01; /* tag: TPM_ST_NO_SESSIONS */ + resp[2] = 0x00; resp[3] = 0x00; /* responseSize, upper half */ + resp[4] = 0x00; resp[5] = 0x0A; /* responseSize = 10 */ + resp[6] = (byte)((rc >> 24) & 0xFF); /* responseCode, MSB */ + resp[7] = (byte)((rc >> 16) & 0xFF); + resp[8] = (byte)((rc >> 8) & 0xFF); + resp[9] = (byte)(rc & 0xFF); /* responseCode, LSB */ + return TPM2_HEADER_SIZE; +} + static int fwtpmSpdmTpmDispatch(void* userCtx, const byte* cmd, word32 cmdSz, byte* resp, word32 respBufSz, word32* respSz) { + /* The dispatcher needs a full-size buffer but the SPDM envelope is + * smaller, and ctx->rspBuf is already holding the SPDM frame this call + * is nested inside (fwtpm_io.c), so stage here. Static, not stack: the + * server serves one command at a time and this is 8KB. */ + static byte stageBuf[FWTPM_MAX_COMMAND_SIZE]; FWTPM_CTX* ctx = (FWTPM_CTX*)userCtx; int rc; int rspSize; @@ -92,22 +110,31 @@ static int fwtpmSpdmTpmDispatch(void* userCtx, if (ctx == NULL || cmd == NULL || resp == NULL || respSz == NULL) { return BAD_FUNC_ARG; } - rspSize = (int)respBufSz; - rc = FWTPM_ProcessCommand(ctx, cmd, (int)cmdSz, resp, &rspSize, 0); + rspSize = (int)sizeof(stageBuf); + rc = FWTPM_ProcessCommand(ctx, cmd, (int)cmdSz, stageBuf, &rspSize, 0); + if (rc == TPM_RC_SUCCESS && rspSize >= TPM2_HEADER_SIZE) { + if ((word32)rspSize <= respBufSz) { + XMEMCPY(resp, stageBuf, (size_t)rspSize); + } + else if (respBufSz >= TPM2_HEADER_SIZE) { + /* Does not fit the envelope; report it as a TPM error rather + * than truncating the response. */ + rspSize = FwtpmBuildErrorFrame(resp, TPM_RC_SIZE); + } + else { + rspSize = 0; + } + } /* A non-zero TPM_RC is a valid TPM response - the requester must see * the actual error code. If the dispatcher didn't write one (rspSize * left at zero or partial), synthesize a 10-byte TPM_ST_NO_SESSIONS * error frame here so the SPDM layer encrypts and sends it back. */ if ((rc != TPM_RC_SUCCESS || rspSize < TPM2_HEADER_SIZE) && respBufSz >= TPM2_HEADER_SIZE) { - resp[0] = 0x80; resp[1] = 0x01; /* TPM_ST_NO_SESSIONS */ - resp[2] = 0x00; resp[3] = 0x00; resp[4] = 0x00; resp[5] = 0x0A; - resp[6] = (byte)((rc >> 24) & 0xFF); - resp[7] = (byte)((rc >> 16) & 0xFF); - resp[8] = (byte)((rc >> 8) & 0xFF); - resp[9] = (byte)(rc & 0xFF); - rspSize = TPM2_HEADER_SIZE; + rspSize = FwtpmBuildErrorFrame(resp, (TPM_RC)rc); } + /* The staging copy holds the same plaintext as the wire response */ + TPM2_ForceZero(stageBuf, sizeof(stageBuf)); *respSz = (word32)rspSize; return 0; /* I/O layer succeeded; TPM error code is in the response. */ } diff --git a/src/fwtpm/fwtpm_tis.c b/src/fwtpm/fwtpm_tis.c index c7ec2310..8d26fe2c 100644 --- a/src/fwtpm/fwtpm_tis.c +++ b/src/fwtpm/fwtpm_tis.c @@ -164,6 +164,7 @@ static void TisHandleRegAccess(FWTPM_CTX* ctx, FWTPM_TIS_REGS* regs) /* Gated to the owner above, so loc == ctx->tisLocality: * execute under the addressed (owning) locality. */ + rspSize = (int)sizeof(regs->rsp_buf); procRc = FWTPM_ProcessCommand(ctx, localCmd, (int)localCmdLen, regs->rsp_buf, &rspSize, loc); diff --git a/src/tpm2_packet.c b/src/tpm2_packet.c index 737ba81a..c62f62eb 100644 --- a/src/tpm2_packet.c +++ b/src/tpm2_packet.c @@ -134,6 +134,7 @@ void TPM2_Packet_InitBuf(TPM2_Packet* packet, byte* buf, int size) packet->buf = buf; packet->pos = TPM2_HEADER_SIZE; /* skip header (fill during finalize) */ packet->size = size; + packet->overflow = 0; } } @@ -150,6 +151,9 @@ void TPM2_Packet_AppendU8(TPM2_Packet* packet, UINT8 data) packet->buf[packet->pos] = data; packet->pos += sizeof(UINT8); } + else if (packet != NULL) { + packet->overflow = 1; + } } void TPM2_Packet_ParseU8(TPM2_Packet* packet, UINT8* data) { @@ -170,6 +174,9 @@ void TPM2_Packet_AppendU16(TPM2_Packet* packet, UINT16 data) XMEMCPY(&packet->buf[packet->pos], &data, sizeof(UINT16)); packet->pos += sizeof(UINT16); } + else if (packet != NULL) { + packet->overflow = 1; + } } void TPM2_Packet_ParseU16(TPM2_Packet* packet, UINT16* data) { @@ -190,6 +197,9 @@ void TPM2_Packet_AppendU32(TPM2_Packet* packet, UINT32 data) XMEMCPY(&packet->buf[packet->pos], &data, sizeof(UINT32)); packet->pos += sizeof(UINT32); } + else if (packet != NULL) { + packet->overflow = 1; + } } void TPM2_Packet_ParseU32(TPM2_Packet* packet, UINT32* data) { @@ -212,6 +222,9 @@ void TPM2_Packet_AppendU64(TPM2_Packet* packet, UINT64 data) XMEMCPY(&packet->buf[packet->pos], &data, sizeof(UINT64)); packet->pos += sizeof(UINT64); } + else if (packet != NULL) { + packet->overflow = 1; + } } void TPM2_Packet_ParseU64(TPM2_Packet* packet, UINT64* data) { @@ -234,6 +247,9 @@ void TPM2_Packet_AppendS32(TPM2_Packet* packet, INT32 data) XMEMCPY(&packet->buf[packet->pos], &data, sizeof(INT32)); packet->pos += sizeof(INT32); } + else if (packet != NULL) { + packet->overflow = 1; + } } void TPM2_Packet_AppendBytes(TPM2_Packet* packet, byte* buf, int size) @@ -243,6 +259,9 @@ void TPM2_Packet_AppendBytes(TPM2_Packet* packet, byte* buf, int size) XMEMCPY(&packet->buf[packet->pos], buf, size); packet->pos += size; } + else if (packet != NULL) { + packet->overflow = 1; + } } void TPM2_Packet_ParseBytes(TPM2_Packet* packet, byte* buf, int size) { diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index 0f67bf82..dc908dee 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -6218,6 +6218,87 @@ static void test_fwtpm_mldsa87_maxbuf(void) fwtpm_pass("MLDSA-87 max-buffer roundtrip:", 1); } +#define FWTPM_TEST_CANARY_SZ 1024 +#define FWTPM_TEST_CANARY_BYTE 0x5A + +static byte gCapRsp[FWTPM_MAX_COMMAND_SIZE + FWTPM_TEST_CANARY_SZ]; + +static void test_fwtpm_response_buffer_capacity(void) +{ + FWTPM_CTX ctx; + int rc, rspSize, cmdSz, pos, j; + UINT32 handle; + UINT32 seqHandle; + byte msg[16]; + + memset(&ctx, 0, sizeof(ctx)); + AssertIntEQ(fwtpm_test_startup(&ctx), 0); + + cmdSz = BuildCreatePrimaryCmdParam(gCmd, TPM_ALG_MLDSA, TPM_MLDSA_87); + rspSize = 0; + rc = FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + handle = GetU32BE(gRsp + TPM2_HEADER_SIZE); + + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_NO_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_SignSequenceStart); pos += 4; + PutU32BE(gCmd + pos, handle); pos += 4; + PutU16BE(gCmd + pos, 0); pos += 2; + PutU16BE(gCmd + pos, 0); pos += 2; + PutU32BE(gCmd + 2, (UINT32)pos); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, pos, gRsp, &rspSize, 0); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + seqHandle = GetU32BE(gRsp + TPM2_HEADER_SIZE); + memset(msg, 0xAB, sizeof(msg)); + + pos = 0; + PutU16BE(gCmd + pos, TPM_ST_SESSIONS); pos += 2; + PutU32BE(gCmd + pos, 0); pos += 4; + PutU32BE(gCmd + pos, TPM_CC_SignSequenceComplete); pos += 4; + PutU32BE(gCmd + pos, seqHandle); pos += 4; + PutU32BE(gCmd + pos, handle); pos += 4; + PutU32BE(gCmd + pos, 18); pos += 4; + PutU32BE(gCmd + pos, TPM_RS_PW); pos += 4; + PutU16BE(gCmd + pos, 0); pos += 2; + gCmd[pos++] = 0; PutU16BE(gCmd + pos, 0); pos += 2; + PutU32BE(gCmd + pos, TPM_RS_PW); pos += 4; + PutU16BE(gCmd + pos, 0); pos += 2; + gCmd[pos++] = 0; PutU16BE(gCmd + pos, 0); pos += 2; + PutU16BE(gCmd + pos, sizeof(msg)); pos += 2; + memcpy(gCmd + pos, msg, sizeof(msg)); pos += sizeof(msg); + PutU32BE(gCmd + 2, (UINT32)pos); + + /* A full-size buffer is the documented contract: the response fits and + * nothing is written past it. */ + memset(gCapRsp, 0, sizeof(gCapRsp)); + memset(gCapRsp + FWTPM_MAX_COMMAND_SIZE, FWTPM_TEST_CANARY_BYTE, + FWTPM_TEST_CANARY_SZ); + rspSize = FWTPM_MAX_COMMAND_SIZE; + rc = FWTPM_ProcessCommand(&ctx, gCmd, pos, gCapRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gCapRsp), TPM_RC_SUCCESS); + AssertTrue(rspSize <= FWTPM_MAX_COMMAND_SIZE); + for (j = 0; j < FWTPM_TEST_CANARY_SZ; j++) { + AssertIntEQ(gCapRsp[FWTPM_MAX_COMMAND_SIZE + j], + FWTPM_TEST_CANARY_BYTE); + } + + /* An MLDSA-87 signature is the largest response and must still fit. */ + AssertTrue(rspSize > 4096); + + BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 14, TPM_CC_FlushContext); + PutU32BE(gCmd + 10, handle); + rspSize = 0; + FWTPM_ProcessCommand(&ctx, gCmd, 14, gRsp, &rspSize, 0); + + FWTPM_Cleanup(&ctx); + fwtpm_pass("Response buffer capacity respected:", 1); +} + /* ---- Hash-ML-DSA sequence round-trip across 44/65/87 ----------------- * SignSequenceStart -> SequenceUpdate(chunked) -> SignSequenceComplete * exercises the hash accumulator path (wc_HashUpdate) through all three @@ -11046,6 +11127,7 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_signseq_slot_exhaustion(); test_fwtpm_signseq_longmsg_boundary(); test_fwtpm_mldsa87_maxbuf(); + test_fwtpm_response_buffer_capacity(); test_fwtpm_mlkem1024_maxbuf(); test_fwtpm_hash_mldsa_seq_all_params(); #endif diff --git a/wolftpm/fwtpm/fwtpm_command.h b/wolftpm/fwtpm/fwtpm_command.h index 705b309a..f9e38d95 100644 --- a/wolftpm/fwtpm/fwtpm_command.h +++ b/wolftpm/fwtpm/fwtpm_command.h @@ -46,7 +46,9 @@ \param cmdBuf input command buffer (big-endian TPM packet) \param cmdSize size of cmdBuf in bytes \param rspBuf output response buffer (caller-allocated) - \param rspSize in: capacity of rspBuf; out: bytes written + \param rspSize out: bytes written. rspBuf must be at least + FWTPM_MAX_COMMAND_SIZE bytes; a caller whose transport buffer is + smaller must stage the response through one that is not. \param locality TPM locality (0-4) reported by the transport \sa FWTPM_IO_ServerLoop diff --git a/wolftpm/tpm2_packet.h b/wolftpm/tpm2_packet.h index 1699d6f9..52d7c747 100644 --- a/wolftpm/tpm2_packet.h +++ b/wolftpm/tpm2_packet.h @@ -98,6 +98,7 @@ typedef struct TPM2_Packet { byte* buf; int pos; int size; + unsigned int overflow:1; } TPM2_Packet;