Skip to content

Add new Firmware TPM (fwTPM)#474

Open
dgarske wants to merge 7 commits intowolfSSL:masterfrom
dgarske:fwtpm
Open

Add new Firmware TPM (fwTPM)#474
dgarske wants to merge 7 commits intowolfSSL:masterfrom
dgarske:fwtpm

Conversation

@dgarske
Copy link
Contributor

@dgarske dgarske commented Mar 21, 2026

Summary

  • Add firmware TPM 2.0 server (fwTPM) implementing TPM 2.0 spec v1.38 (105/113 commands, 93% coverage)
  • Spec-compliant primary key derivation: deterministic RSA (iterative KDFa prime generation), ECC (KDFa scalar, Q=d*G), KEYEDHASH/SYMCIPHER keys — same seed + template always produces the same key
  • ChangePPS / ChangeEPS hierarchy commands with seed regeneration and cache flush
  • NV storage with TLV journal format (flash-friendly, append-only)
  • Socket (mssim) and TIS/SHM transports for desktop, UART transport for embedded
  • STM32 Cortex-M33 bare-metal port with TrustZone (CMSE) support
  • Shared crypto refactoring: extract KDFa/KDFe, AES-CFB, HMAC/Hash to tpm2_crypto.c
  • Bounds-checked TPM2_Packet_ParseU16Buf variant for safer response parsing
  • Auth validation: reject oversized auth values instead of silent truncation
  • SIGTERM/SIGINT signal handler for graceful NV save on server kill

fwTPM Server

Core TPM 2.0 command processing in src/fwtpm/:

  • fwtpm_command.c — 105 command handlers with full auth, sessions, parameter encryption
  • fwtpm_nv.c — TLV journal NV storage (file-based default, HAL-abstracted for flash)
  • fwtpm_io.c — Socket transport (mssim + swtpm protocol auto-detection)
  • fwtpm_tis.c / fwtpm_tis_shm.c — TIS register interface via POSIX shared memory
  • fwtpm_crypto.c — Key generation, sign/verify, seed encrypt/decrypt helpers
  • Clock HAL with NV-persisted clockOffset

Build: ./configure --enable-fwtpm && make

Primary Key Derivation (TPM 2.0 Part 1 Section 26)

  • RSA: iterative KDFa prime generation with labels "RSA p" / "RSA q"
  • ECC: KDFa scalar derivation, public point Q = d*G
  • KEYEDHASH/SYMCIPHER: KDFa with algorithm-specific labels
  • hashUnique = H(sensitiveCreate.data || unique) per Section 26.1
  • Primary cache retained as performance optimization, no longer required for correctness

STM32 Port (src/fwtpm/ports/stm32/)

Bare-metal port for NUCLEO-H563ZI (Cortex-M33, 250MHz, 2MB flash, 640KB SRAM):

  • TrustZone secure world with CMSE NSC interface (make TZEN=1)
  • Internal flash NV HAL, hardware RNG, SysTick clock HAL
  • mssim protocol over UART (115200 baud, ST-Link VCP)
  • Semihosting debug support (make SEMIHOSTING=1)
  • Self-test mode for CI (make SELFTEST=1)

UART Transport (--enable-swtpm=uart)

New transport option for wolfTPM client library to communicate with embedded fwTPM over serial:

  • ./configure --enable-swtpm=uart — uses termios serial I/O instead of TCP sockets
  • TPM2_SWTPM_HOST env var selects serial device at runtime
  • Same mssim protocol as socket transport (compatible with all wolfTPM examples)

Testing

  • 311 tpm2-tools compatibility tests (scripts/tpm2_tools_test.sh)
  • Full wolfTPM example suite (examples/run_examples.sh)
  • libFuzzer harness with corpus generator (tests/fuzz/)
  • m33mu Cortex-M33 emulator CI test (scripts/fwtpm_emu_test.sh)
  • ASan / UBSan clean
  • 20 CI matrix configurations (pedantic gcc/clang -Werror, sanitizers, feature-disable variants)

Test plan

  • Desktop: scripts/fwtpm_build_test.sh --all (examples + make check + 311 tpm2-tools)
  • Emulator: scripts/fwtpm_emu_test.sh (m33mu self-test with BKPT assertion)
  • Build matrix: no-RSA, no-ECC, fwtpm-only, minimal, small-stack, pedantic, ASan, UBSan
  • Hardware: NUCLEO-H563ZI TZEN=0 and TZEN=1 (caps, keygen RSA+ECC, seal/unseal, NV, RSA encrypt/decrypt)
  • UART transport: TPM2_SWTPM_HOST=/dev/ttyACM0 ./examples/wrap/caps
  • Primary key determinism: same key across server restart verified
  • ChangePPS/ChangeEPS: positive tests, negative auth, endorsement auth reset

Copy link

@wolfSSL-Fenrir-bot wolfSSL-Fenrir-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fenrir Automated Review — PR #474

Scan targets checked: wolftpm-bugs, wolftpm-src
Findings: 4

Medium (2)

Unchecked sem_wait return value in TIS client IO callback

File: hal/tpm_io_fwtpm.c:198-199
Function: TPM2_IoCb_FwTPM
Category: Incorrect error handling

The sem_post and sem_wait return values are not checked. sem_wait can return -1 with errno set to EINTR if interrupted by a signal. When this occurs, the function proceeds to read from shm->reg_data as if the server has processed the request, but the server may not have completed (or even started) processing. This results in reading stale or incorrect data from the shared memory region, which could cause silent data corruption in TIS register reads.

/* Signal server and wait for completion */
    sem_post((sem_t*)client->semCmd);
    sem_wait((sem_t*)client->semRsp);

    /* Copy result for reads */
    if (isRead) {
        XMEMCPY(buf, shm->reg_data, size);
    }

Recommendation: Check the return value of sem_wait and retry on EINTR. For example:

int ret;
do {
    ret = sem_wait((sem_t*)client->semRsp);
} while (ret == -1 && errno == EINTR);
if (ret != 0) {
    return TPM_RC_FAILURE;
}

Unchecked sem_wait return allows use of stale shared memory data

File: hal/tpm_io_fwtpm.c:188-195
Function: TPM2_IoCb_FwTPM
Category: HAL implementation flaws

The sem_wait() call that waits for the fwTPM server response does not check its return value. sem_wait can fail with EINTR (signal interruption) or other errors, returning -1. When this happens, the function proceeds to XMEMCPY(buf, shm->reg_data, size) for read operations, copying potentially stale or uninitialized data from shared memory into the caller's buffer. For TIS register reads, this could return incorrect TPM status/data values, causing the TIS state machine in tpm2_tis.c to misinterpret TPM state. In a security context, this could cause command/response desynchronization or leak data from a previous TPM transaction.

/* Signal server and wait for completion */
    sem_post((sem_t*)client->semCmd);
    sem_wait((sem_t*)client->semRsp);

    /* Copy result for reads */
    if (isRead) {
        XMEMCPY(buf, shm->reg_data, size);
    }

    return TPM_RC_SUCCESS;

Recommendation: Check the return value of sem_wait(). If it returns -1, retry on EINTR (standard pattern: while (sem_wait(sem) == -1 && errno == EINTR) { }) and return TPM_RC_FAILURE for other errors. Similarly check sem_post() return value.


Low (2)

Static fwTPM client context never disconnected

File: hal/tpm_io_fwtpm.c:168-177
Function: TPM2_IoCb_FwTPM
Category: Resource leaks on error paths

The static gFwtpmClient is lazily initialized on first call via FWTPM_TIS_ClientConnect, which opens a shared memory mapping, a file descriptor, and two POSIX semaphores. However, FWTPM_TIS_ClientDisconnect is never called from any code path — there is no cleanup mechanism (e.g., atexit handler) to release these resources. While the OS reclaims these on process exit, the semaphore references (sem_close) are leaked if the library is dynamically loaded and unloaded without process termination.

static FWTPM_TIS_CLIENT_CTX gFwtpmClient;
static int gFwtpmClientInit = 0;

...

    /* Lazy connect on first call */
    if (!gFwtpmClientInit) {
        int rc = FWTPM_TIS_ClientConnect(client);
        if (rc != TPM_RC_SUCCESS) {
            return rc;
        }
        gFwtpmClientInit = 1;
    }

Recommendation: Consider adding a cleanup function that calls FWTPM_TIS_ClientDisconnect(&gFwtpmClient) and resetting gFwtpmClientInit = 0. This could be registered via atexit() after successful connection, or called from TPM2_Cleanup or an equivalent teardown path.


No concurrency protection on shared memory register access fields

File: hal/tpm_io_fwtpm.c:178-195
Function: TPM2_IoCb_FwTPM
Category: HAL implementation flaws

The function writes multiple shared memory fields (reg_addr, reg_len, reg_is_write, reg_data) non-atomically before signaling the server via sem_post. If multiple threads or processes invoke TPM2_IoCb_FwTPM concurrently (e.g., in a multi-threaded application using wolfTPM), one caller's reg_addr/reg_len could be overwritten by another caller before the semaphore is posted. This would cause the server to process an incorrect register access, potentially reading/writing wrong TIS registers. The static gFwtpmClient global and gFwtpmClientInit flag compound this by sharing a single connection across all callers with no synchronization.

/* Fill register access request */
    shm->reg_addr = addr;
    shm->reg_len = size;
    shm->reg_is_write = isRead ? 0 : 1;

    if (!isRead) {
        XMEMCPY(shm->reg_data, buf, size);
    }

    /* Signal server and wait for completion */
    sem_post((sem_t*)client->semCmd);
    sem_wait((sem_t*)client->semRsp);

Recommendation: Add a mutex (e.g., pthread_mutex_t) to serialize access to the shared memory region across concurrent callers. Alternatively, document that this HAL is single-threaded only and add an assertion or compile-time guard.


This review was generated automatically by Fenrir. Findings are non-blocking.

- fwTPM server implementing TPM 2.0 spec v1.38 (98/113 commands, 87%)
- NV storage with TLV journal format (flash-friendly, append-only)
- Socket (mssim/swtpm auto-detect) and TIS/SHM transports for desktop
- STM32 Cortex-M33 bare-metal port with TrustZone (CMSE) support
- UART transport for embedded fwTPM communication
- Shared crypto refactoring: KDFa/KDFe, AES-CFB, HMAC/Hash to tpm2_crypto.c
- Bounds-checked TPM2_Packet_ParseU16Buf for safer response parsing
- libFuzzer harness with gen_corpus.py and tpm2.dict
- 290+ tpm2-tools compatibility tests
- CI: fwtpm-test.yml (examples, tpm2-tools, emulator), fuzz.yml
@dgarske dgarske changed the title Add fwTPM firmware TPM 2.0 server with STM32 port Add fwTPM firmware TPM 2.0 server Mar 21, 2026
@dgarske dgarske changed the title Add fwTPM firmware TPM 2.0 server Add new Firmware TPM (fwTPM) Mar 22, 2026
dgarske added 4 commits March 23, 2026 14:29
…IGTERM handler

Primary Key Derivation (TPM 2.0 Part 1 Section 26):
- Derive RSA primary keys via iterative KDFa prime generation ("RSA p"/"RSA q")
- Derive ECC primary keys via KDFa scalar derivation, Q = d*G
- Derive KEYEDHASH/SYMCIPHER primary keys via KDFa ("KEYEDHASH"/"SYMCIPHER")
- Compute hashUnique = H(sensitiveCreate.data || unique) per Section 26.1
- Same seed + same template now always produces the same key (deterministic)
- Primary cache retained as performance optimization, no longer required
  for correctness

ChangePPS / ChangeEPS (TPM 2.0 Part 3 Sections 24.4-24.5):
- ChangePPS: re-generate platform seed, flush platform cache entries
- ChangeEPS: re-generate endorsement seed, reset endorsement auth/policy,
  flush endorsement cache entries
- Both require TPM_RH_PLATFORM authorization

Bug fixes:
- Flush stale primary cache entries in FwCmd_Clear when seeds are
  regenerated (cache from old seed produced wrong keys)
- Add SIGTERM/SIGINT signal handler for graceful NV save on server kill

Code style:
- Remove 8 mid-function brace-scoped variable declaration blocks
- Deduplicate docs/FWTPM.md and src/fwtpm/README.md

Testing:
- tpm2-tools: ChangePPS/ChangeEPS positive tests, determinism-across-restart,
  negative auth tests, endorsement auth reset verification (311 pass, 0 fail)
- Update command count: 105/113 v1.38 commands (93% coverage)
… real TPM's and reduce memory/NV use. Fix fuzzer report.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants