fix(core): clear PIN keyboard in bootscreen
What changed, and why it matters
This commit changes how the Trezor hardware wallet displays and clears its PIN-entry keyboard during the boot screen. The title says it 'clears PIN keyboard in bootscreen,' suggesting the previous code may have left PIN-keyboard graphics or state visible when the device went to sleep or resumed. The patch adds a notification channel so the boot screen can be told when a suspend is happening, and it re-creates the lockscreen after suspend instead of continuing straight to PIN entry. There is no direct evidence in the diff of a security vulnerability such as PIN leakage, but a leftover PIN keyboard on screen could theoretically expose partial PIN-entry state or confuse the user interface.
Treat as a low-severity UI hardening fix. Review whether the old behavior could leave PIN-keyboard pixels or touch-target state active across suspend, and confirm the new loop correctly handles all suspend edge cases without introducing race conditions. No urgent action required unless additional security analysis shows actual PIN exposure.
Security signals we found
UI state cleanup around PIN entry
Boot screen redraw after suspend/resume
Mailbox-based notification between power manager and boot flow
Evidence from the diff
The patch introduces lock_manager.notify_bootscreen, a mailbox that is signaled when _schedule_suspend_after_workflow() is called. In boot.py, when USE_POWER_MANAGER is enabled, pin_unlock_sequence() now races verify_user_pin() against wait_for_suspend(). If suspend wins, it sleeps 100 ms and loops, recreating the Lockscreen object. This ensures the lockscreen (and its cleared PIN keyboard) is redrawn after the device wakes from suspend, rather than resuming directly into verify_user_pin() with whatever UI state was previously on screen. The change is defensive UI hardening; the diff does not show any cryptographic or PIN-handling logic change.
Changed components
core/src/apps/common/lock_manager.pycore/src/boot.pyTrezor boot screen / lockscreen UIPower manager suspend/resume integrationInspect captured patch +37 / −9
diff --git a/core/src/apps/common/lock_manager.py b/core/src/apps/common/lock_manager.py
index ec02b2b5..01f04404 100644
--- a/core/src/apps/common/lock_manager.py
+++ b/core/src/apps/common/lock_manager.py
@@ -29,6 +29,7 @@ else:
_SHOULD_SUSPEND = False
_notify_power_button: loop.mailbox[None] = loop.mailbox()
+ notify_bootscreen: loop.mailbox[None] = loop.mailbox()
def _schedule_suspend_after_workflow() -> None:
"""Signal that the device should be suspended by the default task after the
@@ -47,6 +48,7 @@ else:
Notifies an asynchronous task to perform the suspend in a separate thread.
"""
+ notify_bootscreen.put(None, replace=True)
_notify_power_button.put(None, replace=True)
async def _power_handler() -> None:
diff --git a/core/src/boot.py b/core/src/boot.py
index 29532519..f87380ec 100644
--- a/core/src/boot.py
+++ b/core/src/boot.py
@@ -10,6 +10,7 @@ welcome_screen_start_ms = utime.ticks_ms()
import storage
import storage.device
+
from trezor import config, io, log, loop, ui, utils, wire, translations
from trezor.pin import (
allow_all_loader_messages,
@@ -24,8 +25,9 @@ if utils.USE_OPTIGA:
from trezor.crypto import optiga
if utils.USE_POWER_MANAGER:
+ from micropython import const
from trezor import workflow
- from apps.common.lock_manager import configure_autodim, boot as boot_power_manager
+ from apps.common import lock_manager
# have to use "==" over "in (list)" so that it can be statically replaced
# with the correct value during the build process
@@ -50,6 +52,35 @@ def enforce_welcome_screen_duration() -> None:
utime.sleep_ms(100)
+if not utils.USE_POWER_MANAGER:
+
+ async def pin_unlock_sequence() -> None:
+ lockscreen = Lockscreen(label=storage.device.get_label(), bootscreen=True)
+ await lockscreen.get_result()
+ lockscreen.__del__()
+ await verify_user_pin()
+
+else:
+ _SUSPEND_MARKER: int = const(1)
+
+ async def wait_for_suspend() -> int:
+ lock_manager.notify_bootscreen.clear()
+ await lock_manager.notify_bootscreen
+ return _SUSPEND_MARKER
+
+ async def pin_unlock_sequence() -> None:
+ while True:
+ lockscreen = Lockscreen(label=storage.device.get_label(), bootscreen=True)
+ await lockscreen.get_result()
+ lockscreen.__del__()
+ res = await loop.race(verify_user_pin(), wait_for_suspend())
+ if res is _SUSPEND_MARKER:
+ # make some delay for the suspend
+ await loop.sleep(100)
+ continue
+ return
+
+
async def bootscreen() -> None:
"""Sequence of actions to be done on boot (after device is connected).
@@ -60,7 +91,7 @@ async def bootscreen() -> None:
Allowing all of them before returning.
"""
if utils.USE_POWER_MANAGER:
- configure_autodim()
+ lock_manager.configure_autodim()
while True:
try:
@@ -74,12 +105,7 @@ async def bootscreen() -> None:
io.haptic.haptic_set_enabled(storage.device.get_haptic_feedback())
if utils.USE_RGB_LED:
io.rgb_led.rgb_led_set_enabled(storage.device.get_rgb_led())
- lockscreen = Lockscreen(
- label=storage.device.get_label(), bootscreen=True
- )
- await lockscreen.get_result()
- lockscreen.__del__()
- await verify_user_pin()
+ await pin_unlock_sequence()
storage.init_unlocked()
allow_all_loader_messages()
break
@@ -130,7 +156,7 @@ if not utils.USE_OPTIGA or (optiga.get_sec() or 0) < 150:
config.init(show_pin_timeout)
translations.init()
if utils.USE_POWER_MANAGER:
- boot_power_manager()
+ lock_manager.boot()
if __debug__ and not utils.EMULATOR:
config.wipe()
Why this scored 47/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.