feat(core): introduce enabling/disabling of BLE on driver level
What changed, and why it matters
This commit adds a new software switch that lets the device turn its Bluetooth Low Energy (BLE) radio on or off from the driver level. It is a feature addition, not a fix for an existing bug. The change introduces guards so that when BLE is disabled, the driver refuses to process incoming BLE data, send data, enter pairing, or switch the radio on. There is no evidence in the commit that this is a security patch or that it addresses a known vulnerability.
Treat as a routine feature commit. Review whether the new `set_enabled` MicroPython binding is reachable only by authorized code, since an attacker who can already run Python code on the device could use it to disable BLE. No immediate security response is indicated by the commit itself.
Security signals we found
New driver-level kill switch for BLE functionality
Multiple operations gated on the enabled flag
Enabled state persisted across device suspend/resume
No changelog entry (marked [no changelog])
No CVE, advisory, or security-related commit message
Evidence from the diff
The patch extends the Trezor firmware BLE driver with an enabled flag in ble_wakeup_params_t and the internal ble_driver_t. New ble_set_enabled() and ble_get_enabled() functions are exposed through the C driver, Unix stub, syscall table, Rust FFI bindings, and MicroPython trezorble module. The flag is initialized to true in ble_init() and preserved across suspend/resume. When enabled is false, ble_process_data(), ble_can_write(), ble_write(), ble_switch_on(), and ble_enter_pairing_mode() return early or fail. ble_set_enabled(false) additionally calls ble_switch_off().
Changed components
core/embed/io/ble/stm32/ble.ccore/embed/io/ble/unix/ble.ccore/embed/io/ble/inc/io/ble.hcore/embed/sys/syscall/stm32/syscall_dispatch.ccore/embed/sys/syscall/stm32/syscall_stubs.ccore/embed/sys/syscall/inc/sys/syscall_numbers.hcore/embed/rust/src/trezorhal/ble/mod.rscore/embed/rust/src/trezorhal/ble/micropython.rscore/embed/rust/build.rscore/embed/rust/librust_qstr.hcore/mocks/generated/trezorble.pyiInspect captured patch +124 / −9
diff --git a/core/embed/io/ble/inc/io/ble.h b/core/embed/io/ble/inc/io/ble.h
index d16932d9c..af4c43be9 100644
--- a/core/embed/io/ble/inc/io/ble.h
+++ b/core/embed/io/ble/inc/io/ble.h
@@ -91,6 +91,7 @@ typedef struct {
bool next_adv_with_disconnect;
uint8_t name[BLE_ADV_NAME_LEN]; /**< Advertising name */
bool static_mac; /**< Use static MAC address */
+ bool enabled; /**< BLE functionality enabled */
} ble_wakeup_params_t;
/**
@@ -379,3 +380,17 @@ void ble_set_tx_power(ble_tx_power_level_t level);
* @param len Length of data
*/
void ble_notify(const uint8_t *data, size_t len);
+
+/**
+ * @brief Set BLE enabled state
+ *
+ * @param enabled: true to enable, false to disable
+ */
+void ble_set_enabled(bool enabled);
+
+/**
+ * @brief Get BLE enabled state
+ *
+ * @return true if enabled, false otherwise
+ */
+bool ble_get_enabled(void);
diff --git a/core/embed/io/ble/stm32/ble.c b/core/embed/io/ble/stm32/ble.c
index 3df7d3265..fe3518abd 100644
--- a/core/embed/io/ble/stm32/ble.c
+++ b/core/embed/io/ble/stm32/ble.c
@@ -58,6 +58,7 @@ typedef struct {
bt_le_addr_t connected_addr;
uint8_t peer_count;
bool initialized;
+ bool enabled;
bool status_valid;
bool accept_msgs;
bool reboot_on_resume;
@@ -517,11 +518,7 @@ static bool ble_connected_add_match(ble_driver_t *drv, const uint8_t *addr) {
static void ble_process_data(const uint8_t *data, uint32_t len) {
ble_driver_t *drv = &g_ble_driver;
- if (!drv->initialized) {
- return;
- }
-
- if (!drv->accept_msgs) {
+ if (!drv->initialized || !drv->enabled || !drv->accept_msgs) {
return;
}
@@ -663,6 +660,7 @@ bool ble_init(void) {
}
drv->power_level = BLE_TX_POWER_PLUS_4_DBM;
+ drv->enabled = true;
drv->initialized = true;
return true;
@@ -717,6 +715,7 @@ void ble_suspend(ble_wakeup_params_t *wakeup_params) {
wakeup_params->next_adv_with_disconnect = drv->next_adv_with_disconnect;
wakeup_params->restart_adv_on_disconnect = drv->restart_adv_on_disconnect;
wakeup_params->static_mac = drv->static_mac;
+ wakeup_params->enabled = drv->enabled;
memcpy(wakeup_params->name, drv->adv_name, sizeof(drv->adv_name));
ble_deinit_common(drv);
@@ -755,6 +754,7 @@ bool ble_resume(const ble_wakeup_params_t *wakeup_params) {
drv->peer_count = wakeup_params->peer_count;
drv->high_speed = wakeup_params->high_speed;
drv->static_mac = wakeup_params->static_mac;
+ drv->enabled = wakeup_params->enabled;
memcpy(&drv->connected_addr, &wakeup_params->connected_addr,
sizeof(drv->connected_addr));
@@ -826,7 +826,7 @@ bool ble_can_write(void) {
irq_key_t key = irq_lock();
- if (!drv->connected || !drv->accept_msgs) {
+ if (!drv->connected || !drv->accept_msgs || !drv->enabled) {
irq_unlock(key);
return false;
}
@@ -847,7 +847,7 @@ bool ble_write(const uint8_t *data, uint16_t len) {
irq_key_t key = irq_lock();
- if (!drv->connected || !drv->accept_msgs) {
+ if (!drv->connected || !drv->accept_msgs || !drv->enabled) {
irq_unlock(key);
return false;
}
@@ -940,7 +940,7 @@ bool ble_switch_off(void) {
bool ble_switch_on(void) {
ble_driver_t *drv = &g_ble_driver;
- if (!drv->initialized) {
+ if (!drv->initialized || !drv->enabled) {
return false;
}
@@ -995,7 +995,7 @@ bool ble_enter_pairing_mode(const uint8_t *name, size_t name_len) {
ble_driver_t *drv = &g_ble_driver;
- if (!drv->initialized) {
+ if (!drv->initialized || !drv->enabled) {
return false;
}
@@ -1345,6 +1345,28 @@ void ble_notify(const uint8_t *data, size_t len) {
nrf_send_msg(NRF_SERVICE_BLE_MANAGER, cmd, MIN(32, len + 1), NULL, NULL);
}
+void ble_set_enabled(bool enabled) {
+ ble_driver_t *drv = &g_ble_driver;
+ if (!drv->initialized) {
+ return;
+ }
+
+ if (!enabled) {
+ ble_switch_off();
+ return;
+ }
+
+ drv->enabled = enabled;
+}
+
+bool ble_get_enabled(void) {
+ ble_driver_t *drv = &g_ble_driver;
+ if (!drv->initialized) {
+ return false;
+ }
+ return drv->enabled;
+}
+
static void on_ble_iface_event_poll(void *context, bool read_awaited,
bool write_awaited) {
UNUSED(context);
diff --git a/core/embed/io/ble/unix/ble.c b/core/embed/io/ble/unix/ble.c
index 69acdebe0..e9045c468 100644
--- a/core/embed/io/ble/unix/ble.c
+++ b/core/embed/io/ble/unix/ble.c
@@ -58,3 +58,7 @@ uint8_t ble_get_bond_list(bt_le_addr_t *bonds, size_t count) { return 0; }
void ble_set_high_speed(bool enable){};
void ble_notify(const uint8_t *data, size_t len){};
+
+void ble_set_enabled(bool enabled) {}
+
+bool ble_get_enabled(void) { return false; }
diff --git a/core/embed/rust/build.rs b/core/embed/rust/build.rs
index 2dd9de274..2d26f667c 100644
--- a/core/embed/rust/build.rs
+++ b/core/embed/rust/build.rs
@@ -438,6 +438,8 @@ fn generate_trezorhal_bindings() {
.allowlist_function("ble_unpair")
.allowlist_function("ble_get_bond_list")
.allowlist_function("ble_set_high_speed")
+ .allowlist_function("ble_set_enabled")
+ .allowlist_function("ble_get_enabled")
.allowlist_type("ble_command_t")
.allowlist_type("ble_state_t")
.allowlist_type("ble_event_t")
diff --git a/core/embed/rust/librust_qstr.h b/core/embed/rust/librust_qstr.h
index 06abe5556..0b730d7a1 100644
--- a/core/embed/rust/librust_qstr.h
+++ b/core/embed/rust/librust_qstr.h
@@ -349,6 +349,7 @@ static void _librust_qstrs(void) {
MP_QSTR_flow_get_pubkey;
MP_QSTR_get;
MP_QSTR_get_bonds;
+ MP_QSTR_get_enabled;
MP_QSTR_get_language;
MP_QSTR_get_transition_out;
MP_QSTR_haptic_feedback__disable;
@@ -796,6 +797,7 @@ static void _librust_qstrs(void) {
MP_QSTR_send__transaction_signed;
MP_QSTR_send__you_are_contributing;
MP_QSTR_set_brightness;
+ MP_QSTR_set_enabled;
MP_QSTR_set_high_speed;
MP_QSTR_set_name;
MP_QSTR_setting__adjust;
diff --git a/core/embed/rust/src/trezorhal/ble/micropython.rs b/core/embed/rust/src/trezorhal/ble/micropython.rs
index 452beff35..90bbf563c 100644
--- a/core/embed/rust/src/trezorhal/ble/micropython.rs
+++ b/core/embed/rust/src/trezorhal/ble/micropython.rs
@@ -209,6 +209,21 @@ extern "C" fn py_get_bonds() -> Obj {
unsafe { util::try_or_raise(block) }
}
+extern "C" fn py_set_enabled(enable: Obj) -> Obj {
+ let block = || {
+ let enable: bool = enable.try_into()?;
+
+ set_enabled(enable);
+
+ Ok(Obj::const_none())
+ };
+ unsafe { util::try_or_raise(block) }
+}
+
+extern "C" fn py_get_enabled() -> Obj {
+ get_enabled().into()
+}
+
extern "C" fn py_iface_num(_self: Obj) -> Obj {
Obj::small_int(unwrap!(ffi::syshandle_t_SYSHANDLE_BLE_IFACE_0.try_into()))
}
@@ -424,4 +439,17 @@ pub static mp_module_trezorble: Module = obj_module! {
/// Raises exception if BLE driver reports an error.
/// """
Qstr::MP_QSTR_reject_pairing => obj_fn_0!(py_reject_pairing).as_obj(),
+
+ /// def set_enabled(bool):
+ /// """
+ /// Enable/Disable BLE.
+ /// """
+ Qstr::MP_QSTR_set_enabled => obj_fn_1!(py_set_enabled).as_obj(),
+
+ /// def get_enabled() -> bool:
+ /// """
+ /// True if BLE is enabled.
+ /// """
+ Qstr::MP_QSTR_get_enabled => obj_fn_0!(py_get_enabled).as_obj(),
+
};
diff --git a/core/embed/rust/src/trezorhal/ble/mod.rs b/core/embed/rust/src/trezorhal/ble/mod.rs
index 3c73c5771..63d0eb62a 100644
--- a/core/embed/rust/src/trezorhal/ble/mod.rs
+++ b/core/embed/rust/src/trezorhal/ble/mod.rs
@@ -99,6 +99,14 @@ pub fn switch_off() -> Result<(), Error> {
res_to_result(res)
}
+pub fn set_enabled(enabled: bool) {
+ unsafe { ffi::ble_set_enabled(enabled) };
+}
+
+pub fn get_enabled() -> bool {
+ unsafe { ffi::ble_get_enabled() }
+}
+
pub fn allow_pairing(code: u32) -> Result<(), Error> {
let mut tmp_code = code;
let mut pairing_code: [u8; PAIRING_CODE_LEN] = [0; PAIRING_CODE_LEN];
diff --git a/core/embed/sys/syscall/inc/sys/syscall_numbers.h b/core/embed/sys/syscall/inc/sys/syscall_numbers.h
index e1c2e7f0f..d19b44c63 100644
--- a/core/embed/sys/syscall/inc/sys/syscall_numbers.h
+++ b/core/embed/sys/syscall/inc/sys/syscall_numbers.h
@@ -137,6 +137,8 @@ typedef enum {
SYSCALL_BLE_UNPAIR,
SYSCALL_BLE_GET_BOND_LIST,
SYSCALL_BLE_SET_HIGH_SPEED,
+ SYSCALL_BLE_SET_ENABLED,
+ SYSCALL_BLE_GET_ENABLED,
SYSCALL_NRF_UPDATE_REQUIRED,
SYSCALL_NRF_UPDATE,
diff --git a/core/embed/sys/syscall/stm32/syscall_dispatch.c b/core/embed/sys/syscall/stm32/syscall_dispatch.c
index d479a1ef2..3b845c92b 100644
--- a/core/embed/sys/syscall/stm32/syscall_dispatch.c
+++ b/core/embed/sys/syscall/stm32/syscall_dispatch.c
@@ -682,6 +682,16 @@ __attribute((no_stack_protector)) void syscall_handler(uint32_t *args,
bool enable = args[0];
ble_set_high_speed(enable);
} break;
+
+ case SYSCALL_BLE_SET_ENABLED: {
+ bool enabled = (args[0] != 0);
+ ble_set_enabled(enabled);
+ } break;
+
+ case SYSCALL_BLE_GET_ENABLED: {
+ args[0] = ble_get_enabled();
+ } break;
+
#endif
#ifdef USE_NRF
diff --git a/core/embed/sys/syscall/stm32/syscall_stubs.c b/core/embed/sys/syscall/stm32/syscall_stubs.c
index 2f78d6604..fac5e44a3 100644
--- a/core/embed/sys/syscall/stm32/syscall_stubs.c
+++ b/core/embed/sys/syscall/stm32/syscall_stubs.c
@@ -660,6 +660,14 @@ void ble_set_high_speed(bool enable) {
syscall_invoke1((uint32_t)enable, SYSCALL_BLE_SET_HIGH_SPEED);
}
+void ble_set_enabled(bool enabled) {
+ syscall_invoke1((uint32_t)enabled, SYSCALL_BLE_SET_ENABLED);
+}
+
+bool ble_get_enabled(void) {
+ return (bool)syscall_invoke0(SYSCALL_BLE_GET_ENABLED);
+}
+
#endif
#ifdef USE_NRF
diff --git a/core/mocks/generated/trezorble.pyi b/core/mocks/generated/trezorble.pyi
index a68608e8e..bd483a4f1 100644
--- a/core/mocks/generated/trezorble.pyi
+++ b/core/mocks/generated/trezorble.pyi
@@ -171,3 +171,17 @@ def reject_pairing():
Reject BLE pairing request.
Raises exception if BLE driver reports an error.
"""
+
+
+# rust/src/trezorhal/ble/micropython.rs
+def set_enabled(bool):
+ """
+ Enable/Disable BLE.
+ """
+
+
+# rust/src/trezorhal/ble/micropython.rs
+def get_enabled() -> bool:
+ """
+ True if BLE is enabled.
+ """
Why this scored 23/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.