-
Notifications
You must be signed in to change notification settings - Fork 395
Harden API and WebSocket receive paths #1845
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: master
Are you sure you want to change the base?
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 |
|---|---|---|
|
|
@@ -74,6 +74,30 @@ static int system_statistics_prebuffer_len = 256; | |
| static int system_wifi_scan_prebuffer_len = 256; | ||
| static int api_common_prebuffer_len = 256; | ||
|
|
||
| esp_err_t HTTP_receive_body(httpd_req_t *req, char *buffer, size_t buffer_size) | ||
| { | ||
| if (req == NULL || buffer == NULL || buffer_size == 0) { | ||
| return ESP_ERR_INVALID_ARG; | ||
| } | ||
|
|
||
| const size_t content_len = req->content_len; | ||
| if (content_len == 0 || content_len >= buffer_size) { | ||
| return ESP_ERR_INVALID_SIZE; | ||
| } | ||
|
|
||
| size_t received_len = 0; | ||
| while (received_len < content_len) { | ||
| int received = httpd_req_recv(req, buffer + received_len, content_len - received_len); | ||
| if (received <= 0) { | ||
| return ESP_FAIL; | ||
| } | ||
| received_len += (size_t)received; | ||
| } | ||
|
|
||
| buffer[received_len] = '\0'; | ||
| return ESP_OK; | ||
| } | ||
|
|
||
| typedef enum | ||
| { | ||
| SRC_HASHRATE, | ||
|
|
@@ -1043,25 +1067,16 @@ static esp_err_t PATCH_update_settings(httpd_req_t * req) | |
| return ESP_OK; | ||
| } | ||
|
|
||
| int total_len = req->content_len; | ||
| int cur_len = 0; | ||
| char * buf = ((rest_server_context_t *) (req->user_ctx))->scratch; | ||
| int received = 0; | ||
| if (total_len >= SCRATCH_BUFSIZE) { | ||
| /* Respond with 500 Internal Server Error */ | ||
| httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "content too long"); | ||
| return ESP_OK; | ||
| esp_err_t receive_result = HTTP_receive_body(req, buf, SCRATCH_BUFSIZE); | ||
|
Collaborator
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. Medium: HTTP_receive_body now supplies a correctly terminated buffer, but the handlers pass it to cJSON_Parse, which accepts a valid first JSON value followed by arbitrary non-whitespace bytes. Settings, pool, boot, and theme requests can therefore accept malformed documents such as
Collaborator
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. Proposed patch. It applies to the exact reviewed head; it was compile-validated in the full ESP-IDF 5.5.3 firmware build and included in the combined validation: diff --git a/main/http_server/http_server.c b/main/http_server/http_server.c
index a789d35..7ca78f4 100644
--- a/main/http_server/http_server.c
+++ b/main/http_server/http_server.c
@@ -1078,7 +1078,7 @@ static esp_err_t PATCH_update_settings(httpd_req_t * req)
return ESP_FAIL;
}
- cJSON * root = cJSON_Parse(buf);
+ cJSON * root = cJSON_ParseWithOpts(buf, NULL, true);
if (root == NULL) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON");
return ESP_OK;
@@ -1272,7 +1272,7 @@ static esp_err_t PUT_system_pool(httpd_req_t *req)
return ESP_FAIL;
}
- cJSON *root = cJSON_Parse(buf);
+ cJSON *root = cJSON_ParseWithOpts(buf, NULL, true);
if (!root) {
return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON");
}
@@ -1453,7 +1453,7 @@ static esp_err_t POST_system_boot(httpd_req_t *req)
return ESP_FAIL;
}
- cJSON *root = cJSON_Parse(buf);
+ cJSON *root = cJSON_ParseWithOpts(buf, NULL, true);
if (!root) {
return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON");
}
diff --git a/main/http_server/theme_api.c b/main/http_server/theme_api.c
index 7878ee8..f2d0b0d 100644
--- a/main/http_server/theme_api.c
+++ b/main/http_server/theme_api.c
@@ -57,7 +57,7 @@ static esp_err_t theme_post_handler(httpd_req_t *req)
return ESP_FAIL;
}
- cJSON *root = cJSON_Parse(content);
+ cJSON *root = cJSON_ParseWithOpts(content, NULL, true);
if (!root) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON");
return ESP_FAIL;
|
||
| if (receive_result == ESP_ERR_INVALID_SIZE) { | ||
| httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid request length"); | ||
| return ESP_FAIL; | ||
| } | ||
| while (cur_len < total_len) { | ||
| received = httpd_req_recv(req, buf + cur_len, total_len); | ||
| if (received <= 0) { | ||
| /* Respond with 500 Internal Server Error */ | ||
| httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to post control value"); | ||
| return ESP_OK; | ||
| } | ||
| cur_len += received; | ||
| if (receive_result != ESP_OK) { | ||
| httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to receive request data"); | ||
| return ESP_FAIL; | ||
| } | ||
| buf[total_len] = '\0'; | ||
|
|
||
| cJSON * root = cJSON_Parse(buf); | ||
| if (root == NULL) { | ||
|
|
@@ -1246,17 +1261,16 @@ static esp_err_t PUT_system_pool(httpd_req_t *req) | |
| return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid pool index"); | ||
| } | ||
|
|
||
| int total_len = req->content_len; | ||
| if (total_len <= 0 || total_len >= SCRATCH_BUFSIZE) { | ||
| return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid request length"); | ||
| } | ||
|
|
||
| char *buf = ((rest_server_context_t *)(req->user_ctx))->scratch; | ||
| int received = httpd_req_recv(req, buf, total_len); | ||
| if (received <= 0) { | ||
| return httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to receive request data"); | ||
| esp_err_t receive_result = HTTP_receive_body(req, buf, SCRATCH_BUFSIZE); | ||
| if (receive_result == ESP_ERR_INVALID_SIZE) { | ||
| httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid request length"); | ||
| return ESP_FAIL; | ||
| } | ||
| if (receive_result != ESP_OK) { | ||
| httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to receive request data"); | ||
| return ESP_FAIL; | ||
| } | ||
| buf[received] = '\0'; | ||
|
|
||
| cJSON *root = cJSON_Parse(buf); | ||
| if (!root) { | ||
|
|
@@ -1428,25 +1442,18 @@ static esp_err_t POST_system_boot(httpd_req_t *req) | |
| return ESP_OK; | ||
| } | ||
|
|
||
| size_t total_len = req->content_len; | ||
| if (total_len == 0) { | ||
| return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Empty request body"); | ||
| } | ||
|
|
||
| char *buf = malloc(total_len + 1); | ||
| if (!buf) { | ||
| return httpd_resp_send_500(req); | ||
| char *buf = ((rest_server_context_t *)(req->user_ctx))->scratch; | ||
| esp_err_t receive_result = HTTP_receive_body(req, buf, SCRATCH_BUFSIZE); | ||
| if (receive_result == ESP_ERR_INVALID_SIZE) { | ||
| httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid request length"); | ||
| return ESP_FAIL; | ||
| } | ||
|
|
||
| int ret = httpd_req_recv(req, buf, total_len); | ||
| if (ret <= 0) { | ||
| free(buf); | ||
| return httpd_resp_send_500(req); | ||
| if (receive_result != ESP_OK) { | ||
| httpd_resp_send_500(req); | ||
| return ESP_FAIL; | ||
| } | ||
| buf[ret] = '\0'; | ||
|
|
||
| cJSON *root = cJSON_Parse(buf); | ||
| free(buf); | ||
| if (!root) { | ||
| return httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON"); | ||
| } | ||
|
|
||
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.
Coverage: the new shared body reader and WebSocket cap have no automated tests. In particular, the exact buffer boundary, fragmented positive reads, zero/negative/over-read failures, and 1024/1025-byte WebSocket boundary are unprotected. The proposal reply extracts the decisions into a small production-used api_rx policy component and covers all of those branches in QEMU.
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.
Proposed coverage patch (apply after the strict-JSON patch above). It passes 77/77 ESP32-S3 QEMU tests and a fresh full ESP-IDF 5.5.3 firmware build: