Skip to content

Boot-time unlock loop never calls reset_activity — device flickers Enter-PIN/Locked forever and needs two unlocks (#713 fixed in only one of two paths) #728

Description

@Nicola-Ceornea

Summary

Observed on pq1 silicon (sealed EVT unit, interactive ui-lcd image, 2026-09-22): a device left at the boot PIN prompt flickers between "Enter PIN / to unlock" and "Locked" indefinitely, and the operator has to enter the PIN twice before reaching the main screen.

Operator, verbatim:

"now it flickers between enter pin and something else in a loop"
"I unlocked the first time, and then less than a second later it asked me again to unlock. Only then it went on the main screen."

No PIN attempts are consumed — GET_STATUS read locked=0 remaining=10 afterwards. The loop never submits a PIN, so it does not walk toward the 10-attempt wipe. That is the one piece of good news.

Cause

There are two unlock loops. #713's fix landed in one of them.

secure/src/main.rs:4295 — PendSV re-unlock, correct:

ui::show_status("Enter PIN", "to unlock");
timeout::reset_activity();          // <-- the #713 fix
let mut pin = match enter_pin() { ...

secure/src/main.rs:3938 — boot-time unlock, missing it:

loop {
    ui::show_status("Enter PIN", "to unlock");     // no reset_activity
    let mut pin = match enter_pin() {
        PinEntryResult::Cancelled | PinEntryResult::IdleWipe => {
            ui::show_status("Locked", "");
            continue;                               // re-prompt, timer STILL expired
        }

ui::pin_entry::enter_pin (pin_entry.rs:102) samples timeout::is_idle() before waiting and calls reset_activity() only after a button event (pin_entry.rs:112). So once the idle deadline has passed, wait_button returns None immediately, enter_pin returns IdleWipe, the loop shows "Locked" and re-prompts — forever, with nothing ever resetting the timer.

grep -rn 'timeout::reset_activity()' secure/src/ --include=*.rs (excluding tests) confirms the production call sites are main.rs:4297, main.rs:4323, pin_entry.rs:112 and the seed wizard. Nothing in the 3938 loop.

Why the operator sees a "double unlock"

The first entry is consumed by an idle cycle; once a button press lands, pin_entry.rs:112 resets the timer and the next entry succeeds. From the outside that reads as "it asked me twice".

Impact

Fix

Call timeout::reset_activity() immediately before enter_pin() in the boot-time loop, matching 4297. Consider instead moving the reset inside enter_pin() at entry, so no future call site can reintroduce the asymmetry — the two loops have now diverged once already.

Whatever the fix, it wants a test that asserts BOTH loops reset activity, not just the one that was reported.

Evidence

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

    findingAdversarial-review finding; evidence in docs/security/adversarial-review/findings/priority:highSecurity-criticalsurface:trusted-uiAttack surface / subsystem: trusted-ui

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions