From eac8cf28924609df5b7acc55fc0c4ee80165511a Mon Sep 17 00:00:00 2001 From: David Garske Date: Tue, 3 Nov 2026 12:57:54 -0800 Subject: [PATCH 1/2] wrap: flush only the transient handles the TPM reports as loaded --- examples/pqc/pqc_ctrl.c | 42 ++++------------------------------------- src/tpm2_wrap.c | 31 +++++++++++++++++++++++++++++- tests/unit_tests.c | 28 +++++++++++++++++++++++++++ wolftpm/tpm2_wrap.h | 1 + 4 files changed, 63 insertions(+), 39 deletions(-) diff --git a/examples/pqc/pqc_ctrl.c b/examples/pqc/pqc_ctrl.c index 897cb089e..b4c53903a 100644 --- a/examples/pqc/pqc_ctrl.c +++ b/examples/pqc/pqc_ctrl.c @@ -120,40 +120,6 @@ static const char* mlkemName(TPMI_MLKEM_PARAMETER_SET ps) "768"; } -/* Reclaim the transient object table by flushing exactly the handles the TPM - * reports as loaded. Parts with a small object table (e.g. SealSQ QVault TPM) - * hit TPM_RC_OBJECT_MEMORY when a key plus a sign/verify sequence object are - * live at once. wolfTPM2_UnloadHandles_AllTransient() only covers - * MAX_HANDLE_NUM (3) fixed handles, so query the actual loaded set instead. */ -static int pqc_flush_transient(WOLFTPM2_DEV* dev) -{ - int rc; - word32 i; - GetCapability_In in; - GetCapability_Out out; - FlushContext_In flushCtx; - - (void)dev; /* operates on the active TPM context */ - XMEMSET(&in, 0, sizeof(in)); - XMEMSET(&out, 0, sizeof(out)); - XMEMSET(&flushCtx, 0, sizeof(flushCtx)); - in.capability = TPM_CAP_HANDLES; - in.property = TRANSIENT_FIRST; - in.propertyCount = MAX_CAP_HANDLES; - rc = TPM2_GetCapability(&in, &out); - if (rc != TPM_RC_SUCCESS) { - return rc; - } - if (out.capabilityData.capability != TPM_CAP_HANDLES) { - return TPM_RC_FAILURE; - } - for (i = 0; i < out.capabilityData.data.handles.count; i++) { - flushCtx.flushHandle = out.capabilityData.data.handles.handle[i]; - (void)TPM2_FlushContext(&flushCtx); - } - return TPM_RC_SUCCESS; -} - static int do_caps(WOLFTPM2_DEV* dev) { int rc; @@ -401,7 +367,7 @@ static int do_mldsa(WOLFTPM2_DEV* dev, TPMI_MLDSA_PARAMETER_SET ps) XMEMSET(&validation, 0, sizeof(validation)); XMEMSET(&flushCtx, 0, sizeof(flushCtx)); - (void)pqc_flush_transient(dev); + (void)wolfTPM2_UnloadHandles_AllTransient(dev); sig = (byte*)XMALLOC(MAX_MLDSA_SIG_SIZE, NULL, DYNAMIC_TYPE_TMP_BUFFER); if (sig == NULL) @@ -495,7 +461,7 @@ static int do_hash_mldsa(WOLFTPM2_DEV* dev, TPMI_MLDSA_PARAMETER_SET ps) #endif digestSz = TPM2_GetHashDigestSize(preHash); - (void)pqc_flush_transient(dev); + (void)wolfTPM2_UnloadHandles_AllTransient(dev); sig = (byte*)XMALLOC(MAX_MLDSA_SIG_SIZE, NULL, DYNAMIC_TYPE_TMP_BUFFER); if (sig == NULL) @@ -571,7 +537,7 @@ static int do_mlkem(WOLFTPM2_DEV* dev, TPMI_MLKEM_PARAMETER_SET ps) XMEMSET(ss1, 0, sizeof(ss1)); XMEMSET(ss2, 0, sizeof(ss2)); - (void)pqc_flush_transient(dev); + (void)wolfTPM2_UnloadHandles_AllTransient(dev); ct = (byte*)XMALLOC(MAX_MLKEM_CT_SIZE, NULL, DYNAMIC_TYPE_TMP_BUFFER); if (ct == NULL) @@ -721,7 +687,7 @@ static int handleOpt(WOLFTPM2_DEV* dev, const char* arg, int validateOnly) if (validateOnly) { return 0; } - rc = pqc_flush_transient(dev); + rc = wolfTPM2_UnloadHandles_AllTransient(dev); if (rc != TPM_RC_SUCCESS) { printf("FAIL flush 0x%x: %s\n", rc, wolfTPM2_GetRCString(rc)); return rc; diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index 097fc830a..dff48aff3 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -9450,7 +9450,36 @@ int wolfTPM2_UnloadHandles(WOLFTPM2_DEV* dev, word32 handleStart, int wolfTPM2_UnloadHandles_AllTransient(WOLFTPM2_DEV* dev) { - return wolfTPM2_UnloadHandles(dev, TRANSIENT_FIRST, MAX_HANDLE_NUM); + int rc; + word32 i; + GetCapability_In in; + GetCapability_Out out; + TPML_HANDLE* handles = &out.capabilityData.data.handles; + WOLFTPM2_HANDLE handle; + + if (dev == NULL) { + return BAD_FUNC_ARG; + } + + /* flush only the handles the TPM reports as loaded */ + XMEMSET(&in, 0, sizeof(in)); + in.capability = TPM_CAP_HANDLES; + in.property = TRANSIENT_FIRST; + in.propertyCount = MAX_CAP_HANDLES; + rc = TPM2_GetCapability(&in, &out); + if (rc != TPM_RC_SUCCESS || + out.capabilityData.capability != TPM_CAP_HANDLES) { + return wolfTPM2_UnloadHandles(dev, TRANSIENT_FIRST, MAX_HANDLE_NUM); + } + + XMEMSET(&handle, 0, sizeof(handle)); + wolfTPM2_CopyAuth(&handle.auth, &dev->session[0].auth); + for (i = 0; i < handles->count; i++) { + handle.hndl = handles->handle[i]; + (void)wolfTPM2_UnloadHandle(dev, &handle); + } + TPM2_ForceZero(&handle, sizeof(handle)); + return TPM_RC_SUCCESS; } int wolfTPM2_ChangeHierarchyAuth(WOLFTPM2_DEV* dev, WOLFTPM2_SESSION* session, diff --git a/tests/unit_tests.c b/tests/unit_tests.c index b0a50cc3d..0530f83e2 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -1866,6 +1866,33 @@ static void test_wolfTPM2_GetRandom(void) rc == 0 ? "Passed" : "Failed"); } +static void test_wolfTPM2_UnloadHandles_AllTransient(void) +{ + int rc; + WOLFTPM2_DEV dev; + WOLFTPM2_KEY srk; + + rc = wolfTPM2_Init(&dev, TPM2_IoCb, NULL); + AssertIntEQ(rc, 0); + + rc = wolfTPM2_UnloadHandles_AllTransient(NULL); + AssertIntEQ(rc, BAD_FUNC_ARG); + + XMEMSET(&srk, 0, sizeof(srk)); + rc = wolfTPM2_CreateSRK(&dev, &srk, TPM_ALG_ECC, NULL, 0); + AssertIntEQ(rc, 0); + AssertIntGE(wolfTPM2_GetHandles(TRANSIENT_FIRST, NULL), 1); + + rc = wolfTPM2_UnloadHandles_AllTransient(&dev); + AssertIntEQ(rc, 0); + AssertIntEQ(wolfTPM2_GetHandles(TRANSIENT_FIRST, NULL), 0); + + wolfTPM2_Cleanup(&dev); + + printf("Test TPM Wrapper: %-40s %s\n", "Unload All Transient:", + rc == 0 ? "Passed" : "Failed"); +} + static void test_wolfTPM2_HashFinish_BufferTooSmall(void) { #ifndef WOLFTPM2_NO_WOLFCRYPT @@ -11847,6 +11874,7 @@ int unit_tests(int argc, char *argv[]) test_wolfTPM2_OpenExisting(); test_wolfTPM2_GetCapabilities(); test_wolfTPM2_GetRandom(); + test_wolfTPM2_UnloadHandles_AllTransient(); test_wolfTPM2_DictionaryAttack(); test_wolfTPM2_HashFinish_BufferTooSmall(); test_TPM2_PCRSel(); diff --git a/wolftpm/tpm2_wrap.h b/wolftpm/tpm2_wrap.h index 8b19603ec..d648e3f7a 100644 --- a/wolftpm/tpm2_wrap.h +++ b/wolftpm/tpm2_wrap.h @@ -4042,6 +4042,7 @@ WOLFTPM_API int wolfTPM2_UnloadHandles(WOLFTPM2_DEV* dev, word32 handleStart, /*! \ingroup wolfTPM2_Wrappers \brief One-shot API to unload all transient TPM handles + \note Flushes the transient handles the TPM reports as loaded, or the first MAX_HANDLE_NUM if TPM_CAP_HANDLES fails \note If there are Primary Keys as transient objects, they need to be recreated before TPM keys can be used \return TPM_RC_SUCCESS: successful From caa7cdaea85c2eb5cd9a7bc50f7c37b41ab9c194 Mon Sep 17 00:00:00 2001 From: David Garske Date: Tue, 3 Nov 2026 15:45:32 -0800 Subject: [PATCH 2/2] Follow TPM_CAP_HANDLES continuation and skip non-transient handles in UnloadHandles_AllTransient --- src/tpm2_wrap.c | 47 +++++++++++++++++++++++++++++++-------------- tests/unit_tests.c | 5 ++++- wolftpm/tpm2_wrap.h | 2 +- 3 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index dff48aff3..ac369e282 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -9452,6 +9452,7 @@ int wolfTPM2_UnloadHandles_AllTransient(WOLFTPM2_DEV* dev) { int rc; word32 i; + TPM_HANDLE next = TRANSIENT_FIRST; GetCapability_In in; GetCapability_Out out; TPML_HANDLE* handles = &out.capabilityData.data.handles; @@ -9462,24 +9463,42 @@ int wolfTPM2_UnloadHandles_AllTransient(WOLFTPM2_DEV* dev) } /* flush only the handles the TPM reports as loaded */ - XMEMSET(&in, 0, sizeof(in)); - in.capability = TPM_CAP_HANDLES; - in.property = TRANSIENT_FIRST; - in.propertyCount = MAX_CAP_HANDLES; - rc = TPM2_GetCapability(&in, &out); - if (rc != TPM_RC_SUCCESS || - out.capabilityData.capability != TPM_CAP_HANDLES) { - return wolfTPM2_UnloadHandles(dev, TRANSIENT_FIRST, MAX_HANDLE_NUM); - } - XMEMSET(&handle, 0, sizeof(handle)); wolfTPM2_CopyAuth(&handle.auth, &dev->session[0].auth); - for (i = 0; i < handles->count; i++) { - handle.hndl = handles->handle[i]; - (void)wolfTPM2_UnloadHandle(dev, &handle); + for (;;) { + XMEMSET(&in, 0, sizeof(in)); + in.capability = TPM_CAP_HANDLES; + in.property = next; + in.propertyCount = MAX_CAP_HANDLES; + rc = TPM2_GetCapability(&in, &out); + if (rc == TPM_RC_SUCCESS && + out.capabilityData.capability != TPM_CAP_HANDLES) { + rc = TPM_RC_FAILURE; + } + if (rc != TPM_RC_SUCCESS) { + break; + } + for (i = 0; i < handles->count; i++) { + if (handles->handle[i] < next || + (handles->handle[i] & HR_RANGE_MASK) != HR_TRANSIENT) { + break; + } + handle.hndl = handles->handle[i]; + (void)wolfTPM2_UnloadHandle(dev, &handle); + next = handle.hndl + 1; + } + /* continue only after a non-empty page of ascending transient + * handles that reports more data */ + if (!out.moreData || handles->count == 0 || i < handles->count) { + break; + } } TPM2_ForceZero(&handle, sizeof(handle)); - return TPM_RC_SUCCESS; + + if (rc != TPM_RC_SUCCESS && next == TRANSIENT_FIRST) { + return wolfTPM2_UnloadHandles(dev, TRANSIENT_FIRST, MAX_HANDLE_NUM); + } + return rc; } int wolfTPM2_ChangeHierarchyAuth(WOLFTPM2_DEV* dev, WOLFTPM2_SESSION* session, diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 0530f83e2..e2543426c 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -1871,6 +1871,7 @@ static void test_wolfTPM2_UnloadHandles_AllTransient(void) int rc; WOLFTPM2_DEV dev; WOLFTPM2_KEY srk; + int eccSupported = 0; rc = wolfTPM2_Init(&dev, TPM2_IoCb, NULL); AssertIntEQ(rc, 0); @@ -1878,8 +1879,10 @@ static void test_wolfTPM2_UnloadHandles_AllTransient(void) rc = wolfTPM2_UnloadHandles_AllTransient(NULL); AssertIntEQ(rc, BAD_FUNC_ARG); + AssertIntEQ(wolfTPM2_IsAlgSupported(&dev, TPM_ALG_ECC, &eccSupported), 0); XMEMSET(&srk, 0, sizeof(srk)); - rc = wolfTPM2_CreateSRK(&dev, &srk, TPM_ALG_ECC, NULL, 0); + rc = wolfTPM2_CreateSRK(&dev, &srk, + eccSupported ? TPM_ALG_ECC : TPM_ALG_RSA, NULL, 0); AssertIntEQ(rc, 0); AssertIntGE(wolfTPM2_GetHandles(TRANSIENT_FIRST, NULL), 1); diff --git a/wolftpm/tpm2_wrap.h b/wolftpm/tpm2_wrap.h index d648e3f7a..f24119188 100644 --- a/wolftpm/tpm2_wrap.h +++ b/wolftpm/tpm2_wrap.h @@ -4042,7 +4042,7 @@ WOLFTPM_API int wolfTPM2_UnloadHandles(WOLFTPM2_DEV* dev, word32 handleStart, /*! \ingroup wolfTPM2_Wrappers \brief One-shot API to unload all transient TPM handles - \note Flushes the transient handles the TPM reports as loaded, or the first MAX_HANDLE_NUM if TPM_CAP_HANDLES fails + \note Flushes the transient handles the TPM reports as loaded, following TPM_CAP_HANDLES continuation pages, or the first MAX_HANDLE_NUM if the first TPM_CAP_HANDLES query fails \note If there are Primary Keys as transient objects, they need to be recreated before TPM keys can be used \return TPM_RC_SUCCESS: successful