@@ -3417,6 +3417,7 @@ WOLFMQTT_LOCAL void BrokerOrphan_DropFull(MqttBroker* broker,
34173417 WOLFMQTT_FREE (sp -> client_id );
34183418 }
34193419 WOLFMQTT_FREE (sp );
3420+ broker -> subs_gen ++ ;
34203421 }
34213422 else {
34223423 prev = sp ;
@@ -3902,6 +3903,7 @@ static void BrokerSubs_RemoveClient(MqttBroker* broker, BrokerClient* bc)
39023903 WOLFMQTT_FREE (cur -> client_id );
39033904 }
39043905 WOLFMQTT_FREE (cur );
3906+ broker -> subs_gen ++ ;
39053907 }
39063908 else {
39073909 prev = cur ;
@@ -4053,6 +4055,11 @@ static int BrokerSubs_Add(MqttBroker* broker, BrokerClient* bc,
40534055 sub -> filter [filter_len ] = '\0' ;
40544056 sub -> next = broker -> subs ;
40554057 broker -> subs = sub ;
4058+ /* Bump on add too so subs_gen reflects every structural change to the
4059+ * list, not only removals. Also narrows the ABA window where a free and
4060+ * a same-address add in one fan-out iteration could otherwise pass the
4061+ * successor-still-linked check. */
4062+ broker -> subs_gen ++ ;
40564063 }
40574064 else if (sub != NULL ) {
40584065 WOLFMQTT_FREE (sub );
@@ -4138,6 +4145,7 @@ static void BrokerSubs_Remove(MqttBroker* broker, BrokerClient* bc,
41384145 WOLFMQTT_FREE (cur -> client_id );
41394146 }
41404147 WOLFMQTT_FREE (cur );
4148+ broker -> subs_gen ++ ;
41414149 if (bc -> sub_count > 0 ) {
41424150 bc -> sub_count -- ;
41434151 }
@@ -4263,6 +4271,7 @@ static void BrokerSubs_RemoveByClientId(MqttBroker* broker,
42634271 WOLFMQTT_FREE (cur -> client_id );
42644272 }
42654273 WOLFMQTT_FREE (cur );
4274+ broker -> subs_gen ++ ;
42664275 }
42674276 else {
42684277 prev = cur ;
@@ -5394,6 +5403,7 @@ static void BrokerClient_PublishWillImmediate(MqttBroker* broker,
53945403#else
53955404 BrokerSub * sub ;
53965405 BrokerSub * next_sub = NULL ;
5406+ word32 subs_gen_snapshot = 0 ;
53975407#endif
53985408
53995409 if (broker == NULL || topic == NULL ) {
@@ -5426,8 +5436,10 @@ static void BrokerClient_PublishWillImmediate(MqttBroker* broker,
54265436 /* Snapshot the successor before any MqttPacket_Write: a WS fan-out
54275437 * write can drive an lws_service spin whose re-entrant CLOSED frees
54285438 * this client's BrokerSub nodes, so reading sub->next afterwards
5429- * would dereference a freed node. */
5439+ * would dereference a freed node. Also snapshot the subs generation so
5440+ * next_sub is only re-validated when a free actually happened. */
54305441 next_sub = sub -> next ;
5442+ subs_gen_snapshot = broker -> subs_gen ;
54315443#endif
54325444 if (sub -> client != NULL && sub -> client -> protocol_level != 0 &&
54335445#ifdef WOLFMQTT_STATIC_MEMORY
@@ -5534,6 +5546,13 @@ static void BrokerClient_PublishWillImmediate(MqttBroker* broker,
55345546 {
55355547 MqttPublish out_pub ;
55365548 int enc_rc , wr_rc ;
5549+ /* Cache the client before the write: MqttPacket_Write can drive
5550+ * a re-entrant WS close that frees this subscriber's BrokerSub
5551+ * nodes (this `sub`), while the BrokerClient itself survives via
5552+ * deferred removal. Reaching it through the freed `sub` after
5553+ * the write would be a use-after-free; go through wc instead,
5554+ * mirroring BrokerHandle_Publish. */
5555+ BrokerClient * wc = sub -> client ;
55375556 XMEMSET (& out_pub , 0 , sizeof (out_pub ));
55385557 out_pub .topic_name = (char * )topic ;
55395558 out_pub .qos = eff_qos ;
@@ -5545,16 +5564,16 @@ static void BrokerClient_PublishWillImmediate(MqttBroker* broker,
55455564 out_pub .packet_id = BrokerNextPacketId (broker );
55465565 }
55475566#ifdef WOLFMQTT_V5
5548- out_pub .protocol_level = sub -> client -> protocol_level ;
5567+ out_pub .protocol_level = wc -> protocol_level ;
55495568#endif
5550- enc_rc = MqttEncode_Publish (sub -> client -> tx_buf ,
5551- BROKER_CLIENT_TX_SZ (sub -> client ), & out_pub , 0 );
5569+ enc_rc = MqttEncode_Publish (wc -> tx_buf ,
5570+ BROKER_CLIENT_TX_SZ (wc ), & out_pub , 0 );
55525571 if (enc_rc > 0 ) {
5553- wr_rc = MqttPacket_Write (& sub -> client -> client ,
5554- sub -> client -> tx_buf , enc_rc );
5572+ wr_rc = MqttPacket_Write (& wc -> client ,
5573+ wc -> tx_buf , enc_rc );
55555574 /* Scrub tx_buf unless still in-progress (CONTINUE). */
55565575 if (wr_rc != MQTT_CODE_CONTINUE ) {
5557- BROKER_FORCE_ZERO (sub -> client -> tx_buf , enc_rc );
5576+ BROKER_FORCE_ZERO (wc -> tx_buf , enc_rc );
55585577 }
55595578 }
55605579 }
@@ -5586,9 +5605,11 @@ static void BrokerClient_PublishWillImmediate(MqttBroker* broker,
55865605 }
55875606#endif
55885607#ifndef WOLFMQTT_STATIC_MEMORY
5589- /* The write above can drive a re-entrant WS close that frees next_sub;
5590- * stop the walk if that snapshot is no longer linked. */
5591- if (next_sub != NULL && !BrokerSubs_StillLinked (broker , next_sub )) {
5608+ /* The write above can drive a re-entrant WS close that frees next_sub.
5609+ * Only re-validate it when a subscription was actually removed during
5610+ * the write (generation changed), so the common case stays O(1). */
5611+ if (next_sub != NULL && broker -> subs_gen != subs_gen_snapshot &&
5612+ !BrokerSubs_StillLinked (broker , next_sub )) {
55925613 break ;
55935614 }
55945615 sub = next_sub ;
@@ -7177,6 +7198,7 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len,
71777198#ifndef WOLFMQTT_STATIC_MEMORY
71787199 BrokerSub * sub = broker -> subs ;
71797200 BrokerSub * next_sub = NULL ;
7201+ word32 subs_gen_snapshot = 0 ;
71807202#endif
71817203 /* Fan out to matching subscribers */
71827204#ifdef WOLFMQTT_STATIC_MEMORY
@@ -7188,8 +7210,11 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len,
71887210 /* Snapshot the successor before any MqttPacket_Write: a fan-out
71897211 * write can drive an lws_service spin that frees this client's
71907212 * BrokerSub nodes re-entrantly (LWS_CALLBACK_CLOSED), so reading
7191- * sub->next afterwards would dereference a freed node. */
7213+ * sub->next afterwards would dereference a freed node. Also snapshot
7214+ * the subs generation so next_sub is only re-validated when a free
7215+ * actually happened. */
71927216 next_sub = sub -> next ;
7217+ subs_gen_snapshot = broker -> subs_gen ;
71937218#endif
71947219 if (sub -> client != NULL &&
71957220 sub -> client -> protocol_level != 0 &&
@@ -7422,8 +7447,11 @@ static int BrokerHandle_Publish(BrokerClient* bc, int rx_len,
74227447 }
74237448 }
74247449 /* The write above can drive a re-entrant WS close that frees
7425- * next_sub; stop the walk if that snapshot is no longer linked. */
7426- if (next_sub != NULL && !BrokerSubs_StillLinked (broker , next_sub )) {
7450+ * next_sub. Only re-validate it when a subscription was actually
7451+ * removed during the write (generation changed), so the common
7452+ * case stays O(1). */
7453+ if (next_sub != NULL && broker -> subs_gen != subs_gen_snapshot &&
7454+ !BrokerSubs_StillLinked (broker , next_sub )) {
74277455 break ;
74287456 }
74297457 sub = next_sub ;
@@ -8564,6 +8592,7 @@ static void BrokerSubs_FreeAll(MqttBroker* broker)
85648592 WOLFMQTT_FREE (broker -> subs -> client_id );
85658593 }
85668594 WOLFMQTT_FREE (broker -> subs );
8595+ broker -> subs_gen ++ ;
85678596 broker -> subs = next ;
85688597 }
85698598#endif
0 commit comments