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.
Triaged during the 7.14.2 residual pass and deferred out of 7.14.2 as
medium. Still present atrelease/7.14.2head.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 theclear:label at :1454 that would dostorage->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.