CVE-2024-28182: Insufficient Information (NVD-CWE-noinfo)
value: 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.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-04-04T15:15Z
Repository: https://github.com/nghttp2/nghttp2
Language: C
Files changed: lib/includes/nghttp2/nghttp2.h, lib/nghttp2_helper.c, lib/nghttp2_session.c, lib/nghttp2_session.h
Commit date: 2024-04-04 17:08:46 +0900
Commit message: Limit CONTINUATION frames following an incoming HEADER frame

Diff: diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h
index 889176097..a9629c782 100644
--- a/lib/includes/nghttp2/nghttp2.h
+++ b/lib/includes/nghttp2/nghttp2.h
@@ -464,11 +464,16 @@ typedef enum {
    * ACK flag set are queued for transmission.  These frames are
    * response for the peer initiated frames, and peer can cause memory
    * 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;
 
 /**
  * @struct
  *
diff --git a/lib/nghttp2_helper.c b/lib/nghttp2_helper.c
index 93dd4754b..b3563d98e 100644
--- a/lib/nghttp2_helper.c
+++ b/lib/nghttp2_helper.c
@@ -334,10 +334,12 @@ 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";
+  case NGHTTP2_ERR_TOO_MANY_CONTINUATIONS:
+    return "Too many CONTINUATION frames following a HEADER frame";
   default:
     return "Unknown error code";
   }
 }
 
diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c
index ea4fcbec5..fc4c77a3f 100644
--- a/lib/nghttp2_session.c
+++ b/lib/nghttp2_session.c
@@ -495,10 +495,11 @@ 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;
   (*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) &&
         option->no_auto_window_update) {
 
@@ -6824,10 +6825,12 @@ nghttp2_ssize nghttp2_session_mem_recv2(nghttp2_session *session,
           if (nghttp2_is_fatal(rv)) {
             return rv;
           }
         }
         session_inbound_frame_reset(session);
+
+        session->num_continuations = 0;
       }
       break;
     }
     case NGHTTP2_IB_IGN_PAYLOAD:
       DEBUGF("recv: [IB_IGN_PAYLOAD]\n");
@@ -6945,10 +6948,14 @@ nghttp2_ssize nghttp2_session_mem_recv2(nghttp2_session *session,
       } else {
         fprintf(stderr, "recv: [IB_IGN_CONTINUATION]\n");
       }
 #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;
 
       if (nghttp2_buf_mark_avail(&iframe->sbuf)) {
         return (nghttp2_ssize)(in - first);
diff --git a/lib/nghttp2_session.h b/lib/nghttp2_session.h
index b119329a0..ef8f7b27d 100644
--- a/lib/nghttp2_session.h
+++ b/lib/nghttp2_session.h
@@ -108,10 +108,14 @@ typedef struct {
 
 /* The default values for stream reset rate limiter. */
 #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 */
   NGHTTP2_IB_READ_CLIENT_MAGIC,
   NGHTTP2_IB_READ_FIRST_SETTINGS,
@@ -288,10 +292,16 @@ 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;
+  /* 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,
      this is the last stream ID it has sent.  For server session, it
      is the last promised stream ID sent in PUSH_PROMISE. */

Vulnerable code:   NGHTTP2_ERR_FLOODED = -904
Fixed code:   NGHTTP2_ERR_FLOODED = -904,
  /**
   * When a local endpoint receives too many CONTINUATION frames
   * following a HEADER frame.
   */
  NGHTTP2_ERR_TOO_MANY_CONTINUATIONS = -905,
  case NGHTTP2_ERR_TOO_MANY_CONTINUATIONS:
    return "Too many CONTINUATION frames following a HEADER frame";
  (*session_ptr)->max_continuations = NGHTTP2_DEFAULT_MAX_CONTINUATIONS;

        session->num_continuations = 0;
      if (++session->num_continuations > session->max_continuations) {
        return NGHTTP2_ERR_TOO_MANY_CONTINUATIONS;
      }

/* The default max number of CONTINUATION frames following an incoming
   HEADER frame. */
#define NGHTTP2_DEFAULT_MAX_CONTINUATIONS 8

  /* 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;