-
Notifications
You must be signed in to change notification settings - Fork 39
fix Invalid JSON desearilaztion beyond buffer end #184
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
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR addresses issue #183 by correcting JSON deserialization beyond the buffer end.
- Updated deserializeJson call for ArduinoJson v6 and non-v6 implementations, passing request->contentLength() as an additional boundary argument.
src/AsyncJson.cpp
Outdated
@@ -125,12 +125,12 @@ void AsyncCallbackJsonWebHandler::handleRequest(AsyncWebServerRequest *request) | |||
if (json.success()) { | |||
#elif ARDUINOJSON_VERSION_MAJOR == 6 | |||
DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize); | |||
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject)); | |||
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject), request->contentLength()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding an inline comment here to explain that the request->contentLength() parameter is used to restrict the deserialization to the valid buffer size, addressing issue #183.
Copilot uses AI. Check for mistakes.
src/AsyncJson.cpp
Outdated
if (!error) { | ||
JsonVariant json = jsonBuffer.as<JsonVariant>(); | ||
#else | ||
JsonDocument jsonBuffer; | ||
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject)); | ||
DeserializationError error = deserializeJson(jsonBuffer, (uint8_t *)(request->_tempObject), request->contentLength()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding a similar inline comment here to clarify the purpose of passing request->contentLength() for preventing invalid JSON read beyond the allocated buffer.
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fix is not as simple as relying on request->contentLength());
.
What is happening if you run:
curl -v -X POST -H 'Content-Type: application/json' -d '5' -H 'Content-Length: 3' http://127.0.0.1:8080/json2
If you send 1 byte but specify in headers that you are sending 3 then the server will keep on waiting for 2 more bytes. Eventually it will timeout and This is what curl will say:
An edge case in the other direction where you send 5 bytes but claim 3 in the headers will result in 2 bytes being ignored. curl:
where ESP will say
So 555 and not 55555. Expected. So it seems that it works however you have a point. There are two different |
Actually, after further inspection, I take it back. |
I agree! |
Do you want to see how this can be improved ? |
yes, I will update the PR. |
@@ -261,6 +261,7 @@ class AsyncWebServerRequest { | |||
public: | |||
File _tempFile; | |||
void *_tempObject; | |||
size_t _tempSize; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be better to have a struct with :
- size_t len
- uint8_t* buffer (type TBD)
that you would put inside the _tempObject instead of adding a new field for all requests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be better to have a struct with :
* size_t len * uint8_t* buffer (type TBD)
Yes, it is possible. A struct with a buffer at the end, so it can be variable size.
size_t length
uint8_t buffer[1]
} | ||
if (request->_tempObject != NULL) { | ||
memcpy((uint8_t *)(request->_tempObject) + index, data, len); | ||
// check if the buffer is the right size so we don't write out of bounds | ||
if (request->_tempSize >= total) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not understand this check: handleBody can be called several times, but _tempSize and total do not change
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not understand this check: handleBody can be called several times, but _tempSize and total do not change
I wasn't sure if total
won't change from one call to handleBody
to the other. In my tests it was the same but I wanted to take precautions.
Can I assume it will be the same all the time? If that is the case then we don't need _tempSize
at all.
tempSize
represents the max number of bytes we can store in the buffer. So once it is allocated it doesn't change.
} else if (request->_tempObject != NULL) { | ||
} | ||
// this is not a GET | ||
// check if body is too large, if it is, don't parse |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wrong: it is already parsed: handleBody is called before handleRequest.
(but handleBody will have done nothing)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant JSON parsing. Should replace "parse" with "de-serialize" here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR looks better! Just a few things to fix.
If you want to go further in code cleanup, like using the index instead of the tmpObject to make it a little more readable, go ahead!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR addresses an issue with invalid JSON deserialization beyond the buffer end by improving buffer allocation and JSON parsing logic. Key changes include:
- Adding a new _tempSize member to track allocated buffer size.
- Removing the unused _contentLength variable in the JSON handler.
- Adjusting JSON parsing logic to ensure proper null termination and buffer bounds checking.
Reviewed Changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
File | Description |
---|---|
src/WebRequest.cpp | Added _tempSize to the constructor initializer list. |
src/ESPAsyncWebServer.h | Declared _tempSize member to support buffer size tracking. |
src/AsyncJson.h | Removed the unused _contentLength member. |
src/AsyncJson.cpp | Modified JSON parsing and buffer handling logic for safety. |
Comments suppressed due to low confidence (1)
src/AsyncJson.cpp:174
- Since the buffer is allocated with an extra byte (i.e., malloc(total + 1)), consider renaming or updating _tempSize to reflect the actual allocated size to avoid potential off-by-one misunderstandings in future code changes.
request->_tempSize = total; // store the size of allocation we made into _tempSize
min(request->contentLength(), request->_tempSize); // smaller value of contentLength or the size of the buffer. normally those should match. | ||
#if ARDUINOJSON_VERSION_MAJOR == 5 | ||
DynamicJsonBuffer jsonBuffer; | ||
JsonVariant json = jsonBuffer.parse((uint8_t *)(request->_tempObject)); | ||
uint8_t *p = (uint8_t *)(request->_tempObject); | ||
p[dataSize] = '\0'; // null terminate, assume we allocated one extra char |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The null termination here relies on the assumption that the allocated buffer has an extra byte, but _tempSize is set to the original total. To improve clarity, consider documenting or renaming _tempSize so it explicitly reflects the buffer's capacity (i.e., total bytes allocated minus one) to avoid confusion in future modifications.
Copilot uses AI. Check for mistakes.
What is "the index"? How will it replace |
fix for #183