From 22865fff63fa046e4f77f43fb96abea72c46bab3 Mon Sep 17 00:00:00 2001 From: jmestwa-coder Date: Sat, 20 Jun 2026 13:31:45 +0530 Subject: [PATCH] fix use-after-free on next_sub in broker fan-out loops Both subscription fan-out loops snapshot next_sub before a write that can drive a re-entrant WebSocket close. That close runs BrokerSubs_RemoveClient and frees the subscriber's sub nodes, while pending_remove only defers the client struct, so next_sub itself can be freed. Revalidate it against broker->subs after the write, like MqttBroker_Step does for the client list. --- src/mqtt_broker.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/mqtt_broker.c b/src/mqtt_broker.c index 525e1ca5..aedbc5ed 100644 --- a/src/mqtt_broker.c +++ b/src/mqtt_broker.c @@ -2959,6 +2959,29 @@ static void BrokerSubs_Remove(MqttBroker* broker, BrokerClient* bc, #endif } +#ifndef WOLFMQTT_STATIC_MEMORY +/* Confirm a fan-out loop's snapshotted successor is still linked in + * broker->subs. A peer-initiated WS LWS_CALLBACK_CLOSED delivered during a + * fan-out write runs BrokerSubs_RemoveClient, which frees every BrokerSub + * owned by the closing subscriber - and that can include the node a loop saved + * as next_sub. The pending_remove deferral keeps the BrokerClient struct alive + * across the spin, but the sub nodes are freed regardless, so snapshotting + * next_sub guards only against sub->next moving, not against next_sub itself + * being freed. Re-check it before the next dereference, mirroring the + * client-list revalidation in MqttBroker_Step. */ +static int BrokerSubs_StillLinked(MqttBroker* broker, const BrokerSub* node) +{ + BrokerSub* cur = broker->subs; + while (cur != NULL) { + if (cur == node) { + return 1; + } + cur = cur->next; + } + return 0; +} +#endif + /* -------------------------------------------------------------------------- */ /* Packet ID generation */ /* -------------------------------------------------------------------------- */ @@ -4067,6 +4090,11 @@ static void BrokerClient_PublishWillImmediate(MqttBroker* broker, } } #ifndef WOLFMQTT_STATIC_MEMORY + /* The write above can drive a re-entrant WS close that frees next_sub; + * stop the walk if it is gone. */ + if (next_sub != NULL && !BrokerSubs_StillLinked(broker, next_sub)) { + break; + } sub = next_sub; #endif } @@ -5476,6 +5504,11 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len, } } } + /* The fan-out write above can drive a re-entrant WS close that + * frees next_sub; stop the walk if it is gone. */ + if (next_sub != NULL && !BrokerSubs_StillLinked(broker, next_sub)) { + break; + } sub = next_sub; #endif }