From 315e318ff0a411cd3ae246d0c9efe004227866dd Mon Sep 17 00:00:00 2001 From: David Garske Date: Tue, 15 Sep 2026 17:07:29 -0700 Subject: [PATCH 1/7] Add native helpers to query algorithm and PCR bank support --- src/tpm2.c | 88 ++++++++++++++++++++++++++++++++++++++++++++++ src/tpm2_wrap.c | 32 ++--------------- tests/unit_tests.c | 50 ++++++++++++++++++++++++++ wolftpm/tpm2.h | 40 +++++++++++++++++++++ 4 files changed, 180 insertions(+), 30 deletions(-) diff --git a/src/tpm2.c b/src/tpm2.c index 15312139..b72dbbc7 100644 --- a/src/tpm2.c +++ b/src/tpm2.c @@ -7919,6 +7919,94 @@ void TPM2_PrintPublicArea(const TPM2B_PUBLIC* pub) } #endif /* DEBUG_WOLFTPM */ +/* TPM_CAP_ALGS returns algorithms with ID >= property, so a match at index 0 + * means implemented. Fails closed: *isSupported is 0 on any error. */ +int TPM2_IsAlgSupported(TPM_ALG_ID alg, int* isSupported) +{ + int rc; + GetCapability_In in; + GetCapability_Out out; + TPML_ALG_PROPERTY* algs; + + if (isSupported == NULL) { + return BAD_FUNC_ARG; + } + *isSupported = 0; + + XMEMSET(&in, 0, sizeof(in)); + XMEMSET(&out, 0, sizeof(out)); + in.capability = TPM_CAP_ALGS; + in.property = alg; + in.propertyCount = 1; + rc = TPM2_GetCapability(&in, &out); + if (rc != TPM_RC_SUCCESS) { + return rc; /* query failure, distinct from "not supported" */ + } + /* union - confirm the capability asked for */ + if (out.capabilityData.capability != TPM_CAP_ALGS) { + return TPM_RC_VALUE; + } + + algs = &out.capabilityData.data.algorithms; + if (algs->count >= 1 && algs->algProperties[0].alg == alg) { + *isSupported = 1; + } + return TPM_RC_SUCCESS; +} + +/* Implementing a hash and allocating a bank for it are separate: a TPM may + * offer SHA-1 while allocating no SHA-1 bank, and a selection naming an + * unallocated bank is rejected. Ask before TPM2_SetupPCRSel(). Fails closed. */ +int TPM2_IsPcrBankAllocated(TPM_ALG_ID hashAlg, int pcrIndex, int* isAllocated) +{ + int rc; + word32 i; + GetCapability_In in; + GetCapability_Out out; + TPML_PCR_SELECTION* banks; + + if (isAllocated == NULL) { + return BAD_FUNC_ARG; + } + *isAllocated = 0; + if (pcrIndex < 0) { + return BAD_FUNC_ARG; + } + + XMEMSET(&in, 0, sizeof(in)); + XMEMSET(&out, 0, sizeof(out)); + in.capability = TPM_CAP_PCRS; + in.property = 0; + in.propertyCount = HASH_COUNT; /* all assigned banks */ + rc = TPM2_GetCapability(&in, &out); + if (rc != TPM_RC_SUCCESS) { + return rc; + } + if (out.capabilityData.capability != TPM_CAP_PCRS) { + return TPM_RC_VALUE; + } + + banks = &out.capabilityData.data.assignedPCR; + /* HASH_COUNT is this build's TPML capacity, not the TPM's bank count, so a + * TPM with more banks returns a partial page. Reporting "not allocated" + * from a truncated list would be a false negative. */ + if (out.moreData == YES) { + return TPM_RC_SIZE; + } + for (i = 0; i < banks->count; i++) { + if (banks->pcrSelections[i].hash != hashAlg) { + continue; + } + if ((pcrIndex / 8) < (int)banks->pcrSelections[i].sizeofSelect && + (banks->pcrSelections[i].pcrSelect[pcrIndex / 8] & + (1 << (pcrIndex % 8))) != 0) { + *isAllocated = 1; + break; + } + } + return TPM_RC_SUCCESS; +} + /******************************************************************************/ /* --- END Helpful API's -- */ /******************************************************************************/ diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index ca7ff76a..38bd48b5 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -1250,15 +1250,9 @@ int wolfTPM2_GetCapabilities(WOLFTPM2_DEV* dev, WOLFTPM2_CAPS* cap) * Returns TPM_RC_SUCCESS with *isSupported set to 1 (supported) or 0 (not * supported); on any failure a non-zero rc is returned and *isSupported is set * to 0 so a caller that ignores the rc fails closed. - * Queries TPM_CAP_ALGS: the TPM returns algorithms with ID >= property, so a - * match at index 0 for a single-property query means it is implemented. */ + * Delegates to TPM2_IsAlgSupported(); dev is validated but unused. */ int wolfTPM2_IsAlgSupported(WOLFTPM2_DEV* dev, TPM_ALG_ID alg, int* isSupported) { - int rc; - GetCapability_In in; - GetCapability_Out out; - TPML_ALG_PROPERTY* algs; - if (isSupported == NULL) { return BAD_FUNC_ARG; } @@ -1267,29 +1261,7 @@ int wolfTPM2_IsAlgSupported(WOLFTPM2_DEV* dev, TPM_ALG_ID alg, int* isSupported) if (dev == NULL) { return BAD_FUNC_ARG; } - XMEMSET(&in, 0, sizeof(in)); - XMEMSET(&out, 0, sizeof(out)); - in.capability = TPM_CAP_ALGS; - in.property = alg; - in.propertyCount = 1; - rc = TPM2_GetCapability(&in, &out); - if (rc != TPM_RC_SUCCESS) { - return rc; /* query failure, distinct from "not supported" */ - } - /* capabilityData.data is a union - confirm the TPM answered with the - * capability we asked for before reading the algorithm member, so a - * non-conforming response cannot be reinterpreted as an algorithm - * property. */ - if (out.capabilityData.capability != TPM_CAP_ALGS) { - return TPM_RC_VALUE; - } - /* The TPM returns algorithms with ID >= property; a match at index 0 - * means the requested algorithm is implemented. */ - algs = &out.capabilityData.data.algorithms; - if (algs->count >= 1 && algs->algProperties[0].alg == alg) { - *isSupported = 1; - } - return TPM_RC_SUCCESS; + return TPM2_IsAlgSupported(alg, isSupported); } int wolfTPM2_GetHandles(TPM_HANDLE handle, TPML_HANDLE* handles) diff --git a/tests/unit_tests.c b/tests/unit_tests.c index a139bf88..43d189d9 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -1408,6 +1408,55 @@ static void test_wolfTPM2_IsAlgSupported(void) #endif /* WOLFTPM_SWTPM */ } +/* TPM2_IsPcrBankAllocated: argument validation always, plus a live query on + * the simulator. Mirrors test_wolfTPM2_IsAlgSupported. */ +static void test_TPM2_IsPcrBankAllocated(void) +{ + int isAllocated = 1; /* seeded true to prove the error paths clear it */ +#if defined(WOLFTPM_SWTPM) + int rc; + WOLFTPM2_DEV dev; +#endif + + /* NULL out-param */ + AssertIntEQ(TPM2_IsPcrBankAllocated(TPM_ALG_SHA256, 0, NULL), + BAD_FUNC_ARG); + /* negative index must fail and must not leave the out-param set */ + AssertIntEQ(TPM2_IsPcrBankAllocated(TPM_ALG_SHA256, -1, &isAllocated), + BAD_FUNC_ARG); + AssertIntEQ(isAllocated, 0); + +#if defined(WOLFTPM_SWTPM) + XMEMSET(&dev, 0, sizeof(dev)); + rc = wolfTPM2_Init(&dev, TPM2_IoCb, NULL); + AssertIntEQ(rc, 0); + + /* Every TPM 2.0 part allocates a SHA2-256 bank covering PCR 0 */ + isAllocated = 0; + AssertIntEQ(TPM2_IsPcrBankAllocated(TPM_ALG_SHA256, 0, &isAllocated), + TPM_RC_SUCCESS); + AssertIntEQ(isAllocated, 1); + + /* A hash no bank uses reports not-allocated, with a success rc because + * the query itself worked - the distinction this API exists to make. */ + isAllocated = 1; + AssertIntEQ(TPM2_IsPcrBankAllocated((TPM_ALG_ID)0x7FFF, 0, &isAllocated), + TPM_RC_SUCCESS); + AssertIntEQ(isAllocated, 0); + + /* An index beyond the PCR count is not allocated in any bank */ + isAllocated = 1; + AssertIntEQ(TPM2_IsPcrBankAllocated(TPM_ALG_SHA256, 250, &isAllocated), + TPM_RC_SUCCESS); + AssertIntEQ(isAllocated, 0); + + wolfTPM2_Cleanup(&dev); + printf("Test PcrBank: %-40s Passed\n", "Args + Query:"); +#else + printf("Test PcrBank: %-40s Passed\n", "Arg Validation:"); +#endif /* WOLFTPM_SWTPM */ +} + /* Success path for wolfTPM2_PolicyOR: satisfy one branch of a real two-branch * OR on a live policy session and confirm the TPM's running policy digest * matches the offline computation. Simulator only. */ @@ -9527,6 +9576,7 @@ int unit_tests(int argc, char *argv[]) test_wolfTPM2_FirmwareUpgrade_ex_session(); #endif test_wolfTPM2_IsAlgSupported(); + test_TPM2_IsPcrBankAllocated(); test_wolfTPM2_PolicyOR_success(); #if defined(WOLFTPM_MLDSA) && defined(WOLFTPM_MLKEM) /* Run non-TPM-dependent tests first */ diff --git a/wolftpm/tpm2.h b/wolftpm/tpm2.h index 77ae060a..f15f5e11 100644 --- a/wolftpm/tpm2.h +++ b/wolftpm/tpm2.h @@ -4135,6 +4135,46 @@ WOLFTPM_API void TPM2_SetupPCRSel(TPML_PCR_SELECTION* pcr, TPM_ALG_ID alg, WOLFTPM_API void TPM2_SetupPCRSelArray(TPML_PCR_SELECTION* pcr, TPM_ALG_ID alg, byte* pcrArray, word32 pcrArraySz); +/*! + \ingroup TPM2_Proprietary + \brief Report whether the TPM implements a given algorithm + + \note Queries TPM_CAP_ALGS. Fails closed: *isSupported is 0 on any error, + so a query failure cannot be mistaken for "supported". + + \return TPM_RC_SUCCESS: query completed; *isSupported is 1 or 0 + \return BAD_FUNC_ARG: isSupported is NULL + + \param alg the algorithm identifier to test (for example TPM_ALG_SHA512) + \param isSupported output, set to 1 if implemented by the TPM, else 0 + + \sa TPM2_IsPcrBankAllocated +*/ +WOLFTPM_API int TPM2_IsAlgSupported(TPM_ALG_ID alg, int* isSupported); + +/*! + \ingroup TPM2_Proprietary + \brief Report whether a PCR index is allocated in a bank of a given hash + + \note Queries TPM_CAP_PCRS. Implementing a hash and allocating a bank for + it are separate: a TPM may offer SHA-1 while allocating no SHA-1 + bank, and a selection naming an unallocated bank is rejected. Ask + before building a selection with TPM2_SetupPCRSel(). Fails closed: + *isAllocated is 0 on any error. + + \return TPM_RC_SUCCESS: query completed; *isAllocated is 1 or 0 + \return BAD_FUNC_ARG: isAllocated is NULL or pcrIndex is negative + + \param hashAlg the PCR bank hash algorithm (for example TPM_ALG_SHA256) + \param pcrIndex the PCR index to test + \param isAllocated output, set to 1 if allocated in that bank, else 0 + + \sa TPM2_SetupPCRSel + \sa TPM2_IsAlgSupported +*/ +WOLFTPM_API int TPM2_IsPcrBankAllocated(TPM_ALG_ID hashAlg, int pcrIndex, + int* isAllocated); + /*! \ingroup TPM2_Proprietary \brief Get a human readable string for any TPM 2.0 return code From 8389c5c0d533d3dc7790513014c58e4e6a555bb2 Mon Sep 17 00:00:00 2001 From: David Garske Date: Tue, 15 Sep 2026 17:07:29 -0700 Subject: [PATCH 2/7] Reject oversized signatures before TPM2_VerifySignature --- examples/pqc/pqc_ctrl.c | 23 +++++++ src/tpm2_wrap.c | 137 ++++++++++++++++++++++++++++++++++++++++ tests/unit_tests.c | 46 ++++++++++++++ wolftpm/tpm2.h | 4 ++ 4 files changed, 210 insertions(+) diff --git a/examples/pqc/pqc_ctrl.c b/examples/pqc/pqc_ctrl.c index 2e41c1bd..c4d9c1e2 100644 --- a/examples/pqc/pqc_ctrl.c +++ b/examples/pqc/pqc_ctrl.c @@ -430,6 +430,15 @@ static int do_mldsa(WOLFTPM2_DEV* dev, TPMI_MLDSA_PARAMETER_SET ps) if (rc != TPM_RC_SUCCESS) goto exit; rc = wolfTPM2_VerifySequenceComplete(dev, seq, &key, NULL, 0, sig, sigSz, &validation); + if (rc == BUFFER_E) { + /* Same oversize case do_hash_mldsa() reports: signing worked but the + * TPM cannot take the signature back, so skip rather than fail. */ + printf("SKIP ML-DSA-%-3s signed %d bytes, not verified: " + "signature exceeds this TPM's input buffer\n", + mldsaName(ps), sigSz); + rc = TPM_RC_SUCCESS; + goto exit_quiet; + } if (rc != TPM_RC_SUCCESS) goto exit; seq = 0; /* Complete consumed the sequence object */ @@ -448,6 +457,9 @@ static int do_mldsa(WOLFTPM2_DEV* dev, TPMI_MLDSA_PARAMETER_SET ps) printf("FAIL ML-DSA-%-3s 0x%x: %s\n", mldsaName(ps), rc, wolfTPM2_GetRCString(rc)); } +exit_quiet: + /* The size guard runs before the sequence is consumed, so seq is still + * live on BUFFER_E and must be flushed here like any other exit. */ if (seq != 0) { flushCtx.flushHandle = seq; (void)TPM2_FlushContext(&flushCtx); @@ -493,6 +505,16 @@ static int do_hash_mldsa(WOLFTPM2_DEV* dev, TPMI_MLDSA_PARAMETER_SET ps) rc = wolfTPM2_VerifyDigestSignature(dev, &key, digest, (int)sizeof(digest), sig, sigSz, NULL, 0, &validation); + if (rc == BUFFER_E) { + /* Too large for this TPM to accept back for on-TPM verification. + * Signing worked but nothing checked it, so skip, not pass. Only + * BUFFER_E means oversize; a query error still reports FAIL. */ + printf("SKIP HashML-DSA-%-3s signed %d bytes, not verified: " + "signature exceeds this TPM's input buffer\n", + mldsaName(ps), sigSz); + rc = TPM_RC_SUCCESS; + goto exit_quiet; + } if (rc != TPM_RC_SUCCESS) goto exit; if (validation.tag != TPM_ST_DIGEST_VERIFIED) { @@ -510,6 +532,7 @@ static int do_hash_mldsa(WOLFTPM2_DEV* dev, TPMI_MLDSA_PARAMETER_SET ps) printf("FAIL HashML-DSA-%-3s 0x%x: %s\n", mldsaName(ps), rc, wolfTPM2_GetRCString(rc)); } +exit_quiet: wolfTPM2_UnloadHandle(dev, &key.handle); XFREE(sig, NULL, DYNAMIC_TYPE_TMP_BUFFER); return rc; diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index 38bd48b5..7d1d6f39 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -5760,6 +5760,124 @@ int wolfTPM2_SignHash(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, } +/* Reject a signature the TPM cannot accept as a parameter. Some parts stop + * responding until a hardware reset rather than erroring, so this must not be + * left to the TPM. Two limits apply: TPM_PT_INPUT_BUFFER bounds the parameter + * and TPM_PT_MAX_COMMAND_SIZE bounds the whole command, so the capability read + * is skipped only when the signature plus overhead fits inside + * TPM_MIN_INPUT_BUFFER, the floor every conformant TPM meets. That still + * covers every ECC signature and RSA up to 4096. Above it the limit must be + * established, so this fails closed. Returns TPM_RC_SUCCESS if it fits, + * BUFFER_E if it provably does not, and the query error otherwise so callers + * can tell the two apart. */ + +/* Bytes that share the command with the signature: 10-byte header, up to two + * 4-byte handles, a 4-byte auth-area size, a password session (~9) or an HMAC + * session with a nonce and digest (~75), the digest TPM2B (up to 66), and the + * TPMT_SIGNATURE tag/alg/size fields (~8). Rounded up with margin. Only used + * to reserve room against TPM_PT_MAX_COMMAND_SIZE, so an over-estimate can + * reject a signature that would just fit; override if that ever bites. */ +#ifndef TPM_SIG_CMD_OVERHEAD +#define TPM_SIG_CMD_OVERHEAD 176 +#endif + +/* extraSz is the caller's other variable-length parameters (digest, context), + * which the fixed overhead estimate does not cover. */ +static int wolfTPM2_CheckSigInputBuffer(int sigSz, int extraSz) +{ + int rc; + GetCapability_In in; + GetCapability_Out out; + TPML_TAGGED_TPM_PROPERTY* props; + UINT32 inputBuffer; + UINT32 cmdSz; + + if (sigSz < 0 || extraSz < 0) { + return BUFFER_E; + } + cmdSz = (UINT32)sigSz + (UINT32)extraSz + TPM_SIG_CMD_OVERHEAD; + /* Overhead is included: a signature that fits the parameter floor could + * still overflow a TPM whose command limit equals that floor. */ + if (cmdSz <= TPM_MIN_INPUT_BUFFER) { + return TPM_RC_SUCCESS; + } + + XMEMSET(&in, 0, sizeof(in)); + XMEMSET(&out, 0, sizeof(out)); + in.capability = TPM_CAP_TPM_PROPERTIES; + in.property = TPM_PT_INPUT_BUFFER; + in.propertyCount = 1; + rc = TPM2_GetCapability(&in, &out); + if (rc != TPM_RC_SUCCESS) { + #ifdef DEBUG_WOLFTPM + printf("Signature size check: cannot read TPM_PT_INPUT_BUFFER " + "(0x%x), refusing a %d byte signature\n", rc, sigSz); + #endif + return rc; /* query failure, distinct from a genuine oversize */ + } + /* union - confirm the capability and property asked for */ + props = &out.capabilityData.data.tpmProperties; + if (out.capabilityData.capability != TPM_CAP_TPM_PROPERTIES || + props->count == 0 || + props->tpmProperty[0].property != TPM_PT_INPUT_BUFFER) { + #ifdef DEBUG_WOLFTPM + printf("Signature size check: unexpected capability response, " + "refusing a %d byte signature\n", sigSz); + #endif + return TPM_RC_VALUE; /* not an oversize; the TPM answered wrongly */ + } + + inputBuffer = props->tpmProperty[0].value; + if (inputBuffer == 0) { + return TPM_RC_VALUE; /* nonsensical limit, treat as unreadable */ + } + if ((UINT32)sigSz > inputBuffer) { + #ifdef DEBUG_WOLFTPM + printf("Signature size check: signature %d bytes exceeds the TPM's " + "%u byte input buffer\n", sigSz, (unsigned int)inputBuffer); + #endif + return BUFFER_E; + } + + /* The whole command has to fit too, and that is the limit this guard + * exists to respect, so an unreadable or malformed answer fails closed + * rather than letting the oversized command through. */ + XMEMSET(&in, 0, sizeof(in)); + XMEMSET(&out, 0, sizeof(out)); + in.capability = TPM_CAP_TPM_PROPERTIES; + in.property = TPM_PT_MAX_COMMAND_SIZE; + in.propertyCount = 1; + rc = TPM2_GetCapability(&in, &out); + if (rc != TPM_RC_SUCCESS) { + #ifdef DEBUG_WOLFTPM + printf("Signature size check: cannot read TPM_PT_MAX_COMMAND_SIZE " + "(0x%x), refusing a %d byte signature\n", rc, sigSz); + #endif + return rc; + } + props = &out.capabilityData.data.tpmProperties; + if (out.capabilityData.capability != TPM_CAP_TPM_PROPERTIES || + props->count == 0 || + props->tpmProperty[0].property != TPM_PT_MAX_COMMAND_SIZE || + props->tpmProperty[0].value == 0) { + #ifdef DEBUG_WOLFTPM + printf("Signature size check: unexpected command-size response, " + "refusing a %d byte signature\n", sigSz); + #endif + return TPM_RC_VALUE; + } + if (cmdSz > props->tpmProperty[0].value) { + #ifdef DEBUG_WOLFTPM + printf("Signature size check: command %u bytes exceeds the TPM's " + "%u byte command limit\n", (unsigned int)cmdSz, + (unsigned int)props->tpmProperty[0].value); + #endif + return BUFFER_E; + } + + return TPM_RC_SUCCESS; +} + /* sigAlg: TPM_ALG_RSASSA, TPM_ALG_RSAPSS, TPM_ALG_ECDSA or TPM_ALG_ECDAA */ /* hashAlg: TPM_ALG_SHA1, TPM_ALG_SHA256, TPM_ALG_SHA384 or TPM_ALG_SHA512 */ int wolfTPM2_VerifyHashTicket(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, @@ -5777,6 +5895,11 @@ int wolfTPM2_VerifyHashTicket(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, return BAD_FUNC_ARG; } + rc = wolfTPM2_CheckSigInputBuffer(sigSz, digestSz); + if (rc != TPM_RC_SUCCESS) { + return rc; + } + if (key->pub.publicArea.type == TPM_ALG_ECC) { if (sigAlg == TPM_ALG_NULL) sigAlg = key->pub.publicArea.parameters.eccDetail.scheme.scheme; @@ -6206,6 +6329,15 @@ int wolfTPM2_VerifySequenceComplete(WOLFTPM2_DEV* dev, return BAD_FUNC_ARG; } + /* Before the sequence is advanced: bailing out after SequenceUpdate + * would leave the sequence slot allocated. The data is hashed by the TPM + * rather than carried in the verify command, so only the signature and + * the fixed overhead count here. */ + rc = wolfTPM2_CheckSigInputBuffer(sigSz, 0); + if (rc != TPM_RC_SUCCESS) { + return rc; + } + /* Validate per-key-type sigSz BEFORE the internal SequenceUpdate * call. Otherwise we advance the TPM-side sequence and then bail out * before Complete, leaving the slot allocated until the caller @@ -6493,6 +6625,11 @@ int wolfTPM2_VerifyDigestSignature(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* key, return BAD_FUNC_ARG; } + rc = wolfTPM2_CheckSigInputBuffer(sigSz, digestSz + contextSz); + if (rc != TPM_RC_SUCCESS) { + return rc; + } + XMEMSET(&verifyDigestSigIn, 0, sizeof(verifyDigestSigIn)); verifyDigestSigIn.keyHandle = key->handle.hndl; verifyDigestSigIn.digest.size = (UINT16)digestSz; diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 43d189d9..e12305af 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -8619,6 +8619,49 @@ static void test_TPM2_GetHashDigestSize_AllAlgs(void) * only when both families are present (a WOLFTPM_NO_MLDSA or WOLFTPM_NO_MLKEM * build excludes the matching wrapper definitions). CI always builds full * PQC, so coverage is unchanged there. */ +/* The signature-size gate is static, so reach it through wolfTPM2_VerifyHashTicket: + * that caller is built in every configuration and passes sigSz straight to the + * gate, so the gate's own checks are what answer here. */ +static void test_wolfTPM2_SigInputBufferGate(void) +{ + WOLFTPM2_DEV dev; + WOLFTPM2_KEY key; + byte digest[32]; + byte sig[64]; +#if defined(WOLFTPM_SWTPM) + int rc; +#endif + + XMEMSET(&dev, 0, sizeof(dev)); + XMEMSET(&key, 0, sizeof(key)); + XMEMSET(digest, 0, sizeof(digest)); + XMEMSET(sig, 0, sizeof(sig)); + + /* A negative size must be refused as a size problem, not read as huge */ + AssertIntEQ(wolfTPM2_VerifyHashTicket(&dev, &key, sig, -1, digest, + (int)sizeof(digest), TPM_ALG_NULL, TPM_ALG_SHA256, NULL), BUFFER_E); + +#if defined(WOLFTPM_SWTPM) + rc = wolfTPM2_Init(&dev, TPM2_IoCb, NULL); + AssertIntEQ(rc, 0); + + /* Below the parameter floor the gate short-circuits without a TPM query, + * so this gets past it and fails later on the empty key instead */ + AssertIntNE(wolfTPM2_VerifyHashTicket(&dev, &key, sig, (int)sizeof(sig), + digest, (int)sizeof(digest), TPM_ALG_NULL, TPM_ALG_SHA256, NULL), + BUFFER_E); + + /* Larger than any TPM input buffer must be refused up front */ + AssertIntEQ(wolfTPM2_VerifyHashTicket(&dev, &key, sig, 0x7FFFFFFF, digest, + (int)sizeof(digest), TPM_ALG_NULL, TPM_ALG_SHA256, NULL), BUFFER_E); + + wolfTPM2_Cleanup(&dev); + printf("Test TPM Wrapper: %-40s Passed\n", "SigInputBuffer gate:"); +#else + printf("Test TPM Wrapper: %-40s Passed\n", "SigInputBuffer args:"); +#endif +} + #if defined(WOLFTPM_MLDSA) && defined(WOLFTPM_MLKEM) /* Post-Quantum Cryptography (PQC) Unit Tests - TPM 2.0 v185 */ @@ -9586,6 +9629,9 @@ int unit_tests(int argc, char *argv[]) test_wolfTPM2_PQC(); test_wolfTPM2_VerifySequence_NoLeak(); #endif + /* The gate guards classical RSA/ECC verifies too, so it must not sit + * behind the post-quantum guard */ + test_wolfTPM2_SigInputBufferGate(); test_wolfTPM2_Cleanup(); test_wolfTPM2_Reset_contract(); test_wolfTPM2_thread_local_storage(); diff --git a/wolftpm/tpm2.h b/wolftpm/tpm2.h index f15f5e11..6d4cb2ed 100644 --- a/wolftpm/tpm2.h +++ b/wolftpm/tpm2.h @@ -744,6 +744,10 @@ typedef enum { } TPM_PT_T; typedef UINT32 TPM_PT; +/* Smallest TPM_PT_INPUT_BUFFER a conformant TPM may report (TCG Part 2), so a + * parameter at or below it fits without reading the capability. */ +#define TPM_MIN_INPUT_BUFFER 1024 + /* PCR Property Tag */ typedef enum { TPM_PT_PCR_FIRST = 0x00000000, From b99540970a3311c5e1a5b3f3664a702a9481b2b5 Mon Sep 17 00:00:00 2001 From: David Garske Date: Tue, 15 Sep 2026 17:07:29 -0700 Subject: [PATCH 3/7] Skip example paths for features the TPM does not implement --- examples/bench/bench.c | 10 ++-- examples/native/native_test.c | 85 ++++++++++++++++++++++++--------- examples/wrap/encrypt_decrypt.c | 12 ++--- examples/wrap/wrap_test.c | 37 ++++++++------ src/tpm2_wrap.c | 2 +- tests/unit_tests.c | 25 ++++++++++ wolftpm/tpm2.h | 40 +++++++++++----- 7 files changed, 151 insertions(+), 60 deletions(-) diff --git a/examples/bench/bench.c b/examples/bench/bench.c index 37e96b8f..31d6d533 100644 --- a/examples/bench/bench.c +++ b/examples/bench/bench.c @@ -129,7 +129,8 @@ static void bench_stats_asym_finish(const char* algo, int strength, * can skip it instead of aborting). Masks parameter bits on FMT1 codes. */ static int bench_unsupported(int rc) { - return ((rc & 0xBF) == TPM_RC_SCHEME) || WOLFTPM_IS_COMMAND_UNAVAILABLE(rc); + return ((rc & 0xBF) == TPM_RC_SCHEME) || + WOLFTPM_IS_COMMAND_UNAVAILABLE_OR_DISABLED(rc); } /* Print timing on success, "Skipped" if the op was not implemented. Returns @@ -203,9 +204,10 @@ static int bench_sym_aes(WOLFTPM2_DEV* dev, WOLFTPM2_KEY* storageKey, XMEMSET(iv, 0, sizeof(iv)); rc = wolfTPM2_EncryptDecrypt(dev, &aesKey, in, out, inOutSz, iv, sizeof(iv), isDecrypt); - if (WOLFTPM_IS_COMMAND_UNAVAILABLE(rc)) { - printf("Encrypt/Decrypt unavailable\n"); - break; + if (bench_unsupported(rc)) { + printf("%-16s Skipped (not supported)\n", desc); + rc = 0; + goto exit; } if (rc != 0) goto exit; } while (bench_stats_check(start, &count, maxDuration)); diff --git a/examples/native/native_test.c b/examples/native/native_test.c index 11d7c0ad..ad9580bb 100644 --- a/examples/native/native_test.c +++ b/examples/native/native_test.c @@ -339,6 +339,7 @@ int TPM2_Native_Test(void* userCtx) int TPM2_Native_TestArgs(void* userCtx, int argc, char *argv[]) { int rc; + int isAllocated = 0; TPM2_CTX tpm2Ctx; union { @@ -709,6 +710,19 @@ int TPM2_Native_TestArgs(void* userCtx, int argc, char *argv[]) } printf("TPM2_ReadClock: success\n"); + /* TEST_WRAP_DIGEST is SHA-1 on some targets, and a TPM that allocates no + * SHA-1 bank would abort every PCR step below. Check once, up front. */ + rc = TPM2_IsPcrBankAllocated(TEST_WRAP_DIGEST, 0, &isAllocated); + if (rc != TPM_RC_SUCCESS) { + printf("TPM2_IsPcrBankAllocated failed 0x%x: %s\n", rc, + TPM2_GetRCString(rc)); + goto exit; + } + if (!isAllocated) { + printf("PCR tests skipped (no 0x%x PCR bank allocated)\n", + (unsigned int)TEST_WRAP_DIGEST); + goto pcr_tests_done; + } /* PCR Read */ for (i=0; i= 0 gate. */ + AssertIntNE(0, WOLFTPM_IS_COMMAND_DISABLED((int)TPM_RC_DISABLED)); + AssertIntNE(0, WOLFTPM_IS_COMMAND_DISABLED(0x000b0120)); /* vendor bits */ + AssertIntEQ(0, WOLFTPM_IS_COMMAND_DISABLED((int)TPM_RC_COMMAND_CODE)); + AssertIntEQ(0, WOLFTPM_IS_COMMAND_DISABLED((int)TPM_RC_SUCCESS)); + AssertIntEQ(0, WOLFTPM_IS_COMMAND_DISABLED(-189)); + AssertIntEQ(0, WOLFTPM_IS_COMMAND_DISABLED(-1)); + + /* The combined form accepts either, and nothing else. */ + AssertIntNE(0, + WOLFTPM_IS_COMMAND_UNAVAILABLE_OR_DISABLED((int)TPM_RC_COMMAND_CODE)); + AssertIntNE(0, + WOLFTPM_IS_COMMAND_UNAVAILABLE_OR_DISABLED((int)TPM_RC_DISABLED)); + AssertIntEQ(0, + WOLFTPM_IS_COMMAND_UNAVAILABLE_OR_DISABLED((int)TPM_RC_SUCCESS)); + AssertIntEQ(0, WOLFTPM_IS_COMMAND_UNAVAILABLE_OR_DISABLED(-189)); + + /* The shared base macro underlying all three. Note TPM_RC_VALUE is not + * usable here: tpm2_asn.h defines it as an ASN error (-203), shadowing the + * response code of the same name from tpm2.h in this translation unit. */ + AssertIntNE(0, WOLFTPM_RC_IS((int)TPM_RC_DISABLED, TPM_RC_DISABLED)); + AssertIntEQ(0, WOLFTPM_RC_IS((int)TPM_RC_DISABLED, TPM_RC_COMMAND_CODE)); + AssertIntEQ(0, WOLFTPM_RC_IS(-189, TPM_RC_DISABLED)); + printf("Test TPM Wrapper: %-40s Passed\n", "IsCommandUnavailable:"); } diff --git a/wolftpm/tpm2.h b/wolftpm/tpm2.h index 6d4cb2ed..2cf91c0f 100644 --- a/wolftpm/tpm2.h +++ b/wolftpm/tpm2.h @@ -2205,21 +2205,35 @@ struct wolfTPM_winContext { #define TPM_E_COMMAND_BLOCKED (0x80280400) #endif -/* Mask off vendor/layer high bits so a vendor-decorated TPM_RC_COMMAND_CODE - * (e.g. NS350 returns 0x000b0143 for 0x143) still matches. Gate on >= 0 so a - * propagated negative wolfCrypt error (e.g. -189) is never misread as an - * unavailable command. TPM_E_COMMAND_BLOCKED is a Windows HRESULT (negative), - * matched exactly. */ -#define WOLFTPM_IS_COMMAND_UNAVAILABLE(code) \ - (((code) >= 0 && \ - (((UINT32)(code)) & 0xFFFFu) == (UINT32)TPM_RC_COMMAND_CODE) || \ - (code) == (int)TPM_E_COMMAND_BLOCKED) -#else -#define WOLFTPM_IS_COMMAND_UNAVAILABLE(code) \ - ((code) >= 0 && \ - (((UINT32)(code)) & 0xFFFFu) == (UINT32)TPM_RC_COMMAND_CODE) #endif /* WOLFTPM_WINAPI */ +/* Compare a return code against a TPM_RC, masking off vendor/layer high bits + * so a vendor-decorated code (NS350 returns 0x000b0143 for 0x143) still + * matches. Gate on >= 0 so a propagated negative wolfCrypt error (e.g. -189) + * is never misread as a TPM response code. */ +#define WOLFTPM_RC_IS(code, rc) \ + ((code) >= 0 && (((UINT32)(code)) & 0xFFFFu) == (UINT32)(rc)) + +/* The TPM does not implement this command. TPM_E_COMMAND_BLOCKED is a Windows + * HRESULT (negative), so it is matched exactly rather than masked. */ +#ifdef WOLFTPM_WINAPI + #define WOLFTPM_IS_COMMAND_UNAVAILABLE(code) \ + (WOLFTPM_RC_IS(code, TPM_RC_COMMAND_CODE) || \ + (code) == (int)TPM_E_COMMAND_BLOCKED) +#else + #define WOLFTPM_IS_COMMAND_UNAVAILABLE(code) \ + WOLFTPM_RC_IS(code, TPM_RC_COMMAND_CODE) +#endif + +/* Implemented but switched off, commonly TPM2_EncryptDecrypt for export + * controls; answers TPM_RC_DISABLED not TPM_RC_COMMAND_CODE. */ +#define WOLFTPM_IS_COMMAND_DISABLED(code) \ + WOLFTPM_RC_IS(code, TPM_RC_DISABLED) + +/* Either form of "the TPM will not run this command". */ +#define WOLFTPM_IS_COMMAND_UNAVAILABLE_OR_DISABLED(code) \ + (WOLFTPM_IS_COMMAND_UNAVAILABLE(code) || WOLFTPM_IS_COMMAND_DISABLED(code)) + /* make sure advanced IO is enabled for I2C */ #ifdef WOLFTPM_I2C #undef WOLFTPM_ADV_IO From 3bf8e1999e4f8ca3a38e7112fe4a09153cf13fc8 Mon Sep 17 00:00:00 2001 From: David Garske Date: Tue, 15 Sep 2026 17:07:29 -0700 Subject: [PATCH 4/7] Bound TIS wait loops by real time where a monotonic clock exists --- src/tpm2_tis.c | 140 ++++++++++++++++++++++++++++++++++++++++--- wolftpm/tpm2_types.h | 57 ++++++++++++++++++ 2 files changed, 189 insertions(+), 8 deletions(-) diff --git a/src/tpm2_tis.c b/src/tpm2_tis.c index 0a02df57..cdd6bcfb 100644 --- a/src/tpm2_tis.c +++ b/src/tpm2_tis.c @@ -417,22 +417,140 @@ int TPM2_TIS_Status(TPM2_CTX* ctx, byte* status) sizeof(*status)); } +/* Budget for the wait loops below. An iteration count makes the real timeout + * depend on host speed, so a >20 s RSA key generation times out intermittently. + * Use TPM_TIMEOUT_MS of real time where a monotonic clock exists; elsewhere + * keep counting exactly as before so no port gains a requirement. */ +typedef struct TPM2_TIS_TIMEOUT { +#ifdef WOLFTPM_HAVE_MONOTONIC_MS + word32 start; + word32 last; /* most recent tick that differed from the one before */ + int stagnant; /* consecutive polls with no clock progress */ +#endif +#if defined(DEBUG_WOLFTPM) && !defined(WOLFTPM_NO_STD_HEADERS) && \ + defined(WOLFTPM_HAVE_MONOTONIC_MS) + #define WOLFTPM_TIS_PROGRESS + word32 secs; /* whole seconds already marked with a dot */ +#endif + int tries; +} TPM2_TIS_TIMEOUT; + +static void TPM2_TIS_TimeoutStart(TPM2_TIS_TIMEOUT* to) +{ + XMEMSET(to, 0, sizeof(*to)); + to->tries = TPM_TIMEOUT_TRIES; +#ifdef WOLFTPM_HAVE_MONOTONIC_MS + /* A zero tick is a real timestamp (boot, or the word32 wrapping through + * 0), so it must not be treated as an unreadable clock. */ + to->start = XTPM_GET_TIMEMS(); + to->last = to->start; +#endif +} + +/* Returns 1 once the budget is spent, 0 while there is still time. */ +static int TPM2_TIS_TimeoutExpired(TPM2_TIS_TIMEOUT* to) +{ +#ifdef WOLFTPM_HAVE_MONOTONIC_MS + word32 now, elapsed; +#endif + + if (to->tries > 0) { + to->tries--; + } +#ifdef WOLFTPM_HAVE_MONOTONIC_MS + now = XTPM_GET_TIMEMS(); + /* unsigned subtraction stays correct across the word32 wrap */ + elapsed = (word32)(now - to->start); + + /* A wait never legitimately spans hours, so an implausible elapsed means + * the tick source returned garbage (a failed read yields zero, which the + * subtraction turns into a huge value). Treat it as no progress rather + * than expiring on one bad sample. */ + if (elapsed > (word32)(TPM_TIMEOUT_MS) * 10u) { + now = to->last; + #ifdef WOLFTPM_TIS_PROGRESS + elapsed = (word32)(now - to->start); + #endif + } + else if (elapsed >= TPM_TIMEOUT_MS) { + return 1; + } + + /* A clock that stops advancing - at startup or part way through - would + * otherwise never reach the deadline and the wait would hang. Bound it by + * consecutive polls that saw no progress, independent of the iteration + * cap, so this holds when TPM_TIMEOUT_TRIES is zero ("no cap") too. */ + if (now == to->last) { + if (++to->stagnant >= TPM_TIMEOUT_STAGNANT_POLLS) { + return 1; + } + } + else { + to->last = now; + to->stagnant = 0; + } + +#ifdef WOLFTPM_TIS_PROGRESS + /* Waits of a minute or more are normal for key generation, so show a dot + * per second rather than let a slow command look like a hang. */ + if (elapsed / 1000u > to->secs) { + to->secs = elapsed / 1000u; + printf("."); + fflush(stdout); + } +#endif + return 0; +#else + return (to->tries <= 0) ? 1 : 0; +#endif +} + +#ifdef WOLFTPM_TIS_PROGRESS +/* End the dot line, if any were printed. */ +static void TPM2_TIS_TimeoutDone(TPM2_TIS_TIMEOUT* to) +{ + if (to->secs > 0) { + printf("\n"); + fflush(stdout); + } +} +#else + #define TPM2_TIS_TimeoutDone(to) (void)(to) +#endif + +#ifdef WOLFTPM_DEBUG_TIMEOUT +/* Elapsed ms where a clock exists, else polls taken. */ +static word32 TPM2_TIS_TimeoutSpent(TPM2_TIS_TIMEOUT* to) +{ +#ifdef WOLFTPM_HAVE_MONOTONIC_MS + return (word32)(XTPM_GET_TIMEMS() - to->start); +#else + return (word32)(TPM_TIMEOUT_TRIES - to->tries); +#endif +} +#endif + int TPM2_TIS_WaitForStatus(TPM2_CTX* ctx, byte status, byte status_mask) { int rc; - int timeout = TPM_TIMEOUT_TRIES; + int expired = 0; + TPM2_TIS_TIMEOUT to; byte reg = 0; + TPM2_TIS_TimeoutStart(&to); do { rc = TPM2_TIS_Status(ctx, ®); if (rc == TPM_RC_SUCCESS && (reg & status) == status_mask) break; XTPM_WAIT(); - } while (rc == TPM_RC_SUCCESS && --timeout > 0); + expired = TPM2_TIS_TimeoutExpired(&to); + } while (rc == TPM_RC_SUCCESS && !expired); + TPM2_TIS_TimeoutDone(&to); #ifdef WOLFTPM_DEBUG_TIMEOUT - printf("TIS_WaitForStatus: Timeout %d\n", TPM_TIMEOUT_TRIES - timeout); + printf("TIS_WaitForStatus: spent %u\n", + (unsigned int)TPM2_TIS_TimeoutSpent(&to)); #endif - if (timeout <= 0) + if (expired) return TPM_RC_TIMEOUT; return rc; } @@ -458,7 +576,10 @@ int TPM2_TIS_GetBurstCount(TPM2_CTX* ctx, word16* burstCount) #endif { - int timeout = TPM_TIMEOUT_TRIES; + int expired = 0; + TPM2_TIS_TIMEOUT to; + + TPM2_TIS_TimeoutStart(&to); *burstCount = 0; do { rc = TPM2_TIS_Read(ctx, TPM_BURST_COUNT(ctx->locality), @@ -469,16 +590,19 @@ int TPM2_TIS_GetBurstCount(TPM2_CTX* ctx, word16* burstCount) if (rc == TPM_RC_SUCCESS && *burstCount > 0) break; XTPM_WAIT(); - } while (rc == TPM_RC_SUCCESS && --timeout > 0); + expired = TPM2_TIS_TimeoutExpired(&to); + } while (rc == TPM_RC_SUCCESS && !expired); + TPM2_TIS_TimeoutDone(&to); #ifdef WOLFTPM_DEBUG_TIMEOUT - printf("TIS_GetBurstCount: Timeout %d\n", TPM_TIMEOUT_TRIES - timeout); + printf("TIS_GetBurstCount: spent %u\n", + (unsigned int)TPM2_TIS_TimeoutSpent(&to)); #endif if (*burstCount > MAX_SPI_FRAMESIZE) *burstCount = MAX_SPI_FRAMESIZE; - if (timeout <= 0) + if (expired) return TPM_RC_TIMEOUT; } diff --git a/wolftpm/tpm2_types.h b/wolftpm/tpm2_types.h index 68463eb1..a7f139f8 100644 --- a/wolftpm/tpm2_types.h +++ b/wolftpm/tpm2_types.h @@ -716,6 +716,63 @@ typedef int64_t INT64; #endif #endif +/* Monotonic ms tick for the TIS wait loops. Wraps ~49 days, so compare with + * unsigned subtraction. WOLFTPM_HAVE_MONOTONIC_MS is set only where a clock + * exists, port-supplied included; otherwise the iteration counter is kept. + * WOLFTPM_NO_MONOTONIC_MS forces the counter. */ +#ifndef WOLFTPM_NO_MONOTONIC_MS + +/* Without this a port-supplied hook would be silently ignored. */ +#ifdef XTPM_GET_TIMEMS + #define WOLFTPM_HAVE_MONOTONIC_MS +#elif !defined(WOLFTPM_NO_STD_HEADERS) + #if defined(WOLFTPM_ZEPHYR) + #include + #define XTPM_GET_TIMEMS() ((word32)k_uptime_get()) + #define WOLFTPM_HAVE_MONOTONIC_MS + #elif defined(WOLFSSL_ESPIDF) || defined(FREERTOS) + #define XTPM_GET_TIMEMS() \ + ((word32)xTaskGetTickCount() * (word32)portTICK_PERIOD_MS) + #define WOLFTPM_HAVE_MONOTONIC_MS + #elif defined(_WIN32) + #include + #define XTPM_GET_TIMEMS() ((word32)GetTickCount64()) + #define WOLFTPM_HAVE_MONOTONIC_MS + #else + /* Include first, then feature-test: strict C99 may not expose + * CLOCK_MONOTONIC, and __linux__ alone would fail to compile. */ + #include + #if defined(CLOCK_MONOTONIC) && \ + (!defined(_POSIX_TIMERS) || _POSIX_TIMERS > 0) + static inline word32 XTPM_GET_TIMEMS(void) + { + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) { + return 0; + } + return (word32)((word32)ts.tv_sec * 1000u + + (word32)(ts.tv_nsec / 1000000L)); + } + #define WOLFTPM_HAVE_MONOTONIC_MS + #endif + #endif +#endif /* XTPM_GET_TIMEMS */ + +#endif /* !WOLFTPM_NO_MONOTONIC_MS */ + +/* Must cover the slowest single command. RSA-2048 key generation is an + * unbounded prime search: repeated runs on a current part ranged from 26 s to + * over 180 s for the same command, so this is a tail, not an average. */ +#ifndef TPM_TIMEOUT_MS +#define TPM_TIMEOUT_MS 300000 +#endif + +/* Bound for a tick source that stops advancing part way through a wait. Only a + * stopped or broken clock reaches this: a working one advances long before. */ +#ifndef TPM_TIMEOUT_STAGNANT_POLLS +#define TPM_TIMEOUT_STAGNANT_POLLS 1000000 +#endif + #ifndef BUFFER_ALIGNMENT #define BUFFER_ALIGNMENT 4 #endif From a0e4f002286a037cb56e8f535203d0f7cb7e3d7b Mon Sep 17 00:00:00 2001 From: David Garske Date: Tue, 15 Sep 2026 17:07:29 -0700 Subject: [PATCH 5/7] Disambiguate ASN error codes from TPM response codes --- src/tpm2_asn.c | 28 ++++++++++++++-------------- wolftpm/tpm2_asn.h | 43 +++++++++++++++++++++++++++++-------------- 2 files changed, 43 insertions(+), 28 deletions(-) diff --git a/src/tpm2_asn.c b/src/tpm2_asn.c index c0943327..f3c8be76 100644 --- a/src/tpm2_asn.c +++ b/src/tpm2_asn.c @@ -50,7 +50,7 @@ int TPM2_ASN_GetLength_ex(const uint8_t* input, word32* inOutIdx, int* len, *len = 0; /* default length */ if ((idx + 1) > maxIdx) { - return TPM_RC_INSUFFICIENT; + return TPM_RC_ASN_INSUFFICIENT; } b = input[idx++]; @@ -58,7 +58,7 @@ int TPM2_ASN_GetLength_ex(const uint8_t* input, word32* inOutIdx, int* len, word32 bytes = b & 0x7F; /* DER does not allow BER indefinite-length (0x80 => bytes == 0) */ if (bytes == 0 || bytes > 3 || (idx + bytes) > maxIdx) { - return TPM_RC_INSUFFICIENT; + return TPM_RC_ASN_INSUFFICIENT; } while (bytes--) { b = input[idx++]; @@ -69,7 +69,7 @@ int TPM2_ASN_GetLength_ex(const uint8_t* input, word32* inOutIdx, int* len, length = b; if (check && (idx + length) > maxIdx) { - return TPM_RC_INSUFFICIENT; + return TPM_RC_ASN_INSUFFICIENT; } *inOutIdx = idx; @@ -93,7 +93,7 @@ int TPM2_ASN_GetLength(const uint8_t* input, word32* inOutIdx, int* len, \param inOutIdx Current position in buffer, updated to new position \param len Decoded length value \param maxIdx Maximum allowed index in buffer - \return Length on success, TPM_RC_VALUE on tag mismatch, TPM_RC_INSUFFICIENT on buffer error + \return Length on success, TPM_RC_ASN_VALUE on tag mismatch, TPM_RC_ASN_INSUFFICIENT on buffer error */ static int TPM2_ASN_GetHeader(const uint8_t* input, byte tag, word32* inOutIdx, int* len, word32 maxIdx) @@ -103,14 +103,14 @@ static int TPM2_ASN_GetHeader(const uint8_t* input, byte tag, word32* inOutIdx, int length; if ((idx + 1) > maxIdx) - return TPM_RC_INSUFFICIENT; + return TPM_RC_ASN_INSUFFICIENT; b = input[idx++]; if (b != tag) - return TPM_RC_VALUE; + return TPM_RC_ASN_VALUE; if (TPM2_ASN_GetLength(input, &idx, &length, maxIdx) < 0) - return TPM_RC_VALUE; + return TPM_RC_ASN_VALUE; *len = length; *inOutIdx = idx; @@ -164,7 +164,7 @@ int TPM2_ASN_DecodeX509Cert(uint8_t* input, int inputSz, byte sigParamTag = 0; if (input == NULL || x509 == NULL) { - rc = TPM_RC_VALUE; + rc = TPM_RC_ASN_VALUE; } /* Decode outer SEQUENCE */ @@ -193,14 +193,14 @@ int TPM2_ASN_DecodeX509Cert(uint8_t* input, int inputSz, if (rc >= 0) { if (len <= 0 || idx >= (word32)inputSz) { - rc = TPM_RC_VALUE; + rc = TPM_RC_ASN_VALUE; } } if (rc >= 0) { /* check version tag is INTEGER */ if (input[idx] != TPM2_ASN_INTEGER) { - rc = TPM_RC_VALUE; + rc = TPM_RC_ASN_VALUE; } } @@ -282,7 +282,7 @@ int TPM2_ASN_DecodeX509Cert(uint8_t* input, int inputSz, if (outerSigAlgSz != tbsSigAlgSz || XMEMCMP(input + outerSigAlgBegin, input + tbsSigAlgBegin, outerSigAlgSz) != 0) { - rc = TPM_RC_VALUE; + rc = TPM_RC_ASN_VALUE; } } } @@ -299,13 +299,13 @@ int TPM2_ASN_DecodeX509Cert(uint8_t* input, int inputSz, if (sigParamTag != TPM2_ASN_TAG_NULL && sigParamTag != (TPM2_ASN_SEQUENCE | TPM2_ASN_CONSTRUCTED)) { - rc = TPM_RC_VALUE; + rc = TPM_RC_ASN_VALUE; } else { rc = TPM2_ASN_GetHeader(input, sigParamTag, &idx, &len, sigAlgEnd); if (rc >= 0 && sigParamTag == TPM2_ASN_TAG_NULL && len != 0) { - rc = TPM_RC_VALUE; + rc = TPM_RC_ASN_VALUE; } if (rc >= 0) { idx += len; @@ -313,7 +313,7 @@ int TPM2_ASN_DecodeX509Cert(uint8_t* input, int inputSz, } } if (rc >= 0 && idx != sigAlgEnd) { - rc = TPM_RC_VALUE; + rc = TPM_RC_ASN_VALUE; } } diff --git a/wolftpm/tpm2_asn.h b/wolftpm/tpm2_asn.h index 0ad05a3d..1ff69870 100644 --- a/wolftpm/tpm2_asn.h +++ b/wolftpm/tpm2_asn.h @@ -35,11 +35,26 @@ #define MAX_CERT_SZ 2048 #endif -/* ASN Error Codes */ -#define TPM_RC_ASN_PARSE (-201) /* ASN parsing error */ -#define TPM_RC_INSUFFICIENT (-202) /* ASN insufficient data */ -#define TPM_RC_VALUE (-203) /* ASN value error (invalid tag) */ -#define TPM_RC_BUFFER (-204) /* ASN buffer error */ +/* ASN Error Codes. + * Spelled TPM_RC_ASN_* so they cannot shadow the TPM response codes of the + * same short name in tpm2.h: TPM_RC_VALUE there is an enum equal to 0x084 and + * TPM_RC_INSUFFICIENT is 0x09A. Because those are enum constants rather than + * macros, a #ifndef guard cannot see them, so any file including both headers + * silently got the ASN meaning. */ +#define TPM_RC_ASN_PARSE (-201) /* ASN parsing error */ +#define TPM_RC_ASN_INSUFFICIENT (-202) /* ASN insufficient data */ +#define TPM_RC_ASN_VALUE (-203) /* ASN value error (invalid tag) */ +#define TPM_RC_ASN_BUFFER (-204) /* ASN buffer error */ + +/* Deprecated short spellings, kept so existing callers still build. They + * shadow the tpm2.h response codes of the same name, so prefer the + * TPM_RC_ASN_* forms above. Define WOLFTPM_NO_DEPRECATED_ASN_RC to drop them + * and get the tpm2.h meanings instead. */ +#ifndef WOLFTPM_NO_DEPRECATED_ASN_RC + #define TPM_RC_INSUFFICIENT TPM_RC_ASN_INSUFFICIENT + #define TPM_RC_VALUE TPM_RC_ASN_VALUE + #define TPM_RC_BUFFER TPM_RC_ASN_BUFFER +#endif /* ASN.1 Constants */ enum { @@ -78,7 +93,7 @@ typedef struct DecodedX509 { \param inOutIdx Current position in buffer, updated to new position \param len Decoded length value \param maxIdx Maximum allowed index in buffer - \return Length on success, TPM_RC_INSUFFICIENT on buffer error + \return Length on success, TPM_RC_ASN_INSUFFICIENT on buffer error */ WOLFTPM_API int TPM2_ASN_GetLength(const uint8_t* input, word32* inOutIdx, int* len, word32 maxIdx); @@ -91,7 +106,7 @@ WOLFTPM_API int TPM2_ASN_GetLength(const uint8_t* input, word32* inOutIdx, \param len Decoded length value \param maxIdx Maximum allowed index in buffer \param check Flag to enable length validation - \return Length on success, TPM_RC_INSUFFICIENT on buffer error + \return Length on success, TPM_RC_ASN_INSUFFICIENT on buffer error */ WOLFTPM_API int TPM2_ASN_GetLength_ex(const uint8_t* input, word32* inOutIdx, int* len, word32 maxIdx, int check); @@ -104,7 +119,7 @@ WOLFTPM_API int TPM2_ASN_GetLength_ex(const uint8_t* input, word32* inOutIdx, \param inOutIdx Current position in buffer, updated to new position \param tag_len Decoded length value \param tag Expected ASN.1 tag value - \return 0 on success, TPM_RC_INSUFFICIENT on buffer error, TPM_RC_VALUE on tag mismatch + \return 0 on success, TPM_RC_ASN_INSUFFICIENT on buffer error, TPM_RC_ASN_VALUE on tag mismatch */ WOLFTPM_API int TPM2_ASN_DecodeTag(const uint8_t* input, int inputSz, int* inOutIdx, int* tag_len, uint8_t tag); @@ -114,8 +129,8 @@ WOLFTPM_API int TPM2_ASN_DecodeTag(const uint8_t* input, int inputSz, \brief Decodes RSA signature from ASN.1 format \param pInput Pointer to buffer containing ASN.1 encoded RSA signature \param inputSz Size of input buffer - \return Size of decoded signature on success, TPM_RC_VALUE on invalid input, - TPM_RC_INSUFFICIENT on buffer error + \return Size of decoded signature on success, TPM_RC_ASN_VALUE on invalid input, + TPM_RC_ASN_INSUFFICIENT on buffer error */ WOLFTPM_API int TPM2_ASN_RsaDecodeSignature(uint8_t** pInput, int inputSz); @@ -124,7 +139,7 @@ WOLFTPM_API int TPM2_ASN_RsaDecodeSignature(uint8_t** pInput, int inputSz); \param input Buffer containing ASN.1 encoded X.509 certificate \param inputSz Size of input buffer \param x509 Structure to store decoded certificate data - \return 0 on success, TPM_RC_VALUE on invalid input, TPM_RC_INSUFFICIENT on buffer error + \return 0 on success, TPM_RC_ASN_VALUE on invalid input, TPM_RC_ASN_INSUFFICIENT on buffer error */ WOLFTPM_API int TPM2_ASN_DecodeX509Cert(uint8_t* input, int inputSz, DecodedX509* x509); @@ -135,8 +150,8 @@ WOLFTPM_API int TPM2_ASN_DecodeX509Cert(uint8_t* input, int inputSz, \param input Buffer containing ASN.1 encoded RSA public key \param inputSz Size of input buffer \param pub TPM2B_PUBLIC structure to store decoded key - \return 0 on success, TPM_RC_VALUE on invalid input, - TPM_RC_INSUFFICIENT on buffer error + \return 0 on success, TPM_RC_ASN_VALUE on invalid input, + TPM_RC_ASN_INSUFFICIENT on buffer error */ WOLFTPM_API int TPM2_ASN_DecodeRsaPubKey(uint8_t* input, int inputSz, TPM2B_PUBLIC* pub); @@ -150,7 +165,7 @@ WOLFTPM_API int TPM2_ASN_DecodeRsaPubKey(uint8_t* input, int inputSz, \param pSig Pointer to buffer containing padded signature, updated to point to unpadded data \param sigSz Size of signature buffer, updated with unpadded size - \return 0 on success, TPM_RC_VALUE on invalid padding + \return 0 on success, TPM_RC_ASN_VALUE on invalid padding */ WOLFTPM_API int TPM2_ASN_RsaUnpadPkcsv15(uint8_t** pSig, int* sigSz); From 35bd9616a1fd9105d3b84ec8b76823ebd05029b9 Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 17 Sep 2026 16:27:33 -0700 Subject: [PATCH 6/7] Add PCR bank allocation wrapper, example and tests --- .gitignore | 1 + CMakeLists.txt | 1 + ChangeLog.md | 6 + examples/README.md | 1 + examples/pcr/README.md | 66 ++++++++ examples/pcr/allocate.c | 329 ++++++++++++++++++++++++++++++++++++++ examples/pcr/include.am | 13 +- examples/pcr/pcr.h | 1 + examples/run_examples.sh | 22 +++ src/fwtpm/fwtpm_command.c | 46 +++++- src/fwtpm/fwtpm_nv.c | 24 ++- src/tpm2_wrap.c | 166 +++++++++++++++++++ tests/fwtpm_unit_tests.c | 176 ++++++++++++++++++++ tests/unit_tests.c | 136 ++++++++++++++++ wolftpm/fwtpm/fwtpm.h | 5 + wolftpm/tpm2.h | 25 +++ wolftpm/tpm2_wrap.h | 76 +++++++++ 17 files changed, 1082 insertions(+), 12 deletions(-) create mode 100644 examples/pcr/allocate.c diff --git a/.gitignore b/.gitignore index a248c34b..fb9e0b3b 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,7 @@ examples/pcr/extend examples/pcr/policy examples/pcr/policy_sign examples/pcr/reset +examples/pcr/allocate examples/timestamp/clock_set examples/management/flush examples/management/tpmclear diff --git a/CMakeLists.txt b/CMakeLists.txt index 866c07c3..c1f958e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -794,6 +794,7 @@ if (WOLFTPM_EXAMPLES AND BUILD_WOLFTPM_LIB) add_tpm_example(quote pcr/quote.c) add_tpm_example(read_pcr pcr/read_pcr.c) add_tpm_example(reset pcr/reset.c) + add_tpm_example(allocate pcr/allocate.c) add_tpm_example(pkcs7 pkcs7/pkcs7.c) add_tpm_example(seal seal/seal.c) add_tpm_example(unseal seal/unseal.c) diff --git a/ChangeLog.md b/ChangeLog.md index 990e3053..a7ce0203 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -2,6 +2,12 @@ ## Unreleased +* Added `wolfTPM2_AllocatePCRBanks` for changing which PCR banks a TPM allocates. + - Added `examples/pcr/allocate` to report and re-provision the banks. + - Fixed the fwTPM applying the allocation immediately instead of at the next + `Startup(CLEAR)` per TPM 2.0 Part 3 22.5, accepting a selection that would + leave it with no PCR banks, ignoring the `pcrSelect` bitmap, and reporting + no allocated banks after a restart against an existing NV file. * Added SPDM transport-bound TPM policies (PR #594). - Added `TPM2_PolicyTransportSPDM` client support and fwTPM enforcement. - Added SPDM-bound NV policy examples and tests. diff --git a/examples/README.md b/examples/README.md index d7f4e7b3..6fda0cdc 100644 --- a/examples/README.md +++ b/examples/README.md @@ -55,6 +55,7 @@ More information about how to test and use PCR attestation can be found in the i `./examples/pcr/quote` `./examples/pcr/extend` `./examples/pcr/reset` +`./examples/pcr/allocate` ### Remote Attestation challenge diff --git a/examples/pcr/README.md b/examples/pcr/README.md index 8213a349..8c795c04 100644 --- a/examples/pcr/README.md +++ b/examples/pcr/README.md @@ -11,6 +11,7 @@ Examples: * `./examples/pcr/reset`: Used to clear the content of a PCR (restrictions apply, see below) * `./examples/pcr/extend`: Used to modify the content of a PCR (extend is a cryptographic operation, see below) * `./examples/pcr/quote`: Used to generate a TPM2.0 Quote structure containing the PCR digest and TPM-generated signature +* `./examples/pcr/allocate`: Used to report which PCR banks the TPM implements and has allocated, and to change that allocation Scripts: @@ -42,6 +43,18 @@ Reset locality (TCG PC Client): PCR16/23 reset at localities 0-3, PCR20-22 at lo The TPM 2.0 `TPM2_Extend` API uses a SHA1 or SHA256 cryptographic operation to combine the current value of the PCR and with newly provided hash digest. +### Bank allocation + +A TPM keeps a separate set of PCRs per hash algorithm, called a bank. Which banks exist is fixed in silicon, but which of them are *allocated* is provisioned with `TPM2_PCR_Allocate` and can be changed. Many parts, including the Infineon SLB9672 and later, allocate only one bank at a time, so moving from SHA-256 to SHA-384 means deallocating SHA-256 rather than adding a second bank. SHA-1 is deprecated and is not allocated on current parts. + +Three things make this operation different from the others here: + +* The selection **replaces** the allocation. Any bank not named in the request is deallocated, so asking for SHA-384 alone on a SHA-256 TPM removes SHA-256. +* It needs the **platform hierarchy**. Under an OS the platform firmware has usually disabled it, and the TPM then answers `TPM_RC_HIERARCHY` no matter what authorization is supplied. Use `wolfTPM2_AllocatePCRBanks_ex` with a session where platform auth is not the empty password. +* It takes effect at the **next TPM reset**, not on return. There is no command that performs that reset: power cycle the TPM, or restart the simulator process, then re-read the banks to confirm. + +Changing banks invalidates every `PolicyPCR` digest and makes anything sealed to PCR values unsealable. PCR contents are zeroed at the reset, so re-allocating the original bank does not bring them back. + ### Quote The TPM 2.0 `TPM2_Quote` API is a standard operation that encapsulates the PCR digest in a TCG defined structure called `TPMS_ATTEST` together with TPM signature. The signature is produced from a TPM generated key called Attestation Identity Key (AIK) that only the TPM can use. This provides guarantee for the source of the Quote and PCR digest. Together, the Quote and PCR provide the means for system measurement and integrity. @@ -62,6 +75,59 @@ Expected usage: Demo usage without parameters, resets PCR16. ``` +### Allocate Example Usage + +```sh +$ ./examples/pcr/allocate -? +Expected usage: +./examples/pcr/allocate [-sha1] [-sha256] [-sha384] [-sha512] + [-restore] +* no algorithm flags: report the current allocation and exit +* -shaN: include that bank in the new allocation (repeatable) +* -restore: put the original allocation back before exiting +Demo usage without parameters, reports the PCR banks. + +WARNING: the algorithm flags REPLACE the allocation. Banks not +named are deallocated, every PolicyPCR digest changes, and blobs +sealed to PCR values become unsealable. Many TPMs support only +one active bank at a time. + +The new allocation takes effect at the next TPM reset, so power +cycle the TPM (or restart the simulator) and re-run to confirm. +``` + +Report the banks the TPM has, with the PCRs selected in each. The list comes from the TPM's own `TPM_CAP_PCRS` response, so a bank this build has no name for prints as its hash algorithm id, and `pcrSelect` is the raw bitmap: + +```sh +$ ./examples/pcr/allocate +PCR banks: + Bank Allocated pcrSelect + SHA-256 yes FFFFFF + SHA-384 yes FFFFFF + SHA-1 no 000000 +``` + +Move to a SHA-384 only allocation, then confirm it after a reset: + +```sh +$ ./examples/pcr/allocate -sha384 +TPM reported: allocationSuccess YES, maxPCR 24, sizeNeeded 1152, sizeAvailable 4608 +PCR allocation staged. It takes effect at the next TPM reset +(Startup(CLEAR) after a _TPM_Init) - power cycle the TPM, or +restart the simulator process, then re-run to confirm. + +$ ./examples/pcr/allocate +PCR banks: + Bank Allocated pcrSelect + SHA-256 no 000000 + SHA-384 yes FFFFFF + SHA-1 no 000000 +``` + +On a TPM that keeps one bank active, asking for two is rejected outright with `TPM_RC_PCR`. A TPM that accepts the command but lacks the space instead reports `allocationSuccess = NO`, which the wrapper returns as `BUFFER_E` with `sizeNeeded` greater than `sizeAvailable`. Neither is a wolfTPM error. + +Use `-restore` in scripts so a run leaves the TPM's banks as it found them: it replays the exact selection read at startup, bitmaps included, rather than re-deriving it, so a partially selected bank comes back partial. + ### Extend Example Usage ```sh diff --git a/examples/pcr/allocate.c b/examples/pcr/allocate.c new file mode 100644 index 00000000..cec5c06a --- /dev/null +++ b/examples/pcr/allocate.c @@ -0,0 +1,329 @@ +/* allocate.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 + */ + +/* This is a helper tool for inspecting and changing the TPM's PCR banks */ + +#ifdef HAVE_CONFIG_H + #include +#endif + +#include + +#ifndef WOLFTPM2_NO_WRAPPER + +#include +#include +#include + +#include + +/******************************************************************************/ +/* --- BEGIN TPM2.0 PCR Allocate example tool -- */ +/******************************************************************************/ + +/* Names for the banks a TPM commonly reports; anything else prints as hex. */ +static const char* bank_name(TPM_ALG_ID alg) +{ + switch (alg) { + case TPM_ALG_SHA1: return "SHA-1"; + case TPM_ALG_SHA256: return "SHA-256"; + case TPM_ALG_SHA384: return "SHA-384"; + case TPM_ALG_SHA512: return "SHA-512"; + default: return NULL; + } +} + +static void usage(void) +{ + printf("Expected usage:\n"); + printf("./examples/pcr/allocate [-sha1] [-sha256] [-sha384] [-sha512]\n"); + printf(" [-restore]\n"); + printf("* no algorithm flags: report the current allocation and exit\n"); + printf("* -shaN: include that bank in the new allocation (repeatable)\n"); + printf("* -restore: put the original selection back before exiting\n"); + printf("Demo usage without parameters, reports the PCR banks.\n"); + printf("\n"); + printf("WARNING: the algorithm flags REPLACE the allocation. Banks not\n"); + printf("named are deallocated, every PolicyPCR digest changes, and blobs\n"); + printf("sealed to PCR values become unsealable. Many TPMs support only\n"); + printf("one active bank at a time.\n"); + printf("\n"); + printf("The new allocation takes effect at the next TPM reset, so power\n"); + printf("cycle the TPM (or restart the simulator) and re-run to confirm.\n"); +} + +/* Read the TPM's own bank list. That list, not a fixed table of algorithms, + * is what the TPM implements, and its bitmaps are what -restore replays. */ +static int read_banks(TPML_PCR_SELECTION* banks) +{ + int rc; + GetCapability_In in; + GetCapability_Out out; + + XMEMSET(&in, 0, sizeof(in)); + XMEMSET(&out, 0, sizeof(out)); + in.capability = TPM_CAP_PCRS; + in.property = 0; + in.propertyCount = HASH_COUNT; + rc = TPM2_GetCapability(&in, &out); + if (rc != TPM_RC_SUCCESS) { + printf("TPM2_GetCapability(TPM_CAP_PCRS) failed 0x%x: %s\n", rc, + TPM2_GetRCString(rc)); + return rc; + } + if (out.capabilityData.capability != TPM_CAP_PCRS) { + return TPM_RC_VALUE; + } + if (out.moreData == YES) { + printf("The TPM reports more PCR banks than this build can hold" + " (HASH_COUNT %d)\n", (int)HASH_COUNT); + return TPM_RC_SIZE; + } + XMEMCPY(banks, &out.capabilityData.data.assignedPCR, sizeof(*banks)); + return TPM_RC_SUCCESS; +} + +/* Print every bank the TPM reports, with the PCRs selected in each. */ +static void print_banks(TPML_PCR_SELECTION* banks) +{ + const char* name; + word32 i; + int j, any; + + printf("PCR banks:\n"); + printf(" %-10s %-10s %s\n", "Bank", "Allocated", "pcrSelect"); + for (i = 0; i < banks->count; i++) { + any = 0; + for (j = 0; j < (int)banks->pcrSelections[i].sizeofSelect; j++) { + if (banks->pcrSelections[i].pcrSelect[j] != 0) { + any = 1; + } + } + name = bank_name(banks->pcrSelections[i].hash); + if (name != NULL) { + printf(" %-10s %-10s ", name, any ? "yes" : "no"); + } + else { + printf(" 0x%04X %-10s ", banks->pcrSelections[i].hash, + any ? "yes" : "no"); + } + for (j = 0; j < (int)banks->pcrSelections[i].sizeofSelect; j++) { + printf("%02X", banks->pcrSelections[i].pcrSelect[j]); + } + printf("\n"); + } +} + +/* Replay a previously captured selection verbatim, bitmaps included, so a + * partial bank comes back partial rather than expanded to every PCR. */ +static int restore_banks(WOLFTPM2_DEV* dev, TPML_PCR_SELECTION* banks) +{ + int rc; + PCR_Allocate_In in; + PCR_Allocate_Out out; + + XMEMSET(&in, 0, sizeof(in)); + XMEMSET(&out, 0, sizeof(out)); + in.authHandle = TPM_RH_PLATFORM; + XMEMCPY(&in.pcrAllocation, banks, sizeof(in.pcrAllocation)); + + rc = wolfTPM2_SetAuthPassword(dev, 0, NULL); + if (rc != TPM_RC_SUCCESS) { + return rc; + } + dev->session[0].sessionAttributes = 0; + rc = TPM2_PCR_Allocate(&in, &out); + if (rc == TPM_RC_SUCCESS && out.allocationSuccess != YES) { + rc = BUFFER_E; + } + return rc; +} + +static void print_alloc_out(PCR_Allocate_Out* allocOut) +{ + printf("TPM reported: allocationSuccess %s, maxPCR %u, sizeNeeded %u," + " sizeAvailable %u\n", + (allocOut->allocationSuccess == YES) ? "YES" : "NO", + (unsigned int)allocOut->maxPCR, + (unsigned int)allocOut->sizeNeeded, + (unsigned int)allocOut->sizeAvailable); +} + +int TPM2_PCR_Allocate_Test(void* userCtx, int argc, char *argv[]) +{ + int i, rc = -1; + int doRestore = 0; + int algCount = 0; + TPM_ALG_ID algs[HASH_COUNT]; + TPML_PCR_SELECTION origBanks; + PCR_Allocate_Out allocOut; + WOLFTPM2_DEV dev; + + XMEMSET(algs, 0, sizeof(algs)); + XMEMSET(&origBanks, 0, sizeof(origBanks)); + XMEMSET(&allocOut, 0, sizeof(allocOut)); + + for (i = 1; i < argc; i++) { + if (XSTRCMP(argv[i], "-?") == 0 || + XSTRCMP(argv[i], "-h") == 0 || + XSTRCMP(argv[i], "--help") == 0) { + usage(); + return 0; + } + else if (XSTRCMP(argv[i], "-restore") == 0) { + doRestore = 1; + } + else if (XSTRCMP(argv[i], "-sha1") == 0 || + XSTRCMP(argv[i], "-sha256") == 0 || + XSTRCMP(argv[i], "-sha384") == 0 || + XSTRCMP(argv[i], "-sha512") == 0) { + /* Repeatable, so bound the array before writing */ + if (algCount >= (int)(sizeof(algs)/sizeof(algs[0]))) { + printf("Too many algorithm flags (max %d)\n", + (int)(sizeof(algs)/sizeof(algs[0]))); + usage(); + return -1; + } + if (XSTRCMP(argv[i], "-sha1") == 0) { + algs[algCount++] = TPM_ALG_SHA1; + } + else if (XSTRCMP(argv[i], "-sha256") == 0) { + algs[algCount++] = TPM_ALG_SHA256; + } + else if (XSTRCMP(argv[i], "-sha384") == 0) { + algs[algCount++] = TPM_ALG_SHA384; + } + else { + algs[algCount++] = TPM_ALG_SHA512; + } + } + else { + printf("Incorrect arguments\n"); + usage(); + return -1; + } + } + + printf("Demo how to inspect and change the TPM PCR banks\n"); + rc = wolfTPM2_Init(&dev, TPM2_IoCb, userCtx); + if (rc != TPM_RC_SUCCESS) { + printf("wolfTPM2_Init failed 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + return rc; + } + printf("wolfTPM2_Init: success\n"); + + rc = read_banks(&origBanks); + if (rc != TPM_RC_SUCCESS) { + goto exit; + } + print_banks(&origBanks); + if (algCount == 0) { + printf("No algorithm flags given, nothing changed.\n"); + goto exit; + } + + printf("\nWARNING: replacing the PCR bank allocation. Banks not listed are\n" + "deallocated, PCR values are zeroed at the next reset, and blobs\n" + "sealed to PCR values become unsealable.\n\n"); + + rc = wolfTPM2_AllocatePCRBanks(&dev, algs, algCount, &allocOut); + if (rc == TPM_RC_HASH) { + printf("The TPM does not implement one of the requested banks.\n"); + printf("Nothing was sent to the TPM and the allocation is unchanged.\n"); + goto exit; + } + if (WOLFTPM_RC_IS(rc, TPM_RC_PCR)) { /* mask any vendor/layer bits */ + printf("The TPM refused that PCR bank selection (TPM_RC_PCR).\n"); + printf("Parts that keep one bank active reject a multi-bank request" + " outright - try a single -shaN flag.\n"); + goto exit; + } + /* Only meaningful once the TPM answered with parameters */ + if (rc == TPM_RC_SUCCESS || rc == BUFFER_E) { + print_alloc_out(&allocOut); + } + if (rc == BUFFER_E) { + if (allocOut.sizeNeeded > 0) { + printf("The TPM does not have room for that bank set (needed %u," + " available %u).\n", (unsigned int)allocOut.sizeNeeded, + (unsigned int)allocOut.sizeAvailable); + printf("Try a single -shaN flag.\n"); + } + else { + /* No sizing reported means the TPM rejected the selection itself, + * not that it ran out of room */ + printf("The TPM rejected that bank selection and reported no" + " sizing.\n"); + } + goto exit; + } + if (rc != TPM_RC_SUCCESS) { + printf("wolfTPM2_AllocatePCRBanks failed 0x%x: %s\n", rc, + TPM2_GetRCString(rc)); + goto exit; + } + + printf("PCR allocation staged. It takes effect at the next TPM reset\n" + "(Startup(CLEAR) after a _TPM_Init) - power cycle the TPM, or\n" + "restart the simulator process, then re-run to confirm.\n"); + + if (doRestore && origBanks.count > 0) { + printf("\nRestoring the original selection...\n"); + rc = restore_banks(&dev, &origBanks); + if (rc != TPM_RC_SUCCESS) { + printf("Restore failed 0x%x: %s\n", rc, TPM2_GetRCString(rc)); + goto exit; + } + printf("Original allocation staged.\n"); + } + +exit: + + wolfTPM2_Cleanup(&dev); + + return rc; +} + +/******************************************************************************/ +/* --- END TPM2.0 PCR Allocate example tool -- */ +/******************************************************************************/ +#endif /* !WOLFTPM2_NO_WRAPPER */ + +#ifndef NO_MAIN_DRIVER +int main(int argc, char *argv[]) +{ + int rc = -1; + +#ifndef WOLFTPM2_NO_WRAPPER + rc = TPM2_PCR_Allocate_Test(NULL, argc, argv); + /* TPM rc is wider than an exit status (0x100 truncates to 0) */ + if (rc != 0) { + rc = 1; + } +#else + printf("Wrapper code not compiled in\n"); + (void)argc; + (void)argv; +#endif /* !WOLFTPM2_NO_WRAPPER */ + + return rc; +} +#endif diff --git a/examples/pcr/include.am b/examples/pcr/include.am index 3d21bd64..4fedb532 100644 --- a/examples/pcr/include.am +++ b/examples/pcr/include.am @@ -7,7 +7,8 @@ noinst_PROGRAMS += examples/pcr/quote \ examples/pcr/extend \ examples/pcr/policy \ examples/pcr/policy_sign \ - examples/pcr/reset + examples/pcr/reset \ + examples/pcr/allocate noinst_HEADERS += examples/pcr/quote.h \ examples/pcr/pcr.h @@ -29,6 +30,10 @@ examples_pcr_reset_SOURCES = examples/pcr/reset.c examples_pcr_reset_LDADD = src/libwolftpm.la $(LIB_STATIC_ADD) examples_pcr_reset_DEPENDENCIES = src/libwolftpm.la +examples_pcr_allocate_SOURCES = examples/pcr/allocate.c +examples_pcr_allocate_LDADD = src/libwolftpm.la $(LIB_STATIC_ADD) +examples_pcr_allocate_DEPENDENCIES = src/libwolftpm.la + examples_pcr_policy_SOURCES = examples/pcr/policy.c \ examples/tpm_test_keys.c examples_pcr_policy_LDADD = src/libwolftpm.la $(LIB_STATIC_ADD) @@ -47,14 +52,16 @@ dist_example_pcr_DATA = \ examples/pcr/extend.c \ examples/pcr/policy.c \ examples/pcr/policy_sign.c \ - examples/pcr/reset.c + examples/pcr/reset.c \ + examples/pcr/allocate.c DISTCLEANFILES+= examples/pcr/.libs/quote \ examples/pcr/.libs/read_pcr \ examples/pcr/.libs/policy \ examples/pcr/.libs/policy_sign \ examples/pcr/.libs/extend \ - examples/pcr/.libs/reset + examples/pcr/.libs/reset \ + examples/pcr/.libs/allocate EXTRA_DIST+= examples/pcr/README.md \ examples/pcr/demo.sh \ diff --git a/examples/pcr/pcr.h b/examples/pcr/pcr.h index 87bc6758..967af1f3 100644 --- a/examples/pcr/pcr.h +++ b/examples/pcr/pcr.h @@ -29,6 +29,7 @@ int TPM2_PCR_Read_Test(void* userCtx, int argc, char *argv[]); int TPM2_PCR_Extend_Test(void* userCtx, int argc, char *argv[]); int TPM2_PCR_Reset_Test(void* userCtx, int argc, char *argv[]); +int TPM2_PCR_Allocate_Test(void* userCtx, int argc, char *argv[]); int TPM2_PCR_Policy_Test(void* userCtx, int argc, char *argv[]); int TPM2_PCR_PolicySign_Example(void* userCtx, int argc, char *argv[]); diff --git a/examples/run_examples.sh b/examples/run_examples.sh index d33807ef..85ae0062 100755 --- a/examples/run_examples.sh +++ b/examples/run_examples.sh @@ -890,6 +890,28 @@ if [ $WOLFCRYPT_ENABLE -eq 1 ] && [ $NO_FILESYSTEM -eq 0 ]; then # Keeping keyblob.bin for tests later fi +# PCR bank allocation. Only the query-only form runs by default: changing the +# allocation is persistent platform configuration that invalidates PCR policies +# and sealed objects, and the two commands are not transactional - an interrupt +# between them leaves a new allocation staged. Strict assertions live in +# tests/fwtpm_check.sh where the NV image is disposable. +echo -e "PCR bank allocation" +./examples/pcr/allocate >> $TPMPWD/run.out 2>&1 +RESULT=$? +[ $RESULT -ne 0 ] && echo -e "pcr allocate query failed! $RESULT" && exit 1 +if test $ENABLE_DESTRUCTIVE_TESTS -eq 1 +then + ALLOC_OUT=$(./examples/pcr/allocate -sha256 -restore 2>&1) + echo "$ALLOC_OUT" >> $TPMPWD/run.out + if echo "$ALLOC_OUT" | grep -q "Original allocation staged"; then + : # platform auth available and the round trip worked + elif echo "$ALLOC_OUT" | grep -qiE "hierarchy|BAD_AUTH|auth"; then + echo -e " PCR allocate needs platform auth, skipping" + else + echo -e "pcr allocate failed!" && echo "$ALLOC_OUT" && exit 1 + fi +fi + # PCR Quote Tests echo -e "PCR Quote tests" ./examples/pcr/reset 16 >> $TPMPWD/run.out 2>&1 diff --git a/src/fwtpm/fwtpm_command.c b/src/fwtpm/fwtpm_command.c index 3d38b8bf..023c6dfb 100644 --- a/src/fwtpm/fwtpm_command.c +++ b/src/fwtpm/fwtpm_command.c @@ -1017,6 +1017,13 @@ static TPM_RC FwCmd_Startup(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, XMEMSET(ctx->pcrDigest[i][b], 0, TPM_MAX_DIGEST_SIZE); } } + /* A staged TPM2_PCR_Allocate takes effect here, and only here */ + if (ctx->pcrAllocPending) { + ctx->pcrAllocatedBanks = ctx->pcrAllocatedBanksPending; + ctx->pcrAllocatedBanksPending = 0; + ctx->pcrAllocPending = 0; + (void)FWTPM_NV_Save(ctx); + } ctx->globalNvWriteLock = 0; /* shEnable/ehEnable/phEnableNV re-enable on TPM Reset only; * phEnable re-enables on every startup (handled below). */ @@ -3081,8 +3088,8 @@ static TPM_RC FwCmd_PCR_Event(FWTPM_CTX* ctx, TPM2_Packet* cmd, } /* --- TPM2_PCR_Allocate (CC 0x012B) --- */ -/* Allocate PCR banks. Per spec Section 22.5, takes effect after next Startup(CLEAR). - * For software TPM, we always succeed. */ +/* Allocate PCR banks. Per spec Section 22.5 the selection is recorded and takes + * effect at the next Startup(CLEAR), so it is staged rather than applied. */ static TPM_RC FwCmd_PCR_Allocate(FWTPM_CTX* ctx, TPM2_Packet* cmd, int cmdSize, TPM2_Packet* rsp, UINT16 cmdTag) { @@ -3092,6 +3099,9 @@ static TPM_RC FwCmd_PCR_Allocate(FWTPM_CTX* ctx, TPM2_Packet* cmd, UINT32 c; UINT8 newBanks = 0; int paramSzPos, paramStart; + int anySelected = 0; + int b; + UINT8 selByte = 0; UINT32 sizeNeeded = 0; UINT32 sizeAvailable; @@ -3122,10 +3132,18 @@ static TPM_RC FwCmd_PCR_Allocate(FWTPM_CTX* ctx, TPM2_Packet* cmd, rc = TPM_RC_COMMAND_SIZE; break; } - cmd->pos += sizeOfSelect; /* skip pcrSelect bytes */ + /* An all-zero pcrSelect deallocates the bank, so read it */ + anySelected = 0; + for (b = 0; b < (int)sizeOfSelect; b++) { + selByte = 0; + TPM2_Packet_ParseU8(cmd, &selByte); + if (selByte != 0) { + anySelected = 1; + } + } bank = FwGetPcrBankIndex(hashAlg); - if (bank >= 0) { + if (bank >= 0 && anySelected) { newBanks |= (UINT8)(1 << bank); sizeNeeded += (UINT32)(IMPLEMENTATION_PCR * TPM2_GetHashDigestSize(hashAlg)); @@ -3138,15 +3156,27 @@ static TPM_RC FwCmd_PCR_Allocate(FWTPM_CTX* ctx, TPM2_Packet* cmd, #ifdef DEBUG_WOLFTPM printf("fwTPM: PCR_Allocate(banks=0x%02x)\n", newBanks); #endif - ctx->pcrAllocatedBanks = newBanks; + sizeAvailable = (UINT32)(IMPLEMENTATION_PCR * FWTPM_PCR_BANKS * + TPM_MAX_DIGEST_SIZE); + + /* Storing an empty result would persist "no banks at all" to NV */ + if (newBanks == 0) { + paramStart = FwRspParamsBegin(rsp, cmdTag, ¶mSzPos); + TPM2_Packet_AppendU8(rsp, 0); /* allocationSuccess = NO */ + TPM2_Packet_AppendU32(rsp, (UINT32)IMPLEMENTATION_PCR); + TPM2_Packet_AppendU32(rsp, sizeNeeded); + TPM2_Packet_AppendU32(rsp, sizeAvailable); + FwRspParamsEnd(rsp, cmdTag, paramSzPos, paramStart); + return rc; + } + + ctx->pcrAllocatedBanksPending = newBanks; + ctx->pcrAllocPending = 1; rc = FWTPM_NV_Save(ctx); if (rc != TPM_RC_SUCCESS) { return rc; } - sizeAvailable = (UINT32)(IMPLEMENTATION_PCR * FWTPM_PCR_BANKS * - TPM_MAX_DIGEST_SIZE); - paramStart = FwRspParamsBegin(rsp, cmdTag, ¶mSzPos); TPM2_Packet_AppendU8(rsp, 1); /* allocationSuccess = YES */ TPM2_Packet_AppendU32(rsp, (UINT32)IMPLEMENTATION_PCR); diff --git a/src/fwtpm/fwtpm_nv.c b/src/fwtpm/fwtpm_nv.c index 925f3364..8db5689e 100644 --- a/src/fwtpm/fwtpm_nv.c +++ b/src/fwtpm/fwtpm_nv.c @@ -1345,6 +1345,12 @@ static int FwNvProcessEntry(FWTPM_CTX* ctx, UINT16 tag, FwNvUnmarshalU16(value, &vPos, vMax, &ctx->pcrPolicyAlg[idx]); } + /* Absent in records written before staging existed */ + if (vPos < vMax) { + FwNvUnmarshalU8(value, &vPos, vMax, &ctx->pcrAllocPending); + FwNvUnmarshalU8(value, &vPos, vMax, + &ctx->pcrAllocatedBanksPending); + } break; } @@ -1816,6 +1822,14 @@ int FWTPM_NV_Init(FWTPM_CTX* ctx) return BAD_FUNC_ARG; } + /* The journal only carries a PCR_AUTH record when the allocation is + * non-default or a PCR has auth set, so a replay would otherwise leave + * this at zero - no banks allocated - on every restart. Seed the default; + * FwNvGenFreshState and a replayed record both override it. */ + ctx->pcrAllocatedBanks = FWTPM_PCR_ALLOC_DEFAULT; + ctx->pcrAllocatedBanksPending = 0; + ctx->pcrAllocPending = 0; + #ifdef WOLFTPM_FWTPM_NV_APPEND_ONLY ctx->nvRebuild = 0; #endif @@ -2103,7 +2117,8 @@ int FWTPM_NV_Save(FWTPM_CTX* ctx) break; } } - if (hasPcrAuth || ctx->pcrAllocatedBanks != FWTPM_PCR_ALLOC_DEFAULT) { + if (hasPcrAuth || ctx->pcrAllocPending || + ctx->pcrAllocatedBanks != FWTPM_PCR_ALLOC_DEFAULT) { word32 needed = 1 + IMPLEMENTATION_PCR * (2 + 64 + 2 + 64 + 2); if (needed > bufSz) { byte* newBuf; @@ -2129,6 +2144,10 @@ int FWTPM_NV_Save(FWTPM_CTX* ctx) FwNvMarshalU16(buf, &pos, bufSz, ctx->pcrPolicyAlg[i]); } + /* Appended last so an older record still parses */ + FwNvMarshalU8(buf, &pos, bufSz, ctx->pcrAllocPending); + FwNvMarshalU8(buf, &pos, bufSz, + ctx->pcrAllocatedBanksPending); rc = FwNvAppendEntry(ctx, FWTPM_NV_TAG_PCR_AUTH, buf, (UINT16)pos); } @@ -2446,6 +2465,9 @@ int FWTPM_NV_SavePcrAuth(FWTPM_CTX* ctx) FwNvMarshalDigest(buf, &pos, bufSz, &ctx->pcrPolicy[i]); FwNvMarshalU16(buf, &pos, bufSz, ctx->pcrPolicyAlg[i]); } + /* Appended last so an older record still parses */ + FwNvMarshalU8(buf, &pos, bufSz, ctx->pcrAllocPending); + FwNvMarshalU8(buf, &pos, bufSz, ctx->pcrAllocatedBanksPending); rc = FwNvAppendEntry(ctx, FWTPM_NV_TAG_PCR_AUTH, buf, (UINT16)pos); diff --git a/src/tpm2_wrap.c b/src/tpm2_wrap.c index 824dbbf6..266e0ecd 100644 --- a/src/tpm2_wrap.c +++ b/src/tpm2_wrap.c @@ -7522,6 +7522,172 @@ int wolfTPM2_ExtendPCR(WOLFTPM2_DEV* dev, int pcrIndex, int hashAlg, return rc; } +int wolfTPM2_AllocatePCRBanks_ex(WOLFTPM2_DEV* dev, WOLFTPM2_SESSION* session, + const TPM_ALG_ID* hashAlgs, int hashAlgCount, PCR_Allocate_Out* allocOut) +{ + int rc; + int i, wanted; + word32 j, selIdx; + byte pcrArray[PCR_LAST - PCR_FIRST + 1]; + GetCapability_In capIn; + GetCapability_Out capOut; + TPML_PCR_SELECTION* banks; + PCR_Allocate_In in; + PCR_Allocate_Out out; + TPM2_AUTH_SESSION saveSess; + + if (allocOut != NULL) { + XMEMSET(allocOut, 0, sizeof(*allocOut)); + } + if (dev == NULL || hashAlgs == NULL) { + return BAD_FUNC_ARG; + } + /* A zero-length selection is legal and leaves the TPM with no PCR banks */ + if (hashAlgCount <= 0 || hashAlgCount > (int)HASH_COUNT) { + return BAD_FUNC_ARG; + } + + for (i = 0; i < hashAlgCount; i++) { + if (hashAlgs[i] == TPM_ALG_NULL || hashAlgs[i] == TPM_ALG_ERROR) { + return BAD_FUNC_ARG; + } + for (j = 0; j < (word32)i; j++) { + if (hashAlgs[j] == hashAlgs[i]) { + return BAD_FUNC_ARG; + } + } + } + + /* The bank list is the authority on what can be allocated, not + * TPM_CAP_ALGS - parts advertise hashes there that have no PCR bank */ + XMEMSET(&capIn, 0, sizeof(capIn)); + XMEMSET(&capOut, 0, sizeof(capOut)); + capIn.capability = TPM_CAP_PCRS; + capIn.property = 0; + capIn.propertyCount = HASH_COUNT; + rc = TPM2_GetCapability(&capIn, &capOut); + if (rc != TPM_RC_SUCCESS) { + return rc; + } + if (capOut.capabilityData.capability != TPM_CAP_PCRS) { + return TPM_RC_VALUE; + } + /* A truncated list would mean mirroring an incomplete selection, which + * silently deallocates the banks that did not fit the page. */ + if (capOut.moreData == YES) { + return TPM_RC_SIZE; + } + banks = &capOut.capabilityData.data.assignedPCR; + + /* An unimplemented bank is silently ignored (Part 3 22.5), which would + * allocate nothing and still report success */ + for (i = 0; i < hashAlgCount; i++) { + wanted = 0; + for (j = 0; j < banks->count; j++) { + if (banks->pcrSelections[j].hash == hashAlgs[i]) { + wanted = 1; + break; + } + } + if (!wanted) { + return TPM_RC_HASH; + } + } + + for (i = 0; i < (int)sizeof(pcrArray); i++) { + pcrArray[i] = (byte)(PCR_FIRST + i); + } + + XMEMSET(&in, 0, sizeof(in)); + XMEMSET(&out, 0, sizeof(out)); + in.authHandle = TPM_RH_PLATFORM; + + /* Mirror the bank list, giving dropped banks an all-zero bitmap. Part 3 + * 22.5 says an omitted bank is deallocated, but Infineon parts answer + * TPM_RC_PCR unless each deallocation is spelled out. */ + for (j = 0; j < banks->count; j++) { + wanted = 0; + for (i = 0; i < hashAlgCount; i++) { + if (hashAlgs[i] == banks->pcrSelections[j].hash) { + wanted = 1; + break; + } + } + if (wanted) { + TPM2_SetupPCRSelArray(&in.pcrAllocation, + banks->pcrSelections[j].hash, pcrArray, + (word32)sizeof(pcrArray)); + } + else { + selIdx = in.pcrAllocation.count; + if (selIdx >= HASH_COUNT) { + return TPM_RC_VALUE; /* the TPM reported more banks than fit */ + } + in.pcrAllocation.pcrSelections[selIdx].hash = + banks->pcrSelections[j].hash; + in.pcrAllocation.pcrSelections[selIdx].sizeofSelect = + banks->pcrSelections[j].sizeofSelect; + in.pcrAllocation.count++; + } + } + + /* Platform auth setup below overwrites session[0] */ + XMEMCPY(&saveSess, &dev->session[0], sizeof(saveSess)); + + if (session == NULL) { + rc = wolfTPM2_SetAuthPassword(dev, 0, NULL); + if (rc == TPM_RC_SUCCESS) { + dev->session[0].sessionAttributes = 0; + } + } + else { + rc = wolfTPM2_SetAuthSession(dev, 0, session, + (TPMA_SESSION_continueSession)); + } + if (rc == TPM_RC_SUCCESS) { + rc = TPM2_PCR_Allocate(&in, &out); + if (rc != TPM_RC_SUCCESS) { + #ifdef DEBUG_WOLFTPM + printf("TPM2_PCR_Allocate failed 0x%x: %s\n", rc, + TPM2_GetRCString(rc)); + #endif + } + } + + /* A continuing session gets a fresh nonceTPM from the response; hand it + * back before slot 0 is overwritten or the caller's next use of the + * session computes an invalid HMAC (see wolfTPM2_UnsetAuthSession). */ + if (session != NULL) { + XMEMCPY(&session->nonceTPM, &dev->session[0].nonceTPM, + sizeof(TPM2B_NONCE)); + } + + /* Restore previous session[0] state and clear the stack copy */ + XMEMCPY(&dev->session[0], &saveSess, sizeof(dev->session[0])); + TPM2_ForceZero(&saveSess, sizeof(saveSess)); + + if (allocOut != NULL) { + XMEMCPY(allocOut, &out, sizeof(*allocOut)); + } + if (rc != TPM_RC_SUCCESS) { + return rc; + } + /* No room for the set - fail rather than make the caller check a field */ + if (out.allocationSuccess != YES) { + return BUFFER_E; + } + /* Staged: applied at the next Startup(CLEAR), which needs a _TPM_Init + * only a power cycle provides - no command does it */ + return TPM_RC_SUCCESS; +} + +int wolfTPM2_AllocatePCRBanks(WOLFTPM2_DEV* dev, const TPM_ALG_ID* hashAlgs, + int hashAlgCount, PCR_Allocate_Out* allocOut) +{ + return wolfTPM2_AllocatePCRBanks_ex(dev, NULL, hashAlgs, hashAlgCount, + allocOut); +} + int wolfTPM2_UnloadHandle(WOLFTPM2_DEV* dev, WOLFTPM2_HANDLE* handle) { int rc; diff --git a/tests/fwtpm_unit_tests.c b/tests/fwtpm_unit_tests.c index 7373dade..47ceaf5d 100644 --- a/tests/fwtpm_unit_tests.c +++ b/tests/fwtpm_unit_tests.c @@ -1915,6 +1915,181 @@ static void test_fwtpm_pcr_read(void) fwtpm_pass("PCR_Read(0):", 0); } +/* One TPMS_PCR_SELECTION per algs[] entry; selectAll=0 sends an all-zero + * bitmap, which deallocates that bank */ +static int BuildPcrAllocateCmd(byte* buf, UINT32 authHandle, + const UINT16* algs, UINT32 count, int selectAll) +{ + int pos, i; + UINT32 c; + + pos = BuildCmdHeader(buf, TPM_ST_SESSIONS, 0, TPM_CC_PCR_Allocate); + PutU32BE(buf + pos, authHandle); pos += 4; + /* Auth area: size(4) + sessionHandle(4) + nonce(2) + attrs(1) + hmac(2) */ + PutU32BE(buf + pos, 9); pos += 4; + PutU32BE(buf + pos, TPM_RS_PW); pos += 4; + PutU16BE(buf + pos, 0); pos += 2; + buf[pos++] = 0; + PutU16BE(buf + pos, 0); pos += 2; + PutU32BE(buf + pos, count); pos += 4; + for (c = 0; c < count; c++) { + PutU16BE(buf + pos, algs[c]); pos += 2; + buf[pos++] = (byte)PCR_SELECT_MAX; + for (i = 0; i < PCR_SELECT_MAX; i++) { + buf[pos + i] = selectAll ? 0xFF : 0x00; + } + pos += PCR_SELECT_MAX; + } + PutU32BE(buf + 2, (UINT32)pos); + return pos; +} + +/* 1 if hashAlg has any PCR bits set in TPM_CAP_PCRS */ +static int fwtpm_bank_allocated(FWTPM_CTX* ctx, UINT16 hashAlg) +{ + int rc, rspSize, cmdSz, pos, b, i, found = 0; + UINT32 bankCount; + + cmdSz = BuildCmdHeader(gCmd, TPM_ST_NO_SESSIONS, 0, TPM_CC_GetCapability); + PutU32BE(gCmd + cmdSz, TPM_CAP_PCRS); cmdSz += 4; + PutU32BE(gCmd + cmdSz, 0); cmdSz += 4; + PutU32BE(gCmd + cmdSz, HASH_COUNT); cmdSz += 4; + PutU32BE(gCmd + 2, (UINT32)cmdSz); + + rspSize = 0; + rc = FWTPM_ProcessCommand(ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + if (rc != TPM_RC_SUCCESS || GetRspRC(gRsp) != TPM_RC_SUCCESS) { + return -1; + } + + /* header + moreData(1) + capability(4) + count(4) */ + pos = TPM2_HEADER_SIZE + 1 + 4; + bankCount = GetU32BE(gRsp + pos); pos += 4; + for (b = 0; b < (int)bankCount && pos + 3 <= rspSize; b++) { + UINT16 alg = (UINT16)GetU16BE(gRsp + pos); pos += 2; + int sizeOfSelect = gRsp[pos++]; + if (pos + sizeOfSelect > rspSize) { + break; + } + if (alg == hashAlg) { + for (i = 0; i < sizeOfSelect; i++) { + if (gRsp[pos + i] != 0) { + found = 1; + } + } + } + pos += sizeOfSelect; + } + return found; +} + +static void test_fwtpm_pcr_allocate(void) +{ + FWTPM_CTX ctx; + int rc, rspSize, cmdSz; + UINT16 algs[2]; + /* A TPM_ST_SESSIONS response prefixes the parameters with paramSize(4), + * so allocationSuccess sits 4 bytes past the header. */ + + memset(&ctx, 0, sizeof(ctx)); + rc = fwtpm_test_startup(&ctx); + AssertIntEQ(rc, 0); + + algs[0] = TPM_ALG_SHA256; + algs[1] = TPM_ALG_SHA384; + + /* Only the platform hierarchy may re-provision the banks */ + cmdSz = BuildPcrAllocateCmd(gCmd, TPM_RH_OWNER, algs, 1, 1); + rspSize = 0; + rc = FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_AUTH_TYPE); + + /* Oversized selection count is rejected before parsing */ + cmdSz = BuildPcrAllocateCmd(gCmd, TPM_RH_PLATFORM, algs, 1, 1); + PutU32BE(gCmd + TPM2_HEADER_SIZE + 4 + 13, FWTPM_PCR_BANKS * 4 + 1); + rspSize = 0; + rc = FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SIZE); + + /* Both banks selected. Per spec 22.5 the change is staged, so the live + * allocation must not move until the next Startup(CLEAR). */ + cmdSz = BuildPcrAllocateCmd(gCmd, TPM_RH_PLATFORM, algs, 2, 1); + rspSize = 0; + rc = FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(gRsp[TPM2_HEADER_SIZE + 4], 1); /* allocationSuccess */ + AssertIntEQ(fwtpm_bank_allocated(&ctx, TPM_ALG_SHA256), 1); + AssertIntEQ(fwtpm_bank_allocated(&ctx, TPM_ALG_SHA384), 1); + + /* SHA-256 only: staged, so SHA-384 is still allocated right now */ + cmdSz = BuildPcrAllocateCmd(gCmd, TPM_RH_PLATFORM, algs, 1, 1); + rspSize = 0; + rc = FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(gRsp[TPM2_HEADER_SIZE + 4], 1); + AssertIntEQ(fwtpm_bank_allocated(&ctx, TPM_ALG_SHA384), 1); + + /* The restart performs the _TPM_Init and Startup(CLEAR) that apply it. + * Replace, not add: the bank left out of the selection is now gone. + * Without NV nothing survives the restart - the TPM comes back with the + * default banks - so the applied state is only observable with NV. */ +#ifndef FWTPM_NO_NV + FWTPM_Cleanup(&ctx); + memset(&ctx, 0, sizeof(ctx)); + rc = fwtpm_test_startup(&ctx); + AssertIntEQ(rc, 0); + AssertIntEQ(fwtpm_bank_allocated(&ctx, TPM_ALG_SHA256), 1); + AssertIntEQ(fwtpm_bank_allocated(&ctx, TPM_ALG_SHA384), 0); +#endif + + /* An all-zero bitmap deallocates, but must not leave zero banks */ + algs[0] = TPM_ALG_SHA256; + cmdSz = BuildPcrAllocateCmd(gCmd, TPM_RH_PLATFORM, algs, 1, 0); + rspSize = 0; + rc = FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(gRsp[TPM2_HEADER_SIZE + 4], 0); /* allocationSuccess = NO */ + AssertIntEQ(fwtpm_bank_allocated(&ctx, TPM_ALG_SHA256), 1); /* unchanged */ + + /* Same trap: an unimplemented bank must not store "no banks" */ + algs[0] = (UINT16)0x7FFF; + cmdSz = BuildPcrAllocateCmd(gCmd, TPM_RH_PLATFORM, algs, 1, 1); + rspSize = 0; + rc = FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + AssertIntEQ(gRsp[TPM2_HEADER_SIZE + 4], 0); + AssertIntEQ(fwtpm_bank_allocated(&ctx, TPM_ALG_SHA256), 1); + + /* Restore both banks, applied by the restart below */ + algs[0] = TPM_ALG_SHA256; + cmdSz = BuildPcrAllocateCmd(gCmd, TPM_RH_PLATFORM, algs, 2, 1); + rspSize = 0; + rc = FWTPM_ProcessCommand(&ctx, gCmd, cmdSz, gRsp, &rspSize, 0); + AssertIntEQ(rc, TPM_RC_SUCCESS); + AssertIntEQ(GetRspRC(gRsp), TPM_RC_SUCCESS); + + FWTPM_Cleanup(&ctx); + + /* Restarting applies the staged change and must not lose the banks. With + * NV the journal carries no PCR_AUTH record while the allocation is the + * default, so a replay that does not seed the default reports no banks at + * all; without NV the defaults are regenerated. Both must end up here. */ + memset(&ctx, 0, sizeof(ctx)); + rc = fwtpm_test_startup(&ctx); + AssertIntEQ(rc, 0); + AssertIntEQ(fwtpm_bank_allocated(&ctx, TPM_ALG_SHA256), 1); + AssertIntEQ(fwtpm_bank_allocated(&ctx, TPM_ALG_SHA384), 1); + + FWTPM_Cleanup(&ctx); + fwtpm_pass("PCR_Allocate:", 0); +} + static void test_fwtpm_pcr_extend_and_read(void) { FWTPM_CTX ctx; @@ -15900,6 +16075,7 @@ int fwtpm_unit_tests(int argc, char *argv[]) test_fwtpm_getcap_flushcontext_chandles(); test_fwtpm_getcap_properties(); test_fwtpm_getcap_pcrs(); + test_fwtpm_pcr_allocate(); test_fwtpm_getcap_paging(); test_fwtpm_getcap_ecc_curves(); #if defined(HAVE_ECC) && !defined(FWTPM_NO_ECDH) && \ diff --git a/tests/unit_tests.c b/tests/unit_tests.c index 770d7d12..62a89f93 100644 --- a/tests/unit_tests.c +++ b/tests/unit_tests.c @@ -1457,6 +1457,141 @@ static void test_TPM2_IsPcrBankAllocated(void) #endif /* WOLFTPM_SWTPM */ } +static void test_wolfTPM2_AllocatePCRBanks(void) +{ + WOLFTPM2_DEV dev; + PCR_Allocate_Out allocOut; + TPM_ALG_ID algs[4]; +#if defined(WOLFTPM_SWTPM) + TPM_ALG_ID origAlgs[4]; + WOLFTPM2_SESSION sess; + int origCount = 0; + int isAllocated; + int i; + int rc; +#endif + + XMEMSET(&dev, 0, sizeof(dev)); + algs[0] = TPM_ALG_SHA256; + algs[1] = TPM_ALG_SHA384; + + /* Seeded non-zero to prove a rejection still clears the out-param */ + XMEMSET(&allocOut, 0xFF, sizeof(allocOut)); + AssertIntEQ(wolfTPM2_AllocatePCRBanks(NULL, algs, 1, &allocOut), + BAD_FUNC_ARG); + AssertIntEQ(allocOut.allocationSuccess, 0); + AssertIntEQ((int)allocOut.maxPCR, 0); + AssertIntEQ((int)allocOut.sizeNeeded, 0); + AssertIntEQ((int)allocOut.sizeAvailable, 0); + + AssertIntEQ(wolfTPM2_AllocatePCRBanks(&dev, NULL, 1, &allocOut), + BAD_FUNC_ARG); + /* Zero banks is legal at the TPM; the wrapper must make it unreachable */ + AssertIntEQ(wolfTPM2_AllocatePCRBanks(&dev, algs, 0, &allocOut), + BAD_FUNC_ARG); + AssertIntEQ(wolfTPM2_AllocatePCRBanks(&dev, algs, -1, &allocOut), + BAD_FUNC_ARG); + AssertIntEQ(wolfTPM2_AllocatePCRBanks(&dev, algs, HASH_COUNT + 1, + &allocOut), BAD_FUNC_ARG); + + algs[0] = TPM_ALG_NULL; + AssertIntEQ(wolfTPM2_AllocatePCRBanks(&dev, algs, 1, &allocOut), + BAD_FUNC_ARG); + algs[0] = TPM_ALG_SHA256; + + /* Duplicates would silently halve the requested set */ + algs[1] = TPM_ALG_SHA256; + AssertIntEQ(wolfTPM2_AllocatePCRBanks(&dev, algs, 2, &allocOut), + BAD_FUNC_ARG); + algs[1] = TPM_ALG_SHA384; + + /* _ex applies the same checks before touching session[0] */ + AssertIntEQ(wolfTPM2_AllocatePCRBanks_ex(NULL, NULL, algs, 1, &allocOut), + BAD_FUNC_ARG); + AssertIntEQ(wolfTPM2_AllocatePCRBanks_ex(&dev, NULL, NULL, 1, &allocOut), + BAD_FUNC_ARG); + AssertIntEQ(wolfTPM2_AllocatePCRBanks_ex(&dev, NULL, algs, 0, &allocOut), + BAD_FUNC_ARG); + +#if defined(WOLFTPM_SWTPM) + rc = wolfTPM2_Init(&dev, TPM2_IoCb, NULL); + AssertIntEQ(rc, 0); + + /* Record the current set to restore later - the fwTPM persists this to + * NV, so a leaked change breaks later PCR tests and subsequent runs */ + algs[0] = TPM_ALG_SHA256; + algs[1] = TPM_ALG_SHA384; + for (i = 0; i < 2; i++) { + isAllocated = 0; + AssertIntEQ(TPM2_IsPcrBankAllocated(algs[i], (int)PCR_FIRST, + &isAllocated), TPM_RC_SUCCESS); + if (isAllocated) { + origAlgs[origCount++] = algs[i]; + } + } + AssertIntGT(origCount, 0); + + /* Refused before sending: the TPM would silently ignore it */ + algs[0] = (TPM_ALG_ID)0x7FFF; + AssertIntEQ(wolfTPM2_AllocatePCRBanks(&dev, algs, 1, &allocOut), + TPM_RC_HASH); + isAllocated = 0; + AssertIntEQ(TPM2_IsPcrBankAllocated(origAlgs[0], (int)PCR_FIRST, + &isAllocated), TPM_RC_SUCCESS); + AssertIntEQ(isAllocated, 1); /* unchanged - no command was sent */ + + /* The TPM accepts the request and reports sensible sizing. The resulting + * allocation is deliberately not asserted: it takes effect at the next + * Startup(CLEAR) following a _TPM_Init, which no command can trigger, so + * a correct TPM still reports the old banks here. Replace semantics are + * covered by tests/fwtpm_unit_tests.c across a restart. */ + algs[0] = TPM_ALG_SHA256; + XMEMSET(&allocOut, 0, sizeof(allocOut)); + AssertIntEQ(wolfTPM2_AllocatePCRBanks(&dev, algs, 1, &allocOut), + TPM_RC_SUCCESS); + AssertIntEQ(allocOut.allocationSuccess, YES); + AssertIntGT((int)allocOut.maxPCR, 0); + AssertIntLE((int)allocOut.sizeNeeded, (int)allocOut.sizeAvailable); + + /* Stage the original set again so nothing is left pending for the next + * reset, whichever way this TPM applies the change */ + XMEMSET(&allocOut, 0, sizeof(allocOut)); + AssertIntEQ(wolfTPM2_AllocatePCRBanks(&dev, origAlgs, origCount, + &allocOut), TPM_RC_SUCCESS); + AssertIntEQ(allocOut.allocationSuccess, YES); + + /* allocOut is optional */ + AssertIntEQ(wolfTPM2_AllocatePCRBanks(&dev, origAlgs, origCount, NULL), + TPM_RC_SUCCESS); + + /* _ex with a NULL session is the path the base function takes */ + AssertIntEQ(wolfTPM2_AllocatePCRBanks_ex(&dev, NULL, origAlgs, origCount, + NULL), TPM_RC_SUCCESS); + + /* Slot 0 authorization is a password or a policy session (see + * TPM2_GetCmdAuthCount); an HMAC session there is not an auth session. + * Prove the policy session is wired into the auth area: the TPM must + * evaluate it and answer, not reject the command as unauthorized. + * Authorizing for real needs a platform authPolicy this TPM has not been + * provisioned with, so the result itself is not asserted. */ + XMEMSET(&sess, 0, sizeof(sess)); + rc = wolfTPM2_StartSession(&dev, &sess, NULL, NULL, TPM_SE_POLICY, + TPM_ALG_NULL); + if (rc == TPM_RC_SUCCESS) { + rc = wolfTPM2_AllocatePCRBanks_ex(&dev, &sess, origAlgs, origCount, + NULL); + AssertIntNE(rc, BAD_FUNC_ARG); + AssertIntNE(rc, TPM_RC_AUTH_MISSING); + wolfTPM2_UnloadHandle(&dev, &sess.handle); + } + + wolfTPM2_Cleanup(&dev); + printf("Test PcrAlloc: %-40s Passed\n", "Args + Allocate:"); +#else + printf("Test PcrAlloc: %-40s Passed\n", "Arg Validation:"); +#endif /* WOLFTPM_SWTPM */ +} + /* Success path for wolfTPM2_PolicyOR: satisfy one branch of a real two-branch * OR on a live policy session and confirm the TPM's running policy digest * matches the offline computation. Simulator only. */ @@ -9645,6 +9780,7 @@ int unit_tests(int argc, char *argv[]) #endif test_wolfTPM2_IsAlgSupported(); test_TPM2_IsPcrBankAllocated(); + test_wolfTPM2_AllocatePCRBanks(); test_wolfTPM2_PolicyOR_success(); #if defined(WOLFTPM_MLDSA) && defined(WOLFTPM_MLKEM) /* Run non-TPM-dependent tests first */ diff --git a/wolftpm/fwtpm/fwtpm.h b/wolftpm/fwtpm/fwtpm.h index ec06d804..dba15498 100644 --- a/wolftpm/fwtpm/fwtpm.h +++ b/wolftpm/fwtpm/fwtpm.h @@ -795,6 +795,11 @@ typedef struct FWTPM_CTX { TPMI_ALG_HASH pcrPolicyAlg[IMPLEMENTATION_PCR]; /* PCR bank allocation (bitmap: bit 0=SHA-256, bit 1=SHA-384) */ UINT8 pcrAllocatedBanks; /* default: 0x03 = both banks */ + /* TPM2_PCR_Allocate is recorded here and applied at the next + * Startup(CLEAR), per TPM 2.0 Part 3 22.5. Persisted so the change + * survives the power cycle that performs that reset. */ + UINT8 pcrAllocatedBanksPending; + UINT8 pcrAllocPending; /* 1 when a change is staged */ /* Transient object slots */ FWTPM_Object objects[FWTPM_MAX_OBJECTS]; diff --git a/wolftpm/tpm2.h b/wolftpm/tpm2.h index 2cf91c0f..08d17278 100644 --- a/wolftpm/tpm2.h +++ b/wolftpm/tpm2.h @@ -3011,6 +3011,31 @@ typedef struct { UINT32 sizeNeeded; UINT32 sizeAvailable; } PCR_Allocate_Out; +/*! + \ingroup TPM2_Proprietary + \brief Set which PCR banks the TPM allocates + \note The selection REPLACES the current allocation - banks not named in it + are deallocated. Algorithms the TPM does not implement are silently + ignored (TPM 2.0 Part 3 22.5), so a selection naming only unimplemented + algorithms can leave the TPM with no PCR banks at all. Prefer + wolfTPM2_AllocatePCRBanks, which pre-checks each algorithm and refuses + an empty result. + \note The change takes effect at the next Startup(CLEAR), not on return. + \note Requires an active session (TPM2_SetAuthPassword or + wolfTPM2_SetAuthSession on slot 0) and the platform hierarchy; without + one this returns BAD_FUNC_ARG rather than a TPM response code. + + \return TPM_RC_SUCCESS: the TPM processed the request - check + out->allocationSuccess, which is NO when the TPM lacks the space + \return TPM_RC_HIERARCHY: the platform hierarchy is disabled + \return BAD_FUNC_ARG: check the provided arguments, or no session is set + + \param in pointer to a PCR_Allocate_In struct + \param out pointer to a PCR_Allocate_Out struct + + \sa wolfTPM2_AllocatePCRBanks + \sa TPM2_IsPcrBankAllocated +*/ WOLFTPM_API TPM_RC TPM2_PCR_Allocate(PCR_Allocate_In* in, PCR_Allocate_Out* out); diff --git a/wolftpm/tpm2_wrap.h b/wolftpm/tpm2_wrap.h index a3166cf3..ca55514b 100644 --- a/wolftpm/tpm2_wrap.h +++ b/wolftpm/tpm2_wrap.h @@ -3021,6 +3021,82 @@ WOLFTPM_API int wolfTPM2_SetLocality(WOLFTPM2_DEV* dev, int locality); WOLFTPM_API int wolfTPM2_ExtendPCR(WOLFTPM2_DEV* dev, int pcrIndex, int hashAlg, const byte* digest, int digestLen); +/*! + \ingroup wolfTPM2_Wrappers + \brief Re-provision which PCR banks the TPM has allocated + \note This REPLACES the allocation: hashAlgs is the complete new set and + every bank not named in it is deallocated. Allocating SHA-384 alone on a + SHA-256 TPM removes the SHA-256 bank. Many parts (Infineon SLB9672 and + later) support only one active bank at a time and reject a multi-bank + request outright with TPM_RC_PCR. A TPM that answers but lacks the + space reports it in allocOut instead, which maps to BUFFER_E. + \note Changing banks invalidates every PolicyPCR digest and makes every blob + sealed to PCR values unsealable. The PCR contents are zeroed at the + Startup(CLEAR) and re-allocating the old bank does not bring them back. + \note Success means the change is STAGED. The TPM applies it at the next + Startup(CLEAR) following a _TPM_Init, so the caller must power cycle + the TPM (or restart the simulator process) and re-read the banks with + TPM2_IsPcrBankAllocated to confirm. No TPM command performs that reset. + \note Requires the platform hierarchy. Under an OS the platform firmware has + usually cleared phEnable, in which case the TPM answers TPM_RC_HIERARCHY + no matter what auth is supplied. + + \return TPM_RC_SUCCESS: the new allocation is staged for the next reset + \return BUFFER_E: the TPM has no room for the requested bank set; see + allocOut->sizeNeeded and allocOut->sizeAvailable + \return TPM_RC_HASH: the TPM does not implement one of the requested algs; + nothing was sent and the allocation is unchanged + \return TPM_RC_PCR: the TPM refused the selection, typically because it + keeps a single bank active and more than one was requested + \return TPM_RC_HIERARCHY: the platform hierarchy is disabled + \return TPM_RC_BAD_AUTH: platform auth is not the empty password; use + wolfTPM2_AllocatePCRBanks_ex with a session + \return BAD_FUNC_ARG: check the provided arguments + + \param dev pointer to a TPM2_DEV struct + \param hashAlgs array of TPM_ALG_ID hash algorithms naming the complete new + bank set + \param hashAlgCount number of entries in hashAlgs, 1 to HASH_COUNT; zero is + rejected because it would leave the TPM with no PCR banks + \param allocOut optional, receives the TPM's allocationSuccess, maxPCR, + sizeNeeded and sizeAvailable; zeroed on entry + + \sa wolfTPM2_AllocatePCRBanks_ex + \sa TPM2_IsPcrBankAllocated + \sa wolfTPM2_ExtendPCR +*/ +WOLFTPM_API int wolfTPM2_AllocatePCRBanks(WOLFTPM2_DEV* dev, + const TPM_ALG_ID* hashAlgs, int hashAlgCount, PCR_Allocate_Out* allocOut); + +/*! + \ingroup wolfTPM2_Wrappers + \brief Re-provision the TPM's PCR banks using a caller supplied session + \note Same as wolfTPM2_AllocatePCRBanks, but takes a session for a platform + hierarchy that carries an authPolicy. This must be a POLICY session: + slot 0 authorization is a password or a policy session, so an HMAC + session there is not an authorization session and the TPM answers + TPM_RC_AUTH_MISSING. Passing NULL is identical to + wolfTPM2_AllocatePCRBanks. + \note Session slot 0 is saved and restored around the command, and the + response nonce is copied back into the caller's session so it stays + usable for a following command. + + \return TPM_RC_SUCCESS: the new allocation is staged for the next reset + \return BAD_FUNC_ARG: check the provided arguments + + \param dev pointer to a TPM2_DEV struct + \param session pointer to a WOLFTPM2_SESSION for platform auth, or NULL + \param hashAlgs array of TPM_ALG_ID hash algorithms naming the complete new + bank set + \param hashAlgCount number of entries in hashAlgs, 1 to HASH_COUNT + \param allocOut optional, receives the TPM's allocation result + + \sa wolfTPM2_AllocatePCRBanks +*/ +WOLFTPM_API int wolfTPM2_AllocatePCRBanks_ex(WOLFTPM2_DEV* dev, + WOLFTPM2_SESSION* session, const TPM_ALG_ID* hashAlgs, int hashAlgCount, + PCR_Allocate_Out* allocOut); + /* Newer API's that use WOLFTPM2_NV context and support auth */ /*! From cc95586ca02357700541cbc59105d04ed7afb2b3 Mon Sep 17 00:00:00 2001 From: David Garske Date: Thu, 17 Sep 2026 18:30:38 -0700 Subject: [PATCH 7/7] Add a fixed-iteration benchmark mode with per-iteration spread --- examples/bench/bench.c | 68 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 3 deletions(-) diff --git a/examples/bench/bench.c b/examples/bench/bench.c index 31d6d533..3c367833 100644 --- a/examples/bench/bench.c +++ b/examples/bench/bench.c @@ -43,16 +43,44 @@ #define TPM2_BENCH_DURATION_KEYGEN_SEC 15 static int gUseBase2 = 1; +/* Fixed-iteration mode (-iter=N). Zero keeps the default duration mode, where + * each algorithm runs for a wall-clock budget instead of a set count. */ +static int gBenchIter = 0; +/* Per-iteration spread, so a single slow outlier is visible rather than + * averaged away. Rejection sampling makes ML-DSA signing vary run to run. */ +static double gIterPrev, gIterMin, gIterMax; + static inline void bench_stats_start(int* count, double* start) { *count = 0; *start = gettime_secs(1); + gIterPrev = *start; + gIterMin = 0; + gIterMax = 0; } static inline int bench_stats_check(double start, int* count, double maxDurSec) { + double now, each; + (*count)++; - return ((gettime_secs(0) - start) < maxDurSec); + now = gettime_secs(0); + each = now - gIterPrev; + gIterPrev = now; + if (*count == 1) { + gIterMin = each; + gIterMax = each; + } + else if (each > gIterMax) { + gIterMax = each; + } + else if (each < gIterMin) { + gIterMin = each; + } + if (gBenchIter > 0) { + return (*count < gBenchIter); + } + return ((now - start) < maxDurSec); } /* countSz is number of bytes that 1 count represents. Normally bench_size, @@ -105,8 +133,14 @@ static void bench_stats_sym_finish(const char* desc, int count, int countSz, } /* format and print to terminal */ - printf("%-16s %5.0f %s took %5.3f seconds, %8.3f %s/s\n", + printf("%-16s %5.0f %s took %5.3f seconds, %8.3f %s/s", desc, blocks, blockType, total, persec, blockType); + if (gBenchIter > 0) { + printf(", %d ops, avg %5.3f ms, min %5.3f ms, max %5.3f ms", + count, (count > 0) ? (total / count) * 1000 : 0, + gIterMin * 1000, gIterMax * 1000); + } + printf("\n"); } static void bench_stats_asym_finish(const char* algo, int strength, @@ -121,8 +155,13 @@ static void bench_stats_asym_finish(const char* algo, int strength, milliEach = each * 1000; /* milliseconds */ printf("%-6s %5d %-9s %6d ops took %5.3f sec, avg %5.3f ms," - " %.3f ops/sec\n", algo, strength, desc, + " %.3f ops/sec", algo, strength, desc, count, total, milliEach, opsSec); + if (gBenchIter > 0) { + printf(", min %5.3f ms, max %5.3f ms", + gIterMin * 1000, gIterMax * 1000); + } + printf("\n"); } /* True if rc means the TPM does not implement the operation (so the bench @@ -146,6 +185,14 @@ static int bench_asym_done(const char* algo, int strength, const char* desc, printf("%-6s %5d %-9s Skipped (not supported)\n", algo, strength, desc); return 0; } + /* A post-quantum signature can exceed the TPM's input buffer, which the + * verify guard reports before sending. That is a property of the part, + * not a benchmark failure, so record it and carry on. */ + if (rc == BUFFER_E) { + printf("%-6s %5d %-9s Skipped (signature exceeds TPM input buffer)\n", + algo, strength, desc); + return 0; + } return rc; } @@ -370,6 +417,9 @@ static void usage(void) printf("* -aes/xor: Use Parameter Encryption\n"); printf("* -maxdur=[ms]: Maximum runtime for each algorithm in milliseconds " "(default %d)\n", TPM2_BENCH_DURATION_SEC*1000); + printf("* -iter=[n]: Run each algorithm exactly n times and report the\n"); + printf(" average with the per-iteration min and max, instead of\n"); + printf(" running for a duration. Overrides -maxdur.\n"); } /******************************************************************************/ @@ -400,6 +450,9 @@ int TPM2_Wrapper_BenchArgs(void* userCtx, int argc, char *argv[]) double maxDuration = TPM2_BENCH_DURATION_SEC; double maxKeyGenDurSec = TPM2_BENCH_DURATION_KEYGEN_SEC; + /* Static, so a previous call with -iter must not leak into this one */ + gBenchIter = 0; + if (argc >= 2) { if (XSTRCMP(argv[1], "-?") == 0 || XSTRCMP(argv[1], "-h") == 0 || @@ -419,6 +472,15 @@ int TPM2_Wrapper_BenchArgs(void* userCtx, int argc, char *argv[]) const char* maxStr = argv[argc-1] + XSTRLEN("-maxdur="); maxKeyGenDurSec = maxDuration = XATOI(maxStr) / 1000.0; } + else if (XSTRNCMP(argv[argc-1], "-iter=", XSTRLEN("-iter=")) == 0) { + const char* iterStr = argv[argc-1] + XSTRLEN("-iter="); + gBenchIter = XATOI(iterStr); + if (gBenchIter <= 0) { + printf("Iteration count must be greater than zero\n"); + usage(); + return BAD_FUNC_ARG; + } + } else { printf("Warning: Unrecognized option: %s\n", argv[argc-1]); }