fix(python): avoid crash when scanning BLE multiple times
What changed, and why it matters
This commit fixes several small bugs in Trezor's Python library for Bluetooth (BLE) communication. The main fix prevents a crash when scanning for BLE devices multiple times, caused by a typo that iterated over dictionary values instead of items. Other changes add safety checks before reading or writing to a BLE device and improve error messages. There is no clear security vulnerability being patched; it looks like ordinary bug fixing and hardening.
Treat as a routine bug-fix and minor hardening patch. Reviewers may optionally verify that the new guard checks do not mask deeper lifecycle issues in BLE peripheral management, but no urgent security action is indicated.
Security signals we found
Fixes a runtime crash in BLE transport layer
Adds null/None guards before read and write operations on BLE peripheral
Improves exception chaining and error diagnostics
No explicit security framing by vendor
Evidence from the diff
The diff modifies python/src/trezorlib/transport/ble.py. Key changes: (1) fixes a dict comprehension from .values() to .items() when filtering self.devices, which would previously crash with a TypeError; (2) adds a type annotation dict[str, Peripheral]; (3) skips devices with None names when returning scan results; (4) adds guard checks that periph.queue and periph.client are not None before read/write operations; (5) improves error messages and exception chaining. The commit title frames this as avoiding a crash when scanning BLE multiple times. No changelog entry is requested.
Changed components
python/src/trezorlib/transport/ble.pyTrezor Python client library BLE transportInspect captured patch +12 / −6
diff --git a/python/src/trezorlib/transport/ble.py b/python/src/trezorlib/transport/ble.py
index 691ffda1..a603940e 100644
--- a/python/src/trezorlib/transport/ble.py
+++ b/python/src/trezorlib/transport/ble.py
@@ -194,7 +194,7 @@ class BleAsync:
asyncio.run(self.main(pipe))
async def main(self, pipe: Connection) -> None:
- self.devices = {}
+ self.devices: dict[str, Peripheral] = {}
self.did_scan = False
LOG.debug("async BLE process started")
@@ -239,7 +239,7 @@ class BleAsync:
# throw away non connected peripherals
self.devices = {
- addr: periph for addr, periph in self.devices.values() if periph.client
+ addr: periph for addr, periph in self.devices.items() if periph.client
}
for address, (dev, adv_data) in devices.items():
if TREZOR_SERVICE_UUID not in adv_data.service_uuids:
@@ -254,7 +254,9 @@ class BleAsync:
self.devices[address] = Peripheral(dev, adv_data)
self.did_scan = True
return [
- (periph.address, periph.device.name) for periph in self.devices.values()
+ (periph.address, periph.device.name)
+ for periph in self.devices.values()
+ if periph.device.name is not None
]
async def connect(self, address: str) -> None:
@@ -263,7 +265,7 @@ class BleAsync:
periph = self.devices.get(address)
if not periph:
- raise RuntimeError("device not found")
+ raise RuntimeError(f"Device not found: {address}")
if periph.client:
LOG.debug(f"Already connected to {periph.address}")
@@ -341,13 +343,17 @@ class BleAsync:
async def read(self, address: str, timeout: float | None) -> bytes:
periph = self.devices[address]
+ if periph.queue is None:
+ raise RuntimeError("Connect to peripheral before reading")
try:
return await asyncio.wait_for(periph.queue.get(), timeout=timeout)
- except (TimeoutError, asyncio.TimeoutError):
- raise Timeout(f"Timeout reading BLE packet ({timeout}s)")
+ except (TimeoutError, asyncio.TimeoutError) as err:
+ raise Timeout(f"Timeout reading BLE packet ({timeout}s)") from err
async def write(self, address: str, chunk: bytes) -> None:
periph = self.devices[address]
+ if periph.client is None:
+ raise RuntimeError("Connect to peripheral before writing")
await periph.client.write_gatt_char(
TREZOR_CHARACTERISTIC_RX, chunk, response=SHOULD_WRITE_WITH_RESPONSE
)
Why this scored 20/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.