Repository navigation
Fix request processing failures caused by failed allocations - #501
mathieucarbou wants to merge 3 commits into
Conversation
Abort the request instead of crashing, corrupting the wire format or silently mis-parsing it when a heap allocation fails. Arduino String leaves a failed string empty, and its c_str() is null on ESP32. Sites that copied header or body data and then passed c_str() to C string functions could crash (issue #500), and sites that stored the copy in request state silently lost or corrupted data. Header parsing: - Add AsyncWebHeader::tryParse() reporting ParseStatus::NoMemory so a failed name/value copy fails the request instead of dropping the header (a lost Content-Length desynchronizes the body parser). - Scan Accept and Transfer-Encoding in place: the lowercased copies are gone, so issue #500 cannot happen and chunked framing is no longer silently missed. - Abort on failed copies into _host, _contentType, _authorization and on failed header-line/body accumulation in the plain and multipart parsers. - Write the 100-continue reply from the literal: the String copy exceeded the small-buffer size and an OOM one silently stalled the client. Responses: - Check every concat() in _assembleHead(); callers now close the connection instead of writing a truncated head (basic, abstract, WebSocket and SSE responses). - AsyncWebSocketResponse flags RESPONSE_FAILED when it cannot build Sec-WebSocket-Accept, and _sourceValid() reflects it, so the upgrade is refused rather than completing with a garbage accept header. Allocation helpers: - Use std::nothrow for response objects created by beginResponse*(), AsyncStaticWebHandler and AsyncEventSource so failures are observable instead of aborting the device; send()/_send() handle a null response. - Replace plain new with malloc in generateBasicHash() and the WebSocket printf helpers, where the null checks were dead code. Static file handler: - Stop and fall through to 404 when the path cannot be built, rather than serving the default or a wrong file. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
@willmmiles : this is a starting point, not fully tested yet. |
Keep the code self-explanatory: the allocation change does not need an inline comment in each of the call sites. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| } | ||
|
|
||
| char *buffer = new char[len + 1]; | ||
| char *buffer = (char *)malloc(len + 1); |
There was a problem hiding this comment.
I think we should be consistent with new (std::nothrow) char[len+1] instead of malloc(). We may want to remind @copilot that this is a C++ project, not C. Removing C++ allocators to use C style is a step backwards IMO.
Better still would be upgrading to smart pointers.
There was a problem hiding this comment.
Done. The four WebSocket printf/printf_P helpers and generateBasicHash() now use std::unique_ptr<char[]> with new (std::nothrow) char[...], and the manual free() calls are gone. I couldn't build it here because pio isn't installed in this environment, so the change is not compiled or tested yet. I left the other malloc sites in WebSocket.cpp, WebHandlers.cpp and WebRequest.cpp alone because I don't think this PR introduced them. I can convert those too if you want.
There was a problem hiding this comment.
Sorry, I wasn't asking you to make immediate changes to the code - the bigger problem is that you replaced new with malloc() in the first place. Why would you choose to replace C++ with C constructs, when we've been slowly migrating the code the other way? How do we prevent you from making mistakes like that in the future?
…nd generateBasicHash Co-authored-by: willmmiles <6540455+willmmiles@users.noreply.github.com>
|
Apart from If we want to support handling allocation failure on platforms with |
That’s true, but maybe we can do it in 2 pass ? |
Summary
Fixes #500 and the other places where request processing broke when a heap allocation failed.
An Arduino
Stringwhose allocation fails is left empty, and itsc_str()returns NULL on ESP32. Depending on where that happened, the library crashed, silently mis-parsed the request, corrupted the HTTP wire format, or served the wrong file.Why
Issue #500 reported a crash in
_parseReqHeader(): theAcceptvalue was copied into a newString, the copy failed under heap pressure, andstrstr(NULL, ...)hard-faulted in theasync_tcptask. The same pattern existed in several other places, with different (but equally wrong) outcomes.What changed
Header parsing (
WebRequest.cpp,AsyncWebHeader.cpp)AsyncWebHeader::tryParse()returnsParseStatus::{Ok,Invalid,NoMemory}so a failed name/value copy fails the request rather than silently dropping the header. A droppedContent-LengthorTransfer-Encodingdesynchronizes the body parser.AcceptandTransfer-Encodingare now scanned in place — the lowercased copies are gone entirely, so the Null dereference in_parseReqHeader()when theAcceptheader copy fails to allocate (crash instead of rejecting the request) #500 crash cannot occur and chunked framing can no longer be silently missed._host,_contentTypeand_authorizationabort the request instead of handing handlers corrupt state._onData()and failed byte accumulation in the plain/multipart body parsers abort instead of truncating data.100 Continuereply is written straight from the literal (theStringcopy exceeded the small-buffer size, and an OOM one silently stalled the client).Responses (
WebResponses.cpp,AsyncWebSocket.cpp,AsyncEventSource.cpp)concat()in_assembleHead()is checked; on OOM the buffer is emptied and all four_respond()callers close the connection instead of writing a truncated head.AsyncWebSocketResponsesetsRESPONSE_FAILEDwhen it cannot buildSec-WebSocket-Accept, and_sourceValid()reflects it, so the upgrade is refused instead of completing with a garbage accept header.Allocation helpers
beginResponse*(),AsyncStaticWebHandlerandAsyncEventSourceusestd::nothrow, making their existing null checks live instead of unreachable (plainnewaborts the device on OOM).send()/_send()now handle a null response.generateBasicHash()and the WebSocketprintfhelpers usemallocwhere the null checks were dead code.Static file handler (
WebHandlers.cpp)canHandle()returns false so the request falls through to another handler / 404 instead of serving the default or a wrong file.Known limitation (not addressed here)
std::list::emplace_back(headers, params),std::vector,std::make_shared(WebSocket message buffers) and thecbufallocated insideAsyncResponseStreamstill use throwing-new, so an OOM there aborts the device. Fixing that needs custom allocators/containers and is out of scope for this change.Validation
arduino-3,esp8266,raspberrypi,libretiny,arduino-2-esp-idf-log(core 2.x),no-json,regex,no-chunk-inflight,latest-asynctcp— all pass.arduino-2env itself fails before compiling any source because PlatformIO no longer serves the pinnedtool-esptoolpy@^5.3.0; this happens on unmodifiedmaintoo. Thearduino-2-esp-idf-logenv covers the same core.cpplintwith the CI filter set: 0 errors.codespell: clean. Formatted with the pinnedclang-format18.1.3.Opened as a draft for review.