Conversation
When a single TCP delivery ends on exactly one byte of the next frame's header, the STATE_FRAME_START branch reads fdata[1] out of bounds and computes plen -= 2 on plen == 1, underflowing size_t to SIZE_MAX; the parser then walks wild memory until the device panics. The v3.12 websocket refactor (ESP32Async#462) added careful torn-frame handling for the mask bytes and the payload, but the base 2-byte header consume is still only guarded by 'while (plen > 0)'. Drop the un-parseable tail instead of crashing: the connection desynchronizes and is closed by the client, the same trade-off the adjacent extended-length guards already make.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When a single TCP delivery ends on exactly one byte of the next frame's 2-byte header, the
STATE_FRAME_STARTbranch ofAsyncWebSocketClient::_onData():fdata[1]out of bounds (only 1 byte is valid), anddata += 2; plen -= 2;onplen == 1, underflowingsize_ttoSIZE_MAX,after which the parser walks wild memory until the device panics.
The v3.12 websocket refactor (#462) added careful torn-frame handling for the mask bytes (
STATE_FRAME_MASK) and the payload, but the base 2-byte header consume is still only guarded bywhile (plen > 0). This PR adds the missing guard.Reproduction
Naturally occurs under retransmission/load (a segment happens to split at a frame boundary). Deterministic trigger with a raw-socket client that writes one header byte, flushes, waits, then writes the rest:
Evidence
Load access faultpanic atAsyncWebSocket.cpp:538, 3/3 runs under loadGuru Meditation Error: Core 0 panic'ed (Store access fault)-> reboot; occasionally the accept path stalls for seconds afterwardsTested on ESP32-C3 (arduino-esp32 3.2.1 / IDF 5.4, AsyncTCP 3.5.0); plain WS echo scenario, no TLS.
The fix
Dropping the un-parseable tail makes the connection desynchronize and get closed by the client - the same trade-off the adjacent
plen >= 2/plen >= 8/plen >= 4extended-length and mask guards already make.A larger alternative would accumulate the base header across deliveries with a new
STATE_FRAME_HEADERstate, mirroring theSTATE_FRAME_MASKmachinery - happy to rework in that direction if you prefer it. The minimal guard is offered first because it is trivially reviewable and the desync outcome is already how the other partial-header paths behave.Testing