fix TOCTOU RC: reject incoming APDU if there is already a pending one#1588
fix TOCTOU RC: reject incoming APDU if there is already a pending one#1588iartemov-ledger wants to merge 1 commit into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1588 +/- ##
=======================================
Coverage 91.87% 91.87%
=======================================
Files 39 39
Lines 4653 4653
Branches 596 596
=======================================
Hits 4275 4275
Misses 266 266
Partials 112 112
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
b4941c1 to
161bf7e
Compare
161bf7e to
5a61275
Compare
There was a problem hiding this comment.
Pull request overview
This PR introduces a “reply pending” latch to mitigate a TOCTOU issue by rejecting any new incoming APDU command while a previous command is still awaiting its reply, preventing an in-flight transaction from being mutated during on-screen review.
Changes:
- Add a global IO “reply pending” latch (
os_io_set_reply_pending/os_io_reply_pending) inio. - Arm the latch when a command is accepted in
io_legacy_apdu_rx, and reject subsequent commands withSWO_COMMAND_NOT_ACCEPTEDwhile the latch is armed. - Clear the latch when replying via
io_legacy_apdu_tx, and also clear it from the U2F/CTAP2 reply path (u2f_message_reply) since that bypassesio_legacy_apdu_tx.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| lib_u2f_legacy/src/u2f_legacy.c | Clears the latch from the U2F/CTAP2 reply path. |
| io/src/os_io.c | Adds storage and accessors for the “reply pending” latch. |
| io/include/os_io.h | Exposes the latch API with documentation of intended behavior. |
| io_legacy/src/os_io_legacy.c | Arms/clears the latch around legacy APDU RX/TX and rejects new commands while pending. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
5a61275 to
f0eb71e
Compare
d2a4753 to
9afd48c
Compare
9afd48c to
ba90b52
Compare
ba90b52 to
1c95fdd
Compare
bboilot-ledger
left a comment
There was a problem hiding this comment.
We might want to notify the DMK/LW team in case they encounter this new status word.
Description
TOCTOU consent latch to the legacy IO loop.
When the app accepts a command that defers its reply (e.g. a transaction review), it sets a reply_pending latch (
os_io_set_reply_pending(true)); the reply path clears it (inio_legacy_apdu_txandu2f_message_reply).While the latch is set, any new incoming APDU is rejected with
SWO_COMMAND_NOT_ACCEPTED (0x6901)instead of being processed.U2F/CTAP framing is deliberately exempt from the reject gate (
CANCEL/RAW/CBOR), with CBOR still arming/clearing the latch like a normal command — so FIDO traffic isn't disturbed (CTAPHIDdoes its ownCHANNEL_BUSYarbitration above this layer).The fix, in one paragraph
A static
bool io_reply_pending(latch) inio_legacy/src/os_io_legacy.c:io_legacy_apdu_rx(excludes CANCEL/RAW);SWO_COMMAND_NOT_ACCEPTED(
0x6901) (exempts CANCEL/RAW/CBOR);io_legacy_apdu_tx(sync + async/deferred) andu2f_message_reply(U2F/CTAP, unconditional).This hooks the shared
io_legacy_apdu_rxchokepoint, so it covers every app and everydeferred-UI window without per-app changes.
Findings rebuttal
u2f_message_reply, which clears the latch unconditionally → TOCTOU window reopens behind a pending reviewu2f_message_replycan never erase a genuine pending review. Documented in-code with a "make armer-aware if such an app ever ships" pointer. The proposed enum adds fragility to guard a combination that cannot occur.G_io_apdu_bufferbehind the UICTAP1_ERR_CHANNEL_BUSYatlib_u2f/src/u2f_transport.c:81-91and is never reassembled into a delivered 0x24 packet. So no secondmemmoveinto the shared buffer ever happens — no overwrite, no TOCTOU.true→ all later APDUs rejected0x6901until power-cycleio_legacy_apdu_txfor sync and async/deferred replies,u2f_message_replyfor U2F) — the review's own table admits this. The "fire-and-forget /SWO_NO_RESPONSEwithout TX" path doesn't exist in the legacy dispatch model: an accepted command always ends in an SW, and any non-OK SW exits to dashboard (state reset). A wedge would be a self-DoS recoverable by disconnect, not a security defect.CHANNEL_BUSY,u2f_transport.c:81), which also preserves the legitimate CANCEL/keepalive flow. Having the latch also reject CBOR would be redundant and would force it to re-implement the CANCEL exception to avoid breaking FIDO. The asymmetry is correct per frame type.0x6901rejection must go back to the host on the transport that sent the offending frame (os_io_legacy.c:410). Standard SDK reply behavior, not a flaw.Test run with the same fix applied to API_LEVEL_26-derived branch (
reject_if_pending_26):Changes include
Breaking changes
It may break:
Additional comments
Please post additional comments in this section if you have them, otherwise delete it.
Auto cherry-pick in API_LEVEL
If requested to port the commits from this PR on a dedicated API_LEVEL branch,
select the targeted one(s), or add new references if not listed:
[x] TARGET_API_LEVEL: API_LEVEL_26
This will only create the PR with cherry-picks, ready to be reviewed and merged.
Remember: