Skip to content

fix(M1): cmd_login no longer grants credential-free admin - #15

Open
d33mobile wants to merge 1 commit into
hakierspejs:masterfrom
d33mobile:fix/m1-login-failclosed
Open

fix(M1): cmd_login no longer grants credential-free admin#15
d33mobile wants to merge 1 commit into
hakierspejs:masterfrom
d33mobile:fix/m1-login-failclosed

Conversation

@d33mobile

Copy link
Copy Markdown

Finding (ISSUES.md M1)

serial/commands_system.c cmd_login: three branches set admin_mode = true before any key/TOTP check, ignoring argv entirely:

  • no WiFi config stored — wins even when valid admin keys exist
  • RTC unset and uptime past the boot-bypass window — inducible from the network (deny WiFi assoc so the RTC never inits)
  • no enabled+valid admin key

Remediation: never grant admin from absent time or absent config (fail closed); reorder so credential verification wins whenever any enabled+valid admin key exists; restrict "open mode" to a genuinely unprovisioned device.

The branches that existed

cmd_login set admin_mode = true and returned, before verifying any credential, in three cases:

  1. storage_wifi_get() returned false → warning: wifi not configured - open mode. This took precedence even when valid admin keys were present.
  2. clock_get_unix_time() returned false (RTC unset / NTP never synced) and time_us_64() >= BOOT_BYPASS_WINDOW_US (5 min) → warning: RTC not set - open mode. An on-path attacker can force this by denying WiFi association so the clock never initialises, then waiting out the window.
  3. !any_adminwarning: no admin keys configured - bootstrap mode.

Because branches 1 and 2 preceded the credential check, a fully provisioned device with valid admin keys could be entered with no key id and no TOTP code.

New authorization logic

After the existing storage_is_mounted() guard (fail closed on unavailable storage), cmd_login:

  1. Scans for any enabled + valid (good-checksum) admin key (any_admin).
  2. If no such key exists, the device is genuinely unprovisioned: grant a bootstrap/provisioning session so an operator can add the first admin key. This is the only credential-free path, gated strictly on any_admin == false. It closes automatically the moment an enabled admin key exists (adding one flips any_admin to true), so it is effectively one-shot provisioning rather than a persistent fail-open state.
  3. If an admin key does exist, a matching key id + TOTP code is mandatory. No absent-wifi-config and no absent-time condition can bypass it. An unset RTC is never an escalation: totp_verify() reads the clock and simply fails while time is unknown, so login stays closed until time is known.

Removed the wifi-absent and RTC-unset admin grants and the now-unused BOOT_BYPASS_WINDOW_US.

The "persist last-known-good unix time to flash" sub-item overlaps C3's max-time watermark (already merged) and is left as follow-up to keep this PR focused on cmd_login's authorization logic. Physical proof-of-presence for the provisioning path is M4's territory.

Host tests / regression

Added test/harness_login.c, a CI-gated regression harness that links the real cmd_login (serial/commands_system.c) against controllable storage/clock/TOTP doubles and asserts the fail-closed contract:

  • provisioned device + no wifi config + wrong TOTP → denied (regression for bypass 1)
  • provisioned device + unset clock → denied (regression for bypass 2)
  • no-wifi and unset-clock with a valid admin key → still fails closed
  • wrong TOTP / unknown key id / disabled or corrupt admin key → denied or provisioning as appropriate
  • unprovisioned device (no admin key) → provisioning mode is the only credential-free grant
  • provisioning closes once an enabled admin key exists
  • storage unavailable → fail closed

Wired into make -C test asan|valgrind|coverage. All host gates pass locally: make -C test asan = 0, valgrind = 0, coverage = 0, ./ci --action=check = 0. The gcc asan_commands dispatcher harness (spy-based routing) is unchanged and still passes. libFuzzer fuzz_*.c harnesses untouched.

Firmware-on-hardware behaviour (RTC init, NTP sync timing) = verify-on-hw.

Scope: M1 only.

…ey exists

cmd_login had three branches that set admin_mode = true before any
key/TOTP check, ignoring argv entirely:
  - no wifi config stored -> "open mode" (won even when valid admin keys
    existed)
  - RTC unset and uptime past a 5-minute window -> "open mode"
    (network-inducible: deny wifi assoc so the RTC never inits)
  - no enabled+valid admin key -> "bootstrap mode"

Reorder and fail closed: after the storage-mounted guard, scan for any
enabled+valid admin key. If one exists the device is provisioned and a
matching key id + TOTP code is mandatory - no absent-config and no
absent-time condition may bypass it (totp_verify reads the clock and
fails while time is unknown, so an unset RTC is never an escalation).
The only credential-free path is a genuinely unprovisioned device (no
enabled admin key exists at all); it is gated strictly on any_admin ==
false and closes automatically once an admin key is added, so it is
effectively one-shot provisioning rather than a persistent fail-open
state. Removed the wifi-absent and RTC-unset admin grants and the now
unused BOOT_BYPASS_WINDOW_US.

Adds test/harness_login.c, a CI-gated regression harness that links the
REAL cmd_login against controllable storage/clock/TOTP doubles and
asserts the fail-closed contract (no-wifi / unset-clock / wrong-TOTP all
denied when an admin key exists; provisioning only when none does).
Wired into asan/valgrind/coverage. Host asan/valgrind/coverage/ci-check
all 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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