From 64a6437e0eb89dd608605b3a566d3b678c625bb7 Mon Sep 17 00:00:00 2001 From: Aidan Garske Date: Wed, 16 Sep 2026 10:41:13 -0700 Subject: [PATCH] Reduce FWTPM_NO_NV context memory --- .github/workflows/fwtpm-test.yml | 7 +++ docs/FWTPM.md | 4 +- src/fwtpm/README.md | 3 +- src/fwtpm/fwtpm.c | 8 ++++ src/fwtpm/fwtpm_nv.c | 41 +++++++++++++--- tests/fwtpm_unit_tests.c | 81 ++++++++++++++++++++++++++++++++ wolftpm/fwtpm/fwtpm.h | 7 ++- 7 files changed, 139 insertions(+), 12 deletions(-) diff --git a/.github/workflows/fwtpm-test.yml b/.github/workflows/fwtpm-test.yml index 429311ed..ce6c38a2 100644 --- a/.github/workflows/fwtpm-test.yml +++ b/.github/workflows/fwtpm-test.yml @@ -439,6 +439,13 @@ jobs: wolfssl_config: --enable-wolftpm --enable-pkcallbacks --enable-keygen extra_cflags: -DIMPLEMENTATION_PCR=8 -DPLATFORM_PCR=8 + # FWTPM_NO_NV has distinct volatile-only initialization and context + # layout, so run the handler suite in addition to the build-only leg. + - name: no-nv + wolftpm_config: --enable-fwtpm --enable-swtpm + wolfssl_config: --enable-wolftpm --enable-pkcallbacks --enable-keygen + extra_cflags: -DFWTPM_NO_NV + steps: - name: Checkout wolfTPM uses: actions/checkout@v4 diff --git a/docs/FWTPM.md b/docs/FWTPM.md index 4dcac6d1..a88c3fce 100644 --- a/docs/FWTPM.md +++ b/docs/FWTPM.md @@ -559,7 +559,7 @@ All macros are compile-time overridable (e.g., `-DFWTPM_MAX_OBJECTS=8`). | `FWTPM_MAX_HASH_SEQ` | 4 | Maximum concurrent hash/HMAC sequences | | `FWTPM_MAX_PRIMARY_CACHE` | 16 | Cached primary keys per hierarchy+template | | `FWTPM_MAX_SESSIONS` | 8 | Maximum concurrent auth sessions | -| `FWTPM_MAX_NV_INDICES` | 16 | Maximum NV RAM index slots | +| `FWTPM_MAX_NV_INDICES` | 16 | Maximum NV RAM index slots; omitted from `FWTPM_CTX` with `FWTPM_NO_NV` | | `FWTPM_MAX_NV_DATA` | 2048 | Maximum data per NV index (bytes) | | `FWTPM_DA_DEFAULT_MAX_TRIES` | 32 | DA failed-auth count before lockout | | `FWTPM_DA_DEFAULT_RECOVERY` | 600 | DA self-heal interval (seconds per try) | @@ -665,7 +665,7 @@ to reduce code size on constrained targets. | Macro | Default | Commands Excluded | |-------|---------|-------------------| | `FWTPM_NO_ATTESTATION` | not defined | `Quote`, `Certify`, `CertifyCreation`, `GetTime`, `NV_Certify` | -| `FWTPM_NO_NV` | not defined | `NV_DefineSpace`, `NV_UndefineSpace`, `NV_ReadPublic`, `NV_Write`, `NV_Read`, `NV_Extend`, `NV_Increment`, `NV_WriteLock`, `NV_ReadLock`, `NV_Certify` | +| `FWTPM_NO_NV` | not defined | `NV_DefineSpace`, `NV_UndefineSpace`, `NV_ReadPublic`, `NV_Write`, `NV_Read`, `NV_Extend`, `NV_Increment`, `NV_WriteLock`, `NV_ReadLock`, `NV_Certify`; also removes the in-memory NV index slots from `FWTPM_CTX` | | `FWTPM_NO_POLICY` | not defined | `PolicyGetDigest`, `PolicyRestart`, `PolicyPCR`, `PolicyPassword`, `PolicyAuthValue`, `PolicyCommandCode`, `PolicyOR`, `PolicySecret`, `PolicyAuthorize`, `PolicyNV` | | `FWTPM_NO_CREDENTIAL` | not defined | `MakeCredential`, `ActivateCredential` | | `FWTPM_NO_DA` | not defined | `DictionaryAttackParameters`, `DictionaryAttackLockReset`, and all lockout accounting | diff --git a/src/fwtpm/README.md b/src/fwtpm/README.md index 1b0589c5..047a0712 100644 --- a/src/fwtpm/README.md +++ b/src/fwtpm/README.md @@ -250,7 +250,8 @@ EncryptDecrypt, EncryptDecrypt2 - `FWTPM_NO_NV`: NV\_DefineSpace, NV\_UndefineSpace, NV\_UndefineSpaceSpecial, NV\_ReadPublic, NV\_Write, NV\_Read, NV\_Extend, NV\_Increment, NV\_WriteLock, NV\_ReadLock, NV\_SetBits, NV\_ChangeAuth, NV\_GlobalWriteLock (13 commands). - Also gates PolicyNV and PolicyAuthorizeNV when policy is enabled. + Also gates PolicyNV and PolicyAuthorizeNV when policy is enabled and removes + the `FWTPM_CTX` in-memory NV index slots. - `FWTPM_NO_ATTESTATION`: Quote, Certify, CertifyCreation, GetTime, NV\_Certify - `FWTPM_NO_CREDENTIAL`: MakeCredential, ActivateCredential - `FWTPM_NO_DA`: DictionaryAttackLockReset, DictionaryAttackParameters (2 commands) diff --git a/src/fwtpm/fwtpm.c b/src/fwtpm/fwtpm.c index 12d65372..d54a0356 100644 --- a/src/fwtpm/fwtpm.c +++ b/src/fwtpm/fwtpm.c @@ -137,6 +137,14 @@ int FWTPM_Init(FWTPM_CTX* ctx) if (rc == 0) { ctx->pcrAllocatedBanks = FWTPM_PCR_ALLOC_DEFAULT; } +#ifndef FWTPM_NO_DA + if (rc == 0) { + ctx->daMaxTries = FWTPM_DA_DEFAULT_MAX_TRIES; + ctx->daRecoveryTime = FWTPM_DA_DEFAULT_RECOVERY; + ctx->daLockoutRecovery = FWTPM_DA_DEFAULT_LOCKOUT_RECOVERY; + ctx->orderly = 1; + } +#endif #endif if (rc != 0) { diff --git a/src/fwtpm/fwtpm_nv.c b/src/fwtpm/fwtpm_nv.c index 9c4ecc61..925f3364 100644 --- a/src/fwtpm/fwtpm_nv.c +++ b/src/fwtpm/fwtpm_nv.c @@ -469,6 +469,7 @@ static int FwNvUnmarshalName(const byte* buf, word32* pos, word32 maxSz, return rc; } +#ifndef FWTPM_NO_NV /* Marshal TPMS_NV_PUBLIC manually (no packet function exists) */ static int FwNvMarshalNvPublic(byte* buf, word32* pos, word32 maxSz, const TPMS_NV_PUBLIC* nvPub) @@ -603,6 +604,7 @@ static int FwNvUnmarshalNvIndex(const byte* buf, word32* pos, word32 maxSz, } return rc; } +#endif /* !FWTPM_NO_NV */ /* Marshal FWTPM_Object → value bytes */ static int FwNvMarshalObject(byte* buf, word32* pos, word32 maxSz, @@ -1115,21 +1117,24 @@ static int FwNvTagIsDelete(UINT16 tag) static int FwNvAppendEntry(FWTPM_CTX* ctx, UINT16 tag, const byte* value, UINT16 valueLen) { - FWTPM_NV_HAL* hal = &ctx->nvHal; + FWTPM_NV_HAL* hal; word32 entrySize = TLV_HDR_SIZE + valueLen; word32 reserve = FWTPM_NV_MAC_SIZE; byte tlvHdr[TLV_HDR_SIZE]; word32 savedWritePos; int rc; - if (hal->write == NULL) { #ifdef FWTPM_NO_NV - /* Volatile-only build: there is no backing store, so a state change - * succeeds without being persisted rather than reporting a failure. */ - return TPM_RC_SUCCESS; -#else - return TPM_RC_FAILURE; + (void)ctx; + (void)tag; + (void)value; + (void)valueLen; + return TPM_RC_SUCCESS; #endif + + hal = &ctx->nvHal; + if (hal->write == NULL) { + return TPM_RC_FAILURE; } #ifdef WOLFTPM_FWTPM_NV_APPEND_ONLY @@ -1218,6 +1223,7 @@ static int FwNvAppendEntry(FWTPM_CTX* ctx, UINT16 tag, /* Journal Load (Init) */ /* ========================================================================= */ +#ifndef FWTPM_NO_NV /* Find NV index slot by handle, or allocate empty slot */ static int FwNvFindOrAllocNvSlot(FWTPM_CTX* ctx, UINT32 nvHandle) { @@ -1235,6 +1241,7 @@ static int FwNvFindOrAllocNvSlot(FWTPM_CTX* ctx, UINT32 nvHandle) } return freeSlot; } +#endif /* !FWTPM_NO_NV */ /* Find persistent object slot by handle, or allocate empty slot */ static int FwNvFindOrAllocPersSlot(FWTPM_CTX* ctx, UINT32 handle) @@ -1440,6 +1447,7 @@ static int FwNvProcessEntry(FWTPM_CTX* ctx, UINT16 tag, break; } + #ifndef FWTPM_NO_NV case FWTPM_NV_TAG_NV_INDEX: { FWTPM_NvIndex nv; int slot; @@ -1469,6 +1477,7 @@ static int FwNvProcessEntry(FWTPM_CTX* ctx, UINT16 tag, } break; } + #endif /* !FWTPM_NO_NV */ case FWTPM_NV_TAG_PERSISTENT: { FWTPM_Object obj; @@ -1996,6 +2005,10 @@ int FWTPM_NV_Save(FWTPM_CTX* ctx) return BAD_FUNC_ARG; } +#ifdef FWTPM_NO_NV + return TPM_RC_SUCCESS; +#endif + hal = &ctx->nvHal; if (hal->write == NULL) { return TPM_RC_FAILURE; @@ -2200,6 +2213,7 @@ int FWTPM_NV_Save(FWTPM_CTX* ctx) } } + #ifndef FWTPM_NO_NV /* --- NV indices (only used slots, minus a pending deletion) --- */ for (i = 0; i < FWTPM_MAX_NV_INDICES && rc == 0; i++) { if (ctx->nvIndices[i].inUse && @@ -2230,6 +2244,7 @@ int FWTPM_NV_Save(FWTPM_CTX* ctx) } } } + #endif /* !FWTPM_NO_NV */ /* --- Persistent objects (only used slots, minus a pending deletion) --- */ for (i = 0; i < FWTPM_MAX_PERSISTENT && rc == 0; i++) { @@ -2546,6 +2561,11 @@ int FWTPM_NV_SaveHierarchyPolicy(FWTPM_CTX* ctx, UINT32 hierarchy) int FWTPM_NV_SaveNvIndex(FWTPM_CTX* ctx, int slot) { +#ifdef FWTPM_NO_NV + (void)ctx; + (void)slot; + return NOT_COMPILED_IN; +#else int rc; byte* buf; word32 pos = 0; @@ -2580,10 +2600,16 @@ int FWTPM_NV_SaveNvIndex(FWTPM_CTX* ctx, int slot) TPM2_ForceZero(buf, bufSz); XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); return rc; +#endif /* FWTPM_NO_NV */ } int FWTPM_NV_DeleteNvIndex(FWTPM_CTX* ctx, UINT32 nvHandle) { +#ifdef FWTPM_NO_NV + (void)ctx; + (void)nvHandle; + return NOT_COMPILED_IN; +#else int rc; byte buf[4]; word32 pos = 0; @@ -2597,6 +2623,7 @@ int FWTPM_NV_DeleteNvIndex(FWTPM_CTX* ctx, UINT32 nvHandle) rc = FwNvAppendEntry(ctx, FWTPM_NV_TAG_NV_INDEX_DEL, buf, (UINT16)pos); ctx->nvDeleteHandle = 0; return rc; +#endif /* FWTPM_NO_NV */ } int FWTPM_NV_SavePersistent(FWTPM_CTX* ctx, int slot) diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index 9aa3bdc5..22183a36 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -227,6 +227,84 @@ static int fwtpm_test_startup(FWTPM_CTX* ctx) /* 1. Core Lifecycle Tests */ /* ================================================================== */ +#ifdef FWTPM_NO_NV +static int gNoNvHalCalls; + +static int test_no_nv_hal_read(void* userCtx, word32 offset, byte* data, + word32 dataSz) +{ + (void)userCtx; + (void)offset; + (void)data; + (void)dataSz; + gNoNvHalCalls++; + return TPM_RC_FAILURE; +} + +static int test_no_nv_hal_write(void* userCtx, word32 offset, + const byte* data, word32 dataSz) +{ + (void)userCtx; + (void)offset; + (void)data; + (void)dataSz; + gNoNvHalCalls++; + return TPM_RC_FAILURE; +} + +static int test_no_nv_hal_erase(void* userCtx, word32 offset, word32 dataSz) +{ + (void)userCtx; + (void)offset; + (void)dataSz; + gNoNvHalCalls++; + return TPM_RC_FAILURE; +} + +static void test_fwtpm_no_nv_volatile(void) +{ + FWTPM_CTX ctx; + FWTPM_NV_HAL hal; + + memset(&ctx, 0, sizeof(ctx)); + memset(&hal, 0, sizeof(hal)); + hal.read = test_no_nv_hal_read; + hal.write = test_no_nv_hal_write; + hal.erase = test_no_nv_hal_erase; + hal.maxSize = 4096; + AssertIntEQ(FWTPM_NV_SetHAL(&ctx, &hal), TPM_RC_SUCCESS); + + gNoNvHalCalls = 0; + AssertIntEQ(FWTPM_Init(&ctx), TPM_RC_SUCCESS); +#ifndef FWTPM_NO_DA + AssertIntEQ((int)ctx.daMaxTries, FWTPM_DA_DEFAULT_MAX_TRIES); + AssertIntEQ((int)ctx.daRecoveryTime, FWTPM_DA_DEFAULT_RECOVERY); + AssertIntEQ((int)ctx.daLockoutRecovery, + FWTPM_DA_DEFAULT_LOCKOUT_RECOVERY); + AssertIntEQ(ctx.orderly, 1); +#endif + AssertIntEQ(FWTPM_NV_Save(&ctx), TPM_RC_SUCCESS); + AssertIntEQ(FWTPM_NV_SaveSeeds(&ctx), TPM_RC_SUCCESS); + AssertIntEQ(FWTPM_NV_SaveAuth(&ctx, TPM_RH_OWNER), TPM_RC_SUCCESS); + AssertIntEQ(FWTPM_NV_SavePcrState(&ctx), TPM_RC_SUCCESS); + AssertIntEQ(FWTPM_NV_SavePcrAuth(&ctx), TPM_RC_SUCCESS); + AssertIntEQ(FWTPM_NV_SaveFlags(&ctx), TPM_RC_SUCCESS); + AssertIntEQ(FWTPM_NV_SaveClock(&ctx), TPM_RC_SUCCESS); + AssertIntEQ(FWTPM_NV_SaveHierarchyPolicy(&ctx, TPM_RH_OWNER), + TPM_RC_SUCCESS); + AssertIntEQ(FWTPM_NV_SaveNvIndex(&ctx, 0), NOT_COMPILED_IN); + AssertIntEQ(FWTPM_NV_SaveNvIndex(NULL, -1), NOT_COMPILED_IN); + AssertIntEQ(FWTPM_NV_DeleteNvIndex(&ctx, NV_INDEX_FIRST), + NOT_COMPILED_IN); + AssertIntEQ(FWTPM_NV_DeleteNvIndex(NULL, 0), NOT_COMPILED_IN); + AssertIntEQ(gNoNvHalCalls, 0); + AssertIntEQ(FWTPM_Cleanup(&ctx), TPM_RC_SUCCESS); + AssertIntEQ(gNoNvHalCalls, 0); + + fwtpm_pass("No-NV volatile state:", 0); +} +#endif /* FWTPM_NO_NV */ + static void test_fwtpm_init_cleanup(void) { FWTPM_CTX ctx; @@ -15496,6 +15574,9 @@ int fwtpm_unit_tests(int argc, char *argv[]) (void)remove(FWTPM_NV_FILE); /* Lifecycle */ +#ifdef FWTPM_NO_NV + test_fwtpm_no_nv_volatile(); +#endif test_fwtpm_init_cleanup(); test_fwtpm_startup_clear(); test_fwtpm_double_startup(); diff --git a/wolftpm/fwtpm/fwtpm.h b/wolftpm/fwtpm/fwtpm.h index cca588fe..5096eefb 100644 --- a/wolftpm/fwtpm/fwtpm.h +++ b/wolftpm/fwtpm/fwtpm.h @@ -128,7 +128,7 @@ * FWTPM_NO_CREDENTIAL - MakeCredential/ActivateCredential * FWTPM_NO_DA - dictionary-attack lockout protection (see below) * FWTPM_NO_PARAM_ENC - command/response parameter encryption - * FWTPM_NO_NV - all NV_* commands + * FWTPM_NO_NV - all NV_* commands and in-memory NV index slots * FWTPM_NO_KEY_MIGRATION - Import/Duplicate/Rewrap * FWTPM_NO_ECDH - ECDH_KeyGen/ECDH_ZGen/EC_Ephemeral/ZGen_2Phase/ * ECC_Parameters (ECDSA sign/verify are retained) @@ -795,8 +795,11 @@ typedef struct FWTPM_CTX { /* Persistent object slots (0x81xxxxxx handles via EvictControl) */ FWTPM_Object persistent[FWTPM_MAX_PERSISTENT]; - /* NV index slots (0x01xxxxxx handles) */ + /* NV index slots (0x01xxxxxx handles). They are the dominant fixed RAM + * cost of the NV command group, so omit them when that group is disabled. */ +#ifndef FWTPM_NO_NV FWTPM_NvIndex nvIndices[FWTPM_MAX_NV_INDICES]; +#endif /* Hash sequence slots */ #ifndef FWTPM_NO_HASH_CMDS