Skip to content

Commit

Permalink
http: add maximum chunk extension size
Browse files Browse the repository at this point in the history
  • Loading branch information
ShogunPanda authored and marco-ippolito committed Feb 12, 2024
1 parent e6b4c10 commit 911cb33
Show file tree
Hide file tree
Showing 10 changed files with 293 additions and 19 deletions.
1 change: 1 addition & 0 deletions deps/llhttp/.gitignore
@@ -0,0 +1 @@
libllhttp.pc
2 changes: 1 addition & 1 deletion deps/llhttp/CMakeLists.txt
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.5.1)
cmake_policy(SET CMP0069 NEW)

project(llhttp VERSION 6.0.11)
project(llhttp VERSION 6.1.0)
include(GNUInstallDirs)

set(CMAKE_C_STANDARD 99)
Expand Down
7 changes: 5 additions & 2 deletions deps/llhttp/include/llhttp.h
Expand Up @@ -2,8 +2,8 @@
#define INCLUDE_LLHTTP_H_

#define LLHTTP_VERSION_MAJOR 6
#define LLHTTP_VERSION_MINOR 0
#define LLHTTP_VERSION_PATCH 11
#define LLHTTP_VERSION_MINOR 1
#define LLHTTP_VERSION_PATCH 0

#ifndef LLHTTP_STRICT_MODE
# define LLHTTP_STRICT_MODE 0
Expand Down Expand Up @@ -348,6 +348,9 @@ struct llhttp_settings_s {
*/
llhttp_cb on_headers_complete;

/* Possible return values 0, -1, HPE_USER */
llhttp_data_cb on_chunk_parameters;

/* Possible return values 0, -1, HPE_USER */
llhttp_data_cb on_body;

Expand Down
7 changes: 7 additions & 0 deletions deps/llhttp/src/api.c
Expand Up @@ -355,6 +355,13 @@ int llhttp__on_chunk_header(llhttp_t* s, const char* p, const char* endp) {
}


int llhttp__on_chunk_parameters(llhttp_t* s, const char* p, const char* endp) {
int err;
SPAN_CALLBACK_MAYBE(s, on_chunk_parameters, p, endp - p);
return err;
}


int llhttp__on_chunk_complete(llhttp_t* s, const char* p, const char* endp) {
int err;
CALLBACK_MAYBE(s, on_chunk_complete);
Expand Down
122 changes: 108 additions & 14 deletions deps/llhttp/src/llhttp.c
Expand Up @@ -340,6 +340,8 @@ enum llparse_state_e {
s_n_llhttp__internal__n_invoke_is_equal_content_length,
s_n_llhttp__internal__n_chunk_size_almost_done,
s_n_llhttp__internal__n_chunk_parameters,
s_n_llhttp__internal__n_span_start_llhttp__on_chunk_parameters,
s_n_llhttp__internal__n_chunk_parameters_ows,
s_n_llhttp__internal__n_chunk_size_otherwise,
s_n_llhttp__internal__n_chunk_size,
s_n_llhttp__internal__n_chunk_size_digit,
Expand Down Expand Up @@ -539,6 +541,10 @@ int llhttp__on_body(
llhttp__internal_t* s, const unsigned char* p,
const unsigned char* endp);

int llhttp__on_chunk_parameters(
llhttp__internal_t* s, const unsigned char* p,
const unsigned char* endp);

int llhttp__on_status(
llhttp__internal_t* s, const unsigned char* p,
const unsigned char* endp);
Expand Down Expand Up @@ -1226,8 +1232,7 @@ static llparse_state_t llhttp__internal__run(
goto s_n_llhttp__internal__n_chunk_parameters;
}
case 2: {
p++;
goto s_n_llhttp__internal__n_chunk_size_almost_done;
goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_parameters;
}
default: {
goto s_n_llhttp__internal__n_error_10;
Expand All @@ -1236,6 +1241,34 @@ static llparse_state_t llhttp__internal__run(
/* UNREACHABLE */;
abort();
}
case s_n_llhttp__internal__n_span_start_llhttp__on_chunk_parameters:
s_n_llhttp__internal__n_span_start_llhttp__on_chunk_parameters: {
if (p == endp) {
return s_n_llhttp__internal__n_span_start_llhttp__on_chunk_parameters;
}
state->_span_pos0 = (void*) p;
state->_span_cb0 = llhttp__on_chunk_parameters;
goto s_n_llhttp__internal__n_chunk_parameters;
/* UNREACHABLE */;
abort();
}
case s_n_llhttp__internal__n_chunk_parameters_ows:
s_n_llhttp__internal__n_chunk_parameters_ows: {
if (p == endp) {
return s_n_llhttp__internal__n_chunk_parameters_ows;
}
switch (*p) {
case ' ': {
p++;
goto s_n_llhttp__internal__n_chunk_parameters_ows;
}
default: {
goto s_n_llhttp__internal__n_span_start_llhttp__on_chunk_parameters;
}
}
/* UNREACHABLE */;
abort();
}
case s_n_llhttp__internal__n_chunk_size_otherwise:
s_n_llhttp__internal__n_chunk_size_otherwise: {
if (p == endp) {
Expand All @@ -1246,13 +1279,9 @@ static llparse_state_t llhttp__internal__run(
p++;
goto s_n_llhttp__internal__n_chunk_size_almost_done;
}
case ' ': {
p++;
goto s_n_llhttp__internal__n_chunk_parameters;
}
case ';': {
p++;
goto s_n_llhttp__internal__n_chunk_parameters;
goto s_n_llhttp__internal__n_chunk_parameters_ows;
}
default: {
goto s_n_llhttp__internal__n_error_11;
Expand Down Expand Up @@ -6074,6 +6103,24 @@ static llparse_state_t llhttp__internal__run(
/* UNREACHABLE */;
abort();
}
s_n_llhttp__internal__n_span_end_llhttp__on_chunk_parameters: {
const unsigned char* start;
int err;

start = state->_span_pos0;
state->_span_pos0 = NULL;
err = llhttp__on_chunk_parameters(state, start, p);
if (err != 0) {
state->error = err;
state->error_pos = (const char*) (p + 1);
state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_size_almost_done;
return s_error;
}
p++;
goto s_n_llhttp__internal__n_chunk_size_almost_done;
/* UNREACHABLE */;
abort();
}
s_n_llhttp__internal__n_error_10: {
state->error = 0x2;
state->reason = "Invalid character in chunk parameters";
Expand Down Expand Up @@ -8441,6 +8488,8 @@ enum llparse_state_e {
s_n_llhttp__internal__n_invoke_is_equal_content_length,
s_n_llhttp__internal__n_chunk_size_almost_done,
s_n_llhttp__internal__n_chunk_parameters,
s_n_llhttp__internal__n_span_start_llhttp__on_chunk_parameters,
s_n_llhttp__internal__n_chunk_parameters_ows,
s_n_llhttp__internal__n_chunk_size_otherwise,
s_n_llhttp__internal__n_chunk_size,
s_n_llhttp__internal__n_chunk_size_digit,
Expand Down Expand Up @@ -8635,6 +8684,10 @@ int llhttp__on_body(
llhttp__internal_t* s, const unsigned char* p,
const unsigned char* endp);

int llhttp__on_chunk_parameters(
llhttp__internal_t* s, const unsigned char* p,
const unsigned char* endp);

int llhttp__on_status(
llhttp__internal_t* s, const unsigned char* p,
const unsigned char* endp);
Expand Down Expand Up @@ -9299,8 +9352,7 @@ static llparse_state_t llhttp__internal__run(
goto s_n_llhttp__internal__n_chunk_parameters;
}
case 2: {
p++;
goto s_n_llhttp__internal__n_chunk_size_almost_done;
goto s_n_llhttp__internal__n_span_end_llhttp__on_chunk_parameters;
}
default: {
goto s_n_llhttp__internal__n_error_6;
Expand All @@ -9309,6 +9361,34 @@ static llparse_state_t llhttp__internal__run(
/* UNREACHABLE */;
abort();
}
case s_n_llhttp__internal__n_span_start_llhttp__on_chunk_parameters:
s_n_llhttp__internal__n_span_start_llhttp__on_chunk_parameters: {
if (p == endp) {
return s_n_llhttp__internal__n_span_start_llhttp__on_chunk_parameters;
}
state->_span_pos0 = (void*) p;
state->_span_cb0 = llhttp__on_chunk_parameters;
goto s_n_llhttp__internal__n_chunk_parameters;
/* UNREACHABLE */;
abort();
}
case s_n_llhttp__internal__n_chunk_parameters_ows:
s_n_llhttp__internal__n_chunk_parameters_ows: {
if (p == endp) {
return s_n_llhttp__internal__n_chunk_parameters_ows;
}
switch (*p) {
case ' ': {
p++;
goto s_n_llhttp__internal__n_chunk_parameters_ows;
}
default: {
goto s_n_llhttp__internal__n_span_start_llhttp__on_chunk_parameters;
}
}
/* UNREACHABLE */;
abort();
}
case s_n_llhttp__internal__n_chunk_size_otherwise:
s_n_llhttp__internal__n_chunk_size_otherwise: {
if (p == endp) {
Expand All @@ -9319,13 +9399,9 @@ static llparse_state_t llhttp__internal__run(
p++;
goto s_n_llhttp__internal__n_chunk_size_almost_done;
}
case ' ': {
p++;
goto s_n_llhttp__internal__n_chunk_parameters;
}
case ';': {
p++;
goto s_n_llhttp__internal__n_chunk_parameters;
goto s_n_llhttp__internal__n_chunk_parameters_ows;
}
default: {
goto s_n_llhttp__internal__n_error_7;
Expand Down Expand Up @@ -13951,6 +14027,24 @@ static llparse_state_t llhttp__internal__run(
/* UNREACHABLE */;
abort();
}
s_n_llhttp__internal__n_span_end_llhttp__on_chunk_parameters: {
const unsigned char* start;
int err;

start = state->_span_pos0;
state->_span_pos0 = NULL;
err = llhttp__on_chunk_parameters(state, start, p);
if (err != 0) {
state->error = err;
state->error_pos = (const char*) (p + 1);
state->_current = (void*) (intptr_t) s_n_llhttp__internal__n_chunk_size_almost_done;
return s_error;
}
p++;
goto s_n_llhttp__internal__n_chunk_size_almost_done;
/* UNREACHABLE */;
abort();
}
s_n_llhttp__internal__n_error_6: {
state->error = 0x2;
state->reason = "Invalid character in chunk parameters";
Expand Down
12 changes: 12 additions & 0 deletions doc/api/errors.md
Expand Up @@ -3132,6 +3132,18 @@ malconfigured clients, if more than 8 KiB of HTTP header data is received then
HTTP parsing will abort without a request or response object being created, and
an `Error` with this code will be emitted.

<a id="HPE_CHUNK_EXTENSIONS_OVERFLOW"></a>

### `HPE_CHUNK_EXTENSIONS_OVERFLOW`

<!-- YAML
added: REPLACEME
-->

Too much data was received for a chunk extensions. In order to protect against
malicious or malconfigured clients, if more than 16 KiB of data is received
then an `Error` with this code will be emitted.

<a id="HPE_UNEXPECTED_CONTENT_LENGTH"></a>

### `HPE_UNEXPECTED_CONTENT_LENGTH`
Expand Down
8 changes: 8 additions & 0 deletions lib/_http_server.js
Expand Up @@ -846,6 +846,11 @@ const requestHeaderFieldsTooLargeResponse = Buffer.from(
'Connection: close\r\n\r\n', 'ascii',
);

const requestChunkExtensionsTooLargeResponse = Buffer.from(
`HTTP/1.1 413 ${STATUS_CODES[413]}\r\n` +
'Connection: close\r\n\r\n', 'ascii',
);

function warnUnclosedSocket() {
if (warnUnclosedSocket.emitted) {
return;
Expand Down Expand Up @@ -881,6 +886,9 @@ function socketOnError(e) {
case 'HPE_HEADER_OVERFLOW':
response = requestHeaderFieldsTooLargeResponse;
break;
case 'HPE_CHUNK_EXTENSIONS_OVERFLOW':
response = requestChunkExtensionsTooLargeResponse;
break;
case 'ERR_HTTP_REQUEST_TIMEOUT':
response = requestTimeoutResponse;
break;
Expand Down
20 changes: 19 additions & 1 deletion src/node_http_parser.cc
Expand Up @@ -79,6 +79,8 @@ const uint32_t kOnExecute = 5;
const uint32_t kOnTimeout = 6;
// Any more fields than this will be flushed into JS
const size_t kMaxHeaderFieldsCount = 32;
// Maximum size of chunk extensions
const size_t kMaxChunkExtensionsSize = 16384;

const uint32_t kLenientNone = 0;
const uint32_t kLenientHeaders = 1 << 0;
Expand Down Expand Up @@ -261,6 +263,7 @@ class Parser : public AsyncWrap, public StreamListener {

num_fields_ = num_values_ = 0;
headers_completed_ = false;
chunk_extensions_nread_ = 0;
last_message_start_ = uv_hrtime();
url_.Reset();
status_message_.Reset();
Expand Down Expand Up @@ -516,9 +519,22 @@ class Parser : public AsyncWrap, public StreamListener {
return 0;
}

// Reset nread for the next chunk
int on_chunk_extension(const char* at, size_t length) {
chunk_extensions_nread_ += length;

if (chunk_extensions_nread_ > kMaxChunkExtensionsSize) {
llhttp_set_error_reason(&parser_,
"HPE_CHUNK_EXTENSIONS_OVERFLOW:Chunk extensions overflow");
return HPE_USER;
}

return 0;
}

// Reset nread for the next chunk and also reset the extensions counter
int on_chunk_header() {
header_nread_ = 0;
chunk_extensions_nread_ = 0;
return 0;
}

Expand Down Expand Up @@ -986,6 +1002,7 @@ class Parser : public AsyncWrap, public StreamListener {
bool headers_completed_ = false;
bool pending_pause_ = false;
uint64_t header_nread_ = 0;
uint64_t chunk_extensions_nread_ = 0;
uint64_t max_http_header_size_;
uint64_t last_message_start_;
ConnectionsList* connectionsList_;
Expand Down Expand Up @@ -1157,6 +1174,7 @@ const llhttp_settings_t Parser::settings = {
Proxy<DataCall, &Parser::on_header_field>::Raw,
Proxy<DataCall, &Parser::on_header_value>::Raw,
Proxy<Call, &Parser::on_headers_complete>::Raw,
Proxy<DataCall, &Parser::on_chunk_extension>::Raw,
Proxy<DataCall, &Parser::on_body>::Raw,
Proxy<Call, &Parser::on_message_complete>::Raw,
Proxy<Call, &Parser::on_chunk_header>::Raw,
Expand Down

0 comments on commit 911cb33

Please sign in to comment.