mirror of https://github.com/wolfSSL/wolfBoot.git
F-8007: wipe TPM advanced-IO staging buffers in TPM2_IoCb()
With WOLFTPM_ADV_IO the TIS layer hands the raw command payload to the HAL callback, so TPM2_IoCb() stages it in stack-local txBuf/rxBuf. Both were left intact on the normal return and on the wait-state error return, keeping a TPM command's plaintext authValue (and the response bytes) in bootloader stack SRAM. Wipe them like TPM2_TIS_Read()/TPM2_TIS_Write() already do for their own staging buffers in the non-advanced-IO path. Adds unit-tpm-advio-zeroize, which drives TPM2_IoCb() through the write, read, payload-error and wait-state-timeout paths with a mock SPI slave and inspects the staging buffers afterwards.pull/851/head
parent
93edc29082
commit
f446a4ad9f
|
|
@ -204,6 +204,7 @@ tools/unit-tests/unit-otp-keystore
|
|||
tools/unit-tests/unit-otp-keystore-gen-zeroize
|
||||
tools/unit-tests/unit-tpm-api-names
|
||||
tools/unit-tests/unit-tpm-nsc-cert
|
||||
tools/unit-tests/unit-tpm-advio-zeroize
|
||||
tools/unit-tests/unit-elf-bss-guard
|
||||
tools/unit-tests/unit-fit-fpga
|
||||
tools/unit-tests/unit-flash-erase-c0
|
||||
|
|
|
|||
|
|
@ -202,6 +202,11 @@ static int TPM2_IoCb(TPM2_CTX* ctx, const uint8_t* txBuf, uint8_t* rxBuf,
|
|||
/* On error make sure SPI is de-asserted */
|
||||
else {
|
||||
spi_xfer(SPI_CS_TPM, NULL, NULL, 0, 0);
|
||||
#ifdef WOLFTPM_ADV_IO
|
||||
/* don't leave the command (may hold an authValue) on the stack */
|
||||
TPM2_ForceZero(txBuf, sizeof(txBuf));
|
||||
TPM2_ForceZero(rxBuf, sizeof(rxBuf));
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
#else /* Send Entire Message - no wait states */
|
||||
|
|
@ -221,6 +226,10 @@ static int TPM2_IoCb(TPM2_CTX* ctx, const uint8_t* txBuf, uint8_t* rxBuf,
|
|||
wolfBoot_print_bin(buf, size);
|
||||
#endif
|
||||
}
|
||||
/* the staging buffers hold the raw command / response, which can carry
|
||||
* a plaintext authValue - wipe them like TPM2_TIS_Read/Write() do */
|
||||
TPM2_ForceZero(txBuf, sizeof(txBuf));
|
||||
TPM2_ForceZero(rxBuf, sizeof(rxBuf));
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ TESTS:=unit-parser unit-fdt unit-extflash unit-string unit-spi-flash unit-aes128
|
|||
TESTS+=unit-tpm-check-rot-auth
|
||||
TESTS+=unit-tpm-api-names
|
||||
TESTS+=unit-tpm-nsc-cert
|
||||
TESTS+=unit-tpm-advio-zeroize
|
||||
TESTS+=unit-pkcs11-nsc-zeroize
|
||||
TESTS+=unit-diagnostics
|
||||
TESTS+=unit-diagnostics-256
|
||||
|
|
@ -324,6 +325,13 @@ unit-tpm-blob: ../../include/target.h unit-tpm-blob.c
|
|||
-DWOLFBOOT_HASH_SHA256 \
|
||||
-ffunction-sections -fdata-sections $(LDFLAGS) -Wl,--gc-sections
|
||||
|
||||
unit-tpm-advio-zeroize: ../../include/target.h unit-tpm-advio-zeroize.c
|
||||
gcc -o $@ $^ $(CFLAGS) -I$(WOLFBOOT_LIB_WOLFTPM) -DWOLFBOOT_TPM \
|
||||
-DWOLFTPM_USER_SETTINGS -DWOLFTPM_ADV_IO \
|
||||
-DWOLFTPM_CHECK_WAIT_STATE -DWOLFBOOT_SIGN_RSA2048 \
|
||||
-DWOLFBOOT_HASH_SHA256 \
|
||||
-ffunction-sections -fdata-sections $(LDFLAGS) -Wl,--gc-sections
|
||||
|
||||
unit-policy-create: ../../include/target.h unit-policy-create.c \
|
||||
$(WOLFBOOT_LIB_WOLFSSL)/wolfcrypt/src/memory.c
|
||||
gcc -o $@ $^ -I../tpm $(CFLAGS) -I$(WOLFBOOT_LIB_WOLFTPM) -DWOLFBOOT_TPM \
|
||||
|
|
|
|||
|
|
@ -0,0 +1,260 @@
|
|||
/* unit-tpm-advio-zeroize.c
|
||||
*
|
||||
* Regression test for the WOLFTPM_ADV_IO variant of TPM2_IoCb() in src/tpm.c
|
||||
* leaving the TPM command/response frame resident in its stack staging
|
||||
* buffers (txBuf/rxBuf) when it returns. With advanced IO the TIS layer in
|
||||
* wolfTPM hands the raw payload straight to the HAL callback, so the wipe
|
||||
* that TPM2_TIS_Read()/TPM2_TIS_Write() perform on their own txBuf/rxBuf
|
||||
* (lib/wolfTPM/src/tpm2_tis.c) is only done here. A TPM command carrying a
|
||||
* plaintext password authorization therefore stays readable in bootloader
|
||||
* stack SRAM after the transfer completes.
|
||||
*
|
||||
* Copyright (C) 2026 wolfSSL Inc.
|
||||
*
|
||||
* This file is part of wolfBoot.
|
||||
*
|
||||
* wolfBoot is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* wolfBoot is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
|
||||
*/
|
||||
|
||||
#include <check.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef SPI_CS_TPM
|
||||
#define SPI_CS_TPM 1
|
||||
#endif
|
||||
|
||||
#include "wolfboot/wolfboot.h"
|
||||
#include "tpm.h"
|
||||
#include "wolftpm/tpm2_tis.h"
|
||||
|
||||
#define ADV_BUF_SZ (MAX_SPI_FRAMESIZE + TPM_TIS_HEADER_SZ)
|
||||
|
||||
/* Plaintext authValue as it would appear inside a TPM2_NV_Write /
|
||||
* TPM2_Load authorization area handed down to the HAL callback. */
|
||||
static const uint8_t test_auth[] = {
|
||||
'u', 'n', 'i', 't', '-', 't', 'p', 'm', '-', 'a', 'u', 't', 'h'
|
||||
};
|
||||
|
||||
static uint8_t* captured_tx;
|
||||
static uint8_t* captured_rx;
|
||||
static int spi_calls;
|
||||
static int spi_fail_payload;
|
||||
static int spi_never_ready;
|
||||
|
||||
/* Snapshots of the (now dead) TPM2_IoCb() frame, taken by the tests with an
|
||||
* inline volatile copy loop so that no intervening call can reuse the stack
|
||||
* before the contents are inspected. */
|
||||
static uint8_t snapshot_tx[ADV_BUF_SZ];
|
||||
static uint8_t snapshot_rx[ADV_BUF_SZ];
|
||||
|
||||
int wolfBoot_printf(const char* fmt, ...)
|
||||
{
|
||||
(void)fmt;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void spi_init(int polarity, int phase)
|
||||
{
|
||||
(void)polarity;
|
||||
(void)phase;
|
||||
}
|
||||
|
||||
void spi_release(void)
|
||||
{
|
||||
}
|
||||
|
||||
/* Minimal TIS-speaking SPI slave: acknowledges the header (LSB of the last
|
||||
* header byte set) and answers a payload read with a recognizable pattern. */
|
||||
int spi_xfer(int cs, const uint8_t* tx, uint8_t* rx, uint32_t sz, int flags)
|
||||
{
|
||||
uint32_t i;
|
||||
|
||||
(void)cs;
|
||||
(void)flags;
|
||||
spi_calls++;
|
||||
|
||||
if (sz == 0) /* de-assert only */
|
||||
return 0;
|
||||
|
||||
if (spi_calls == 1) {
|
||||
captured_tx = (uint8_t*)tx;
|
||||
captured_rx = rx;
|
||||
for (i = 0; i < sz; i++)
|
||||
rx[i] = 0;
|
||||
if (!spi_never_ready)
|
||||
rx[sz - 1] = TPM_TIS_READY_MASK;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (spi_fail_payload)
|
||||
return -1;
|
||||
|
||||
for (i = 0; i < sz; i++)
|
||||
rx[i] = (uint8_t)(0xA0 + (i & 0x0F));
|
||||
return 0;
|
||||
}
|
||||
|
||||
void TPM2_ForceZero(void* mem, word32 len)
|
||||
{
|
||||
volatile uint8_t* p = (volatile uint8_t*)mem;
|
||||
word32 i;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
p[i] = 0;
|
||||
}
|
||||
|
||||
#include "../../src/tpm.c"
|
||||
|
||||
/* Copy the dead frame without calling anything (a memcpy() or a helper
|
||||
* function would push its own frame over the bytes under test). */
|
||||
#define SNAPSHOT_FRAME() \
|
||||
do { \
|
||||
volatile const uint8_t* _t = (volatile const uint8_t*)captured_tx;\
|
||||
volatile const uint8_t* _r = (volatile const uint8_t*)captured_rx;\
|
||||
unsigned _i; \
|
||||
for (_i = 0; _i < ADV_BUF_SZ; _i++) { \
|
||||
snapshot_tx[_i] = _t[_i]; \
|
||||
snapshot_rx[_i] = _r[_i]; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static void assert_no_residue(const uint8_t* snap, const char* which)
|
||||
{
|
||||
unsigned i, j;
|
||||
|
||||
for (i = 0; i + sizeof(test_auth) <= ADV_BUF_SZ; i++) {
|
||||
for (j = 0; j < sizeof(test_auth); j++) {
|
||||
if (snap[i + j] != test_auth[j])
|
||||
break;
|
||||
}
|
||||
ck_assert_msg(j != sizeof(test_auth),
|
||||
"%s still holds the plaintext TPM authValue at offset %u", which,
|
||||
i);
|
||||
}
|
||||
}
|
||||
|
||||
static void setup(void)
|
||||
{
|
||||
captured_tx = NULL;
|
||||
captured_rx = NULL;
|
||||
spi_calls = 0;
|
||||
spi_fail_payload = 0;
|
||||
spi_never_ready = 0;
|
||||
memset(snapshot_tx, 0xFF, sizeof(snapshot_tx));
|
||||
memset(snapshot_rx, 0xFF, sizeof(snapshot_rx));
|
||||
}
|
||||
|
||||
/* Normal return: the command (including its authorization area) was copied
|
||||
* into txBuf and must not survive the call. */
|
||||
START_TEST(test_advio_write_wipes_txbuf)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = TPM2_IoCb(&wolftpm_dev.ctx, 0 /* write */, 0x24, (uint8_t*)test_auth,
|
||||
(word16)sizeof(test_auth), NULL);
|
||||
SNAPSHOT_FRAME();
|
||||
|
||||
ck_assert_int_eq(rc, 0);
|
||||
ck_assert_ptr_ne(captured_tx, NULL);
|
||||
assert_no_residue(snapshot_tx, "txBuf");
|
||||
}
|
||||
END_TEST
|
||||
|
||||
/* Wait-state error return: spi_xfer() fails on the payload transfer, so the
|
||||
* function returns early - the copy of the command is still in txBuf. */
|
||||
START_TEST(test_advio_write_wipes_txbuf_on_error)
|
||||
{
|
||||
int rc;
|
||||
|
||||
spi_fail_payload = 1;
|
||||
rc = TPM2_IoCb(&wolftpm_dev.ctx, 0 /* write */, 0x24, (uint8_t*)test_auth,
|
||||
(word16)sizeof(test_auth), NULL);
|
||||
SNAPSHOT_FRAME();
|
||||
|
||||
ck_assert_int_ne(rc, 0);
|
||||
ck_assert_ptr_ne(captured_tx, NULL);
|
||||
assert_no_residue(snapshot_tx, "txBuf");
|
||||
}
|
||||
END_TEST
|
||||
|
||||
/* Timeout error return: the wait-state loop never sees the ready bit, so
|
||||
* TPM2_IoCb() bails out through the de-assert path. */
|
||||
START_TEST(test_advio_write_wipes_txbuf_on_timeout)
|
||||
{
|
||||
int rc;
|
||||
|
||||
spi_never_ready = 1;
|
||||
rc = TPM2_IoCb(&wolftpm_dev.ctx, 0 /* write */, 0x24, (uint8_t*)test_auth,
|
||||
(word16)sizeof(test_auth), NULL);
|
||||
SNAPSHOT_FRAME();
|
||||
|
||||
ck_assert_int_ne(rc, 0);
|
||||
ck_assert_ptr_ne(captured_tx, NULL);
|
||||
assert_no_residue(snapshot_tx, "txBuf");
|
||||
}
|
||||
END_TEST
|
||||
|
||||
/* Read: the TPM response lands in rxBuf and is copied out to the caller;
|
||||
* the staging copy must not be left behind. */
|
||||
START_TEST(test_advio_read_wipes_rxbuf)
|
||||
{
|
||||
uint8_t out[sizeof(test_auth)];
|
||||
unsigned i;
|
||||
int rc;
|
||||
|
||||
rc = TPM2_IoCb(&wolftpm_dev.ctx, 1 /* read */, 0x24, out,
|
||||
(word16)sizeof(out), NULL);
|
||||
SNAPSHOT_FRAME();
|
||||
|
||||
ck_assert_int_eq(rc, 0);
|
||||
ck_assert_ptr_ne(captured_rx, NULL);
|
||||
/* the response really was delivered to the caller ... */
|
||||
for (i = 0; i < sizeof(out); i++)
|
||||
ck_assert_uint_eq(out[i], (uint8_t)(0xA0 + (i & 0x0F)));
|
||||
/* ... and no copy of it remains in the staging buffer */
|
||||
for (i = 0; i < ADV_BUF_SZ; i++) {
|
||||
ck_assert_msg(snapshot_rx[i] == 0,
|
||||
"rxBuf still holds TPM response byte 0x%02x at offset %u",
|
||||
snapshot_rx[i], i);
|
||||
}
|
||||
}
|
||||
END_TEST
|
||||
|
||||
static Suite *tpm_advio_zeroize_suite(void)
|
||||
{
|
||||
Suite *s = suite_create("tpm_advio_zeroize");
|
||||
TCase *tc = tcase_create("zeroize");
|
||||
tcase_add_checked_fixture(tc, setup, NULL);
|
||||
tcase_add_test(tc, test_advio_write_wipes_txbuf);
|
||||
tcase_add_test(tc, test_advio_write_wipes_txbuf_on_error);
|
||||
tcase_add_test(tc, test_advio_write_wipes_txbuf_on_timeout);
|
||||
tcase_add_test(tc, test_advio_read_wipes_rxbuf);
|
||||
suite_add_tcase(s, tc);
|
||||
return s;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int failed;
|
||||
Suite *s = tpm_advio_zeroize_suite();
|
||||
SRunner *sr = srunner_create(s);
|
||||
srunner_run_all(sr, CK_NORMAL);
|
||||
failed = srunner_ntests_failed(sr);
|
||||
srunner_free(sr);
|
||||
return failed == 0 ? 0 : 1;
|
||||
}
|
||||
Loading…
Reference in New Issue