fix(core/bootloader): do not disconnect BLE when rebooting to firmware
What changed, and why it matters
This commit changes how the Trezor hardware wallet's Bluetooth Low Energy (BLE) connection behaves when the device reboots from the bootloader into the main firmware. Previously, the bootloader would shut down BLE entirely during this handoff, forcing the user to reconnect. Now it tries to keep the existing BLE connection alive across the reboot. This is a user-experience and availability fix rather than a security patch, but it slightly alters the attack surface during a sensitive transition.
Treat as a functional/UX change, not an urgent security fix. Review the BLE state machine to confirm that BLE_MODE_KEEP_CONNECTION does not leave the device discoverable or pairable during firmware boot, and that bond/pairing state is correctly revalidated after the transition. Consider whether an attacker could exploit the preserved connection to inject or replay messages before the firmware has fully initialized.
Security signals we found
Changes BLE state machine during security-critical bootloader-to-firmware transition
Introduces new BLE command that suppresses advertising while keeping a connection
Alters disconnect/reconnect behavior around reboot
No explicit security claims or changelog entry in commit
Evidence from the diff
The patch introduces a new BLE command, BLE_KEEP_CONNECTION, and a driver flag restart_adv_on_disconnect. In the bootloader’s BLE deinitialization path, it now issues BLE_KEEP_CONNECTION instead of BLE_SWITCH_OFF. The BLE driver logic is updated so that when restart_adv_on_disconnect is false, a connected device stays in BLE_MODE_KEEP_CONNECTION and advertising is not restarted on disconnect. Additionally, in wf_bootloader.c, after handling a BLE pairing request, if no IO interfaces are active the bootloader issues BLE_KEEP_CONNECTION to stop advertising while preserving any active connection. The goal is to avoid dropping the BLE link when rebooting from bootloader to firmware.
Changed components
core/embed/io/ble/inc/io/ble.hcore/embed/io/ble/stm32/ble.ccore/embed/projects/bootloader/wire/wire_iface_ble.ccore/embed/projects/bootloader/workflow/wf_bootloader.cInspect captured patch +32 / −7
diff --git a/core/embed/io/ble/inc/io/ble.h b/core/embed/io/ble/inc/io/ble.h
index 48444577..fb9588a6 100644
--- a/core/embed/io/ble/inc/io/ble.h
+++ b/core/embed/io/ble/inc/io/ble.h
@@ -40,6 +40,8 @@ typedef enum {
BLE_ALLOW_PAIRING = 5, // Accept pairing request
BLE_REJECT_PAIRING = 6, // Reject pairing request
BLE_UNPAIR = 7, // Erase bond for currently connected device
+ BLE_KEEP_CONNECTION =
+ 8, // Keep connection to the connected device, but do not advertise
} ble_command_type_t;
typedef enum {
diff --git a/core/embed/io/ble/stm32/ble.c b/core/embed/io/ble/stm32/ble.c
index 68221475..bb5c34ec 100644
--- a/core/embed/io/ble/stm32/ble.c
+++ b/core/embed/io/ble/stm32/ble.c
@@ -58,6 +58,7 @@ typedef struct {
bool status_valid;
bool accept_msgs;
bool reboot_on_resume;
+ bool restart_adv_on_disconnect;
uint8_t busy_flag;
bool pairing_allowed;
bool pairing_requested;
@@ -287,17 +288,19 @@ static void ble_process_rx_msg_status(const uint8_t *data, uint32_t len) {
}
if (drv->mode_requested == BLE_MODE_KEEP_CONNECTION && !drv->connected) {
- if (drv->peer_count > 0) {
+ if (drv->peer_count > 0 && drv->restart_adv_on_disconnect) {
drv->mode_requested = BLE_MODE_CONNECTABLE;
} else {
drv->mode_requested = BLE_MODE_OFF;
}
}
- if (msg.peer_count > 1 && drv->peer_count <= 1) {
- // new bond
- if (msg.connected && drv->mode_requested == BLE_MODE_KEEP_CONNECTION) {
- drv->mode_requested = BLE_MODE_CONNECTABLE;
+ if (drv->mode_requested == BLE_MODE_CONNECTABLE &&
+ !drv->restart_adv_on_disconnect) {
+ if (drv->connected) {
+ drv->mode_requested = BLE_MODE_KEEP_CONNECTION;
+ } else {
+ drv->mode_requested = BLE_MODE_OFF;
}
}
@@ -801,10 +804,12 @@ bool ble_issue_command(ble_command_t *command) {
switch (command->cmd_type) {
case BLE_SWITCH_OFF:
+ drv->restart_adv_on_disconnect = false;
drv->mode_requested = BLE_MODE_OFF;
result = true;
break;
case BLE_SWITCH_ON:
+ drv->restart_adv_on_disconnect = true;
memcpy(&drv->adv_cmd, &command->data.adv_start, sizeof(drv->adv_cmd));
if (drv->connected) {
drv->mode_requested = BLE_MODE_KEEP_CONNECTION;
@@ -814,6 +819,7 @@ bool ble_issue_command(ble_command_t *command) {
result = true;
break;
case BLE_PAIRING_MODE:
+ drv->restart_adv_on_disconnect = true;
irq_unlock(key);
result = ble_start_pairing(command);
return result;
@@ -832,6 +838,14 @@ bool ble_issue_command(ble_command_t *command) {
case BLE_UNPAIR:
result = ble_send_unpair(drv);
break;
+ case BLE_KEEP_CONNECTION:
+ drv->restart_adv_on_disconnect = false;
+ if (drv->connected) {
+ drv->mode_requested = BLE_MODE_KEEP_CONNECTION;
+ } else {
+ drv->mode_requested = BLE_MODE_OFF;
+ }
+ break;
default:
break;
}
diff --git a/core/embed/projects/bootloader/wire/wire_iface_ble.c b/core/embed/projects/bootloader/wire/wire_iface_ble.c
index ddb2c96a..3e018bae 100644
--- a/core/embed/projects/bootloader/wire/wire_iface_ble.c
+++ b/core/embed/projects/bootloader/wire/wire_iface_ble.c
@@ -137,7 +137,7 @@ void ble_iface_deinit(void) {
}
ble_command_t cmd = {
- .cmd_type = BLE_SWITCH_OFF,
+ .cmd_type = BLE_KEEP_CONNECTION,
};
ble_issue_command(&cmd);
diff --git a/core/embed/projects/bootloader/workflow/wf_bootloader.c b/core/embed/projects/bootloader/workflow/wf_bootloader.c
index a0745e63..683a355d 100644
--- a/core/embed/projects/bootloader/workflow/wf_bootloader.c
+++ b/core/embed/projects/bootloader/workflow/wf_bootloader.c
@@ -27,12 +27,15 @@
#include <sys/power_manager.h>
#endif
+#ifdef USE_BLE
+#include <io/ble.h>
+#endif
+
#include <io/display.h>
#include <io/display_utils.h>
#include "bootui.h"
#include "rust_ui_bootloader.h"
-#include "wire/wire_iface_usb.h"
#include "workflow.h"
workflow_result_t workflow_menu(const vendor_header* const vhdr,
@@ -60,6 +63,12 @@ workflow_result_t workflow_menu(const vendor_header* const vhdr,
workflow_ifaces_pause(ios);
workflow_ble_pairing_request(vhdr, hdr);
workflow_ifaces_resume(ios);
+ if (ios == NULL) {
+ // in case we were not in connected-mode, stop advertising
+ ble_command_t cmd = {0};
+ cmd.cmd_type = BLE_KEEP_CONNECTION;
+ ble_issue_command(&cmd);
+ }
continue;
}
#endif
Why this scored 25/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.