-
Notifications
You must be signed in to change notification settings - Fork 92
Fix #384: Remove the buffer overflow intended by design allowing users to add a null terminator after the buffer end #385
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -586,7 +586,6 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) { | |
| } | ||
|
|
||
| const size_t datalen = std::min((size_t)(_pinfo.len - _pinfo.index), plen); | ||
| const auto datalast = datalen ? data[datalen] : 0; | ||
|
|
||
| if (_pinfo.masked) { | ||
| for (size_t i = 0; i < datalen; i++) { | ||
|
|
@@ -606,7 +605,31 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) { | |
| } | ||
|
|
||
| if (datalen > 0) { | ||
| _server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, data, datalen); | ||
| // ------------------------------------------------------------ | ||
| // Issue 384: https://github.com/ESP32Async/ESPAsyncWebServer/issues/384 | ||
| // Discussion: https://github.com/ESP32Async/ESPAsyncWebServer/pull/383#discussion_r2760425739 | ||
| // The initial design of the library was doing a backup of the byte following the data buffer because the client code | ||
| // was allowed and documented to do something like data[len] = 0; to facilitate null-terminated string handling. | ||
| // 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. | ||
| // 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. | ||
| // ------------------------------------------------------------ | ||
| uint8_t *copy = (uint8_t *)malloc(datalen + 1); | ||
|
|
||
| if (copy == NULL) { | ||
| async_ws_log_e("Failed to allocate"); | ||
| _status = WS_DISCONNECTED; | ||
| if (_client) { | ||
| _client->abort(); | ||
| } | ||
| return; | ||
| } | ||
|
|
||
| memcpy(copy, data, datalen); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note: I decided to always do the copy regardless of the frame type (WS_TEXT, WS_BINARY, WS_CONTINUATION) because only the first frame holds the info WS_TEXT, WS_BINARY. Others are continuation frame. So this is more correct by the spec since when in next frames we do not have the information about the message type, but we could decide to optimize and only do the copy when we encounter a WS_TEXT frame, because a frame max length is 2^63 so this is not likely that we would encounter continuation frames on a MCU... I am opened to both solutions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Strictly speaking, we only need to make a copy if the message is at the end of the pbuf; for messages that don't consume the entire packet, we could pre-read or null out and restore the last byte, which would be faster.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right but we don't know that right ? We only have the pointer to the buffer and the quantity that can be read. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At this point in the processing loop, we do know: if
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @willmmiles I will try this optimization. when the content of data frame is handled, we have: datalen will basically be always equal to plen, except if the network buffer contains several fragments of frames inside, in this case it will be less. So it would be in the cases where browsers are sending several ws messages within the same paquet without waiting for a ack... Let me add a commit in #388 and test it. |
||
| copy[datalen] = 0; | ||
|
|
||
| _server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, copy, datalen); | ||
|
|
||
| free(copy); | ||
mathieucarbou marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| // track index for next fragment | ||
|
|
@@ -652,12 +675,37 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) { | |
|
|
||
| } else if (_pinfo.opcode < WS_DISCONNECT) { // continuation or text/binary frame | ||
| async_ws_log_v("WS[%" PRIu32 "]: processing data frame num=%" PRIu32 "", _clientId, _pinfo.num); | ||
| _server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, data, datalen); | ||
|
|
||
| // ------------------------------------------------------------ | ||
| // Issue 384: https://github.com/ESP32Async/ESPAsyncWebServer/issues/384 | ||
| // Discussion: https://github.com/ESP32Async/ESPAsyncWebServer/pull/383#discussion_r2760425739 | ||
| // The initial design of the library was doing a backup of the byte following the data buffer because the client code | ||
| // was allowed and documented to do something like data[len] = 0; to facilitate null-terminated string handling. | ||
| // 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. | ||
| // 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. | ||
| // ------------------------------------------------------------ | ||
| uint8_t *copy = (uint8_t *)malloc(datalen + 1); | ||
|
|
||
| if (copy == NULL) { | ||
| async_ws_log_e("Failed to allocate"); | ||
| _status = WS_DISCONNECTED; | ||
| if (_client) { | ||
| _client->abort(); | ||
| } | ||
| return; | ||
| } | ||
|
|
||
| memcpy(copy, data, datalen); | ||
| copy[datalen] = 0; | ||
|
|
||
| _server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, copy, datalen); | ||
| if (_pinfo.final) { | ||
| _pinfo.num = 0; | ||
| } else { | ||
| _pinfo.num += 1; | ||
| } | ||
|
|
||
| free(copy); | ||
mathieucarbou marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| } else { | ||
|
|
@@ -674,11 +722,6 @@ void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) { | |
| break; | ||
| } | ||
|
|
||
| // restore byte as _handleEvent may have added a null terminator i.e., data[len] = 0; | ||
| if (datalen) { | ||
| data[datalen] = datalast; | ||
| } | ||
|
|
||
| data += datalen; | ||
| plen -= datalen; | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.