diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bae745f --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +SOURCES/nghttp2-1.33.0.tar.xz diff --git a/.nghttp2.metadata b/.nghttp2.metadata new file mode 100644 index 0000000..3bade3d --- /dev/null +++ b/.nghttp2.metadata @@ -0,0 +1 @@ +0a11f7de6ea8e66fbecc9fe4ddc61b6ab40af469 SOURCES/nghttp2-1.33.0.tar.xz diff --git a/SOURCES/nghttp2-1.31.1-CVE-2019-9511-and-CVE-2019-9513.patch b/SOURCES/nghttp2-1.31.1-CVE-2019-9511-and-CVE-2019-9513.patch new file mode 100644 index 0000000..b252d57 --- /dev/null +++ b/SOURCES/nghttp2-1.31.1-CVE-2019-9511-and-CVE-2019-9513.patch @@ -0,0 +1,454 @@ +From 4b7aefd8fd1612d455f2f128c09230335ed0cee6 Mon Sep 17 00:00:00 2001 +From: Tatsuhiro Tsujikawa +Date: Tue, 6 Aug 2019 20:48:50 +0900 +Subject: [PATCH 1/3] nghttpx: Fix request stall + +Fix request stall if backend connection is reused and buffer is full. + +Upstream-commit: db2f612a30d54aa152ce5530fa1d683738baa4d1 +Signed-off-by: Kamil Dudka +--- + integration-tests/nghttpx_http1_test.go | 29 +++++++++++++++++++++++++ + integration-tests/server_tester.go | 4 +++- + src/shrpx_downstream.cc | 12 +++++++++- + src/shrpx_downstream.h | 4 ++++ + src/shrpx_http_downstream_connection.cc | 16 +++++++++++++- + src/shrpx_https_upstream.cc | 4 +--- + 6 files changed, 63 insertions(+), 6 deletions(-) + +diff --git a/integration-tests/nghttpx_http1_test.go b/integration-tests/nghttpx_http1_test.go +index a765333..3d41677 100644 +--- a/integration-tests/nghttpx_http1_test.go ++++ b/integration-tests/nghttpx_http1_test.go +@@ -625,6 +625,35 @@ func TestH1H1HTTPSRedirectPort(t *testing.T) { + } + } + ++// TestH1H1POSTRequests tests that server can handle 2 requests with ++// request body. ++func TestH1H1POSTRequests(t *testing.T) { ++ st := newServerTester(nil, t, noopHandler) ++ defer st.Close() ++ ++ res, err := st.http1(requestParam{ ++ name: "TestH1H1POSTRequestsNo1", ++ body: make([]byte, 1), ++ }) ++ if err != nil { ++ t.Fatalf("Error st.http1() = %v", err) ++ } ++ if got, want := res.status, 200; got != want { ++ t.Errorf("res.status: %v; want %v", got, want) ++ } ++ ++ res, err = st.http1(requestParam{ ++ name: "TestH1H1POSTRequestsNo2", ++ body: make([]byte, 65536), ++ }) ++ if err != nil { ++ t.Fatalf("Error st.http1() = %v", err) ++ } ++ if got, want := res.status, 200; got != want { ++ t.Errorf("res.status: %v; want %v", got, want) ++ } ++} ++ + // // TestH1H2ConnectFailure tests that server handles the situation that + // // connection attempt to HTTP/2 backend failed. + // func TestH1H2ConnectFailure(t *testing.T) { +diff --git a/integration-tests/server_tester.go b/integration-tests/server_tester.go +index d145519..1156986 100644 +--- a/integration-tests/server_tester.go ++++ b/integration-tests/server_tester.go +@@ -662,7 +662,9 @@ func cloneHeader(h http.Header) http.Header { + return h2 + } + +-func noopHandler(w http.ResponseWriter, r *http.Request) {} ++func noopHandler(w http.ResponseWriter, r *http.Request) { ++ ioutil.ReadAll(r.Body) ++} + + type APIResponse struct { + Status string `json:"status,omitempty"` +diff --git a/src/shrpx_downstream.cc b/src/shrpx_downstream.cc +index 360a9a9..48db65b 100644 +--- a/src/shrpx_downstream.cc ++++ b/src/shrpx_downstream.cc +@@ -144,7 +144,8 @@ Downstream::Downstream(Upstream *upstream, MemchunkPool *mcpool, + request_header_sent_(false), + accesslog_written_(false), + new_affinity_cookie_(false), +- blocked_request_data_eof_(false) { ++ blocked_request_data_eof_(false), ++ expect_100_continue_(false) { + + auto &timeoutconf = get_config()->http2.timeout; + +@@ -807,6 +808,11 @@ void Downstream::inspect_http1_request() { + chunked_request_ = true; + } + } ++ ++ auto expect = req_.fs.header(http2::HD_EXPECT); ++ expect_100_continue_ = ++ expect && ++ util::strieq(expect->value, StringRef::from_lit("100-continue")); + } + + void Downstream::inspect_http1_response() { +@@ -1103,4 +1109,8 @@ bool Downstream::get_blocked_request_data_eof() const { + return blocked_request_data_eof_; + } + ++bool Downstream::get_expect_100_continue() const { ++ return expect_100_continue_; ++} ++ + } // namespace shrpx +diff --git a/src/shrpx_downstream.h b/src/shrpx_downstream.h +index c81fcf6..b9a851f 100644 +--- a/src/shrpx_downstream.h ++++ b/src/shrpx_downstream.h +@@ -466,6 +466,8 @@ public: + EVENT_TIMEOUT = 0x2, + }; + ++ bool get_expect_100_continue() const; ++ + enum { + DISPATCH_NONE, + DISPATCH_PENDING, +@@ -556,6 +558,8 @@ private: + // true if eof is received from client before sending header fields + // to backend. + bool blocked_request_data_eof_; ++ // true if request contains "expect: 100-continue" header field. ++ bool expect_100_continue_; + }; + + } // namespace shrpx +diff --git a/src/shrpx_http_downstream_connection.cc b/src/shrpx_http_downstream_connection.cc +index f50c0f4..85ca947 100644 +--- a/src/shrpx_http_downstream_connection.cc ++++ b/src/shrpx_http_downstream_connection.cc +@@ -698,7 +698,8 @@ int HttpDownstreamConnection::push_request_headers() { + // signal_write() when we received request body chunk, and it + // enables us to send headers and data in one writev system call. + if (connect_method || downstream_->get_blocked_request_buf()->rleft() || +- (!req.http2_expect_body && req.fs.content_length == 0)) { ++ (!req.http2_expect_body && req.fs.content_length == 0) || ++ downstream_->get_expect_100_continue()) { + signal_write(); + } + +@@ -1172,6 +1173,19 @@ int HttpDownstreamConnection::write_reuse_first() { + + reuse_first_write_done_ = true; + ++ // upstream->resume_read() might be called in ++ // write_tls()/write_clear(), but before blocked_request_buf_ is ++ // reset. So upstream read might still be blocked. Let's do it ++ // again here. ++ auto input = downstream_->get_request_buf(); ++ if (input->rleft() == 0) { ++ auto upstream = downstream_->get_upstream(); ++ auto &req = downstream_->request(); ++ ++ upstream->resume_read(SHRPX_NO_BUFFER, downstream_, ++ req.unconsumed_body_length); ++ } ++ + return 0; + } + +diff --git a/src/shrpx_https_upstream.cc b/src/shrpx_https_upstream.cc +index 452ec90..96ca2cd 100644 +--- a/src/shrpx_https_upstream.cc ++++ b/src/shrpx_https_upstream.cc +@@ -467,9 +467,7 @@ int htp_hdrs_completecb(http_parser *htp) { + // and let them decide whether responds with 100 Continue or not. + // For alternative mode, we have no backend, so just send 100 + // Continue here to make the client happy. +- auto expect = req.fs.header(http2::HD_EXPECT); +- if (expect && +- util::strieq(expect->value, StringRef::from_lit("100-continue"))) { ++ if (downstream->get_expect_100_continue()) { + auto output = downstream->get_response_buf(); + constexpr auto res = StringRef::from_lit("HTTP/1.1 100 Continue\r\n\r\n"); + output->append(res); +-- +2.20.1 + + +From 589a98eba0b3c7a4dbb2262c60b609cac2b1f838 Mon Sep 17 00:00:00 2001 +From: Tatsuhiro Tsujikawa +Date: Tue, 25 Jun 2019 22:33:35 +0900 +Subject: [PATCH 2/3] Add nghttp2_option_set_max_outbound_ack + +Upstream-commit: a76d0723b5f52902139ff453e0ec840673e86e75 +Signed-off-by: Kamil Dudka +--- + doc/Makefile.am | 1 + + lib/includes/nghttp2/nghttp2.h | 11 +++++++++++ + lib/nghttp2_option.c | 5 +++++ + lib/nghttp2_option.h | 5 +++++ + lib/nghttp2_session.c | 9 +++++++-- + lib/nghttp2_session.h | 8 ++++++-- + tests/nghttp2_session_test.c | 4 ++-- + 7 files changed, 37 insertions(+), 6 deletions(-) + +diff --git a/doc/Makefile.am b/doc/Makefile.am +index 07cd34e..66e5ba3 100644 +--- a/doc/Makefile.am ++++ b/doc/Makefile.am +@@ -67,6 +67,7 @@ APIDOCS= \ + nghttp2_option_set_no_recv_client_magic.rst \ + nghttp2_option_set_peer_max_concurrent_streams.rst \ + nghttp2_option_set_user_recv_extension_type.rst \ ++ nghttp2_option_set_max_outbound_ack.rst \ + nghttp2_pack_settings_payload.rst \ + nghttp2_priority_spec_check_default.rst \ + nghttp2_priority_spec_default_init.rst \ +diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h +index 14f8950..137a675 100644 +--- a/lib/includes/nghttp2/nghttp2.h ++++ b/lib/includes/nghttp2/nghttp2.h +@@ -2637,6 +2637,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 + * +diff --git a/lib/nghttp2_option.c b/lib/nghttp2_option.c +index aec5dcf..ae22493 100644 +--- a/lib/nghttp2_option.c ++++ b/lib/nghttp2_option.c +@@ -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; ++} +diff --git a/lib/nghttp2_option.h b/lib/nghttp2_option.h +index c743e33..86d31f7 100644 +--- a/lib/nghttp2_option.h ++++ b/lib/nghttp2_option.h +@@ -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; + + /** +@@ -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. +diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c +index c58f059..8628cc7 100644 +--- a/lib/nghttp2_session.c ++++ b/lib/nghttp2_session.c +@@ -457,6 +457,7 @@ static int session_new(nghttp2_session **session_ptr, + (*session_ptr)->remote_settings.max_concurrent_streams = 100; + + (*session_ptr)->max_send_header_block_length = NGHTTP2_MAX_HEADERSLEN; ++ (*session_ptr)->max_outbound_ack = NGHTTP2_DEFAULT_MAX_OBQ_FLOOD_ITEM; + + if (option) { + if ((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE) && +@@ -516,6 +517,10 @@ static int session_new(nghttp2_session **session_ptr, + option->no_closed_streams) { + (*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_NO_CLOSED_STREAMS; + } ++ ++ if (option->opt_set_mask & NGHTTP2_OPT_MAX_OUTBOUND_ACK) { ++ (*session_ptr)->max_outbound_ack = option->max_outbound_ack; ++ } + } + + rv = nghttp2_hd_deflate_init2(&(*session_ptr)->hd_deflater, +@@ -6831,7 +6836,7 @@ int nghttp2_session_add_ping(nghttp2_session *session, uint8_t flags, + mem = &session->mem; + + if ((flags & NGHTTP2_FLAG_ACK) && +- session->obq_flood_counter_ >= NGHTTP2_MAX_OBQ_FLOOD_ITEM) { ++ session->obq_flood_counter_ >= session->max_outbound_ack) { + return NGHTTP2_ERR_FLOODED; + } + +@@ -6976,7 +6981,7 @@ int nghttp2_session_add_settings(nghttp2_session *session, uint8_t flags, + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + +- if (session->obq_flood_counter_ >= NGHTTP2_MAX_OBQ_FLOOD_ITEM) { ++ if (session->obq_flood_counter_ >= session->max_outbound_ack) { + return NGHTTP2_ERR_FLOODED; + } + } +diff --git a/lib/nghttp2_session.h b/lib/nghttp2_session.h +index c7cb27d..d9e2846 100644 +--- a/lib/nghttp2_session.h ++++ b/lib/nghttp2_session.h +@@ -97,7 +97,7 @@ typedef struct { + response frames are stacked up, which leads to memory exhaustion. + The value selected here is arbitrary, but safe value and if we have + these frames in this number, it is considered suspicious. */ +-#define NGHTTP2_MAX_OBQ_FLOOD_ITEM 10000 ++#define NGHTTP2_DEFAULT_MAX_OBQ_FLOOD_ITEM 1000 + + /* The default value of maximum number of concurrent streams. */ + #define NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS 0xffffffffu +@@ -260,8 +260,12 @@ struct nghttp2_session { + size_t num_idle_streams; + /* The number of bytes allocated for nvbuf */ + size_t nvbuflen; +- /* Counter for detecting flooding in outbound queue */ ++ /* Counter for detecting flooding in outbound queue. If it exceeds ++ max_outbound_ack, session will be closed. */ + size_t obq_flood_counter_; ++ /* The maximum number of outgoing SETTINGS ACK and PING ACK in ++ outbound queue. */ ++ size_t max_outbound_ack; + /* 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; +diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c +index 783b0ed..debec59 100644 +--- a/tests/nghttp2_session_test.c ++++ b/tests/nghttp2_session_test.c +@@ -9894,7 +9894,7 @@ void test_nghttp2_session_flooding(void) { + + buf = &bufs.head->buf; + +- for (i = 0; i < NGHTTP2_MAX_OBQ_FLOOD_ITEM; ++i) { ++ for (i = 0; i < NGHTTP2_DEFAULT_MAX_OBQ_FLOOD_ITEM; ++i) { + CU_ASSERT( + (ssize_t)nghttp2_buf_len(buf) == + nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf))); +@@ -9916,7 +9916,7 @@ void test_nghttp2_session_flooding(void) { + + buf = &bufs.head->buf; + +- for (i = 0; i < NGHTTP2_MAX_OBQ_FLOOD_ITEM; ++i) { ++ for (i = 0; i < NGHTTP2_DEFAULT_MAX_OBQ_FLOOD_ITEM; ++i) { + CU_ASSERT( + (ssize_t)nghttp2_buf_len(buf) == + nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf))); +-- +2.20.1 + + +From e32b3e4c9df4abb83ca1c1c41901fadbae44699b Mon Sep 17 00:00:00 2001 +From: Tatsuhiro Tsujikawa +Date: Tue, 25 Jun 2019 22:38:43 +0900 +Subject: [PATCH 3/3] Don't read too greedily + +Upstream-commit: 83d362c6d21f76599b86e7b94cd1992288a1d43c +Signed-off-by: Kamil Dudka +--- + src/HttpServer.cc | 2 ++ + src/shrpx_client_handler.cc | 9 +++++++-- + 2 files changed, 9 insertions(+), 2 deletions(-) + +diff --git a/src/HttpServer.cc b/src/HttpServer.cc +index b3e35ef..a75cee4 100644 +--- a/src/HttpServer.cc ++++ b/src/HttpServer.cc +@@ -650,6 +650,7 @@ int Http2Handler::read_clear() { + } + return -1; + } ++ break; + } + + return write_(*this); +@@ -775,6 +776,7 @@ int Http2Handler::read_tls() { + } + return -1; + } ++ break; + } + + fin: +diff --git a/src/shrpx_client_handler.cc b/src/shrpx_client_handler.cc +index 21430dd..fa1fc87 100644 +--- a/src/shrpx_client_handler.cc ++++ b/src/shrpx_client_handler.cc +@@ -111,6 +111,7 @@ void writecb(struct ev_loop *loop, ev_io *w, int revents) { + int ClientHandler::noop() { return 0; } + + int ClientHandler::read_clear() { ++ auto should_break = false; + rb_.ensure_chunk(); + for (;;) { + if (rb_.rleft() && on_read() != 0) { +@@ -123,7 +124,7 @@ int ClientHandler::read_clear() { + return 0; + } + +- if (!ev_is_active(&conn_.rev)) { ++ if (!ev_is_active(&conn_.rev) || should_break) { + return 0; + } + +@@ -141,6 +142,7 @@ int ClientHandler::read_clear() { + } + + rb_.write(nread); ++ should_break = true; + } + } + +@@ -205,6 +207,8 @@ int ClientHandler::tls_handshake() { + } + + int ClientHandler::read_tls() { ++ auto should_break = false; ++ + ERR_clear_error(); + + rb_.ensure_chunk(); +@@ -221,7 +225,7 @@ int ClientHandler::read_tls() { + return 0; + } + +- if (!ev_is_active(&conn_.rev)) { ++ if (!ev_is_active(&conn_.rev) || should_break) { + return 0; + } + +@@ -239,6 +243,7 @@ int ClientHandler::read_tls() { + } + + rb_.write(nread); ++ should_break = true; + } + } + +-- +2.20.1 + diff --git a/SOURCES/nghttp2-1.33.0-CVE-2020-11080.patch b/SOURCES/nghttp2-1.33.0-CVE-2020-11080.patch new file mode 100644 index 0000000..f2a11e4 --- /dev/null +++ b/SOURCES/nghttp2-1.33.0-CVE-2020-11080.patch @@ -0,0 +1,340 @@ +From 34670cfbc56f1c63ec046c38b9ad518010b5c84d Mon Sep 17 00:00:00 2001 +From: James M Snell +Date: Fri, 17 Apr 2020 16:53:51 -0700 +Subject: [PATCH 1/2] Implement max settings option + +Upstream-commit: 336a98feb0d56b9ac54e12736b18785c27f75090 +Signed-off-by: Kamil Dudka +--- + doc/CMakeLists.txt | 1 + + doc/Makefile.am | 1 + + lib/includes/nghttp2/nghttp2.h | 23 +++++++++++++ + lib/nghttp2_helper.c | 2 ++ + lib/nghttp2_option.c | 5 +++ + lib/nghttp2_option.h | 5 +++ + lib/nghttp2_session.c | 21 ++++++++++++ + lib/nghttp2_session.h | 2 ++ + tests/main.c | 2 ++ + tests/nghttp2_session_test.c | 61 ++++++++++++++++++++++++++++++++++ + tests/nghttp2_session_test.h | 1 + + 11 files changed, 124 insertions(+) + +diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt +index 34c0279..f3aec84 100644 +--- a/doc/CMakeLists.txt ++++ b/doc/CMakeLists.txt +@@ -42,6 +42,7 @@ set(APIDOCS + nghttp2_option_set_no_recv_client_magic.rst + nghttp2_option_set_peer_max_concurrent_streams.rst + nghttp2_option_set_user_recv_extension_type.rst ++ nghttp2_option_set_max_settings.rst + nghttp2_pack_settings_payload.rst + nghttp2_priority_spec_check_default.rst + nghttp2_priority_spec_default_init.rst +diff --git a/doc/Makefile.am b/doc/Makefile.am +index c17d933..5a58f8e 100644 +--- a/doc/Makefile.am ++++ b/doc/Makefile.am +@@ -68,6 +68,7 @@ APIDOCS= \ + nghttp2_option_set_peer_max_concurrent_streams.rst \ + nghttp2_option_set_user_recv_extension_type.rst \ + nghttp2_option_set_max_outbound_ack.rst \ ++ nghttp2_option_set_max_settings.rst \ + nghttp2_pack_settings_payload.rst \ + nghttp2_priority_spec_check_default.rst \ + nghttp2_priority_spec_default_init.rst \ +diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h +index d79bf48..b0ff6c5 100644 +--- a/lib/includes/nghttp2/nghttp2.h ++++ b/lib/includes/nghttp2/nghttp2.h +@@ -222,6 +222,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 + * +@@ -392,6 +399,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., +@@ -2648,6 +2660,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/lib/nghttp2_helper.c b/lib/nghttp2_helper.c +index 3b282c7..49bbf07 100644 +--- a/lib/nghttp2_helper.c ++++ b/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/lib/nghttp2_option.c b/lib/nghttp2_option.c +index e53f22d..34348e6 100644 +--- a/lib/nghttp2_option.c ++++ b/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/lib/nghttp2_option.h b/lib/nghttp2_option.h +index 1f740aa..939729f 100644 +--- a/lib/nghttp2_option.h ++++ b/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/lib/nghttp2_session.c b/lib/nghttp2_session.c +index 670f83f..7638823 100644 +--- a/lib/nghttp2_session.c ++++ b/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, +@@ -5658,6 +5664,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); + +@@ -7413,6 +7429,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/lib/nghttp2_session.h b/lib/nghttp2_session.h +index 2969364..e62a3bb 100644 +--- a/lib/nghttp2_session.h ++++ b/lib/nghttp2_session.h +@@ -269,6 +269,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, +diff --git a/tests/main.c b/tests/main.c +index 13865de..1f795cd 100644 +--- a/tests/main.c ++++ b/tests/main.c +@@ -313,6 +313,8 @@ int main() { + test_nghttp2_session_set_local_window_size) || + !CU_add_test(pSuite, "session_cancel_from_before_frame_send", + test_nghttp2_session_cancel_from_before_frame_send) || ++ !CU_add_test(pSuite, "session_too_many_settings", ++ test_nghttp2_session_too_many_settings) || + !CU_add_test(pSuite, "session_removed_closed_stream", + test_nghttp2_session_removed_closed_stream) || + !CU_add_test(pSuite, "session_pause_data", +diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c +index 0013e92..ab76ab4 100644 +--- a/tests/nghttp2_session_test.c ++++ b/tests/nghttp2_session_test.c +@@ -10450,6 +10450,67 @@ void test_nghttp2_session_cancel_from_before_frame_send(void) { + nghttp2_session_del(session); + } + ++void test_nghttp2_session_too_many_settings(void) { ++ nghttp2_session *session; ++ nghttp2_option *option; ++ nghttp2_session_callbacks callbacks; ++ nghttp2_frame frame; ++ nghttp2_bufs bufs; ++ nghttp2_buf *buf; ++ ssize_t rv; ++ my_user_data ud; ++ nghttp2_settings_entry iv[3]; ++ nghttp2_mem *mem; ++ nghttp2_outbound_item *item; ++ ++ mem = nghttp2_mem_default(); ++ frame_pack_bufs_init(&bufs); ++ ++ memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); ++ callbacks.on_frame_recv_callback = on_frame_recv_callback; ++ callbacks.send_callback = null_send_callback; ++ ++ nghttp2_option_new(&option); ++ nghttp2_option_set_max_settings(option, 1); ++ ++ nghttp2_session_client_new2(&session, &callbacks, &ud, option); ++ ++ CU_ASSERT(1 == session->max_settings); ++ ++ nghttp2_option_del(option); ++ ++ iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; ++ iv[0].value = 3000; ++ ++ iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; ++ iv[1].value = 16384; ++ ++ nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 2), ++ 2); ++ ++ rv = nghttp2_frame_pack_settings(&bufs, &frame.settings); ++ ++ CU_ASSERT(0 == rv); ++ CU_ASSERT(nghttp2_bufs_len(&bufs) > 0); ++ ++ nghttp2_frame_settings_free(&frame.settings, mem); ++ ++ buf = &bufs.head->buf; ++ assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf)); ++ ++ ud.frame_recv_cb_called = 0; ++ ++ rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); ++ CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); ++ ++ item = nghttp2_session_get_next_ob_item(session); ++ CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type); ++ ++ nghttp2_bufs_reset(&bufs); ++ nghttp2_bufs_free(&bufs); ++ nghttp2_session_del(session); ++} ++ + static void + prepare_session_removed_closed_stream(nghttp2_session *session, + nghttp2_hd_deflater *deflater) { +diff --git a/tests/nghttp2_session_test.h b/tests/nghttp2_session_test.h +index 35a48b8..c5095c2 100644 +--- a/tests/nghttp2_session_test.h ++++ b/tests/nghttp2_session_test.h +@@ -155,6 +155,7 @@ void test_nghttp2_session_repeated_priority_change(void); + void test_nghttp2_session_repeated_priority_submission(void); + void test_nghttp2_session_set_local_window_size(void); + void test_nghttp2_session_cancel_from_before_frame_send(void); ++void test_nghttp2_session_too_many_settings(void); + void test_nghttp2_session_removed_closed_stream(void); + void test_nghttp2_session_pause_data(void); + void test_nghttp2_session_no_closed_streams(void); +-- +2.21.3 + + +From da3f4a5730ffa015a9e2d62e6e876a02f1dced20 Mon Sep 17 00:00:00 2001 +From: James M Snell +Date: Sun, 19 Apr 2020 09:12:24 -0700 +Subject: [PATCH 2/2] Earlier check for settings flood + +Upstream-commit: f8da73bd042f810f34d19f9eae02b46d870af394 +Signed-off-by: Kamil Dudka +--- + lib/nghttp2_session.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c +index 7638823..8271198 100644 +--- a/lib/nghttp2_session.c ++++ b/lib/nghttp2_session.c +@@ -5654,6 +5654,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) { +-- +2.21.3 + diff --git a/SPECS/nghttp2.spec b/SPECS/nghttp2.spec new file mode 100644 index 0000000..26a5c36 --- /dev/null +++ b/SPECS/nghttp2.spec @@ -0,0 +1,316 @@ +Summary: Experimental HTTP/2 client, server and proxy +Name: nghttp2 +Version: 1.33.0 +Release: 3%{?dist}.1 +License: MIT +Group: Applications/Internet +URL: https://nghttp2.org/ +Source0: https://github.com/tatsuhiro-t/nghttp2/releases/download/v%{version}/nghttp2-%{version}.tar.xz + +# backport security fixes from nghttp2-1.39.2 (CVE-2019-9511 and CVE-2019-9513) +Patch1: nghttp2-1.31.1-CVE-2019-9511-and-CVE-2019-9513.patch + +# prevent DoS caused by overly large SETTINGS frames (CVE-2020-11080) +Patch2: nghttp2-1.33.0-CVE-2020-11080.patch + +BuildRequires: automake +BuildRequires: libtool + +BuildRequires: CUnit-devel +BuildRequires: c-ares-devel +BuildRequires: gcc-c++ +BuildRequires: libev-devel +BuildRequires: openssl-devel +BuildRequires: python3-devel +BuildRequires: systemd-devel +BuildRequires: zlib-devel + +Requires: libnghttp2%{?_isa} = %{version}-%{release} +%{?systemd_requires} + +%description +This package contains the HTTP/2 client, server and proxy programs. + + +%package -n libnghttp2 +Summary: A library implementing the HTTP/2 protocol +Group: Development/Libraries + +%description -n libnghttp2 +libnghttp2 is a library implementing the Hypertext Transfer Protocol +version 2 (HTTP/2) protocol in C. + + +%package -n libnghttp2-devel +Summary: Files needed for building applications with libnghttp2 +Group: Development/Libraries +Requires: libnghttp2%{?_isa} = %{version}-%{release} +Requires: pkgconfig + +%description -n libnghttp2-devel +The libnghttp2-devel package includes libraries and header files needed +for building applications with libnghttp2. + + +%prep +%setup -q +%patch1 -p1 +%patch2 -p1 +autoreconf -fiv + +# make fetch-ocsp-response use Python 3 +sed -e '1 s|^#!/.*python|&3|' -i script/fetch-ocsp-response + +%build +%configure PYTHON=%{__python3} \ + --disable-python-bindings \ + --disable-static \ + --without-libxml2 \ + --without-spdylay + +# avoid using rpath +sed -i libtool \ + -e 's/^runpath_var=.*/runpath_var=/' \ + -e 's/^hardcode_libdir_flag_spec=".*"$/hardcode_libdir_flag_spec=""/' + +make %{?_smp_mflags} V=1 + + +%install +%make_install +install -D -m0444 -p contrib/nghttpx.service \ + "$RPM_BUILD_ROOT%{_unitdir}/nghttpx.service" + +# not needed on Fedora/RHEL +rm -f "$RPM_BUILD_ROOT%{_libdir}/libnghttp2.la" + +# will be installed via %%doc +rm -f "$RPM_BUILD_ROOT%{_datadir}/doc/nghttp2/README.rst" + +%ldconfig_scriptlets -n libnghttp2 + +%post +%systemd_post nghttpx.service + +%postun +%systemd_postun nghttpx.service + + +%check +# test the just built library instead of the system one, without using rpath +export "LD_LIBRARY_PATH=$RPM_BUILD_ROOT%{_libdir}:$LD_LIBRARY_PATH" +make %{?_smp_mflags} check + + +%files +%{_bindir}/h2load +%{_bindir}/nghttp +%{_bindir}/nghttpd +%{_bindir}/nghttpx +%{_datadir}/nghttp2 +%{_mandir}/man1/h2load.1* +%{_mandir}/man1/nghttp.1* +%{_mandir}/man1/nghttpd.1* +%{_mandir}/man1/nghttpx.1* +%{_unitdir}/nghttpx.service + +%files -n libnghttp2 +%{_libdir}/libnghttp2.so.* +%{!?_licensedir:%global license %%doc} +%license COPYING + +%files -n libnghttp2-devel +%{_includedir}/nghttp2 +%{_libdir}/pkgconfig/libnghttp2.pc +%{_libdir}/libnghttp2.so +%doc README.rst + + +%changelog +* Tue Jun 09 2020 Kamil Dudka 1.33.0-3.el8_2.1 +- prevent DoS caused by overly large SETTINGS frames (CVE-2020-11080) + +* Wed Aug 28 2019 Kamil Dudka 1.33.0-3 +- rebuild to trigger gating (#1681044) + +* Mon Aug 19 2019 Kamil Dudka 1.33.0-2 +- backport security fixes from nghttp2-1.39.2 (CVE-2019-9511 and CVE-2019-9513) + +* Tue Oct 09 2018 Kamil Dudka 1.33.0-1 +- use python3 for build +- update to the latest upstream release (#1636992) + +* Wed May 09 2018 Kamil Dudka 1.32.0-1 +- update to the latest upstream release + +* Fri Apr 13 2018 Kamil Dudka 1.31.1-1 +- update to the latest upstream release (fixes CVE-2018-1000168) + +* Thu Mar 15 2018 Kamil Dudka 1.31.0-2 +- make fetch-ocsp-response use Python 3 + +* Tue Feb 27 2018 Kamil Dudka 1.31.0-1 +- update to the latest upstream release + +* Mon Feb 19 2018 Kamil Dudka 1.30.0-3 +- add explicit BR for the gcc-c++ compiler + +* Thu Feb 08 2018 Fedora Release Engineering - 1.30.0-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild + +* Mon Feb 05 2018 Kamil Dudka 1.30.0-1 +- update to the latest upstream release + +* Sat Feb 03 2018 Igor Gnatenko - 1.29.0-2 +- Switch to %%ldconfig_scriptlets + +* Tue Dec 19 2017 Kamil Dudka 1.29.0-1 +- update to the latest upstream release + +* Sun Nov 26 2017 Kamil Dudka 1.28.0-1 +- update to the latest upstream release + +* Wed Oct 25 2017 Kamil Dudka 1.27.0-1 +- update to the latest upstream release + +* Wed Sep 20 2017 Kamil Dudka 1.26.0-1 +- update to the latest upstream release + +* Fri Aug 18 2017 Kamil Dudka 1.25.0-1 +- update to the latest upstream release + +* Thu Aug 03 2017 Fedora Release Engineering - 1.24.0-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Binutils_Mass_Rebuild + +* Sat Jul 29 2017 Florian Weimer - 1.24.0-3 +- Rebuild with binutils fix for ppc64le (#1475636) + +* Wed Jul 26 2017 Kamil Dudka 1.24.0-2 +- drop workaround for a GCC bug that has been fixed (#1358845) + +* Sun Jul 02 2017 Kamil Dudka 1.24.0-1 +- update to the latest upstream release + +* Tue May 30 2017 Kamil Dudka 1.23.1-1 +- update to the latest upstream release + +* Sat May 27 2017 Kamil Dudka 1.23.0-1 +- update to the latest upstream release + +* Mon Apr 24 2017 Kamil Dudka 1.22.0-1 +- update to the latest upstream release + +* Mon Apr 10 2017 Kamil Dudka 1.21.1-1 +- update to the latest upstream release + +* Mon Mar 27 2017 Kamil Dudka 1.21.0-1 +- update to the latest upstream release + +* Sun Feb 26 2017 Tomasz Torcz - 1.20.0-1 +- package systemd unit file (#1426929) +- update to latest upstream release + +* Fri Feb 10 2017 Fedora Release Engineering - 1.19.0-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild + +* Wed Jan 25 2017 Kamil Dudka 1.19.0-1 +- update to the latest upstream release + +* Thu Jan 05 2017 Kamil Dudka 1.18.1-1 +- update to the latest upstream release + +* Tue Dec 27 2016 Kamil Dudka 1.18.0-1 +- update to the latest upstream release (requires c-ares for apps) + +* Mon Nov 28 2016 Kamil Dudka 1.17.0-1 +- update to the latest upstream release + +* Tue Nov 15 2016 Kamil Dudka 1.16.1-1 +- update to the latest upstream release + +* Mon Oct 24 2016 Kamil Dudka 1.16.0-1 +- update to the latest upstream release + +* Mon Sep 26 2016 Kamil Dudka 1.15.0-1 +- update to the latest upstream release + +* Mon Sep 12 2016 Kamil Dudka 1.14.1-1 +- update to the latest upstream release + +* Thu Aug 25 2016 Kamil Dudka 1.14.0-1 +- update to the latest upstream release + +* Tue Jul 26 2016 Kamil Dudka 1.13.0-2 +- prevent nghttpx from crashing on armv7hl (#1358845) + +* Thu Jul 21 2016 Kamil Dudka 1.13.0-1 +- update to the latest upstream release + +* Mon Jun 27 2016 Kamil Dudka 1.12.0-1 +- update to the latest upstream release + +* Sun May 29 2016 Kamil Dudka 1.11.1-1 +- update to the latest upstream release + +* Thu May 26 2016 Kamil Dudka 1.11.0-1 +- update to the latest upstream release + +* Mon Apr 25 2016 Kamil Dudka 1.10.0-1 +- update to the latest upstream release + +* Sun Apr 03 2016 Kamil Dudka 1.9.2-1 +- update to the latest upstream release + +* Tue Mar 29 2016 Kamil Dudka 1.9.1-1 +- update to the latest upstream release + +* Thu Feb 25 2016 Kamil Dudka 1.8.0-1 +- update to the latest upstream release + +* Thu Feb 11 2016 Kamil Dudka 1.7.1-1 +- update to the latest upstream release (fixes CVE-2016-1544) + +* Fri Feb 05 2016 Kamil Dudka 1.7.0-3 +- make the package compile with gcc-6 + +* Thu Feb 04 2016 Fedora Release Engineering - 1.7.0-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_24_Mass_Rebuild + +* Mon Jan 25 2016 Kamil Dudka 1.7.0-1 +- update to the latest upstream release + +* Fri Dec 25 2015 Kamil Dudka 1.6.0-1 +- update to the latest upstream release (fixes CVE-2015-8659) + +* Thu Nov 26 2015 Kamil Dudka 1.5.0-1 +- update to the latest upstream release + +* Mon Oct 26 2015 Kamil Dudka 1.4.0-1 +- update to the latest upstream release + +* Thu Sep 24 2015 Kamil Dudka 1.3.4-1 +- update to the latest upstream release + +* Wed Sep 23 2015 Kamil Dudka 1.3.3-1 +- update to the latest upstream release + +* Wed Sep 16 2015 Kamil Dudka 1.3.2-1 +- update to the latest upstream release + +* Mon Sep 14 2015 Kamil Dudka 1.3.1-1 +- update to the latest upstream release + +* Mon Aug 31 2015 Kamil Dudka 1.3.0-1 +- update to the latest upstream release + +* Mon Aug 17 2015 Kamil Dudka 1.2.1-1 +- update to the latest upstream release + +* Sun Aug 09 2015 Kamil Dudka 1.2.0-1 +- update to the latest upstream release + +* Wed Jul 15 2015 Kamil Dudka 1.1.1-1 +- update to the latest upstream release + +* Tue Jun 30 2015 Kamil Dudka 1.0.5-1 +- packaged for Fedora (#1237247)