168f669e613ee4eb8467db7f335181b1cf5b4903
* esp32: fix NimBLE use-after-free crash when disabling bluetooth while connected
Saving certain configs over BLE (e.g. MQTT module config) calls
disableBluetooth() -> NimbleBluetooth::deinit() while the phone is still
connected. deinit() called BLEDevice::deinit(true) straight away, which
deletes the BLEServer before nimble_port_stop(). Stopping the NimBLE
port with a live connection makes the host synthesize unsubscribe events
via ble_gatts_connection_broken() and dispatch them into the freed
BLEServer -> LoadProhibited panic in BLEServer::handleGATTServerEvent
(iterating server->m_notifyChrVec on freed memory).
Backtrace tail:
BLEServer::handleGATTServerEvent (SUBSCRIBE)
<- ble_gatts_subscribe_event <- ble_gatts_connection_broken
<- ble_gap_conn_broken <- ble_gap_rx_disconn_complete
It was reliably preceded by our onRead callback pinning the NimBLE host
task in its up-to-20s polling loop ('BLE onRead: timeout after 19920 ms,
4000 tries'), so the host task couldn't process the teardown until long
after the objects under it were freed.
Fix: drain before demolition in deinit(). Clear
onReadCallbackIsWaitingForData so an in-flight onRead returns
immediately; disconnect the peer and wait (bounded, ~2s) for the host
task to run onDisconnect (which clears nimbleBluetoothConnHandle) so the
unsubscribe/GATT teardown lands on the still-live server; only then
BLEDevice::deinit(true). isDeInit is raised after the drain (onDisconnect
early-returns when set and would otherwise never clear the handle), the
deferred advertising restart is dropped (the stack is going away), and
the now-dangling bleServer pointer is nulled.
deinit() runs on the main task, so waiting on the host task's callbacks
is safe; the waits are bounded regardless.
Upstream ordering bug (delete BLEServer before nimble_port_stop) to be
reported to espressif/arduino-esp32 separately.
* esp32: gate onRead during BLE teardown and use Throttle for drain wait
Addresses review feedback on the NimBLE deinit UAF fix:
- Set a bleDraining flag before disconnecting so onRead bails immediately
instead of arming the up-to-20s wait. Without this, a read that re-arms
after deinit() clears onReadCallbackIsWaitingForData could pin the NimBLE
host task, block onDisconnect from clearing the conn handle, and make the
2s drain expire. onRead now skips the wait when draining, and the wait
loop also observes the flag so an in-flight read is released.
- Use Throttle::isWithinTimespanMs() for the bounded drain wait instead of
a raw millis() subtraction, per coding guidelines.
* esp32: reset BLE teardown guards on re-init
deinit() latches bleDraining (new) and isDeInit (pre-existing) as teardown
guards, but setup() never cleared them. AdminModule's disable-bluetooth admin
command calls deinit() directly without a reboot, and PowerFSM can re-enable
via setBluetoothEnable(true) -> setup() on the same boot (isActive() is false
because deinit() nulls bleServer). Without resetting the flags, the re-initialized
stack has onRead permanently bailing on the drain path (empty reads) and
onDisconnect early-returning without clearing the connection handle.
Clear both flags at the top of setup() so a re-init recovers cleanly.
Overview
This repository contains the official device firmware for Meshtastic, an open-source LoRa mesh networking project designed for long-range, low-power communication without relying on internet or cellular infrastructure. The firmware supports various hardware platforms, including ESP32, nRF52, RP2040/RP2350, and Linux-based devices.
Meshtastic enables text messaging, location sharing, and telemetry over a decentralized mesh network, making it ideal for outdoor adventures, emergency preparedness, and remote operations.
Get Started
- 🔧 Building Instructions - Learn how to compile the firmware from source.
- ⚡ Flashing Instructions - Install or update the firmware on your device.
Join our community and help improve Meshtastic! 🚀
Stats
Languages
C++
70.8%
C
25.8%
Python
2.2%
Shell
0.7%
Batchfile
0.2%
Other
0.1%
