fix(core): exit pairing screen when an already bonded device connects
What changed, and why it matters
This update fixes a Bluetooth pairing screen bug in Trezor hardware wallets. Previously, if an already-paired (bonded) host device connected while the wallet was showing a pairing screen, the wallet did not automatically exit that screen. The fix adds a new internal event, 'pairing not needed,' so the user interface can close the pairing screen in that situation. The change is a user-experience and minor reliability fix; the commit itself does not describe a security vulnerability.
Treat as a routine bug fix. Reviewers may want to confirm that emitting BLE_PAIRING_NOT_NEEDED before ble_pairing_end() does not race with other pairing state transitions, and that renumbering CONNECTION_CHANGED from 6 to 7 is backward-compatible with any stored event serialization or external consumers.
Security signals we found
UI state machine fix for Bluetooth pairing flow
New internal event for bonded-device-connects-during-pairing scenario
No explicit security claim in commit message or changelog
No CVE, advisory, or researcher attribution present in supplied materials
Evidence from the diff
The patch introduces a new BLE event type BLE_PAIRING_NOT_NEEDED (enum value 6) in the C HAL and Rust UI layers. The STM32 BLE driver now emits this event when a bonded host connects while the device is in BLE_MODE_PAIRING, immediately before calling ble_pairing_end(). Rust code maps the event, and the pairing UI treats it like PairingCanceled/Disconnected by returning a Cancel message. The previous CONNECTION_CHANGED event is renumbered from 6 to 7. The changelog entries say only ‘Exit pairing screen when already paired host connects.’
Changed components
Trezor Core BLE HAL (core/embed/io/ble)Trezor Rust BLE HAL bindings (core/embed/rust/src/trezorhal/ble)Trezor Rust BLE UI components/events (core/embed/rust/src/ui/component/ble.rs, core/embed/rust/src/ui/event/ble.rs)Eckhart bootloader pairing mode screen (core/embed/rust/src/ui/layout_eckhart/bootloader/pairing_mode.rs)Inspect captured patch +27 / −11
diff --git a/core/.changelog.d/5897.fixed b/core/.changelog.d/5897.fixed
new file mode 100644
index 00000000..f7fe6d34
--- /dev/null
+++ b/core/.changelog.d/5897.fixed
@@ -0,0 +1 @@
+Exit pairing screen when already paired host connects.
diff --git a/core/embed/io/ble/inc/io/ble.h b/core/embed/io/ble/inc/io/ble.h
index 1f981fff..1b18053d 100644
--- a/core/embed/io/ble/inc/io/ble.h
+++ b/core/embed/io/ble/inc/io/ble.h
@@ -106,14 +106,15 @@ typedef struct {
} ble_wakeup_params_t;
typedef enum {
- BLE_NONE = 0, // No event
- BLE_CONNECTED = 1, // Connected to a device
- BLE_DISCONNECTED = 2, // Disconnected from a device
- BLE_PAIRING_REQUEST = 3, // Pairing request received
- BLE_PAIRING_CANCELLED = 4, // Pairing was canceled by host
- BLE_PAIRING_COMPLETED = 5, // Pairing was completed successfully
+ BLE_NONE = 0, // No event
+ BLE_CONNECTED = 1, // Connected to a device
+ BLE_DISCONNECTED = 2, // Disconnected from a device
+ BLE_PAIRING_REQUEST = 3, // Pairing request received
+ BLE_PAIRING_CANCELLED = 4, // Pairing was canceled by host
+ BLE_PAIRING_COMPLETED = 5, // Pairing was completed successfully
+ BLE_PAIRING_NOT_NEEDED = 6, // Pairing is not needed
BLE_CONNECTION_CHANGED =
- 6, // Connection change (e.g. different device connected)
+ 7, // Connection change (e.g. different device connected)
} ble_event_type_t;
typedef struct {
diff --git a/core/embed/io/ble/stm32/ble.c b/core/embed/io/ble/stm32/ble.c
index a752819d..7578096d 100644
--- a/core/embed/io/ble/stm32/ble.c
+++ b/core/embed/io/ble/stm32/ble.c
@@ -338,6 +338,10 @@ static void ble_process_rx_msg_status(const uint8_t *data, uint32_t len) {
if (msg.connected && msg.flags.bonded_connection &&
drv->mode_requested == BLE_MODE_PAIRING) {
// bonded device connected in pairing mode - end pairing
+
+ ble_event_t event = {.type = BLE_PAIRING_NOT_NEEDED};
+ tsqueue_enqueue(&drv->event_queue, (uint8_t *)&event, sizeof(event), NULL);
+
ble_pairing_end(drv);
}
diff --git a/core/embed/projects/prodtest/.changelog.d/5897.fixed b/core/embed/projects/prodtest/.changelog.d/5897.fixed
new file mode 100644
index 00000000..f7fe6d34
--- /dev/null
+++ b/core/embed/projects/prodtest/.changelog.d/5897.fixed
@@ -0,0 +1 @@
+Exit pairing screen when already paired host connects.
diff --git a/core/embed/rust/src/trezorhal/ble/mod.rs b/core/embed/rust/src/trezorhal/ble/mod.rs
index 5ad1f5f6..50f2d18d 100644
--- a/core/embed/rust/src/trezorhal/ble/mod.rs
+++ b/core/embed/rust/src/trezorhal/ble/mod.rs
@@ -42,6 +42,7 @@ pub fn ble_parse_event(event: ffi::ble_event_t) -> BLEEvent {
}
ffi::ble_event_type_t_BLE_PAIRING_CANCELLED => BLEEvent::PairingCanceled,
ffi::ble_event_type_t_BLE_PAIRING_COMPLETED => BLEEvent::PairingCompleted,
+ ffi::ble_event_type_t_BLE_PAIRING_NOT_NEEDED => BLEEvent::PairingNotNeeded,
ffi::ble_event_type_t_BLE_CONNECTION_CHANGED => BLEEvent::ConnectionChanged,
_ => panic!(),
}
diff --git a/core/embed/rust/src/ui/component/ble.rs b/core/embed/rust/src/ui/component/ble.rs
index ade703cc..7ba9ba37 100644
--- a/core/embed/rust/src/ui/component/ble.rs
+++ b/core/embed/rust/src/ui/component/ble.rs
@@ -53,9 +53,12 @@ where
Event::BLE(BLEEvent::PairingCompleted),
BLEHandlerMode::WaitingForPairingCompletion,
) => return Some(BLEHandlerMsg::PairingCompleted),
- (Event::BLE(BLEEvent::PairingCanceled | BLEEvent::Disconnected), _) => {
- return Some(BLEHandlerMsg::Cancelled)
- }
+ (
+ Event::BLE(
+ BLEEvent::PairingCanceled | BLEEvent::Disconnected | BLEEvent::PairingNotNeeded,
+ ),
+ _,
+ ) => return Some(BLEHandlerMsg::Cancelled),
_ => {}
}
self.inner.event(ctx, event).map(BLEHandlerMsg::Content)
diff --git a/core/embed/rust/src/ui/event/ble.rs b/core/embed/rust/src/ui/event/ble.rs
index 16624b62..96e849a7 100644
--- a/core/embed/rust/src/ui/event/ble.rs
+++ b/core/embed/rust/src/ui/event/ble.rs
@@ -8,6 +8,7 @@ pub enum BLEEvent {
ConnectionChanged,
PairingRequest(u32),
PairingCanceled,
+ PairingNotNeeded,
PairingCompleted,
}
@@ -19,7 +20,8 @@ impl BLEEvent {
(3, Some(code)) => Self::PairingRequest(code),
(4, None) => Self::PairingCanceled,
(5, None) => Self::PairingCompleted,
- (6, None) => Self::ConnectionChanged,
+ (6, None) => Self::PairingNotNeeded,
+ (7, None) => Self::ConnectionChanged,
_ => return Err(Error::ValueError(c"Invalid BLE event")),
};
Ok(result)
diff --git a/core/embed/rust/src/ui/layout_eckhart/bootloader/pairing_mode.rs b/core/embed/rust/src/ui/layout_eckhart/bootloader/pairing_mode.rs
index 359ad634..fdb8ab68 100644
--- a/core/embed/rust/src/ui/layout_eckhart/bootloader/pairing_mode.rs
+++ b/core/embed/rust/src/ui/layout_eckhart/bootloader/pairing_mode.rs
@@ -89,6 +89,9 @@ impl Component for PairingModeScreen {
if let Event::BLE(BLEEvent::Disconnected) = event {
return Some(PairingMsg::Cancel);
}
+ if let Event::BLE(BLEEvent::PairingNotNeeded) = event {
+ return Some(PairingMsg::Cancel);
+ }
None
}
Why this scored 35/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.