refactor(core): use `HomescreenBase` as a context manager
What changed, and why it matters
This commit is a code cleanup: it replaces explicit calls to a destructor-like method with Python's standard 'with' context-manager pattern for homescreen objects. It does not add or remove security behavior; it only makes the cleanup more reliable and idiomatic. There is no indication this fixes an exploitable vulnerability.
No immediate action required. Treat as routine code hygiene. If auditing, verify that the new context-manager implementation correctly handles exceptions and does not introduce reference cycles that delay cleanup on the embedded target.
Security signals we found
Refactor of resource cleanup pattern (destructor to context manager)
Comment added indicating __del__ is safe to call repeatedly
No changelog entry, suggesting not treated as a security fix by vendor
No explicit security relevance stated in commit message or diff
Evidence from the diff
The patch refactors HomescreenBase and its subclasses (Homescreen, Lockscreen, Busyscreen) to implement enter/exit so callers can use ‘with … as obj:’ instead of manually invoking obj.del() in try/finally blocks. Layout.del is updated with a comment noting it is safe to call even if self.layout was already dropped. The change is defensive against double-cleanup or missed cleanup but is presented as a refactor with no changelog entry.
Changed components
core/src/apps/homescreen/__init__.pycore/src/boot.pycore/src/trezor/ui/__init__.pycore/src/trezor/ui/layouts/homescreen.pyInspect captured patch +26 / −21
diff --git a/core/src/apps/homescreen/__init__.py b/core/src/apps/homescreen/__init__.py
index d6614075..99dd4b5f 100644
--- a/core/src/apps/homescreen/__init__.py
+++ b/core/src/apps/homescreen/__init__.py
@@ -14,11 +14,8 @@ from apps.common.lock_manager import lock_device
async def busyscreen() -> None:
- obj = Busyscreen(busy_expiry_ms())
- try:
+ with Busyscreen(busy_expiry_ms()) as obj:
await obj.get_result()
- finally:
- obj.__del__()
async def homescreen() -> None:
@@ -69,15 +66,12 @@ async def homescreen() -> None:
False,
)
- obj = Homescreen(
+ with Homescreen(
label=label,
notification=notification,
lockable=config.has_pin(),
- )
- try:
+ ) as obj:
res = await obj.get_result()
- finally:
- obj.__del__()
if utils.INTERNAL_MODEL == "T3W1":
if res is trezorui_api.INFO:
@@ -94,14 +88,11 @@ async def _lockscreen(screensaver: bool = False) -> None:
# Only show the lockscreen UI if the device can in fact be locked, or if it is
# and OLED device (in which case the lockscreen is a screensaver).
if can_lock_device() or screensaver:
- obj = Lockscreen(
+ with Lockscreen(
label=storage.device.get_label(),
coinjoin_authorized=is_set_any_session(MessageType.AuthorizeCoinJoin),
- )
- try:
+ ) as obj:
await obj.get_result()
- finally:
- obj.__del__()
# Otherwise proceed directly to unlock() call. If the device is already unlocked,
# it should be a no-op storage-wise, but it resets the internal configuration
# to an unlocked state.
diff --git a/core/src/boot.py b/core/src/boot.py
index f87380ec..940f0334 100644
--- a/core/src/boot.py
+++ b/core/src/boot.py
@@ -55,9 +55,11 @@ def enforce_welcome_screen_duration() -> None:
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__()
+ with Lockscreen(
+ label=storage.device.get_label(),
+ bootscreen=True,
+ ) as lockscreen:
+ await lockscreen.get_result()
await verify_user_pin()
else:
@@ -70,9 +72,11 @@ else:
async def pin_unlock_sequence() -> None:
while True:
- lockscreen = Lockscreen(label=storage.device.get_label(), bootscreen=True)
- await lockscreen.get_result()
- lockscreen.__del__()
+ with Lockscreen(
+ label=storage.device.get_label(),
+ bootscreen=True,
+ ) as lockscreen:
+ await lockscreen.get_result()
res = await loop.race(verify_user_pin(), wait_for_suspend())
if res is _SUSPEND_MARKER:
# make some delay for the suspend
diff --git a/core/src/trezor/ui/__init__.py b/core/src/trezor/ui/__init__.py
index f4402a5c..ed762f0b 100644
--- a/core/src/trezor/ui/__init__.py
+++ b/core/src/trezor/ui/__init__.py
@@ -564,6 +564,7 @@ class Layout(Generic[T]):
loop.schedule(task, finalizer=self._task_finalizer)
def __del__(self) -> None:
+ # safe to call even if `self.layout` has been already dropped.
self.layout.__del__()
diff --git a/core/src/trezor/ui/layouts/homescreen.py b/core/src/trezor/ui/layouts/homescreen.py
index de9adc13..c386c223 100644
--- a/core/src/trezor/ui/layouts/homescreen.py
+++ b/core/src/trezor/ui/layouts/homescreen.py
@@ -9,6 +9,7 @@ if TYPE_CHECKING:
from typing import Any, Callable, Iterator, ParamSpec, Tuple, TypeVar
from trezor import loop
+ from typing_extensions import Self
P = ParamSpec("P")
R = TypeVar("R")
@@ -57,7 +58,7 @@ class UsbAwareLayout(ui.Layout):
class HomescreenBase(UsbAwareLayout):
RENDER_INDICATOR: object | None = None
- def __init__(self, layout: Any) -> None:
+ def __init__(self, layout: trezorui_api.LayoutObj[trezorui_api.UiResult]) -> None:
super().__init__(layout=layout)
self.should_resume = self._should_resume()
@@ -74,6 +75,14 @@ class HomescreenBase(UsbAwareLayout):
else:
self._paint()
+ def __enter__(self) -> Self:
+ self.layout.__enter__()
+ return self
+
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
+ """Drop internal Rust root component."""
+ self.layout.__exit__(exc_type, exc_val, exc_tb)
+
class Homescreen(HomescreenBase):
RENDER_INDICATOR = storage_cache.HOMESCREEN_ON
Why this scored 18/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.