Skip to content

fix(M4): format-storage requires a board-id-derived confirmation token - #17

Closed
d33mobile wants to merge 1 commit into
hakierspejs:masterfrom
d33mobile:fix/m4-format-auth
Closed

fix(M4): format-storage requires a board-id-derived confirmation token#17
d33mobile wants to merge 1 commit into
hakierspejs:masterfrom
d33mobile:fix/m4-format-auth

Conversation

@d33mobile

Copy link
Copy Markdown

Finding (ISSUES.md M4 — format-storage is unauthenticated and chains to fail-open admin)

serial/commands.c:68 registers it with requires_admin = false, and commands.c:110 allow-lists it while storage is unmounted. The handler's only guard is "is storage mounted" (commands_system.c:219-224) — not identity.

The mount-failure precondition is the design intent (you cannot login when storage is down)... The fallout is not: after format-storage + CONFIRM, all keys and the WiFi config are destroyed... and the device reboots into a state where login 0 0 succeeds. The attacker then runs add-key 0 me + set-key-admin 0 and owns the lock.

  • Require physical proof-of-presence for format-storage, or derive the confirm token from the board's unique ID rather than the fixed CONFIRM.

The fix

cmd_format_storage (serial/commands_system.c) no longer accepts the fixed literal CONFIRM. It now derives the confirmation token from pico_get_unique_board_id(): the operator must type the board's 16-hex uppercase unique id to proceed.

Why this works even when storage is down — the mount-failure entry point is design intent and cannot rely on login (storage is unavailable, so cmd_login refuses). This remediation needs no storage and no clock. Crucially, while storage is unmounted the dispatcher (commands.c:110) allow-lists only format-storage/help/?, so status (the only command that prints the board id) is not reachable, and the handler never echoes the token — it only echoes the operator's own keystrokes. A remote/blind attacker who reaches the console in the unmounted state therefore cannot learn the token. Reading it requires physical access to the device (via a probe, or a prior status while healthy). Input is uppercase-normalised for hex a-f; the read is bounded to the token length and the buffer is sized PICO_UNIQUE_BOARD_ID_SIZE_BYTES * 2 + 1 (16 hex + NUL), preserving the prior safe bounds.

The existing storage_is_mounted() gate is kept unchanged.

Deferred to M1

The second M4 sub-item — "force a provisioning state after format in which the door cannot open until an admin key is enrolled" — overlaps M1 (the fail-open-admin cluster). M1's cmd_login rework already makes the only credential-free path a genuinely unprovisioned device that self-closes once an admin key is added, and the door already fails closed with no admin key / unset clock. This PR is kept focused on the confirm-token hardening to avoid duplicating M1; the provisioning-state item is covered there.

Host-test status

The board-id path is only compiled compile-only in the coverage build; the pico/unique_id.h host stub already declares pico_get_unique_board_id (added for status), and harness_commands links a SPY for cmd_format_storage, so no stub/harness changes were needed. All CI-gated host gates pass (0):

  • make -C test asan — 0
  • make -C test valgrind — 0
  • make -C test coverage — 0
  • ./ci --action=check (clang-format lint) — 0

fuzz_*.c harnesses untouched.

verify-on-hw

The board-id read (pico_get_unique_board_id against the real flash chip) and the post-format reboot into the unprovisioned state cannot be exercised on the host — confirm on hardware that the printed/typed token matches the status board id and that the device reboots correctly after a successful format.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@d33mobile
d33mobile force-pushed the fix/m4-format-auth branch from 1257a12 to 7ed9daa Compare August 4, 2026 16:56
@Tomunek Tomunek closed this Aug 4, 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.

2 participants