diff --git a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp index d489d21ee96..25734456e59 100644 --- a/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp +++ b/src/graphics/niche/InkHUD/Applets/System/Menu/MenuApplet.cpp @@ -1993,6 +1993,10 @@ uint16_t InkHUD::MenuApplet::getSystemInfoPanelHeight() void InkHUD::MenuApplet::sendText(NodeNum dest, ChannelIndex channel, const char *message) { meshtastic_MeshPacket *p = router->allocForSending(); + if (!p) { + LOG_WARN("MenuApplet::sendText: packet pool exhausted, dropping message"); + return; + } p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP; p->to = dest; p->channel = channel; diff --git a/src/mesh/MeshModule.cpp b/src/mesh/MeshModule.cpp index 83b64a8733d..7c95ac280fd 100644 --- a/src/mesh/MeshModule.cpp +++ b/src/mesh/MeshModule.cpp @@ -57,6 +57,12 @@ meshtastic_MeshPacket *MeshModule::allocAckNak(meshtastic_Routing_Error err, Nod // So we manually call pb_encode_to_bytes and specify routing port number // auto p = allocDataProtobuf(c); meshtastic_MeshPacket *p = router->allocForSending(); + if (!p) { + // Router::allocForSending / MemoryPool::alloc already LOG_WARN on exhaustion; + // don't double-log. An unthrottled WARN here would spam during an ACK/NAK storm + // — which is exactly the scenario that drives the pool to empty in the first place. + return nullptr; + } p->decoded.portnum = meshtastic_PortNum_ROUTING_APP; p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_Routing_msg, &c); @@ -79,6 +85,8 @@ meshtastic_MeshPacket *MeshModule::allocErrorResponse(meshtastic_Routing_Error e uint8_t channelIndex = p->which_payload_variant == meshtastic_MeshPacket_decoded_tag ? p->channel : channels.getPrimaryIndex(); auto r = allocAckNak(err, getFrom(p), p->id, channelIndex); + if (!r) + return nullptr; setReplyTo(r, *p); diff --git a/src/mesh/ProtobufModule.h b/src/mesh/ProtobufModule.h index 27e653efe2a..1eef1b5b63f 100644 --- a/src/mesh/ProtobufModule.h +++ b/src/mesh/ProtobufModule.h @@ -42,8 +42,14 @@ template class ProtobufModule : protected SinglePortModule */ meshtastic_MeshPacket *allocDataProtobuf(const T &payload) { - // Update our local node info with our position (even if we don't decide to update anyone else) + // allocDataPacket() now returns nullptr on packet-pool exhaustion (since + // Router::allocForSending was made null-safe in #10261). Propagate the nullptr + // to the caller rather than dereferencing `p->decoded`. All current callers + // either null-check the return or forward it to a helper that does — see the + // caller audit in the #10261 PR description. meshtastic_MeshPacket *p = allocDataPacket(); + if (!p) + return nullptr; p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), fields, &payload); diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 836cd1a2291..d64b195fc7c 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -208,6 +208,9 @@ PacketId generatePacketId() meshtastic_MeshPacket *Router::allocForSending() { meshtastic_MeshPacket *p = packetPool.allocZeroed(); + if (!p) { + return nullptr; + } p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; // Assume payload is decoded at start. p->from = nodeDB->getNodeNum(); diff --git a/src/mesh/SinglePortModule.h b/src/mesh/SinglePortModule.h index e43de09d12c..67444329acc 100644 --- a/src/mesh/SinglePortModule.h +++ b/src/mesh/SinglePortModule.h @@ -32,6 +32,8 @@ class SinglePortModule : public MeshModule { // Update our local node info with our position (even if we don't decide to update anyone else) meshtastic_MeshPacket *p = router->allocForSending(); + if (!p) + return nullptr; p->decoded.portnum = ourPortNum; return p; diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index 20d4d7d8c7e..f491c24e47d 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -309,6 +309,10 @@ int32_t SerialModule::runOnce() void SerialModule::sendTelemetry(meshtastic_Telemetry m) { meshtastic_MeshPacket *p = router->allocForSending(); + if (!p) { + LOG_WARN("SerialModule::sendTelemetry: packet pool exhausted, dropping"); + return; + } p->decoded.portnum = meshtastic_PortNum_TELEMETRY_APP; p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), &meshtastic_Telemetry_msg, &m); diff --git a/src/modules/SerialModule.h b/src/modules/SerialModule.h index dbe4f75dbc3..768feeec632 100644 --- a/src/modules/SerialModule.h +++ b/src/modules/SerialModule.h @@ -71,6 +71,8 @@ class SerialModuleRadio : public MeshModule { // Update our local node info with our position (even if we don't decide to update anyone else) meshtastic_MeshPacket *p = router->allocForSending(); + if (!p) + return nullptr; p->decoded.portnum = ourPortNum; return p; diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index aba06c21005..ebc277229c9 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -189,6 +189,10 @@ inline void onReceiveJson(byte *payload, size_t length) // construct protobuf data packet using TEXT_MESSAGE, send it to the mesh meshtastic_MeshPacket *p = router->allocForSending(); + if (!p) { + LOG_WARN("MQTT downlink sendtext dropped: packet pool exhausted"); + return; + } p->decoded.portnum = meshtastic_PortNum_TEXT_MESSAGE_APP; if (json.find("channel") != json.end() && json["channel"]->IsNumber() && (json["channel"]->AsNumber() < channels.getNumChannels())) @@ -202,7 +206,11 @@ inline void onReceiveJson(byte *payload, size_t length) p->decoded.payload.size = jsonPayloadStr.length(); service->sendToMesh(p, RX_SRC_LOCAL); } else { + // Release the allocated packet back to the pool — `p` would otherwise leak, + // permanently reducing the number of available slots for every future + // send attempt. LOG_WARN("Received MQTT json payload too long, drop"); + packetPool.release(p); } } else if (json["type"]->AsString().compare("sendposition") == 0 && json["payload"]->IsObject()) { // invent the "sendposition" type for a valid envelope @@ -220,6 +228,10 @@ inline void onReceiveJson(byte *payload, size_t length) // construct protobuf data packet using POSITION, send it to the mesh meshtastic_MeshPacket *p = router->allocForSending(); + if (!p) { + LOG_WARN("MQTT downlink sendposition dropped: packet pool exhausted"); + return; + } p->decoded.portnum = meshtastic_PortNum_POSITION_APP; if (json.find("channel") != json.end() && json["channel"]->IsNumber() && (json["channel"]->AsNumber() < channels.getNumChannels()))