Merge branch 'fix/http_server_chunked_501' into 'master'

Fix(esp_http_server): Fail cleanly on incoming chunked request

See merge request espressif/esp-idf!52126
This commit is contained in:
Mahavir Jain
2026-09-06 15:55:17 +05:30
4 changed files with 225 additions and 19 deletions
+33 -7
View File
@@ -55,6 +55,8 @@ typedef struct {
size_t raw_datalen; /*!< Full length of the raw data in scratch buffer */
} parser_data_t;
static const char *httpd_find_hdr_value(struct httpd_req_aux *ra, const char *field);
static esp_err_t verify_url (http_parser *parser)
{
parser_data_t *parser_data = (parser_data_t *) parser->data;
@@ -66,12 +68,10 @@ static esp_err_t verify_url (http_parser *parser)
const char *at = parser_data->last.at;
size_t length = parser_data->last.length;
/* http_parser stops with HPE_INVALID_METHOD before the URL callback for
* any method token it does not know, so the method is always valid here.
* That parser error is mapped to 501 in parse_block(). */
r->method = parser->method;
if (r->method < 0) {
ESP_LOGW(TAG, LOG_FMT("HTTP method not supported (%d)"), r->method);
parser_data->error = HTTPD_501_METHOD_NOT_IMPLEMENTED;
return ESP_FAIL;
}
if (sizeof(r->uri) < (length + 1)) {
ESP_LOGW(TAG, LOG_FMT("URI length (%"NEWLIB_NANO_COMPAT_FORMAT") greater than supported (%"NEWLIB_NANO_COMPAT_FORMAT")"),
@@ -372,6 +372,21 @@ static esp_err_t cb_headers_complete(http_parser *parser)
return ESP_FAIL;
}
/* This server implements no transfer codings, so reject any request that
* carries a Transfer-Encoding header with 501 (RFC 9112 section 6.1).
* Without this check a chunked body is treated as an empty body and its
* chunk framing is parsed as a separate pipelined request, which is a
* request-smuggling primitive when the server sits behind a proxy. The
* header-presence check also covers codings that http_parser does not
* flag as chunked (e.g. "gzip, chunked"). */
if ((parser->flags & F_CHUNKED) ||
httpd_find_hdr_value(ra, "Transfer-Encoding") != NULL) {
ESP_LOGW(TAG, LOG_FMT("Transfer-Encoding is not supported"));
parser_data->error = HTTPD_501_METHOD_NOT_IMPLEMENTED;
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
/* In absence of body/chunked encoding, http_parser sets content_len to ULLONG_MAX */
if (parser->content_length != ULLONG_MAX) {
/* Content-Length was specified. Reject any value above UINT32_MAX: it is
@@ -583,6 +598,17 @@ static int read_block(httpd_req_t *req, http_parser *parser, size_t offset, size
return nbytes;
}
/* Map an http_parser failure to the HTTP status sent back. An unrecognized
* request method is a 501 (RFC 9110 section 15.6.2), every other parser
* error means the request syntax is malformed. */
static httpd_err_code_t parser_errno_to_err_code(const http_parser *parser)
{
if (HTTP_PARSER_ERRNO(parser) == HPE_INVALID_METHOD) {
return HTTPD_501_METHOD_NOT_IMPLEMENTED;
}
return HTTPD_400_BAD_REQUEST;
}
static int parse_block(http_parser *parser, size_t offset, size_t length)
{
parser_data_t *data = (parser_data_t *)(parser->data);
@@ -644,14 +670,14 @@ static int parse_block(http_parser *parser, size_t offset, size_t length)
return 0;
} else if (nparsed != length) {
/* http_parser error */
data->error = HTTPD_400_BAD_REQUEST;
data->error = parser_errno_to_err_code(parser);
data->status = PARSING_FAILED;
ESP_LOGW(TAG, LOG_FMT("incomplete (%"NEWLIB_NANO_COMPAT_FORMAT"/%"NEWLIB_NANO_COMPAT_FORMAT") with parser error = %d"),
NEWLIB_NANO_COMPAT_CAST(nparsed), NEWLIB_NANO_COMPAT_CAST(length), parser->http_errno);
return -1;
} else if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {
/* http_parser error */
data->error = HTTPD_400_BAD_REQUEST;
data->error = parser_errno_to_err_code(parser);
data->status = PARSING_FAILED;
ESP_LOGE(TAG, LOG_FMT("parser error: %s"), http_errno_description(HTTP_PARSER_ERRNO(parser)));
return -1;
+10 -3
View File
@@ -603,9 +603,16 @@ esp_err_t httpd_req_handle_err(httpd_req_t *req, httpd_err_code_t error)
if (hd->err_handler_fns[error]) {
ret = hd->err_handler_fns[error](req, error);
/* If error code is 500, force return failure
* irrespective of the handler's return value */
ret = (error == HTTPD_500_INTERNAL_SERVER_ERROR ? ESP_FAIL : ret);
/* Force failure irrespective of the handler's return value for
* errors after which the session cannot continue safely: 500
* (internal state is broken) and 501 (parsing aborted mid-request,
* so the position of the next request in the byte stream is
* unknown and keep-alive would let leftover bytes be parsed and
* answered as a further request). */
if (error == HTTPD_500_INTERNAL_SERVER_ERROR ||
error == HTTPD_501_METHOD_NOT_IMPLEMENTED) {
ret = ESP_FAIL;
}
} else {
/* If no handler is registered for this error default
* behavior is to send the HTTP error response and