Skip to content

Add caller-supplied policy authorization for firmware upgrade - #560

Merged
aidangarske merged 1 commit into
wolfSSL:masterfrom
dgarske:firmware_upgrade_policy_auth
Aug 13, 2026
Merged

Add caller-supplied policy authorization for firmware upgrade#560
aidangarske merged 1 commit into
wolfSSL:masterfrom
dgarske:firmware_upgrade_policy_auth

Conversation

@dgarske

@dgarske dgarske commented Jul 24, 2026

Copy link
Copy Markdown
Member

Summary

Lets callers authorize the firmware-upgrade start command with their own platform-hierarchy policy (multi-branch PolicyOR, SHA-256/384/512) instead of the fixed library authorization. Backward compatible - no existing API changes.

Features

  • New _ex APIs taking a caller-satisfied session: wolfTPM2_FirmwareUpgradeHash_ex, wolfTPM2_FirmwareUpgrade_ex, wolfTPM2_FirmwareUpgradeRecover_ex; the existing calls forward with a NULL session.
  • New public wrappers: wolfTPM2_PolicyOR, wolfTPM2_SetPrimaryPolicy, wolfTPM2_PolicyCommandCodeMake, wolfTPM2_IsAlgSupported.
  • Infineon: a caller session leaves the platform primary policy untouched; ST33: it replaces the default TPM_RS_PW.
  • Firmware examples: non-destructive --policytest self-test and --policy/--policyor [--sha256|--sha384|--sha512] end-to-end modes; shared helpers factored into examples/firmware/firmware_policy.c.

Fixes

  • wolfTPM2_PolicyOR validates branch count and each branch digest size against its buffer (out-of-bounds read, CWE-125).
  • Examples skip a policy hash the TPM does not implement (via TPM_CAP_ALGS) instead of failing with TPM_RC_SIZE.
  • Example arg parsing rejects unknown/extra options; the caller session is released only when not consumed by a started upgrade; a provisioned platform policy is cleared on the failure path.
  • CMake builds the new shared example source.

@dgarske dgarske self-assigned this Jul 24, 2026
Copilot AI review requested due to automatic review settings July 24, 2026 22:28

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR extends wolfTPM’s firmware-upgrade wrappers to support caller-supplied authorization sessions (e.g., policy sessions) for the vendor “firmware start” command, enabling platforms that gate upgrade behind custom platform hierarchy policies.

Changes:

  • Added a wolfTPM2_PolicyOR() wrapper to satisfy policy sessions via TPM2_PolicyOR.
  • Added wolfTPM2_FirmwareUpgradeHash_ex(..., startSession) and routed the legacy wolfTPM2_FirmwareUpgradeHash() through it (preserving existing behavior when startSession == NULL).
  • Updated the ST33 firmware update example and documentation to demonstrate/describe policy-based authorization and a safe self-test flow.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
wolftpm/tpm2_wrap.h Declares new wolfTPM2_PolicyOR wrapper and wolfTPM2_FirmwareUpgradeHash_ex API with caller-supplied session support.
src/tpm2_wrap.c Implements wolfTPM2_PolicyOR and adds the _ex firmware upgrade routing + vendor start-session plumbing.
examples/firmware/st33_fw_update.c Adds --policytest self-test to validate policy-session + PolicyOR behavior without performing an upgrade.
examples/firmware/README.md Documents advanced policy-based firmware start authorization and the new _ex API usage.

Comment thread src/tpm2_wrap.c

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Comment thread src/tpm2_wrap.c
Comment thread src/tpm2_wrap.c Outdated
@dgarske
dgarske force-pushed the firmware_upgrade_policy_auth branch 2 times, most recently from 0b72b95 to bda21be Compare August 3, 2026 21:17
@dgarske
dgarske marked this pull request as ready for review August 3, 2026 21:25
@dgarske
dgarske force-pushed the firmware_upgrade_policy_auth branch from bda21be to 01bc093 Compare August 4, 2026 21:36
@dgarske
dgarske requested a lite review from Copilot August 5, 2026 00:47

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.

Suppressed comments (5)

src/tpm2_wrap.c:11213

  • Severity: Critical (CWE-121/CWE-476). tpm2_ifx_firmware_start() builds a command in a fixed-size stack buffer (TPM_SHA512_DIGEST_SIZE) and copies manifest_hash using manifest_hash_sz without validating it. Through the public wolfTPM2_FirmwareUpgradeHash_ex() API a caller can pass a larger size (or a non-NULL size with NULL manifest_hash), causing a stack overflow or NULL dereference. Validate manifest_hash pointer and size against hashAlg before constructing the command.
    int rc;
    WOLFTPM2_SESSION tpmSession;
    TPM_HANDLE sessionHandle = TPM_RH_NULL;
    int ownSession = 0;

    XMEMSET(&tpmSession, 0, sizeof(tpmSession));

src/tpm2_wrap.c:10739

  • Severity: Medium (CWE-20). wolfTPM2_SetPrimaryPolicy() accepts authPolicySz > 0 with authPolicy == NULL, which would silently send an empty policy digest (size=0) while still setting hashAlg. This is an argument-validation bug that can result in unexpected policy changes or confusing TPM errors; fail fast when a non-zero size is provided without a buffer (and when clearing, enforce hashAlg==TPM_ALG_NULL implies authPolicySz==0).

This issue also appears on line 11207 of the same file.

    if (dev == NULL) {
        return BAD_FUNC_ARG;
    }
    if (authPolicySz > (word32)sizeof(in.authPolicy.buffer)) {
        return BAD_FUNC_ARG;
    }

examples/firmware/firmware_policy.c:184

  • Severity: Low (CWE-476). firmware_policy_session_setup() dereferences dev and session without checking for NULL (it calls wolfTPM2_IsAlgSupported(dev, ...) and XMEMSET(session,...)). Even though this is example code, it’s a shared helper and should fail cleanly with BAD_FUNC_ARG on NULL inputs.
    if (hsz == 0 || hsz > TPM_MAX_DIGEST_SIZE) {
        return BAD_FUNC_ARG;
    }
    XMEMSET(session, 0, sizeof(*session));
    XMEMSET(&orList, 0, sizeof(orList));

examples/firmware/st33_fw_update.c:425

  • Severity: Low (CWE-772). In the ST33 example, sessionStarted is set to 1 before calling wolfTPM2_FirmwareUpgrade_ex(). If that call fails before FieldUpgradeStart is issued (e.g., comms/GetCapabilities failure), cleanup will skip UnloadHandle and leak the policy session. Consider only marking the session as “consumed” on overall success; if the TPM already consumed it on a later failure, UnloadHandle may fail harmlessly and is already ignored.
        if (rc == 0) {
            if (policyMode) {
                printf("Using caller-supplied policy session\n");
                /* session is handed to FieldUpgradeStart, which consumes it */
                sessionStarted = 1;
            }
            rc = wolfTPM2_FirmwareUpgrade_ex(&dev,
                fwinfo.manifest_buf, (uint32_t)fwinfo.manifest_bufSz,
                TPM2_ST33_FwData_Cb, &fwinfo,
                policyMode ? &policySession : NULL);

examples/firmware/ifx_fw_update.c:251

  • Severity: Low (CWE-772). In the Infineon example, sessionStarted is set based only on caps.opMode before the upgrade call. If the upgrade fails before reaching FieldUpgradeStart (e.g., IO/GetCapabilities errors inside the library), cleanup will skip UnloadHandle and leak the policy session. Mark the session as “consumed” only when the overall call succeeds and the path actually reaches start (opMode != 0x03).
    if (rc == 0) {
        /* The upgrade hands the session to FieldUpgradeStart, which the TPM
         * consumes as it enters upgrade mode. opMode 0x03 (finalize-only) does
         * not reach start, so the session there is not consumed. */
        if (policyMode) {

@dgarske
dgarske force-pushed the firmware_upgrade_policy_auth branch 3 times, most recently from f29c7d6 to 04a9269 Compare August 11, 2026 22:52
@dgarske dgarske assigned aidangarske and unassigned dgarske Aug 11, 2026
@dgarske
dgarske requested a review from aidangarske August 11, 2026 22:52
aidangarske

This comment was marked as resolved.

@aidangarske aidangarske left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Skoll Multi-Scan Review

Modes: review + review-security

Overall recommendation: COMMENT
Findings: 5 total — 5 posted, 0 skipped
5 finding(s) posted as inline comments (see file-level comments below)

Posted findings

  • [Medium] [review] wolfTPM2_IsAlgSupported tri-state return (1/0/rc) is easy to misusesrc/tpm2_wrap.c:1030-1056
  • [Low] [review-security] Policy self-test hard-fails when TPM implements a hash wolfCrypt was not built withexamples/firmware/firmware_policy.c:153-173
  • [Low] [review] Cleanup comment/guard says library 'zeroes' the session handle, but it sets TPM_RH_NULLexamples/firmware/ifx_fw_update.c:311-314; examples/firmware/st33_fw_update.c:472-475
  • [Low] [review] No unit coverage for wolfTPM2_PolicyOR / wolfTPM2_IsAlgSupported success pathstests/unit_tests.c:8200-8209
  • [Info] [review-security] README describes consumed session as 'zeroed' but library sets TPM_RH_NULLexamples/firmware/README.md:270-276

Review generated by Skoll

Comment thread src/tpm2_wrap.c Outdated
Comment thread examples/firmware/firmware_policy.c
Comment thread examples/firmware/ifx_fw_update.c Outdated
Comment thread tests/unit_tests.c
Comment thread examples/firmware/README.md
Lets a deployment gate the TPM firmware-update start command behind its own
platform hierarchy policy instead of the vendor default. Previously wolfTPM
always managed that authorization internally: on Infineon it installed and
satisfied a PolicyCommandCode(TPM_CC_FieldUpgradeStartVendor) policy on the
platform primary policy, and on ST33 it used password authorization with an
empty platform password. Both assume default platform auth, which a hardened
deployment will not have.

New _ex entry points take an already-satisfied session:
  wolfTPM2_FirmwareUpgradeHash_ex()
  wolfTPM2_FirmwareUpgrade_ex()
  wolfTPM2_FirmwareUpgradeRecover_ex()
Passing NULL for startSession keeps the existing library-managed behavior, so
the original functions are unchanged wrappers and existing code is unaffected.
With a session supplied, Infineon no longer overwrites the platform primary
policy (the caller provisions it) and ST33 uses the session in place of
TPM_RS_PW.

Supporting wrappers:
  wolfTPM2_PolicyOR()               satisfy a session with a compound OR
  wolfTPM2_PolicyCommandCodeMake()  offline PolicyCommandCode digest
  wolfTPM2_IsAlgSupported()         report whether the TPM implements an alg
PolicyOR is hash-agnostic (each branch carries its own size), so SHA2-256
through SHA2-512 policy branches all work. It requires at least two branches
per TPM 2.0 Part 3 Sec.23.6 and validates each branch size against the digest
buffer. PolicyCommandCodeMake takes digestSz as in/out - input is the output
buffer capacity and BUFFER_E is returned when it is too small, checked before
the buffer is written. IsAlgSupported reports through an out-parameter and
returns TPM_RC, so a capability-query failure cannot be misread as
"supported"; it sets the out-parameter to 0 on every error path.

Session contract: the vendor FieldUpgradeStart commands are hand-marshalled
with an authorization area carrying only the session handle - empty
nonceCaller, zero attributes, empty HMAC. That is correct only for an
unsalted, unbound policy session with no auth value, so the _ex path validates
the caller session up front and rejects anything needing a computed session
HMAC or parameter encryption (PolicyAuthValue/PolicyPassword, attached auth
value, bind, salt, encrypt/decrypt/audit attributes, or a non-policy handle).
Validation runs before any TPM traffic. On a successful start the TPM consumes
the session and the library sets handle.hndl to TPM_RH_NULL; this is now
documented, including that it is not zero and that wolfTPM2_UnloadHandle
no-ops on it.

Examples: ifx_fw_update and st33_fw_update gain --policy, --policyor and
--policytest, backed by a shared examples/firmware/firmware_policy.c. The
--policytest mode is a non-destructive self-test that checks the TPM's running
policy digest against an offline computation at SHA2-256/384/512, skipping any
hash the TPM does not implement or the local wolfCrypt build was not compiled
with (a build mismatch is a skip, not a failure). The provisioned PolicyOR
carries a PolicyCommandCode(TPM_CC_SetPrimaryPolicy) branch so the policy can
authorize its own removal, and cleanup is gated on a FirmwarePolicyCtx that
records what was actually provisioned, so an early failure cannot clear a
policy the deployment installed itself. A failed rollback is reported but
never overwrites the upgrade error that explains the run, and is skipped once
the start has succeeded (the TPM has reset into upgrade mode and will not
service SetPrimaryPolicy).

Because TPM 2.0 offers no way to read a hierarchy authPolicy back, the example
cannot detect or restore one it replaces, so provisioning prints an explicit
warning naming that. The policy modes are also refused where the session could
never be used: any Infineon operational mode other than normal (recovery and
finalize skip the start entirely), and on ST33 when the TPM is already in
firmware-upgrade mode - previously the flags were silently ignored there,
which is the authorization downgrade they exist to prevent. Rollback normally uses platform password
authorization, which an installed authPolicy does not disable (TPM 2.0 Part 1
Sec.19.7); this was confirmed on ST33KTPM2X, SLB9670 and SLB9673 parts.

The firmware examples now require wolfCrypt in their feature guards. They
drive wolfTPM2_FirmwareUpgrade_ex, which hashes the manifest with SHA-384 and
only exists with wolfCrypt, so --enable-firmware with --disable-wolfcrypt
previously failed to compile.

Also converts examples/nvram/extend.c to the new PolicyCommandCodeMake
capacity contract.

Tests: argument validation and known-answer vectors for the new wrappers,
short-buffer and canary coverage for PolicyCommandCodeMake, caller-session
rejection for every unsupported session shape plus an accepted session,
simulator-backed success paths for PolicyOR and IsAlgSupported, and a
regression test that a platform authPolicy remains clearable with password
authorization while a non-matching policy session is refused.
@dgarske
dgarske force-pushed the firmware_upgrade_policy_auth branch from e777fc0 to 2b8e41c Compare August 13, 2026 20:02
@dgarske
dgarske requested a review from aidangarske August 13, 2026 20:02

@aidangarske aidangarske left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good!

@aidangarske
aidangarske merged commit 1446aad into wolfSSL:master Aug 13, 2026
202 checks passed
@aidangarske
aidangarske deleted the firmware_upgrade_policy_auth branch August 13, 2026 21:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants