Blob Blame History Raw
diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h
index 59fc867..3b14027 100644
--- a/lib/includes/nghttp2/nghttp2.h
+++ b/lib/includes/nghttp2/nghttp2.h
@@ -221,6 +221,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
  *
@@ -382,6 +389,11 @@ typedef enum {
    * Unexpected internal error, but recovered.
    */
   NGHTTP2_ERR_INTERNAL = -534,
+  /**
+   * 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.,
@@ -2106,6 +2118,17 @@ NGHTTP2_EXTERN void
 nghttp2_option_set_max_reserved_remote_streams(nghttp2_option *option,
                                                uint32_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 884abc6..b4d9e55 100644
--- a/lib/nghttp2_helper.c
+++ b/lib/nghttp2_helper.c
@@ -297,6 +297,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 04dbbc6..e8ee7b4 100644
--- a/lib/nghttp2_option.c
+++ b/lib/nghttp2_option.c
@@ -62,3 +62,8 @@ void nghttp2_option_set_max_reserved_remote_streams(nghttp2_option *option,
   option->opt_set_mask |= NGHTTP2_OPT_MAX_RESERVED_REMOTE_STREAMS;
   option->max_reserved_remote_streams = 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 ebf416a..1ae1a4f 100644
--- a/lib/nghttp2_option.h
+++ b/lib/nghttp2_option.h
@@ -59,7 +59,8 @@ typedef enum {
   NGHTTP2_OPT_PEER_MAX_CONCURRENT_STREAMS = 1 << 1,
   NGHTTP2_OPT_NO_RECV_CLIENT_MAGIC = 1 << 2,
   NGHTTP2_OPT_NO_HTTP_MESSAGING = 1 << 3,
-  NGHTTP2_OPT_MAX_RESERVED_REMOTE_STREAMS = 1 << 4
+  NGHTTP2_OPT_MAX_RESERVED_REMOTE_STREAMS = 1 << 4,
+  NGHTTP2_OPT_MAX_SETTINGS = 1 << 12,
 } nghttp2_option_flag;
 
 /**
@@ -91,6 +92,11 @@ struct nghttp2_option {
    * NGHTTP2_OPT_NO_HTTP_MESSAGING
    */
   int no_http_messaging;
+
+  /**
+   * NGHTTP2_OPT_MAX_SETTINGS
+   */
+  size_t max_settings;
 };
 
 #endif /* NGHTTP2_OPTION_H */
diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c
index e42e46b..a3a84b4 100644
--- a/lib/nghttp2_session.c
+++ b/lib/nghttp2_session.c
@@ -375,6 +375,8 @@ static int session_new(nghttp2_session **session_ptr,
   /* Limit max outgoing concurrent streams to sensible value */
   (*session_ptr)->remote_settings.max_concurrent_streams = 100;
 
+  (*session_ptr)->max_settings = NGHTTP2_DEFAULT_MAX_SETTINGS;
+
   if (option) {
     if ((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE) &&
         option->no_auto_window_update) {
@@ -405,6 +407,11 @@ static int session_new(nghttp2_session **session_ptr,
 
       (*session_ptr)->opt_flags |= NGHTTP2_OPTMASK_NO_HTTP_MESSAGING;
     }
+
+    if ((option->opt_set_mask & NGHTTP2_OPT_MAX_SETTINGS) &&
+         option->max_settings) {
+       (*session_ptr)->max_settings = option->max_settings;
+    }
   }
 
   (*session_ptr)->callbacks = *callbacks;
@@ -4837,6 +4844,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
   ssize_t padlen;
   int rv;
   int busy = 0;
+  int max_niv;
   nghttp2_frame_hd cont_hd;
   nghttp2_stream *stream;
   size_t pri_fieldlen;
@@ -5123,9 +5131,30 @@ 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_ >= NGHTTP2_MAX_OBQ_FLOOD_ITEM &&
+            !(iframe->frame.hd.flags & NGHTTP2_FLAG_ACK)) {
+          return NGHTTP2_ERR_FLOODED;
+        }
+
         iframe->state = NGHTTP2_IB_READ_SETTINGS;
 
         if (iframe->payloadleft) {
+          /* We allocate iv with additional one entry, to store the
+             minimum header table size. */
+          max_niv =
+               iframe->frame.hd.length / NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH + 1;
+
+          if (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;
+          }
+
           inbound_frame_set_mark(iframe, NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH);
           break;
         }
@@ -6531,6 +6560,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 204aea9..87aac84 100644
--- a/lib/nghttp2_session.h
+++ b/lib/nghttp2_session.h
@@ -244,6 +244,8 @@ struct nghttp2_session {
   size_t nvbuflen;
   /* Counter for detecting flooding in outbound queue */
   size_t obq_flood_counter_;
+  /* 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,