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,