diff --git a/examples/boot/secure_rot.c b/examples/boot/secure_rot.c index c009e139f..69cc3a0d8 100644 --- a/examples/boot/secure_rot.c +++ b/examples/boot/secure_rot.c @@ -252,6 +252,9 @@ int TPM2_Boot_SecureROT_Example(void* userCtx, int argc, char *argv[]) rc = wolfTPM2_NVReadPublic(&dev, nvIndex, &nvPublic); if (rc == 0) { digestSz = nvPublic.dataSize; + if (digestSz > (int)sizeof(digest)) { + digestSz = (int)sizeof(digest); + } } /* Read access */ diff --git a/examples/gpio/gpio_config.c b/examples/gpio/gpio_config.c index 92ed6c61d..20d791669 100644 --- a/examples/gpio/gpio_config.c +++ b/examples/gpio/gpio_config.c @@ -377,7 +377,7 @@ int TPM2_GPIO_Config_Example(void* userCtx, int argc, char *argv[]) /* Extra step for open-drain with pull-up mode */ if (gpioMode == TPM_GPIO_MODE_PULLUP) { - newConfig.GpioPullUp &= ~(1 << gpioNum); + newConfig.GpioPullUp |= (1 << gpioNum); } #ifdef WOLFTPM_DEBUG_VERBOSE diff --git a/examples/pqc/mldsa_sign.c b/examples/pqc/mldsa_sign.c index 5423402ba..43967d08d 100644 --- a/examples/pqc/mldsa_sign.c +++ b/examples/pqc/mldsa_sign.c @@ -24,10 +24,10 @@ * (SignSequenceComplete), Sec.17.6 (VerifySequenceStart), Sec.20.3 * (VerifySequenceComplete). * - * Pure ML-DSA is one-shot on the sign path: SequenceUpdate is rejected - * with TPM_RC_ONE_SHOT_SIGNATURE, the full message must arrive via the - * SignSequenceComplete buffer. Verify sequences do accept Update per - * Sec.20.3 and this example uses that path to exercise both idioms. */ + * Pure ML-DSA is streamable on both sign and verify: SequenceUpdate is + * accepted (only multi-pass schemes such as EdDSA are one-shot). This + * example passes the whole message via SignSequenceComplete on sign and + * streams it through SequenceUpdate on verify to exercise both idioms. */ #ifdef HAVE_CONFIG_H #include @@ -144,9 +144,8 @@ static int mldsa_sign_run(int argc, char *argv[]) (unsigned)mldsaKey.handle.hndl, (unsigned)mldsaKey.pub.publicArea.unique.mldsa.size); - /* Sign: Pure ML-DSA is one-shot per Sec.17.5. Message goes via - * SignSequenceComplete's buffer parameter, not via SequenceUpdate - * (which returns TPM_RC_ONE_SHOT_SIGNATURE for Pure MLDSA keys). */ + /* Sign: pass the whole message via SignSequenceComplete's buffer. + * Pure ML-DSA also accepts SequenceUpdate since it is streamable. */ rc = wolfTPM2_SignSequenceStart(&dev, &mldsaKey, NULL, 0, &seqHandle); if (rc != TPM_RC_SUCCESS) { printf("SignSequenceStart failed 0x%x: %s\n", diff --git a/examples/pqc/mldsa_verify_neg.c b/examples/pqc/mldsa_verify_neg.c index a9797c543..aecc20798 100644 --- a/examples/pqc/mldsa_verify_neg.c +++ b/examples/pqc/mldsa_verify_neg.c @@ -163,7 +163,7 @@ static int mldsa_verify_neg_run(int argc, char *argv[]) goto exit; } - /* Sign the message (Pure ML-DSA is one-shot via Complete's buffer). */ + /* Sign the message via Complete's buffer (Pure ML-DSA is streamable). */ rc = wolfTPM2_SignSequenceStart(&dev, &mldsaKey, NULL, 0, &seqHandle); if (rc == TPM_RC_SUCCESS) { rc = wolfTPM2_SignSequenceComplete(&dev, seqHandle, &mldsaKey, diff --git a/examples/tpm_test_keys.c b/examples/tpm_test_keys.c index 3988e1b2e..171252d88 100644 --- a/examples/tpm_test_keys.c +++ b/examples/tpm_test_keys.c @@ -224,7 +224,7 @@ int readKeyBlob(const char* filename, WOLFTPM2_KEYBLOB* key) goto exit; } fileSz -= bytes_read; - if (key->pub.size > sizeof(UINT16) + sizeof(pubAreaBuffer)) { + if (sizeof(UINT16) + key->pub.size > sizeof(pubAreaBuffer)) { printf("Public key size is too large\n"); rc = BUFFER_E; goto exit; } @@ -249,11 +249,23 @@ int readKeyBlob(const char* filename, WOLFTPM2_KEYBLOB* key) if (fileSz > 0) { printf("Reading the private part of the key\n"); - bytes_read = XFREAD(&key->priv, 1, fileSz, fp); - if (bytes_read != fileSz) { + /* Read and validate the size marker before the buffer so a + * crafted file cannot overflow priv */ + bytes_read = XFREAD(&key->priv.size, 1, sizeof(key->priv.size), fp); + if (bytes_read != sizeof(key->priv.size)) { + printf("Read %zu, expected private size marker of %zu bytes\n", + bytes_read, sizeof(key->priv.size)); + rc = BUFFER_E; goto exit; + } + if (key->priv.size > sizeof(key->priv.buffer)) { + printf("Private key size is too large\n"); + rc = BUFFER_E; goto exit; + } + bytes_read = XFREAD(key->priv.buffer, 1, key->priv.size, fp); + if (bytes_read != key->priv.size) { printf("Read %zu, expected private blob %zu bytes\n", - bytes_read, fileSz); - goto exit; + bytes_read, (size_t)key->priv.size); + rc = BUFFER_E; goto exit; } rc = 0; /* success */ } diff --git a/src/spdm/spdm_kdf.c b/src/spdm/spdm_kdf.c index e17158ce4..e823ed649 100644 --- a/src/spdm/spdm_kdf.c +++ b/src/spdm/spdm_kdf.c @@ -170,6 +170,19 @@ int wolfSPDM_DeriveFromHandshakeSecret(WOLFSPDM_CTX* ctx, ctx->rspDataKey, ctx->rspDataIv); } + if (rc != WOLFSPDM_SUCCESS) { + /* wipe any partially derived material so it cannot carry into a + * reused ctx */ + wc_ForceZero(ctx->reqHsSecret, sizeof(ctx->reqHsSecret)); + wc_ForceZero(ctx->rspHsSecret, sizeof(ctx->rspHsSecret)); + wc_ForceZero(ctx->reqFinishedKey, sizeof(ctx->reqFinishedKey)); + wc_ForceZero(ctx->rspFinishedKey, sizeof(ctx->rspFinishedKey)); + wc_ForceZero(ctx->reqDataKey, sizeof(ctx->reqDataKey)); + wc_ForceZero(ctx->rspDataKey, sizeof(ctx->rspDataKey)); + wc_ForceZero(ctx->reqDataIv, sizeof(ctx->reqDataIv)); + wc_ForceZero(ctx->rspDataIv, sizeof(ctx->rspDataIv)); + } + return rc; } diff --git a/src/tpm2.c b/src/tpm2.c index c594df12c..aa95dbf0f 100644 --- a/src/tpm2.c +++ b/src/tpm2.c @@ -450,14 +450,19 @@ static TPM_RC TPM2_SPDM_SendCommand(TPM2_CTX* ctx, TPM2_Packet* packet) rc = wolfTPM2_SPDM_SecuredExchange(spdmCtx, packet->buf, packet->pos, tpmResp, &tpmRespSz); - if (rc != 0) + if (rc != 0) { + TPM2_ForceZero(tpmResp, sizeof(tpmResp)); return rc; + } - if (tpmRespSz > MAX_RESPONSE_SIZE) + if (tpmRespSz > MAX_RESPONSE_SIZE) { + TPM2_ForceZero(tpmResp, sizeof(tpmResp)); return TPM_RC_SIZE; + } XMEMCPY(packet->buf, tpmResp, tpmRespSz); packet->pos = 0; packet->size = tpmRespSz; + TPM2_ForceZero(tpmResp, sizeof(tpmResp)); return TPM_RC_SUCCESS; } #endif /* WOLFTPM_SPDM */ @@ -6529,6 +6534,8 @@ int TPM2_GetNonceNoLock(byte* nonceBuf, int nonceSz) TPM2_Packet_ParseBytes(&packet, &nonceBuf[randSz], outSz); randSz += outSz; } + /* response buffer held freshly generated random; wipe before return */ + TPM2_ForceZero(buffer, sizeof(buffer)); #endif return rc; diff --git a/src/tpm2_linux.c b/src/tpm2_linux.c index 10054607f..5c86e65a4 100644 --- a/src/tpm2_linux.c +++ b/src/tpm2_linux.c @@ -92,15 +92,19 @@ int TPM2_LINUX_SendCommand(TPM2_CTX* ctx, TPM2_Packet* packet) #include /* TPM Device Path Configuration: - * - /dev/tpm0: TPM raw device (default) - * - /dev/tpmrm0: TPM resource manager (requires kernel 5.12+) - * Enabled with WOLFTPM_USE_TPMRM + * - /dev/tpmrm0: TPM resource manager (requires kernel 5.12+), preferred as it + * isolates and flushes per-connection transient objects/sessions + * - /dev/tpm0: TPM raw device, no in-kernel resource management + * The default prefers the resource manager and falls back to the raw device + * when it is unavailable. Define WOLFTPM_USE_TPMRM for resource manager only, + * or set TPM2_LINUX_DEV to pin a specific device. */ #ifndef TPM2_LINUX_DEV #ifdef WOLFTPM_USE_TPMRM #define TPM2_LINUX_DEV "/dev/tpmrm0" #else - #define TPM2_LINUX_DEV "/dev/tpm0" + #define TPM2_LINUX_DEV "/dev/tpmrm0" + #define TPM2_LINUX_DEV_FALLBACK "/dev/tpm0" #endif #endif @@ -121,14 +125,23 @@ int TPM2_LINUX_SendCommand(TPM2_CTX* ctx, TPM2_Packet* packet) int rc_poll, nfds = 1; /* Polling single TPM dev file */ struct pollfd fds; int rspSz = 0; + const char* devName = TPM2_LINUX_DEV; #ifdef WOLFTPM_DEBUG_VERBOSE printf("Command size: %d\n", packet->pos); TPM2_PrintBin(packet->buf, packet->pos); #endif - if (ctx->fd < 0) + if (ctx->fd < 0) { ctx->fd = open(TPM2_LINUX_DEV, O_RDWR | O_NONBLOCK); +#ifdef TPM2_LINUX_DEV_FALLBACK + if (ctx->fd < 0) { + ctx->fd = open(TPM2_LINUX_DEV_FALLBACK, O_RDWR | O_NONBLOCK); + if (ctx->fd >= 0) + devName = TPM2_LINUX_DEV_FALLBACK; + } +#endif + } if (ctx->fd >= 0) { /* Send the TPM command */ if (write(ctx->fd, packet->buf, packet->pos) == packet->pos) { @@ -147,11 +160,11 @@ int TPM2_LINUX_SendCommand(TPM2_CTX* ctx, TPM2_Packet* packet) else { #ifdef DEBUG_WOLFTPM if (ret == 0) { - printf("Received EOF from %s\n", TPM2_LINUX_DEV); + printf("Received EOF from %s\n", devName); } else { printf("Failed to read from %s (ret %zd):" - " errno %d = %s\n", TPM2_LINUX_DEV, ret, + " errno %d = %s\n", devName, ret, errno, strerror(errno)); } #endif @@ -161,7 +174,7 @@ int TPM2_LINUX_SendCommand(TPM2_CTX* ctx, TPM2_Packet* packet) else { #ifdef DEBUG_WOLFTPM printf("Failed poll on %s: errno %d = %s\n", - TPM2_LINUX_DEV, errno, strerror(errno)); + devName, errno, strerror(errno)); #endif rc = TPM_RC_FAILURE; } @@ -169,19 +182,19 @@ int TPM2_LINUX_SendCommand(TPM2_CTX* ctx, TPM2_Packet* packet) else { #ifdef DEBUG_WOLFTPM printf("Failed write to %s: errno %d = %s\n", - TPM2_LINUX_DEV, errno, strerror(errno)); + devName, errno, strerror(errno)); #endif rc = TPM_RC_FAILURE; } } else if (ctx->fd == -1 && errno == EACCES) { printf("Permission denied on %s\n" - "Use sudo or add tss group to user.\n", TPM2_LINUX_DEV); + "Use sudo or add tss group to user.\n", devName); } else { #ifdef DEBUG_WOLFTPM printf("Failed to open %s: errno %d = %s\n", - TPM2_LINUX_DEV, errno, strerror(errno)); + devName, errno, strerror(errno)); #endif } diff --git a/src/tpm2_packet.c b/src/tpm2_packet.c index 338837b6c..1c0125d1e 100644 --- a/src/tpm2_packet.c +++ b/src/tpm2_packet.c @@ -830,18 +830,26 @@ void TPM2_Packet_AppendSensitive(TPM2_Packet* packet, TPM2B_SENSITIVE* sensitive switch (sensitive->sensitiveArea.sensitiveType) { case TPM_ALG_RSA: + if (sens->rsa.size > sizeof(sens->rsa.buffer)) + sens->rsa.size = sizeof(sens->rsa.buffer); TPM2_Packet_AppendU16(packet, sens->rsa.size); TPM2_Packet_AppendBytes(packet, sens->rsa.buffer, sens->rsa.size); break; case TPM_ALG_ECC: + if (sens->ecc.size > sizeof(sens->ecc.buffer)) + sens->ecc.size = sizeof(sens->ecc.buffer); TPM2_Packet_AppendU16(packet, sens->ecc.size); TPM2_Packet_AppendBytes(packet, sens->ecc.buffer, sens->ecc.size); break; case TPM_ALG_KEYEDHASH: + if (sens->bits.size > sizeof(sens->bits.buffer)) + sens->bits.size = sizeof(sens->bits.buffer); TPM2_Packet_AppendU16(packet, sens->bits.size); TPM2_Packet_AppendBytes(packet, sens->bits.buffer, sens->bits.size); break; case TPM_ALG_SYMCIPHER: + if (sens->sym.size > sizeof(sens->sym.buffer)) + sens->sym.size = sizeof(sens->sym.buffer); TPM2_Packet_AppendU16(packet, sens->sym.size); TPM2_Packet_AppendBytes(packet, sens->sym.buffer, sens->sym.size); break; @@ -1206,6 +1214,9 @@ void TPM2_Packet_ParsePublic(TPM2_Packet* packet, TPM2B_PUBLIC* pub) TPM2_Packet_ParseU16(packet, &pub->size); pubStartPos = (packet != NULL) ? packet->pos : 0; + if (pub->size == 0) { + XMEMSET(&pub->publicArea, 0, sizeof(pub->publicArea)); + } if (pub->size > 0) { TPM2_Packet_ParseU16(packet, &pub->publicArea.type); TPM2_Packet_ParseU16(packet, &pub->publicArea.nameAlg); diff --git a/src/tpm2_spdm.c b/src/tpm2_spdm.c index a0859a72c..63f912dfd 100644 --- a/src/tpm2_spdm.c +++ b/src/tpm2_spdm.c @@ -66,6 +66,16 @@ /* TIS I/O Callback (SPI/I2C TPM transport for SPDM) */ /* -------------------------------------------------------------------------- */ +/* Bound a framed response size against both the caller buffer and the local + * I/O buffer before copying into the caller's rxBuf. */ +int wolfTPM2_SPDM_ValidateRspSz(word32 rspSz, word32 rxSz, word32 ioBufSz) +{ + if (rspSz > rxSz || rspSz > ioBufSz) { + return -1; + } + return 0; +} + #ifdef WOLFTPM_SPDM_TIS_IO /* TIS I/O callback for routing wolfSPDM through TPM SPI/I2C FIFO. * This matches the WOLFSPDM_IO_CB signature. TCG framing (headers) is @@ -117,7 +127,7 @@ static int wolfTPM2_SPDM_TisIoCb( XMEMCPY(&rspSz, &ioBuf[2], sizeof(UINT32)); rspSz = TPM2_Packet_SwapU32(rspSz); - if (rspSz > *rxSz || rspSz > sizeof(ioBuf)) { + if (wolfTPM2_SPDM_ValidateRspSz(rspSz, *rxSz, sizeof(ioBuf)) != 0) { return -1; } @@ -171,7 +181,10 @@ static int wolfTPM2_SPDM_SwtpmIoCb( XMEMCPY(&rspSz, &ioBuf[2], sizeof(word32)); rspSz = TPM2_Packet_SwapU32(rspSz); - if (rspSz < TPM2_HEADER_SIZE || rspSz > *rxSz || rspSz > sizeof(ioBuf)) { + if (rspSz < TPM2_HEADER_SIZE) { + return -1; + } + if (wolfTPM2_SPDM_ValidateRspSz(rspSz, *rxSz, sizeof(ioBuf)) != 0) { return -1; } diff --git a/src/tpm2_swtpm.c b/src/tpm2_swtpm.c index 89a542f35..723005eea 100644 --- a/src/tpm2_swtpm.c +++ b/src/tpm2_swtpm.c @@ -478,6 +478,18 @@ static TPM_RC SwTpmDisconnect(TPM2_CTX* ctx) return rc; } +/* Validate a swtpm-reported response size against the receive buffer before + * it is used to read into packet->buf. Rejects undersized headers and any + * length that would overflow the buffer. */ +int TPM2_SwtpmValidateRspSz(int packetSize, uint32_t rspSz) +{ + int rc = TPM_RC_SUCCESS; + if (rspSz < TPM2_HEADER_SIZE || rspSz > (uint32_t)packetSize) { + rc = TPM_RC_FAILURE; + } + return rc; +} + /* Talk to a TPM through socket * return TPM_RC_SUCCESS on success, * TPM_RC_FAILURE on other errors @@ -543,13 +555,14 @@ int TPM2_SWTPM_SendCommand(TPM2_CTX* ctx, TPM2_Packet* packet) if (rc == TPM_RC_SUCCESS) { rc = SwTpmReceive(ctx, &tss_word, sizeof(uint32_t)); rspSz = TPM2_Packet_SwapU32(tss_word); - if (rspSz < TPM2_HEADER_SIZE || - rspSz > (uint32_t)packet->size) { - #ifdef WOLFTPM_DEBUG_VERBOSE - printf("Response size(%u) out of range (header=%d, buf=%d)\n", - rspSz, TPM2_HEADER_SIZE, packet->size); - #endif - rc = TPM_RC_FAILURE; + if (rc == TPM_RC_SUCCESS) { + rc = TPM2_SwtpmValidateRspSz(packet->size, rspSz); + if (rc != TPM_RC_SUCCESS) { + #ifdef WOLFTPM_DEBUG_VERBOSE + printf("Response size(%u) out of range (header=%d, buf=%d)\n", + rspSz, TPM2_HEADER_SIZE, packet->size); + #endif + } } } diff --git a/src/tpm2_tis.c b/src/tpm2_tis.c index 717345bb4..3b67c5b8e 100644 --- a/src/tpm2_tis.c +++ b/src/tpm2_tis.c @@ -203,6 +203,8 @@ int TPM2_TIS_Read(TPM2_CTX* ctx, word32 addr, byte* result, rc = ctx->ioCb(ctx, txBuf, rxBuf, len + TPM_TIS_HEADER_SZ, ctx->userCtx); XMEMCPY(result, &rxBuf[TPM_TIS_HEADER_SZ], len); + TPM2_ForceZero(txBuf, sizeof(txBuf)); + TPM2_ForceZero(rxBuf, sizeof(rxBuf)); #endif TPM2_TIS_UNLOCK(); #ifdef WOLFTPM_DEBUG_IO @@ -243,6 +245,8 @@ int TPM2_TIS_Write(TPM2_CTX* ctx, word32 addr, const byte* value, XMEMSET(rxBuf, 0, sizeof(rxBuf)); rc = ctx->ioCb(ctx, txBuf, rxBuf, len + TPM_TIS_HEADER_SZ, ctx->userCtx); + TPM2_ForceZero(txBuf, sizeof(txBuf)); + TPM2_ForceZero(rxBuf, sizeof(rxBuf)); #endif TPM2_TIS_UNLOCK(); #ifdef WOLFTPM_DEBUG_IO @@ -435,6 +439,17 @@ int TPM2_TIS_GetBurstCount(TPM2_CTX* ctx, word16* burstCount) return rc; } +/* Validate a TPM-reported response size from the wire header against the + * caller buffer before the receive loop writes into it. */ +int TPM2_TIS_ValidateRspSz(int rspSz, int packetSize) +{ + int rc = TPM_RC_SUCCESS; + if (rspSz < 0 || rspSz >= MAX_RESPONSE_SIZE || rspSz > packetSize) { + rc = TPM_RC_FAILURE; + } + return rc; +} + int TPM2_TIS_SendCommand(TPM2_CTX* ctx, TPM2_Packet* packet) { int rc; @@ -557,8 +572,8 @@ int TPM2_TIS_SendCommand(TPM2_CTX* ctx, TPM2_Packet* packet) rspSz = TPM2_Packet_SwapU32(tmpSz); /* safety check for stuck FFFF case */ - if (rspSz < 0 || rspSz >= MAX_RESPONSE_SIZE || rspSz > packet->size) { - rc = TPM_RC_FAILURE; + rc = TPM2_TIS_ValidateRspSz(rspSz, packet->size); + if (rc != TPM_RC_SUCCESS) { goto exit; } } diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index bc1d8de1c..5051fe379 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -2943,6 +2943,7 @@ int wolfTPM2_CreateKey(WOLFTPM2_DEV* dev, WOLFTPM2_KEYBLOB* keyBlob, wolfTPM2_CopyPriv(&keyBlob->priv, &createOut.outPrivate); TPM2_ForceZero(&createIn.inSensitive, sizeof(createIn.inSensitive)); + TPM2_ForceZero(&createOut.outPrivate, sizeof(createOut.outPrivate)); return rc; } @@ -3088,6 +3089,8 @@ int wolfTPM2_CreateLoadedKey(WOLFTPM2_DEV* dev, WOLFTPM2_KEYBLOB* keyBlob, TPM2_ForceZero(&createLoadedIn.inSensitive, sizeof(createLoadedIn.inSensitive)); + TPM2_ForceZero(&createLoadedOut.outPrivate, + sizeof(createLoadedOut.outPrivate)); return rc; } @@ -9196,6 +9199,7 @@ int wolfTPM2_CreateKeySeal_ex(WOLFTPM2_DEV* dev, WOLFTPM2_KEYBLOB* keyBlob, wolfTPM2_CopyPriv(&keyBlob->priv, &createOut.outPrivate); TPM2_ForceZero(&createIn.inSensitive, sizeof(createIn.inSensitive)); + TPM2_ForceZero(&createOut.outPrivate, sizeof(createOut.outPrivate)); return rc; } diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 8afe674fb..f40bfea13 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -29,6 +29,9 @@ #include #include #include +#include +#include +#include #include #include @@ -36,6 +39,9 @@ #include #include +#if defined(__linux__) || defined(__APPLE__) || defined(__unix__) +#include +#endif /* Test Fail Helpers */ #ifndef NO_ABORT @@ -3134,6 +3140,161 @@ static void test_TPM2_Sensitive_Roundtrip(void) printf("Test TPM Wrapper: %-40s Passed\n", "Sensitive roundtrip:"); } +#ifdef WOLFTPM_SPDM +/* Pin the rxBuf bound in the SPDM I/O callbacks: a response larger than the + * caller buffer must be rejected even when it fits the local I/O buffer. */ +static void test_wolfTPM2_SPDM_ValidateRspSz(void) +{ + word32 ioBufSz = MAX_RESPONSE_SIZE; + word32 rxSz = 256; + + AssertIntEQ(wolfTPM2_SPDM_ValidateRspSz(rxSz, rxSz, ioBufSz), 0); + AssertIntEQ(wolfTPM2_SPDM_ValidateRspSz(rxSz + 1, rxSz, ioBufSz), -1); + AssertIntEQ(wolfTPM2_SPDM_ValidateRspSz(ioBufSz + 1, ioBufSz, ioBufSz), -1); + + printf("Test TPM2: %-40s Passed\n", "SPDM ValidateRspSz:"); +} +#endif /* WOLFTPM_SPDM */ + +/* Pin the TIS response-size bounds so a mutation dropping the buffer-bound or + * the MAX_RESPONSE_SIZE term is caught. */ +static void test_TPM2_TIS_ValidateRspSz(void) +{ + int packetSize = 1024; + + AssertIntEQ(TPM2_TIS_ValidateRspSz(TPM2_HEADER_SIZE, packetSize), + TPM_RC_SUCCESS); + AssertIntEQ(TPM2_TIS_ValidateRspSz(packetSize, packetSize), + TPM_RC_SUCCESS); + + AssertIntEQ(TPM2_TIS_ValidateRspSz(packetSize + 1, packetSize), + TPM_RC_FAILURE); + AssertIntEQ(TPM2_TIS_ValidateRspSz(MAX_RESPONSE_SIZE, MAX_RESPONSE_SIZE), + TPM_RC_FAILURE); + AssertIntEQ(TPM2_TIS_ValidateRspSz(-1, packetSize), + TPM_RC_FAILURE); + + printf("Test TPM2: %-40s Passed\n", "TIS ValidateRspSz:"); +} + +/* A zero-size TPM2B_PUBLIC must clear publicArea so stale fields from a reused + * struct cannot survive a parse. */ +static void test_TPM2_ParsePublic_EmptyClears(void) +{ + TPM2_Packet packet; + byte buf[8]; + TPM2B_PUBLIC pub; + + XMEMSET(&pub, 0, sizeof(pub)); + pub.publicArea.type = TPM_ALG_RSA; + pub.publicArea.nameAlg = TPM_ALG_SHA256; + pub.publicArea.objectAttributes = 0xFFFFFFFFUL; + + XMEMSET(buf, 0, sizeof(buf)); + XMEMSET(&packet, 0, sizeof(packet)); + packet.buf = buf; + packet.size = sizeof(buf); + + TPM2_Packet_ParsePublic(&packet, &pub); + + AssertIntEQ(pub.size, 0); + AssertIntEQ(pub.publicArea.type, 0); + AssertIntEQ(pub.publicArea.nameAlg, 0); + AssertIntEQ(pub.publicArea.objectAttributes, 0); + + printf("Test TPM2: %-40s Passed\n", "ParsePublic empty clears:"); +} + +/* An oversized inner size on a classic arm must be clamped to the arm buffer + * size, matching the PQC arms, so AppendBytes does not over-read the source. + * Each arm has its own buffer member so all four are exercised. */ +static void test_TPM2_AppendSensitive_Clamp(void) +{ + TPM2_Packet packet; + byte buf[1024]; + TPM2B_SENSITIVE sens; + word16 rsaCap, eccCap, bitsCap, symCap; + + rsaCap = (word16)sizeof(sens.sensitiveArea.sensitive.rsa.buffer); + eccCap = (word16)sizeof(sens.sensitiveArea.sensitive.ecc.buffer); + bitsCap = (word16)sizeof(sens.sensitiveArea.sensitive.bits.buffer); + symCap = (word16)sizeof(sens.sensitiveArea.sensitive.sym.buffer); + + XMEMSET(&sens, 0, sizeof(sens)); + sens.sensitiveArea.sensitiveType = TPM_ALG_RSA; + sens.sensitiveArea.sensitive.rsa.size = rsaCap + 100; + XMEMSET(&packet, 0, sizeof(packet)); + packet.buf = buf; + packet.size = sizeof(buf); + TPM2_Packet_AppendSensitive(&packet, &sens); + AssertIntEQ(sens.sensitiveArea.sensitive.rsa.size, rsaCap); + + XMEMSET(&sens, 0, sizeof(sens)); + sens.sensitiveArea.sensitiveType = TPM_ALG_ECC; + sens.sensitiveArea.sensitive.ecc.size = eccCap + 100; + XMEMSET(&packet, 0, sizeof(packet)); + packet.buf = buf; + packet.size = sizeof(buf); + TPM2_Packet_AppendSensitive(&packet, &sens); + AssertIntEQ(sens.sensitiveArea.sensitive.ecc.size, eccCap); + + XMEMSET(&sens, 0, sizeof(sens)); + sens.sensitiveArea.sensitiveType = TPM_ALG_KEYEDHASH; + sens.sensitiveArea.sensitive.bits.size = bitsCap + 100; + XMEMSET(&packet, 0, sizeof(packet)); + packet.buf = buf; + packet.size = sizeof(buf); + TPM2_Packet_AppendSensitive(&packet, &sens); + AssertIntEQ(sens.sensitiveArea.sensitive.bits.size, bitsCap); + + XMEMSET(&sens, 0, sizeof(sens)); + sens.sensitiveArea.sensitiveType = TPM_ALG_SYMCIPHER; + sens.sensitiveArea.sensitive.sym.size = symCap + 100; + XMEMSET(&packet, 0, sizeof(packet)); + packet.buf = buf; + packet.size = sizeof(buf); + TPM2_Packet_AppendSensitive(&packet, &sens); + AssertIntEQ(sens.sensitiveArea.sensitive.sym.size, symCap); + + printf("Test TPM2: %-40s Passed\n", "AppendSensitive clamp:"); +} + +/* Roundtrip a maximum-size inner payload (size == buffer capacity) so the + * parse-side ParseU16Buf clamp branch is exercised with valid data. */ +static void test_TPM2_Sensitive_MaxRoundtrip(void) +{ + TPM2_Packet packet; + byte buf[1024]; + TPM2B_SENSITIVE sensIn, sensOut; + word16 cap, i; + + XMEMSET(&sensIn, 0, sizeof(sensIn)); + sensIn.sensitiveArea.sensitiveType = TPM_ALG_RSA; + cap = (word16)sizeof(sensIn.sensitiveArea.sensitive.rsa.buffer); + sensIn.sensitiveArea.sensitive.rsa.size = cap; + for (i = 0; i < cap; i++) { + sensIn.sensitiveArea.sensitive.rsa.buffer[i] = (byte)(i & 0xFF); + } + + XMEMSET(buf, 0, sizeof(buf)); + XMEMSET(&packet, 0, sizeof(packet)); + packet.buf = buf; + packet.size = sizeof(buf); + + TPM2_Packet_AppendSensitive(&packet, &sensIn); + + packet.pos = 0; + XMEMSET(&sensOut, 0, sizeof(sensOut)); + TPM2_Packet_ParseSensitive(&packet, &sensOut); + + AssertIntEQ(sensOut.sensitiveArea.sensitiveType, TPM_ALG_RSA); + AssertIntEQ(sensOut.sensitiveArea.sensitive.rsa.size, cap); + AssertIntEQ(XMEMCMP(sensOut.sensitiveArea.sensitive.rsa.buffer, + sensIn.sensitiveArea.sensitive.rsa.buffer, cap), 0); + + printf("Test TPM2: %-40s Passed\n", "Sensitive max roundtrip:"); +} + static void test_KeySealTemplate(void) { int rc; @@ -3826,6 +3987,141 @@ static void test_wolfTPM2_SPDM_Functions(void) } #endif /* WOLFTPM_SPDM */ +#ifdef WOLFTPM_SWTPM +/* Pin the swtpm response-size bounds so a mutation dropping either the + * lower (header) or upper (buffer) bound is caught. */ +static void test_TPM2_SwtpmValidateRspSz(void) +{ + int packetSize = 4096; + + AssertIntEQ(TPM2_SwtpmValidateRspSz(packetSize, TPM2_HEADER_SIZE), + TPM_RC_SUCCESS); + AssertIntEQ(TPM2_SwtpmValidateRspSz(packetSize, (uint32_t)packetSize), + TPM_RC_SUCCESS); + + AssertIntEQ(TPM2_SwtpmValidateRspSz(packetSize, TPM2_HEADER_SIZE - 1), + TPM_RC_FAILURE); + AssertIntEQ(TPM2_SwtpmValidateRspSz(packetSize, (uint32_t)packetSize + 1), + TPM_RC_FAILURE); + AssertIntEQ(TPM2_SwtpmValidateRspSz(packetSize, 0xFFFFFFFFUL), + TPM_RC_FAILURE); + + printf("Test TPM2: %-40s Passed\n", "Swtpm ValidateRspSz:"); +} +#endif /* WOLFTPM_SWTPM */ + +#if !defined(NO_FILESYSTEM) && !defined(NO_WRITE_TEMP_FILES) && \ + !defined(WOLFTPM2_NO_WOLFCRYPT) && defined(HAVE_ECC) +/* Create a temp file for writing with owner-only permissions, so the test + * does not leave a world-writable file as plain fopen("wb") would. */ +static XFILE openTestFileWrite(const char* fn) +{ +#if defined(__linux__) || defined(__APPLE__) || defined(__unix__) + int fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (fd < 0) { + return XBADFILE; + } + return fdopen(fd, "wb"); +#else + return XFOPEN(fn, "wb"); +#endif +} + +/* Craft a key-blob file with a small valid public area and an oversized + * private tail, then load it with readKeyBlob(). A canary placed directly + * after the keyblob catches any write past it. No TPM is required. */ +static void test_readKeyBlob_PrivOverflow(void) +{ + int rc; + int pubAreaSize = 0; + word32 i; + size_t privTailSz, remaining, chunk; + UINT16 privSizeMarker; + UINT16 bigMarker; + XFILE fp; + byte pubAreaBuffer[sizeof(TPM2B_PUBLIC)]; + byte filler[64]; + WOLFTPM2_KEYBLOB tmpl; + const char* filename = "keyblob_overflow_test.raw"; + struct { + WOLFTPM2_KEYBLOB key; + byte canary[128]; + } guarded; + + XMEMSET(&tmpl, 0, sizeof(tmpl)); + + /* Build a real minimal public area so TPM2_ParsePublic() accepts it and + * readKeyBlob proceeds to the private read. */ + rc = wolfTPM2_GetKeyTemplate_ECC(&tmpl.pub.publicArea, + TPMA_OBJECT_sign | TPMA_OBJECT_userWithAuth | TPMA_OBJECT_noDA, + TPM_ECC_NIST_P256, TPM_ALG_ECDSA); + AssertIntEQ(rc, 0); + rc = TPM2_AppendPublic(pubAreaBuffer, (word32)sizeof(pubAreaBuffer), + &pubAreaSize, &tmpl.pub); + AssertIntEQ(rc, 0); + + /* Private tail longer than the destination so the pre-fix path, which + * reads the whole remaining file into &key->priv, overflows past the + * keyblob. Keep the size marker small and valid so the post-read sanity + * check passes. */ + privTailSz = sizeof(guarded.key.priv) + sizeof(filler); + privSizeMarker = 32; + + fp = openTestFileWrite(filename); + AssertNotNull(fp); + XFWRITE(&tmpl.pub.size, 1, sizeof(tmpl.pub.size), fp); + XFWRITE(pubAreaBuffer, 1, sizeof(UINT16) + tmpl.pub.size, fp); + XFWRITE(&privSizeMarker, 1, sizeof(privSizeMarker), fp); + XMEMSET(filler, 0xAB, sizeof(filler)); + remaining = privTailSz - sizeof(privSizeMarker); + while (remaining > 0) { + chunk = (remaining < sizeof(filler)) ? remaining : sizeof(filler); + XFWRITE(filler, 1, chunk, fp); + remaining -= chunk; + } + XFCLOSE(fp); + + XMEMSET(&guarded, 0, sizeof(guarded)); + XMEMSET(guarded.canary, 0x5A, sizeof(guarded.canary)); + + rc = readKeyBlob(filename, &guarded.key); + (void)rc; + + for (i = 0; i < sizeof(guarded.canary); i++) { + AssertIntEQ(guarded.canary[i], 0x5A); + } + + remove(filename); + + /* Rejection branch: priv.size marker larger than the destination buffer + * must be refused with BUFFER_E before any bytes are read. */ + bigMarker = (UINT16)(sizeof(guarded.key.priv.buffer) + 1); + fp = openTestFileWrite(filename); + AssertNotNull(fp); + XFWRITE(&tmpl.pub.size, 1, sizeof(tmpl.pub.size), fp); + XFWRITE(pubAreaBuffer, 1, sizeof(UINT16) + tmpl.pub.size, fp); + XFWRITE(&bigMarker, 1, sizeof(bigMarker), fp); + XFCLOSE(fp); + XMEMSET(&guarded, 0, sizeof(guarded)); + rc = readKeyBlob(filename, &guarded.key); + AssertIntEQ(rc, BUFFER_E); + remove(filename); + + /* Rejection branch: pub.size marker larger than the public area buffer. */ + bigMarker = (UINT16)sizeof(pubAreaBuffer); + fp = openTestFileWrite(filename); + AssertNotNull(fp); + XFWRITE(&bigMarker, 1, sizeof(bigMarker), fp); + XFCLOSE(fp); + XMEMSET(&guarded, 0, sizeof(guarded)); + rc = readKeyBlob(filename, &guarded.key); + AssertIntEQ(rc, BUFFER_E); + remove(filename); + + printf("Test TPM Wrapper: %-40s Passed\n", "readKeyBlob priv overflow:"); +} +#endif + /* Test creating key and exporting keyblob as buffer, * importing and loading key. */ static void test_wolfTPM2_KeyBlob(TPM_ALG_ID alg) @@ -5227,6 +5523,10 @@ int unit_tests(int argc, char *argv[]) test_TPM2_Public_PQC_Roundtrip(); #endif test_TPM2_Sensitive_Roundtrip(); + test_TPM2_TIS_ValidateRspSz(); + test_TPM2_ParsePublic_EmptyClears(); + test_TPM2_AppendSensitive_Clamp(); + test_TPM2_Sensitive_MaxRoundtrip(); test_KeySealTemplate(); test_SealAndKeyedHash_Boundaries(); test_GetAlgId(); @@ -5251,6 +5551,13 @@ int unit_tests(int argc, char *argv[]) test_wolfTPM2_NVDeleteKey_BoundaryChecks(); test_wolfTPM2_UnloadHandle_PersistentGuard(); test_TPM2_GetHashDigestSize_AllAlgs(); + #ifdef WOLFTPM_SWTPM + test_TPM2_SwtpmValidateRspSz(); + #endif + #if !defined(NO_FILESYSTEM) && !defined(NO_WRITE_TEMP_FILES) && \ + !defined(WOLFTPM2_NO_WOLFCRYPT) && defined(HAVE_ECC) + test_readKeyBlob_PrivOverflow(); + #endif test_wolfTPM2_KeyBlob(TPM_ALG_RSA); test_wolfTPM2_KeyBlob(TPM_ALG_ECC); #if !defined(WOLFTPM2_NO_WOLFCRYPT) && defined(HAVE_ECC) && \ @@ -5273,6 +5580,7 @@ int unit_tests(int argc, char *argv[]) test_wolfTPM2_Cleanup(); test_wolfTPM2_thread_local_storage(); #ifdef WOLFTPM_SPDM + test_wolfTPM2_SPDM_ValidateRspSz(); test_wolfTPM2_SPDM_Functions(); #endif #endif /* !WOLFTPM2_NO_WRAPPER */ diff --git a/wolftpm/tpm2_spdm.h b/wolftpm/tpm2_spdm.h index 5dfdde878..31c7fcab9 100644 --- a/wolftpm/tpm2_spdm.h +++ b/wolftpm/tpm2_spdm.h @@ -181,6 +181,10 @@ WOLFTPM_API int wolfTPM2_SPDM_SetTisIO( WOLFTPM2_SPDM_CTX* ctx ); +/* Validate a framed response size against caller and I/O buffer sizes */ +WOLFTPM_API int wolfTPM2_SPDM_ValidateRspSz(word32 rspSz, word32 rxSz, + word32 ioBufSz); + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/wolftpm/tpm2_swtpm.h b/wolftpm/tpm2_swtpm.h index 48db51eb0..982c5fbd6 100644 --- a/wolftpm/tpm2_swtpm.h +++ b/wolftpm/tpm2_swtpm.h @@ -47,6 +47,9 @@ /* TPM2 IO for using TPM through a Socket connection */ WOLFTPM_LOCAL int TPM2_SWTPM_SendCommand(TPM2_CTX* ctx, TPM2_Packet* packet); +/* Validate a swtpm-reported response size against the receive buffer size */ +WOLFTPM_API int TPM2_SwtpmValidateRspSz(int packetSize, uint32_t rspSz); + #ifdef WOLFTPM_SWTPM_UART /* Close the persistent UART FD during final TPM context cleanup */ WOLFTPM_LOCAL void TPM2_SwtpmCloseUART(TPM2_CTX* ctx); diff --git a/wolftpm/tpm2_tis.h b/wolftpm/tpm2_tis.h index 42bcdbec9..28c10b418 100644 --- a/wolftpm/tpm2_tis.h +++ b/wolftpm/tpm2_tis.h @@ -52,6 +52,7 @@ WOLFTPM_LOCAL int TPM2_TIS_GetBurstCount(TPM2_CTX* ctx, word16* burstCount); WOLFTPM_LOCAL int TPM2_TIS_SendCommand(TPM2_CTX* ctx, TPM2_Packet* packet); +WOLFTPM_API int TPM2_TIS_ValidateRspSz(int rspSz, int packetSize); WOLFTPM_LOCAL int TPM2_TIS_Ready(TPM2_CTX* ctx); WOLFTPM_LOCAL int TPM2_TIS_WaitForStatus(TPM2_CTX* ctx, byte status, byte status_mask); WOLFTPM_LOCAL int TPM2_TIS_Status(TPM2_CTX* ctx, byte* status);