CVE-2024-28182 (published 2024-04-04T15:15Z)

nghttp2 is an implementation of the Hypertext Transfer Protocol version 2 in C. The nghttp2 library prior to version 1.61.0 keeps reading the unbounded number of HTTP/2 CONTINUATION frames even after a stream is reset to keep HPACK context in sync.  This causes excessive CPU usage to decode HPACK stream. nghttp2 v1.61.0 mitigates this vulnerability by limiting the number of CONTINUATION frames it accepts per stream. There is no workaround for this vulnerability.


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/nghttp2/nghttp2
commit_details: repo_url: https://github.com/nghttp2/nghttp2
author: Tatsuhiro Tsujikawa
author_date: 2024-03-09 16:26:42+09:00
msg: Limit CONTINUATION frames following an incoming HEADER frame
repository: repo_url: https://github.com/nghttp2/nghttp2
repo_name: nghttp2/nghttp2
description: nghttp2 - HTTP/2 C Library and tools
repo_language: C++
owner: nghttp2
file_changes: filename: nghttp2.h
change_type: ModificationType.MODIFY
diff: @@ -466,7 +466,12 @@ typedef enum {
    * exhaustion on server side to send these frames forever and does
    * not read network.
    */
-  NGHTTP2_ERR_FLOODED = -904
+  NGHTTP2_ERR_FLOODED = -904,
+  /**
+   * When a local endpoint receives too many CONTINUATION frames
+   * following a HEADER frame.
+   */
+  NGHTTP2_ERR_TOO_MANY_CONTINUATIONS = -905,
 } nghttp2_error;
 
 /**

programming_language: C
filename: nghttp2_helper.c
change_type: ModificationType.MODIFY
diff: @@ -336,6 +336,8 @@ const char *nghttp2_strerror(int error_code) {
            "closed";
   case NGHTTP2_ERR_TOO_MANY_SETTINGS:
     return "SETTINGS frame contained more than the maximum allowed entries";
+  case NGHTTP2_ERR_TOO_MANY_CONTINUATIONS:
+    return "Too many CONTINUATION frames following a HEADER frame";
   default:
     return "Unknown error code";
   }

programming_language: C
filename: nghttp2_session.c
change_type: ModificationType.MODIFY
diff: @@ -497,6 +497,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;
+  (*session_ptr)->max_continuations = NGHTTP2_DEFAULT_MAX_CONTINUATIONS;
 
   if (option) {
     if ((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE) &&
@@ -6826,6 +6827,8 @@ nghttp2_ssize nghttp2_session_mem_recv2(nghttp2_session *session,
           }
         }
         session_inbound_frame_reset(session);
+
+        session->num_continuations = 0;
       }
       break;
     }
@@ -6947,6 +6950,10 @@ nghttp2_ssize nghttp2_session_mem_recv2(nghttp2_session *session,
       }
 #endif /* DEBUGBUILD */
 
+      if (++session->num_continuations > session->max_continuations) {
+        return NGHTTP2_ERR_TOO_MANY_CONTINUATIONS;
+      }
+
       readlen = inbound_frame_buf_read(iframe, in, last);
       in += readlen;
 

programming_language: C
filename: nghttp2_session.h
change_type: ModificationType.MODIFY
diff: @@ -110,6 +110,10 @@ typedef struct {
 #define NGHTTP2_DEFAULT_STREAM_RESET_BURST 1000
 #define NGHTTP2_DEFAULT_STREAM_RESET_RATE 33
 
+/* The default max number of CONTINUATION frames following an incoming
+   HEADER frame. */
+#define NGHTTP2_DEFAULT_MAX_CONTINUATIONS 8
+
 /* Internal state when receiving incoming frame */
 typedef enum {
   /* Receiving frame header */
@@ -290,6 +294,12 @@ struct nghttp2_session {
   size_t max_send_header_block_length;
   /* The maximum number of settings accepted per SETTINGS frame. */
   size_t max_settings;
+  /* The maximum number of CONTINUATION frames following an incoming
+     HEADER frame. */
+  size_t max_continuations;
+  /* The number of CONTINUATION frames following an incoming HEADER
+     frame.  This variable is reset when END_HEADERS flag is seen. */
+  size_t num_continuations;
   /* 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,

programming_language: C
repo_url: https://github.com/nghttp2/nghttp2
commit_details: repo_url: https://github.com/nghttp2/nghttp2
author: Tatsuhiro Tsujikawa
author_date: 2024-03-09 16:48:10+09:00
msg: Add nghttp2_option_set_max_continuations
repository: repo_url: https://github.com/nghttp2/nghttp2
repo_name: nghttp2/nghttp2
description: nghttp2 - HTTP/2 C Library and tools
repo_language: C++
owner: nghttp2
file_changes: filename: Makefile.am
change_type: ModificationType.MODIFY
diff: @@ -77,6 +77,7 @@ APIDOCS= \
 	nghttp2_option_set_peer_max_concurrent_streams.rst \
 	nghttp2_option_set_server_fallback_rfc7540_priorities.rst \
 	nghttp2_option_set_user_recv_extension_type.rst \
+	nghttp2_option_set_max_continuations.rst \
 	nghttp2_option_set_max_outbound_ack.rst \
 	nghttp2_option_set_max_settings.rst \
 	nghttp2_option_set_stream_reset_rate_limit.rst \

programming_language: Shell
filename: nghttp2.h
change_type: ModificationType.MODIFY
diff: @@ -3210,6 +3210,17 @@ NGHTTP2_EXTERN void
 nghttp2_option_set_stream_reset_rate_limit(nghttp2_option *option,
                                            uint64_t burst, uint64_t rate);
 
+/**
+ * @function
+ *
+ * This function sets the maximum number of CONTINUATION frames
+ * following an incoming HEADER frame.  If more than those frames are
+ * received, the remote endpoint is considered to be misbehaving and
+ * session will be closed.  The default value is 8.
+ */
+NGHTTP2_EXTERN void nghttp2_option_set_max_continuations(nghttp2_option *option,
+                                                         size_t val);
+
 /**
  * @function
  *

programming_language: C
filename: nghttp2_option.c
change_type: ModificationType.MODIFY
diff: @@ -150,3 +150,8 @@ void nghttp2_option_set_stream_reset_rate_limit(nghttp2_option *option,
   option->stream_reset_burst = burst;
   option->stream_reset_rate = rate;
 }
+
+void nghttp2_option_set_max_continuations(nghttp2_option *option, size_t val) {
+  option->opt_set_mask |= NGHTTP2_OPT_MAX_CONTINUATIONS;
+  option->max_continuations = val;
+}

programming_language: C
filename: nghttp2_option.h
change_type: ModificationType.MODIFY
diff: @@ -71,6 +71,7 @@ typedef enum {
   NGHTTP2_OPT_SERVER_FALLBACK_RFC7540_PRIORITIES = 1 << 13,
   NGHTTP2_OPT_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION = 1 << 14,
   NGHTTP2_OPT_STREAM_RESET_RATE_LIMIT = 1 << 15,
+  NGHTTP2_OPT_MAX_CONTINUATIONS = 1 << 16,
 } nghttp2_option_flag;
 
 /**
@@ -98,6 +99,10 @@ struct nghttp2_option {
    * NGHTTP2_OPT_MAX_SETTINGS
    */
   size_t max_settings;
+  /**
+   * NGHTTP2_OPT_MAX_CONTINUATIONS
+   */
+  size_t max_continuations;
   /**
    * Bitwise OR of nghttp2_option_flag to determine that which fields
    * are specified.

programming_language: C
filename: nghttp2_session.c
change_type: ModificationType.MODIFY
diff: @@ -586,6 +586,10 @@ static int session_new(nghttp2_session **session_ptr,
                            option->stream_reset_burst,
                            option->stream_reset_rate);
     }
+
+    if (option->opt_set_mask & NGHTTP2_OPT_MAX_CONTINUATIONS) {
+      (*session_ptr)->max_continuations = option->max_continuations;
+    }
   }
 
   rv = nghttp2_hd_deflate_init2(&(*session_ptr)->hd_deflater,

programming_language: C