Implement size limits on HTTP header length and body length.

Patch from Constantine Verutin, simplified a little.

svn:r1500
This commit is contained in:
Nick Mathewson 2009-11-04 20:17:32 +00:00
parent 86db1c851b
commit 47bad8abb7
7 changed files with 231 additions and 9 deletions

View File

@ -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 <sys/types.h>

View File

@ -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;

102
http.c
View File

@ -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;

View File

@ -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);

View File

@ -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 */

View File

@ -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
};

View File

@ -36,6 +36,7 @@
#ifdef _EVENT_HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#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