Skip to content

Return INV_CRB_CTRL_DATA for empty CRB control data on Start#4

Closed
dymk wants to merge 1 commit into
mainfrom
dymk/tpm-spec-inv-crb-ctrl-data
Closed

Return INV_CRB_CTRL_DATA for empty CRB control data on Start#4
dymk wants to merge 1 commit into
mainfrom
dymk/tpm-spec-inv-crb-ctrl-data

Conversation

@dymk

@dymk dymk commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Per ARM DEN0138 TPM Service CRB Interface Over FF-A, the Start function's response codes have these semantics:

CRB_FFA_DENIED: the TPM has previously DISABLED locality requests and command processing at the given locality
CRB_FFA_INV_CRB_CTRL_DATA: CRB control data or locality control data at the given TPM locality is not valid

The SP currently returns DENIED for two scenarios that don't fit the spec's DENIED semantics:

  1. handle_command() falls through to a default Denied whenever no state-transition bit is set in the CRB control_request / control_start registers (e.g., the host issues Start(COMMAND) before writing cmdReady).
  2. handle_locality_request() returns Denied when neither the RELINQUISH nor REQUEST_ACCESS bit is set in locality_control.

Both cases are "host issued Start with empty CRB control data" — which per DEN0138 is INV_CRB_CTRL_DATA, not DENIED. The semantic distinction is corroborated by two independent authoritative consumers of DEN0138:

  • Linux kernel (drivers/char/tpm/tpm_crb_ffa.c): the CRB_FFA_START docstring lists the same four status codes with the same semantics.
  • TianoCore EDK2 (SecurityPkg/Library/Tpm2DeviceLibFfa/Tpm2ServiceFfaRaw.c::TranslateTpmReturnStatus): maps INV_CRB_CTRL_DATAEFI_COMPROMISED_DATA and DENIEDEFI_ACCESS_DENIED, confirming the distinction between "malformed CRB" and "access disabled."

Diff

Two minimal edits to ec-service-lib/src/services/tpm.rs:

// handle_command(): early-return InvCrbCtrlData when both
// crb_control_request and crb_control_start are zero (no operation requested).
if crb.crb_control_request == 0 && crb.crb_control_start == 0 {
    error!("CRB has no operation requested");
    return TpmStatus::InvCrbCtrlData;
}

// handle_locality_request(): change the "no bits set" branch
// from `Denied` to `InvCrbCtrlData`.

Other paths (locality mismatch, closed locality, invalid arguments, source-id check) are left as-is — their existing return codes are spec-conformant.

Surfaced from / verified by

Systematic debugging of odp-platform-qemu-sbsa's Phase 2 e2e tests (tpm_start_command_locality_mismatch, tpm_start_command_idle_no_bits, tpm_start_locality_no_crb_bits). Companion test-side change is opened against dymk/odp-platform-qemu-sbsa — it updates the test expectations to INV_CRB_CTRL_DATA for the affected cases and restructures the "closed locality" tests to target a locality that's actually closed by default (locality 2 instead of 0, since OpenDevicePartnership#76 made 0+1 default-open).

Verified locally inside the qemu-sbsa devcontainer: with this SP fix + the companion test fix + the downstream odp-platform-qemu-sbsa PRs that unblock the harness (TPM device wiring + uart-logger virt retarget), make e2e-test QEMU_TIMEOUT=900 reports RESULT: ALL TESTS PASSED (27/27).

@dymk dymk force-pushed the dymk/tpm-spec-inv-crb-ctrl-data branch from d717a3e to 5fd3231 Compare June 12, 2026 15:42
dymk added a commit to dymk/odp-platform-qemu-sbsa that referenced this pull request Jun 12, 2026
Five test edits in e2e-tests/tests/tpm/src/main.rs, paired with the
SP-side fix (companion commit in mod/secure-services/odp-secure-services)
that returns INV_CRB_CTRL_DATA for empty CRB control data:

  - Add TPM2_FFA_INV_CRB_CTRL_DATA constant (0x8e00_0006).

  - tpm_start_closed_locality / tpm_start_locality_qualifier_closed:
    change locality argument from 0 to 2. Localities 0 and 1 default to
    Open after SP init (per odp-secure-services PR OpenDevicePartnership#76), but the test
    name still wants to exercise the 'closed locality' branch — so use
    locality 2 (or 3 or 4), which remain Closed by default. The DENIED
    expectation is unchanged because locality 2 is genuinely
    administratively disabled at boot.

  - tpm_start_locality_no_crb_bits: change expected from DENIED to
    INV_CRB_CTRL_DATA. The scenario is 'Start(LOCALITY) with neither
    Request/Relinquish bit set' — per DEN0138 this is malformed CRB
    control data, not a DENIED-class fault.

  - tpm_start_command_locality_mismatch + tpm_start_command_idle_no_bits:
    change expected from INV_ARG to INV_CRB_CTRL_DATA. Both exercise
    the same code path now ('Start(COMMAND) with no command queued in
    CRB'). The test name 'locality_mismatch' is historical from when
    these tests were written assuming standalone-SP behavior; in the
    live SP+EC scenario the EC's earlier Start(LOCALITY,0) traffic
    sets active_locality=0, so the locality matches and execution
    falls into handle_command, where the empty CRB now returns
    INV_CRB_CTRL_DATA per DEN0138.

Also bumps the odp-secure-services submodule to 5fd3231 (the
companion SP fix at dymk/odp-secure-services#4) and temporarily
redirects .gitmodules to the dymk fork so CI can fetch the
submodule commit (per AGENTS.md cascade pattern — the URL is
restored to the OpenDevicePartnership org once the upstream SP PR
merges).

Verified locally inside the devcontainer: make e2e-test
QEMU_TIMEOUT=900 now reports RESULT: ALL TESTS PASSED (27/27),
including the four previously-failing or coincidentally-passing
tests above.

Stacked on top of PRs for issues #3 (TPM device) + #5 (CI gate)
+ #4 (uart-logger virt). The clean PR for this fix will target a
main that already has those landed.

Assisted-by: GitHub Copilot:claude-opus-4.7-1m-internal
Per ARM DEN0138 'TPM Service CRB Interface Over FF-A':
  CRB_FFA_DENIED:           the TPM has previously DISABLED locality
                             requests and command processing at the
                             given locality
  CRB_FFA_INV_CRB_CTRL_DATA: CRB control data or locality control
                             data at the given TPM locality is not valid

The SP currently returns DENIED for two scenarios that don't match
the DENIED semantics:

  1. handle_command() falls through to a default 'Denied' status
     whenever no state-transition bit is set in the CRB control_request
     / control_start registers (e.g., the host issues Start(COMMAND)
     before writing cmdReady).

  2. handle_locality_request() returns 'Denied' when neither the
     RELINQUISH nor REQUEST_ACCESS bit is set in locality_control.

Both are 'host gave us a Start with empty CRB control data', which
per DEN0138 is INV_CRB_CTRL_DATA, not DENIED. The TianoCore EDK2
client maps INV_CRB_CTRL_DATA to EFI_COMPROMISED_DATA and DENIED to
EFI_ACCESS_DENIED — confirming the spec's semantic distinction
between 'malformed CRB' and 'access disabled'.

Fix is two minimal edits to ec-service-lib/src/services/tpm.rs:

  - handle_command(): early-return InvCrbCtrlData when both
    crb_control_request and crb_control_start are zero.
  - handle_locality_request(): change the 'no bits set' branch
    return from Denied to InvCrbCtrlData.

Other paths (locality mismatch, closed locality, invalid arguments)
are left as-is — their existing return codes are spec-conformant.

Surfaced from systematic debugging of qemu-sbsa Phase 2 e2e tests
(tpm_start_command_locality_mismatch, tpm_start_command_idle_no_bits,
tpm_start_locality_no_crb_bits). The qemu-sbsa test expectations
are updated in a companion commit.

References:
  - Linux kernel driver: drivers/char/tpm/tpm_crb_ffa.c (CRB_FFA_START documented function status codes)
  - TianoCore EDK2: SecurityPkg/Library/Tpm2DeviceLibFfa/Tpm2ServiceFfaRaw.c::TranslateTpmReturnStatus

Assisted-by: GitHub Copilot:claude-opus-4.7-1m-internal
@dymk dymk force-pushed the dymk/tpm-spec-inv-crb-ctrl-data branch from 5fd3231 to 6d164c9 Compare June 12, 2026 19:42
dymk added a commit to dymk/odp-platform-qemu-sbsa that referenced this pull request Jun 12, 2026
Five test edits in e2e-tests/tests/tpm/src/main.rs, paired with the
SP-side fix (companion commit in mod/secure-services/odp-secure-services)
that returns INV_CRB_CTRL_DATA for empty CRB control data:

  - Add TPM2_FFA_INV_CRB_CTRL_DATA constant (0x8e00_0006).

  - tpm_start_closed_locality / tpm_start_locality_qualifier_closed:
    change locality argument from 0 to 2. Localities 0 and 1 default to
    Open after SP init (per odp-secure-services PR OpenDevicePartnership#76), but the test
    name still wants to exercise the 'closed locality' branch — so use
    locality 2 (or 3 or 4), which remain Closed by default. The DENIED
    expectation is unchanged because locality 2 is genuinely
    administratively disabled at boot.

  - tpm_start_locality_no_crb_bits: change expected from DENIED to
    INV_CRB_CTRL_DATA. The scenario is 'Start(LOCALITY) with neither
    Request/Relinquish bit set' — per DEN0138 this is malformed CRB
    control data, not a DENIED-class fault.

  - tpm_start_command_locality_mismatch + tpm_start_command_idle_no_bits:
    change expected from INV_ARG to INV_CRB_CTRL_DATA. Both exercise
    the same code path now ('Start(COMMAND) with no command queued in
    CRB'). The test name 'locality_mismatch' is historical from when
    these tests were written assuming standalone-SP behavior; in the
    live SP+EC scenario the EC's earlier Start(LOCALITY,0) traffic
    sets active_locality=0, so the locality matches and execution
    falls into handle_command, where the empty CRB now returns
    INV_CRB_CTRL_DATA per DEN0138.

Also bumps the odp-secure-services submodule to 5fd3231 (the
companion SP fix at dymk/odp-secure-services#4) and temporarily
redirects .gitmodules to the dymk fork so CI can fetch the
submodule commit (per AGENTS.md cascade pattern — the URL is
restored to the OpenDevicePartnership org once the upstream SP PR
merges).

Verified locally inside the devcontainer: make e2e-test
QEMU_TIMEOUT=900 now reports RESULT: ALL TESTS PASSED (27/27),
including the four previously-failing or coincidentally-passing
tests above.

Stacked on top of PRs for issues #3 (TPM device) + #5 (CI gate)
+ #4 (uart-logger virt). The clean PR for this fix will target a
main that already has those landed.

Assisted-by: GitHub Copilot:claude-opus-4.7-1m-internal
@dymk

dymk commented Jun 15, 2026

Copy link
Copy Markdown
Owner Author

Superseded — promoted to upstream as OpenDevicePartnership#79, which merged at commit 29af461. The qemu-sbsa consumer side has now also landed via OpenDevicePartnership/odp-platform-qemu-sbsa#93 (merged at b3d09316). Closing the fork-review trail.

@dymk dymk closed this Jun 15, 2026
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.

1 participant