diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index f8420435..fd720d09 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -83,7 +83,13 @@ static int32_t nghttp2_pushed_stream_pri(nghttp2_stream *stream) int nghttp2_session_fail_session(nghttp2_session *session, nghttp2_error_code error_code) { + if(session->goaway_flags & NGHTTP2_GOAWAY_FAIL_ON_SEND) { + return 0; + } session->goaway_flags |= NGHTTP2_GOAWAY_FAIL_ON_SEND; + if(session->goaway_flags & NGHTTP2_GOAWAY_SEND) { + return 0; + } return nghttp2_submit_goaway(session, error_code, NULL, 0); } @@ -2806,8 +2812,23 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, session->iframe.state = NGHTTP2_RECV_PAYLOAD; session->iframe.payloadlen = nghttp2_get_uint16(&session->iframe.headbuf[0]); - if(!nghttp2_frame_is_data_frame(session->iframe.headbuf)) { - /* control frame */ + /* TODO Make payloadlen configurable up to + NGHTTP2_MAX_FRAME_LENGTH */ + if(session->iframe.payloadlen > NGHTTP2_MAX_HTTP_FRAME_LENGTH) { + session->iframe.error_code = NGHTTP2_ERR_FRAME_TOO_LARGE; + session->iframe.state = NGHTTP2_RECV_PAYLOAD_IGN; + /* Make inflater fail forcibly to disallow reception of + further HEADERS or PUSH_PROMISE */ + session->hd_inflater.bad = 1; + /* Just tear down session for now */ + r = nghttp2_session_fail_session(session, NGHTTP2_FRAME_TOO_LARGE); + if(r != 0) { + /* FATAL */ + assert(r < NGHTTP2_ERR_FATAL); + return r; + } + } else if(!nghttp2_frame_is_data_frame(session->iframe.headbuf)) { + /* non-DATA frame */ ssize_t buflen = session->iframe.payloadlen; session->iframe.buflen = buflen; r = nghttp2_reserve_buffer(&session->iframe.buf, @@ -2901,15 +2922,19 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, } } if(session->iframe.payloadlen == session->iframe.off) { - if(!nghttp2_frame_is_data_frame(session->iframe.headbuf)) { - r = nghttp2_session_process_ctrl_frame(session); - } else { - r = nghttp2_session_process_data_frame(session); - } - if(r < 0) { - /* FATAL */ - assert(r < NGHTTP2_ERR_FATAL); - return r; + if(session->iframe.error_code != NGHTTP2_ERR_FRAME_TOO_LARGE) { + if(!nghttp2_frame_is_data_frame(session->iframe.headbuf)) { + /* TODO Introduce callback which is invoked when payload is + ignored, especially for frame too large */ + r = nghttp2_session_process_ctrl_frame(session); + } else { + r = nghttp2_session_process_data_frame(session); + } + if(r < 0) { + /* FATAL */ + assert(r < NGHTTP2_ERR_FATAL); + return r; + } } nghttp2_inbound_frame_reset(&session->iframe); } diff --git a/tests/main.c b/tests/main.c index 23a11110..260c7e34 100644 --- a/tests/main.c +++ b/tests/main.c @@ -84,6 +84,8 @@ int main(int argc, char* argv[]) test_nghttp2_session_recv_eof) || !CU_add_test(pSuite, "session_recv_data", test_nghttp2_session_recv_data) || + !CU_add_test(pSuite, "session_recv_frame_too_large", + test_nghttp2_session_recv_frame_too_large) || !CU_add_test(pSuite, "session_add_frame", test_nghttp2_session_add_frame) || !CU_add_test(pSuite, "session_on_request_headers_received", diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index 59130416..1ddde737 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -101,10 +101,9 @@ static ssize_t scripted_recv_callback(nghttp2_session *session, size_t wlen = df->feedseq[df->seqidx] > len ? len : df->feedseq[df->seqidx]; memcpy(data, df->datamark, wlen); df->datamark += wlen; - if(wlen <= len) { + df->feedseq[df->seqidx] -= wlen; + if(df->feedseq[df->seqidx] == 0) { ++df->seqidx; - } else { - df->feedseq[df->seqidx] -= wlen; } return wlen; } @@ -282,6 +281,62 @@ static const char *null_val_nv[] = { "Version", "HTTP/1.1", "Foo", NULL, NULL }; +void test_nghttp2_session_recv_frame_too_large(void) +{ + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + scripted_data_feed df; + my_user_data user_data; + uint8_t data[NGHTTP2_FRAME_HEAD_LENGTH + NGHTTP2_MAX_HTTP_FRAME_LENGTH + 1]; + nghttp2_frame_hd hd; + nghttp2_outbound_item *item; + nghttp2_frame frame; + uint8_t *framebuf = NULL; + size_t framebuflen = 0; + size_t framelen; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.recv_callback = scripted_recv_callback; + callbacks.on_frame_recv_callback = on_frame_recv_callback; + user_data.df = &df; + + nghttp2_session_client_new(&session, &callbacks, &user_data); + + nghttp2_session_open_stream(session, 1, NGHTTP2_FLAG_NONE, + NGHTTP2_PRI_DEFAULT, NGHTTP2_STREAM_OPENED, + NULL); + memset(data, 0, sizeof(data)); + hd.length = NGHTTP2_MAX_HTTP_FRAME_LENGTH + 1; + hd.type = NGHTTP2_DATA; + hd.flags = NGHTTP2_FLAG_END_STREAM; + hd.stream_id = 1; + nghttp2_frame_pack_frame_hd(data, &hd); + scripted_data_feed_init(&df, data, sizeof(data)); + + CU_ASSERT(0 == nghttp2_session_recv(session)); + + item = nghttp2_session_get_next_ob_item(session); + CU_ASSERT(item != NULL); + CU_ASSERT(NGHTTP2_GOAWAY == OB_CTRL_TYPE(item)); + CU_ASSERT(NGHTTP2_FRAME_TOO_LARGE == OB_CTRL(item)->goaway.error_code); + + /* Check next frame can be received */ + nghttp2_frame_ping_init(&frame.ping, NGHTTP2_FLAG_NONE, NULL); + framelen = nghttp2_frame_pack_ping(&framebuf, &framebuflen, &frame.ping); + nghttp2_frame_ping_free(&frame.ping); + + scripted_data_feed_init(&df, framebuf, framelen); + + user_data.frame_recv_cb_called = 0; + CU_ASSERT(0 == nghttp2_session_recv(session)); + + CU_ASSERT(1 == user_data.frame_recv_cb_called); + + free(framebuf); + nghttp2_session_del(session); +} + void test_nghttp2_session_recv(void) { nghttp2_session *session; diff --git a/tests/nghttp2_session_test.h b/tests/nghttp2_session_test.h index ed59bee8..e9ff58c8 100644 --- a/tests/nghttp2_session_test.h +++ b/tests/nghttp2_session_test.h @@ -30,6 +30,7 @@ void test_nghttp2_session_recv_invalid_stream_id(void); void test_nghttp2_session_recv_invalid_frame(void); void test_nghttp2_session_recv_eof(void); void test_nghttp2_session_recv_data(void); +void test_nghttp2_session_recv_frame_too_large(void); void test_nghttp2_session_add_frame(void); void test_nghttp2_session_on_request_headers_received(void); void test_nghttp2_session_on_response_headers_received(void);