fix(python): ignore packets with mismatched THP channel
What changed, and why it matters
This commit fixes the Trezor Python host library so it ignores USB packets whose channel ID does not match the current session. Previously, packets from other channels (or broadcast packets at the wrong time) could be accepted and processed, which could confuse the host-side state machine during the encrypted Trezor-Host Protocol (THP). The fix adds a check that drops mismatched packets unless they are explicitly allowed as broadcast packets.
Treat as a security hardening fix. Review whether the device-side firmware enforces equivalent channel validation, since host-side filtering alone does not protect against a malicious or compromised host. No immediate user action is required beyond updating the Python library.
Security signals we found
Input validation added for channel identifier (CID)
Cross-channel packet filtering
Broadcast packet handling restricted to explicit allowlist
State-machine hardening in THP transport layer
Evidence from the diff
In python/src/trezorlib/thp/channel.py, the Channel._read() method now validates message.cid against self.channel_id. If they differ and either broadcast is not allowed or the packet is not on BROADCAST_CHANNEL_ID, the packet is logged and discarded. The ping/pong handshake passes allow_broadcast=True because it legitimately expects broadcast PONG responses. All other reads now enforce strict channel matching. This prevents cross-channel packet injection or state desynchronization between the host and device.
Changed components
python/src/trezorlib/thp/channel.pyTrezor Python host library THP channel implementationInspect captured patch +16 / −3
diff --git a/python/src/trezorlib/thp/channel.py b/python/src/trezorlib/thp/channel.py
index b5000025..40b89cc1 100644
--- a/python/src/trezorlib/thp/channel.py
+++ b/python/src/trezorlib/thp/channel.py
@@ -36,7 +36,7 @@ from ..exceptions import (
)
from . import control_byte, curve25519, exceptions, thp_io
from .credentials import TrezorPublicKeys, find_credential
-from .message import Message
+from .message import BROADCAST_CHANNEL_ID, Message
if t.TYPE_CHECKING:
from contextlib import AbstractContextManager
@@ -209,7 +209,7 @@ class Channel:
message = Message.broadcast(control_byte.PING, nonce)
thp_io.write_payload_to_wire(self.transport, message)
for _ in range(1 + retries):
- message = self._read(timeout=timeout)
+ message = self._read(timeout=timeout, allow_broadcast=True)
if not message.is_pong():
LOG.debug(
"Discarding non-pong message: %s", message.to_bytes().hex()
@@ -510,7 +510,9 @@ class Channel:
self._send_ack(message)
return self.noise.decrypt(bytes(message.data))
- def _read(self, timeout: float | None = None) -> Message:
+ def _read(
+ self, timeout: float | None = None, allow_broadcast: bool = False
+ ) -> Message:
if timeout is None:
timeout = client._DEFAULT_READ_TIMEOUT
@@ -521,6 +523,17 @@ class Channel:
while True:
message = thp_io.read(self.transport, timeout)
+
+ if message.cid != self.channel_id and (
+ not allow_broadcast or message.cid != BROADCAST_CHANNEL_ID
+ ):
+ LOG.warning(
+ "Received message with unexpected channel_id=%04x, expected=%04x",
+ message.cid,
+ self.channel_id,
+ )
+ continue
+
if message.seq_bit is not None:
if message.seq_bit != self.sync_bit_receive:
LOG.warning(
Why this scored 46/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.