From 77374ac6e2ff8dea48a9eae5bd99f8e26418ec34 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sun, 27 Jul 2014 16:58:04 +0900 Subject: [PATCH] Implement SETTINGS_MAX_FRAME_SIZE and SETTINGS_MAX_HEADER_LIST_SIZE --- lib/includes/nghttp2/nghttp2.h | 10 ++++++- lib/nghttp2_frame.c | 8 ++++++ lib/nghttp2_frame.h | 4 ++- lib/nghttp2_session.c | 51 ++++++++++++++++++++++++++++++++++ lib/nghttp2_session.h | 4 ++- tests/main.c | 2 ++ tests/nghttp2_frame_test.c | 17 ++++++++++++ tests/nghttp2_session_test.c | 49 ++++++++++++++++++++++++++++++++ tests/nghttp2_session_test.h | 1 + 9 files changed, 143 insertions(+), 3 deletions(-) diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index f7a13e83..91df053f 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -510,7 +510,15 @@ typedef enum { /** * SETTINGS_INITIAL_WINDOW_SIZE */ - NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE = 0x04 + NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE = 0x04, + /** + * SETTINGS_MAX_FRAME_SIZE + */ + NGHTTP2_SETTINGS_MAX_FRAME_SIZE = 0x05, + /** + * SETTINGS_MAX_HEADER_SET_SIZE + */ + NGHTTP2_SETTINGS_MAX_HEADER_SET_SIZE = 0x06 } nghttp2_settings_id; /* Note: If we add SETTINGS, update the capacity of NGHTTP2_INBOUND_NUM_IV as well */ diff --git a/lib/nghttp2_frame.c b/lib/nghttp2_frame.c index 93009674..eff701b5 100644 --- a/lib/nghttp2_frame.c +++ b/lib/nghttp2_frame.c @@ -1037,6 +1037,14 @@ int nghttp2_iv_check(const nghttp2_settings_entry *iv, size_t niv) return 0; } break; + case NGHTTP2_SETTINGS_MAX_FRAME_SIZE: + if(iv[i].value < NGHTTP2_MAX_FRAME_SIZE_MIN || + iv[i].value > NGHTTP2_MAX_FRAME_SIZE_MAX) { + return 0; + } + break; + case NGHTTP2_SETTINGS_MAX_HEADER_SET_SIZE: + break; default: return 0; } diff --git a/lib/nghttp2_frame.h b/lib/nghttp2_frame.h index a62beb0a..4361fb20 100644 --- a/lib/nghttp2_frame.h +++ b/lib/nghttp2_frame.h @@ -33,7 +33,6 @@ #include "nghttp2_hd.h" #include "nghttp2_buf.h" -#define NGHTTP2_FRAME_LENGTH_MASK ((1 << 24) - 1) #define NGHTTP2_STREAM_ID_MASK ((1u << 31) - 1) #define NGHTTP2_PRI_GROUP_ID_MASK ((1u << 31) - 1) #define NGHTTP2_PRIORITY_MASK ((1u << 31) - 1) @@ -43,6 +42,9 @@ /* The number of bytes of frame header. */ #define NGHTTP2_FRAME_HDLEN 9 +#define NGHTTP2_MAX_FRAME_SIZE_MAX ((1 << 24) - 1) +#define NGHTTP2_MAX_FRAME_SIZE_MIN (1 << 14) + #define NGHTTP2_MAX_PAYLOADLEN 16384 /* The one frame buffer length for tranmission. We may use several of them to support CONTINUATION. To account for Pad Length field, we diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index 53bc10a3..3cc8fd24 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -292,6 +292,8 @@ static void init_settings(nghttp2_settings_storage *settings) settings->enable_push = 1; settings->max_concurrent_streams = NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS; settings->initial_window_size = NGHTTP2_INITIAL_WINDOW_SIZE; + settings->max_frame_size = NGHTTP2_MAX_FRAME_SIZE_MIN; + settings->max_header_set_size = UINT32_MAX; } static void active_outbound_item_reset(nghttp2_active_outbound_item *aob) @@ -3273,6 +3275,12 @@ int nghttp2_session_update_local_settings(nghttp2_session *session, case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: session->local_settings.initial_window_size = iv[i].value; break; + case NGHTTP2_SETTINGS_MAX_FRAME_SIZE: + session->local_settings.max_frame_size = iv[i].value; + break; + case NGHTTP2_SETTINGS_MAX_HEADER_SET_SIZE: + session->local_settings.max_header_set_size = iv[i].value; + break; } } @@ -3393,6 +3401,23 @@ int nghttp2_session_on_settings_received(nghttp2_session *session, session->remote_settings.initial_window_size = entry->value; + break; + case NGHTTP2_SETTINGS_MAX_FRAME_SIZE: + + if(entry->value < NGHTTP2_MAX_FRAME_SIZE_MIN || + entry->value > NGHTTP2_MAX_FRAME_SIZE_MAX) { + return session_handle_invalid_connection + (session, frame, NGHTTP2_PROTOCOL_ERROR, + "SETTINGS: invalid SETTINGS_MAX_FRAME_SIZE"); + } + + session->remote_settings.max_frame_size = entry->value; + + break; + case NGHTTP2_SETTINGS_MAX_HEADER_SET_SIZE: + + session->remote_settings.max_header_set_size = entry->value; + break; } } @@ -4141,6 +4166,8 @@ static void inbound_frame_set_settings_entry(nghttp2_inbound_frame *iframe) case NGHTTP2_SETTINGS_ENABLE_PUSH: case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: + case NGHTTP2_SETTINGS_MAX_FRAME_SIZE: + case NGHTTP2_SETTINGS_MAX_HEADER_SET_SIZE: break; default: DEBUGF(fprintf(stderr, "recv: ignore unknown settings id=0x%02x\n", @@ -4273,6 +4300,26 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, iframe->frame.hd.flags, iframe->frame.hd.stream_id)); + if(iframe->frame.hd.length > session->local_settings.max_frame_size) { + DEBUGF(fprintf(stderr, + "recv: legnth is too large %u > %u\n", + iframe->frame.hd.length, + session->local_settings.max_frame_size)); + + busy = 1; + + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + + rv = nghttp2_session_terminate_session_with_reason + (session, NGHTTP2_PROTOCOL_ERROR, "too large frame size"); + + if(nghttp2_is_fatal(rv)) { + return rv; + } + + break; + } + switch(iframe->frame.hd.type) { case NGHTTP2_DATA: { DEBUGF(fprintf(stderr, "recv: DATA\n")); @@ -5688,6 +5735,10 @@ uint32_t nghttp2_session_get_remote_settings(nghttp2_session *session, return session->remote_settings.max_concurrent_streams; case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: return session->remote_settings.initial_window_size; + case NGHTTP2_SETTINGS_MAX_FRAME_SIZE: + return session->remote_settings.max_frame_size; + case NGHTTP2_SETTINGS_MAX_HEADER_SET_SIZE: + return session->remote_settings.max_header_set_size; } assert(0); diff --git a/lib/nghttp2_session.h b/lib/nghttp2_session.h index a946ddb1..2b7aa38f 100644 --- a/lib/nghttp2_session.h +++ b/lib/nghttp2_session.h @@ -80,7 +80,7 @@ typedef enum { NGHTTP2_IB_IGN_DATA } nghttp2_inbound_state; -#define NGHTTP2_INBOUND_NUM_IV 5 +#define NGHTTP2_INBOUND_NUM_IV 7 typedef struct { nghttp2_frame frame; @@ -113,6 +113,8 @@ typedef struct { uint32_t enable_push; uint32_t max_concurrent_streams; uint32_t initial_window_size; + uint32_t max_frame_size; + uint32_t max_header_set_size; } nghttp2_settings_storage; typedef enum { diff --git a/tests/main.c b/tests/main.c index 8df6fd97..6db64cbd 100644 --- a/tests/main.c +++ b/tests/main.c @@ -95,6 +95,8 @@ int main(int argc, char* argv[]) test_nghttp2_session_recv_unexpected_continuation) || !CU_add_test(pSuite, "session_recv_settings_header_table_size", test_nghttp2_session_recv_settings_header_table_size) || + !CU_add_test(pSuite, "session_recv_too_large_frame_length", + test_nghttp2_session_recv_too_large_frame_length) || !CU_add_test(pSuite, "session_continue", test_nghttp2_session_continue) || !CU_add_test(pSuite, "session_add_frame", test_nghttp2_session_add_frame) || diff --git a/tests/nghttp2_frame_test.c b/tests/nghttp2_frame_test.c index 7b3baaf3..7c1ce142 100644 --- a/tests/nghttp2_frame_test.c +++ b/tests/nghttp2_frame_test.c @@ -709,4 +709,21 @@ void test_nghttp2_iv_check(void) iv[1].settings_id = NGHTTP2_SETTINGS_HEADER_TABLE_SIZE; iv[1].value = UINT32_MAX; CU_ASSERT(!nghttp2_iv_check(iv, 2)); + + /* Too small SETTINGS_MAX_FRAME_SIZE */ + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE; + iv[0].value = NGHTTP2_MAX_FRAME_SIZE_MIN - 1; + CU_ASSERT(!nghttp2_iv_check(iv, 1)); + + /* Too large SETTINGS_MAX_FRAME_SIZE */ + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE; + iv[0].value = NGHTTP2_MAX_FRAME_SIZE_MAX + 1; + CU_ASSERT(!nghttp2_iv_check(iv, 1)); + + /* Max and min SETTINGS_MAX_FRAME_SIZE */ + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE; + iv[0].value = NGHTTP2_MAX_FRAME_SIZE_MIN; + iv[1].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE; + iv[1].value = NGHTTP2_MAX_FRAME_SIZE_MAX; + CU_ASSERT(nghttp2_iv_check(iv, 2)); } diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index 26c0ef11..88358157 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -1410,6 +1410,36 @@ void test_nghttp2_session_recv_settings_header_table_size(void) nghttp2_session_del(session); } +void test_nghttp2_session_recv_too_large_frame_length(void) +{ + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + uint8_t buf[NGHTTP2_FRAME_HDLEN]; + nghttp2_outbound_item *item; + nghttp2_frame_hd hd = { + /* Initial max frame size is NGHTTP2_MAX_FRAME_SIZE_MIN */ + NGHTTP2_MAX_FRAME_SIZE_MIN + 1, + 1, + NGHTTP2_HEADERS, + NGHTTP2_FLAG_NONE + }; + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_frame_pack_frame_hd(buf, &hd); + + CU_ASSERT(sizeof(buf) == + nghttp2_session_mem_recv(session, buf, sizeof(buf))); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(item != NULL); + CU_ASSERT(NGHTTP2_GOAWAY == OB_CTRL_TYPE(item)); + + nghttp2_session_del(session); +} + void test_nghttp2_session_continue(void) { nghttp2_session *session; @@ -2162,6 +2192,25 @@ void test_nghttp2_session_on_settings_received(void) nghttp2_frame_settings_free(&frame.settings); nghttp2_session_del(session); + + /* Check too large SETTINGS_MAX_FRAME_SIZE */ + nghttp2_session_server_new(&session, &callbacks, NULL); + + iv[0].settings_id = NGHTTP2_SETTINGS_MAX_FRAME_SIZE; + iv[0].value = NGHTTP2_MAX_FRAME_SIZE_MAX + 1; + + nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, + dup_iv(iv, 1), 1); + + CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0)); + + item = nghttp2_session_get_next_ob_item(session); + + CU_ASSERT(item != NULL); + CU_ASSERT(NGHTTP2_GOAWAY == OB_CTRL_TYPE(item)); + + nghttp2_frame_settings_free(&frame.settings); + nghttp2_session_del(session); } void test_nghttp2_session_on_push_promise_received(void) diff --git a/tests/nghttp2_session_test.h b/tests/nghttp2_session_test.h index 57fb9695..0cdd7394 100644 --- a/tests/nghttp2_session_test.h +++ b/tests/nghttp2_session_test.h @@ -37,6 +37,7 @@ void test_nghttp2_session_recv_altsvc(void); void test_nghttp2_session_recv_unknown_frame(void); void test_nghttp2_session_recv_unexpected_continuation(void); void test_nghttp2_session_recv_settings_header_table_size(void); +void test_nghttp2_session_recv_too_large_frame_length(void); void test_nghttp2_session_continue(void); void test_nghttp2_session_add_frame(void); void test_nghttp2_session_on_request_headers_received(void);