feat(python): ignore retransmitted THP payloads
What changed, and why it matters
This commit changes the Trezor Python library's handling of messages sent over the new THP (Trezor Host Protocol) transport. Previously, if a message had a bad checksum, the code would read a new message but did not track whether the newly received message was a fresh one or an old retransmission. The new code loops until it receives a message that passes the checksum and also has the expected sequence/sync bit, ignoring stale or out-of-order retransmissions. This is a hardening change: it makes the host-side protocol implementation more robust against duplicate or replayed packets, which could otherwise confuse state machines or cause commands to be processed twice.
Treat as a defensive hardening improvement. Review whether the device-side THP implementation has matching sequence-bit validation, and confirm that retransmissions are only generated by legitimate protocol conditions rather than attacker-injected packets. No urgent patch action is indicated solely from this diff.
Security signals we found
Adds sequence-bit validation to ignore retransmitted/out-of-sync THP payloads
Prevents potential replay or duplicate-packet acceptance in host-side protocol parser
Hardens checksum retry loop against stale/retried messages
No changelog entry suggests minor/internal hardening rather than advertised security fix
Evidence from the diff
In python/src/trezorlib/transport/thp/protocol_v2.py, the _read_message method is refactored from a checksum-only retry loop into an infinite loop that validates both the checksum and the THP sequence bit (ctrl_byte seq_bit). If the checksum is invalid, the loop continues and reads again. If the seq_bit is present and does not match self.sync_bit_receive, the packet is logged as unexpected and skipped; when it matches, sync_bit_receive is toggled. This adds explicit duplicate/retransmission detection on the host side of the THP v2 channel.
Changed components
python/src/trezorlib/transport/thp/protocol_v2.pyProtocolV2Channel._read_messageTrezor Python client library THP transportInspect captured patch +17 / −7
diff --git a/python/src/trezorlib/transport/thp/protocol_v2.py b/python/src/trezorlib/transport/thp/protocol_v2.py
index c34a3e34..c805e249 100644
--- a/python/src/trezorlib/transport/thp/protocol_v2.py
+++ b/python/src/trezorlib/transport/thp/protocol_v2.py
@@ -371,18 +371,28 @@ class ProtocolV2Channel(Channel):
if timeout is None:
timeout = self._DEFAULT_READ_TIMEOUT
- is_valid = False
- header, payload, chksum = thp_io.read(self.transport, timeout)
- while not is_valid:
- is_valid = checksum.is_valid(chksum, header.to_bytes_init() + payload)
- if not is_valid:
+ while True:
+ header, payload, chksum = thp_io.read(self.transport, timeout)
+ if not checksum.is_valid(chksum, header.to_bytes_init() + payload):
LOG.error(
"Received a message with an invalid checksum:"
+ hexlify(header.to_bytes_init() + payload + chksum).decode()
)
- header, payload, chksum = thp_io.read(self.transport, timeout)
+ continue
- return header, payload
+ seq_bit = control_byte.get_seq_bit(header.ctrl_byte)
+ if seq_bit is not None:
+ if seq_bit != self.sync_bit_receive:
+ LOG.warning(
+ "Received unexpected message: sync bit=%d, expected=%d",
+ seq_bit,
+ self.sync_bit_receive,
+ )
+ continue
+
+ self.sync_bit_receive = 1 - self.sync_bit_receive
+
+ return header, payload
def _is_valid_channel_allocation_response(
self, header: MessageHeader, payload: bytes, original_nonce: bytes
Why this scored 35/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.