From 3fa6eb579837f25b1131f7df7b7daa1479be2f12 Mon Sep 17 00:00:00 2001 From: omaidnebari Date: Fri, 5 Dec 2025 14:25:56 -0800 Subject: [PATCH] Memory fixes --- src/WebSockets.cpp | 63 ++++++++++++++++++++++++++++++++++++++++ src/WebSockets.h | 14 +++++++++ src/WebSocketsClient.cpp | 15 ++++++++-- src/WebSocketsServer.cpp | 10 ++++++- 4 files changed, 98 insertions(+), 4 deletions(-) diff --git a/src/WebSockets.cpp b/src/WebSockets.cpp index 9b9ca8f2..394da6dc 100644 --- a/src/WebSockets.cpp +++ b/src/WebSockets.cpp @@ -760,3 +760,66 @@ void WebSockets::handleHBTimeout(WSclient_t * client) { } } } + +/** + * Read a line from TCP stream with maximum length limit. + * Security fix: Prevents unbounded heap allocation from malicious peers + * sending continuous data without newlines (CVE prevention). + * @param client WSclient_t * ptr to the client struct + * @param maxLen Maximum characters to read before truncating + * @return String containing the line (without newline), empty if disconnected + */ +String WebSockets::readLineWithLimit(WSclient_t * client, size_t maxLen) { + String line; + if(!client->tcp || !client->tcp->connected()) { + return line; + } + + line.reserve(std::min(maxLen, (size_t)128)); // Pre-allocate reasonable initial size + + size_t count = 0; + unsigned long timeout = millis(); + + while(client->tcp->connected() && count < maxLen) { + if((millis() - timeout) > WEBSOCKETS_TCP_TIMEOUT) { + DEBUG_WEBSOCKETS("[readLineWithLimit] TIMEOUT!\n"); + break; + } + + if(!client->tcp->available()) { + WEBSOCKETS_YIELD_MORE(); + continue; + } + + int c = client->tcp->read(); + if(c < 0) { + break; + } + + timeout = millis(); // Reset timeout on successful read + + if(c == '\n') { + break; + } + if(c != '\r') { + line += (char)c; + count++; + } + } + + // If we hit maxLen, consume remaining bytes until newline to stay in sync + if(count >= maxLen) { + DEBUG_WEBSOCKETS("[readLineWithLimit] line exceeded maxLen (%zu), truncating\n", maxLen); + while(client->tcp->connected() && client->tcp->available()) { + int c = client->tcp->read(); + if(c < 0 || c == '\n') { + break; + } + if((millis() - timeout) > WEBSOCKETS_TCP_TIMEOUT) { + break; + } + } + } + + return line; +} \ No newline at end of file diff --git a/src/WebSockets.h b/src/WebSockets.h index 51ee3efe..12819625 100644 --- a/src/WebSockets.h +++ b/src/WebSockets.h @@ -140,6 +140,11 @@ // max size of the WS Message Header #define WEBSOCKETS_MAX_HEADER_SIZE (14) +// Maximum HTTP header line length (security: prevents unbounded heap allocation) +#ifndef WEBSOCKETS_MAX_HEADER_LINE_LENGTH +#define WEBSOCKETS_MAX_HEADER_LINE_LENGTH (1024) +#endif + #if !defined(WEBSOCKETS_NETWORK_TYPE) // select Network type based #if defined(ESP8266) || defined(ESP31B) @@ -455,6 +460,15 @@ class WebSockets { void enableHeartbeat(WSclient_t * client, uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount); void handleHBTimeout(WSclient_t * client); + + /** + * Read a line from TCP stream with maximum length limit. + * Security fix: Prevents unbounded heap allocation from malicious peers. + * @param client WSclient_t * ptr to the client struct + * @param maxLen Maximum characters to read before truncating + * @return String containing the line (without newline) + */ + String readLineWithLimit(WSclient_t * client, size_t maxLen = WEBSOCKETS_MAX_HEADER_LINE_LENGTH); }; #ifndef UNUSED diff --git a/src/WebSocketsClient.cpp b/src/WebSocketsClient.cpp index 1edb8146..0091795d 100644 --- a/src/WebSocketsClient.cpp +++ b/src/WebSocketsClient.cpp @@ -639,12 +639,14 @@ void WebSocketsClient::handleClientData(void) { if(len > 0) { switch(_client.status) { case WSC_HEADER: { - String headerLine = _client.tcp->readStringUntil('\n'); + // Security fix: Use length-limited read to prevent unbounded heap allocation + String headerLine = readLineWithLimit(&_client); handleHeader(&_client, &headerLine); } break; case WSC_BODY: { - char buf[256] = { 0 }; - _client.tcp->readBytes(&buf[0], std::min((size_t)len, sizeof(buf))); + // Security: buf[256] is always '\0' due to zero-init, preventing strlen overread + char buf[257] = { 0 }; + _client.tcp->readBytes(&buf[0], std::min((size_t)len, (size_t)256)); String bodyLine = buf; handleHeader(&_client, &bodyLine); } break; @@ -757,6 +759,13 @@ void WebSocketsClient::sendHeader(WSclient_t * client) { * @param client WSclient_t * ptr to the client struct */ void WebSocketsClient::handleHeader(WSclient_t * client, String * headerLine) { + // Security: Reject excessively long header lines (defense-in-depth for async paths) + if(headerLine->length() > WEBSOCKETS_MAX_HEADER_LINE_LENGTH) { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] header line too long, disconnecting\n"); + clientDisconnect(client, "Header line too long"); + return; + } + headerLine->trim(); // remove \r // this code handels the http body for Socket.IO V3 requests diff --git a/src/WebSocketsServer.cpp b/src/WebSocketsServer.cpp index 9d64c3a3..27ff4b89 100644 --- a/src/WebSocketsServer.cpp +++ b/src/WebSocketsServer.cpp @@ -702,7 +702,8 @@ void WebSocketsServerCore::handleClientData(void) { // DEBUG_WEBSOCKETS("[WS-Server][%d][handleClientData] len: %d\n", client->num, len); switch(client->status) { case WSC_HEADER: { - String headerLine = client->tcp->readStringUntil('\n'); + // Security fix: Use length-limited read to prevent unbounded heap allocation + String headerLine = readLineWithLimit(client); handleHeader(client, &headerLine); } break; case WSC_CONNECTED: @@ -743,6 +744,13 @@ bool WebSocketsServerCore::hasMandatoryHeader(String headerName) { void WebSocketsServerCore::handleHeader(WSclient_t * client, String * headerLine) { static const char * NEW_LINE = "\r\n"; + // Security: Reject excessively long header lines (defense-in-depth for async paths) + if(headerLine->length() > WEBSOCKETS_MAX_HEADER_LINE_LENGTH) { + DEBUG_WEBSOCKETS("[WS-Server][%d][handleHeader] header line too long, disconnecting\n", client->num); + clientDisconnect(client); + return; + } + headerLine->trim(); // remove \r if(headerLine->length() > 0) {