fix(core): wait for `_handle_button_requests` only on THP devices
What changed, and why it matters
This commit fixes a regression in the Trezor hardware wallet's user-interface code. A previous change had started waiting for a synchronization signal (called a 'mailbox') during every button request, but that wait was only needed for newer THP-style communication. On older non-THP devices, the extra wait could cause the device to hang or behave incorrectly when confirming actions on screen. The patch restores the old behavior for non-THP devices while keeping the new behavior for THP devices.
Treat as a bug-fix regression patch. Review whether the original change that introduced the unconditional mailbox was shipped in a release, and if so, consider whether a firmware update or advisory is warranted for users of non-THP devices experiencing hangs during confirmations. No independent security exploit is evident from the diff alone.
Security signals we found
UI flow regression that could cause device hang or unresponsiveness during user confirmation
Communication-channel desynchronization risk specifically mentioned for THP path
Behavior restored for non-THP devices to avoid unintended side effects
Evidence from the diff
In core/src/trezor/ui/init.py, the Layout class’s main loop was unconditionally creating a loop.mailbox() and passing it to _handle_button_requests. The mailbox is used on THP (Trezor Host Protocol) devices to ensure the ButtonRequest ACK is received before the result is returned, preventing back-to-back writes that could desync the THP channel. On non-THP devices this mailbox is unnecessary and appears to have caused a regression. The patch gates both the mailbox creation and the post-result wait on utils.USE_THP, and makes _handle_button_requests accept None for the is_done parameter, only signaling it when present.
Changed components
core/src/trezor/ui/__init__.pyLayout class_handle_button_requests coroutineTHP and non-THP device interaction flowsInspect captured patch +6 / −4
diff --git a/core/src/trezor/ui/__init__.py b/core/src/trezor/ui/__init__.py
index 5c4ad9039..83dd2fbfd 100644
--- a/core/src/trezor/ui/__init__.py
+++ b/core/src/trezor/ui/__init__.py
@@ -264,13 +264,14 @@ class Layout(Generic[T]):
is_done = None
try:
if self.context is not None and self.result_box.is_empty():
- is_done = loop.mailbox()
+ if utils.USE_THP:
+ is_done = loop.mailbox() # (see below)
self.button_request_task = self._handle_button_requests(is_done)
self._start_task(self.button_request_task)
result = await self.result_box
- if is_done is not None:
+ if utils.USE_THP and is_done is not None:
# Make sure ButtonRequest is ACKed, before the result is returned.
# Otherwise, THP channel may become desynced (due to two consecutive writes).
if __debug__:
@@ -453,7 +454,7 @@ class Layout(Generic[T]):
finally:
touch.close()
- async def _handle_button_requests(self, is_done: loop.mailbox[None]) -> None:
+ async def _handle_button_requests(self, is_done: loop.mailbox[None] | None) -> None:
try:
if self.context is None:
return
@@ -489,7 +490,8 @@ class Layout(Generic[T]):
except Exception:
raise
finally:
- is_done.put(None)
+ if is_done is not None:
+ is_done.put(None)
if utils.USE_BLE:
Why this scored 34/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.