]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-mail: message-parser - Limit headers total count to 50MB by default
authorMarco Bettini <marco.bettini@open-xchange.com>
Wed, 24 Apr 2024 10:45:46 +0000 (10:45 +0000)
committeraki.tuomi <aki.tuomi@open-xchange.com>
Wed, 14 Aug 2024 12:36:23 +0000 (12:36 +0000)
(including top headers and all mime-sections headers)

src/lib-mail/message-parser-private.h
src/lib-mail/message-parser.c
src/lib-mail/message-parser.h
src/lib-mail/test-message-parser.c

index 41c32daf3a4c85bf7bfd7afcf06924e8bde08136..8b362a9e7138e66d66e55bf1ff47c33a9378ae57 100644 (file)
@@ -30,6 +30,8 @@ struct message_parser_ctx {
        enum message_parser_flags flags;
        unsigned int max_nested_mime_parts;
        unsigned int max_total_mime_parts;
+       size_t all_headers_max_size;
+       size_t all_headers_total_size;
 
        char *last_boundary;
        struct message_boundary *boundaries;
index 9a9c9a3515f70a30c075f70cc2472f3133a175c0..c7e3b1e96a22e3f7f894dac2e868c637924304eb 100644 (file)
@@ -617,7 +617,18 @@ static int parse_next_header(struct message_parser_ctx *ctx,
        }
        if (ret < 0) {
                /* no boundary */
+               size_t headers_available =
+                       ctx->all_headers_max_size > ctx->all_headers_total_size ?
+                       ctx->all_headers_max_size - ctx->all_headers_total_size : 0;
+               message_parse_header_lower_limit(ctx->hdr_parser_ctx, headers_available);
                ret = message_parse_header_next(ctx->hdr_parser_ctx, &hdr);
+               if (ret > 0) {
+                       if (!hdr->continues) {
+                               ctx->all_headers_total_size += hdr->name_len;
+                               ctx->all_headers_total_size += hdr->middle_len;
+                       }
+                       ctx->all_headers_total_size += hdr->value_len;
+               }
                if (ret == 0 || (ret < 0 && ctx->input->stream_errno != 0)) {
                        ctx->want_count = i_stream_get_data_size(ctx->input) + 1;
                        return ret;
@@ -762,6 +773,9 @@ message_parser_init_int(struct istream *input,
        ctx->max_total_mime_parts = set->max_total_mime_parts != 0 ?
                set->max_total_mime_parts :
                MESSAGE_PARSER_DEFAULT_MAX_TOTAL_MIME_PARTS;
+       ctx->all_headers_max_size = set->all_headers_max_size != 0 ?
+               set->all_headers_max_size :
+               MESSAGE_PARSER_DEFAULT_ALL_HEADERS_MAX_SIZE;
        ctx->input = input;
        i_stream_ref(input);
        return ctx;
@@ -779,6 +793,7 @@ message_parser_init(pool_t part_pool, struct istream *input,
        ctx->next_part = &ctx->part->children;
        ctx->parse_next_block = parse_next_header_init;
        ctx->total_parts_count = 1;
+       ctx->all_headers_total_size = 0;
        i_array_init(&ctx->next_part_stack, 4);
        return ctx;
 }
index f19e526284da193f9f5ab1db6ab4c18e9739c434..8d70d73f053a2188c5c7390c3d571837e6f068ca 100644 (file)
@@ -19,6 +19,7 @@ enum message_parser_flags {
 
 #define MESSAGE_PARSER_DEFAULT_MAX_NESTED_MIME_PARTS 100
 #define MESSAGE_PARSER_DEFAULT_MAX_TOTAL_MIME_PARTS 10000
+#define MESSAGE_PARSER_DEFAULT_ALL_HEADERS_MAX_SIZE ((size_t) 50 * 1024*1024)
 
 struct message_parser_settings {
        enum message_header_parser_flags hdr_flags;
@@ -30,6 +31,11 @@ struct message_parser_settings {
        /* Maximum MIME parts in total.
           0 = MESSAGE_PARSER_DEFAULT_MAX_TOTAL_MIME_PARTS. */
        unsigned int max_total_mime_parts;
+
+       /* Maximum bytes fore headers in top header plus all
+          MIME sections headers
+          0 = MESSAGE_PARSER_DEFAULT_ALL_HEADERS_MAX_SIZE */
+       size_t all_headers_max_size;
 };
 
 struct message_parser_ctx;
index 663bfe8c5a49cc2dd178e560ac8fe45e8aa93759..b6bada230311f48313ff4fdc3b2139bd54aae12a 100644 (file)
@@ -1369,6 +1369,158 @@ static const char input_msg[] =
        test_end();
 }
 
+#define test_assert_virtual_size(part) \
+       test_assert((part).virtual_size == (part).lines + (part).physical_size)
+
+#define test_assert_part(part, flags_, children, h_lines, h_size, b_lines, b_size ) \
+STMT_START {                                                           \
+       test_assert((part)->flags == (flags_));                         \
+       test_assert((part)->children_count == children);                \
+       test_assert((part)->header_size.lines == h_lines);              \
+       test_assert((part)->header_size.physical_size == h_size);       \
+       test_assert((part)->body_size.lines == b_lines);                \
+       test_assert((part)->body_size.physical_size == b_size);         \
+       test_assert_virtual_size((part)->header_size);                  \
+       test_assert_virtual_size((part)->body_size);                    \
+} STMT_END
+
+static const enum message_part_flags FLAGS_MULTIPART =
+       MESSAGE_PART_FLAG_IS_MIME | MESSAGE_PART_FLAG_MULTIPART;
+static const enum message_part_flags FLAGS_RFC822 =
+       MESSAGE_PART_FLAG_IS_MIME | MESSAGE_PART_FLAG_MESSAGE_RFC822;
+static const enum message_part_flags FLAGS_TEXT =
+       MESSAGE_PART_FLAG_IS_MIME | MESSAGE_PART_FLAG_TEXT;
+
+static const char too_many_header_bytes_input_msg[] =
+       "Content-Type: multipart/mixed; boundary=\"1\"\n\n"
+               "--1\n"
+               "Content-Type: multipart/mixed; boundary=\"2\"\n\n"
+                       "--2\n"
+                       "Content-Type: message/rfc822\n\n"
+                               "Content-Type: text/plain\n\n1-rfc822\n"
+                       "--2\n"
+                       "Content-Type: message/rfc822\n\n"
+                               "Content-Type: text/plain\n\n2-rfc822\n"
+               "--1\n"
+                       "Content-Type: message/rfc822\n\n"
+                               "Content-Type: text/plain\n\n3-rfc822\n";
+
+static void test_message_parser_too_many_header_bytes_run(
+       const struct message_parser_settings *parser_set,
+       pool_t *pool_r, struct istream **input_r,
+       struct message_part **parts_r)
+{
+       *pool_r = pool_alloconly_create("message parser", 10240);
+       *input_r = test_istream_create(too_many_header_bytes_input_msg);
+       struct message_parser_ctx *parser = message_parser_init(*pool_r, *input_r, parser_set);
+
+       int ret;
+       struct message_block block ATTR_UNUSED;
+       while ((ret = message_parser_parse_next_block(parser, &block)) > 0);
+       test_assert(ret < 0);
+
+       message_parser_deinit(&parser, parts_r);
+}
+
+static void test_message_parser_too_many_header_bytes_default(void)
+{
+       test_begin("message parser too many header bytes default");
+
+       pool_t pool;
+       struct istream *input;
+       struct message_part *part_root;
+       const struct message_parser_settings parser_set = { .all_headers_max_size = 0 };
+
+       test_message_parser_too_many_header_bytes_run(&parser_set, &pool, &input, &part_root);
+
+       // test_assert_part(part, flags_, children, h_lines, h_size, b_lines, b_size )
+
+       test_assert_part(part_root, FLAGS_MULTIPART, 7, 2, 45, 21, 256);
+       test_assert(part_root->parent == NULL);
+
+               struct message_part *part_1 = part_root->children;
+               test_assert_part(part_1, FLAGS_MULTIPART, 4, 2, 45, 11, 137);
+
+                       struct message_part *part_1_1 = part_1->children;
+                       test_assert_part(part_1_1, FLAGS_RFC822, 1, 2, 30, 2, 34);
+
+                               struct message_part *part_1_1_1 = part_1_1->children;
+                               test_assert_part(part_1_1_1, FLAGS_TEXT, 0, 2, 26, 0, 8);
+
+                               test_assert(part_1_1_1->next == NULL);
+
+                       struct message_part *part_1_2 = part_1_1->next;
+                       test_assert_part(part_1_2, FLAGS_RFC822, 1, 2, 30, 2, 34);
+
+                               struct message_part *part_1_2_1 = part_1_2->children;
+                               test_assert_part(part_1_2_1, FLAGS_TEXT, 0, 2, 26, 0, 8);
+
+                               test_assert(part_1_2_1->next == NULL);
+
+                       test_assert(part_1_2->next == NULL);
+
+               struct message_part *part_2 = part_1->next;
+               test_assert_part(part_2, FLAGS_RFC822, 1, 2, 30, 3, 35);
+
+                       struct message_part *part_2_1 = part_2->children;
+                       test_assert_part(part_2_1, FLAGS_TEXT, 0, 2, 26, 1, 9);
+                       test_assert(part_2_1->next == NULL);
+
+               test_assert(part_2->next == NULL);
+
+       test_assert(part_root->next == NULL);
+
+       test_parsed_parts(input, part_root);
+       i_stream_unref(&input);
+       pool_unref(&pool);
+       test_end();
+}
+
+static void test_message_parser_too_many_header_bytes_100(void)
+{
+       test_begin("message parser too many header bytes 100");
+
+       pool_t pool;
+       struct istream *input;
+       struct message_part *part_root;
+       const struct message_parser_settings parser_set = { .all_headers_max_size = 100 };
+
+       test_message_parser_too_many_header_bytes_run(&parser_set, &pool, &input, &part_root);
+
+       // test_assert_part(part, flags_, children, h_lines, h_size, b_lines, b_size )
+
+       test_assert_part(part_root, FLAGS_MULTIPART, 5, 2, 45, 21, 256);
+       test_assert(part_root->parent == NULL);
+
+               struct message_part *part_1 = part_root->children;
+               test_assert_part(part_1, FLAGS_MULTIPART, 3, 2, 45, 11, 137);
+
+                       struct message_part *part_1_1 = part_1->children;
+                       test_assert_part(part_1_1, FLAGS_RFC822, 1, 2, 30, 2, 34);
+
+                               struct message_part *part_1_1_1 = part_1_1->children;
+                               test_assert_part(part_1_1_1, MESSAGE_PART_FLAG_IS_MIME, 0, 2, 26, 0, 8);
+
+                               test_assert(part_1_1_1->next == NULL);
+
+                       struct message_part *part_1_2 = part_1_1->next;
+                       test_assert_part(part_1_2, MESSAGE_PART_FLAG_IS_MIME, 0, 2, 30, 2, 34);
+
+                       test_assert(part_1_2->next == NULL);
+
+               struct message_part *part_2 = part_1->next;
+               test_assert_part(part_2, MESSAGE_PART_FLAG_IS_MIME, 0, 2, 30, 3, 35);
+
+               test_assert(part_2->next == NULL);
+
+       test_assert(part_root->next == NULL);
+
+       test_parsed_parts(input, part_root);
+       i_stream_unref(&input);
+       pool_unref(&pool);
+       test_end();
+}
+
 int main(void)
 {
        static void (*const test_functions[])(void) = {
@@ -1392,6 +1544,8 @@ int main(void)
                test_message_parser_mime_part_limit_rfc822,
                test_message_parser_mime_version,
                test_message_parser_mime_version_missing,
+               test_message_parser_too_many_header_bytes_default,
+               test_message_parser_too_many_header_bytes_100,
                NULL
        };
        return test_run(test_functions);