refactor(core): scope `LayoutObj` lifetime at `request_word()`
What changed, and why it matters
This commit is a small code cleanup in the Trezor hardware wallet firmware. It changes how on-screen keyboards are managed during wallet recovery (when users type in their backup seed words). Previously, the code explicitly called a destructor-like method `keyboard.__del__()` inside a try/finally block to ensure cleanup. Now it uses Python's `with keyboard:` context manager, which is a more standard and safer way to guarantee cleanup happens. There is no direct evidence this fixes a security vulnerability.
No immediate action required. Treat as routine defensive refactoring. If auditing, verify that `LayoutObj.__enter__` and `__exit__` methods correctly invoke the same cleanup previously done by `__del__`, and that the eckhart variant's new `with keyboard:` scope does not introduce any ordering side effects.
Security signals we found
Explicit destructor call replaced with context-manager lifetime scoping
Cleanup now guaranteed on exception/cancellation paths
No functional change to user-visible recovery flow
No mention of security, CVE, bug bounty, or researcher attribution in commit
Evidence from the diff
The patch refactors four UI layout implementations (bolt, caesar, delizia, eckhart) for the request_word() async function used during mnemonic/recovery phrase entry. It replaces an explicit try/finally: keyboard.__del__() pattern with a with keyboard: context manager. In the eckhart variant, which previously lacked any explicit cleanup, it adds the with keyboard: scoping. This is a defensive refactor that ensures LayoutObj cleanup runs reliably even if interact() raises an exception or the coroutine is cancelled. The change is behaviorally equivalent in the success path and strictly safer in exceptional paths.
Changed components
core/src/trezor/ui/layouts/bolt/recovery.pycore/src/trezor/ui/layouts/caesar/recovery.pycore/src/trezor/ui/layouts/delizia/recovery.pycore/src/trezor/ui/layouts/eckhart/recovery.pyInspect captured patch +12 / −21
diff --git a/core/src/trezor/ui/layouts/bolt/recovery.py b/core/src/trezor/ui/layouts/bolt/recovery.py
index 9ceda554..949ab63f 100644
--- a/core/src/trezor/ui/layouts/bolt/recovery.py
+++ b/core/src/trezor/ui/layouts/bolt/recovery.py
@@ -45,15 +45,12 @@ async def request_word(
prompt=prompt, prefill_word=prefill_word, can_go_back=can_go_back
)
- try:
- word: str = await interact(
+ with keyboard:
+ return await interact(
keyboard,
"mnemonic" if send_button_request else None,
ButtonRequestType.MnemonicInput,
)
- finally:
- keyboard.__del__()
- return word
def format_remaining_shares_info(
diff --git a/core/src/trezor/ui/layouts/caesar/recovery.py b/core/src/trezor/ui/layouts/caesar/recovery.py
index 55e1e6e4..ac3b2253 100644
--- a/core/src/trezor/ui/layouts/caesar/recovery.py
+++ b/core/src/trezor/ui/layouts/caesar/recovery.py
@@ -51,15 +51,12 @@ async def request_word(
prompt=prompt, prefill_word=prefill_word, can_go_back=can_go_back
)
- try:
- word: str = await interact(
+ with keyboard:
+ return await interact(
keyboard,
"mnemonic" if send_button_request else None,
ButtonRequestType.MnemonicInput,
)
- finally:
- keyboard.__del__()
- return word
async def show_remaining_shares(
diff --git a/core/src/trezor/ui/layouts/delizia/recovery.py b/core/src/trezor/ui/layouts/delizia/recovery.py
index 89276656..5255dcc0 100644
--- a/core/src/trezor/ui/layouts/delizia/recovery.py
+++ b/core/src/trezor/ui/layouts/delizia/recovery.py
@@ -46,15 +46,12 @@ async def request_word(
prompt=prompt, prefill_word=prefill_word, can_go_back=can_go_back
)
- try:
- word: str = await interact(
+ with keyboard:
+ return await interact(
keyboard,
"mnemonic" if send_button_request else None,
ButtonRequestType.MnemonicInput,
)
- finally:
- keyboard.__del__()
- return word
def format_remaining_shares_info(
diff --git a/core/src/trezor/ui/layouts/eckhart/recovery.py b/core/src/trezor/ui/layouts/eckhart/recovery.py
index 2da4e15d..95852e55 100644
--- a/core/src/trezor/ui/layouts/eckhart/recovery.py
+++ b/core/src/trezor/ui/layouts/eckhart/recovery.py
@@ -46,12 +46,12 @@ async def request_word(
prompt=prompt, prefill_word=prefill_word, can_go_back=True
)
- word: str = await interact(
- keyboard,
- "mnemonic" if send_button_request else None,
- ButtonRequestType.MnemonicInput,
- )
- return word
+ with keyboard:
+ return await interact(
+ keyboard,
+ "mnemonic" if send_button_request else None,
+ ButtonRequestType.MnemonicInput,
+ )
def format_remaining_shares_info(
Why this scored 12/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.