Mercurial > dovecot > original-hg > dovecot-2.2
changeset 16745:572b9a9031e7
lib-http: Implemented limits on overall HTTP header size, size of individual header fields and the number of fields in the header.
author | Stephan Bosch <stephan@rename-it.nl> |
---|---|
date | Sun, 15 Sep 2013 03:46:12 +0300 |
parents | dca140149d80 |
children | bbe4a469e276 |
files | src/lib-http/http-client-connection.c src/lib-http/http-client.c src/lib-http/http-client.h src/lib-http/http-header-parser.c src/lib-http/http-header-parser.h src/lib-http/http-header.h src/lib-http/http-message-parser.c src/lib-http/http-message-parser.h src/lib-http/http-request-parser.c src/lib-http/http-request-parser.h src/lib-http/http-response-parser.c src/lib-http/http-response-parser.h src/lib-http/http-transfer-chunked.c src/lib-http/test-http-header-parser.c src/lib-http/test-http-response-parser.c src/lib-http/test-http-server.c |
diffstat | 16 files changed, 207 insertions(+), 56 deletions(-) [+] |
line wrap: on
line diff
--- a/src/lib-http/http-client-connection.c Sun Sep 15 03:44:42 2013 +0300 +++ b/src/lib-http/http-client-connection.c Sun Sep 15 03:46:12 2013 +0300 @@ -721,7 +721,8 @@ } /* start protocol I/O */ - conn->http_parser = http_response_parser_init(conn->conn.input); + conn->http_parser = http_response_parser_init + (conn->conn.input, &conn->client->set.response_hdr_limits); o_stream_set_flush_callback(conn->conn.output, http_client_connection_output, conn); }
--- a/src/lib-http/http-client.c Sun Sep 15 03:44:42 2013 +0300 +++ b/src/lib-http/http-client.c Sun Sep 15 03:46:12 2013 +0300 @@ -96,6 +96,7 @@ (set->max_pipelined_requests > 0 ? set->max_pipelined_requests : 1); client->set.max_attempts = set->max_attempts; client->set.max_redirects = set->max_redirects; + client->set.response_hdr_limits = set->response_hdr_limits; client->set.request_timeout_msecs = set->request_timeout_msecs; client->set.connect_timeout_msecs = set->connect_timeout_msecs; client->set.soft_connect_timeout_msecs = set->soft_connect_timeout_msecs;
--- a/src/lib-http/http-client.h Sun Sep 15 03:44:42 2013 +0300 +++ b/src/lib-http/http-client.h Sun Sep 15 03:46:12 2013 +0300 @@ -59,6 +59,9 @@ /* maximum number of attempts for a request */ unsigned int max_attempts; + /* response header limits */ + struct http_header_limits response_hdr_limits; + /* max time to wait for HTTP request to finish before retrying (default = unlimited) */ unsigned int request_timeout_msecs;
--- a/src/lib-http/http-header-parser.c Sun Sep 15 03:44:42 2013 +0300 +++ b/src/lib-http/http-header-parser.c Sun Sep 15 03:46:12 2013 +0300 @@ -6,6 +6,7 @@ #include "str.h" #include "str-sanitize.h" #include "http-parser.h" +#include "http-header.h" #include "http-header-parser.h" @@ -25,6 +26,10 @@ struct http_header_parser { struct istream *input; + struct http_header_limits limits; + uoff_t size, field_size; + unsigned int field_count; + const unsigned char *begin, *cur, *end; const char *error; @@ -34,9 +39,9 @@ enum http_header_parse_state state; }; -// FIXME(Stephan): Add support for limiting maximum header size. - -struct http_header_parser *http_header_parser_init(struct istream *input) +struct http_header_parser * +http_header_parser_init(struct istream *input, + const struct http_header_limits *limits) { struct http_header_parser *parser; @@ -45,6 +50,16 @@ parser->name = str_new(default_pool, 128); parser->value_buf = buffer_create_dynamic(default_pool, 4096); + if (limits != NULL) + parser->limits = *limits; + + if (parser->limits.max_size == 0) + parser->limits.max_size = (uoff_t)-1; + if (parser->limits.max_field_size == 0) + parser->limits.max_field_size = (uoff_t)-1; + if (parser->limits.max_fields == 0) + parser->limits.max_fields = (unsigned int)-1; + return parser; } @@ -62,6 +77,9 @@ void http_header_parser_reset(struct http_header_parser *parser) { parser->state = HTTP_HEADER_PARSE_STATE_INIT; + parser->size = 0; + parser->field_size = 0; + parser->field_count = 0; } static int http_header_parse_name(struct http_header_parser *parser) @@ -144,7 +162,7 @@ if (http_char_is_token(*parser->cur)) { if ((ret=http_header_parse_name(parser)) <= 0) return ret; - } else if (str_len(parser->name) == 0) { + } else if (*parser->cur != ':' && str_len(parser->name) == 0) { parser->state = HTTP_HEADER_PARSE_STATE_LAST_LINE; break; } @@ -163,6 +181,10 @@ parser->error = "Empty header field name"; return -1; } + if (++parser->field_count > parser->limits.max_fields) { + parser->error = "Excessive number of header fields"; + return -1; + } parser->state = HTTP_HEADER_PARSE_STATE_OWS; /* fall through */ case HTTP_HEADER_PARSE_STATE_OWS: @@ -203,7 +225,7 @@ parser->state = HTTP_HEADER_PARSE_STATE_OWS; break; } - parser->state = HTTP_HEADER_PARSE_STATE_NAME; + parser->state = HTTP_HEADER_PARSE_STATE_INIT; return 1; case HTTP_HEADER_PARSE_STATE_LAST_LINE: if (*parser->cur == '\r') { @@ -247,12 +269,35 @@ const char **name_r, const unsigned char **data_r, size_t *size_r, const char **error_r) { + const uoff_t max_size = parser->limits.max_size; + const uoff_t max_field_size = parser->limits.max_field_size; const unsigned char *data; - size_t size; + uoff_t size; int ret; + *error_r = NULL; + while ((ret=i_stream_read_data (parser->input, &parser->begin, &size, 0)) > 0) { + + /* check header size limits */ + if (parser->size >= max_size) { + *error_r = "Excessive header size"; + return -1; + } + if (parser->field_size > max_field_size) { + *error_r = "Excessive header field size"; + return -1; + } + + /* don't parse beyond header size limits */ + if (size > (max_size - parser->size)) + size = max_size - parser->size; + if (size > (max_field_size - parser->field_size)) { + size = max_field_size - parser->field_size; + size = (size == 0 ? 1 : size); /* need to parse one more byte */ + } + parser->cur = parser->begin; parser->end = parser->cur + size; @@ -262,8 +307,12 @@ } i_stream_skip(parser->input, parser->cur - parser->begin); + parser->size += parser->cur - parser->begin; + parser->field_size += parser->cur - parser->begin; if (ret == 1) { + parser->field_size = 0; + if (parser->state != HTTP_HEADER_PARSE_STATE_EOH) { data = buffer_get_data(parser->value_buf, &size);
--- a/src/lib-http/http-header-parser.h Sun Sep 15 03:44:42 2013 +0300 +++ b/src/lib-http/http-header-parser.h Sun Sep 15 03:46:12 2013 +0300 @@ -1,9 +1,12 @@ #ifndef HTTP_HEADER_PARSER_H #define HTTP_HEADER_PARSER_H +struct http_header_limits; struct http_header_parser; -struct http_header_parser *http_header_parser_init(struct istream *input); +struct http_header_parser * +http_header_parser_init(struct istream *input, + const struct http_header_limits *limits); void http_header_parser_deinit(struct http_header_parser **_parser); void http_header_parser_reset(struct http_header_parser *parser);
--- a/src/lib-http/http-header.h Sun Sep 15 03:44:42 2013 +0300 +++ b/src/lib-http/http-header.h Sun Sep 15 03:46:12 2013 +0300 @@ -3,6 +3,12 @@ struct http_header; +struct http_header_limits { + uoff_t max_size; + uoff_t max_field_size; + unsigned int max_fields; +}; + struct http_header_field { const char *key; /* FIXME: rename to 'name' for v2.3 */ const char *value;
--- a/src/lib-http/http-message-parser.c Sun Sep 15 03:44:42 2013 +0300 +++ b/src/lib-http/http-message-parser.c Sun Sep 15 03:46:12 2013 +0300 @@ -13,10 +13,12 @@ #include <ctype.h> void http_message_parser_init(struct http_message_parser *parser, - struct istream *input) + struct istream *input, const struct http_header_limits *hdr_limits) { memset(parser, 0, sizeof(*parser)); parser->input = input; + if (hdr_limits != NULL) + parser->header_limits = *hdr_limits; } void http_message_parser_deinit(struct http_message_parser *parser) @@ -34,10 +36,12 @@ { i_assert(parser->payload == NULL); - if (parser->header_parser == NULL) - parser->header_parser = http_header_parser_init(parser->input); - else + if (parser->header_parser == NULL) { + parser->header_parser = + http_header_parser_init(parser->input, &parser->header_limits); + } else { http_header_parser_reset(parser->header_parser); + } if (parser->msg.pool != NULL) pool_unref(&parser->msg.pool);
--- a/src/lib-http/http-message-parser.h Sun Sep 15 03:44:42 2013 +0300 +++ b/src/lib-http/http-message-parser.h Sun Sep 15 03:46:12 2013 +0300 @@ -4,7 +4,7 @@ #include "http-response.h" #include "http-transfer.h" -struct http_header; +#include "http-header.h" struct http_message { pool_t pool; @@ -26,6 +26,7 @@ struct http_message_parser { struct istream *input; + struct http_header_limits header_limits; const unsigned char *cur, *end; @@ -37,7 +38,8 @@ }; void http_message_parser_init(struct http_message_parser *parser, - struct istream *input); + struct istream *input, const struct http_header_limits *hdr_limits) + ATTR_NULL(3); void http_message_parser_deinit(struct http_message_parser *parser); void http_message_parser_restart(struct http_message_parser *parser, pool_t pool);
--- a/src/lib-http/http-request-parser.c Sun Sep 15 03:44:42 2013 +0300 +++ b/src/lib-http/http-request-parser.c Sun Sep 15 03:46:12 2013 +0300 @@ -29,12 +29,14 @@ unsigned int skipping_line:1; }; -struct http_request_parser *http_request_parser_init(struct istream *input) +struct http_request_parser * +http_request_parser_init(struct istream *input, + const struct http_header_limits *hdr_limits) { struct http_request_parser *parser; parser = i_new(struct http_request_parser, 1); - http_message_parser_init(&parser->parser, input); + http_message_parser_init(&parser->parser, input, hdr_limits); return parser; }
--- a/src/lib-http/http-request-parser.h Sun Sep 15 03:44:42 2013 +0300 +++ b/src/lib-http/http-request-parser.h Sun Sep 15 03:46:12 2013 +0300 @@ -4,7 +4,8 @@ #include "http-request.h" struct http_request_parser * -http_request_parser_init(struct istream *input); +http_request_parser_init(struct istream *input, + const struct http_header_limits *hdr_limits) ATTR_NULL(2); void http_request_parser_deinit(struct http_request_parser **_parser); int http_request_parse_next(struct http_request_parser *parser,
--- a/src/lib-http/http-response-parser.c Sun Sep 15 03:44:42 2013 +0300 +++ b/src/lib-http/http-response-parser.c Sun Sep 15 03:46:12 2013 +0300 @@ -28,12 +28,15 @@ const char *response_reason; }; -struct http_response_parser *http_response_parser_init(struct istream *input) +struct http_response_parser * +http_response_parser_init(struct istream *input, + const struct http_header_limits *hdr_limits) { struct http_response_parser *parser; + /* FIXME: implement status line limit */ parser = i_new(struct http_response_parser, 1); - http_message_parser_init(&parser->parser, input); + http_message_parser_init(&parser->parser, input, hdr_limits); return parser; }
--- a/src/lib-http/http-response-parser.h Sun Sep 15 03:44:42 2013 +0300 +++ b/src/lib-http/http-response-parser.h Sun Sep 15 03:46:12 2013 +0300 @@ -3,10 +3,12 @@ #include "http-response.h" +struct http_header_limits; struct http_response_parser; struct http_response_parser * -http_response_parser_init(struct istream *input); +http_response_parser_init(struct istream *input, + const struct http_header_limits *hdr_limits) ATTR_NULL(2); void http_response_parser_deinit(struct http_response_parser **_parser); int http_response_parse_next(struct http_response_parser *parser,
--- a/src/lib-http/http-transfer-chunked.c Sun Sep 15 03:44:42 2013 +0300 +++ b/src/lib-http/http-transfer-chunked.c Sun Sep 15 03:46:12 2013 +0300 @@ -425,7 +425,9 @@ int ret; if (tcstream->header_parser == NULL) { - tcstream->header_parser = http_header_parser_init(tcstream->istream.parent); + /* FIXME: limit trailer size */ + tcstream->header_parser = + http_header_parser_init(tcstream->istream.parent, 0); } while ((ret=http_header_parse_next_field(tcstream->header_parser,
--- a/src/lib-http/test-http-header-parser.c Sun Sep 15 03:44:42 2013 +0300 +++ b/src/lib-http/test-http-header-parser.c Sun Sep 15 03:46:12 2013 +0300 @@ -3,6 +3,7 @@ #include "test-lib.h" #include "istream.h" #include "test-common.h" +#include "http-response.h" #include "http-header-parser.h" #include <time.h> @@ -14,6 +15,7 @@ struct http_header_parse_test { const char *header; + struct http_header_limits limits; const struct http_header_parse_result *fields; }; @@ -138,7 +140,12 @@ "Content-Type: text/html; charset=utf-8\r\n" "Content-Encoding: gzip\r\n" "\r\n", - .fields = valid_header_parse_result4 + .fields = valid_header_parse_result4, + .limits = { + .max_size = 340, + .max_field_size = 46, + .max_fields = 12 + } },{ .header = "\r\n", @@ -155,7 +162,8 @@ for (i = 0; i < valid_header_parse_test_count; i++) T_BEGIN { struct istream *input; struct http_header_parser *parser; - const char *header, *field_name, *error; + const struct http_header_limits *limits; + const char *header, *field_name, *error = NULL; const unsigned char *field_data; size_t field_size; int ret; @@ -163,8 +171,9 @@ header = valid_header_parse_tests[i].header; header_len = strlen(header); + limits = &valid_header_parse_tests[i].limits; input = test_istream_create_data(header, header_len); - parser = http_header_parser_init(input); + parser = http_header_parser_init(input, limits); test_begin(t_strdup_printf("http header valid [%d]", i)); @@ -199,35 +208,96 @@ j++; } - test_out("parse success", ret > 0); + test_out_reason("parse success", ret > 0, error); test_end(); http_header_parser_deinit(&parser); } T_END; } -static const char *invalid_header_parse_tests[] = { - "Date: Sat, 06 Oct 2012 16:01:44 GMT\r\n" - "Server : Apache/2.2.16 (Debian)\r\n" - "Last-Modified: Mon, 30 Jul 2012 11:09:28 GMT\r\n" - "\r\n", - "Date: Sat, 06 Oct 2012 17:18:22 GMT\r\n" - "Server: Apache/2.2.3 (CentOS)\r\n" - "X Powered By: PHP/5.3.6\r\n" - "\r\n", - "Host: www.example.com\n\r" - "Accept: image/png,image/*;q=0.8,*/*;q=0.5\n\r" - "Accept-Language: en-us,en;q=0.5\n\r" - "Accept-Encoding: gzip, deflate\n\r" - "\n\r", - "Host: p5-lrqzb4yavu4l7nagydw-428649-i2-v6exp3-ds.metric.example.com\n" - "User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64; rv:15.0)\n" - "Accept:\t\timage/png,image/*;q=0.8,*/\177;q=0.5\n" - "\n", - "Date: Sat, 06 Oct 2012 17:12:37 GMT\r\n" - "Server: Apache/2.2.16 (Debian) PHP/5.3.3-7+squeeze14 with\r\n" - "Suhosin-Patch proxy_html/3.0.1 mod_python/3.3.1 Python/2.6.6\r\n" - "mod_ssl/2.2.16 OpenSSL/0.9.8o mod_perl/2.0.4 Perl/v5.10.1\r\n" - "\r\n", +static const struct http_header_parse_test invalid_header_parse_tests[] = { + { + .header = + "Date: Sat, 06 Oct 2012 16:01:44 GMT\r\n" + "Server : Apache/2.2.16 (Debian)\r\n" + "Last-Modified: Mon, 30 Jul 2012 11:09:28 GMT\r\n" + "\r\n" + },{ + .header = + "Date: Sat, 06 Oct 2012 17:18:22 GMT\r\n" + "Server: Apache/2.2.3 (CentOS)\r\n" + "X Powered By: PHP/5.3.6\r\n" + "\r\n" + },{ + .header = + "Host: www.example.com\n\r" + "Accept: image/png,image/*;q=0.8,*/*;q=0.5\n\r" + "Accept-Language: en-us,en;q=0.5\n\r" + "Accept-Encoding: gzip, deflate\n\r" + "\n\r" + },{ + .header = + "Host: p5-lrqzb4yavu4l7nagydw-428649-i2-v6exp3-ds.metric.example.com\n" + "User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64; rv:15.0)\n" + "Accept:\t\timage/png,image/*;q=0.8,*/\177;q=0.5\n" + "\n" + },{ + .header = + "Date: Sat, 06 Oct 2012 17:12:37 GMT\r\n" + "Server: Apache/2.2.16 (Debian) PHP/5.3.3-7+squeeze14 with\r\n" + "Suhosin-Patch proxy_html/3.0.1 mod_python/3.3.1 Python/2.6.6\r\n" + "mod_ssl/2.2.16 OpenSSL/0.9.8o mod_perl/2.0.4 Perl/v5.10.1\r\n" + "\r\n" + },{ + .header = + "Age: 58 \r\n" + "Date: Sun, 04 Aug 2013 09:33:09 GMT\r\n" + "Expires: Sun, 04 Aug 2013 09:34:08 GMT\r\n" + "Cache-Control: max-age=60 \r\n" + "Content-Length: 17336 \r\n" + "Connection: Keep-Alive\r\n" + "Via: NS-CACHE-9.3\r\n" + "Server: Apache\r\n" + "Vary: Host\r\n" + "Last-Modified: Sun, 04 Aug 2013 09:33:07 GMT\r\n" + "Content-Type: text/html; charset=utf-8\r\n" + "Content-Encoding: gzip\r\n" + "\r\n", + .limits = { .max_size = 339 } + },{ + .header = + "Age: 58 \r\n" + "Date: Sun, 04 Aug 2013 09:33:09 GMT\r\n" + "Expires: Sun, 04 Aug 2013 09:34:08 GMT\r\n" + "Cache-Control: max-age=60 \r\n" + "Content-Length: 17336 \r\n" + "Connection: Keep-Alive\r\n" + "Via: NS-CACHE-9.3\r\n" + "Server: Apache\r\n" + "Vary: Host\r\n" + "Last-Modified: Sun, 04 Aug 2013 09:33:07 GMT\r\n" + "Content-Type: text/html; charset=utf-8\r\n" + "Content-Encoding: gzip\r\n" + "\r\n", + .fields = valid_header_parse_result4, + .limits = { .max_field_size = 45 } + },{ + .header = + "Age: 58 \r\n" + "Date: Sun, 04 Aug 2013 09:33:09 GMT\r\n" + "Expires: Sun, 04 Aug 2013 09:34:08 GMT\r\n" + "Cache-Control: max-age=60 \r\n" + "Content-Length: 17336 \r\n" + "Connection: Keep-Alive\r\n" + "Via: NS-CACHE-9.3\r\n" + "Server: Apache\r\n" + "Vary: Host\r\n" + "Last-Modified: Sun, 04 Aug 2013 09:33:07 GMT\r\n" + "Content-Type: text/html; charset=utf-8\r\n" + "Content-Encoding: gzip\r\n" + "\r\n", + .fields = valid_header_parse_result4, + .limits = { .max_fields = 11 } + } }; unsigned int invalid_header_parse_test_count = N_ELEMENTS(invalid_header_parse_tests); @@ -239,14 +309,16 @@ for (i = 0; i < invalid_header_parse_test_count; i++) T_BEGIN { struct istream *input; struct http_header_parser *parser; - const char *header, *field_name, *error; + const struct http_header_limits *limits; + const char *header, *field_name, *error = NULL; const unsigned char *field_data; size_t field_size; int ret; - header = invalid_header_parse_tests[i]; + header = invalid_header_parse_tests[i].header; + limits = &invalid_header_parse_tests[i].limits; input = i_stream_create_from_data(header, strlen(header)); - parser = http_header_parser_init(input); + parser = http_header_parser_init(input, limits); test_begin(t_strdup_printf("http header invalid [%d]", i)); @@ -255,7 +327,7 @@ if (field_name == NULL) break; } - test_out("parse failure", ret < 0); + test_out_reason("parse failure", ret < 0, error); test_end(); http_header_parser_deinit(&parser); } T_END;
--- a/src/lib-http/test-http-response-parser.c Sun Sep 15 03:44:42 2013 +0300 +++ b/src/lib-http/test-http-response-parser.c Sun Sep 15 03:46:12 2013 +0300 @@ -108,7 +108,7 @@ response_text = test->response; response_text_len = strlen(response_text); input = test_istream_create_data(response_text, response_text_len); - parser = http_response_parser_init(input); + parser = http_response_parser_init(input, NULL); test_begin(t_strdup_printf("http response valid [%d]", i)); @@ -196,7 +196,7 @@ test = invalid_response_parse_tests[i]; response_text = test; input = i_stream_create_from_data(response_text, strlen(response_text)); - parser = http_response_parser_init(input); + parser = http_response_parser_init(input, NULL); test_begin(t_strdup_printf("http response invalid [%d]", i)); @@ -212,7 +212,7 @@ test_begin("http response with NULs"); input = i_stream_create_from_data(invalid_response_with_nuls, sizeof(invalid_response_with_nuls)-1); - parser = http_response_parser_init(input); + parser = http_response_parser_init(input, 0); while ((ret=http_response_parse_next(parser, FALSE, &response, &error)) > 0); test_assert(ret < 0); test_end();
--- a/src/lib-http/test-http-server.c Sun Sep 15 03:44:42 2013 +0300 +++ b/src/lib-http/test-http-server.c Sun Sep 15 03:46:12 2013 +0300 @@ -84,7 +84,7 @@ client = i_new(struct client, 1); connection_init_server(clients, &client->conn, "(http client)", fd, fd); - client->parser = http_request_parser_init(client->conn.input); + client->parser = http_request_parser_init(client->conn.input, 0); } static void client_accept(void *context ATTR_UNUSED)