]> granicus.if.org Git - libevent/commitdiff
Implement size limits on HTTP header length and body length.
authorNick Mathewson <nickm@torproject.org>
Wed, 4 Nov 2009 20:17:32 +0000 (20:17 +0000)
committerNick Mathewson <nickm@torproject.org>
Wed, 4 Nov 2009 20:17:32 +0000 (20:17 +0000)
Patch from Constantine Verutin, simplified a little.

svn:r1500

configure.in
http-internal.h
http.c
include/event2/http.h
include/event2/http_struct.h
test/regress_http.c
util-internal.h

index 2fad969b3459d0f149e19716cae842ab45127332..2056d4111212890fec379ba39939eca5a4634802 100644 (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>
index 5567e179a757b08613af413f97e3c023da924388..72a011b38d992d06f30597df25a6515b7d6c905e 100644 (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;
 
diff --git a/http.c b/http.c
index dda2c012ed3fdb47e02887c593f59650e5eb6948..cf10692b840979a144d67a40bf205887567d6d2e 100644 (file)
--- 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;
 
index 30cc5f16fca8c7f27918b08d539ac35d69e535ab..4a7fba3f69789bd508c2aa65cf6231d6c1436657 100644 (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);
 
index ccd9f10b71f0a1a54e043564f88d72a0dfbb070a..902eab74238438f696e1e9276ecba061e1d5d7cd 100644 (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 */
index 1ca02f0c7387d8b103cde421d6409601da7119cd..2fa58b8b32cd413723985df66ee176a7fefd00a6 100644 (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
 };
index ef82af995872049eb67f81eab1307afe4aef9df8..3c243d6339a36fbe96bb2bf9cb55a9e12da2c71d 100644 (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