Skip to content

Commit d015d5a

Browse files
committed
Fix #384: Remove the buffer overflow intended by design allowing users to add a null terminator after the buffer end
1 parent f2b8a99 commit d015d5a

File tree

4 files changed

+55
-16
lines changed

4 files changed

+55
-16
lines changed

examples/WebSocket/WebSocket.ino

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,11 @@ void setup() {
7676
// Run in terminal 2: websocat ws://192.168.4.1/ws => should stream data
7777
// Run in terminal 3: websocat ws://192.168.4.1/ws => should fail:
7878
//
79-
// To send a message to the WebSocket server:
79+
// To send a message to the WebSocket server (\n at the end):
80+
// > echo "Hello!" | websocat ws://192.168.4.1/ws
8081
//
81-
// echo "Hello!" | websocat ws://192.168.4.1/ws
82+
// Generates 2001 characters (\n at the end) to cause a fragmentation (over TCP MSS):
83+
// > openssl rand -hex 1000 | websocat ws://192.168.4.1/ws
8284
//
8385
ws.onEvent([](AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
8486
(void)len;
@@ -108,7 +110,6 @@ void setup() {
108110
// complete frame
109111
if (info->final && info->index == 0 && info->len == len) {
110112
if (info->opcode == WS_TEXT) {
111-
data[len] = 0;
112113
Serial.printf("ws text: %s\n", (char *)data);
113114
client->ping();
114115
}
@@ -130,7 +131,6 @@ void setup() {
130131
);
131132

132133
if (info->message_opcode == WS_TEXT) {
133-
data[len] = 0;
134134
Serial.printf("%s\n", (char *)data);
135135
} else {
136136
for (size_t i = 0; i < len; i++) {

idf_component_examples/websocket/main/main.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ void setup() {
5555
String msg = "";
5656
if (info->final && info->index == 0 && info->len == len) {
5757
if (info->opcode == WS_TEXT) {
58-
data[len] = 0;
5958
Serial.printf("ws text: %s\n", (char *)data);
6059
}
6160
}

src/AsyncWebSocket.cpp

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,6 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) {
586586
}
587587

588588
const size_t datalen = std::min((size_t)(_pinfo.len - _pinfo.index), plen);
589-
const auto datalast = datalen ? data[datalen] : 0;
590589

591590
if (_pinfo.masked) {
592591
for (size_t i = 0; i < datalen; i++) {
@@ -606,7 +605,31 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) {
606605
}
607606

608607
if (datalen > 0) {
609-
_server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, data, datalen);
608+
// ------------------------------------------------------------
609+
// Issue 384: https://github.com/ESP32Async/ESPAsyncWebServer/issues/384
610+
// Discussion: https://github.com/ESP32Async/ESPAsyncWebServer/pull/383#discussion_r2760425739
611+
// The initial design of the library was doing a backup of the byte following the data buffer because the client code
612+
// was allowed and documented to do something like data[len] = 0; to facilitate null-terminated string handling.
613+
// This was a bit hacky but it was working and it was documented, although completely incorrect because it was modifying a byte outside of the data buffer.
614+
// So to fix this behavior and to avoid breaking existing client code that may be relying on this behavior, we now have to copy the data to a temporary buffer that has an extra byte for the null terminator.
615+
// ------------------------------------------------------------
616+
uint8_t *copy = (uint8_t *)malloc(datalen + 1);
617+
618+
if (copy == NULL) {
619+
async_ws_log_e("Failed to allocate");
620+
_status = WS_DISCONNECTED;
621+
if (_client) {
622+
_client->abort();
623+
}
624+
return;
625+
}
626+
627+
memcpy(copy, data, datalen);
628+
copy[datalen] = 0;
629+
630+
_server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, copy, datalen);
631+
632+
free(copy);
610633
}
611634

612635
// track index for next fragment
@@ -652,12 +675,37 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) {
652675

653676
} else if (_pinfo.opcode < WS_DISCONNECT) { // continuation or text/binary frame
654677
async_ws_log_v("WS[%" PRIu32 "]: processing data frame num=%" PRIu32 "", _clientId, _pinfo.num);
655-
_server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, data, datalen);
678+
679+
// ------------------------------------------------------------
680+
// Issue 384: https://github.com/ESP32Async/ESPAsyncWebServer/issues/384
681+
// Discussion: https://github.com/ESP32Async/ESPAsyncWebServer/pull/383#discussion_r2760425739
682+
// The initial design of the library was doing a backup of the byte following the data buffer because the client code
683+
// was allowed and documented to do something like data[len] = 0; to facilitate null-terminated string handling.
684+
// This was a bit hacky but it was working and it was documented, although completely incorrect because it was modifying a byte outside of the data buffer.
685+
// So to fix this behavior and to avoid breaking existing client code that may be relying on this behavior, we now have to copy the data to a temporary buffer that has an extra byte for the null terminator.
686+
// ------------------------------------------------------------
687+
uint8_t *copy = (uint8_t *)malloc(datalen + 1);
688+
689+
if (copy == NULL) {
690+
async_ws_log_e("Failed to allocate");
691+
_status = WS_DISCONNECTED;
692+
if (_client) {
693+
_client->abort();
694+
}
695+
return;
696+
}
697+
698+
memcpy(copy, data, datalen);
699+
copy[datalen] = 0;
700+
701+
_server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, copy, datalen);
656702
if (_pinfo.final) {
657703
_pinfo.num = 0;
658704
} else {
659705
_pinfo.num += 1;
660706
}
707+
708+
free(copy);
661709
}
662710

663711
} else {
@@ -674,11 +722,6 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) {
674722
break;
675723
}
676724

677-
// restore byte as _handleEvent may have added a null terminator i.e., data[len] = 0;
678-
if (datalen) {
679-
data[datalen] = datalast;
680-
}
681-
682725
data += datalen;
683726
plen -= datalen;
684727
}

src/AsyncWebSocket.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -554,9 +554,6 @@ class AsyncWebSocketMessageHandler {
554554
}
555555
} else if (type == WS_EVT_DATA) {
556556
AwsFrameInfo *info = (AwsFrameInfo *)arg;
557-
if (info->opcode == WS_TEXT) {
558-
data[len] = 0;
559-
}
560557
if (info->final && info->index == 0 && info->len == len) {
561558
if (_onMessage) {
562559
_onMessage(server, client, data, len);

0 commit comments

Comments
 (0)