Nick,
On Thu, Oct 8, 2009 at 3:01 AM, Nick Mathewson <[email protected]> wrote:
> So long as we are painting the bike shed, I'd suggest that we just
> admit that we want a signed type and use an ssize_t (or in our case,
> an ev_ssize_t) for this. No muss, no fuss.
>
> As for the original patch, I'm wondering a bit about the complexity.
> I'm assuming that the idea here is to keep from running out of memory
> if the HTTP request or response is too big or complex, and that sounds
> like a fine idea. But what's the rationale for having separate limits
> for the first line and for the total headers? And why limit both
> the number of headers and their total length?
>
Please see the updated patch.
--
WBR,
Constantine
diff -ur ./libevent-2.0.2-alpha/http.c ./libevent-2.0.2-alpha-patched/http.c
--- ./libevent-2.0.2-alpha/http.c 2009-05-15 09:40:58.000000000 +0300
+++ ./libevent-2.0.2-alpha-patched/http.c 2009-10-15 22:20:09.000000000 +0300
@@ -556,6 +556,19 @@
}
}
+void
+evhttp_connection_set_max_headers_size(struct evhttp_connection *evcon,
+ ssize_t new_max_headers_size)
+{
+ evcon->max_headers_size = new_max_headers_size;
+}
+void
+evhttp_connection_set_max_body_size(struct evhttp_connection* evcon,
+ ssize_t new_max_body_size)
+{
+ evcon->max_body_size = new_max_body_size;
+}
+
static int
evhttp_connection_incoming_fail(struct evhttp_request *req,
enum evhttp_connection_error error)
@@ -759,6 +772,13 @@
/* could not get chunk size */
return (DATA_CORRUPTED);
}
+ if (req->evcon->max_body_size != SIZE_UNLIMITED &&
+ req->body_size + ntoread > req->evcon->max_body_size) {
+ /* failed body length test */
+ event_debug(("%s\n", "Request body is too long"));
+ return (DATA_CORRUPTED);
+ }
+ req->body_size += ntoread;
req->ntoread = ntoread;
if (req->ntoread == 0) {
/* Last chunk */
@@ -839,14 +859,25 @@
} 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->evcon->max_body_size != SIZE_UNLIMITED &&
+ req->body_size > req->evcon->max_body_size) {
+ /* failed body length test */
+ event_debug(("%s\n", "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);
@@ -1451,10 +1482,26 @@
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;
+ line = evbuffer_readln(buffer, &line_length, EVBUFFER_EOL_CRLF);
+ if (line == NULL) {
+ if (req->evcon != NULL &&
+ req->evcon->max_headers_size != SIZE_UNLIMITED &&
+ evbuffer_get_length(buffer) > req->evcon->max_headers_size)
+ return (DATA_CORRUPTED);
+ else
+ return (MORE_DATA_EXPECTED);
+ }
+
+ if (req->evcon != NULL &&
+ req->evcon->max_headers_size != SIZE_UNLIMITED &&
+ line_length > req->evcon->max_headers_size) {
+ mm_free(line);
+ return (DATA_CORRUPTED);
+ }
+ req->headers_size = line_length;
+
switch (req->kind) {
case EVHTTP_REQUEST:
if (evhttp_parse_request_line(req, line) == -1)
@@ -1502,10 +1549,18 @@
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->evcon->max_headers_size != SIZE_UNLIMITED &&
+ req->headers_size > req->evcon->max_headers_size)
+ goto error;
+
if (*line == '\0') { /* Last header - Done */
status = ALL_DATA_READ;
mm_free(line);
@@ -1534,6 +1589,12 @@
mm_free(line);
}
+ if (status == MORE_DATA_EXPECTED) {
+ if (req->evcon->max_headers_size != SIZE_UNLIMITED &&
+ req->headers_size + evbuffer_get_length(buffer) > req->evcon->max_headers_size)
+ return (DATA_CORRUPTED);
+ }
+
return (status);
error:
@@ -1713,6 +1774,9 @@
evcon->fd = -1;
evcon->port = port;
+ evcon->max_headers_size = SIZE_UNLIMITED;
+ evcon->max_body_size = SIZE_UNLIMITED;
+
evcon->timeout = -1;
evcon->retry_cnt = evcon->retry_max = 0;
@@ -2444,7 +2508,9 @@
}
http->timeout = -1;
-
+ evhttp_set_max_headers_size(http, SIZE_UNLIMITED);
+ evhttp_set_max_body_size(http, SIZE_UNLIMITED);
+
TAILQ_INIT(&http->sockets);
TAILQ_INIT(&http->callbacks);
TAILQ_INIT(&http->connections);
@@ -2561,6 +2627,14 @@
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)
@@ -2626,6 +2700,9 @@
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) {
@@ -2778,6 +2855,9 @@
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 -ur ./libevent-2.0.2-alpha/http-internal.h ./libevent-2.0.2-alpha-patched/http-internal.h
--- ./libevent-2.0.2-alpha/http-internal.h 2009-07-25 06:42:30.000000000 +0300
+++ ./libevent-2.0.2-alpha-patched/http-internal.h 2009-10-15 16:11:02.000000000 +0300
@@ -20,6 +20,8 @@
#define HTTP_PREFIX "http://"
#define HTTP_DEFAULTPORT 80
+#define SIZE_UNLIMITED -1
+
enum message_read_status {
ALL_DATA_READ = 1,
MORE_DATA_EXPECTED = 0,
@@ -70,6 +72,9 @@
char *address; /* address to connect to */
u_short port;
+ ssize_t max_headers_size;
+ ssize_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 +132,11 @@
/* 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 -ur ./libevent-2.0.2-alpha/include/event2/http.h ./libevent-2.0.2-alpha-patched/include/event2/http.h
--- ./libevent-2.0.2-alpha/include/event2/http.h 2009-07-25 06:42:30.000000000 +0300
+++ ./libevent-2.0.2-alpha-patched/include/event2/http.h 2009-10-15 16:12:07.000000000 +0300
@@ -119,6 +119,9 @@
*/
void evhttp_free(struct evhttp* http);
+void evhttp_set_max_headers_size(struct evhttp* http, ssize_t max_headers_size);
+void evhttp_set_max_body_size(struct evhttp* http, ssize_t max_body_size);
+
/**
Set a callback for a specified URI
@@ -313,6 +316,12 @@
/** 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 -ur ./libevent-2.0.2-alpha/include/event2/http_struct.h ./libevent-2.0.2-alpha-patched/include/event2/http_struct.h
--- ./libevent-2.0.2-alpha/include/event2/http_struct.h 2009-05-29 01:50:37.000000000 +0300
+++ ./libevent-2.0.2-alpha-patched/include/event2/http_struct.h 2009-10-15 16:49:06.000000000 +0300
@@ -88,6 +88,9 @@
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 */
Only in ./libevent-2.0.2-alpha/test: regress.gen.c
Only in ./libevent-2.0.2-alpha/test: regress.gen.h
diff -ur ./libevent-2.0.2-alpha/test/regress_http.c ./libevent-2.0.2-alpha-patched/test/regress_http.c
--- ./libevent-2.0.2-alpha/test/regress_http.c 2009-07-25 06:42:29.000000000 +0300
+++ ./libevent-2.0.2-alpha-patched/test/regress_http.c 2009-10-15 18:03:14.000000000 +0300
@@ -2190,6 +2190,81 @@
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;
+
+ 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);
+
+ char long_str[8192];
+ 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 @@
HTTP_LEGACY(stream_in_cancel),
HTTP_LEGACY(connection_retry),
+ HTTP_LEGACY(data_length_constraints),
END_OF_TESTCASES
};