What changed, and why it matters
This commit adds an auto-suspend feature for Trezor hardware wallets when running on battery power. It changes how the device locks and dims the screen, and refactors power-button handling. There is no clear security vulnerability in the diff itself, but the change touches sensitive lock/suspend code paths and includes a temporary default-value change (20 seconds instead of the intended 40 seconds) marked with a TODO.
Review the battery autolock default and ensure the TODO is resolved before release; verify that suspend_device() and handle_wakeup_from_suspend() do not introduce race conditions or bypass PIN checks; test on real hardware since emulator behavior is explicitly incomplete.
Security signals we found
Code changes device lock/suspend behavior on battery power
Temporary default value change (20s vs intended 40s) marked TODO
Suspend path marked FIXME for emulator
Power-button handler refactored to use shared lock_device_if_unlocked()
No explicit security claim or advisory in commit message
Evidence from the diff
The patch implements auto-suspend behind the USE_POWER_MANAGER flag. It moves autodim timer registration into reload_settings_from_storage(), adds a battery-aware autolock timer (get_autolock_delay_battery_ms()), and calls suspend_device() inside lock_device_if_unlocked() when not on USB power. The power-button handler now calls lock_device_if_unlocked() and raises Shutdown() when a PIN is set and autolock interrupts workflows. A FIXME notes suspend is not implemented on the emulator, and a TODO in storage/device.py indicates the battery autolock default was temporarily lowered to 20 seconds. The diff is a feature implementation with no obvious exploit, but it alters device-locking behavior and includes unfinished/temporary values.
Changed components
core/src/apps/base.pycore/src/storage/device.pycore/src/trezor/ui/__init__.pyInspect captured patch +43 / −16
diff --git a/core/src/apps/base.py b/core/src/apps/base.py
index d8afaff81..50bd88d6d 100644
--- a/core/src/apps/base.py
+++ b/core/src/apps/base.py
@@ -11,6 +11,11 @@ from trezor.wire.message_handler import filters, remove_filter
from . import workflow_handlers
+if utils.USE_POWER_MANAGER:
+ from trezor import io
+ from trezor.power_management.autodim import autodim_display
+ from trezor.power_management.suspend import suspend_device
+
if TYPE_CHECKING:
from typing import NoReturn
@@ -571,6 +576,28 @@ def lock_device_if_unlocked() -> None:
elif config.is_unlocked():
lock_device(interrupt_workflow=workflow.autolock_interrupts_workflow)
+ if utils.USE_POWER_MANAGER and not utils.EMULATOR:
+ # FIXME: suspend not implemented on emulator
+ wakeup_flag = suspend_device()
+ handle_wakeup_from_suspend(wakeup_flag)
+
+
+if utils.USE_POWER_MANAGER:
+
+ def lock_device_if_unlocked_on_battery() -> None:
+ """Lock the device if it is unlocked and running on battery or wireless charger."""
+ if not io.pm.is_usb_connected():
+ lock_device_if_unlocked()
+
+ def handle_wakeup_from_suspend(wakeup_flag: int) -> None:
+ """Handle wakeup from suspend."""
+ from trezor.ui import CURRENT_LAYOUT
+
+ if wakeup_flag == io.pm.WAKEUP_FLAG_BUTTON:
+ if CURRENT_LAYOUT is not None:
+ CURRENT_LAYOUT.layout.request_complete_repaint()
+ # TODO: handle PWR
+
async def unlock_device() -> None:
"""Ensure the device is in unlocked state.
@@ -609,6 +636,14 @@ def reload_settings_from_storage() -> None:
workflow.idle_timer.set(
storage_device.get_autolock_delay_ms(), lock_device_if_unlocked
)
+
+ if utils.USE_POWER_MANAGER:
+ # autodim setting is not from storage but keeping it here for simplicity
+ workflow.idle_timer.set(30_000, autodim_display)
+ workflow.idle_timer.set(
+ storage_device.get_autolock_delay_battery_ms(),
+ lock_device_if_unlocked_on_battery,
+ )
wire.message_handler.EXPERIMENTAL_ENABLED = (
storage_device.get_experimental_features()
)
@@ -642,11 +677,6 @@ def boot() -> None:
workflow_handlers.register(msg_type, handler)
reload_settings_from_storage()
- if utils.USE_POWER_MANAGER:
- from apps.management.pm.autodim import autodim_display
-
- workflow.idle_timer.set(30_000, autodim_display)
-
if backup.repeated_backup_enabled():
backup.activate_repeated_backup()
if not config.is_unlocked():
diff --git a/core/src/storage/device.py b/core/src/storage/device.py
index 2abb611e5..0352ed697 100644
--- a/core/src/storage/device.py
+++ b/core/src/storage/device.py
@@ -68,7 +68,7 @@ AUTOLOCK_DELAY_USB_MAX_MS = const(0x2000_0000) # ~6 days
if utils.USE_POWER_MANAGER:
AUTOLOCK_DELAY_BATT_MIN_MS = 30 * 1000 # 30 seconds
- AUTOLOCK_DELAY_BATT_DEFAULT_MS = const(40 * 1000) # 40 seconds
+ AUTOLOCK_DELAY_BATT_DEFAULT_MS = const(20 * 1000) # 40 seconds TODO: change to 40s
AUTOLOCK_DELAY_BATT_MAX_MS = const(10 * 60 * 1000) # 10 minutes
diff --git a/core/src/trezor/ui/__init__.py b/core/src/trezor/ui/__init__.py
index b458dc115..43f9fae48 100644
--- a/core/src/trezor/ui/__init__.py
+++ b/core/src/trezor/ui/__init__.py
@@ -17,6 +17,7 @@ from trezorui_api import (
)
if utils.USE_POWER_MANAGER:
+ from trezor import config
from trezor.power_management.autodim import autodim_clear
if TYPE_CHECKING:
@@ -105,18 +106,14 @@ if utils.USE_POWER_MANAGER:
def _handle_power_button_press() -> None:
"""Handle power button press event during firmware operation."""
- from trezor import config
+ from apps.base import lock_device_if_unlocked
- from apps.base import lock_device
- from apps.management.pm.suspend import suspend_device
+ will_close_workflow = config.has_pin() and workflow.autolock_interrupts_workflow
+ lock_device_if_unlocked()
- if config.has_pin() and config.is_unlocked():
- lock_device(interrupt_workflow=True)
+ if will_close_workflow:
+ # prevent further layout interaction
raise Shutdown()
- else:
- suspend_device()
- if CURRENT_LAYOUT is not None:
- CURRENT_LAYOUT.layout.request_complete_repaint()
class Layout(Generic[T]):
@@ -489,7 +486,7 @@ class Layout(Generic[T]):
while True:
flags = yield pm
if flags & io.pm.EVENT_USB_CONNECTED_CHANGED:
- # disconnecting from charger restarts autodim/autosuspend timer
+ # disconnecting from charger restarts autodim/autolock timer
# connecting to charger clears autodim state
workflow.idle_timer.touch()
autodim_clear()
Why this scored 11/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.