]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-mail: message-header-parser - Limit header block to 10MB by default
authorMarco Bettini <marco.bettini@open-xchange.com>
Fri, 12 Apr 2024 15:06:43 +0000 (15:06 +0000)
committeraki.tuomi <aki.tuomi@open-xchange.com>
Wed, 14 Aug 2024 12:36:23 +0000 (12:36 +0000)
src/lib-mail/message-header-parser.c
src/lib-mail/message-header-parser.h
src/lib-mail/test-message-header-parser.c

index c5026f1bb72d63edf07927c66f7ad58be2ae551c..5e020bbeb33d48e1778eccc88315ca60d13c4cf4 100644 (file)
@@ -21,6 +21,9 @@ struct message_header_parser_ctx {
        string_t *name;
        buffer_t *value_buf;
 
+       size_t header_block_max_size;
+       size_t header_block_total_size;
+
        enum message_header_parser_flags flags;
        bool skip_line:1;
        bool has_nuls:1;
@@ -38,6 +41,7 @@ message_parse_header_init(struct istream *input, struct message_size *hdr_size,
        ctx->name = str_new(default_pool, 128);
        ctx->flags = flags;
        ctx->value_buf = buffer_create_dynamic(default_pool, 4096);
+       ctx->header_block_max_size = MESSAGE_HEADER_BLOCK_DEFAULT_MAX_SIZE;
        i_stream_ref(input);
 
        if (hdr_size != NULL)
@@ -45,6 +49,21 @@ message_parse_header_init(struct istream *input, struct message_size *hdr_size,
        return ctx;
 }
 
+void
+message_parse_header_set_limit(struct message_header_parser_ctx *parser,
+                              size_t header_block_max_size)
+{
+       parser->header_block_max_size = header_block_max_size;
+}
+
+void
+message_parse_header_lower_limit(struct message_header_parser_ctx *parser,
+                                size_t header_block_max_size)
+{
+       if (header_block_max_size < parser->header_block_max_size)
+               message_parse_header_set_limit(parser, header_block_max_size);
+}
+
 void message_parse_header_deinit(struct message_header_parser_ctx **_ctx)
 {
        struct message_header_parser_ctx *ctx = *_ctx;
@@ -77,6 +96,7 @@ int message_parse_header_next(struct message_header_parser_ctx *ctx,
                /* new header line */
                line->name_offset = ctx->input->v_offset;
                colon_pos = UINT_MAX;
+               ctx->header_block_total_size += ctx->value_buf->used;
                buffer_set_used_size(ctx->value_buf, 0);
        }
 
@@ -342,33 +362,39 @@ int message_parse_header_next(struct message_header_parser_ctx *ctx,
                }
        }
 
+       line->value_len = I_MIN(line->value_len, ctx->header_block_max_size);
+       size_t line_value_size = line->value_len;
+       size_t header_total_used = ctx->header_block_total_size + ctx->value_buf->used;
+       size_t line_available = ctx->header_block_max_size <= header_total_used ? 0 :
+                               ctx->header_block_max_size - header_total_used;
+       line_value_size = I_MIN(line_value_size, line_available);
+
        if (!line->continued) {
                /* first header line. make a copy of the line since we can't
                   really trust input stream not to lose it. */
-               buffer_append(ctx->value_buf, line->value, line->value_len);
+               buffer_append(ctx->value_buf, line->value, line_value_size);
                line->value = line->full_value = ctx->value_buf->data;
-               line->full_value_len = line->value_len;
+               line->full_value_len = line->value_len = line_value_size;
        } else if (line->use_full_value) {
                /* continue saving the full value. */
                if (last_no_newline) {
                        /* line is longer than fit into our buffer, so we
                           were forced to break it into multiple
                           message_header_lines */
-               } else {
-                       if (last_crlf)
+               } else if (line_value_size > 1) {
+                       if (last_crlf && line_value_size > 2)
                                buffer_append_c(ctx->value_buf, '\r');
                        buffer_append_c(ctx->value_buf, '\n');
                }
                if ((ctx->flags & MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE) != 0 &&
                    line->value_len > 0 && line->value[0] != ' ' &&
-                   IS_LWSP(line->value[0])) {
+                   IS_LWSP(line->value[0]) &&
+                   line_value_size > 0) {
                        buffer_append_c(ctx->value_buf, ' ');
-                       buffer_append(ctx->value_buf,
-                                     line->value + 1, line->value_len - 1);
-               } else {
-                       buffer_append(ctx->value_buf,
-                                     line->value, line->value_len);
-               }
+                       buffer_append(ctx->value_buf, line->value + 1, line_value_size - 1);
+               } else
+                       buffer_append(ctx->value_buf, line->value, line_value_size);
+
                line->full_value = ctx->value_buf->data;
                line->full_value_len = ctx->value_buf->used;
        } else {
index ce0825c8e57a5267c0a6f6b0b999a7bc59134616..43cf95e56ae117d62925b934036ed9a8d1a12fb3 100644 (file)
@@ -1,6 +1,9 @@
 #ifndef MESSAGE_HEADER_PARSER_H
 #define MESSAGE_HEADER_PARSER_H
 
+/* This can be overridden by message_parse_header_set_limit() */
+#define MESSAGE_HEADER_BLOCK_DEFAULT_MAX_SIZE ((size_t) 10 * 1024*1024)
+
 #define IS_LWSP(c) \
        ((c) == ' ' || (c) == '\t')
 
@@ -48,6 +51,13 @@ message_parse_header_init(struct istream *input, struct message_size *hdr_size,
                          enum message_header_parser_flags flags) ATTR_NULL(2);
 void message_parse_header_deinit(struct message_header_parser_ctx **ctx);
 
+void
+message_parse_header_set_limit(struct message_header_parser_ctx *parser,
+                              size_t header_block_max_size);
+void
+message_parse_header_lower_limit(struct message_header_parser_ctx *parser,
+                                size_t header_block_max_size);
+
 /* Read and return next header line. Returns 1 if header is returned, 0 if
    input stream is non-blocking and more data needs to be read, -1 when all is
    done or error occurred (see stream's error status). */
index 700d3413f144876e5224b08b339eda3840fc160b..93d8842002aa14f4bbed667da6e1552d1788b6fb 100644 (file)
@@ -463,6 +463,71 @@ static void test_message_header_parser_extra_crlf_in_name(void)
        test_end();
 }
 
+#define assert_parsed_field(line, expected, actual, len) STMT_START {          \
+       test_assert_idx(memcmp(expected, actual, strlen(expected)) == 0, line); \
+       test_assert_cmp_idx(strlen(expected), ==, len, line);                   \
+} STMT_END
+
+/* NOTE: implicit variables: (parser, hdr) */
+#define assert_parse_line(line, exp_name, exp_value, exp_full) STMT_START {            \
+       test_assert_idx(message_parse_header_next(parser, &hdr) > 0, line);             \
+       assert_parsed_field(line, exp_name,   hdr->name,       hdr->name_len);          \
+       assert_parsed_field(line, exp_value,  hdr->value,      hdr->value_len);         \
+       assert_parsed_field(line, exp_full,   hdr->full_value, hdr->full_value_len);    \
+       if (hdr->continues) hdr->use_full_value = TRUE;                                 \
+} STMT_END
+
+static const unsigned char test_message_header_truncation_input[] =
+       /*01*/  "header1: this is short\n"
+       /*02*/  "header2: this is multiline\n"
+       /*03*/  " and long 343638404244464850525456586062\n"
+       /*04*/  " 64666870727476788082848688909294969800\n"
+       /*05*/  " 02040608101214161820222426283032343638\n"
+       /*06*/  "header3: I should not appear at all\n"
+       /*07*/  "\n";
+
+static void test_message_header_truncation_clean_oneline(void)
+{
+       test_begin("message header parser truncate + CLEAN_ONELINE flag");
+       struct message_header_line *hdr = NULL;
+       struct istream *input = test_istream_create_data(test_message_header_truncation_input, sizeof(test_message_header_truncation_input));
+       struct message_header_parser_ctx *parser = message_parse_header_init(input, NULL, MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE);
+       message_parse_header_set_limit(parser, 96);
+
+       assert_parse_line( 1, "header1", "this is short",                            "this is short");
+       assert_parse_line( 2, "header2", "this is multiline",                        "this is multiline");
+       assert_parse_line( 3, "header2", " and long 343638404244464850525456586062", "this is multiline and long 343638404244464850525456586062");
+       assert_parse_line( 4, "header2", " 64666870727476788082848688909294969800",  "this is multiline and long 343638404244464850525456586062 6466687072747678808284868");
+       assert_parse_line( 5, "header2", " 02040608101214161820222426283032343638",  "this is multiline and long 343638404244464850525456586062 6466687072747678808284868");
+       assert_parse_line( 6, "header3", "", "");
+       test_assert(message_parse_header_next(parser, &hdr) > 0 && hdr->eoh);
+
+       message_parse_header_deinit(&parser);
+       i_stream_unref(&input);
+       test_end();
+}
+
+static void test_message_header_truncation_flag0(void)
+{
+       test_begin("message header parser truncate + NO flags");
+       struct message_header_line *hdr = NULL;
+       struct istream *input = test_istream_create_data(test_message_header_truncation_input, sizeof(test_message_header_truncation_input));
+       struct message_header_parser_ctx *parser = message_parse_header_init(input, NULL, 0);
+       message_parse_header_set_limit(parser, 96);
+
+       assert_parse_line( 1, "header1", "this is short",                            "this is short");
+       assert_parse_line( 2, "header2", "this is multiline",                        "this is multiline");
+       assert_parse_line( 3, "header2", " and long 343638404244464850525456586062", "this is multiline\n and long 343638404244464850525456586062");
+       assert_parse_line( 4, "header2", " 64666870727476788082848688909294969800",  "this is multiline\n and long 343638404244464850525456586062\n 646668707274767880828486");
+       assert_parse_line( 5, "header2", " 02040608101214161820222426283032343638",  "this is multiline\n and long 343638404244464850525456586062\n 646668707274767880828486");
+       assert_parse_line( 6, "header3", "", "");
+       test_assert(message_parse_header_next(parser, &hdr) > 0 && hdr->eoh);
+
+       message_parse_header_deinit(&parser);
+       i_stream_unref(&input);
+       test_end();
+}
+
 int main(void)
 {
        static void (*const test_functions[])(void) = {
@@ -473,6 +538,8 @@ int main(void)
                test_message_header_parser_no_eoh,
                test_message_header_parser_nul,
                test_message_header_parser_extra_crlf_in_name,
+               test_message_header_truncation_flag0,
+               test_message_header_truncation_clean_oneline,
                NULL
        };
        return test_run(test_functions);