Blame SOURCES/nghttp2-1.33.0-CVE-2020-11080.patch

31f4e3
From 34670cfbc56f1c63ec046c38b9ad518010b5c84d Mon Sep 17 00:00:00 2001
31f4e3
From: James M Snell <jasnell@gmail.com>
31f4e3
Date: Fri, 17 Apr 2020 16:53:51 -0700
31f4e3
Subject: [PATCH 1/2] Implement max settings option
31f4e3
31f4e3
Upstream-commit: 336a98feb0d56b9ac54e12736b18785c27f75090
31f4e3
Signed-off-by: Kamil Dudka <kdudka@redhat.com>
31f4e3
---
31f4e3
 doc/CMakeLists.txt             |  1 +
31f4e3
 doc/Makefile.am                |  1 +
31f4e3
 lib/includes/nghttp2/nghttp2.h | 23 +++++++++++++
31f4e3
 lib/nghttp2_helper.c           |  2 ++
31f4e3
 lib/nghttp2_option.c           |  5 +++
31f4e3
 lib/nghttp2_option.h           |  5 +++
31f4e3
 lib/nghttp2_session.c          | 21 ++++++++++++
31f4e3
 lib/nghttp2_session.h          |  2 ++
31f4e3
 tests/main.c                   |  2 ++
31f4e3
 tests/nghttp2_session_test.c   | 61 ++++++++++++++++++++++++++++++++++
31f4e3
 tests/nghttp2_session_test.h   |  1 +
31f4e3
 11 files changed, 124 insertions(+)
31f4e3
31f4e3
diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt
31f4e3
index 34c0279..f3aec84 100644
31f4e3
--- a/doc/CMakeLists.txt
31f4e3
+++ b/doc/CMakeLists.txt
31f4e3
@@ -42,6 +42,7 @@ set(APIDOCS
31f4e3
   nghttp2_option_set_no_recv_client_magic.rst
31f4e3
   nghttp2_option_set_peer_max_concurrent_streams.rst
31f4e3
   nghttp2_option_set_user_recv_extension_type.rst
31f4e3
+  nghttp2_option_set_max_settings.rst
31f4e3
   nghttp2_pack_settings_payload.rst
31f4e3
   nghttp2_priority_spec_check_default.rst
31f4e3
   nghttp2_priority_spec_default_init.rst
31f4e3
diff --git a/doc/Makefile.am b/doc/Makefile.am
31f4e3
index c17d933..5a58f8e 100644
31f4e3
--- a/doc/Makefile.am
31f4e3
+++ b/doc/Makefile.am
31f4e3
@@ -68,6 +68,7 @@ APIDOCS= \
31f4e3
 	nghttp2_option_set_peer_max_concurrent_streams.rst \
31f4e3
 	nghttp2_option_set_user_recv_extension_type.rst \
31f4e3
 	nghttp2_option_set_max_outbound_ack.rst \
31f4e3
+	nghttp2_option_set_max_settings.rst \
31f4e3
 	nghttp2_pack_settings_payload.rst \
31f4e3
 	nghttp2_priority_spec_check_default.rst \
31f4e3
 	nghttp2_priority_spec_default_init.rst \
31f4e3
diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h
31f4e3
index d79bf48..b0ff6c5 100644
31f4e3
--- a/lib/includes/nghttp2/nghttp2.h
31f4e3
+++ b/lib/includes/nghttp2/nghttp2.h
31f4e3
@@ -222,6 +222,13 @@ typedef struct {
31f4e3
  */
31f4e3
 #define NGHTTP2_CLIENT_MAGIC_LEN 24
31f4e3
 
31f4e3
+/**
31f4e3
+ * @macro
31f4e3
+ *
31f4e3
+ * The default max number of settings per SETTINGS frame
31f4e3
+ */
31f4e3
+#define NGHTTP2_DEFAULT_MAX_SETTINGS 32
31f4e3
+
31f4e3
 /**
31f4e3
  * @enum
31f4e3
  *
31f4e3
@@ -392,6 +399,11 @@ typedef enum {
31f4e3
    * receives an other type of frame.
31f4e3
    */
31f4e3
   NGHTTP2_ERR_SETTINGS_EXPECTED = -536,
31f4e3
+  /**
31f4e3
+   * When a local endpoint receives too many settings entries
31f4e3
+   * in a single SETTINGS frame.
31f4e3
+   */
31f4e3
+  NGHTTP2_ERR_TOO_MANY_SETTINGS = -537,
31f4e3
   /**
31f4e3
    * The errors < :enum:`NGHTTP2_ERR_FATAL` mean that the library is
31f4e3
    * under unexpected condition and processing was terminated (e.g.,
31f4e3
@@ -2648,6 +2660,17 @@ NGHTTP2_EXTERN void nghttp2_option_set_no_closed_streams(nghttp2_option *option,
31f4e3
 NGHTTP2_EXTERN void nghttp2_option_set_max_outbound_ack(nghttp2_option *option,
31f4e3
                                                         size_t val);
31f4e3
 
31f4e3
+/**
31f4e3
+ * @function
31f4e3
+ *
31f4e3
+ * This function sets the maximum number of SETTINGS entries per
31f4e3
+ * SETTINGS frame that will be accepted. If more than those entries
31f4e3
+ * are received, the peer is considered to be misbehaving and session
31f4e3
+ * will be closed. The default value is 32.
31f4e3
+ */
31f4e3
+NGHTTP2_EXTERN void nghttp2_option_set_max_settings(nghttp2_option *option,
31f4e3
+                                                    size_t val);
31f4e3
+
31f4e3
 /**
31f4e3
  * @function
31f4e3
  *
31f4e3
diff --git a/lib/nghttp2_helper.c b/lib/nghttp2_helper.c
31f4e3
index 3b282c7..49bbf07 100644
31f4e3
--- a/lib/nghttp2_helper.c
31f4e3
+++ b/lib/nghttp2_helper.c
31f4e3
@@ -334,6 +334,8 @@ const char *nghttp2_strerror(int error_code) {
31f4e3
   case NGHTTP2_ERR_FLOODED:
31f4e3
     return "Flooding was detected in this HTTP/2 session, and it must be "
31f4e3
            "closed";
31f4e3
+  case NGHTTP2_ERR_TOO_MANY_SETTINGS:
31f4e3
+    return "SETTINGS frame contained more than the maximum allowed entries";
31f4e3
   default:
31f4e3
     return "Unknown error code";
31f4e3
   }
31f4e3
diff --git a/lib/nghttp2_option.c b/lib/nghttp2_option.c
31f4e3
index e53f22d..34348e6 100644
31f4e3
--- a/lib/nghttp2_option.c
31f4e3
+++ b/lib/nghttp2_option.c
31f4e3
@@ -121,3 +121,8 @@ void nghttp2_option_set_max_outbound_ack(nghttp2_option *option, size_t val) {
31f4e3
   option->opt_set_mask |= NGHTTP2_OPT_MAX_OUTBOUND_ACK;
31f4e3
   option->max_outbound_ack = val;
31f4e3
 }
31f4e3
+
31f4e3
+void nghttp2_option_set_max_settings(nghttp2_option *option, size_t val) {
31f4e3
+  option->opt_set_mask |= NGHTTP2_OPT_MAX_SETTINGS;
31f4e3
+  option->max_settings = val;
31f4e3
+}
31f4e3
diff --git a/lib/nghttp2_option.h b/lib/nghttp2_option.h
31f4e3
index 1f740aa..939729f 100644
31f4e3
--- a/lib/nghttp2_option.h
31f4e3
+++ b/lib/nghttp2_option.h
31f4e3
@@ -67,6 +67,7 @@ typedef enum {
31f4e3
   NGHTTP2_OPT_MAX_DEFLATE_DYNAMIC_TABLE_SIZE = 1 << 9,
31f4e3
   NGHTTP2_OPT_NO_CLOSED_STREAMS = 1 << 10,
31f4e3
   NGHTTP2_OPT_MAX_OUTBOUND_ACK = 1 << 11,
31f4e3
+  NGHTTP2_OPT_MAX_SETTINGS = 1 << 12,
31f4e3
 } nghttp2_option_flag;
31f4e3
 
31f4e3
 /**
31f4e3
@@ -85,6 +86,10 @@ struct nghttp2_option {
31f4e3
    * NGHTTP2_OPT_MAX_OUTBOUND_ACK
31f4e3
    */
31f4e3
   size_t max_outbound_ack;
31f4e3
+  /**
31f4e3
+   * NGHTTP2_OPT_MAX_SETTINGS
31f4e3
+   */
31f4e3
+  size_t max_settings;
31f4e3
   /**
31f4e3
    * Bitwise OR of nghttp2_option_flag to determine that which fields
31f4e3
    * are specified.
31f4e3
diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c
31f4e3
index 670f83f..7638823 100644
31f4e3
--- a/lib/nghttp2_session.c
31f4e3
+++ b/lib/nghttp2_session.c
31f4e3
@@ -458,6 +458,7 @@ static int session_new(nghttp2_session **session_ptr,
31f4e3
 
31f4e3
   (*session_ptr)->max_send_header_block_length = NGHTTP2_MAX_HEADERSLEN;
31f4e3
   (*session_ptr)->max_outbound_ack = NGHTTP2_DEFAULT_MAX_OBQ_FLOOD_ITEM;
31f4e3
+  (*session_ptr)->max_settings = NGHTTP2_DEFAULT_MAX_SETTINGS;
31f4e3
 
31f4e3
   if (option) {
31f4e3
     if ((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE) &&
31f4e3
@@ -521,6 +522,11 @@ static int session_new(nghttp2_session **session_ptr,
31f4e3
     if (option->opt_set_mask & NGHTTP2_OPT_MAX_OUTBOUND_ACK) {
31f4e3
       (*session_ptr)->max_outbound_ack = option->max_outbound_ack;
31f4e3
     }
31f4e3
+
31f4e3
+    if ((option->opt_set_mask & NGHTTP2_OPT_MAX_SETTINGS) &&
31f4e3
+        option->max_settings) {
31f4e3
+      (*session_ptr)->max_settings = option->max_settings;
31f4e3
+    }
31f4e3
   }
31f4e3
 
31f4e3
   rv = nghttp2_hd_deflate_init2(&(*session_ptr)->hd_deflater,
31f4e3
@@ -5658,6 +5664,16 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
31f4e3
           iframe->max_niv =
31f4e3
               iframe->frame.hd.length / NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH + 1;
31f4e3
 
31f4e3
+          if (iframe->max_niv - 1 > session->max_settings) {
31f4e3
+            rv = nghttp2_session_terminate_session_with_reason(
31f4e3
+                session, NGHTTP2_ENHANCE_YOUR_CALM,
31f4e3
+                "SETTINGS: too many setting entries");
31f4e3
+            if (nghttp2_is_fatal(rv)) {
31f4e3
+              return rv;
31f4e3
+            }
31f4e3
+            return (ssize_t)inlen;
31f4e3
+          }
31f4e3
+
31f4e3
           iframe->iv = nghttp2_mem_malloc(mem, sizeof(nghttp2_settings_entry) *
31f4e3
                                                    iframe->max_niv);
31f4e3
 
31f4e3
@@ -7413,6 +7429,11 @@ static int nghttp2_session_upgrade_internal(nghttp2_session *session,
31f4e3
   if (settings_payloadlen % NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH) {
31f4e3
     return NGHTTP2_ERR_INVALID_ARGUMENT;
31f4e3
   }
31f4e3
+  /* SETTINGS frame contains too many settings */
31f4e3
+  if (settings_payloadlen / NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH
31f4e3
+        > session->max_settings) {
31f4e3
+    return NGHTTP2_ERR_TOO_MANY_SETTINGS;
31f4e3
+  }
31f4e3
   rv = nghttp2_frame_unpack_settings_payload2(&iv, &niv, settings_payload,
31f4e3
                                               settings_payloadlen, mem);
31f4e3
   if (rv != 0) {
31f4e3
diff --git a/lib/nghttp2_session.h b/lib/nghttp2_session.h
31f4e3
index 2969364..e62a3bb 100644
31f4e3
--- a/lib/nghttp2_session.h
31f4e3
+++ b/lib/nghttp2_session.h
31f4e3
@@ -269,6 +269,8 @@ struct nghttp2_session {
31f4e3
   /* The maximum length of header block to send.  Calculated by the
31f4e3
      same way as nghttp2_hd_deflate_bound() does. */
31f4e3
   size_t max_send_header_block_length;
31f4e3
+  /* The maximum number of settings accepted per SETTINGS frame. */
31f4e3
+  size_t max_settings;
31f4e3
   /* Next Stream ID. Made unsigned int to detect >= (1 << 31). */
31f4e3
   uint32_t next_stream_id;
31f4e3
   /* The last stream ID this session initiated.  For client session,
31f4e3
diff --git a/tests/main.c b/tests/main.c
31f4e3
index 13865de..1f795cd 100644
31f4e3
--- a/tests/main.c
31f4e3
+++ b/tests/main.c
31f4e3
@@ -313,6 +313,8 @@ int main() {
31f4e3
                    test_nghttp2_session_set_local_window_size) ||
31f4e3
       !CU_add_test(pSuite, "session_cancel_from_before_frame_send",
31f4e3
                    test_nghttp2_session_cancel_from_before_frame_send) ||
31f4e3
+      !CU_add_test(pSuite, "session_too_many_settings",
31f4e3
+                   test_nghttp2_session_too_many_settings) ||
31f4e3
       !CU_add_test(pSuite, "session_removed_closed_stream",
31f4e3
                    test_nghttp2_session_removed_closed_stream) ||
31f4e3
       !CU_add_test(pSuite, "session_pause_data",
31f4e3
diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c
31f4e3
index 0013e92..ab76ab4 100644
31f4e3
--- a/tests/nghttp2_session_test.c
31f4e3
+++ b/tests/nghttp2_session_test.c
31f4e3
@@ -10450,6 +10450,67 @@ void test_nghttp2_session_cancel_from_before_frame_send(void) {
31f4e3
   nghttp2_session_del(session);
31f4e3
 }
31f4e3
 
31f4e3
+void test_nghttp2_session_too_many_settings(void) {
31f4e3
+  nghttp2_session *session;
31f4e3
+  nghttp2_option *option;
31f4e3
+  nghttp2_session_callbacks callbacks;
31f4e3
+  nghttp2_frame frame;
31f4e3
+  nghttp2_bufs bufs;
31f4e3
+  nghttp2_buf *buf;
31f4e3
+  ssize_t rv;
31f4e3
+  my_user_data ud;
31f4e3
+  nghttp2_settings_entry iv[3];
31f4e3
+  nghttp2_mem *mem;
31f4e3
+  nghttp2_outbound_item *item;
31f4e3
+
31f4e3
+  mem = nghttp2_mem_default();
31f4e3
+  frame_pack_bufs_init(&bufs);
31f4e3
+
31f4e3
+  memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
31f4e3
+  callbacks.on_frame_recv_callback = on_frame_recv_callback;
31f4e3
+  callbacks.send_callback = null_send_callback;
31f4e3
+
31f4e3
+  nghttp2_option_new(&option);
31f4e3
+  nghttp2_option_set_max_settings(option, 1);
31f4e3
+
31f4e3
+  nghttp2_session_client_new2(&session, &callbacks, &ud, option);
31f4e3
+
31f4e3
+  CU_ASSERT(1 == session->max_settings);
31f4e3
+
31f4e3
+  nghttp2_option_del(option);
31f4e3
+
31f4e3
+  iv[0].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE;
31f4e3
+  iv[0].value = 3000;
31f4e3
+
31f4e3
+  iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
31f4e3
+  iv[1].value = 16384;
31f4e3
+
31f4e3
+  nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 2),
31f4e3
+                              2);
31f4e3
+
31f4e3
+  rv = nghttp2_frame_pack_settings(&bufs, &frame.settings);
31f4e3
+
31f4e3
+  CU_ASSERT(0 == rv);
31f4e3
+  CU_ASSERT(nghttp2_bufs_len(&bufs) > 0);
31f4e3
+
31f4e3
+  nghttp2_frame_settings_free(&frame.settings, mem);
31f4e3
+
31f4e3
+  buf = &bufs.head->buf;
31f4e3
+  assert(nghttp2_bufs_len(&bufs) == nghttp2_buf_len(buf));
31f4e3
+
31f4e3
+  ud.frame_recv_cb_called = 0;
31f4e3
+
31f4e3
+  rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf));
31f4e3
+  CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv);
31f4e3
+
31f4e3
+  item = nghttp2_session_get_next_ob_item(session);
31f4e3
+  CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
31f4e3
+
31f4e3
+  nghttp2_bufs_reset(&bufs);
31f4e3
+  nghttp2_bufs_free(&bufs);
31f4e3
+  nghttp2_session_del(session);
31f4e3
+}
31f4e3
+
31f4e3
 static void
31f4e3
 prepare_session_removed_closed_stream(nghttp2_session *session,
31f4e3
                                       nghttp2_hd_deflater *deflater) {
31f4e3
diff --git a/tests/nghttp2_session_test.h b/tests/nghttp2_session_test.h
31f4e3
index 35a48b8..c5095c2 100644
31f4e3
--- a/tests/nghttp2_session_test.h
31f4e3
+++ b/tests/nghttp2_session_test.h
31f4e3
@@ -155,6 +155,7 @@ void test_nghttp2_session_repeated_priority_change(void);
31f4e3
 void test_nghttp2_session_repeated_priority_submission(void);
31f4e3
 void test_nghttp2_session_set_local_window_size(void);
31f4e3
 void test_nghttp2_session_cancel_from_before_frame_send(void);
31f4e3
+void test_nghttp2_session_too_many_settings(void);
31f4e3
 void test_nghttp2_session_removed_closed_stream(void);
31f4e3
 void test_nghttp2_session_pause_data(void);
31f4e3
 void test_nghttp2_session_no_closed_streams(void);
31f4e3
-- 
31f4e3
2.21.3
31f4e3
31f4e3
31f4e3
From da3f4a5730ffa015a9e2d62e6e876a02f1dced20 Mon Sep 17 00:00:00 2001
31f4e3
From: James M Snell <jasnell@gmail.com>
31f4e3
Date: Sun, 19 Apr 2020 09:12:24 -0700
31f4e3
Subject: [PATCH 2/2] Earlier check for settings flood
31f4e3
31f4e3
Upstream-commit: f8da73bd042f810f34d19f9eae02b46d870af394
31f4e3
Signed-off-by: Kamil Dudka <kdudka@redhat.com>
31f4e3
---
31f4e3
 lib/nghttp2_session.c | 6 ++++++
31f4e3
 1 file changed, 6 insertions(+)
31f4e3
31f4e3
diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c
31f4e3
index 7638823..8271198 100644
31f4e3
--- a/lib/nghttp2_session.c
31f4e3
+++ b/lib/nghttp2_session.c
31f4e3
@@ -5654,6 +5654,12 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
31f4e3
           break;
31f4e3
         }
31f4e3
 
31f4e3
+        /* Check the settings flood counter early to be safe */
31f4e3
+        if (session->obq_flood_counter_ >= session->max_outbound_ack &&
31f4e3
+            !(iframe->frame.hd.flags & NGHTTP2_FLAG_ACK)) {
31f4e3
+          return NGHTTP2_ERR_FLOODED;
31f4e3
+        }
31f4e3
+
31f4e3
         iframe->state = NGHTTP2_IB_READ_SETTINGS;
31f4e3
 
31f4e3
         if (iframe->payloadleft) {
31f4e3
-- 
31f4e3
2.21.3
31f4e3