Commit bb6f842b authored by Tatsuhiro Tsujikawa's avatar Tatsuhiro Tsujikawa

Check request/response submission error based side of session

Disallow request from server, and response from client respectively.
When the violation is detected, return NGHTTP2_ERR_PROTO from
nghttp2_submit_request, nghttp2_submit_response,
nghttp2_submit_headers.

We also did some refactoring, and now self-dependency detection is
placed where it is only required.
parent 8f225ae8
...@@ -3164,6 +3164,8 @@ nghttp2_priority_spec_check_default(const nghttp2_priority_spec *pri_spec); ...@@ -3164,6 +3164,8 @@ nghttp2_priority_spec_check_default(const nghttp2_priority_spec *pri_spec);
* :enum:`NGHTTP2_ERR_INVALID_ARGUMENT` * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT`
* Trying to depend on itself (new stream ID equals * Trying to depend on itself (new stream ID equals
* ``pri_spec->stream_id``). * ``pri_spec->stream_id``).
* :enum:`NGHTTP2_ERR_PROTO`
* The |session| is server session.
* *
* .. warning:: * .. warning::
* *
...@@ -3236,6 +3238,8 @@ nghttp2_submit_request(nghttp2_session *session, ...@@ -3236,6 +3238,8 @@ nghttp2_submit_request(nghttp2_session *session,
* processed yet. Normally, this does not happen, but when * processed yet. Normally, this does not happen, but when
* application wrongly calls `nghttp2_submit_response()` twice, * application wrongly calls `nghttp2_submit_response()` twice,
* this may happen. * this may happen.
* :enum:`NGHTTP2_ERR_PROTO`
* The |session| is client session.
* *
* .. warning:: * .. warning::
* *
...@@ -3380,6 +3384,8 @@ NGHTTP2_EXTERN int nghttp2_submit_trailer(nghttp2_session *session, ...@@ -3380,6 +3384,8 @@ NGHTTP2_EXTERN int nghttp2_submit_trailer(nghttp2_session *session,
* DATA or HEADERS has been already submitted and not fully * DATA or HEADERS has been already submitted and not fully
* processed yet. This happens if stream denoted by |stream_id| * processed yet. This happens if stream denoted by |stream_id|
* is in reserved state. * is in reserved state.
* :enum:`NGHTTP2_ERR_PROTO`
* The |stream_id| is -1, and |session| is server session.
* *
* .. warning:: * .. warning::
* *
......
...@@ -32,6 +32,35 @@ ...@@ -32,6 +32,35 @@
#include "nghttp2_helper.h" #include "nghttp2_helper.h"
#include "nghttp2_priority_spec.h" #include "nghttp2_priority_spec.h"
/*
* Detects the dependency error, that is stream attempted to depend on
* itself. If |stream_id| is -1, we use session->next_stream_id as
* stream ID.
*
* This function returns 0 if it succeeds, or one of the following
* error codes:
*
* NGHTTP2_ERR_INVALID_ARGUMENT
* Stream attempted to depend on itself.
*/
static int detect_self_dependency(nghttp2_session *session, int32_t stream_id,
const nghttp2_priority_spec *pri_spec) {
assert(pri_spec);
if (stream_id == -1) {
if ((int32_t)session->next_stream_id == pri_spec->stream_id) {
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
return 0;
}
if (stream_id == pri_spec->stream_id) {
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
return 0;
}
/* This function takes ownership of |nva_copy|. Regardless of the /* This function takes ownership of |nva_copy|. Regardless of the
return value, the caller must not free |nva_copy| after this return value, the caller must not free |nva_copy| after this
function returns. */ function returns. */
...@@ -50,21 +79,6 @@ static int32_t submit_headers_shared(nghttp2_session *session, uint8_t flags, ...@@ -50,21 +79,6 @@ static int32_t submit_headers_shared(nghttp2_session *session, uint8_t flags,
mem = &session->mem; mem = &session->mem;
if (stream_id == 0) {
rv = NGHTTP2_ERR_INVALID_ARGUMENT;
goto fail;
}
if (stream_id == -1) {
if ((int32_t)session->next_stream_id == pri_spec->stream_id) {
rv = NGHTTP2_ERR_INVALID_ARGUMENT;
goto fail;
}
} else if (stream_id == pri_spec->stream_id) {
rv = NGHTTP2_ERR_INVALID_ARGUMENT;
goto fail;
}
item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item)); item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item));
if (item == NULL) { if (item == NULL) {
rv = NGHTTP2_ERR_NOMEM; rv = NGHTTP2_ERR_NOMEM;
...@@ -156,6 +170,10 @@ static int32_t submit_headers_shared_nva(nghttp2_session *session, ...@@ -156,6 +170,10 @@ static int32_t submit_headers_shared_nva(nghttp2_session *session,
int nghttp2_submit_trailer(nghttp2_session *session, int32_t stream_id, int nghttp2_submit_trailer(nghttp2_session *session, int32_t stream_id,
const nghttp2_nv *nva, size_t nvlen) { const nghttp2_nv *nva, size_t nvlen) {
if (stream_id <= 0) {
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
return (int)submit_headers_shared_nva(session, NGHTTP2_FLAG_END_STREAM, return (int)submit_headers_shared_nva(session, NGHTTP2_FLAG_END_STREAM,
stream_id, NULL, nva, nvlen, NULL, stream_id, NULL, nva, nvlen, NULL,
NULL); NULL);
...@@ -166,9 +184,24 @@ int32_t nghttp2_submit_headers(nghttp2_session *session, uint8_t flags, ...@@ -166,9 +184,24 @@ int32_t nghttp2_submit_headers(nghttp2_session *session, uint8_t flags,
const nghttp2_priority_spec *pri_spec, const nghttp2_priority_spec *pri_spec,
const nghttp2_nv *nva, size_t nvlen, const nghttp2_nv *nva, size_t nvlen,
void *stream_user_data) { void *stream_user_data) {
int rv;
if (stream_id == -1) {
if (session->server) {
return NGHTTP2_ERR_PROTO;
}
} else if (stream_id <= 0) {
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
flags &= NGHTTP2_FLAG_END_STREAM; flags &= NGHTTP2_FLAG_END_STREAM;
if (pri_spec && !nghttp2_priority_spec_check_default(pri_spec)) { if (pri_spec && !nghttp2_priority_spec_check_default(pri_spec)) {
rv = detect_self_dependency(session, stream_id, pri_spec);
if (rv != 0) {
return rv;
}
flags |= NGHTTP2_FLAG_PRIORITY; flags |= NGHTTP2_FLAG_PRIORITY;
} else { } else {
pri_spec = NULL; pri_spec = NULL;
...@@ -281,7 +314,7 @@ int32_t nghttp2_submit_push_promise(nghttp2_session *session, uint8_t flags _U_, ...@@ -281,7 +314,7 @@ int32_t nghttp2_submit_push_promise(nghttp2_session *session, uint8_t flags _U_,
mem = &session->mem; mem = &session->mem;
if (stream_id == 0 || nghttp2_session_is_my_stream_id(session, stream_id)) { if (stream_id <= 0 || nghttp2_session_is_my_stream_id(session, stream_id)) {
return NGHTTP2_ERR_INVALID_ARGUMENT; return NGHTTP2_ERR_INVALID_ARGUMENT;
} }
...@@ -396,8 +429,18 @@ int32_t nghttp2_submit_request(nghttp2_session *session, ...@@ -396,8 +429,18 @@ int32_t nghttp2_submit_request(nghttp2_session *session,
const nghttp2_data_provider *data_prd, const nghttp2_data_provider *data_prd,
void *stream_user_data) { void *stream_user_data) {
uint8_t flags; uint8_t flags;
int rv;
if (session->server) {
return NGHTTP2_ERR_PROTO;
}
if (pri_spec && nghttp2_priority_spec_check_default(pri_spec)) { if (pri_spec && !nghttp2_priority_spec_check_default(pri_spec)) {
rv = detect_self_dependency(session, -1, pri_spec);
if (rv != 0) {
return rv;
}
} else {
pri_spec = NULL; pri_spec = NULL;
} }
...@@ -418,7 +461,17 @@ static uint8_t set_response_flags(const nghttp2_data_provider *data_prd) { ...@@ -418,7 +461,17 @@ static uint8_t set_response_flags(const nghttp2_data_provider *data_prd) {
int nghttp2_submit_response(nghttp2_session *session, int32_t stream_id, int nghttp2_submit_response(nghttp2_session *session, int32_t stream_id,
const nghttp2_nv *nva, size_t nvlen, const nghttp2_nv *nva, size_t nvlen,
const nghttp2_data_provider *data_prd) { const nghttp2_data_provider *data_prd) {
uint8_t flags = set_response_flags(data_prd); uint8_t flags;
if (stream_id <= 0) {
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
if (!session->server) {
return NGHTTP2_ERR_PROTO;
}
flags = set_response_flags(data_prd);
return submit_headers_shared_nva(session, flags, stream_id, NULL, nva, nvlen, return submit_headers_shared_nva(session, flags, stream_id, NULL, nva, nvlen,
data_prd, NULL); data_prd, NULL);
} }
......
...@@ -3897,6 +3897,15 @@ void test_nghttp2_submit_request_with_data(void) { ...@@ -3897,6 +3897,15 @@ void test_nghttp2_submit_request_with_data(void) {
CU_ASSERT(0 == ud.data_source_length); CU_ASSERT(0 == ud.data_source_length);
nghttp2_session_del(session); nghttp2_session_del(session);
/* nghttp2_submit_request() with server session is error */
nghttp2_session_server_new(&session, &callbacks, NULL);
CU_ASSERT(NGHTTP2_ERR_PROTO == nghttp2_submit_request(session, NULL, reqnv,
ARRLEN(reqnv), NULL,
NULL));
nghttp2_session_del(session);
} }
void test_nghttp2_submit_request_without_data(void) { void test_nghttp2_submit_request_without_data(void) {
...@@ -3984,6 +3993,19 @@ void test_nghttp2_submit_response_with_data(void) { ...@@ -3984,6 +3993,19 @@ void test_nghttp2_submit_response_with_data(void) {
CU_ASSERT(0 == ud.data_source_length); CU_ASSERT(0 == ud.data_source_length);
nghttp2_session_del(session); nghttp2_session_del(session);
/* Various error cases */
nghttp2_session_client_new(&session, &callbacks, NULL);
/* Calling nghttp2_submit_response() with client session is error */
CU_ASSERT(NGHTTP2_ERR_PROTO ==
nghttp2_submit_response(session, 1, resnv, ARRLEN(resnv), NULL));
/* Stream ID <= 0 is error */
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT ==
nghttp2_submit_response(session, 0, resnv, ARRLEN(resnv), NULL));
nghttp2_session_del(session);
} }
void test_nghttp2_submit_response_without_data(void) { void test_nghttp2_submit_response_without_data(void) {
...@@ -4115,6 +4137,18 @@ void test_nghttp2_submit_trailer(void) { ...@@ -4115,6 +4137,18 @@ void test_nghttp2_submit_trailer(void) {
nghttp2_frame_headers_free(&frame.headers, mem); nghttp2_frame_headers_free(&frame.headers, mem);
nghttp2_hd_inflate_free(&inflater); nghttp2_hd_inflate_free(&inflater);
nghttp2_session_del(session); nghttp2_session_del(session);
/* Specifying stream ID <= 0 is error */
nghttp2_session_server_new(&session, &callbacks, NULL);
open_recv_stream(session, 1);
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT ==
nghttp2_submit_trailer(session, 0, trailernv, ARRLEN(trailernv)));
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT ==
nghttp2_submit_trailer(session, -1, trailernv, ARRLEN(trailernv)));
nghttp2_session_del(session);
} }
void test_nghttp2_submit_headers_start_stream(void) { void test_nghttp2_submit_headers_start_stream(void) {
...@@ -4305,6 +4339,22 @@ void test_nghttp2_submit_headers(void) { ...@@ -4305,6 +4339,22 @@ void test_nghttp2_submit_headers(void) {
reqnv, ARRLEN(reqnv), NULL)); reqnv, ARRLEN(reqnv), NULL));
nghttp2_session_del(session); nghttp2_session_del(session);
/* Error cases with invalid stream ID */
nghttp2_session_server_new(&session, &callbacks, NULL);
/* Sending nghttp2_submit_headers() with stream_id == 1 and server
session is error */
CU_ASSERT(NGHTTP2_ERR_PROTO ==
nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, -1, NULL, reqnv,
ARRLEN(reqnv), NULL));
/* Sending stream ID <= 0 is error */
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT ==
nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, 0, NULL, resnv,
ARRLEN(resnv), NULL));
nghttp2_session_del(session);
} }
void test_nghttp2_submit_headers_continuation(void) { void test_nghttp2_submit_headers_continuation(void) {
...@@ -4642,7 +4692,12 @@ void test_nghttp2_submit_push_promise(void) { ...@@ -4642,7 +4692,12 @@ void test_nghttp2_submit_push_promise(void) {
/* submit PUSH_PROMISE while associated stream is not opened */ /* submit PUSH_PROMISE while associated stream is not opened */
CU_ASSERT(NGHTTP2_ERR_STREAM_CLOSED == CU_ASSERT(NGHTTP2_ERR_STREAM_CLOSED ==
nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 3, reqnv, nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 3, reqnv,
ARRLEN(reqnv), &ud)); ARRLEN(reqnv), NULL));
/* Stream ID <= 0 is error */
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT ==
nghttp2_submit_push_promise(session, NGHTTP2_FLAG_NONE, 0, reqnv,
ARRLEN(reqnv), NULL));
nghttp2_session_del(session); nghttp2_session_del(session);
} }
...@@ -4850,19 +4905,10 @@ void test_nghttp2_submit_invalid_nv(void) { ...@@ -4850,19 +4905,10 @@ void test_nghttp2_submit_invalid_nv(void) {
CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, NULL)); CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, NULL));
/* nghttp2_submit_request */
CU_ASSERT(0 < nghttp2_submit_request(session, NULL, empty_name_nv,
ARRLEN(empty_name_nv), NULL, NULL));
/* nghttp2_submit_response */ /* nghttp2_submit_response */
CU_ASSERT(0 == nghttp2_submit_response(session, 2, empty_name_nv, CU_ASSERT(0 == nghttp2_submit_response(session, 2, empty_name_nv,
ARRLEN(empty_name_nv), NULL)); ARRLEN(empty_name_nv), NULL));
/* nghttp2_submit_headers */
CU_ASSERT(0 < nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, -1, NULL,
empty_name_nv, ARRLEN(empty_name_nv),
NULL));
/* nghttp2_submit_push_promise */ /* nghttp2_submit_push_promise */
open_recv_stream(session, 1); open_recv_stream(session, 1);
...@@ -4871,6 +4917,19 @@ void test_nghttp2_submit_invalid_nv(void) { ...@@ -4871,6 +4917,19 @@ void test_nghttp2_submit_invalid_nv(void) {
ARRLEN(empty_name_nv), NULL)); ARRLEN(empty_name_nv), NULL));
nghttp2_session_del(session); nghttp2_session_del(session);
CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, NULL));
/* nghttp2_submit_request */
CU_ASSERT(0 < nghttp2_submit_request(session, NULL, empty_name_nv,
ARRLEN(empty_name_nv), NULL, NULL));
/* nghttp2_submit_headers */
CU_ASSERT(0 < nghttp2_submit_headers(session, NGHTTP2_FLAG_NONE, -1, NULL,
empty_name_nv, ARRLEN(empty_name_nv),
NULL));
nghttp2_session_del(session);
} }
void test_nghttp2_session_open_stream(void) { void test_nghttp2_session_open_stream(void) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment