@@ -325,14 +325,12 @@ void AsyncWebSocketClient::_onAck(size_t len, uint32_t time) {
325325 _controlQueue.pop_front ();
326326 _status = WS_DISCONNECTED ;
327327 async_ws_log_v (" [%s][%" PRIu32 " ] ACK WS_DISCONNECTED" , _server->url (), _clientId);
328- if (_client) {
329- /*
330- Unlocking has to be called before return execution otherwise std::unique_lock ::~unique_lock() will get an exception pthread_mutex_unlock.
331- Due to _client->close() shall call the callback function _onDisconnect()
332- The calling flow _onDisconnect() --> _handleDisconnect() --> ~AsyncWebSocketClient()
333- */
328+ // Capture _client before unlocking: _client->close() triggers the _onDisconnect() --> _handleDisconnect() --> ~AsyncWebSocketClient() chain,
329+ // so we must not access any member after unlock.
330+ AsyncClient *c = _client;
331+ if (c) {
334332 lock.unlock ();
335- _client ->close ();
333+ c ->close ();
336334 }
337335 return ;
338336 }
@@ -472,18 +470,16 @@ bool AsyncWebSocketClient::_queueMessage(AsyncWebSocketSharedBuffer buffer, uint
472470 if (closeWhenFull) {
473471 _status = WS_DISCONNECTED ;
474472
475- if (_client) {
476- /*
477- Unlocking has to be called before return execution otherwise std::unique_lock ::~unique_lock () will get an exception pthread_mutex_unlock.
478- Due to _client->close() shall call the callback function _onDisconnect()
479- The calling flow _onDisconnect() --> _handleDisconnect() --> ~AsyncWebSocketClient()
480- */
473+ async_ws_log_w ( " [%s][% " PRIu32 " ] Too many messages queued: closing connection " , _server-> url (), _clientId);
474+
475+ // Capture _client before unlocking: _client->close() triggers the _onDisconnect() --> _handleDisconnect() --> ~AsyncWebSocketClient () chain,
476+ // so we must not access any member after unlock.
477+ AsyncClient *c = _client;
478+ if (c) {
481479 lock.unlock ();
482- _client ->close ();
480+ c ->close ();
483481 }
484482
485- async_ws_log_w (" [%s][%" PRIu32 " ] Too many messages queued: closing connection" , _server->url (), _clientId);
486-
487483 } else {
488484 async_ws_log_w (" [%s][%" PRIu32 " ] Too many messages queued: discarding new message" , _server->url (), _clientId);
489485 }
@@ -531,8 +527,13 @@ void AsyncWebSocketClient::close(uint16_t code, const char *message) {
531527 return ;
532528 } else {
533529 async_ws_log_e (" Failed to allocate" );
534- if (_client) {
535- _client->abort ();
530+ // Reads _client, then dereference it without any lock.
531+ // A concurrent _onDisconnect could null + delete the client between the check and the use.
532+ // Local capture ensures the pointer is read exactly once, eliminating the null-dereference.
533+ // (TOCTOU)
534+ AsyncClient *c = _client;
535+ if (c) {
536+ c->abort ();
536537 }
537538 }
538539 }
@@ -548,17 +549,27 @@ void AsyncWebSocketClient::_onError(int8_t err) {
548549}
549550
550551void AsyncWebSocketClient::_onTimeout (uint32_t time) {
551- if (!_client) {
552+ // Reads _client, then dereference it without any lock.
553+ // A concurrent _onDisconnect could null + delete the client between the check and the use.
554+ // Local capture ensures the pointer is read exactly once, eliminating the null-dereference.
555+ // (TOCTOU)
556+ AsyncClient *c = _client;
557+ if (!c) {
552558 return ;
553559 }
554560 async_ws_log_v (" [%s][%" PRIu32 " ] TIMEOUT %" PRIu32, _server->url (), _clientId, time);
555- _client ->close ();
561+ c ->close ();
556562}
557563
558564void AsyncWebSocketClient::_onDisconnect () {
559565 async_ws_log_v (" [%s][%" PRIu32 " ] DISCONNECT" , _server->url (), _clientId);
560566 _status = WS_DISCONNECTED ;
561- _client = nullptr ;
567+ {
568+ // Every queue method (_queueControl, _queueMessage, _runQueue, _onPoll, _onAck) reads _client while holding _queue_lock.
569+ // For those guarded reads to be meaningful, the write must also be synchronized. This doesn't change _queue_lock's purpose — it still guards queue integrity — but ensures the "is client alive?" checks that protect queue operations see a consistent value.
570+ asyncsrv::lock_guard_type lock (_queue_lock);
571+ _client = nullptr ;
572+ }
562573 _server->_handleDisconnect (this );
563574}
564575
@@ -951,17 +962,27 @@ bool AsyncWebSocketClient::binary(const __FlashStringHelper *data, size_t len) {
951962#endif
952963
953964IPAddress AsyncWebSocketClient::remoteIP () const {
954- if (!_client) {
965+ // Reads _client, then dereference it without any lock.
966+ // A concurrent _onDisconnect could null + delete the client between the check and the use.
967+ // Local capture ensures the pointer is read exactly once, eliminating the null-dereference.
968+ // (TOCTOU)
969+ AsyncClient *c = _client;
970+ if (!c) {
955971 return IPAddress ((uint32_t )0U );
956972 }
957- return _client ->remoteIP ();
973+ return c ->remoteIP ();
958974}
959975
960976uint16_t AsyncWebSocketClient::remotePort () const {
961- if (!_client) {
977+ // Reads _client, then dereference it without any lock.
978+ // A concurrent _onDisconnect could null + delete the client between the check and the use.
979+ // Local capture ensures the pointer is read exactly once, eliminating the null-dereference.
980+ // (TOCTOU)
981+ AsyncClient *c = _client;
982+ if (!c) {
962983 return 0 ;
963984 }
964- return _client ->remotePort ();
985+ return c ->remotePort ();
965986}
966987
967988/*
0 commit comments