diff -Naur a/doc/Makefile.am b/doc/Makefile.am --- a/doc/Makefile.am 2024-04-15 19:33:38.344785283 +0800 +++ b/doc/Makefile.am 2024-04-15 20:15:35.145757969 +0800 @@ -68,6 +68,7 @@ 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_continuations.rst \ nghttp2_option_set_max_outbound_ack.rst \ nghttp2_option_set_max_settings.rst \ nghttp2_option_set_stream_reset_rate_limit.rst \ diff -Naur a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h --- a/lib/includes/nghttp2/nghttp2.h 2024-04-15 19:33:38.348785336 +0800 +++ b/lib/includes/nghttp2/nghttp2.h 2024-04-15 20:15:35.149758022 +0800 @@ -440,7 +440,12 @@ * 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; /** @@ -2738,6 +2743,17 @@ /** * @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 * * Initializes |*session_ptr| for client use. The all members of * |callbacks| are copied to |*session_ptr|. Therefore |*session_ptr| diff -Naur a/lib/nghttp2_helper.c b/lib/nghttp2_helper.c --- a/lib/nghttp2_helper.c 2024-04-15 19:33:38.352785388 +0800 +++ b/lib/nghttp2_helper.c 2024-04-15 20:15:35.149758022 +0800 @@ -336,6 +336,8 @@ "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 -Naur a/lib/nghttp2_option.c b/lib/nghttp2_option.c --- a/lib/nghttp2_option.c 2024-04-15 19:33:38.348785336 +0800 +++ b/lib/nghttp2_option.c 2024-04-15 20:15:35.149758022 +0800 @@ -133,3 +133,8 @@ 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; +} diff -Naur a/lib/nghttp2_option.h b/lib/nghttp2_option.h --- a/lib/nghttp2_option.h 2024-04-15 19:33:38.352785388 +0800 +++ b/lib/nghttp2_option.h 2024-04-15 20:15:35.153758074 +0800 @@ -69,6 +69,7 @@ NGHTTP2_OPT_MAX_OUTBOUND_ACK = 1 << 11, NGHTTP2_OPT_MAX_SETTINGS = 1 << 12, NGHTTP2_OPT_STREAM_RESET_RATE_LIMIT = 1 << 15, + NGHTTP2_OPT_MAX_CONTINUATIONS = 1 << 16, } nghttp2_option_flag; /** @@ -97,6 +98,10 @@ */ 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. */ diff -Naur a/lib/nghttp2_session.c b/lib/nghttp2_session.c --- a/lib/nghttp2_session.c 2024-04-15 19:33:38.352785388 +0800 +++ b/lib/nghttp2_session.c 2024-04-15 20:15:35.153758074 +0800 @@ -464,6 +464,7 @@ (*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) && @@ -538,6 +539,10 @@ 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, @@ -6310,6 +6315,8 @@ } } session_inbound_frame_reset(session); + + session->num_continuations = 0; } break; } @@ -6430,6 +6437,10 @@ 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; diff -Naur a/lib/nghttp2_session.h b/lib/nghttp2_session.h --- a/lib/nghttp2_session.h 2024-04-15 19:33:38.352785388 +0800 +++ b/lib/nghttp2_session.h 2024-04-15 20:15:35.153758074 +0800 @@ -107,6 +107,10 @@ #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 */ @@ -279,6 +283,12 @@ 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,