chore: roll latest Node.js security release (#23953)

This commit is contained in:
Shelley Vohr
2020-06-09 08:55:34 -07:00
committed by GitHub
parent 27c1fc87e3
commit f10c8e6dd5
6 changed files with 10837 additions and 0 deletions

View File

@@ -41,3 +41,8 @@ fix_remove_uses_of_node_use_v8_platform.patch
fix_call_initializecontextruntime_in_initializecontext.patch
refactor_transferrablemodule_is_deprecated_use_compiledwasmmodule.patch
fix_don_t_preparemainexecution_twice.patch
tls_emit_session_after_verifying_certificate.patch
http2_implement_support_for_max_settings_entries.patch
deps_update_nghttp2_to_1_40_0.patch
deps_update_nghttp2_to_1_41_0.patch
napi_fix_memory_corruption_vulnerability.patch

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,494 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: James M Snell <jasnell@gmail.com>
Date: Mon, 27 Apr 2020 09:27:49 -0700
Subject: deps: update nghttp2 to 1.41.0
Fixes: https://hackerone.com/reports/446662
CVE-ID: CVE-2020-11080
PR-URL: https://github.com/nodejs-private/node-private/pull/204
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
diff --git a/deps/nghttp2/lib/CMakeLists.txt b/deps/nghttp2/lib/CMakeLists.txt
deleted file mode 100644
index 4e3f5da0f9f00afb272de2e110d0e101c075fc01..0000000000000000000000000000000000000000
--- a/deps/nghttp2/lib/CMakeLists.txt
+++ /dev/null
@@ -1,76 +0,0 @@
-add_subdirectory(includes)
-
-include_directories(
- "${CMAKE_CURRENT_SOURCE_DIR}/includes"
- "${CMAKE_CURRENT_BINARY_DIR}/includes"
-)
-
-add_definitions(-DBUILDING_NGHTTP2)
-
-set(NGHTTP2_SOURCES
- nghttp2_pq.c nghttp2_map.c nghttp2_queue.c
- nghttp2_frame.c
- nghttp2_buf.c
- nghttp2_stream.c nghttp2_outbound_item.c
- nghttp2_session.c nghttp2_submit.c
- nghttp2_helper.c
- nghttp2_npn.c
- nghttp2_hd.c nghttp2_hd_huffman.c nghttp2_hd_huffman_data.c
- nghttp2_version.c
- nghttp2_priority_spec.c
- nghttp2_option.c
- nghttp2_callbacks.c
- nghttp2_mem.c
- nghttp2_http.c
- nghttp2_rcbuf.c
- nghttp2_debug.c
-)
-
-set(NGHTTP2_RES "")
-
-if(WIN32)
- configure_file(
- version.rc.in
- ${CMAKE_CURRENT_BINARY_DIR}/version.rc
- @ONLY)
-
- set(NGHTTP2_RES ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
-endif()
-
-# Public shared library
-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)
- add_library(nghttp2_static STATIC ${NGHTTP2_SOURCES})
- set_target_properties(nghttp2_static PROPERTIES
- COMPILE_FLAGS "${WARNCFLAGS}"
- VERSION ${LT_VERSION} SOVERSION ${LT_SOVERSION}
- ARCHIVE_OUTPUT_NAME nghttp2_static
- )
- target_compile_definitions(nghttp2_static PUBLIC "-DNGHTTP2_STATICLIB")
- if(ENABLE_STATIC_LIB)
- install(TARGETS nghttp2_static
- DESTINATION "${CMAKE_INSTALL_LIBDIR}")
- endif()
-endif()
-
-
-install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libnghttp2.pc"
- DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
diff --git a/deps/nghttp2/lib/includes/nghttp2/nghttp2.h b/deps/nghttp2/lib/includes/nghttp2/nghttp2.h
index e3aeb9fed31ecc11e068a42e656c307575b4f024..9be6eea5c02257ac12522e43829f47b3f371b857 100644
--- a/deps/nghttp2/lib/includes/nghttp2/nghttp2.h
+++ b/deps/nghttp2/lib/includes/nghttp2/nghttp2.h
@@ -228,6 +228,13 @@ typedef struct {
*/
#define NGHTTP2_CLIENT_MAGIC_LEN 24
+/**
+ * @macro
+ *
+ * The default max number of settings per SETTINGS frame
+ */
+#define NGHTTP2_DEFAULT_MAX_SETTINGS 32
+
/**
* @enum
*
@@ -398,6 +405,11 @@ typedef enum {
* receives an other type of frame.
*/
NGHTTP2_ERR_SETTINGS_EXPECTED = -536,
+ /**
+ * When a local endpoint receives too many settings entries
+ * in a single SETTINGS frame.
+ */
+ NGHTTP2_ERR_TOO_MANY_SETTINGS = -537,
/**
* The errors < :enum:`NGHTTP2_ERR_FATAL` mean that the library is
* under unexpected condition and processing was terminated (e.g.,
@@ -2659,6 +2671,17 @@ NGHTTP2_EXTERN void nghttp2_option_set_no_closed_streams(nghttp2_option *option,
NGHTTP2_EXTERN void nghttp2_option_set_max_outbound_ack(nghttp2_option *option,
size_t val);
+/**
+ * @function
+ *
+ * This function sets the maximum number of SETTINGS entries per
+ * SETTINGS frame that will be accepted. If more than those entries
+ * are received, the peer is considered to be misbehaving and session
+ * will be closed. The default value is 32.
+ */
+NGHTTP2_EXTERN void nghttp2_option_set_max_settings(nghttp2_option *option,
+ size_t val);
+
/**
* @function
*
diff --git a/deps/nghttp2/lib/includes/nghttp2/nghttp2ver.h b/deps/nghttp2/lib/includes/nghttp2/nghttp2ver.h
index 45d21e2645c6cf8be52e445b3ab8862c08597d85..795a44c1e86863119e33e09309d7305e579af132 100644
--- a/deps/nghttp2/lib/includes/nghttp2/nghttp2ver.h
+++ b/deps/nghttp2/lib/includes/nghttp2/nghttp2ver.h
@@ -29,7 +29,7 @@
* @macro
* Version number of the nghttp2 library release
*/
-#define NGHTTP2_VERSION "1.40.0"
+#define NGHTTP2_VERSION "1.41.0"
/**
* @macro
@@ -37,6 +37,6 @@
* 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 0x012800
+#define NGHTTP2_VERSION_NUM 0x012900
#endif /* NGHTTP2VER_H */
diff --git a/deps/nghttp2/lib/nghttp2_helper.c b/deps/nghttp2/lib/nghttp2_helper.c
index 91136a61986014706b091d177e6abdf88f6444e3..0bd5414723d73688a2a546f9036c3d910ef3e53c 100644
--- a/deps/nghttp2/lib/nghttp2_helper.c
+++ b/deps/nghttp2/lib/nghttp2_helper.c
@@ -334,6 +334,8 @@ const char *nghttp2_strerror(int error_code) {
case NGHTTP2_ERR_FLOODED:
return "Flooding was detected in this HTTP/2 session, and it must be "
"closed";
+ case NGHTTP2_ERR_TOO_MANY_SETTINGS:
+ return "SETTINGS frame contained more than the maximum allowed entries";
default:
return "Unknown error code";
}
diff --git a/deps/nghttp2/lib/nghttp2_option.c b/deps/nghttp2/lib/nghttp2_option.c
index e53f22d367f84a11792721d38a1cdc1a538e2a03..34348e6606ccf4206048f3f2f76d75a2ec366cc8 100644
--- a/deps/nghttp2/lib/nghttp2_option.c
+++ b/deps/nghttp2/lib/nghttp2_option.c
@@ -121,3 +121,8 @@ 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;
}
+
+void nghttp2_option_set_max_settings(nghttp2_option *option, size_t val) {
+ option->opt_set_mask |= NGHTTP2_OPT_MAX_SETTINGS;
+ option->max_settings = val;
+}
diff --git a/deps/nghttp2/lib/nghttp2_option.h b/deps/nghttp2/lib/nghttp2_option.h
index 1f740aaa6e364ed5a8df4804cff307ef36970b0b..939729fdcd5b6ec11078aef0d9b51e45270092a3 100644
--- a/deps/nghttp2/lib/nghttp2_option.h
+++ b/deps/nghttp2/lib/nghttp2_option.h
@@ -67,6 +67,7 @@ typedef enum {
NGHTTP2_OPT_MAX_DEFLATE_DYNAMIC_TABLE_SIZE = 1 << 9,
NGHTTP2_OPT_NO_CLOSED_STREAMS = 1 << 10,
NGHTTP2_OPT_MAX_OUTBOUND_ACK = 1 << 11,
+ NGHTTP2_OPT_MAX_SETTINGS = 1 << 12,
} nghttp2_option_flag;
/**
@@ -85,6 +86,10 @@ struct nghttp2_option {
* NGHTTP2_OPT_MAX_OUTBOUND_ACK
*/
size_t max_outbound_ack;
+ /**
+ * NGHTTP2_OPT_MAX_SETTINGS
+ */
+ size_t max_settings;
/**
* Bitwise OR of nghttp2_option_flag to determine that which fields
* are specified.
diff --git a/deps/nghttp2/lib/nghttp2_session.c b/deps/nghttp2/lib/nghttp2_session.c
index 9df3d6f32938a692191abbad915e028a9669d24d..39f81f498cda798bcd18926414d7297f34361d2b 100644
--- a/deps/nghttp2/lib/nghttp2_session.c
+++ b/deps/nghttp2/lib/nghttp2_session.c
@@ -458,6 +458,7 @@ static int session_new(nghttp2_session **session_ptr,
(*session_ptr)->max_send_header_block_length = NGHTTP2_MAX_HEADERSLEN;
(*session_ptr)->max_outbound_ack = NGHTTP2_DEFAULT_MAX_OBQ_FLOOD_ITEM;
+ (*session_ptr)->max_settings = NGHTTP2_DEFAULT_MAX_SETTINGS;
if (option) {
if ((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE) &&
@@ -521,6 +522,11 @@ static int session_new(nghttp2_session **session_ptr,
if (option->opt_set_mask & NGHTTP2_OPT_MAX_OUTBOUND_ACK) {
(*session_ptr)->max_outbound_ack = option->max_outbound_ack;
}
+
+ if ((option->opt_set_mask & NGHTTP2_OPT_MAX_SETTINGS) &&
+ option->max_settings) {
+ (*session_ptr)->max_settings = option->max_settings;
+ }
}
rv = nghttp2_hd_deflate_init2(&(*session_ptr)->hd_deflater,
@@ -2494,14 +2500,6 @@ static int session_update_stream_consumed_size(nghttp2_session *session,
static int session_update_connection_consumed_size(nghttp2_session *session,
size_t delta_size);
-static int session_update_recv_connection_window_size(nghttp2_session *session,
- size_t delta_size);
-
-static int session_update_recv_stream_window_size(nghttp2_session *session,
- nghttp2_stream *stream,
- size_t delta_size,
- int send_window_update);
-
/*
* Called after a frame is sent. This function runs
* on_frame_send_callback and handles stream closure upon END_STREAM
@@ -2735,7 +2733,7 @@ static int session_after_frame_sent1(nghttp2_session *session) {
if (session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) {
rv = session_update_connection_consumed_size(session, 0);
} else {
- rv = session_update_recv_connection_window_size(session, 0);
+ rv = nghttp2_session_update_recv_connection_window_size(session, 0);
}
if (nghttp2_is_fatal(rv)) {
@@ -2761,7 +2759,8 @@ static int session_after_frame_sent1(nghttp2_session *session) {
if (session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) {
rv = session_update_stream_consumed_size(session, stream, 0);
} else {
- rv = session_update_recv_stream_window_size(session, stream, 0, 1);
+ rv =
+ nghttp2_session_update_recv_stream_window_size(session, stream, 0, 1);
}
if (nghttp2_is_fatal(rv)) {
@@ -5019,22 +5018,10 @@ static int adjust_recv_window_size(int32_t *recv_window_size_ptr, size_t delta,
return 0;
}
-/*
- * Accumulates received bytes |delta_size| for stream-level flow
- * control and decides whether to send WINDOW_UPDATE to that stream.
- * If NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE is set, WINDOW_UPDATE will not
- * be sent.
- *
- * This function returns 0 if it succeeds, or one of the following
- * negative error codes:
- *
- * NGHTTP2_ERR_NOMEM
- * Out of memory.
- */
-static int session_update_recv_stream_window_size(nghttp2_session *session,
- nghttp2_stream *stream,
- size_t delta_size,
- int send_window_update) {
+int nghttp2_session_update_recv_stream_window_size(nghttp2_session *session,
+ nghttp2_stream *stream,
+ size_t delta_size,
+ int send_window_update) {
int rv;
rv = adjust_recv_window_size(&stream->recv_window_size, delta_size,
stream->local_window_size);
@@ -5063,20 +5050,8 @@ static int session_update_recv_stream_window_size(nghttp2_session *session,
return 0;
}
-/*
- * Accumulates received bytes |delta_size| for connection-level flow
- * control and decides whether to send WINDOW_UPDATE to the
- * connection. If NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE is set,
- * WINDOW_UPDATE will not be sent.
- *
- * This function returns 0 if it succeeds, or one of the following
- * negative error codes:
- *
- * NGHTTP2_ERR_NOMEM
- * Out of memory.
- */
-static int session_update_recv_connection_window_size(nghttp2_session *session,
- size_t delta_size) {
+int nghttp2_session_update_recv_connection_window_size(nghttp2_session *session,
+ size_t delta_size) {
int rv;
rv = adjust_recv_window_size(&session->recv_window_size, delta_size,
session->local_window_size);
@@ -5678,6 +5653,12 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
break;
}
+ /* Check the settings flood counter early to be safe */
+ if (session->obq_flood_counter_ >= session->max_outbound_ack &&
+ !(iframe->frame.hd.flags & NGHTTP2_FLAG_ACK)) {
+ return NGHTTP2_ERR_FLOODED;
+ }
+
iframe->state = NGHTTP2_IB_READ_SETTINGS;
if (iframe->payloadleft) {
@@ -5688,6 +5669,16 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
iframe->max_niv =
iframe->frame.hd.length / NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH + 1;
+ if (iframe->max_niv - 1 > session->max_settings) {
+ rv = nghttp2_session_terminate_session_with_reason(
+ session, NGHTTP2_ENHANCE_YOUR_CALM,
+ "SETTINGS: too many setting entries");
+ if (nghttp2_is_fatal(rv)) {
+ return rv;
+ }
+ return (ssize_t)inlen;
+ }
+
iframe->iv = nghttp2_mem_malloc(mem, sizeof(nghttp2_settings_entry) *
iframe->max_niv);
@@ -6454,7 +6445,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
}
/* Pad Length field is subject to flow control */
- rv = session_update_recv_connection_window_size(session, readlen);
+ rv = nghttp2_session_update_recv_connection_window_size(session, readlen);
if (nghttp2_is_fatal(rv)) {
return rv;
}
@@ -6477,7 +6468,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
stream = nghttp2_session_get_stream(session, iframe->frame.hd.stream_id);
if (stream) {
- rv = session_update_recv_stream_window_size(
+ rv = nghttp2_session_update_recv_stream_window_size(
session, stream, readlen,
iframe->payloadleft ||
(iframe->frame.hd.flags & NGHTTP2_FLAG_END_STREAM) == 0);
@@ -6524,7 +6515,8 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
if (readlen > 0) {
ssize_t data_readlen;
- rv = session_update_recv_connection_window_size(session, readlen);
+ rv = nghttp2_session_update_recv_connection_window_size(session,
+ readlen);
if (nghttp2_is_fatal(rv)) {
return rv;
}
@@ -6533,7 +6525,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
return (ssize_t)inlen;
}
- rv = session_update_recv_stream_window_size(
+ rv = nghttp2_session_update_recv_stream_window_size(
session, stream, readlen,
iframe->payloadleft ||
(iframe->frame.hd.flags & NGHTTP2_FLAG_END_STREAM) == 0);
@@ -6634,7 +6626,8 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
if (readlen > 0) {
/* Update connection-level flow control window for ignored
DATA frame too */
- rv = session_update_recv_connection_window_size(session, readlen);
+ rv = nghttp2_session_update_recv_connection_window_size(session,
+ readlen);
if (nghttp2_is_fatal(rv)) {
return rv;
}
@@ -7454,6 +7447,11 @@ static int nghttp2_session_upgrade_internal(nghttp2_session *session,
if (settings_payloadlen % NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH) {
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
+ /* SETTINGS frame contains too many settings */
+ if (settings_payloadlen / NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH
+ > session->max_settings) {
+ return NGHTTP2_ERR_TOO_MANY_SETTINGS;
+ }
rv = nghttp2_frame_unpack_settings_payload2(&iv, &niv, settings_payload,
settings_payloadlen, mem);
if (rv != 0) {
diff --git a/deps/nghttp2/lib/nghttp2_session.h b/deps/nghttp2/lib/nghttp2_session.h
index 90ead9c0395b4f1e42f94daadae57928e6c38df3..07bfbb6c90c8df07c0de4909bb72ff4942d15763 100644
--- a/deps/nghttp2/lib/nghttp2_session.h
+++ b/deps/nghttp2/lib/nghttp2_session.h
@@ -267,6 +267,8 @@ struct nghttp2_session {
/* The maximum length of header block to send. Calculated by the
same way as nghttp2_hd_deflate_bound() does. */
size_t max_send_header_block_length;
+ /* The maximum number of settings accepted per SETTINGS frame. */
+ size_t max_settings;
/* Next Stream ID. Made unsigned int to detect >= (1 << 31). */
uint32_t next_stream_id;
/* The last stream ID this session initiated. For client session,
@@ -898,4 +900,36 @@ int nghttp2_session_terminate_session_with_reason(nghttp2_session *session,
uint32_t error_code,
const char *reason);
+/*
+ * Accumulates received bytes |delta_size| for connection-level flow
+ * control and decides whether to send WINDOW_UPDATE to the
+ * connection. If NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE is set,
+ * WINDOW_UPDATE will not be sent.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ * Out of memory.
+ */
+int nghttp2_session_update_recv_connection_window_size(nghttp2_session *session,
+ size_t delta_size);
+
+/*
+ * Accumulates received bytes |delta_size| for stream-level flow
+ * control and decides whether to send WINDOW_UPDATE to that stream.
+ * If NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE is set, WINDOW_UPDATE will not
+ * be sent.
+ *
+ * This function returns 0 if it succeeds, or one of the following
+ * negative error codes:
+ *
+ * NGHTTP2_ERR_NOMEM
+ * Out of memory.
+ */
+int nghttp2_session_update_recv_stream_window_size(nghttp2_session *session,
+ nghttp2_stream *stream,
+ size_t delta_size,
+ int send_window_update);
+
#endif /* NGHTTP2_SESSION_H */
diff --git a/deps/nghttp2/lib/nghttp2_submit.c b/deps/nghttp2/lib/nghttp2_submit.c
index f604eff5c9017fc272ac876d255d522303826d5e..744a49cf6098ec656a72553aa6f6f25240749109 100644
--- a/deps/nghttp2/lib/nghttp2_submit.c
+++ b/deps/nghttp2/lib/nghttp2_submit.c
@@ -450,6 +450,13 @@ int nghttp2_session_set_local_window_size(nghttp2_session *session,
if (rv != 0) {
return rv;
}
+
+ if (window_size_increment > 0) {
+ return nghttp2_session_add_window_update(session, 0, stream_id,
+ window_size_increment);
+ }
+
+ return nghttp2_session_update_recv_connection_window_size(session, 0);
} else {
stream = nghttp2_session_get_stream(session, stream_id);
@@ -476,11 +483,14 @@ int nghttp2_session_set_local_window_size(nghttp2_session *session,
if (rv != 0) {
return rv;
}
- }
- if (window_size_increment > 0) {
- return nghttp2_session_add_window_update(session, 0, stream_id,
- window_size_increment);
+ if (window_size_increment > 0) {
+ return nghttp2_session_add_window_update(session, 0, stream_id,
+ window_size_increment);
+ }
+
+ return nghttp2_session_update_recv_stream_window_size(session, stream, 0,
+ 1);
}
return 0;

View File

@@ -0,0 +1,192 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: James M Snell <jasnell@gmail.com>
Date: Mon, 27 Apr 2020 10:47:58 -0700
Subject: http2: implement support for max settings entries
Adds the maxSettings option to limit the number of settings
entries allowed per SETTINGS frame. Default 32
Signed-off-by: James M Snell <jasnell@gmail.com>
Fixes: https://hackerone.com/reports/446662
CVE-ID: CVE-2020-11080
PR-URL: https://github.com/nodejs-private/node-private/pull/206
Reviewed-By: Sam Roberts <vieuxtech@gmail.com>
diff --git a/doc/api/http2.md b/doc/api/http2.md
index c37cc41d422384db6360d75d6b564e1e08c0a714..764c4b74ee013d7a3330599b09afea64a08180a1 100644
--- a/doc/api/http2.md
+++ b/doc/api/http2.md
@@ -1958,6 +1958,8 @@ changes:
* `options` {Object}
* `maxDeflateDynamicTableSize` {number} Sets the maximum dynamic table size
for deflating header fields. **Default:** `4Kib`.
+ * `maxSettings` {number} Sets the maximum number of settings entries per
+ `SETTINGS` frame. The minimum value allowed is `1`. **Default:** `32`.
* `maxSessionMemory`{number} Sets the maximum memory that the `Http2Session`
is permitted to use. The value is expressed in terms of number of megabytes,
e.g. `1` equal 1 megabyte. The minimum value allowed is `1`.
@@ -2073,6 +2075,8 @@ changes:
**Default:** `false`.
* `maxDeflateDynamicTableSize` {number} Sets the maximum dynamic table size
for deflating header fields. **Default:** `4Kib`.
+ * `maxSettings` {number} Sets the maximum number of settings entries per
+ `SETTINGS` frame. The minimum value allowed is `1`. **Default:** `32`.
* `maxSessionMemory`{number} Sets the maximum memory that the `Http2Session`
is permitted to use. The value is expressed in terms of number of megabytes,
e.g. `1` equal 1 megabyte. The minimum value allowed is `1`. This is a
@@ -2154,6 +2158,9 @@ server.listen(80);
<!-- YAML
added: v8.4.0
changes:
+ - version: REPLACEME
+ pr-url: https://github.com/nodejs-private/node-private/pull/204
+ description: Added `maxSettings` option with a default of 32.
- version: v8.9.3
pr-url: https://github.com/nodejs/node/pull/17105
description: Added the `maxOutstandingPings` option with a default limit of
@@ -2168,6 +2175,8 @@ changes:
* `options` {Object}
* `maxDeflateDynamicTableSize` {number} Sets the maximum dynamic table size
for deflating header fields. **Default:** `4Kib`.
+ * `maxSettings` {number} Sets the maximum number of settings entries per
+ `SETTINGS` frame. The minimum value allowed is `1`. **Default:** `32`.
* `maxSessionMemory`{number} Sets the maximum memory that the `Http2Session`
is permitted to use. The value is expressed in terms of number of megabytes,
e.g. `1` equal 1 megabyte. The minimum value allowed is `1`.
diff --git a/lib/internal/http2/util.js b/lib/internal/http2/util.js
index 9cc2a30897b6aed9760b619f98d518fc6ee3ed2a..69a9ea2c64158d26362f6df39e146bcdd4b0dc43 100644
--- a/lib/internal/http2/util.js
+++ b/lib/internal/http2/util.js
@@ -194,7 +194,8 @@ const IDX_OPTIONS_MAX_HEADER_LIST_PAIRS = 5;
const IDX_OPTIONS_MAX_OUTSTANDING_PINGS = 6;
const IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS = 7;
const IDX_OPTIONS_MAX_SESSION_MEMORY = 8;
-const IDX_OPTIONS_FLAGS = 9;
+const IDX_OPTIONS_MAX_SETTINGS = 9;
+const IDX_OPTIONS_FLAGS = 10;
function updateOptionsBuffer(options) {
var flags = 0;
@@ -243,6 +244,11 @@ function updateOptionsBuffer(options) {
optionsBuffer[IDX_OPTIONS_MAX_SESSION_MEMORY] =
Math.max(1, options.maxSessionMemory);
}
+ if (typeof options.maxSettings === 'number') {
+ flags |= (1 << IDX_OPTIONS_MAX_SETTINGS);
+ optionsBuffer[IDX_OPTIONS_MAX_SETTINGS] =
+ Math.max(1, options.maxSettings);
+ }
optionsBuffer[IDX_OPTIONS_FLAGS] = flags;
}
diff --git a/src/node_http2.cc b/src/node_http2.cc
index 358c687768d61682943dbb04ee3bd32c051c321f..cac64a8c885b0363202e7f00b415fe969e4552d3 100644
--- a/src/node_http2.cc
+++ b/src/node_http2.cc
@@ -202,6 +202,12 @@ Http2Options::Http2Options(Environment* env, nghttp2_session_type type) {
if (flags & (1 << IDX_OPTIONS_MAX_SESSION_MEMORY)) {
SetMaxSessionMemory(buffer[IDX_OPTIONS_MAX_SESSION_MEMORY] * 1e6);
}
+
+ if (flags & (1 << IDX_OPTIONS_MAX_SETTINGS)) {
+ nghttp2_option_set_max_settings(
+ options_,
+ static_cast<size_t>(buffer[IDX_OPTIONS_MAX_SETTINGS]));
+ }
}
void Http2Session::Http2Settings::Init() {
diff --git a/src/node_http2_state.h b/src/node_http2_state.h
index 692299a187f60a5e5b7d045b9894abc435a29de8..fd8f1c56607b1457f921382f729abfc6c63bfb0d 100644
--- a/src/node_http2_state.h
+++ b/src/node_http2_state.h
@@ -52,6 +52,7 @@ namespace http2 {
IDX_OPTIONS_MAX_OUTSTANDING_PINGS,
IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS,
IDX_OPTIONS_MAX_SESSION_MEMORY,
+ IDX_OPTIONS_MAX_SETTINGS,
IDX_OPTIONS_FLAGS
};
diff --git a/test/parallel/test-http2-max-settings.js b/test/parallel/test-http2-max-settings.js
new file mode 100644
index 0000000000000000000000000000000000000000..2eae223d21bcc7388cb15969b6a3934acc6b3a7f
--- /dev/null
+++ b/test/parallel/test-http2-max-settings.js
@@ -0,0 +1,35 @@
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
+const http2 = require('http2');
+
+const server = http2.createServer({ maxSettings: 1 });
+
+// TODO(@jasnell): There is still a session event
+// emitted on the server side but it will be destroyed
+// immediately after creation and there will be no
+// stream created.
+server.on('session', common.mustCall((session) => {
+ session.on('stream', common.mustNotCall());
+ session.on('remoteSettings', common.mustNotCall());
+}));
+server.on('stream', common.mustNotCall());
+
+server.listen(0, common.mustCall(() => {
+ // Specify two settings entries when a max of 1 is allowed.
+ // Connection should error immediately.
+ const client = http2.connect(
+ `http://localhost:${server.address().port}`, {
+ settings: {
+ // The actual settings values do not matter.
+ headerTableSize: 1000,
+ enablePush: false,
+ } });
+
+ client.on('error', common.mustCall(() => {
+ server.close();
+ }));
+}));
diff --git a/test/parallel/test-http2-util-update-options-buffer.js b/test/parallel/test-http2-util-update-options-buffer.js
index d9cfa0784926aeb720fccc28f0c1257bf35ac30e..9587d93d3bcbc84aafa669d2c95841df8bacc010 100644
--- a/test/parallel/test-http2-util-update-options-buffer.js
+++ b/test/parallel/test-http2-util-update-options-buffer.js
@@ -22,7 +22,8 @@ const IDX_OPTIONS_MAX_HEADER_LIST_PAIRS = 5;
const IDX_OPTIONS_MAX_OUTSTANDING_PINGS = 6;
const IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS = 7;
const IDX_OPTIONS_MAX_SESSION_MEMORY = 8;
-const IDX_OPTIONS_FLAGS = 9;
+const IDX_OPTIONS_MAX_SETTINGS = 9;
+const IDX_OPTIONS_FLAGS = 10;
{
updateOptionsBuffer({
@@ -34,7 +35,8 @@ const IDX_OPTIONS_FLAGS = 9;
maxHeaderListPairs: 6,
maxOutstandingPings: 7,
maxOutstandingSettings: 8,
- maxSessionMemory: 9
+ maxSessionMemory: 9,
+ maxSettings: 10,
});
strictEqual(optionsBuffer[IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE], 1);
@@ -46,6 +48,7 @@ const IDX_OPTIONS_FLAGS = 9;
strictEqual(optionsBuffer[IDX_OPTIONS_MAX_OUTSTANDING_PINGS], 7);
strictEqual(optionsBuffer[IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS], 8);
strictEqual(optionsBuffer[IDX_OPTIONS_MAX_SESSION_MEMORY], 9);
+ strictEqual(optionsBuffer[IDX_OPTIONS_MAX_SETTINGS], 10);
const flags = optionsBuffer[IDX_OPTIONS_FLAGS];
@@ -57,6 +60,7 @@ const IDX_OPTIONS_FLAGS = 9;
ok(flags & (1 << IDX_OPTIONS_MAX_HEADER_LIST_PAIRS));
ok(flags & (1 << IDX_OPTIONS_MAX_OUTSTANDING_PINGS));
ok(flags & (1 << IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS));
+ ok(flags & (1 << IDX_OPTIONS_MAX_SETTINGS));
}
{

View File

@@ -0,0 +1,125 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= <tniessen@tnie.de>
Date: Mon, 27 Jan 2020 13:38:36 -0400
Subject: napi: fix memory corruption vulnerability
Fixes: https://hackerone.com/reports/784186
CVE-ID: CVE-2020-8174
PR-URL: https://github.com/nodejs-private/node-private/pull/195
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Gabriel Schulhof <gabriel.schulhof@intel.com>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Rich Trott <rtrott@gmail.com>
diff --git a/src/js_native_api_v8.cc b/src/js_native_api_v8.cc
index a87af79a8908321a3f080fb32f9afc62695dcf07..a92bdfec2073f3107a170244ab5183c5263a62da 100644
--- a/src/js_native_api_v8.cc
+++ b/src/js_native_api_v8.cc
@@ -2067,7 +2067,7 @@ napi_status napi_get_value_string_latin1(napi_env env,
if (!buf) {
CHECK_ARG(env, result);
*result = val.As<v8::String>()->Length();
- } else {
+ } else if (bufsize != 0) {
int copied =
val.As<v8::String>()->WriteOneByte(env->isolate,
reinterpret_cast<uint8_t*>(buf),
@@ -2079,6 +2079,8 @@ napi_status napi_get_value_string_latin1(napi_env env,
if (result != nullptr) {
*result = copied;
}
+ } else if (result != nullptr) {
+ *result = 0;
}
return napi_clear_last_error(env);
@@ -2106,7 +2108,7 @@ napi_status napi_get_value_string_utf8(napi_env env,
if (!buf) {
CHECK_ARG(env, result);
*result = val.As<v8::String>()->Utf8Length(env->isolate);
- } else {
+ } else if (bufsize != 0) {
int copied = val.As<v8::String>()->WriteUtf8(
env->isolate,
buf,
@@ -2118,6 +2120,8 @@ napi_status napi_get_value_string_utf8(napi_env env,
if (result != nullptr) {
*result = copied;
}
+ } else if (result != nullptr) {
+ *result = 0;
}
return napi_clear_last_error(env);
@@ -2146,7 +2150,7 @@ napi_status napi_get_value_string_utf16(napi_env env,
CHECK_ARG(env, result);
// V8 assumes UTF-16 length is the same as the number of characters.
*result = val.As<v8::String>()->Length();
- } else {
+ } else if (bufsize != 0) {
int copied = val.As<v8::String>()->Write(env->isolate,
reinterpret_cast<uint16_t*>(buf),
0,
@@ -2157,6 +2161,8 @@ napi_status napi_get_value_string_utf16(napi_env env,
if (result != nullptr) {
*result = copied;
}
+ } else if (result != nullptr) {
+ *result = 0;
}
return napi_clear_last_error(env);
diff --git a/test/js-native-api/test_string/test.js b/test/js-native-api/test_string/test.js
index 1be34212a11c3f163e4b5c444c5320afd09976a5..b03cd0b361204466c673473d2d46dfde31857272 100644
--- a/test/js-native-api/test_string/test.js
+++ b/test/js-native-api/test_string/test.js
@@ -81,3 +81,5 @@ assert.throws(() => {
assert.throws(() => {
test_string.TestLargeUtf16();
}, /^Error: Invalid argument$/);
+
+test_string.TestMemoryCorruption(' '.repeat(64 * 1024));
diff --git a/test/js-native-api/test_string/test_string.c b/test/js-native-api/test_string/test_string.c
index 471678c251677afc81b9c14e47fa5a278d4d696b..e840feeacf2110e330e56fc2d47b0132eb0efc6a 100644
--- a/test/js-native-api/test_string/test_string.c
+++ b/test/js-native-api/test_string/test_string.c
@@ -1,4 +1,5 @@
#include <limits.h> // INT_MAX
+#include <string.h>
#include <js_native_api.h>
#include "../common.h"
@@ -244,6 +245,24 @@ static napi_value TestLargeUtf16(napi_env env, napi_callback_info info) {
return output;
}
+static napi_value TestMemoryCorruption(napi_env env, napi_callback_info info) {
+ size_t argc = 1;
+ napi_value args[1];
+ NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL));
+
+ NAPI_ASSERT(env, argc == 1, "Wrong number of arguments");
+
+ char buf[10] = { 0 };
+ NAPI_CALL(env, napi_get_value_string_utf8(env, args[0], buf, 0, NULL));
+
+ char zero[10] = { 0 };
+ if (memcmp(buf, zero, sizeof(buf)) != 0) {
+ NAPI_CALL(env, napi_throw_error(env, NULL, "Buffer overwritten"));
+ }
+
+ return NULL;
+}
+
EXTERN_C_START
napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor properties[] = {
@@ -258,6 +277,7 @@ napi_value Init(napi_env env, napi_value exports) {
DECLARE_NAPI_PROPERTY("TestLargeUtf8", TestLargeUtf8),
DECLARE_NAPI_PROPERTY("TestLargeLatin1", TestLargeLatin1),
DECLARE_NAPI_PROPERTY("TestLargeUtf16", TestLargeUtf16),
+ DECLARE_NAPI_PROPERTY("TestMemoryCorruption", TestMemoryCorruption),
};
NAPI_CALL(env, napi_define_properties(

View File

@@ -0,0 +1,201 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Fedor Indutny <fedor@indutny.com>
Date: Tue, 17 Mar 2020 20:51:38 -0700
Subject: tls: emit `session` after verifying certificate
Prior to this patch `session` event was emitted after `secure` event on
TLSSocket, but before `secureConnect` event. This is problematic for
`https.Agent` because it must cache session only after verifying the
remote peer's certificate.
Connecting to a server that presents an invalid certificate resulted
in the session being cached after the handshake with the server and
evicted right after a certifiate validation error and socket's
destruction. A request initiated during this narrow window would pick
the faulty session, send it to the malicious server and skip the
verification of the server's certificate.
Fixes: https://hackerone.com/reports/811502
CVE-ID: CVE-2020-8172
PR-URL: https://github.com/nodejs-private/node-private/pull/200
Reviewed-By: Sam Roberts <vieuxtech@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js
index 9c3fe656a7ce806381bc18a33102820b7967d6e5..6c124e7223e567ec3e0323938fabe7b54c9f4b76 100644
--- a/lib/_tls_wrap.js
+++ b/lib/_tls_wrap.js
@@ -67,6 +67,8 @@ const kHandshakeTimeout = Symbol('handshake-timeout');
const kRes = Symbol('res');
const kSNICallback = Symbol('snicallback');
const kEnableTrace = Symbol('enableTrace');
+const kPendingSession = Symbol('pendingSession');
+const kIsVerified = Symbol('verified');
const noop = () => {};
@@ -250,7 +252,11 @@ function requestOCSPDone(socket) {
function onnewsessionclient(sessionId, session) {
debug('client emit session');
const owner = this[owner_symbol];
- owner.emit('session', session);
+ if (owner[kIsVerified]) {
+ owner.emit('session', session);
+ } else {
+ owner[kPendingSession] = session;
+ }
}
function onnewsession(sessionId, session) {
@@ -388,6 +394,8 @@ function TLSSocket(socket, opts) {
this.authorized = false;
this.authorizationError = null;
this[kRes] = null;
+ this[kIsVerified] = false;
+ this[kPendingSession] = null;
var wrap;
if ((socket instanceof net.Socket && socket._handle) || !socket) {
@@ -557,6 +565,8 @@ TLSSocket.prototype._destroySSL = function _destroySSL() {
this.ssl._secureContext.context = null;
}
this.ssl = null;
+ this[kPendingSession] = null;
+ this[kIsVerified] = false;
};
// Constructor guts, arbitrarily factored out.
@@ -1350,6 +1360,12 @@ function onConnectSecure() {
this.emit('secureConnect');
}
+ this[kIsVerified] = true;
+ const session = this[kPendingSession];
+ this[kPendingSession] = null;
+ if (session)
+ this.emit('session', session);
+
this.removeListener('end', onConnectEnd);
}
diff --git a/test/parallel/test-https-agent-session-injection.js b/test/parallel/test-https-agent-session-injection.js
new file mode 100644
index 0000000000000000000000000000000000000000..cb9358b1b1700925e893fa9680fc734b35fb699c
--- /dev/null
+++ b/test/parallel/test-https-agent-session-injection.js
@@ -0,0 +1,59 @@
+'use strict';
+const common = require('../common');
+const assert = require('assert');
+
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
+const https = require('https');
+const fixtures = require('../common/fixtures');
+
+const options = {
+ key: fixtures.readKey('agent1-key.pem'),
+
+ // NOTE: Certificate Common Name is 'agent1'
+ cert: fixtures.readKey('agent1-cert.pem'),
+
+ // NOTE: TLS 1.3 creates new session ticket **after** handshake so
+ // `getSession()` output will be different even if the session was reused
+ // during the handshake.
+ secureProtocol: 'TLSv1_2_method'
+};
+
+const ca = [ fixtures.readKey('ca1-cert.pem') ];
+
+const server = https.createServer(options, function(req, res) {
+ res.end('ok');
+}).listen(0, common.mustCall(function() {
+ const port = this.address().port;
+
+ const req = https.get({
+ port,
+ path: '/',
+ ca,
+ servername: 'nodejs.org',
+ }, common.mustNotCall(() => {}));
+
+ req.on('error', common.mustCall((err) => {
+ assert.strictEqual(
+ err.message,
+ 'Hostname/IP does not match certificate\'s altnames: ' +
+ 'Host: nodejs.org. is not cert\'s CN: agent1');
+
+ const second = https.get({
+ port,
+ path: '/',
+ ca,
+ servername: 'nodejs.org',
+ }, common.mustNotCall(() => {}));
+
+ second.on('error', common.mustCall((err) => {
+ server.close();
+
+ assert.strictEqual(
+ err.message,
+ 'Hostname/IP does not match certificate\'s altnames: ' +
+ 'Host: nodejs.org. is not cert\'s CN: agent1');
+ }));
+ }));
+}));
diff --git a/test/parallel/test-tls-secure-session.js b/test/parallel/test-tls-secure-session.js
new file mode 100644
index 0000000000000000000000000000000000000000..b4b9638a2ccc7a27f63285f42e825fbf342e435d
--- /dev/null
+++ b/test/parallel/test-tls-secure-session.js
@@ -0,0 +1,46 @@
+'use strict';
+const common = require('../common');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+const fixtures = require('../common/fixtures');
+const assert = require('assert');
+const tls = require('tls');
+
+const options = {
+ key: fixtures.readKey('agent1-key.pem'),
+
+ // NOTE: Certificate Common Name is 'agent1'
+ cert: fixtures.readKey('agent1-cert.pem'),
+
+ // NOTE: TLS 1.3 creates new session ticket **after** handshake so
+ // `getSession()` output will be different even if the session was reused
+ // during the handshake.
+ secureProtocol: 'TLSv1_2_method'
+};
+
+const server = tls.createServer(options, common.mustCall((socket) => {
+ socket.end();
+})).listen(0, common.mustCall(() => {
+ let connected = false;
+ let session = null;
+
+ const client = tls.connect({
+ rejectUnauthorized: false,
+ port: server.address().port,
+ }, common.mustCall(() => {
+ assert(!connected);
+ assert(!session);
+
+ connected = true;
+ }));
+
+ client.on('session', common.mustCall((newSession) => {
+ assert(connected);
+ assert(!session);
+
+ session = newSession;
+
+ client.end();
+ server.close();
+ }));
+}));