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,