From 23a328ff87f09af6395f00f556b7d5cf5246fe21 Mon Sep 17 00:00:00 2001 From: David Garske Date: Tue, 25 Aug 2026 11:15:20 -0700 Subject: [PATCH] Fix ST33 generation 1 manifest size and refuse oversized commands (ZD 22193) --- examples/firmware/README.md | 47 +++---- examples/firmware/include.am | 6 + examples/firmware/st33_blob0.c | 97 +++++++++++++++ examples/firmware/st33_blob0.h | 78 ++++++++++++ examples/firmware/st33_fw_update.c | 114 +++++++++-------- src/tpm2.c | 13 ++ src/tpm2_packet.c | 3 + src/tpm2_wrap.c | 85 +++++++------ tests/include.am | 3 +- tests/unit_tests.c | 192 ++++++++++++++++++++++++++++- 10 files changed, 517 insertions(+), 121 deletions(-) create mode 100644 examples/firmware/st33_blob0.c create mode 100644 examples/firmware/st33_blob0.h diff --git a/examples/firmware/README.md b/examples/firmware/README.md index 2c861caed..f56852fa0 100644 --- a/examples/firmware/README.md +++ b/examples/firmware/README.md @@ -2,7 +2,7 @@ Currently wolfTPM supports firmware update capability for: - Infineon SLB9672 (SPI) and SLB9673 (I2C) TPM 2.0 modules. Infineon has open sourced their firmware update. -- STMicroelectronics ST33KTPM TPM 2.0 modules. Support includes both Generation 1 firmware versions (< 512, without LMS signature) and Generation 2 firmware versions (>= 512, with LMS signature requirement). +- STMicroelectronics ST33KTPM TPM 2.0 modules. Support covers generation 1 firmware (RSA signed manifest), generation 9 firmware below 512 (ECDSA signed manifest) and generation 9 firmware at 512 and above (LMS signature requirement). ## Infineon Firmware @@ -117,15 +117,19 @@ KeyGroupId 0x7, FwCounter 1253 (254 same) ST33KTPM firmware update automatically detects the required format based on TPM firmware version: -- **Legacy firmware (< 512, e.g., 9.257)**: Non-LMS format - - Manifest size: 177 bytes - - Generation 1 firmware (ECC-only) +The manifest (blob0) is a 33 byte fixed header followed by the firmware digest and the signature over it, so its size follows the algorithms that generation signs with: -- **Modern firmware (>= 512, e.g., 9.512)**: LMS format +- **Generation 1 (major version 1, e.g., 1.257 or 1.771)**: Non-LMS format + - Manifest size: 321 bytes (SHA-256 digest, RSAPSS-2048 signature) + - Always non-LMS, no matter how high the minor version goes + +- **Generation 9 below 512 (e.g., 9.257)**: Non-LMS format + - Manifest size: 177 bytes (SHA-384 digest, ECDSA P-384 signature) + +- **Generation 9 at 512 and above (e.g., 9.512)**: LMS format - Manifest size: 2697 bytes (includes embedded LMS signature) - - Generation 2 firmware (LMS mandatory) -The firmware version is automatically detected from `fwVerMinor` in TPM capabilities. The correct manifest size is determined automatically - no manual format selection is needed. +The LMS requirement is a generation 9 rule, so both `fwVerMajor` and `fwVerMinor` from TPM capabilities are consulted. The example confirms its choice against the file itself: everything after blob0 is a chain of `[type][length]` records that ends exactly at end of file, and only the correct manifest size lands on the final byte. No manual format selection is needed. ### Updating the firmware @@ -145,9 +149,10 @@ Policy options (caller-supplied authorization): --policyor provision+satisfy a PolicyOR (multi-branch) --sha256|--sha384|--sha512 policy hash (default SHA-256) -Firmware format is auto-detected from TPM firmware version: - - Firmware < 512: Non-LMS format (177 byte manifest) - - Firmware >= 512: LMS format (2697 byte manifest with embedded signature) +Firmware format is auto-detected from TPM firmware version and the file: + - Generation 1 (e.g. 1.771): Non-LMS format (321 byte manifest) + - Generation 9 below 512: Non-LMS format (177 byte manifest) + - Generation 9 at 512 and above: LMS format (2697 byte manifest) # Run without arguments to display the current firmware information ./st33_fw_update @@ -156,8 +161,8 @@ TPM2: Caps 0x30000415, Did 0x0003, Vid 0x104a, Rid 0x 1 TPM2_Startup pass Mfg STM (2), Vendor ST33KTPM2X, Fw 9.257 (0x0) Firmware version details: Major=9, Minor=257, Vendor=0x0 -Hardware: ST33K (legacy firmware, Generation 1) -Firmware update: Non-LMS format required +Hardware: ST33K (generation 9 firmware below 512) +Firmware update: Non-LMS format required (177 byte manifest) # Run with firmware file (format auto-detected from TPM version) ./st33_fw_update TPM_ST33KTPM2X_00090200_V1.fi @@ -167,9 +172,9 @@ TPM2: Caps 0x30000415, Did 0x0003, Vid 0x104a, Rid 0x 1 TPM2_Startup pass Mfg STM (2), Vendor ST33KTPM2X, Fw 9.257 (0x0) Firmware version details: Major=9, Minor=257, Vendor=0x0 -Hardware: ST33K (legacy firmware, Generation 1) -Firmware update: Non-LMS format required - Format: Non-LMS (from TPM firmware version) +Hardware: ST33K (generation 9 firmware below 512) +Firmware update: Non-LMS format required (177 byte manifest) + Format: Non-LMS (blob0 177 bytes, verified against the block chain) Firmware Update: Total file size: 364290 bytes Manifest (blob0): 177 bytes @@ -178,7 +183,7 @@ Firmware Update: Firmware update completed successfully. Please reset or power cycle the TPM. -# Example with LMS firmware (Generation 2 TPM, firmware >= 512) +# Example with LMS firmware (generation 9 TPM, firmware at 512 and above) ./st33_fw_update ST33KTPM2X_FAC_00090200_V2.fi ST33 Firmware Update Tool Firmware File: ST33KTPM2X_FAC_00090200_V2.fi @@ -186,9 +191,9 @@ TPM2: Caps 0x30000415, Did 0x0003, Vid 0x104a, Rid 0x 3 TPM2_Startup pass Mfg STM (2), Vendor ST33KTPM2X, Fw 9.512 (0x0) Firmware version details: Major=9, Minor=512, Vendor=0x0 -Hardware: ST33K (modern firmware, Generation 2) -Firmware update: LMS format required - Format: LMS (from TPM firmware version) +Hardware: ST33K (generation 9 firmware at 512 and above) +Firmware update: LMS format required (2697 byte manifest) + Format: LMS (blob0 2697 bytes, verified against the block chain) Firmware Update: Total file size: 360092 bytes Manifest (blob0): 2697 bytes @@ -204,8 +209,8 @@ TPM2: Caps 0x30000415, Did 0x0003, Vid 0x104a, Rid 0x 1 TPM2_Startup pass Mfg STM (2), Vendor ST33KTPM2X, Fw 9.257 (0x0) Firmware version details: Major=9, Minor=257, Vendor=0x0 -Hardware: ST33K (legacy firmware, Generation 1) -Firmware update: Non-LMS format required +Hardware: ST33K (generation 9 firmware below 512) +Firmware update: Non-LMS format required (177 byte manifest) Firmware Update Abandon: Success: Please reset or power cycle TPM ``` diff --git a/examples/firmware/include.am b/examples/firmware/include.am index 1c01f4373..015d357d4 100644 --- a/examples/firmware/include.am +++ b/examples/firmware/include.am @@ -10,11 +10,14 @@ EXTRA_DIST += examples/firmware/ifx_fw_extract.c # Shared caller-supplied policy authorization helpers for the firmware examples EXTRA_DIST += examples/firmware/firmware_policy.c EXTRA_DIST += examples/firmware/firmware_policy.h +EXTRA_DIST += examples/firmware/st33_blob0.c +EXTRA_DIST += examples/firmware/st33_blob0.h if BUILD_EXAMPLES if BUILD_FIRMWARE noinst_HEADERS += examples/firmware/firmware_policy.h +noinst_HEADERS += examples/firmware/st33_blob0.h if BUILD_INFINEON noinst_PROGRAMS += examples/firmware/ifx_fw_update @@ -30,6 +33,7 @@ if BUILD_ST33 noinst_PROGRAMS += examples/firmware/st33_fw_update examples_firmware_st33_fw_update_SOURCES = examples/firmware/st33_fw_update.c \ examples/firmware/firmware_policy.c \ + examples/firmware/st33_blob0.c \ examples/tpm_test_keys.c examples_firmware_st33_fw_update_LDADD = src/libwolftpm.la $(LIB_STATIC_ADD) examples_firmware_st33_fw_update_DEPENDENCIES = src/libwolftpm.la @@ -41,6 +45,8 @@ endif example_firmwaredir = $(exampledir)/firmware dist_example_firmware_DATA = examples/firmware/firmware_policy.c \ examples/firmware/firmware_policy.h \ + examples/firmware/st33_blob0.c \ + examples/firmware/st33_blob0.h \ examples/firmware/ifx_fw_update.c \ examples/firmware/st33_fw_update.c diff --git a/examples/firmware/st33_blob0.c b/examples/firmware/st33_blob0.c new file mode 100644 index 000000000..0f59c7395 --- /dev/null +++ b/examples/firmware/st33_blob0.c @@ -0,0 +1,97 @@ +/* st33_blob0.c + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM 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. + * + * wolfTPM 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 + */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +const size_t st33_blob0_sizes[ST33_BLOB0_SIZE_CNT] = { + ST33_BLOB0_SIZE_NON_LMS_RSA, + ST33_BLOB0_SIZE_NON_LMS, + ST33_BLOB0_SIZE_LMS +}; + +size_t st33_expected_blob0(word32 fwVerMajor, word32 fwVerMinor) +{ + if (fwVerMajor < ST33_BLOB0_GENERATION_LMS_CAPABLE) { + return ST33_BLOB0_SIZE_NON_LMS_RSA; + } + if (fwVerMinor < ST33_BLOB0_VERSION_LMS_REQUIRED) { + return ST33_BLOB0_SIZE_NON_LMS; + } + return ST33_BLOB0_SIZE_LMS; +} + +size_t st33_blob0_candidates(word32 fwVerMajor, word32 fwVerMinor, + int haveCaps, size_t* cand) +{ + size_t idx; + size_t candCnt = 0; + + if (cand == NULL) { + return 0; + } + if (haveCaps) { + cand[candCnt++] = st33_expected_blob0(fwVerMajor, fwVerMinor); + } + for (idx = 0; idx < ST33_BLOB0_SIZE_CNT; idx++) { + if (candCnt == 0 || cand[0] != st33_blob0_sizes[idx]) { + cand[candCnt++] = st33_blob0_sizes[idx]; + } + } + return candCnt; +} + +size_t st33_detect_blob0(const byte* buf, size_t bufSz, const size_t* cand, + size_t candCnt) +{ + size_t i, off, len; + + if (buf == NULL || cand == NULL) { + return 0; + } + for (i = 0; i < candCnt; i++) { + if (bufSz <= cand[i]) { + continue; + } + off = cand[i]; + while (off + 3 <= bufSz) { + if (buf[off] == 0) { + break; /* end marker, not a record */ + } + len = ((size_t)buf[off + 1] << 8) | buf[off + 2]; + /* Reject rather than walk past the end: a payload that does not + * fit means this candidate is not where blob0 ends. Checked as a + * subtraction on the remaining bytes so off + 3 + len can never + * be formed out of range. */ + if (len == 0 || len > bufSz - off - 3) { + break; + } + off += 3 + len; + } + if (off == bufSz) { + return cand[i]; + } + } + return 0; +} diff --git a/examples/firmware/st33_blob0.h b/examples/firmware/st33_blob0.h new file mode 100644 index 000000000..efb33cbe6 --- /dev/null +++ b/examples/firmware/st33_blob0.h @@ -0,0 +1,78 @@ +/* st33_blob0.h + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM 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. + * + * wolfTPM 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 + */ + +/* ST33 firmware image (.fi) layout helpers, split out of st33_fw_update.c so + * the block-chain parser that decides where the manifest ends can be unit + * tested. Pure buffer logic - no TPM calls, no wolfCrypt. */ + +#ifndef WOLFTPM_EXAMPLE_ST33_BLOB0_H +#define WOLFTPM_EXAMPLE_ST33_BLOB0_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* The manifest (blob0) is a 33 byte fixed header followed by the firmware + * digest and the signature over it, so its size follows the algorithms that + * generation signs with. */ +/* gen 1: SHA-256 + RSAPSS-2048 */ +#define ST33_BLOB0_SIZE_NON_LMS_RSA 321 +/* gen 9 below 512: SHA-384 + ECDSA P-384 */ +#define ST33_BLOB0_SIZE_NON_LMS 177 +/* gen 9 at 512 and above: embedded LMS signature */ +#define ST33_BLOB0_SIZE_LMS 2697 + +/* LMS is a generation 9 rule. Generation 1 parts are always non-LMS no matter + * how high the minor version goes (e.g. 1.771). Mirrors + * ST33_FW_GENERATION_LMS_CAPABLE / ST33_FW_VERSION_LMS_REQUIRED in + * src/tpm2_wrap.c, which is the authority the library validates against. */ +#define ST33_BLOB0_GENERATION_LMS_CAPABLE 9 +#define ST33_BLOB0_VERSION_LMS_REQUIRED 512 + +#define ST33_BLOB0_SIZE_CNT 3 +extern const size_t st33_blob0_sizes[ST33_BLOB0_SIZE_CNT]; + +/* Manifest size the running firmware expects for its next update. Takes the + * version fields rather than WOLFTPM2_CAPS so it stays free of the wrapper. */ +size_t st33_expected_blob0(word32 fwVerMajor, word32 fwVerMinor); + +/* Fill cand (at least ST33_BLOB0_SIZE_CNT entries) with every known manifest + * size. When haveCaps is set the size the firmware version implies is placed + * first, so it wins if more than one candidate parses. Returns the count. */ +size_t st33_blob0_candidates(word32 fwVerMajor, word32 fwVerMinor, + int haveCaps, size_t* cand); + +/* Confirm a candidate blob0 size by walking the block chain that follows it. + * Every byte after blob0 is a [type:1][len:2 big-endian][payload] record and + * the chain ends exactly at end of file, so only the correct size lands on + * the final byte. Candidates are tried in the supplied order. Returns the + * blob0 size, or 0 when the file does not parse with any candidate. */ +size_t st33_detect_blob0(const byte* buf, size_t bufSz, const size_t* cand, + size_t candCnt); + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFTPM_EXAMPLE_ST33_BLOB0_H */ diff --git a/examples/firmware/st33_fw_update.c b/examples/firmware/st33_fw_update.c index 76eff46f6..181ab4aad 100644 --- a/examples/firmware/st33_fw_update.c +++ b/examples/firmware/st33_fw_update.c @@ -35,6 +35,7 @@ !defined(WOLFTPM2_NO_WOLFCRYPT) #include +#include #include #include @@ -50,10 +51,6 @@ /* --- BEGIN ST33 TPM2.0 Firmware Update tool -- */ /******************************************************************************/ -/* Manifest sizes per ST33 firmware format */ -#define ST33_BLOB0_SIZE_NON_LMS 177 /* Non-LMS manifest size */ -#define ST33_BLOB0_SIZE_LMS 2697 /* LMS manifest size (includes embedded signature) */ - static void usage(void) { printf("ST33 Firmware Update Usage:\n"); @@ -66,8 +63,14 @@ static void usage(void) printf("\t--policy provision+satisfy a PolicyCommandCode\n"); printf("\t--policyor provision+satisfy a PolicyOR (multi-branch)\n"); printf("\t--sha256|--sha384|--sha512 policy hash (default SHA-256)\n"); - printf("\nFirmware format is auto-detected from the TPM firmware version.\n"); - printf("Just provide the correct .fi file for your TPM and it will be handled automatically.\n"); + printf("\nFirmware format is auto-detected from TPM firmware version " + "and the file:\n"); + printf(" - Generation 1 (e.g. 1.771): Non-LMS format " + "(321 byte manifest)\n"); + printf(" - Generation 9 below 512: Non-LMS format " + "(177 byte manifest)\n"); + printf(" - Generation 9 at 512 and above: LMS format " + "(2697 byte manifest)\n"); } typedef struct { @@ -174,20 +177,27 @@ static int TPM2_ST33_FwData_Cb(uint8_t* data, uint32_t data_req_sz, return data_req_sz; } -static void TPM2_ST33_PrintInfo(WOLFTPM2_CAPS* caps) +static void TPM2_ST33_PrintInfo(const WOLFTPM2_CAPS* caps) { printf("Mfg %s (%d), Vendor %s, Fw %u.%u (0x%x)\n", caps->mfgStr, caps->mfg, caps->vendorStr, caps->fwVerMajor, caps->fwVerMinor, caps->fwVerVendor); printf("Firmware version details: Major=%u, Minor=%u, Vendor=0x%x\n", caps->fwVerMajor, caps->fwVerMinor, caps->fwVerVendor); - if (caps->fwVerMinor < 512) { - printf("Hardware: ST33K (legacy firmware, Generation 1)\n"); - printf("Firmware update: Non-LMS format required\n"); + if (caps->fwVerMajor < ST33_BLOB0_GENERATION_LMS_CAPABLE) { + printf("Hardware: ST33K (generation 1 firmware)\n"); + printf("Firmware update: Non-LMS format required " + "(%d byte manifest)\n", ST33_BLOB0_SIZE_NON_LMS_RSA); + } + else if (caps->fwVerMinor < ST33_BLOB0_VERSION_LMS_REQUIRED) { + printf("Hardware: ST33K (generation 9 firmware below 512)\n"); + printf("Firmware update: Non-LMS format required " + "(%d byte manifest)\n", ST33_BLOB0_SIZE_NON_LMS); } else { - printf("Hardware: ST33K (modern firmware, Generation 2)\n"); - printf("Firmware update: LMS format required\n"); + printf("Hardware: ST33K (generation 9 firmware at 512 and above)\n"); + printf("Firmware update: LMS format required " + "(%d byte manifest)\n", ST33_BLOB0_SIZE_LMS); } } @@ -203,6 +213,8 @@ int TPM2_ST33_Firmware_Update(void* userCtx, int argc, char *argv[]) fw_info_t fwinfo; int abandon = 0; size_t blob0_size; + size_t cand[ST33_BLOB0_SIZE_CNT]; + size_t candCnt; int i; #ifdef WOLFTPM_HAVE_FW_POLICY int policytest = 0; @@ -372,48 +384,44 @@ int TPM2_ST33_Firmware_Update(void* userCtx, int argc, char *argv[]) } load_firmware: - /* Determine blob0 (manifest) size based on firmware version. - * In upgrade mode (caps not available), auto-detect from file size. */ - if (fwinfo.in_upgrade_mode) { - /* In upgrade mode, we don't have caps. Load file first to detect format. */ - rc = loadFile(fi_file, &fwinfo.fi_buf, &fwinfo.fi_bufSz); - if (rc != 0) { - printf("Failed to load firmware file: %s\n", fi_file); - goto exit; - } - /* Auto-detect format from file size: LMS files are larger due to - * 2697 byte manifest vs 177 byte manifest */ - if (fwinfo.fi_bufSz > ST33_BLOB0_SIZE_LMS + 1000) { - /* File large enough to potentially be LMS format. - * Check if blob header at LMS offset looks valid. */ - if (fwinfo.fi_buf[ST33_BLOB0_SIZE_LMS] != 0 && - fwinfo.fi_buf[ST33_BLOB0_SIZE_LMS] != 0xFF) { - blob0_size = ST33_BLOB0_SIZE_LMS; - printf("\tFormat: LMS (auto-detected from file)\n"); - } - else { - blob0_size = ST33_BLOB0_SIZE_NON_LMS; - printf("\tFormat: Non-LMS (auto-detected from file)\n"); - } - } - else { - blob0_size = ST33_BLOB0_SIZE_NON_LMS; - printf("\tFormat: Non-LMS (auto-detected from file)\n"); - } + /* Load the complete .fi file, then determine the blob0 (manifest) size. + * In upgrade mode caps are unavailable, so no size is preferred and the + * block chain alone decides. */ + rc = loadFile(fi_file, &fwinfo.fi_buf, &fwinfo.fi_bufSz); + if (rc != 0) { + printf("Failed to load firmware file: %s\n", fi_file); + goto exit; } - else { - /* Normal mode: determine format from firmware version */ - blob0_size = (caps.fwVerMinor >= 512) ? - ST33_BLOB0_SIZE_LMS : ST33_BLOB0_SIZE_NON_LMS; - printf("\tFormat: %s (from TPM firmware version)\n", - (caps.fwVerMinor >= 512) ? "LMS" : "Non-LMS"); - - /* Load the complete .fi file */ - rc = loadFile(fi_file, &fwinfo.fi_buf, &fwinfo.fi_bufSz); - if (rc != 0) { - printf("Failed to load firmware file: %s\n", fi_file); - goto exit; - } + + candCnt = st33_blob0_candidates(caps.fwVerMajor, caps.fwVerMinor, + !fwinfo.in_upgrade_mode, cand); + + blob0_size = st33_detect_blob0(fwinfo.fi_buf, fwinfo.fi_bufSz, cand, + candCnt); + if (blob0_size == 0) { + printf("Error: could not determine the manifest (blob0) size of %s\n", + fi_file); + printf(" The %zu byte file does not parse as an ST33 firmware image " + "with a\n %d, %d or %d byte manifest.\n", fwinfo.fi_bufSz, + ST33_BLOB0_SIZE_NON_LMS_RSA, ST33_BLOB0_SIZE_NON_LMS, + ST33_BLOB0_SIZE_LMS); + rc = BAD_FUNC_ARG; + goto exit; + } + printf("\tFormat: %s (blob0 %zu bytes, verified against the block " + "chain)\n", + (blob0_size == ST33_BLOB0_SIZE_LMS) ? "LMS" : "Non-LMS", blob0_size); + + /* The file parsed, but at a size this TPM will not accept. Say so here: + * the library rejects it too, but only with a DEBUG_WOLFTPM diagnostic, + * so on a release build the operator would see a bare error code. */ + if (!fwinfo.in_upgrade_mode && blob0_size != cand[0]) { + printf("Error: %s is for a different ST33 generation.\n", fi_file); + printf(" Its manifest is %zu bytes, but firmware %u.%u running on " + "this TPM\n expects %zu bytes. Use the .fi file for this part.\n", + blob0_size, caps.fwVerMajor, caps.fwVerMinor, cand[0]); + rc = BAD_FUNC_ARG; + goto exit; } /* Validate file size */ diff --git a/src/tpm2.c b/src/tpm2.c index 552a6cdce..ea8fdca3d 100644 --- a/src/tpm2.c +++ b/src/tpm2.c @@ -521,6 +521,19 @@ static TPM_RC TPM2_DispatchCommand(TPM2_CTX* ctx, TPM2_Packet* packet) { TPM_RC rc; + /* A command that did not fit the packet buffer would otherwise go out + * silently truncated, since the marshalling helpers drop what does not + * fit and TPM2_Packet_Finalize then stamps the short length. The buffer + * is ctx->cmdBuf[XFER_MAX_SIZE], the larger of MAX_COMMAND_SIZE and + * MAX_RESPONSE_SIZE, which --enable-smallstack lowers to 1024 and 1350 + * respectively, so this is reachable on a supported build. */ + if (packet->overflow) { + #ifdef DEBUG_WOLFTPM + printf("Command exceeds the %d byte packet buffer\n", packet->size); + #endif + return (TPM_RC)BUFFER_E; + } + #ifdef WOLFTPM_SPDM rc = TPM2_SPDM_SendCommand(ctx, packet); if (rc >= 0) diff --git a/src/tpm2_packet.c b/src/tpm2_packet.c index d042a8b4c..5a2da3812 100644 --- a/src/tpm2_packet.c +++ b/src/tpm2_packet.c @@ -1738,6 +1738,9 @@ int TPM2_Packet_RetryRestore(TPM_RC rc, int* retries, TPM2_Packet* packet, * restore the clobbered header and the buffer size for an identical resend */ XMEMCPY(packet->buf, cmdHdr, TPM2_HEADER_SIZE); packet->size = origSize; + /* The command body is intact again, so any overflow flagged while parsing + * the truncated retry response does not belong to the resend */ + packet->overflow = 0; return 1; } #endif /* !WOLFTPM_NO_RETRY */ diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index b79bcf349..2f402c37d 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -11949,15 +11949,34 @@ int wolfTPM2_FirmwareUpgradeCancel(WOLFTPM2_DEV* dev) /* Maximum size of firmware chunks for ST33 */ #define ST33_FW_MAX_CHUNK_SZ 2048 /* Must be large enough for firmware blobs */ -/* ST33 firmware version threshold for LMS requirement: - * < 512: Non-LMS format required (legacy, e.g., 9.257) - * >= 512: LMS format required (modern, e.g., 9.512) */ +/* ST33 firmware version threshold for LMS requirement. LMS is a generation 9 + * feature: on those parts a minor version >= 512 requires the LMS format + * (e.g. 9.512), below that the ECDSA format (e.g. 9.257). Generation 1 parts + * are always non-LMS no matter how high the minor version goes (e.g. 1.771). */ +#define ST33_FW_GENERATION_LMS_CAPABLE 9 #define ST33_FW_VERSION_LMS_REQUIRED 512 -/* ST33 manifest (blob0) sizes determine firmware format. - * The manifest size is used for auto-detection of LMS vs non-LMS format. */ -#define ST33_MANIFEST_SIZE_NON_LMS 177 /* Non-LMS manifest size */ -#define ST33_MANIFEST_SIZE_LMS 2697 /* LMS manifest size (includes embedded signature) */ +/* ST33 manifest (blob0) sizes determine firmware format. The manifest is a + * 33 byte fixed header followed by the firmware digest and the signature over + * it, so its size follows the algorithms that generation signs with. */ +/* gen 1: SHA-256 + RSAPSS-2048 */ +#define ST33_MANIFEST_SIZE_NON_LMS_RSA 321 +/* gen 9 below 512: SHA-384 + ECDSA P-384 */ +#define ST33_MANIFEST_SIZE_NON_LMS 177 +/* gen 9 at 512 and above: embedded LMS signature */ +#define ST33_MANIFEST_SIZE_LMS 2697 + +/* Manifest size the running firmware expects for its next update */ +static uint32_t tpm2_st33_expected_manifest_sz(const WOLFTPM2_CAPS* caps) +{ + if (caps->fwVerMajor < ST33_FW_GENERATION_LMS_CAPABLE) { + return ST33_MANIFEST_SIZE_NON_LMS_RSA; + } + if (caps->fwVerMinor < ST33_FW_VERSION_LMS_REQUIRED) { + return ST33_MANIFEST_SIZE_NON_LMS; + } + return ST33_MANIFEST_SIZE_LMS; +} /* ST33 uses password auth (TPM_RS_PW) for firmware update, not policy */ @@ -12139,10 +12158,11 @@ static int tpm2_st33_firmware_data(WOLFTPM2_DEV* dev, } -/* Main ST33 firmware upgrade function with auto-detection from manifest size. - * The manifest size determines whether LMS format is used: - * - 177 bytes: Non-LMS format (legacy firmware < 512) - * - 2697 bytes: LMS format (modern firmware >= 512, LMS signature embedded) +/* Main ST33 firmware upgrade function. The manifest size selects the format + * and must match what the running firmware generation expects: + * - 321 bytes: Non-LMS, generation 1 (e.g. 1.257, 1.771) + * - 177 bytes: Non-LMS, generation 9 below 512 (e.g. 9.257) + * - 2697 bytes: LMS, generation 9 at 512 and above */ static int tpm2_st33_firmware_upgrade_hash(WOLFTPM2_DEV* dev, TPM_ALG_ID hashAlg, uint8_t* manifest_hash, uint32_t manifest_hash_sz, @@ -12152,6 +12172,7 @@ static int tpm2_st33_firmware_upgrade_hash(WOLFTPM2_DEV* dev, TPM_ALG_ID hashAlg int rc; WOLFTPM2_CAPS caps; int is_lms; + uint32_t expected_sz; /* ST33 sends full manifest directly, not hash */ (void)hashAlg; @@ -12162,13 +12183,16 @@ static int tpm2_st33_firmware_upgrade_hash(WOLFTPM2_DEV* dev, TPM_ALG_ID hashAlg if (manifest_sz == ST33_MANIFEST_SIZE_LMS) { is_lms = 1; } - else if (manifest_sz == ST33_MANIFEST_SIZE_NON_LMS) { + else if (manifest_sz == ST33_MANIFEST_SIZE_NON_LMS || + manifest_sz == ST33_MANIFEST_SIZE_NON_LMS_RSA) { is_lms = 0; } else { #ifdef DEBUG_WOLFTPM - printf("ST33 Error: Invalid manifest size %u (expected %d for non-LMS or %d for LMS)\n", - manifest_sz, ST33_MANIFEST_SIZE_NON_LMS, ST33_MANIFEST_SIZE_LMS); + printf("ST33 Error: Invalid manifest size %u (expected %d or %d " + "for non-LMS or %d for LMS)\n", manifest_sz, + ST33_MANIFEST_SIZE_NON_LMS_RSA, ST33_MANIFEST_SIZE_NON_LMS, + ST33_MANIFEST_SIZE_LMS); #endif return BAD_FUNC_ARG; } @@ -12190,34 +12214,15 @@ static int tpm2_st33_firmware_upgrade_hash(WOLFTPM2_DEV* dev, TPM_ALG_ID hashAlg manifest_sz, is_lms ? "LMS" : "non-LMS"); #endif - /* Validate manifest format matches firmware version requirement */ - if (caps.fwVerMinor < ST33_FW_VERSION_LMS_REQUIRED) { - /* Legacy firmware (< 512): non-LMS only */ - if (is_lms) { - #ifdef DEBUG_WOLFTPM - printf("ST33 Error: LMS manifest provided but firmware version %u < %d requires non-LMS\n", - caps.fwVerMinor, ST33_FW_VERSION_LMS_REQUIRED); - #endif - return BAD_FUNC_ARG; - } + /* Validate the manifest matches what this firmware generation expects */ + expected_sz = tpm2_st33_expected_manifest_sz(&caps); + if (manifest_sz != expected_sz) { #ifdef DEBUG_WOLFTPM - printf("ST33 Using non-LMS path (fwVerMinor < %d)\n", - ST33_FW_VERSION_LMS_REQUIRED); - #endif - } - else { - /* Modern firmware (>= 512): LMS required */ - if (!is_lms) { - #ifdef DEBUG_WOLFTPM - printf("ST33 Error: Non-LMS manifest provided but firmware version %u >= %d requires LMS\n", - caps.fwVerMinor, ST33_FW_VERSION_LMS_REQUIRED); - #endif - return BAD_FUNC_ARG; - } - #ifdef DEBUG_WOLFTPM - printf("ST33 Using LMS path (fwVerMinor >= %d, LMS required)\n", - ST33_FW_VERSION_LMS_REQUIRED); + printf("ST33 Error: manifest size %u does not match the %u bytes " + "firmware %u.%u expects\n", manifest_sz, expected_sz, + caps.fwVerMajor, caps.fwVerMinor); #endif + return BAD_FUNC_ARG; } /* Send manifest - the common function handles both LMS and non-LMS */ diff --git a/tests/include.am b/tests/include.am index 383aa6c06..05b580062 100644 --- a/tests/include.am +++ b/tests/include.am @@ -11,7 +11,8 @@ check_PROGRAMS += tests/unit.test endif noinst_PROGRAMS += tests/unit.test tests_unit_test_SOURCES = tests/unit_tests.c \ - examples/tpm_test_keys.c + examples/tpm_test_keys.c \ + examples/firmware/st33_blob0.c tests_unit_test_CFLAGS = $(AM_CFLAGS) tests_unit_test_LDADD = src/libwolftpm.la $(LIB_STATIC_ADD) tests_unit_test_DEPENDENCIES = src/libwolftpm.la diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 826d11c6c..274594258 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -40,6 +40,7 @@ #include #include #include +#include #include #if defined(__linux__) || defined(__APPLE__) || defined(__unix__) @@ -286,7 +287,7 @@ static void test_wolfTPM2_ST33_FirmwareUpgrade(void) WOLFTPM2_DEV dev; WOLFTPM2_CAPS caps; #if !defined(WOLFTPM2_NO_WOLFCRYPT) && defined(WOLFSSL_SHA384) - /* Invalid manifest size (not 177 or 2697) for testing auto-detection */ + /* Invalid manifest size (not 321, 177 or 2697) for auto-detection */ uint8_t dummy_manifest[10] = {0}; #endif @@ -305,7 +306,8 @@ static void test_wolfTPM2_ST33_FirmwareUpgrade(void) if (caps.mfg == TPM_MFG_STM) { printf("ST33 TPM - Firmware: %u.%u (0x%x), Format: %s\n", caps.fwVerMajor, caps.fwVerMinor, caps.fwVerVendor, - (caps.fwVerMinor >= 512) ? "LMS" : "non-LMS"); + (caps.fwVerMajor >= 9 && caps.fwVerMinor >= 512) ? + "LMS" : "non-LMS"); } #endif @@ -379,11 +381,11 @@ static void test_wolfTPM2_ST33_FirmwareUpgrade(void) AssertIntNE(rc, 0); /* Test ST33-specific manifest size validation if we have an ST33 TPM. - * Invalid manifest size (not 177 or 2697) should return BAD_FUNC_ARG. */ + * The manifest must be exactly 321 (generation 1, RSA signed), 177 + * (generation 9 below 512, ECDSA signed) or 2697 (LMS) bytes, and must + * also match the generation the TPM is running. Any other size is + * rejected before the size is compared against the running firmware. */ if (caps.mfg == TPM_MFG_STM) { - /* wolfTPM2_FirmwareUpgradeHash - invalid manifest size (10 bytes). - * Should fail with BAD_FUNC_ARG because manifest_sz must be - * exactly 177 (non-LMS) or 2697 (LMS). */ rc = wolfTPM2_FirmwareUpgradeHash(&dev, TPM_ALG_SHA384, NULL, 0, dummy_manifest, sizeof(dummy_manifest), NULL, NULL); AssertIntEQ(rc, BAD_FUNC_ARG); @@ -4124,6 +4126,179 @@ static void test_TPM2_Packet_RetryRestore(void) } #endif /* !WOLFTPM_NO_RETRY */ +#if defined(WOLFTPM_FIRMWARE_UPGRADE) && \ + (defined(WOLFTPM_ST33) || defined(WOLFTPM_AUTODETECT)) +/* Counts transport calls so the test can prove nothing was transmitted */ +static int test_ovf_ioCalls; +#ifdef WOLFTPM_ADV_IO +static int test_ovf_ioCb(TPM2_CTX* ctx, INT32 isRead, UINT32 addr, + BYTE* xferBuf, UINT16 xferSz, void* userCtx) +{ + (void)ctx; (void)isRead; (void)addr; (void)xferBuf; (void)xferSz; + (void)userCtx; + test_ovf_ioCalls++; + return TPM_RC_FAILURE; +} +#else +static int test_ovf_ioCb(TPM2_CTX* ctx, const BYTE* txBuf, BYTE* rxBuf, + UINT16 xferSz, void* userCtx) +{ + (void)ctx; (void)txBuf; (void)rxBuf; (void)xferSz; (void)userCtx; + test_ovf_ioCalls++; + return TPM_RC_FAILURE; +} +#endif + +/* The guard that acts on the flag. TPM2_DispatchCommand and + * TPM2_TransmitCommand are static, so drive it through a public builder that + * takes a caller sized payload: TPM2_ST33_FieldUpgradeCommand does + * Packet_Init + AppendBytes + SendCommand. An oversized payload must return + * BUFFER_E without reaching the transport; a small one must reach it, which + * is what proves the guard is selective rather than always-on. */ +static void test_TPM2_DispatchCommand_overflow(void) +{ + TPM2_CTX ctx; + byte small[16]; + byte* big; + word32 bigSz = (word32)XFER_MAX_SIZE + 64; + int usesIoCb; + int rc; + + big = (byte*)XMALLOC(bigSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + AssertNotNull(big); + XMEMSET(big, 0xA5, bigSz); + XMEMSET(small, 0x5A, sizeof(small)); + XMEMSET(&ctx, 0, sizeof(ctx)); + + /* timeoutTries 0, so this performs no IO but does set the active ctx */ + AssertIntEQ(TPM2_Init_minimal(&ctx), TPM_RC_SUCCESS); + ctx.ioCb = test_ovf_ioCb; + + /* Control: a payload that fits is not refused here, it goes on to the + * transport and fails there instead. This is what makes the guard + * selective rather than always-on. Builds whose transport is not the HAL + * callback (swtpm socket, /dev/tpm0) leave the counter at zero, so it is + * only load bearing when this control moved it. */ + test_ovf_ioCalls = 0; + rc = TPM2_ST33_FieldUpgradeCommand(TPM_CC_FieldUpgradeDataVendor_ST33, + small, (word32)sizeof(small)); + AssertIntNE(rc, BUFFER_E); + usesIoCb = (test_ovf_ioCalls > 0); + + /* Too large for ctx->cmdBuf: refused before anything is transmitted */ + test_ovf_ioCalls = 0; + rc = TPM2_ST33_FieldUpgradeCommand(TPM_CC_FieldUpgradeDataVendor_ST33, + big, bigSz); + AssertIntEQ(rc, BUFFER_E); + if (usesIoCb) { + AssertIntEQ(test_ovf_ioCalls, 0); + } + + XFREE(big, NULL, DYNAMIC_TYPE_TMP_BUFFER); + TPM2_Cleanup(&ctx); + + printf("Test TPM Wrapper:\tDispatch overflow guard:\tPassed\n"); +} +#endif /* WOLFTPM_FIRMWARE_UPGRADE && (WOLFTPM_ST33 || WOLFTPM_AUTODETECT) */ + +/* st33_detect_blob0 decides where the manifest ends and firmware data begins, + * including in upgrade mode where the TPM cannot be consulted at all, so a + * false positive splits the image at the wrong byte. */ +static void test_st33_detect_blob0(void) +{ + byte* buf; + size_t bufSz = 8192; + size_t cand[ST33_BLOB0_SIZE_CNT]; + size_t candCnt, i, off, len; + static const size_t recSz = 500; + + buf = (byte*)XMALLOC(bufSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + AssertNotNull(buf); + + /* Version to expected size: the rule the whole PR turns on */ + AssertIntEQ((int)st33_expected_blob0(1, 258), ST33_BLOB0_SIZE_NON_LMS_RSA); + AssertIntEQ((int)st33_expected_blob0(1, 771), ST33_BLOB0_SIZE_NON_LMS_RSA); + AssertIntEQ((int)st33_expected_blob0(9, 257), ST33_BLOB0_SIZE_NON_LMS); + AssertIntEQ((int)st33_expected_blob0(9, 512), ST33_BLOB0_SIZE_LMS); + + /* Preferred candidate leads, every size still present exactly once */ + candCnt = st33_blob0_candidates(1, 771, 1, cand); + AssertIntEQ((int)candCnt, ST33_BLOB0_SIZE_CNT); + AssertIntEQ((int)cand[0], ST33_BLOB0_SIZE_NON_LMS_RSA); + candCnt = st33_blob0_candidates(9, 512, 1, cand); + AssertIntEQ((int)candCnt, ST33_BLOB0_SIZE_CNT); + AssertIntEQ((int)cand[0], ST33_BLOB0_SIZE_LMS); + /* No caps (upgrade mode): the fixed order, chain alone decides */ + candCnt = st33_blob0_candidates(0, 0, 0, cand); + AssertIntEQ((int)candCnt, ST33_BLOB0_SIZE_CNT); + + /* Exact-fit chain at each known manifest size */ + for (i = 0; i < ST33_BLOB0_SIZE_CNT; i++) { + XMEMSET(buf, 0xAB, bufSz); + off = st33_blob0_sizes[i]; + while (off + 3 + recSz <= bufSz) { + buf[off] = 0x01; + buf[off + 1] = (byte)(recSz >> 8); + buf[off + 2] = (byte)(recSz & 0xFF); + off += 3 + recSz; + } + /* final short record lands exactly on the end */ + len = bufSz - off - 3; + buf[off] = 0xFF; + buf[off + 1] = (byte)(len >> 8); + buf[off + 2] = (byte)(len & 0xFF); + candCnt = st33_blob0_candidates(0, 0, 0, cand); + AssertIntEQ((int)st33_detect_blob0(buf, bufSz, cand, candCnt), + (int)st33_blob0_sizes[i]); + + /* Same buffer one byte short: the chain overshoots, no size fits */ + AssertIntEQ((int)st33_detect_blob0(buf, bufSz - 1, cand, candCnt), 0); + /* And one byte long: the chain stops short */ + AssertIntEQ((int)st33_detect_blob0(buf, bufSz + 1, cand, candCnt), 0); + } + + /* Zero-length record mid-chain is rejected, not walked forever */ + XMEMSET(buf, 0xAB, bufSz); + off = ST33_BLOB0_SIZE_NON_LMS; + buf[off] = 0x01; buf[off + 1] = 0; buf[off + 2] = 0; + candCnt = st33_blob0_candidates(0, 0, 0, cand); + AssertIntEQ((int)st33_detect_blob0(buf, bufSz, cand, candCnt), 0); + + /* A file no larger than the candidate is skipped, not read past the end */ + candCnt = st33_blob0_candidates(0, 0, 0, cand); + AssertIntEQ((int)st33_detect_blob0(buf, ST33_BLOB0_SIZE_NON_LMS_RSA, cand, + candCnt), 0); + AssertIntEQ((int)st33_detect_blob0(buf, 0, cand, candCnt), 0); + + /* Ambiguous image: the chain closes on the last byte from BOTH 177 and + * 321, so only the caps-preferred candidate may break the tie. */ + bufSz = 2000; + XMEMSET(buf, 0xAB, bufSz); + len = bufSz - ST33_BLOB0_SIZE_NON_LMS - 3; + buf[ST33_BLOB0_SIZE_NON_LMS] = 0x01; + buf[ST33_BLOB0_SIZE_NON_LMS + 1] = (byte)(len >> 8); + buf[ST33_BLOB0_SIZE_NON_LMS + 2] = (byte)(len & 0xFF); + len = bufSz - ST33_BLOB0_SIZE_NON_LMS_RSA - 3; + buf[ST33_BLOB0_SIZE_NON_LMS_RSA] = 0x01; + buf[ST33_BLOB0_SIZE_NON_LMS_RSA + 1] = (byte)(len >> 8); + buf[ST33_BLOB0_SIZE_NON_LMS_RSA + 2] = (byte)(len & 0xFF); + candCnt = st33_blob0_candidates(1, 771, 1, cand); /* gen 1 -> prefers 321 */ + AssertIntEQ((int)st33_detect_blob0(buf, bufSz, cand, candCnt), + ST33_BLOB0_SIZE_NON_LMS_RSA); + candCnt = st33_blob0_candidates(9, 257, 1, cand); /* gen 9 -> prefers 177 */ + AssertIntEQ((int)st33_detect_blob0(buf, bufSz, cand, candCnt), + ST33_BLOB0_SIZE_NON_LMS); + + /* NULL guards */ + AssertIntEQ((int)st33_detect_blob0(NULL, bufSz, cand, candCnt), 0); + AssertIntEQ((int)st33_detect_blob0(buf, bufSz, NULL, candCnt), 0); + AssertIntEQ((int)st33_blob0_candidates(1, 1, 1, NULL), 0); + + XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + + printf("Test TPM Wrapper:\tST33 blob0 detection:\t\tPassed\n"); +} + /* A sessioned response whose attacker-controlled parameterSize wraps UINT32 * when added to packet->pos must be rejected up front. Without the bounds * check the wrapped authPos passes the "respSz > authPos" guard and the @@ -8499,6 +8674,11 @@ int unit_tests(int argc, char *argv[]) test_TPM2_CommandRetries(); test_TPM2_Packet_RetryRestore(); #endif +#if defined(WOLFTPM_FIRMWARE_UPGRADE) && \ + (defined(WOLFTPM_ST33) || defined(WOLFTPM_AUTODETECT)) + test_TPM2_DispatchCommand_overflow(); +#endif + test_st33_detect_blob0(); test_TPM2_ResponseProcess_ParamSizeOverflow(); test_TPM2_ResponseProcess_DecParamSizeOverflow(); test_TPM2_ResponseProcess_HmacVerify();