From: Nick Mathewson Date: Wed, 4 Nov 2009 20:17:32 +0000 (+0000) Subject: Implement size limits on HTTP header length and body length. X-Git-Tag: release-2.0.3-alpha~45 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=47bad8abb70c49f1d2bc1ddbc13ec7ec96bc20f5;p=libevent Implement size limits on HTTP header length and body length. Patch from Constantine Verutin, simplified a little. svn:r1500 --- diff --git a/configure.in b/configure.in index 2fad969b..2056d411 100644 --- a/configure.in +++ b/configure.in @@ -364,6 +364,7 @@ AC_CHECK_SIZEOF(long long) AC_CHECK_SIZEOF(long) AC_CHECK_SIZEOF(int) AC_CHECK_SIZEOF(short) +AC_CHECK_SIZEOF(size_t) AC_CHECK_TYPES([struct in6_addr, struct sockaddr_in6, sa_family_t], , , [#include diff --git a/http-internal.h b/http-internal.h index 5567e179..72a011b3 100644 --- a/http-internal.h +++ b/http-internal.h @@ -24,7 +24,8 @@ enum message_read_status { ALL_DATA_READ = 1, MORE_DATA_EXPECTED = 0, DATA_CORRUPTED = -1, - REQUEST_CANCELED = -2 + REQUEST_CANCELED = -2, + DATA_TOO_LONG = -3 }; enum evhttp_connection_error { @@ -70,6 +71,9 @@ struct evhttp_connection { char *address; /* address to connect to */ u_short port; + size_t max_headers_size; + uint64_t max_body_size; + int flags; #define EVHTTP_CON_INCOMING 0x0001 /* only one request on it ever */ #define EVHTTP_CON_OUTGOING 0x0002 /* multiple requests possible */ @@ -127,8 +131,11 @@ struct evhttp { /* NULL if this server is not a vhost */ char *vhost_pattern; - int timeout; + int timeout; + size_t default_max_headers_size; + size_t default_max_body_size; + void (*gencb)(struct evhttp_request *req, void *); void *gencbarg; diff --git a/http.c b/http.c index dda2c012..cf10692b 100644 --- a/http.c +++ b/http.c @@ -557,6 +557,25 @@ evhttp_make_header(struct evhttp_connection *evcon, struct evhttp_request *req) } } +void +evhttp_connection_set_max_headers_size(struct evhttp_connection *evcon, + ev_ssize_t new_max_headers_size) +{ + if (new_max_headers_size<0) + evcon->max_headers_size = EV_SIZE_MAX; + else + evcon->max_headers_size = new_max_headers_size; +} +void +evhttp_connection_set_max_body_size(struct evhttp_connection* evcon, + ev_ssize_t new_max_body_size) +{ + if (new_max_body_size<0) + evcon->max_body_size = EV_UINT64_MAX; + else + evcon->max_body_size = new_max_body_size; +} + static int evhttp_connection_incoming_fail(struct evhttp_request *req, enum evhttp_connection_error error) @@ -730,6 +749,8 @@ evhttp_connection_done(struct evhttp_connection *evcon) * data is corrupted * return REQUEST_CANCELED: * request was canceled by the user calling evhttp_cancel_request + * return DATA_TOO_LONG: + * ran over the maximum limit */ static enum message_read_status @@ -760,6 +781,12 @@ evhttp_handle_chunked_read(struct evhttp_request *req, struct evbuffer *buf) /* could not get chunk size */ return (DATA_CORRUPTED); } + if (req->body_size + ntoread > req->evcon->max_body_size) { + /* failed body length test */ + event_debug(("Request body is too long")); + return (DATA_TOO_LONG); + } + req->body_size += ntoread; req->ntoread = ntoread; if (req->ntoread == 0) { /* Last chunk */ @@ -798,6 +825,7 @@ evhttp_read_trailer(struct evhttp_connection *evcon, struct evhttp_request *req) switch (evhttp_parse_headers(req, buf)) { case DATA_CORRUPTED: + case DATA_TOO_LONG: evhttp_connection_fail(evcon, EVCON_HTTP_INVALID_HEADER); break; case ALL_DATA_READ: @@ -825,6 +853,7 @@ evhttp_read_body(struct evhttp_connection *evcon, struct evhttp_request *req) evhttp_read_trailer(evcon, req); return; case DATA_CORRUPTED: + case DATA_TOO_LONG:/*separate error for this? XXX */ /* corrupted data */ evhttp_connection_fail(evcon, EVCON_HTTP_INVALID_HEADER); @@ -840,14 +869,24 @@ evhttp_read_body(struct evhttp_connection *evcon, struct evhttp_request *req) } else if (req->ntoread < 0) { /* Read until connection close. */ evbuffer_add_buffer(req->input_buffer, buf); + req->body_size += evbuffer_get_length(buf); } else if (req->chunk_cb != NULL || evbuffer_get_length(buf) >= req->ntoread) { /* We've postponed moving the data until now, but we're * about to use it. */ req->ntoread -= evbuffer_get_length(buf); + req->body_size += evbuffer_get_length(buf); evbuffer_add_buffer(req->input_buffer, buf); } + if (req->body_size > req->evcon->max_body_size) { + /* failed body length test */ + event_debug(("Request body is too long")); + evhttp_connection_fail(evcon, + EVCON_HTTP_INVALID_HEADER); + return; + } + if (evbuffer_get_length(req->input_buffer) > 0 && req->chunk_cb != NULL) { req->flags |= EVHTTP_REQ_DEFER_FREE; (*req->chunk_cb)(req, req->cb_arg); @@ -1452,9 +1491,24 @@ evhttp_parse_firstline(struct evhttp_request *req, struct evbuffer *buffer) char *line; enum message_read_status status = ALL_DATA_READ; - line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_CRLF); - if (line == NULL) - return (MORE_DATA_EXPECTED); + size_t line_length; + /* XXX try */ + line = evbuffer_readln(buffer, &line_length, EVBUFFER_EOL_CRLF); + if (line == NULL) { + if (req->evcon != NULL && + evbuffer_get_length(buffer) > req->evcon->max_headers_size) + return (DATA_TOO_LONG); + else + return (MORE_DATA_EXPECTED); + } + + if (req->evcon != NULL && + line_length > req->evcon->max_headers_size) { + mm_free(line); + return (DATA_TOO_LONG); + } + + req->headers_size = line_length; switch (req->kind) { case EVHTTP_REQUEST: @@ -1499,14 +1553,24 @@ evhttp_append_to_last_header(struct evkeyvalq *headers, const char *line) enum message_read_status evhttp_parse_headers(struct evhttp_request *req, struct evbuffer* buffer) { + enum message_read_status errcode = DATA_CORRUPTED; char *line; enum message_read_status status = MORE_DATA_EXPECTED; struct evkeyvalq* headers = req->input_headers; - while ((line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_CRLF)) + size_t line_length; + while ((line = evbuffer_readln(buffer, &line_length, EVBUFFER_EOL_CRLF)) != NULL) { char *skey, *svalue; + req->headers_size += line_length; + + if (req->evcon != NULL && + req->headers_size > req->evcon->max_headers_size) { + errcode = DATA_TOO_LONG; + goto error; + } + if (*line == '\0') { /* Last header - Done */ status = ALL_DATA_READ; mm_free(line); @@ -1535,11 +1599,16 @@ evhttp_parse_headers(struct evhttp_request *req, struct evbuffer* buffer) mm_free(line); } + if (status == MORE_DATA_EXPECTED) { + if (req->headers_size + evbuffer_get_length(buffer) > req->evcon->max_headers_size) + return (DATA_TOO_LONG); + } + return (status); error: mm_free(line); - return (DATA_CORRUPTED); + return (errcode); } static int @@ -1615,7 +1684,7 @@ evhttp_read_firstline(struct evhttp_connection *evcon, enum message_read_status res; res = evhttp_parse_firstline(req, bufferevent_get_input(evcon->bufev)); - if (res == DATA_CORRUPTED) { + if (res == DATA_CORRUPTED || res == DATA_TOO_LONG) { /* Error while reading, terminate */ event_debug(("%s: bad header lines on %d\n", __func__, evcon->fd)); @@ -1638,7 +1707,7 @@ evhttp_read_header(struct evhttp_connection *evcon, int fd = evcon->fd; res = evhttp_parse_headers(req, bufferevent_get_input(evcon->bufev)); - if (res == DATA_CORRUPTED) { + if (res == DATA_CORRUPTED || res == DATA_TOO_LONG) { /* Error while reading, terminate */ event_debug(("%s: bad header lines on %d\n", __func__, fd)); evhttp_connection_fail(evcon, EVCON_HTTP_INVALID_HEADER); @@ -1714,6 +1783,9 @@ evhttp_connection_base_new(struct event_base *base, evcon->fd = -1; evcon->port = port; + evcon->max_headers_size = EV_SIZE_MAX; + evcon->max_body_size = EV_SIZE_MAX; + evcon->timeout = -1; evcon->retry_cnt = evcon->retry_max = 0; @@ -2481,6 +2553,8 @@ evhttp_new_object(void) } http->timeout = -1; + evhttp_set_max_headers_size(http, EV_SIZE_MAX); + evhttp_set_max_body_size(http, EV_SIZE_MAX); TAILQ_INIT(&http->sockets); TAILQ_INIT(&http->callbacks); @@ -2598,6 +2672,14 @@ evhttp_set_timeout(struct evhttp* http, int timeout_in_secs) http->timeout = timeout_in_secs; } +void evhttp_set_max_headers_size(struct evhttp* http, ssize_t max_headers_size) { + http->default_max_headers_size = max_headers_size; +} + +void evhttp_set_max_body_size(struct evhttp* http, ssize_t max_body_size) { + http->default_max_body_size = max_body_size; +} + int evhttp_set_cb(struct evhttp *http, const char *uri, void (*cb)(struct evhttp_request *, void *), void *cbarg) @@ -2663,6 +2745,9 @@ evhttp_request_new(void (*cb)(struct evhttp_request *, void *), void *arg) goto error; } + req->headers_size = 0; + req->body_size = 0; + req->kind = EVHTTP_RESPONSE; req->input_headers = mm_calloc(1, sizeof(struct evkeyvalq)); if (req->input_headers == NULL) { @@ -2815,6 +2900,9 @@ evhttp_get_request_connection( if (evcon == NULL) return (NULL); + evhttp_connection_set_max_headers_size(evcon, http->default_max_headers_size); + evhttp_connection_set_max_body_size(evcon, http->default_max_body_size); + evcon->flags |= EVHTTP_CON_INCOMING; evcon->state = EVCON_READING_FIRSTLINE; diff --git a/include/event2/http.h b/include/event2/http.h index 30cc5f16..4a7fba3f 100644 --- a/include/event2/http.h +++ b/include/event2/http.h @@ -172,6 +172,11 @@ evutil_socket_t evhttp_bound_socket_get_fd(struct evhttp_bound_socket *bound_soc */ void evhttp_free(struct evhttp* http); +/** XXX Document. */ +void evhttp_set_max_headers_size(struct evhttp* http, ev_ssize_t max_headers_size); +/** XXX Document. */ +void evhttp_set_max_body_size(struct evhttp* http, ev_ssize_t max_body_size); + /** Set a callback for a specified URI @@ -366,6 +371,12 @@ void evhttp_request_own(struct evhttp_request *req); /** Returns 1 if the request is owned by the user */ int evhttp_request_is_owned(struct evhttp_request *req); +void evhttp_connection_set_max_headers_size(struct evhttp_connection *evcon, + ssize_t new_max_headers_size); + +void evhttp_connection_set_max_body_size(struct evhttp_connection* evcon, + ssize_t new_max_body_size); + /** Frees an http connection */ void evhttp_connection_free(struct evhttp_connection *evcon); diff --git a/include/event2/http_struct.h b/include/event2/http_struct.h index ccd9f10b..902eab74 100644 --- a/include/event2/http_struct.h +++ b/include/event2/http_struct.h @@ -88,6 +88,9 @@ struct { enum evhttp_request_kind kind; enum evhttp_cmd_type type; + size_t headers_size; + size_t body_size; + char *uri; /* uri after HTTP request was parsed */ char major; /* HTTP Major number */ diff --git a/test/regress_http.c b/test/regress_http.c index 1ca02f0c..2fa58b8b 100644 --- a/test/regress_http.c +++ b/test/regress_http.c @@ -2190,6 +2190,81 @@ http_negative_content_length_test(void) evhttp_free(http); } + +static void +http_data_length_constraints_test_done(struct evhttp_request *req, void *arg) +{ + tt_assert(req); + tt_int_op(req->response_code, ==, HTTP_BADREQUEST); +end: + event_loopexit(NULL); +} + +static void +http_data_length_constraints_test(void) +{ + short port = -1; + struct evhttp_connection *evcon = NULL; + struct evhttp_request *req = NULL; + char long_str[8192]; + + test_ok = 0; + + http = http_setup(&port, NULL); + + evcon = evhttp_connection_new("127.0.0.1", port); + tt_assert(evcon); + + /* also bind to local host */ + evhttp_connection_set_local_address(evcon, "127.0.0.1"); + + /* + * At this point, we want to schedule an HTTP GET request + * server using our make request method. + */ + + req = evhttp_request_new(http_data_length_constraints_test_done, NULL); + tt_assert(req); + + memset(long_str, 'a', 8192); + long_str[8191] = '\0'; + /* Add the information that we care about */ + evhttp_set_max_headers_size(http, 8191); + evhttp_add_header(req->output_headers, "Host", "somehost"); + evhttp_add_header(req->output_headers, "Longheader", long_str); + + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, "/?arg=val") == -1) { + tt_abort_msg("Couldn't make request"); + } + event_dispatch(); + + req = evhttp_request_new(http_data_length_constraints_test_done, NULL); + tt_assert(req); + evhttp_add_header(req->output_headers, "Host", "somehost"); + + /* GET /?arg=verylongvalue HTTP/1.1 */ + if (evhttp_make_request(evcon, req, EVHTTP_REQ_GET, long_str) == -1) { + tt_abort_msg("Couldn't make request"); + } + event_dispatch(); + + evhttp_set_max_body_size(http, 8190); + req = evhttp_request_new(http_data_length_constraints_test_done, NULL); + evhttp_add_header(req->output_headers, "Host", "somehost"); + evbuffer_add_printf(req->output_buffer, long_str); + if (evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/") == -1) { + tt_abort_msg("Couldn't make request"); + } + event_dispatch(); + + test_ok = 1; + end: + if (evcon) + evhttp_connection_free(evcon); + if (http) + evhttp_free(http); +} + #define HTTP_LEGACY(name) \ { #name, run_legacy_test_fn, TT_ISOLATED|TT_LEGACY, &legacy_setup, \ http_##name##_test } @@ -2224,6 +2299,7 @@ struct testcase_t http_testcases[] = { HTTP_LEGACY(stream_in_cancel), HTTP_LEGACY(connection_retry), + HTTP_LEGACY(data_length_constraints), END_OF_TESTCASES }; diff --git a/util-internal.h b/util-internal.h index ef82af99..3c243d63 100644 --- a/util-internal.h +++ b/util-internal.h @@ -36,6 +36,7 @@ #ifdef _EVENT_HAVE_SYS_SOCKET_H #include #endif +#include "event2/util.h" #ifdef __cplusplus extern "C" { @@ -167,6 +168,41 @@ int evutil_resolve(int family, const char *hostname, struct sockaddr *sa, } \ } while(0) +#ifdef UINT64_MAX +#define EV_UINT64_MAX UINT64_MAX +#elif defined(WIN32) +#define EV_UINT64_MAX 0xffffffffffffffffui64 +#elif _EVENT_SIZEOF_LONG_LONG == 8 +#define EV_UINT64_MAX 0xffffffffffffffffull +#elif _EVENT_SIZEOF_LONG == 8 +#define EV_UINT64_MAX 0xfffffffffffffffful +#else +/* Hope for a two's complement representation */ +#define EV_UINT64_MAX ((ev_uint64_t)-1) +#endif + +#ifdef UINT32_MAX +#define EV_UINT32_MAX UINT32_MAX +#elif defined(WIN32) +#define EV_UINT32_MAX 0xffffffffui64 +#elif _EVENT_SIZEOF_INT == 4 +#define EV_UINT32_MAX 0xffffffffu +#elif _EVENT_SIZEOF_LONG == 4 +#define EV_UINT32_MAX 0xfffffffful +#else +/* Hope for a two's complement representation */ +#define EV_UINT32_MAX ((ev_uint32_t)-1) +#endif + +#if _EVENT_SIZEOF_SIZE_T == 8 +#define EV_SIZE_MAX EV_UINT64_MAX +#elif _EVENT_SIZEOF_SIZE_T == 4 +#define EV_SIZE_MAX EV_UINT32_MAX +#else +/* Hope for a two's complement representation */ +#define EV_SIZE_MAX ((size_t)-1) +#endif + #ifdef __cplusplus } #endif