u2f: Do not turn off BLE when U2F is used over USB
What changed, and why it matters
This firmware update fixes a bug where using U2F security-key features over USB accidentally turned off Bluetooth Low Energy (BLE). Because iPhones and iPads can use U2F over USB, disabling BLE meant the device could not reconnect wirelessly after the iOS device woke up. The patch also makes the firmware ignore a harmless 64-byte 'wake-up' packet sent by iOS. There is no direct evidence this is an exploitable security vulnerability; it appears to be a connectivity/availability bug.
Treat as a functional/availability fix rather than a security patch. Reviewers may want to confirm that the inverted return value of usb_packet_process() does not affect other callers or state machines, and that ignoring the iOS wake-up frame cannot be abused to drop legitimate packets.
Security signals we found
Behavioral change in USB packet return values (true/false semantics inverted)
Removal of BLE power-down on U2F USB activity
Conditional BLE shutdown only after valid HWW packet processing
Explicit handling of 64-byte null 'garbage' wake-up packet from iOS
CHANGELOG labels it a bug fix, not a security fix
Evidence from the diff
In the main loop, both HWW (main wallet) and U2F USB reads previously called communication_mode_ble_disable() unconditionally. The patch removes BLE shutdown from the U2F path and makes it conditional on usb_packet_process() returning true in the HWW path. usb_packet_process() return semantics are inverted: it now returns true on successful parse and false on ignored/invalid frames. This prevents an iOS wake-up garbage frame (64 null bytes) from shutting down BLE. The CHANGELOG frames the change as a bug fix for BLE being turned off when an iOS device is unlocked.
Changed components
src/firmware_main_loop.csrc/usb/usb_packet.csrc/usb/usb_packet.hBLE/U2F USB interaction subsystemInspect captured patch +17 / −21
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 85ed853..9eed3f5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ customers cannot upgrade their bootloader, its changes are recorded separately.
## Firmware
### [Unreleased]
+- Fix bug that BLE was turned off when iOS device is unlocked
### v9.24.0
- Change title when entering recovery words to `1 of 24`, `2 of 24`, etc.
diff --git a/src/firmware_main_loop.c b/src/firmware_main_loop.c
index 8282b8d..e09bf3a 100644
--- a/src/firmware_main_loop.c
+++ b/src/firmware_main_loop.c
@@ -112,30 +112,25 @@ void firmware_main_loop(void)
#endif
// Do USB Input
if (!hww_data && hid_hww_read(&hww_frame[0])) {
- usb_packet_process((const USB_FRAME*)hww_frame);
- if (communication_mode_ble_enabled()) {
- // Enqueue a power down command to the da14531
- da14531_power_down(&uart_write_queue);
- // Flush out the power down command. This will be the last UART communication we do.
- while (ringbuffer_num(&uart_write_queue) > 0) {
- uart_poll(NULL, 0, NULL, &uart_write_queue);
+ if (usb_packet_process((const USB_FRAME*)hww_frame)) {
+ if (communication_mode_ble_enabled()) {
+ // Enqueue a power down command to the da14531
+ da14531_power_down(&uart_write_queue);
+ // Flush out the power down command. This will be the last UART communication we
+ // do.
+ while (ringbuffer_num(&uart_write_queue) > 0) {
+ uart_poll(NULL, 0, NULL, &uart_write_queue);
+ }
+ communication_mode_ble_disable();
}
- communication_mode_ble_disable();
+ } else {
+ util_log("usb_packet_process: invalid");
}
}
#if APP_U2F == 1
if (!u2f_data && hid_u2f_read(&u2f_frame[0])) {
util_log("u2f data %s", util_dbg_hex((void*)u2f_frame, 16));
u2f_packet_process((const USB_FRAME*)u2f_frame);
- if (communication_mode_ble_enabled()) {
- // Enqueue a power down command to the da14531
- da14531_power_down(&uart_write_queue);
- // Flush out the power down command. This will be the last UART communication we do.
- while (ringbuffer_num(&uart_write_queue) > 0) {
- uart_poll(NULL, 0, NULL, &uart_write_queue);
- }
- communication_mode_ble_disable();
- }
}
#endif
@@ -156,7 +151,7 @@ void firmware_main_loop(void)
}
}
#if APP_U2F == 1
- if (!communication_mode_ble_enabled() && u2f_data) {
+ if (u2f_data) {
if (hid_u2f_write_poll(u2f_data)) {
util_log("u2f wrote %s", util_dbg_hex(u2f_data, 16));
u2f_data = NULL;
diff --git a/src/usb/usb_packet.c b/src/usb/usb_packet.c
index b22620b..991a1cf 100644
--- a/src/usb/usb_packet.c
+++ b/src/usb/usb_packet.c
@@ -67,7 +67,7 @@ bool usb_packet_process(const USB_FRAME* frame)
switch (usb_frame_process(frame, &_in_state)) {
case FRAME_ERR_IGNORE:
// Ignore this frame, i.e. no response.
- break;
+ return false;
case FRAME_ERR_INVALID_SEQ:
// Reset the state becuase this error indicates that there is a host application bug
_reset_state();
@@ -92,7 +92,7 @@ bool usb_packet_process(const USB_FRAME* frame)
ctx, _in_state.data, _in_state.len, _in_state.cmd, _in_state.cid)) {
// Queue filled and will be sent during usb processing
_reset_state();
- return false;
+ return true;
}
// Else: Currently processing a message, reset the state and forget about this packet
_reset_state();
diff --git a/src/usb/usb_packet.h b/src/usb/usb_packet.h
index a037f69..f289c8e 100644
--- a/src/usb/usb_packet.h
+++ b/src/usb/usb_packet.h
@@ -42,7 +42,7 @@ typedef struct {
/**
* Processes an incoming USB packet.
* @param[in] frame The frame that is to be processed.
- * @return true if we are waiting for more frames to complete a packet, false otherwise.
+ * @return true if packet was successfully parsed, false otherwise
*/
bool usb_packet_process(const USB_FRAME* frame);
Why this scored 21/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.