Skip to content

fix(http): prevent use-after-free from synchronous abort/close() #465

Merged
mathieucarbou merged 5 commits into
ESP32Async:mainfrom
willmmiles:fix/use-after-free-shared_ptr
Jul 24, 2026
Merged

fix(http): prevent use-after-free from synchronous abort/close() #465
mathieucarbou merged 5 commits into
ESP32Async:mainfrom
willmmiles:fix/use-after-free-shared_ptr

Conversation

@willmmiles

Copy link
Copy Markdown

Alternative implementation for #464 using std::shared_ptr instead of locks.

Ensure that AsyncWebRequest objects are held in scope until the end of their handler functions, avoiding any possible use-after-free cases when aborted or closed. This is managed by using std::shared_ptr to allow a scoped lifecycle "lock" to be taken without an OS mutex.

Copilot AI review requested due to automatic review settings July 22, 2026 02:07
@willmmiles
willmmiles force-pushed the fix/use-after-free-shared_ptr branch from 37d2837 to 863ab2f Compare July 22, 2026 02:12

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR changes the HTTP request lifetime management in ESPAsyncWebServer to prevent request/client destruction during synchronous abort()/close() re-entrancy (as introduced by AsyncTCP’s synchronous callbacks), by moving AsyncWebServerRequest to a std::shared_ptr-managed lifecycle and holding scoped references during key TCP callbacks.

Changes:

  • Introduces AsyncWebServerRequest::create() factory and stores a self-referential std::shared_ptr to control request lifetime.
  • Updates request TCP callbacks to hold a local shared_ptr in _onData/_onPoll/_onAck to prevent premature destruction during nested disconnect paths.
  • Removes the server-side _handleDisconnect() deletion path and adjusts auth failure handling to avoid leaking responses / accessing the request after abort.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/WebServer.cpp Creates requests via the new AsyncWebServerRequest::create() shared_ptr factory and removes server-driven deletion.
src/WebRequest.cpp Adds scoped shared_ptr holds in several callbacks and switches disconnect cleanup to releasing the self-reference; fixes auth allocation-failure paths.
src/ESPAsyncWebServer.h Makes the request constructor private and adds the create() factory that initializes the self-owned shared_ptr.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/WebRequest.cpp
Comment thread src/WebRequest.cpp Outdated
Comment thread src/WebRequest.cpp
@mathieucarbou

mathieucarbou commented Jul 22, 2026

Copy link
Copy Markdown
Member

@willmmiles : I wasn't able to push a commit until your branch so I copied yours into origin repo so that you and I can push and commit. (and have someone test the changes).,

Here is the result: #466

It contains 2 commits, yours plus one that I added to fix the discussions above.

I tested with the WebSocket example and it works fine now.

Copilot AI review requested due to automatic review settings July 22, 2026 15:12
@willmmiles
willmmiles force-pushed the fix/use-after-free-shared_ptr branch from 863ab2f to bf5bc24 Compare July 22, 2026 15:12

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

src/WebRequest.cpp:1488

  • clientRelease() calls _this.reset(). If _this is the last owning shared_ptr, this can destroy the request object while std::shared_ptr::reset() is still executing on the _this member subobject, which is undefined behavior (same issue this PR is avoiding in _onDisconnect()). Break the self-reference by moving _this to a local variable instead.
  // Now that we are no longer bound to the client, self-destruct at the earliest opportunity
  _this.reset();
  return c;

Comment thread src/WebServer.cpp
Comment thread src/AsyncEventSource.h
Comment thread src/AsyncEventSource.h
Copilot AI review requested due to automatic review settings July 22, 2026 21:01
@willmmiles
willmmiles force-pushed the fix/use-after-free-shared_ptr branch from bf5bc24 to a443fcd Compare July 22, 2026 21:01
…ncTCP 3.5.0)

Ensure that AsyncWebRequest objects are held in scope until the end of
their handler functions, avoiding any possible use-after-free cases
when aborted or closed.  This is managed by using `std::shared_ptr` to
allow a scoped lifecycle "lock" to be taken without an OS mutex.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/AsyncEventSource.h:150

  • The constructor documentation is now out of sync with the actual signature: it references AsyncWebServerRequest::releaseClient() and documents a request parameter, but the constructor takes an AsyncClient* and the request method is clientRelease(). This makes the public API docs misleading for consumers.
   * @brief Construct a new Async Event Source Client object
   * @note constructor is normally passed a client object from AsyncWebServerRequest::releaseClient(); see AsyncEventSourceResponse::_respond()
   *
   * @param request
   * @param server

Comment thread src/WebRequest.cpp
Comment thread src/AsyncWebSocket.h
Fix double-delete cases when AsyncWebRequest is being transformed in to
another form.   Adoping the AsyncClient from the AsyncWebRequest now
marks it for cleanup internally, so handlers need not delete it
manually.

H/T @mathieucarbou
In cases where we close the connection, ensure we immediately exit to
avoid accessing any member variables after the object has been
destroyed.

H/t @mathieucarbou
Copilot AI review requested due to automatic review settings July 23, 2026 02:17
@willmmiles
willmmiles force-pushed the fix/use-after-free-shared_ptr branch from 9516f3c to 86d74a3 Compare July 23, 2026 02:17

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (3)

src/WebRequest.cpp:1485

  • Same as above: use explicit nullptrs to clear AsyncClient callbacks. Using {} risks overload/initializer-list ambiguity and is inconsistent with existing callback-clearing patterns in the repo.
  c->onTimeout({}, nullptr);
  c->onData({}, nullptr);
  c->onPoll({}, nullptr);

src/AsyncEventSource.h:148

  • The doc comment refers to AsyncWebServerRequest::releaseClient(), but the API is clientRelease(). This makes the public header documentation misleading for users migrating to the new ownership model.
   * @brief Construct a new Async Event Source Client object
   * @note constructor is normally passed a client object from AsyncWebServerRequest::releaseClient(); see AsyncEventSourceResponse::_respond()
   *

src/AsyncWebSocket.h:246

  • Removing the public AsyncWebSocketClient(AsyncWebServerRequest*, AsyncWebSocket*) ctor is an API breaking change for any downstream code that instantiated websocket clients directly. If this constructor was intended to be public, consider keeping a deprecated overload (or clearly documenting the migration path in release notes).
  void *_tempObject;

  AsyncWebSocketClient(AsyncClient *client, AsyncWebSocket *server);
  ~AsyncWebSocketClient();

Comment thread src/WebRequest.cpp
Copilot AI review requested due to automatic review settings July 24, 2026 20:05

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings July 24, 2026 20:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@mathieucarbou

Copy link
Copy Markdown
Member

I tested the PR and I didn't see any issue.
When running too many SSE clients (16) in the perf test ESP crashes because of memory pressure but this is not linked to the changes. And I defaulted the logger to DEBUG.

@mathieucarbou
mathieucarbou merged commit f7fb112 into ESP32Async:main Jul 24, 2026
34 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Status: Pending Merge Type: Bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants