Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions src/WebSockets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
14 changes: 14 additions & 0 deletions src/WebSockets.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
15 changes: 12 additions & 3 deletions src/WebSocketsClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
10 changes: 9 additions & 1 deletion src/WebSocketsServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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) {
Expand Down