From d4de61362b837efd8dd734b1ba423b93aa01c144 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 12 Aug 2026 19:14:18 -0500 Subject: [PATCH] fix(mesh): plug pooled-object and driver leaks in core paths (#11450) - MeshService::sendQueueStatusToPhone: release the pooled QueueStatus when the toPhone queue enqueue fails, matching what sendMqttMessageToClientProxy and sendClientNotification already do. The full-queue guard makes this failure rare, but the check/enqueue sequence is not atomic and this path is reachable concurrently from the main loop and the nRF52 BLE write callback; each failure permanently lost one of the four pool slots, and after four losses the phone never receives QueueStatus again until reboot. - MessageStore::storeTextInPool: bail out when the boot-time pool allocation failed instead of memcpy'ing through a null pointer. The read side (getTextFromPool) already guards and maps offset 0 to an empty string. - RF95Interface: hold the RadioLibRF95 driver in a unique_ptr. It is constructed in init(), and when init() subsequently fails (e.g. chip probe NOT_FOUND) initLoRa() destroys the interface, leaking the driver; every sibling interface holds its driver by value so nothing else needed a destructor here. --- src/MessageStore.cpp | 4 ++++ src/mesh/MeshService.cpp | 2 ++ src/mesh/RF95Interface.cpp | 3 ++- src/mesh/RF95Interface.h | 8 +++++++- 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/MessageStore.cpp b/src/MessageStore.cpp index 4030bdd28..913a40c45 100644 --- a/src/MessageStore.cpp +++ b/src/MessageStore.cpp @@ -42,6 +42,10 @@ static inline void resetMessagePool() // If not enough space remains, wrap around (ring buffer style) static inline uint16_t storeTextInPool(const char *src, size_t len) { + // Pool allocation can fail at boot; getTextFromPool() already maps offset 0 to "" in that case + if (!g_messagePool) + return 0; + if (len >= MAX_MESSAGE_SIZE) len = MAX_MESSAGE_SIZE - 1; diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 162d15353..0d450804c 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -353,6 +353,8 @@ ErrorCode MeshService::sendQueueStatusToPhone(const meshtastic_QueueStatus &qs, lastQueueStatus = *copied; res = toPhoneQueueStatusQueue.enqueue(copied, 0); + if (!res) + releaseQueueStatusToPool(copied); fromNum++; return res ? ERRNO_OK : ERRNO_UNKNOWN; diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp index 6968b5654..909d47e23 100644 --- a/src/mesh/RF95Interface.cpp +++ b/src/mesh/RF95Interface.cpp @@ -129,7 +129,8 @@ bool RF95Interface::init() limitPower(RF95_MAX_POWER); - iface = lora = new RadioLibRF95(&module); + lora.reset(new RadioLibRF95(&module)); + iface = lora.get(); #ifdef RF95_TCXO pinMode(RF95_TCXO, OUTPUT); diff --git a/src/mesh/RF95Interface.h b/src/mesh/RF95Interface.h index e01dfe376..2cd483572 100644 --- a/src/mesh/RF95Interface.h +++ b/src/mesh/RF95Interface.h @@ -4,12 +4,18 @@ #include "RadioLibInterface.h" #include "RadioLibRF95.h" +#include + /** * Our new not radiohead adapter for RF95 style radios */ class RF95Interface : public RadioLibInterface { - RadioLibRF95 *lora = NULL; // Either a RFM95 or RFM96 depending on what was stuffed on this board + // Either a RFM95 or RFM96 depending on what was stuffed on this board. + // Owned here; every other radio interface holds its driver by value, but this one is + // constructed in init(), so unique_ptr keeps it from leaking when init() fails and the + // interface is destroyed. + std::unique_ptr lora; public: RF95Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,