Skip to content

Commit 21305e1

Browse files
Merge pull request #450 from ESP32Async/security/advisories/GHSA-8m8p-vhxc-jmjw
fix: NULL pointer dereference in multipart parser (GHSA-8m8p-vhxc-jmjw)
2 parents ef03828 + 4efbd35 commit 21305e1

2 files changed

Lines changed: 34 additions & 0 deletions

File tree

examples/arduino/MultiPart/MultiPart.ino

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,27 @@ curl -v \
8686
--data-binary $'--\r\nContent-Disposition: form-data; name="field"\r\n\r\nhello\r\n----\r\n' \
8787
http://192.168.4.1/upload
8888
89+
11. Boundary string appearing inside file data followed by a non-terminator
90+
byte (GHSA-8m8p-vhxc-jmjw, CWE-476 NULL-pointer dereference / DoS):
91+
92+
Before the fix, the parser freed the upload buffer the moment it matched
93+
"--<boundary>" inside the file body, but left _itemIsFile = true. When
94+
the next byte was neither the closing "--" nor a clean "\r\n", the rewind
95+
logic called itemWriteByte() which dereferenced the now-NULL _itemBuffer
96+
and crashed the device (StoreProhibited on ESP32 → reboot → DoS).
97+
98+
The boundary must appear after a \r\n in the file body so the parser
99+
enters the boundary-matching state machine. The byte after the match
100+
must be neither \r nor - to fall into the rewind branch:
101+
102+
curl -v \
103+
-H 'Content-Type: multipart/form-data; boundary=X' \
104+
--data-binary $'--X\r\nContent-Disposition: form-data; name="file"; filename="f.txt"\r\nContent-Type: text/plain\r\n\r\nhello\r\n--X\tjunk\r\n--X--\r\n' \
105+
http://192.168.4.1/upload
106+
107+
After the fix the server must respond 200 and report the received
108+
parameter(s) without crashing.
109+
89110
── /flash endpoint (ESP32 only) ──────────────────────────────────────────────
90111
91112
Flash firmware and filesystem in one request:

src/WebRequest.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,13 @@ void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data) {
725725
}
726726

727727
void AsyncWebServerRequest::_handleUploadByte(uint8_t data, bool last) {
728+
// CWE-476 defense-in-depth: never write through a NULL buffer pointer.
729+
// The primary fix resets _itemIsFile when the buffer is freed, but this guard
730+
// protects against any future code path that might reach here without one.
731+
if (_itemBuffer == NULL) {
732+
return;
733+
}
734+
728735
_itemBuffer[_itemBufferIndex++] = data;
729736

730737
if (last || _itemBufferIndex == RESPONSE_STREAM_BUFFER_SIZE) {
@@ -917,6 +924,12 @@ void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last) {
917924
});
918925
free(_itemBuffer);
919926
_itemBuffer = NULL;
927+
// CWE-476 fix: _itemIsFile must be cleared when the buffer is freed.
928+
// Otherwise, if the next byte does not confirm a clean boundary close
929+
// (e.g. '--<boundary>' appeared in the file data by coincidence), the
930+
// rewind logic in DASH3_OR_RETURN2 / EXPECT_FEED2 calls itemWriteByte()
931+
// which dereferences the now-NULL _itemBuffer and crashes (DoS).
932+
_itemIsFile = false;
920933
}
921934

922935
} else {

0 commit comments

Comments
 (0)