feat(core): set BLE connection parameters differently in bootloader and firmware
What changed, and why it matters
This commit changes how the Bluetooth Low Energy (BLE) connection behaves on Trezor hardware. It adds a way to request a faster BLE connection (used in the bootloader to speed up firmware uploads) and a slower, more power-efficient mode for normal wallet use. The change is a feature, not a fix for a known vulnerability. There is no direct evidence in the commit that it addresses an active security flaw, but changing connection timing parameters can have side effects on wireless reliability and power use.
Treat as a feature commit rather than a security patch. Reviewers should verify that the new `INTERNAL_CMD_SET_SPEED_HIGH`/`LOW` commands cannot be injected by an unauthenticated peer, confirm that the wider default connection interval does not weaken timing assumptions elsewhere (e.g., timeout handling, side-channel mitigations), and ensure the bootloader's high-speed mode is only active during authorized firmware updates.
Security signals we found
New host-to-coprocessor command interface added (INTERNAL_CMD_SET_SPEED_HIGH/LOW)
Connection parameter logic changed from fixed fast interval to conditional high/low speed profiles
Bootloader explicitly opts into high-speed mode, increasing throughput and power consumption
No input validation visible for the new command beyond command dispatch switch
Status flag bitfield reshuffled to expose high_speed state to host
Evidence from the diff
The patch introduces a host-controlled high-speed BLE mode. The STM32 host driver gains a ble_set_high_speed() API and tracks a high_speed flag across suspend/resume. New internal commands (INTERNAL_CMD_SET_SPEED_HIGH/LOW) are sent to the Nordic BLE coprocessor, which then updates connection parameters via bt_conn_le_param_update(). Default peripheral preferred connection parameters (PPCP) are widened from 6-12 to 24-100, and explicit high-speed/suspend profiles are defined. The bootloader calls ble_set_high_speed(true) to favor upload throughput. The firmware side does not appear to call the new API in this commit, so normal firmware operation would use the new wider default (low-speed) parameters.
Changed components
core/embed/io/ble/stm32/ble.ccore/embed/io/ble/stm32/ble_comm_defs.hcore/embed/io/ble/unix/ble.ccore/embed/projects/bootloader/main.cnordic/trezor/trezor-ble/prj.confnordic/trezor/trezor-ble/src/ble/ble_internal.hnordic/trezor/trezor-ble/src/ble/ble_management.cnordic/trezor/trezor-ble/src/ble/connection.cInspect captured patch +77 / −7
diff --git a/core/embed/io/ble/inc/io/ble.h b/core/embed/io/ble/inc/io/ble.h
index 1c55fa48..b925190b 100644
--- a/core/embed/io/ble/inc/io/ble.h
+++ b/core/embed/io/ble/inc/io/ble.h
@@ -87,6 +87,7 @@ typedef struct {
typedef struct {
bool accept_msgs;
bool reboot_on_resume;
+ bool high_speed;
uint8_t peer_count;
ble_mode_t mode_requested;
bt_le_addr_t connected_addr;
@@ -207,3 +208,9 @@ uint32_t ble_read(uint8_t *data, uint16_t max_len);
// When not using static address, the address is random and may not correspond
// to what is actually used for advertising
bool ble_get_mac(bt_le_addr_t *addr);
+
+// Set high speed connection
+//
+// When enabled, the connection parameters will be set to achieve
+// higher data throughput, at the cost of increased power consumption.
+void ble_set_high_speed(bool enable);
diff --git a/core/embed/io/ble/stm32/ble.c b/core/embed/io/ble/stm32/ble.c
index 0d0739b3..d4103756 100644
--- a/core/embed/io/ble/stm32/ble.c
+++ b/core/embed/io/ble/stm32/ble.c
@@ -78,6 +78,7 @@ typedef struct {
ble_adv_start_cmd_data_t adv_cmd;
bt_le_addr_t mac;
bool mac_ready;
+ bool high_speed;
uint8_t bond_count;
bt_le_addr_t bonds[BLE_MAX_BONDS];
@@ -132,6 +133,14 @@ static bool ble_send_advertising_on(ble_driver_t *drv, bool whitelist) {
NULL, NULL) >= 0;
}
+static bool ble_send_speed_request(ble_driver_t *drv, bool high_speed) {
+ (void)drv;
+ uint8_t cmd =
+ high_speed ? INTERNAL_CMD_SET_SPEED_HIGH : INTERNAL_CMD_SET_SPEED_LOW;
+ return nrf_send_msg(NRF_SERVICE_BLE_MANAGER, &cmd, sizeof(cmd), NULL, NULL) >=
+ 0;
+}
+
static bool ble_send_advertising_off(ble_driver_t *drv) {
(void)drv;
uint8_t cmd = INTERNAL_CMD_ADVERTISING_OFF;
@@ -336,6 +345,11 @@ static void ble_process_rx_msg_status(const uint8_t *data, uint32_t len) {
drv->mode_requested = BLE_MODE_OFF;
}
+ // in case connection speed differs from request, send a command
+ if (msg.flags.high_speed != drv->high_speed) {
+ ble_send_speed_request(drv, drv->high_speed);
+ }
+
drv->status_valid = true;
}
@@ -649,6 +663,7 @@ void ble_suspend(ble_wakeup_params_t *wakeup_params) {
wakeup_params->accept_msgs = connected;
wakeup_params->mode_requested = drv->mode_requested;
wakeup_params->peer_count = drv->peer_count;
+ wakeup_params->high_speed = drv->high_speed;
memcpy(&wakeup_params->adv_data, &drv->adv_cmd, sizeof(drv->adv_cmd));
ble_deinit_common(drv);
@@ -686,6 +701,7 @@ bool ble_resume(const ble_wakeup_params_t *wakeup_params) {
irq_key_t key = irq_lock();
drv->peer_count = wakeup_params->peer_count;
+ drv->high_speed = wakeup_params->high_speed;
memcpy(&drv->connected_addr, &wakeup_params->connected_addr,
sizeof(drv->connected_addr));
@@ -1148,6 +1164,17 @@ bool ble_unpair(const bt_le_addr_t *addr) {
return result;
}
+void ble_set_high_speed(bool enable) {
+ ble_driver_t *drv = &g_ble_driver;
+ if (!drv->initialized) {
+ return;
+ }
+
+ irq_key_t key = irq_lock();
+ drv->high_speed = enable;
+ irq_unlock(key);
+}
+
static void on_ble_iface_event_poll(void *context, bool read_awaited,
bool write_awaited) {
UNUSED(context);
diff --git a/core/embed/io/ble/stm32/ble_comm_defs.h b/core/embed/io/ble/stm32/ble_comm_defs.h
index 27816905..aa527ac6 100644
--- a/core/embed/io/ble/stm32/ble_comm_defs.h
+++ b/core/embed/io/ble/stm32/ble_comm_defs.h
@@ -33,7 +33,8 @@ typedef struct {
uint8_t busy_flag;
struct {
bool bonded_connection : 1;
- uint8_t reserved : 7;
+ bool high_speed : 1;
+ uint8_t reserved : 6;
} flags;
uint8_t sd_version_number;
@@ -71,6 +72,8 @@ typedef enum {
INTERNAL_CMD_GET_MAC = 0x09,
INTERNAL_CMD_SET_BUSY = 0x0A,
INTERNAL_CMD_GET_BOND_LIST = 0x0B,
+ INTERNAL_CMD_SET_SPEED_HIGH = 0x0C,
+ INTERNAL_CMD_SET_SPEED_LOW = 0x0D,
} internal_cmd_t;
typedef struct {
diff --git a/core/embed/io/ble/unix/ble.c b/core/embed/io/ble/unix/ble.c
index 298fbb53..bc80f5d4 100644
--- a/core/embed/io/ble/unix/ble.c
+++ b/core/embed/io/ble/unix/ble.c
@@ -38,3 +38,5 @@ void ble_get_advertising_name(char *name, size_t max_len) {
bool ble_unpair(const bt_le_addr_t *addr) { return false; }
uint8_t ble_get_bond_list(bt_le_addr_t *bonds, size_t count) { return 0; }
+
+void ble_set_high_speed(bool enable){};
diff --git a/core/embed/projects/bootloader/main.c b/core/embed/projects/bootloader/main.c
index fa08e244..4674bbc0 100644
--- a/core/embed/projects/bootloader/main.c
+++ b/core/embed/projects/bootloader/main.c
@@ -300,6 +300,8 @@ static void drivers_init(secbool manufacturing_mode,
#ifdef USE_BLE
ble_init();
+ // increase BLE speed for sake of upload speed
+ ble_set_high_speed(true);
#endif
}
diff --git a/nordic/trezor/trezor-ble/prj.conf b/nordic/trezor/trezor-ble/prj.conf
index a14ecf68..eaec03e1 100644
--- a/nordic/trezor/trezor-ble/prj.conf
+++ b/nordic/trezor/trezor-ble/prj.conf
@@ -54,8 +54,8 @@ CONFIG_BT_TINYCRYPT_ECC=y
CONFIG_BT_LL_SW_SPLIT=y
CONFIG_BT_CTLR_TX_PWR_PLUS_4=y
CONFIG_BT_GAP_PERIPHERAL_PREF_PARAMS=y
-CONFIG_BT_PERIPHERAL_PREF_MIN_INT=6
-CONFIG_BT_PERIPHERAL_PREF_MAX_INT=12
+CONFIG_BT_PERIPHERAL_PREF_MIN_INT=24
+CONFIG_BT_PERIPHERAL_PREF_MAX_INT=100
CONFIG_BT_PERIPHERAL_PREF_TIMEOUT=400
CONFIG_BT_CTLR_PHY_2M=y
CONFIG_BT_USER_PHY_UPDATE=y
diff --git a/nordic/trezor/trezor-ble/src/ble/ble_internal.h b/nordic/trezor/trezor-ble/src/ble/ble_internal.h
index 7625907c..6256e8f7 100644
--- a/nordic/trezor/trezor-ble/src/ble/ble_internal.h
+++ b/nordic/trezor/trezor-ble/src/ble/ble_internal.h
@@ -63,7 +63,8 @@ typedef struct {
uint8_t busy_flag;
struct {
bool bonded_connection : 1;
- uint8_t reserved : 7;
+ bool high_speed : 1;
+ uint8_t reserved : 6;
} flags;
uint8_t sd_version_number;
@@ -102,6 +103,8 @@ typedef enum {
INTERNAL_CMD_GET_MAC = 0x09,
INTERNAL_CMD_SET_BUSY = 0x0A,
INTERNAL_CMD_GET_BOND_LIST = 0x0B,
+ INTERNAL_CMD_SET_SPEED_HIGH = 0x0C,
+ INTERNAL_CMD_SET_SPEED_LOW = 0x0D,
} internal_cmd_t;
typedef struct {
@@ -182,6 +185,12 @@ bool connection_is_connected(void);
struct bt_conn *connection_get_current(void);
// Is current connection bonded
bool connection_is_bonded(void);
+// Is current connection high speed
+bool connection_is_high_speed(void);
+// Set connection to high speed
+void connection_set_high_speed(void);
+// Set connection to low speed
+void connection_set_low_speed(void);
// Pairing functions
// Initialization
diff --git a/nordic/trezor/trezor-ble/src/ble/ble_management.c b/nordic/trezor/trezor-ble/src/ble/ble_management.c
index beea4f54..090ad4a3 100644
--- a/nordic/trezor/trezor-ble/src/ble/ble_management.c
+++ b/nordic/trezor/trezor-ble/src/ble/ble_management.c
@@ -61,6 +61,7 @@ void ble_management_send_status_event(void) {
msg.bld_version = 0;
msg.busy_flag = ble_get_busy_flag();
msg.flags.bonded_connection = connection_is_bonded();
+ msg.flags.high_speed = connection_is_high_speed();
msg.flags.reserved = 0;
if (connected) {
@@ -205,6 +206,12 @@ static void process_command(uint8_t *data, uint16_t len) {
case INTERNAL_CMD_GET_BOND_LIST: {
management_send_bonds();
} break;
+ case INTERNAL_CMD_SET_SPEED_HIGH: {
+ connection_set_high_speed();
+ } break;
+ case INTERNAL_CMD_SET_SPEED_LOW: {
+ connection_set_low_speed();
+ } break;
default:
break;
}
diff --git a/nordic/trezor/trezor-ble/src/ble/connection.c b/nordic/trezor/trezor-ble/src/ble/connection.c
index 65f9046d..db70ce98 100644
--- a/nordic/trezor/trezor-ble/src/ble/connection.c
+++ b/nordic/trezor/trezor-ble/src/ble/connection.c
@@ -32,9 +32,14 @@
#define LOG_MODULE_NAME ble_connection
LOG_MODULE_REGISTER(LOG_MODULE_NAME);
+#define PPCP_SUSPEND BT_LE_CONN_PARAM(400, 800, 1, 500)
+#define PPCP_HIGH_SPEED BT_LE_CONN_PARAM(12, 12, 0, 400)
+#define PPCP_LOW_SPEED BT_LE_CONN_PARAM(24, 100, 0, 400)
+
static struct bt_conn *current_conn = NULL;
static struct bt_conn *next_conn = NULL;
static bool bonded_connection = false;
+static bool high_speed_requested = false;
static void show_params(struct bt_conn *conn) {
struct bt_conn_info info;
@@ -67,7 +72,8 @@ void connected(struct bt_conn *conn, uint8_t err) {
show_params(conn);
- const struct bt_le_conn_param *param = BT_LE_CONN_PARAM(6, 12, 0, 400);
+ const struct bt_le_conn_param *param =
+ high_speed_requested ? PPCP_HIGH_SPEED : PPCP_LOW_SPEED;
bt_conn_le_param_update(conn, param);
// Prefer 2M both directions; 0 options = no specific constraints
@@ -171,7 +177,7 @@ void connection_suspend(void) {
struct bt_conn *conn = connection_get_current();
if (conn != NULL) {
- const struct bt_le_conn_param *param = BT_LE_CONN_PARAM(400, 800, 0, 500);
+ const struct bt_le_conn_param *param = PPCP_SUSPEND;
bt_conn_le_param_update(conn, param);
}
}
@@ -180,9 +186,16 @@ void connection_resume(void) {
struct bt_conn *conn = connection_get_current();
if (conn != NULL) {
- const struct bt_le_conn_param *param = BT_LE_CONN_PARAM(6, 12, 0, 400);
+ const struct bt_le_conn_param *param =
+ high_speed_requested ? PPCP_HIGH_SPEED : PPCP_LOW_SPEED;
bt_conn_le_param_update(conn, param);
}
}
bool connection_is_bonded(void) { return bonded_connection; }
+
+bool connection_is_high_speed(void) { return high_speed_requested; }
+
+void connection_set_high_speed(void) { high_speed_requested = true; }
+
+void connection_set_low_speed(void) { high_speed_requested = false; }
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.