From eeb7219b9e61d7beccd9983fc228a640f5f29034 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Thu, 20 Aug 2026 22:07:38 -0400 Subject: [PATCH 1/7] Fix BLE serial RX buffer size overstated fourfold `_incoming` is `uint32_t[64]`, so `sizeof(_incoming)` is already 256 bytes. Multiplying by `sizeof(uint32_t)` told the ring buffer it had 1024, letting it write past the array. Co-Authored-By: Claude Opus 5 (1M context) --- supervisor/shared/bluetooth/serial.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/supervisor/shared/bluetooth/serial.c b/supervisor/shared/bluetooth/serial.c index 86ff1738a2a..f35e640e516 100644 --- a/supervisor/shared/bluetooth/serial.c +++ b/supervisor/shared/bluetooth/serial.c @@ -133,7 +133,7 @@ void supervisor_start_bluetooth_serial(void) { _common_hal_bleio_characteristic_buffer_construct(&_rx_buffer, &supervisor_ble_circuitpython_rx_characteristic, 0.1f, - (uint8_t *)_incoming, sizeof(_incoming) * sizeof(uint32_t), + (uint8_t *)_incoming, sizeof(_incoming), &rx_static_handler_entry, true /* watch for interrupt character */); From 29d65a2c443208e8ba843f7a89e514acd88f31f7 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Fri, 21 Aug 2026 16:22:10 -0400 Subject: [PATCH 2/7] Fix zero-iteration disconnect wait in bleio_adapter_reset() any_connected was initialized to false, so the wait loop never ran and the stack was torn down before the disconnect went out. The central then saw a supervision timeout instead of a clean disconnect. Same copied bug in nordic, espressif, and silabs. Co-Authored-By: Claude Fable 5 --- ports/espressif/common-hal/_bleio/Adapter.c | 6 +++--- ports/nordic/common-hal/_bleio/Adapter.c | 6 +++--- ports/silabs/common-hal/_bleio/Adapter.c | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ports/espressif/common-hal/_bleio/Adapter.c b/ports/espressif/common-hal/_bleio/Adapter.c index 4feb6b2b96b..61abdb219be 100644 --- a/ports/espressif/common-hal/_bleio/Adapter.c +++ b/ports/espressif/common-hal/_bleio/Adapter.c @@ -837,13 +837,13 @@ void bleio_adapter_reset(bleio_adapter_obj_t *adapter) { // Wait up to 125 ms (128 ticks) for disconnect to complete. This should be // greater than most connection intervals. - bool any_connected = false; + bool any_connected; uint64_t start_ticks = supervisor_ticks_ms64(); - while (any_connected && supervisor_ticks_ms64() - start_ticks < 128) { + do { any_connected = false; for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) { bleio_connection_internal_t *connection = &bleio_connections[i]; any_connected |= connection->conn_handle != BLEIO_HANDLE_INVALID; } - } + } while (any_connected && supervisor_ticks_ms64() - start_ticks < 128); } diff --git a/ports/nordic/common-hal/_bleio/Adapter.c b/ports/nordic/common-hal/_bleio/Adapter.c index 2ca9df89710..a8d6b5e645b 100644 --- a/ports/nordic/common-hal/_bleio/Adapter.c +++ b/ports/nordic/common-hal/_bleio/Adapter.c @@ -998,13 +998,13 @@ void bleio_adapter_reset(bleio_adapter_obj_t *adapter) { // Wait up to 125 ms (128 ticks) for disconnect to complete. This should be // greater than most connection intervals. - bool any_connected = false; + bool any_connected; uint64_t start_ticks = supervisor_ticks_ms64(); - while (any_connected && supervisor_ticks_ms64() - start_ticks < 128) { + do { any_connected = false; for (size_t i = 0; i < BLEIO_TOTAL_CONNECTION_COUNT; i++) { bleio_connection_internal_t *connection = &bleio_connections[i]; any_connected |= connection->conn_handle != BLE_CONN_HANDLE_INVALID; } - } + } while (any_connected && supervisor_ticks_ms64() - start_ticks < 128); } diff --git a/ports/silabs/common-hal/_bleio/Adapter.c b/ports/silabs/common-hal/_bleio/Adapter.c index 14b1dac8207..f29e9d54b15 100644 --- a/ports/silabs/common-hal/_bleio/Adapter.c +++ b/ports/silabs/common-hal/_bleio/Adapter.c @@ -640,11 +640,11 @@ void bleio_adapter_reset(bleio_adapter_obj_t *adapter) { // Wait up to 125 ms (128 ticks) for disconnect to complete. This should be // greater than most connection intervals. start_ticks = supervisor_ticks_ms64(); - while (any_connected && supervisor_ticks_ms64() - start_ticks < 128) { + do { any_connected = false; for (conn_index = 0; conn_index < BLEIO_TOTAL_CONNECTION_COUNT; conn_index++) { connection = &bleio_connections[conn_index]; any_connected |= connection->conn_handle != BLEIO_HANDLE_INVALID; } - } + } while (any_connected && supervisor_ticks_ms64() - start_ticks < 128); } From 411df07e46d31f3aef7457034973e1486bdadcbc Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Fri, 21 Aug 2026 16:22:37 -0400 Subject: [PATCH 3/7] Fix heap handler removal stopping after the first removal ble_drv_remove_event_handler() clears the removed entry's next pointer, so iterating via it->next after removal ended the walk at the first heap handler, leaving the rest pointing into a heap about to be freed. Capture next first. Also remove entries that themselves live on the heap, not just those with a heap param. Same copied bug in nordic ble_drv.c and espressif ble_events.c. Co-Authored-By: Claude Fable 5 --- ports/espressif/common-hal/_bleio/ble_events.c | 9 ++++++--- ports/nordic/bluetooth/ble_drv.c | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/ports/espressif/common-hal/_bleio/ble_events.c b/ports/espressif/common-hal/_bleio/ble_events.c index b57362fe007..d2474d1e00f 100644 --- a/ports/espressif/common-hal/_bleio/ble_events.c +++ b/ports/espressif/common-hal/_bleio/ble_events.c @@ -28,11 +28,14 @@ void ble_event_reset(void) { void ble_event_remove_heap_handlers(void) { ble_event_handler_entry_t *it = MP_STATE_VM(ble_event_handler_entries); while (it != NULL) { - // If the param is on the heap, then delete the handler. - if (gc_ptr_on_heap(it->param)) { + // Save it->next before removing, because removing clears it. + ble_event_handler_entry_t *next = it->next; + // Remove the handler if the entry or its param is on the heap, which is + // about to go away. + if (gc_ptr_on_heap(it) || gc_ptr_on_heap(it->param)) { ble_event_remove_handler(it->func, it->param); } - it = it->next; + it = next; } } diff --git a/ports/nordic/bluetooth/ble_drv.c b/ports/nordic/bluetooth/ble_drv.c index 7085aa477b5..c2122c8845e 100644 --- a/ports/nordic/bluetooth/ble_drv.c +++ b/ports/nordic/bluetooth/ble_drv.c @@ -141,11 +141,14 @@ void ble_drv_reset(void) { void ble_drv_remove_heap_handlers(void) { ble_drv_evt_handler_entry_t *it = MP_STATE_VM(ble_drv_evt_handler_entries); while (it != NULL) { - // If the param is on the heap, then delete the handler. - if (gc_ptr_on_heap(it->param)) { + // Save it->next before removing, because removing clears it. + ble_drv_evt_handler_entry_t *next = it->next; + // Remove the handler if the entry or its param is on the heap, which is + // about to go away. + if (gc_ptr_on_heap(it) || gc_ptr_on_heap(it->param)) { ble_drv_remove_event_handler(it->func, it->param); } - it = it->next; + it = next; } } From 4b2f077623ea80523c2333002937a2db037a3dfb Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Fri, 21 Aug 2026 16:23:15 -0400 Subject: [PATCH 4/7] Fix common_hal_bleio_packet_buffer_deinited() never returning true common_hal_bleio_packet_buffer_deinit() left self->characteristic set, so deinited() -- which tests it against NULL -- never reported the buffer as deinited, and guards like the ones in supervisor/shared/bluetooth/serial.c never took effect. Same copied bug in nordic, silabs, and ble_hci. Co-Authored-By: Claude Fable 5 --- devices/ble_hci/common-hal/_bleio/PacketBuffer.c | 2 ++ ports/nordic/common-hal/_bleio/PacketBuffer.c | 2 ++ ports/silabs/common-hal/_bleio/PacketBuffer.c | 2 ++ 3 files changed, 6 insertions(+) diff --git a/devices/ble_hci/common-hal/_bleio/PacketBuffer.c b/devices/ble_hci/common-hal/_bleio/PacketBuffer.c index 771a1509f39..f29f4dd88a7 100644 --- a/devices/ble_hci/common-hal/_bleio/PacketBuffer.c +++ b/devices/ble_hci/common-hal/_bleio/PacketBuffer.c @@ -245,6 +245,8 @@ void common_hal_bleio_packet_buffer_deinit(bleio_packet_buffer_obj_t *self) { if (!common_hal_bleio_packet_buffer_deinited(self)) { bleio_characteristic_clear_observer(self->characteristic); ringbuf_deinit(&self->ringbuf); + // Mark as deinited, so common_hal_bleio_packet_buffer_deinited() reports it. + self->characteristic = NULL; } } diff --git a/ports/nordic/common-hal/_bleio/PacketBuffer.c b/ports/nordic/common-hal/_bleio/PacketBuffer.c index 6b3e86e3b7a..2d6f5a9e948 100644 --- a/ports/nordic/common-hal/_bleio/PacketBuffer.c +++ b/ports/nordic/common-hal/_bleio/PacketBuffer.c @@ -483,6 +483,8 @@ void common_hal_bleio_packet_buffer_deinit(bleio_packet_buffer_obj_t *self) { if (!common_hal_bleio_packet_buffer_deinited(self)) { ble_drv_remove_event_handler(packet_buffer_on_ble_client_evt, self); ringbuf_deinit(&self->ringbuf); + // Mark as deinited, so common_hal_bleio_packet_buffer_deinited() reports it. + self->characteristic = NULL; } } diff --git a/ports/silabs/common-hal/_bleio/PacketBuffer.c b/ports/silabs/common-hal/_bleio/PacketBuffer.c index 881cf1622eb..291523e0a6d 100644 --- a/ports/silabs/common-hal/_bleio/PacketBuffer.c +++ b/ports/silabs/common-hal/_bleio/PacketBuffer.c @@ -387,6 +387,8 @@ bool common_hal_bleio_packet_buffer_deinited(bleio_packet_buffer_obj_t *self) { void common_hal_bleio_packet_buffer_deinit(bleio_packet_buffer_obj_t *self) { if (!common_hal_bleio_packet_buffer_deinited(self)) { ringbuf_deinit(&self->ringbuf); + // Mark as deinited, so common_hal_bleio_packet_buffer_deinited() reports it. + self->characteristic = NULL; } } From 17680833b7716e8f177176deaea11a0530f8e888 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Fri, 21 Aug 2026 16:23:40 -0400 Subject: [PATCH 5/7] Fix nordic packet_buffer_deinit() removing the wrong event handler Registration chooses packet_buffer_on_ble_client_evt or packet_buffer_on_ble_server_evt by self->client, but deinit always removed the client handler, leaving a server packet buffer's handler registered and pointing at a freed buffer. Co-Authored-By: Claude Fable 5 --- ports/nordic/common-hal/_bleio/PacketBuffer.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ports/nordic/common-hal/_bleio/PacketBuffer.c b/ports/nordic/common-hal/_bleio/PacketBuffer.c index 2d6f5a9e948..5be9896b706 100644 --- a/ports/nordic/common-hal/_bleio/PacketBuffer.c +++ b/ports/nordic/common-hal/_bleio/PacketBuffer.c @@ -481,7 +481,11 @@ bool common_hal_bleio_packet_buffer_deinited(bleio_packet_buffer_obj_t *self) { void common_hal_bleio_packet_buffer_deinit(bleio_packet_buffer_obj_t *self) { if (!common_hal_bleio_packet_buffer_deinited(self)) { - ble_drv_remove_event_handler(packet_buffer_on_ble_client_evt, self); + if (self->client) { + ble_drv_remove_event_handler(packet_buffer_on_ble_client_evt, self); + } else { + ble_drv_remove_event_handler(packet_buffer_on_ble_server_evt, self); + } ringbuf_deinit(&self->ringbuf); // Mark as deinited, so common_hal_bleio_packet_buffer_deinited() reports it. self->characteristic = NULL; From a234a6570927e68d4a683e39f5035411509067d9 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Fri, 21 Aug 2026 16:24:28 -0400 Subject: [PATCH 6/7] Fix resolvable private address rotating every second For unlimited advertising (timeout 0), private_addr_cycle_s = timeout + 1 rotated the address every second -- too fast for a central to resolve an address and connect to it before it changed. Use 0, which selects the SoftDevice default cycle of 15 minutes. Co-Authored-By: Claude Fable 5 --- ports/nordic/common-hal/_bleio/Adapter.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ports/nordic/common-hal/_bleio/Adapter.c b/ports/nordic/common-hal/_bleio/Adapter.c index a8d6b5e645b..129ad01d068 100644 --- a/ports/nordic/common-hal/_bleio/Adapter.c +++ b/ports/nordic/common-hal/_bleio/Adapter.c @@ -794,7 +794,13 @@ uint32_t _common_hal_bleio_adapter_start_advertising(bleio_adapter_obj_t *self, // advertising. This prevents a potential race condition where we // fire off a beacon with the same advertising data but a new MAC // address just as we tear down the connection. - .private_addr_cycle_s = timeout + 1, + // + // For unlimited advertising, timeout + 1 would rotate the address + // every second, too fast for a central to resolve it and connect. + // Zero selects the SoftDevice default cycle of 15 minutes + // (BLE_GAP_DEFAULT_PRIVATE_ADDR_CYCLE_INTERVAL_S). + .private_addr_cycle_s = + timeout == BLE_GAP_ADV_TIMEOUT_GENERAL_UNLIMITED ? 0 : timeout + 1, .p_device_irk = NULL, }; err_code = sd_ble_gap_privacy_set(&privacy); From d85810534b6e1eeb994fecb3e65b66366f353dc9 Mon Sep 17 00:00:00 2001 From: Dan Halbert Date: Fri, 21 Aug 2026 16:24:59 -0400 Subject: [PATCH 7/7] Clear stale bond state when a connection slot is reused On BLE_GAP_EVT_CONNECTED, a recycled slot kept the previous peer's bonding keys, ediv, and pending bond-save flags. The SoftDevice fills in only the keys the new peer distributes, so the leftovers could mix one peer's IRK with another's LTK in a stored bond, or save this connection's bond data under the previous peer's ediv. Co-Authored-By: Claude Fable 5 --- ports/nordic/common-hal/_bleio/Adapter.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ports/nordic/common-hal/_bleio/Adapter.c b/ports/nordic/common-hal/_bleio/Adapter.c index 129ad01d068..05edd832a8b 100644 --- a/ports/nordic/common-hal/_bleio/Adapter.c +++ b/ports/nordic/common-hal/_bleio/Adapter.c @@ -248,6 +248,15 @@ static bool adapter_on_ble_evt(ble_evt_t *ble_evt, void *self_in) { connection->connection_obj = mp_const_none; connection->pair_status = PAIR_NOT_PAIRED; connection->mtu = 0; + // Clear leftover bond state; connection slots are recycled. The + // SoftDevice fills in only the keys the new peer distributes, so a + // stale keyset could mix the previous peer's keys into this peer's + // stored bond, and a stale ediv or pending-save flag could file + // this connection's bond data under the previous peer's key. + connection->ediv = EDIV_INVALID; + connection->do_bond_cccds = false; + connection->do_bond_keys = false; + bonding_clear_keys(&connection->bonding_keys); ble_drv_add_event_handler_entry(&connection->handler_entry, connection_on_ble_evt, connection); self->connection_objs = NULL;