fix(core): skip unexpected THP packet during channel allocation
What changed, and why it matters
This commit changes the Trezor host-side Python library so that when it tries to open a new communication channel with a Trezor device, it can ignore up to 50 unexpected packets before giving up. Previously, a single stray or delayed packet would cause the channel setup to fail. The change makes the connection process more tolerant of leftover traffic, which could happen during retransmissions or after a previous session.
Treat as a normal reliability/robustness improvement. Review whether 50 retries is bounded enough to avoid long stalls, and ensure the loop cannot be abused to cause denial of service by keeping the host waiting. No immediate security response appears necessary based on the diff alone.
Security signals we found
Host-side transport protocol hardening
Retry loop added around packet parsing
CRC-checked packets still validated before use
No cryptographic or authorization logic changed
Evidence from the diff
In python/src/trezorlib/transport/thp/protocol_v2.py, _do_channel_allocation() and _read_channel_allocation_response() are modified to accept a retries parameter (default 0). prepare_channel_without_pairing() now calls _do_channel_allocation(retries=50). The response reader loops up to 1 + retries times, reading packets until it finds a valid channel allocation response matching the expected nonce. If none match, it raises an exception. This is a robustness fix for the host-side transport protocol implementation.
Changed components
python/src/trezorlib/transport/thp/protocol_v2.pyProtocolV2Channel classprepare_channel_without_pairing method_do_channel_allocation method_read_channel_allocation_response methodInspect captured patch +14 / −9
diff --git a/python/src/trezorlib/transport/thp/protocol_v2.py b/python/src/trezorlib/transport/thp/protocol_v2.py
index c353fd60..d46786fb 100644
--- a/python/src/trezorlib/transport/thp/protocol_v2.py
+++ b/python/src/trezorlib/transport/thp/protocol_v2.py
@@ -127,17 +127,20 @@ class ProtocolV2Channel(Channel):
def prepare_channel_without_pairing(self, credential: bytes | None = None) -> int:
self._reset_sync_bits()
- self._do_channel_allocation()
+ # allow skipping unrelated response packets (e.g. in case of retransmissions)
+ self._do_channel_allocation(retries=50)
return self._do_handshake(credential=credential)
def _reset_sync_bits(self) -> None:
self.sync_bit_send = 0
self.sync_bit_receive = 0
- def _do_channel_allocation(self) -> None:
+ def _do_channel_allocation(self, retries: int = 0) -> None:
channel_allocation_nonce = os.urandom(8)
self._send_channel_allocation_request(channel_allocation_nonce)
- cid, dp = self._read_channel_allocation_response(channel_allocation_nonce)
+ cid, dp = self._read_channel_allocation_response(
+ channel_allocation_nonce, retries=retries
+ )
self.channel_id = cid
self.device_properties = dp
@@ -149,13 +152,15 @@ class ProtocolV2Channel(Channel):
)
def _read_channel_allocation_response(
- self,
- expected_nonce: bytes,
+ self, expected_nonce: bytes, retries: int = 0
) -> tuple[int, bytes]:
- header, payload = self._read_until_valid_crc_check()
- if not self._is_valid_channel_allocation_response(
- header, payload, expected_nonce
- ):
+ for _ in range(1 + retries):
+ header, payload = self._read_until_valid_crc_check()
+ if self._is_valid_channel_allocation_response(
+ header, payload, expected_nonce
+ ):
+ break
+ else:
raise Exception("Invalid channel allocation response.")
channel_id = int.from_bytes(payload[8:10], "big")
Why this scored 28/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.