Skip to content
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

[v8.x] http2: mitigate reported DoS attacks #29124

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 1 addition & 2 deletions benchmark/http2/headers.js
Expand Up @@ -42,8 +42,7 @@ function main(conf) {

function doRequest(remaining) {
const req = client.request(headersObject);
req.end();
req.on('data', () => {});
req.resume();
req.on('end', () => {
if (remaining > 0) {
doRequest(remaining - 1);
Expand Down
6 changes: 3 additions & 3 deletions benchmark/http2/respond-with-fd.js
Expand Up @@ -8,9 +8,9 @@ const fs = require('fs');
const file = path.join(path.resolve(__dirname, '../fixtures'), 'alice.html');

const bench = common.createBenchmark(main, {
requests: [100, 1000, 10000, 100000, 1000000],
streams: [100, 200, 1000],
clients: [1, 2],
requests: [100, 1000, 5000],
streams: [1, 10, 20, 40, 100, 200],
clients: [2],
benchmarker: ['h2load']
}, { flags: ['--no-warnings', '--expose-http2'] });

Expand Down
6 changes: 3 additions & 3 deletions benchmark/http2/simple.js
Expand Up @@ -9,9 +9,9 @@ const fs = require('fs');
const file = path.join(path.resolve(__dirname, '../fixtures'), 'alice.html');

const bench = common.createBenchmark(main, {
requests: [100, 1000, 10000, 100000],
streams: [100, 200, 1000],
clients: [1, 2],
requests: [100, 1000, 5000],
streams: [1, 10, 20, 40, 100, 200],
clients: [2],
benchmarker: ['h2load']
}, { flags: ['--no-warnings', '--expose-http2'] });

Expand Down
25 changes: 15 additions & 10 deletions deps/nghttp2/lib/CMakeLists.txt
Expand Up @@ -38,16 +38,23 @@ if(WIN32)
endif()

# Public shared library
add_library(nghttp2 SHARED ${NGHTTP2_SOURCES} ${NGHTTP2_RES})
set_target_properties(nghttp2 PROPERTIES
COMPILE_FLAGS "${WARNCFLAGS}"
VERSION ${LT_VERSION} SOVERSION ${LT_SOVERSION}
C_VISIBILITY_PRESET hidden
)
target_include_directories(nghttp2 INTERFACE
if(ENABLE_SHARED_LIB)
add_library(nghttp2 SHARED ${NGHTTP2_SOURCES} ${NGHTTP2_RES})
set_target_properties(nghttp2 PROPERTIES
COMPILE_FLAGS "${WARNCFLAGS}"
VERSION ${LT_VERSION} SOVERSION ${LT_SOVERSION}
C_VISIBILITY_PRESET hidden
)
target_include_directories(nghttp2 INTERFACE
"${CMAKE_CURRENT_BINARY_DIR}/includes"
"${CMAKE_CURRENT_SOURCE_DIR}/includes"
)
)

install(TARGETS nghttp2
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}"
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}"
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
endif()

if(HAVE_CUNIT OR ENABLE_STATIC_LIB)
# Static library (for unittests because of symbol visibility)
Expand All @@ -64,8 +71,6 @@ if(HAVE_CUNIT OR ENABLE_STATIC_LIB)
endif()
endif()

install(TARGETS nghttp2
DESTINATION "${CMAKE_INSTALL_LIBDIR}")

install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libnghttp2.pc"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
26 changes: 24 additions & 2 deletions deps/nghttp2/lib/includes/nghttp2/nghttp2.h
Expand Up @@ -31,6 +31,11 @@
# define WIN32
#endif

/* Compatibility for non-Clang compilers */
#ifndef __has_declspec_attribute
# define __has_declspec_attribute(x) 0
#endif

#ifdef __cplusplus
extern "C" {
#endif
Expand All @@ -51,7 +56,8 @@ extern "C" {

#ifdef NGHTTP2_STATICLIB
# define NGHTTP2_EXTERN
#elif defined(WIN32)
#elif defined(WIN32) || (__has_declspec_attribute(dllexport) && \
__has_declspec_attribute(dllimport))
# ifdef BUILDING_NGHTTP2
# define NGHTTP2_EXTERN __declspec(dllexport)
# else /* !BUILDING_NGHTTP2 */
Expand Down Expand Up @@ -680,7 +686,12 @@ typedef enum {
/**
* SETTINGS_MAX_HEADER_LIST_SIZE
*/
NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE = 0x06
NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE = 0x06,
/**
* SETTINGS_ENABLE_CONNECT_PROTOCOL
* (`RFC 8441 <https://tools.ietf.org/html/rfc8441>`_)
*/
NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL = 0x08
} nghttp2_settings_id;
/* Note: If we add SETTINGS, update the capacity of
NGHTTP2_INBOUND_NUM_IV as well */
Expand Down Expand Up @@ -2637,6 +2648,17 @@ nghttp2_option_set_max_deflate_dynamic_table_size(nghttp2_option *option,
NGHTTP2_EXTERN void nghttp2_option_set_no_closed_streams(nghttp2_option *option,
int val);

/**
* @function
*
* This function sets the maximum number of outgoing SETTINGS ACK and
* PING ACK frames retained in :type:`nghttp2_session` object. If
* more than those frames are retained, the peer is considered to be
* misbehaving and session will be closed. The default value is 1000.
*/
NGHTTP2_EXTERN void nghttp2_option_set_max_outbound_ack(nghttp2_option *option,
size_t val);

/**
* @function
*
Expand Down
4 changes: 2 additions & 2 deletions deps/nghttp2/lib/includes/nghttp2/nghttp2ver.h
Expand Up @@ -29,14 +29,14 @@
* @macro
* Version number of the nghttp2 library release
*/
#define NGHTTP2_VERSION "1.33.0"
#define NGHTTP2_VERSION "1.39.2"

/**
* @macro
* Numerical representation of the version number of the nghttp2 library
* release. This is a 24 bit number with 8 bits for major number, 8 bits
* for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203.
*/
#define NGHTTP2_VERSION_NUM 0x012100
#define NGHTTP2_VERSION_NUM 0x012702

#endif /* NGHTTP2VER_H */
5 changes: 5 additions & 0 deletions deps/nghttp2/lib/nghttp2_frame.c
Expand Up @@ -1050,6 +1050,11 @@ int nghttp2_iv_check(const nghttp2_settings_entry *iv, size_t niv) {
break;
case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE:
break;
case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL:
if (iv[i].value != 0 && iv[i].value != 1) {
return 0;
}
break;
}
}
return 1;
Expand Down
22 changes: 16 additions & 6 deletions deps/nghttp2/lib/nghttp2_hd.c
Expand Up @@ -45,7 +45,7 @@
/* 3rd parameter is nghttp2_token value for header field name. We use
first enum value if same header names are repeated (e.g.,
:status). */
static nghttp2_hd_static_entry static_table[] = {
static const nghttp2_hd_static_entry static_table[] = {
MAKE_STATIC_ENT(":authority", "", 0, 3153725150u),
MAKE_STATIC_ENT(":method", "GET", 1, 695666056u),
MAKE_STATIC_ENT(":method", "POST", 1, 695666056u),
Expand Down Expand Up @@ -271,6 +271,15 @@ static int32_t lookup_token(const uint8_t *name, size_t namelen) {
break;
}
break;
case 9:
switch (name[8]) {
case 'l':
if (memeq(":protoco", name, 8)) {
return NGHTTP2_TOKEN__PROTOCOL;
}
break;
}
break;
case 10:
switch (name[9]) {
case 'e':
Expand Down Expand Up @@ -1159,7 +1168,7 @@ static search_result search_static_table(const nghttp2_nv *nv, int32_t token,
int name_only) {
search_result res = {token, 0};
int i;
nghttp2_hd_static_entry *ent;
const nghttp2_hd_static_entry *ent;

if (name_only) {
return res;
Expand All @@ -1184,7 +1193,7 @@ static search_result search_hd_table(nghttp2_hd_context *context,
int indexing_mode, nghttp2_hd_map *map,
uint32_t hash) {
search_result res = {-1, 0};
nghttp2_hd_entry *ent;
const nghttp2_hd_entry *ent;
int exact_match;
int name_only = indexing_mode == NGHTTP2_HD_NEVER_INDEXING;

Expand Down Expand Up @@ -1289,8 +1298,9 @@ nghttp2_hd_nv nghttp2_hd_table_get(nghttp2_hd_context *context, size_t idx) {
return hd_ringbuf_get(&context->hd_table, idx - NGHTTP2_STATIC_TABLE_LENGTH)
->nv;
} else {
nghttp2_hd_static_entry *ent = &static_table[idx];
nghttp2_hd_nv nv = {&ent->name, &ent->value, ent->token,
const nghttp2_hd_static_entry *ent = &static_table[idx];
nghttp2_hd_nv nv = {(nghttp2_rcbuf *)&ent->name,
(nghttp2_rcbuf *)&ent->value, ent->token,
NGHTTP2_NV_FLAG_NONE};
return nv;
}
Expand Down Expand Up @@ -1380,7 +1390,7 @@ static int deflate_nv(nghttp2_hd_deflater *deflater, nghttp2_bufs *bufs,
if (indexing_mode == NGHTTP2_HD_WITH_INDEXING) {
nghttp2_hd_nv hd_nv;

if (idx != -1 && idx < (ssize_t)NGHTTP2_STATIC_TABLE_LENGTH) {
if (idx != -1) {
hd_nv.name = nghttp2_hd_table_get(&deflater->ctx, (size_t)idx).name;
nghttp2_rcbuf_incref(hd_nv.name);
} else {
Expand Down
1 change: 1 addition & 0 deletions deps/nghttp2/lib/nghttp2_hd.h
Expand Up @@ -111,6 +111,7 @@ typedef enum {
NGHTTP2_TOKEN_KEEP_ALIVE,
NGHTTP2_TOKEN_PROXY_CONNECTION,
NGHTTP2_TOKEN_UPGRADE,
NGHTTP2_TOKEN__PROTOCOL,
} nghttp2_token;

struct nghttp2_hd_entry;
Expand Down
4 changes: 2 additions & 2 deletions deps/nghttp2/lib/nghttp2_helper.c
Expand Up @@ -340,7 +340,7 @@ const char *nghttp2_strerror(int error_code) {
}

/* Generated by gennmchartbl.py */
static int VALID_HD_NAME_CHARS[] = {
static const int VALID_HD_NAME_CHARS[] = {
0 /* NUL */, 0 /* SOH */, 0 /* STX */, 0 /* ETX */,
0 /* EOT */, 0 /* ENQ */, 0 /* ACK */, 0 /* BEL */,
0 /* BS */, 0 /* HT */, 0 /* LF */, 0 /* VT */,
Expand Down Expand Up @@ -428,7 +428,7 @@ int nghttp2_check_header_name(const uint8_t *name, size_t len) {
}

/* Generated by genvchartbl.py */
static int VALID_HD_VALUE_CHARS[] = {
static const int VALID_HD_VALUE_CHARS[] = {
0 /* NUL */, 0 /* SOH */, 0 /* STX */, 0 /* ETX */,
0 /* EOT */, 0 /* ENQ */, 0 /* ACK */, 0 /* BEL */,
0 /* BS */, 1 /* HT */, 0 /* LF */, 0 /* VT */,
Expand Down
46 changes: 29 additions & 17 deletions deps/nghttp2/lib/nghttp2_http.c
Expand Up @@ -113,7 +113,7 @@ static int check_path(nghttp2_stream *stream) {
}

static int http_request_on_header(nghttp2_stream *stream, nghttp2_hd_nv *nv,
int trailer) {
int trailer, int connect_protocol) {
if (nv->name->base[0] == ':') {
if (trailer ||
(stream->http_flags & NGHTTP2_HTTP_FLAG_PSEUDO_HEADER_DISALLOWED)) {
Expand Down Expand Up @@ -146,10 +146,6 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_hd_nv *nv,
return NGHTTP2_ERR_HTTP_HEADER;
}
stream->http_flags |= NGHTTP2_HTTP_FLAG_METH_CONNECT;
if (stream->http_flags &
(NGHTTP2_HTTP_FLAG__PATH | NGHTTP2_HTTP_FLAG__SCHEME)) {
return NGHTTP2_ERR_HTTP_HEADER;
}
}
break;
case 'S':
Expand All @@ -162,9 +158,6 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_hd_nv *nv,
}
break;
case NGHTTP2_TOKEN__PATH:
if (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) {
return NGHTTP2_ERR_HTTP_HEADER;
}
if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__PATH)) {
return NGHTTP2_ERR_HTTP_HEADER;
}
Expand All @@ -175,9 +168,6 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_hd_nv *nv,
}
break;
case NGHTTP2_TOKEN__SCHEME:
if (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) {
return NGHTTP2_ERR_HTTP_HEADER;
}
if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__SCHEME)) {
return NGHTTP2_ERR_HTTP_HEADER;
}
Expand All @@ -186,6 +176,15 @@ static int http_request_on_header(nghttp2_stream *stream, nghttp2_hd_nv *nv,
stream->http_flags |= NGHTTP2_HTTP_FLAG_SCHEME_HTTP;
}
break;
case NGHTTP2_TOKEN__PROTOCOL:
if (!connect_protocol) {
return NGHTTP2_ERR_HTTP_HEADER;
}

if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG__PROTOCOL)) {
return NGHTTP2_ERR_HTTP_HEADER;
}
break;
case NGHTTP2_TOKEN_HOST:
if (!check_pseudo_header(stream, nv, NGHTTP2_HTTP_FLAG_HOST)) {
return NGHTTP2_ERR_HTTP_HEADER;
Expand Down Expand Up @@ -264,11 +263,14 @@ static int http_response_on_header(nghttp2_stream *stream, nghttp2_hd_nv *nv,
stream->content_length = 0;
return NGHTTP2_ERR_REMOVE_HTTP_HEADER;
}
if (stream->status_code / 100 == 1 ||
(stream->status_code == 200 &&
(stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT))) {
if (stream->status_code / 100 == 1) {
return NGHTTP2_ERR_HTTP_HEADER;
}
/* https://tools.ietf.org/html/rfc7230#section-3.3.3 */
if (stream->status_code / 100 == 2 &&
(stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT)) {
return NGHTTP2_ERR_REMOVE_HTTP_HEADER;
}
if (stream->content_length != -1) {
return NGHTTP2_ERR_HTTP_HEADER;
}
Expand Down Expand Up @@ -458,16 +460,21 @@ int nghttp2_http_on_header(nghttp2_session *session, nghttp2_stream *stream,
}

if (session->server || frame->hd.type == NGHTTP2_PUSH_PROMISE) {
return http_request_on_header(stream, nv, trailer);
return http_request_on_header(stream, nv, trailer,
session->server &&
session->pending_enable_connect_protocol);
}

return http_response_on_header(stream, nv, trailer);
}

int nghttp2_http_on_request_headers(nghttp2_stream *stream,
nghttp2_frame *frame) {
if (stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) {
if ((stream->http_flags & NGHTTP2_HTTP_FLAG__AUTHORITY) == 0) {
if (!(stream->http_flags & NGHTTP2_HTTP_FLAG__PROTOCOL) &&
(stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT)) {
if ((stream->http_flags &
(NGHTTP2_HTTP_FLAG__SCHEME | NGHTTP2_HTTP_FLAG__PATH)) ||
(stream->http_flags & NGHTTP2_HTTP_FLAG__AUTHORITY) == 0) {
return -1;
}
stream->content_length = -1;
Expand All @@ -478,6 +485,11 @@ int nghttp2_http_on_request_headers(nghttp2_stream *stream,
(NGHTTP2_HTTP_FLAG__AUTHORITY | NGHTTP2_HTTP_FLAG_HOST)) == 0) {
return -1;
}
if ((stream->http_flags & NGHTTP2_HTTP_FLAG__PROTOCOL) &&
((stream->http_flags & NGHTTP2_HTTP_FLAG_METH_CONNECT) == 0 ||
(stream->http_flags & NGHTTP2_HTTP_FLAG__AUTHORITY) == 0)) {
return -1;
}
if (!check_path(stream)) {
return -1;
}
Expand Down
5 changes: 5 additions & 0 deletions deps/nghttp2/lib/nghttp2_option.c
Expand Up @@ -116,3 +116,8 @@ void nghttp2_option_set_no_closed_streams(nghttp2_option *option, int val) {
option->opt_set_mask |= NGHTTP2_OPT_NO_CLOSED_STREAMS;
option->no_closed_streams = val;
}

void nghttp2_option_set_max_outbound_ack(nghttp2_option *option, size_t val) {
option->opt_set_mask |= NGHTTP2_OPT_MAX_OUTBOUND_ACK;
option->max_outbound_ack = val;
}
5 changes: 5 additions & 0 deletions deps/nghttp2/lib/nghttp2_option.h
Expand Up @@ -66,6 +66,7 @@ typedef enum {
NGHTTP2_OPT_MAX_SEND_HEADER_BLOCK_LENGTH = 1 << 8,
NGHTTP2_OPT_MAX_DEFLATE_DYNAMIC_TABLE_SIZE = 1 << 9,
NGHTTP2_OPT_NO_CLOSED_STREAMS = 1 << 10,
NGHTTP2_OPT_MAX_OUTBOUND_ACK = 1 << 11,
} nghttp2_option_flag;

/**
Expand All @@ -80,6 +81,10 @@ struct nghttp2_option {
* NGHTTP2_OPT_MAX_DEFLATE_DYNAMIC_TABLE_SIZE
*/
size_t max_deflate_dynamic_table_size;
/**
* NGHTTP2_OPT_MAX_OUTBOUND_ACK
*/
size_t max_outbound_ack;
/**
* Bitwise OR of nghttp2_option_flag to determine that which fields
* are specified.
Expand Down