fix(core): fix `AVOID_RESTARTING_FOR` handling in THP
What changed, and why it matters
This commit fixes a bug in the Trezor hardware wallet's new THP (Trezor Host Protocol) message handling. Previously, the code ignored a special signal that tells the device not to restart the current flow when certain harmless messages (like GetFeatures) arrive. On the TS7 (Trezor Safe 7) device, this caused the recovery/setup flow to be unexpectedly interrupted. The fix makes THP behave the same as the older v1 protocol handler by correctly passing through that signal.
Treat as a reliability/UX bug fix rather than an active security vulnerability. No immediate incident response is indicated, but firmware builds including this fix should be validated to ensure recovery flows are no longer interrupted by GetFeatures on TS7. Review whether other THP state handlers similarly drop return values from lower-layer handlers.
Security signals we found
Workflow interruption / denial-of-service: recovery flow could be aborted by a benign GetFeatures message
Protocol state-handling inconsistency between v1 and THP
Return-value propagation fix for AVOID_RESTARTING_FOR sentinel logic
Evidence from the diff
The patch changes _handle_state_ENCRYPTED_TRANSPORT and GenericSessionContext.handle to return a boolean instead of None. This boolean propagates the result of handle_single_message, which indicates whether the message type is in AVOID_RESTARTING_FOR (i.e., should not cancel/restart the current workflow). Previously, THP always returned False/equivalent, so incoming GetFeatures messages during recovery would restart the flow and interrupt it. The fix aligns THP behavior with the v1 protocol handler.
Changed components
core/src/trezor/wire/thp/received_message_handler.pycore/src/trezor/wire/thp/session_context.pyTrezor Safe 7 (TS7) THP message handlingInspect captured patch +5 / −9
diff --git a/core/src/trezor/wire/thp/received_message_handler.py b/core/src/trezor/wire/thp/received_message_handler.py
index b0b6e310..056ebb3d 100644
--- a/core/src/trezor/wire/thp/received_message_handler.py
+++ b/core/src/trezor/wire/thp/received_message_handler.py
@@ -54,8 +54,7 @@ async def handle_received_message(channel: Channel) -> bool:
try:
state = channel.get_channel_state()
if state is ChannelState.ENCRYPTED_TRANSPORT:
- await _handle_state_ENCRYPTED_TRANSPORT(channel)
- return False
+ return await _handle_state_ENCRYPTED_TRANSPORT(channel)
elif _is_channel_state_pairing(state):
await _handle_pairing(channel)
return False
@@ -271,7 +270,7 @@ async def _handle_state_handshake(
ctx.set_channel_state(ChannelState.TP0)
-async def _handle_state_ENCRYPTED_TRANSPORT(ctx: Channel) -> None:
+async def _handle_state_ENCRYPTED_TRANSPORT(ctx: Channel) -> bool:
if __debug__:
log.debug(__name__, "handle_state_ENCRYPTED_TRANSPORT", iface=ctx.iface)
@@ -290,7 +289,7 @@ async def _handle_state_ENCRYPTED_TRANSPORT(ctx: Channel) -> None:
s = ctx.sessions[session_id]
update_session_last_used(s.channel_id, (s.session_id).to_bytes(1, "big"))
- await s.handle(message)
+ return await s.handle(message)
async def _handle_pairing(ctx: Channel) -> None:
diff --git a/core/src/trezor/wire/thp/session_context.py b/core/src/trezor/wire/thp/session_context.py
index dd76ffb0..a7800603 100644
--- a/core/src/trezor/wire/thp/session_context.py
+++ b/core/src/trezor/wire/thp/session_context.py
@@ -35,7 +35,7 @@ class GenericSessionContext(Context):
self.channel: Channel = channel
self.session_id: int = session_id
- async def handle(self, message: Message) -> None:
+ async def handle(self, message: Message) -> bool:
if __debug__:
log.debug(
__name__,
@@ -47,9 +47,7 @@ class GenericSessionContext(Context):
while True:
try:
- await handle_single_message(self, message)
- if __debug__:
- self.channel._log("session loop is over")
+ return await handle_single_message(self, message)
except protocol_common.WireError as e:
if __debug__:
log.exception(__name__, e, iface=self.iface)
@@ -63,7 +61,6 @@ class GenericSessionContext(Context):
except Exception as exc:
if __debug__:
log.exception(__name__, exc, iface=self.iface)
- return
async def _read_next_message(self) -> Message:
while True:
Why this scored 30/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.