Skip to content

Wipe code leaves the plaintext seed in RAM #457

Description

@BitHighlander

Triaged during the 7.14.2 residual pass and deferred out of 7.14.2 as medium. Still present at release/7.14.2 head.

Evidence

lib/firmware/pin_sm.c:253-258 and lib/firmware/storage.c:1391-1401, 1451

Assessment

Confirmed live at head. The 45 commits touched pin_sm.c only to restructure change_pin into change_pin_staged, and storage.c only to add the setup_isArmed ceremony guard in storage_commit; neither goes near the wipe-code path. Traced the whole path at head: pin_sm.c:253 storage_isWipeCodeCorrect -> session_clear(false) -> storage_clearKeys() -> session_clear_impl(&session, &shadow_config.storage, false). Inside session_clear_impl the wipe-code path always has a PIN set, so storage_hasPin_impl is true, the block at :1437-1449 is skipped and control hits if (!clear_pin) { return (ret); } at storage.c:1451, returning before the clear: label at :1454 that would do storage->has_sec = false; memzero(&storage->sec, ...). session.seed and session.passphrase ARE zeroed at the top of session_clear_impl, and storage_clearKeys zeroes session.storageKey, pub.wrapped_storage_key and pub.storage_key_fingerprint, so the flash secret is genuinely destroyed. What survives is shadow_config.storage.sec — sec.mnemonic, sec.node, sec.pin, sec.cache — with has_sec still true, for the remainder of the power cycle, because pin_protect merely sends Failure_PinInvalid and returns false without rebooting. The irony is sharp: entering a merely WRONG PIN reaches storage_isPinCorrect, whose PIN_WRONG arm at storage.c:1712-1714 calls session_clear_impl with clear_pin=true and does zero sec; entering the duress wipe code does not. I checked for a host-reachable escalation and could not find one. storage_getRootNode at :1878 gates only on has_sec, not on pinCached, so the residue is live key material — but I scripted every fsm_msg_*.h handler that calls fsm_getDerivedNode and all of them sit behind CHECK_PIN or CHECK_PIN_UNCACHED, and after the wipe the correct PIN now fails against the zeroed fingerprint and itself clears sec. I also confirmed storage_writeV17 (storage.c:1146, comment at ~:958 'Ignore whatever was in storage->sec. Only encrypted_sec can be committed.') never writes sec to flash, so this is RAM-only. That caps it below high: no USB host can read it and no host-reachable path signs with it. It stays at medium rather than low because it defeats a duress feature inside that feature's own threat model — the user is told, by silence, that the secret is gone while the plaintext mnemonic sits in SRAM — and the reachable window is not hypothetical: fsm_msg_common.h:357 and :419 (ChangePin, ChangeWipeCode) use CHECK_PIN_UNCACHED, so the prompt can be raised while the session is already unlocked and sec is populated. Exploitation still needs SWD/JTAG on an RDP-downgraded part, a cold-boot read, or a memory-disclosure bug in signed firmware. Deferrable, but the fix is one line (pass clear_pin=true, or memzero sec explicitly in storage_clearKeys) and it should be filed, not forgotten.


Deferred because 7.14.2 is a minimal security release; this did not clear the bar for a late change. Filed so it is not lost.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    securitySecurity-relevant defect or hardeningstorageStorage format, versioning, migration

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions