F-4973: clamp TPM-supplied nvPublic.dataSize before NV read-back in rot.c

The TPM-bus-supplied UINT16 nvPublic.dataSize was assigned to digestSz and
forwarded to wolfTPM2_NVReadAuth as the read byte count with no bound check.
wolfTPM2_NVReadAuthPolicy uses that count as the XMEMCPY length into the
caller's buffer with no separate capacity argument, so a malicious/emulated
TPM (or a pre-existing NV index larger than the hash) reporting dataSize > 64
overflows the 64-byte digest[WC_MAX_DIGEST_SIZE] stack buffer.

Clamp digestSz to sizeof(digest) before the read. Stored values are key-hash
digests (<= WC_MAX_DIGEST_SIZE), so the clamp never truncates valid data.

Add a unit-rot-auth case driving nvPublic.dataSize=1000 through the existing
mocked harness, asserting the requested read count is clamped to the buffer.
pull/791/head
Daniele Lacamera 2026-06-09 10:11:27 +02:00
parent 03f5193dc2
commit eaa6e4201a
2 changed files with 43 additions and 2 deletions

View File

@ -161,6 +161,12 @@ static int TPM2_Boot_SecureROT_Example(TPMI_RH_NV_AUTH authHandle, word32 nvBase
}
if (rc == 0) {
digestSz = nvPublic.dataSize;
/* dataSize is supplied by the TPM over the bus; clamp it to the
* digest buffer so a malicious/emulated TPM (or a pre-existing NV
* index larger than the hash) cannot overflow digest[] during the
* read-back below, which uses digestSz as the copy count. */
if (digestSz > (int)sizeof(digest))
digestSz = (int)sizeof(digest);
/* Read access */
printf("Reading NV 0x%x public key hash\n", nv.handle.hndl);

View File

@ -32,6 +32,8 @@
static uint8_t test_pubkey[32];
static int symmetric_corrupted;
static uint32_t mock_nv_datasize = 32;
static uint32_t mock_nvread_reqsz;
#define TPM2_IoCb NULL
#define XSTRTOL strtol
@ -86,7 +88,7 @@ int wolfTPM2_NVReadPublic(WOLFTPM2_DEV* dev, TPM_HANDLE nvIndex,
(void)dev;
(void)nvIndex;
memset(nvPublic, 0, sizeof(*nvPublic));
nvPublic->dataSize = 32;
nvPublic->dataSize = (UINT16)mock_nv_datasize;
return 0;
}
@ -102,7 +104,14 @@ int wolfTPM2_NVReadAuth(WOLFTPM2_DEV* dev, WOLFTPM2_NV* nv, TPM_HANDLE nvIndex,
memset(&zero_sym, 0, sizeof(zero_sym));
symmetric_corrupted =
memcmp(&nv->handle.symmetric, &zero_sym, sizeof(zero_sym)) != 0;
memset(dataBuf, 0xA5, *dataSz);
/* Record the byte count the caller asked us to copy. The real
* wolfTPM2_NVReadAuth uses this as the XMEMCPY count into dataBuf with no
* separate capacity argument, so the caller must clamp it to its buffer.
* Cap the actual write at WC_MAX_DIGEST_SIZE so this harness never itself
* overflows the caller's digest[] buffer regardless of the requested size. */
mock_nvread_reqsz = *dataSz;
memset(dataBuf, 0xA5,
*dataSz > WC_MAX_DIGEST_SIZE ? WC_MAX_DIGEST_SIZE : *dataSz);
return 0;
}
@ -234,6 +243,31 @@ START_TEST(test_rot_rejects_oversized_auth)
}
END_TEST
START_TEST(test_rot_clamps_nv_datasize)
{
char auth[] = "test-auth";
int rc;
/* Emulate a malicious/emulated TPM (or a pre-existing NV index) whose
* reported dataSize exceeds the 64-byte digest[] read buffer. The value
* must be clamped before being used as the read byte count, otherwise
* wolfTPM2_NVReadAuth overflows digest[WC_MAX_DIGEST_SIZE]. */
memset(test_pubkey, 0x11, sizeof(test_pubkey));
symmetric_corrupted = 0;
mock_nvread_reqsz = 0;
mock_nv_datasize = 1000;
rc = TPM2_Boot_SecureROT_Example(TPM_RH_PLATFORM,
WOLFBOOT_TPM_KEYSTORE_NV_BASE, WC_HASH_TYPE_SHA256, 0, 0, auth,
(int)strlen(auth));
mock_nv_datasize = 32; /* restore default for other tests */
ck_assert_int_eq(rc, 0);
ck_assert_int_le(mock_nvread_reqsz, (uint32_t)WC_MAX_DIGEST_SIZE);
}
END_TEST
static Suite* rot_auth_suite(void)
{
Suite* s;
@ -242,6 +276,7 @@ static Suite* rot_auth_suite(void)
s = suite_create("rot_auth");
tc = tcase_create("auth_validation");
tcase_add_test(tc, test_rot_rejects_oversized_auth);
tcase_add_test(tc, test_rot_clamps_nv_datasize);
suite_add_tcase(s, tc);
return s;
}