mirror of
https://github.com/electron/electron.git
synced 2026-04-10 03:01:51 -04:00
chore: roll latest Node.js security release (#23945)
This commit is contained in:
@@ -36,3 +36,7 @@ weakrefs_split_out_finalizationregistry_cleanupsome.patch
|
||||
fix_window_c-ares_incompatibilities.patch
|
||||
chore_sethostcleanupfinalizationgroupcallback_has_been_removed_from.patch
|
||||
win_use_rtlgenrandom_from_advapi32_dll_directly.patch
|
||||
tls_emit_session_after_verifying_certificate.patch
|
||||
http2_implement_support_for_max_settings_entries.patch
|
||||
deps_update_nghttp2_to_1_41_0.patch
|
||||
napi_fix_memory_corruption_vulnerability.patch
|
||||
|
||||
496
patches/node/deps_update_nghttp2_to_1_41_0.patch
Normal file
496
patches/node/deps_update_nghttp2_to_1_41_0.patch
Normal file
@@ -0,0 +1,496 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: James M Snell <jasnell@gmail.com>
|
||||
Date: Tue, 5 May 2020 13:18:16 -0700
|
||||
Subject: deps: update nghttp2 to 1.41.0
|
||||
|
||||
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/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;
|
||||
@@ -0,0 +1,212 @@
|
||||
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 8a4d3445008a6f424516d29ade6f4b814c39bd11..08884feab9e5e8ac0a7111078ef77d98c04e8b51 100644
|
||||
--- a/doc/api/http2.md
|
||||
+++ b/doc/api/http2.md
|
||||
@@ -1948,6 +1948,9 @@ error will be thrown.
|
||||
<!-- 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: v12.16.0
|
||||
pr-url: https://github.com/nodejs/node/pull/30534
|
||||
description: Added `maxSessionRejectedStreams` option with a default of 100.
|
||||
@@ -1975,6 +1978,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`.
|
||||
@@ -2078,6 +2083,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: v12.16.0
|
||||
pr-url: https://github.com/nodejs/node/pull/30534
|
||||
description: Added `maxSessionRejectedStreams` option with a default of 100.
|
||||
@@ -2105,6 +2113,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
|
||||
@@ -2195,6 +2205,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
|
||||
@@ -2213,6 +2226,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 dcc1355a3230fdc38b1b75ad4ceabaee3a78d7e2..d069f726db7795c96f3345d820337d242caa5f9b 100644
|
||||
--- a/lib/internal/http2/util.js
|
||||
+++ b/lib/internal/http2/util.js
|
||||
@@ -203,7 +203,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) {
|
||||
let flags = 0;
|
||||
@@ -252,6 +253,11 @@ function updateOptionsBuffer(options) {
|
||||
optionsBuffer[IDX_OPTIONS_MAX_SESSION_MEMORY] =
|
||||
MathMax(1, options.maxSessionMemory);
|
||||
}
|
||||
+ if (typeof options.maxSettings === 'number') {
|
||||
+ flags |= (1 << IDX_OPTIONS_MAX_SETTINGS);
|
||||
+ optionsBuffer[IDX_OPTIONS_MAX_SETTINGS] =
|
||||
+ MathMax(1, options.maxSettings);
|
||||
+ }
|
||||
optionsBuffer[IDX_OPTIONS_FLAGS] = flags;
|
||||
}
|
||||
|
||||
diff --git a/src/node_http2.cc b/src/node_http2.cc
|
||||
index 0eebe2935e248bbdc4d0f17a93dfd856430ed0e0..de296fe6453ab897a68333d8085e0ad989db5459 100644
|
||||
--- a/src/node_http2.cc
|
||||
+++ b/src/node_http2.cc
|
||||
@@ -203,6 +203,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));
|
||||
}
|
||||
|
||||
{
|
||||
125
patches/node/napi_fix_memory_corruption_vulnerability.patch
Normal file
125
patches/node/napi_fix_memory_corruption_vulnerability.patch
Normal 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 f232248c4e12d32379b4b9c81fbbad98f3979fc5..ac60f4727b3c3868c32e96e5d89577f309930be2 100644
|
||||
--- a/src/js_native_api_v8.cc
|
||||
+++ b/src/js_native_api_v8.cc
|
||||
@@ -2143,7 +2143,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),
|
||||
@@ -2155,6 +2155,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);
|
||||
@@ -2182,7 +2184,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,
|
||||
@@ -2194,6 +2196,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);
|
||||
@@ -2222,7 +2226,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,
|
||||
@@ -2233,6 +2237,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(
|
||||
201
patches/node/tls_emit_session_after_verifying_certificate.patch
Normal file
201
patches/node/tls_emit_session_after_verifying_certificate.patch
Normal 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 c8ccbdb84286a49e9d311f1bf335bd87410a302d..6ae333ddd05f9b10445cc998f9c2e6b6e97bce74 100644
|
||||
--- a/lib/_tls_wrap.js
|
||||
+++ b/lib/_tls_wrap.js
|
||||
@@ -82,6 +82,8 @@ const kSNICallback = Symbol('snicallback');
|
||||
const kEnableTrace = Symbol('enableTrace');
|
||||
const kPskCallback = Symbol('pskcallback');
|
||||
const kPskIdentityHint = Symbol('pskidentityhint');
|
||||
+const kPendingSession = Symbol('pendingSession');
|
||||
+const kIsVerified = Symbol('verified');
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
@@ -265,7 +267,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) {
|
||||
@@ -464,6 +470,8 @@ function TLSSocket(socket, opts) {
|
||||
this.authorized = false;
|
||||
this.authorizationError = null;
|
||||
this[kRes] = null;
|
||||
+ this[kIsVerified] = false;
|
||||
+ this[kPendingSession] = null;
|
||||
|
||||
let wrap;
|
||||
if ((socket instanceof net.Socket && socket._handle) || !socket) {
|
||||
@@ -634,6 +642,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.
|
||||
@@ -1503,6 +1513,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();
|
||||
+ }));
|
||||
+}));
|
||||
Reference in New Issue
Block a user