Skip to content

Commit f7fb112

Browse files
Merge pull request #465 from willmmiles/fix/use-after-free-shared_ptr
fix(http): prevent use-after-free from synchronous abort/close()
2 parents 526622f + 095a193 commit f7fb112

7 files changed

Lines changed: 82 additions & 66 deletions

File tree

src/AsyncEventSource.cpp

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -147,11 +147,8 @@ size_t AsyncEventSourceMessage::send(AsyncClient *client) {
147147

148148
// Client
149149

150-
AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server) : _client(request->clientRelease()), _server(server) {
151-
152-
if (request->hasHeader(T_Last_Event_ID)) {
153-
_lastId = atoi(request->getHeader(T_Last_Event_ID)->value().c_str());
154-
}
150+
AsyncEventSourceClient::AsyncEventSourceClient(AsyncClient *client, AsyncEventSource *server, uint32_t lastId)
151+
: _client(client), _server(server), _lastId(lastId) {
155152

156153
_client->setRxTimeout(0);
157154
_client->onError(NULL, NULL);
@@ -186,8 +183,6 @@ AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, A
186183

187184
_server->_addClient(this);
188185
_client->setNoDelay(true);
189-
// delete AsyncWebServerRequest object (and bound response) since we have the ownership on client connection now
190-
delete request;
191186
}
192187

193188
AsyncEventSourceClient::~AsyncEventSourceClient() {
@@ -478,24 +473,12 @@ AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server) : _
478473
void AsyncEventSourceResponse::_respond(AsyncWebServerRequest *request) {
479474
String out;
480475
_assembleHead(out, request->version());
481-
// unbind client's onAck callback from AsyncWebServerRequest's, we will destroy it on next callback and steal the client,
482-
// can't do it now 'cause now we are in AsyncWebServerRequest::_onAck 's stack actually
483-
// here we are loosing time on one RTT delay, but with current design we can't get rid of Req/Resp objects other way
484-
_request = request;
485-
request->client()->onAck(
486-
[](void *r, AsyncClient *c, size_t len, uint32_t time) {
487-
if (len) {
488-
static_cast<AsyncEventSourceResponse *>(r)->_switchClient();
489-
}
490-
},
491-
this
492-
);
476+
uint32_t lastId = 0;
477+
if (request->hasHeader(T_Last_Event_ID)) {
478+
lastId = strtoul(request->getHeader(T_Last_Event_ID)->value().c_str(), nullptr, 10);
479+
}
493480
request->client()->write(out.c_str(), _headLength);
494-
_state = RESPONSE_WAIT_ACK;
481+
// Add a new AsyncEventSourceClient to the server's list of clients
482+
// This adopts the ownership of the AsyncTCP's client pointer from `request` parameter
483+
new AsyncEventSourceClient(request->clientRelease(), _server, lastId);
495484
}
496-
497-
void AsyncEventSourceResponse::_switchClient() {
498-
// AsyncEventSourceClient c-tor will take the ownership of AsyncTCP's client connection
499-
new AsyncEventSourceClient(_request, _server);
500-
// AsyncEventSourceClient c-tor would also delete _request and *this
501-
};

src/AsyncEventSource.h

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -144,12 +144,13 @@ class AsyncEventSourceClient {
144144
public:
145145
/**
146146
* @brief Construct a new Async Event Source Client object
147-
* @note constructor would take the ownership of of AsyncTCP's client pointer from `request` parameter and call delete on it!
147+
* @note constructor is normally passed a client object from AsyncWebServerRequest::releaseClient(); see AsyncEventSourceResponse::_respond()
148148
*
149-
* @param request
149+
* @param client
150150
* @param server
151+
* @param lastId
151152
*/
152-
AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server);
153+
AsyncEventSourceClient(AsyncClient *client, AsyncEventSource *server, uint32_t lastId = 0);
153154
~AsyncEventSourceClient();
154155

155156
/**
@@ -319,9 +320,6 @@ class AsyncEventSource : public AsyncWebHandler {
319320
class AsyncEventSourceResponse : public AsyncWebServerResponse {
320321
private:
321322
AsyncEventSource *_server;
322-
AsyncWebServerRequest *_request;
323-
// this call back will switch AsyncTCP client to SSE
324-
void _switchClient();
325323

326324
public:
327325
AsyncEventSourceResponse(AsyncEventSource *server);

src/AsyncWebSocket.cpp

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,7 @@ void AsyncWebSocketClient::close(uint16_t code, const char *message) {
535535
if (c) {
536536
c->abort();
537537
}
538+
return;
538539
}
539540
}
540541
_queueControl(WS_DISCONNECT);
@@ -680,7 +681,9 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) {
680681
"[%s][%" PRIu32 "] DATA processing next fragment of %s frame %" PRIu32 ", index: %" PRIu64 ", len: %" PRIu32 "", _server->url(), _clientId,
681682
(_pinfo.message_opcode == WS_TEXT) ? "text" : "binary", _pinfo.num, _pinfo.index, (uint32_t)datalen
682683
);
683-
_handleDataEvent(data, datalen, datalen == plen); // datalen == plen means that we are processing the last part of the current TCP packet
684+
if (!_handleDataEvent(data, datalen, datalen == plen)) { // datalen == plen means that we are processing the last part of the current TCP packet
685+
return; // stop processing on failure
686+
}
684687
}
685688

686689
// track index for next fragment
@@ -704,6 +707,7 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) {
704707
if (_client) {
705708
_client->close();
706709
}
710+
return; // our object is now destroyed, so we must return immediately to avoid accessing any member
707711
} else {
708712
_status = WS_DISCONNECTING;
709713
if (_client) {
@@ -729,7 +733,9 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) {
729733
(_pinfo.message_opcode == WS_TEXT) ? "text" : "binary", _pinfo.num, _pinfo.index, (uint32_t)datalen
730734
);
731735

732-
_handleDataEvent(data, datalen, datalen == plen); // datalen == plen means that we are processing the last part of the current TCP packet
736+
if (!_handleDataEvent(data, datalen, datalen == plen)) { // datalen == plen means that we are processing the last part of the current TCP packet
737+
return; // stop processing on failure
738+
}
733739

734740
if (_pinfo.final) {
735741
_pinfo.num = 0;
@@ -759,7 +765,7 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) {
759765
}
760766
}
761767

762-
void AsyncWebSocketClient::_handleDataEvent(uint8_t *data, size_t len, bool endOfPaquet) {
768+
bool AsyncWebSocketClient::_handleDataEvent(uint8_t *data, size_t len, bool endOfPaquet) {
763769
// ------------------------------------------------------------
764770
// Issue 384: https://github.com/ESP32Async/ESPAsyncWebServer/issues/384
765771
// Discussion: https://github.com/ESP32Async/ESPAsyncWebServer/pull/383#discussion_r2760425739
@@ -790,9 +796,11 @@ void AsyncWebSocketClient::_handleDataEvent(uint8_t *data, size_t len, bool endO
790796
_server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, copy.get(), len);
791797
} else {
792798
async_ws_log_e("Failed to allocate");
793-
if (_client) {
794-
_client->abort();
799+
AsyncClient *c = _client;
800+
if (c) {
801+
c->abort();
795802
}
803+
return false; // failure!
796804
}
797805
} else {
798806
uint8_t backup = data[len];
@@ -803,6 +811,7 @@ void AsyncWebSocketClient::_handleDataEvent(uint8_t *data, size_t len, bool endO
803811
} else {
804812
_server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, data, len);
805813
}
814+
return true;
806815
}
807816

808817
size_t AsyncWebSocketClient::printf(const char *format, ...) {
@@ -997,11 +1006,13 @@ void AsyncWebSocket::_handleEvent(AsyncWebSocketClient *client, AwsEventType typ
9971006

9981007
AsyncWebSocketClient *AsyncWebSocket::_newClient(AsyncWebServerRequest *request) {
9991008
asyncsrv::lock_guard_type lock(_ws_clients_lock);
1000-
_clients.emplace_back(request, this);
1009+
// Hold the request in scope for the user callback to inspect
1010+
std::shared_ptr<AsyncWebServerRequest> req_lock = request->shared_from_this();
1011+
// Adopt the client object from the request
1012+
_clients.emplace_back(request->clientRelease(), this);
10011013
// we've just detached AsyncTCP client from AsyncWebServerRequest
10021014
_handleEvent(&_clients.back(), WS_EVT_CONNECT, request, NULL, 0);
1003-
// after user code completed CONNECT event callback we can delete req/response objects
1004-
delete request;
1015+
// req_lock releases the request object at the end of this function scope
10051016
return &_clients.back();
10061017
}
10071018

src/AsyncWebSocket.h

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -235,20 +235,13 @@ class AsyncWebSocketClient {
235235
void _clearQueue();
236236

237237
// this function is called when a text message is received, in order to copy the buffer and place a null terminator at the end of the buffer for easier handling of text messages.
238-
void _handleDataEvent(uint8_t *data, size_t len, bool endOfPaquet);
238+
// Returns true on success, false on failure (e.g. memory allocation failure)
239+
bool _handleDataEvent(uint8_t *data, size_t len, bool endOfPaquet);
239240

240241
public:
241242
void *_tempObject;
242243

243244
AsyncWebSocketClient(AsyncClient *client, AsyncWebSocket *server);
244-
245-
/**
246-
* @brief Construct a new Async Web Socket Client object
247-
* @note constructor would take the ownership of of AsyncTCP's client pointer from `request` parameter and call delete on it!
248-
* @param request
249-
* @param server
250-
*/
251-
AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server) : AsyncWebSocketClient(request->clientRelease(), server){};
252245
~AsyncWebSocketClient();
253246

254247
// client id increments for the given server

src/ESPAsyncWebServer.h

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -508,13 +508,28 @@ class AsyncWebServerRequest {
508508

509509
static bool _getEtag(File gzFile, char *eTag);
510510

511+
// Constructor is private to ensure factory is used to create shared_ptrs
512+
AsyncWebServerRequest(AsyncWebServer *, AsyncClient *);
513+
511514
public:
512515
File _tempFile;
513516
void *_tempObject;
514517

515-
AsyncWebServerRequest(AsyncWebServer *, AsyncClient *);
518+
// Factory function
519+
static std::shared_ptr<AsyncWebServerRequest> create(AsyncWebServer *server, AsyncClient *client) {
520+
AsyncWebServerRequest *req = new (std::nothrow) AsyncWebServerRequest(server, client);
521+
if (req) {
522+
req->_this = std::shared_ptr<AsyncWebServerRequest>(req); // store shared pointer to this request
523+
return req->_this;
524+
}
525+
return {}; // empty shared_ptr
526+
}
516527
~AsyncWebServerRequest();
517528

529+
std::shared_ptr<AsyncWebServerRequest> shared_from_this() {
530+
return _this;
531+
}
532+
518533
AsyncClient *client() {
519534
return _client;
520535
}
@@ -524,6 +539,8 @@ class AsyncWebServerRequest {
524539
* AsyncClient pointer will be abandoned in this instance,
525540
* the further ownership of the connection should be managed out of request's life-time scope
526541
* could be used for long lived connection like SSE or WebSockets
542+
* This causes the request object to self-destruct; make sure you're holding a shared_ptr if
543+
* you need to keep it alive any longer (see shared_from_this())
527544
* @note do not call this method unless you know what you are doing, otherwise it may lead to
528545
* memory leaks and connections lingering
529546
*
@@ -1763,7 +1780,6 @@ class AsyncWebServer : public AsyncMiddlewareChain {
17631780

17641781
void reset(); // remove all writers and handlers, with onNotFound/onFileUpload/onRequestBody
17651782

1766-
void _handleDisconnect(AsyncWebServerRequest *request);
17671783
void _attachHandler(AsyncWebServerRequest *request);
17681784
void _rewriteRequest(AsyncWebServerRequest *request);
17691785
};

src/WebRequest.cpp

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ static inline bool isParamChar(char c) {
1717
return ((c) && ((c) != '{') && ((c) != '[') && ((c) != '&') && ((c) != '='));
1818
}
1919

20-
static void doNotDelete(AsyncWebServerRequest *) {}
21-
2220
using namespace asyncsrv;
2321

2422
enum {
@@ -108,8 +106,6 @@ AsyncWebServerRequest::~AsyncWebServerRequest() {
108106
_response = nullptr;
109107
}
110108

111-
_this.reset();
112-
113109
if (_tempObject != NULL) {
114110
free(_tempObject);
115111
}
@@ -124,6 +120,8 @@ AsyncWebServerRequest::~AsyncWebServerRequest() {
124120
}
125121

126122
void AsyncWebServerRequest::_onData(void *buf, size_t len) {
123+
std::shared_ptr<AsyncWebServerRequest> self = _this; // Ensure we stay in scope over the function
124+
127125
// SSL/TLS handshake detection
128126
#ifndef ASYNC_TCP_SSL_ENABLED
129127
if (_parseState == PARSE_REQ_START && len && ((uint8_t *)buf)[0] == 0x16) { // 0x16 indicates a Handshake message (SSL/TLS).
@@ -240,6 +238,7 @@ void AsyncWebServerRequest::_onData(void *buf, size_t len) {
240238
}
241239

242240
void AsyncWebServerRequest::_onPoll() {
241+
std::shared_ptr<AsyncWebServerRequest> self = _this; // Ensure we stay in scope over the function
243242
// os_printf("p\n");
244243
if (_response && _client && _client->canSend()) {
245244
_response->_ack(this, 0, 0);
@@ -252,6 +251,8 @@ void AsyncWebServerRequest::_onAck(size_t len, uint32_t time) {
252251
return;
253252
}
254253

254+
std::shared_ptr<AsyncWebServerRequest> self = _this; // Ensure we stay in scope over the function
255+
255256
if (!_response->_finished()) {
256257
_response->_ack(this, len, time);
257258
// recheck if response has just completed, close connection
@@ -271,6 +272,7 @@ void AsyncWebServerRequest::_onError(int8_t error) {
271272
void AsyncWebServerRequest::_onTimeout(uint32_t time) {
272273
(void)time;
273274
// os_printf("TIMEOUT: %u, state: %s\n", time, _client->stateToString());
275+
// We do not need to lock the shared pointer here as we do no work after closing the client
274276
_client->close();
275277
}
276278

@@ -283,7 +285,12 @@ void AsyncWebServerRequest::_onDisconnect() {
283285
if (_onDisconnectfn) {
284286
_onDisconnectfn();
285287
}
286-
_server->_handleDisconnect(this);
288+
// Pull shared pointer on to this context
289+
// This will induce self-destruction if there are no other active handles
290+
// This is safe to call here as it is the last operation in this callback
291+
// We move to a stack local before destruction a simple reset would attempt to
292+
// write through the now-destroyed object.
293+
std::shared_ptr<AsyncWebServerRequest> self = std::move(_this);
287294
}
288295

289296
void AsyncWebServerRequest::_addGetParams(const String &params) {
@@ -1054,9 +1061,6 @@ AsyncWebServerRequestPtr AsyncWebServerRequest::pause() {
10541061
return _this;
10551062
}
10561063
client()->setRxTimeout(0);
1057-
// this shared ptr will hold the request pointer until it gets destroyed following a disconnect.
1058-
// this is just used as a holder providing weak observers, so the deleter is a no-op.
1059-
_this = std::shared_ptr<AsyncWebServerRequest>(this, doNotDelete);
10601064
_paused = true;
10611065
return _this;
10621066
}
@@ -1065,7 +1069,6 @@ void AsyncWebServerRequest::abort() {
10651069
if (!_sent) {
10661070
_sent = true;
10671071
_paused = false;
1068-
_this.reset();
10691072
async_ws_log_v("Abort request: %s", _url.c_str());
10701073
_client->abort();
10711074
}
@@ -1327,7 +1330,9 @@ void AsyncWebServerRequest::requestAuthentication(AsyncAuthType method, const ch
13271330
r->addHeader(T_WWW_AUTH, header.c_str());
13281331
} else {
13291332
async_ws_log_e("Failed to allocate");
1333+
delete r;
13301334
abort();
1335+
return;
13311336
}
13321337

13331338
break;
@@ -1351,7 +1356,9 @@ void AsyncWebServerRequest::requestAuthentication(AsyncAuthType method, const ch
13511356
r->addHeader(T_WWW_AUTH, header.c_str());
13521357
} else {
13531358
async_ws_log_e("Failed to allocate");
1359+
delete r;
13541360
abort();
1361+
return;
13551362
}
13561363
}
13571364
break;
@@ -1469,6 +1476,17 @@ bool AsyncWebServerRequest::isExpectedRequestedConnType(RequestedConnectionType
14691476
AsyncClient *AsyncWebServerRequest::clientRelease() {
14701477
AsyncClient *c = _client;
14711478
_client = nullptr;
1479+
// Ensure the client object no longer refers to us
1480+
c->onError({}, nullptr);
1481+
c->onAck({}, nullptr);
1482+
c->onDisconnect({}, nullptr);
1483+
c->onTimeout({}, nullptr);
1484+
c->onData({}, nullptr);
1485+
c->onPoll({}, nullptr);
1486+
// Now that we are no longer bound to the client, self-destruct at the earliest opportunity by moving the shared pointer to a local.
1487+
// This will decrement the reference count and delete the object when this function ends if there are no other references.
1488+
// If this function is called by an _onAck handler or the like, the local lock will keep the request object in scope until the handler returns, preventing a use-after-free.
1489+
std::shared_ptr<AsyncWebServerRequest> destroy_self = std::move(_this);
14721490
return c;
14731491
}
14741492

src/WebServer.cpp

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "ESPAsyncWebServer.h"
55
#include "WebHandlerImpl.h"
66

7+
#include <memory>
78
#include <string>
89
#include <utility>
910

@@ -47,8 +48,8 @@ AsyncWebServer::AsyncWebServer(uint16_t port) : _server(port) {
4748
return;
4849
}
4950
c->setRxTimeout(3);
50-
AsyncWebServerRequest *r = new AsyncWebServerRequest((AsyncWebServer *)s, c);
51-
if (r == NULL) {
51+
std::shared_ptr<AsyncWebServerRequest> r = AsyncWebServerRequest::create(static_cast<AsyncWebServer *>(s), c);
52+
if (!r) {
5253
c->abort();
5354
delete c;
5455
}
@@ -127,10 +128,6 @@ void AsyncWebServer::beginSecure(const char *cert, const char *key, const char *
127128
}
128129
#endif
129130

130-
void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest *request) {
131-
delete request;
132-
}
133-
134131
void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest *request) {
135132
// the last rewrite that matches the request will be used
136133
// we do not break the loop to allow for multiple rewrites to be applied and only the last one to be used (allows overriding)

0 commit comments

Comments
 (0)