diff -up dovecot-2.2.36/src/doveadm/doveadm-mail-fetch.c.CVE_2020_12100 dovecot-2.2.36/src/doveadm/doveadm-mail-fetch.c --- dovecot-2.2.36/src/doveadm/doveadm-mail-fetch.c.CVE_2020_12100 2018-04-30 15:52:05.000000000 +0200 +++ dovecot-2.2.36/src/doveadm/doveadm-mail-fetch.c 2020-08-08 13:57:49.295662113 +0200 @@ -265,6 +265,9 @@ static int fetch_text(struct fetch_cmd_c static int fetch_text_utf8(struct fetch_cmd_context *ctx) { + const struct message_parser_settings parser_set = { + .hdr_flags = MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE, + }; struct istream *input; struct message_parser_ctx *parser; struct message_decoder_context *decoder; @@ -275,9 +278,7 @@ static int fetch_text_utf8(struct fetch_ if (mail_get_stream(ctx->mail, NULL, NULL, &input) < 0) return -1; - parser = message_parser_init(pool_datastack_create(), input, - MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE, - 0); + parser = message_parser_init(pool_datastack_create(), input, &parser_set); decoder = message_decoder_init(NULL, 0); while ((ret = message_parser_parse_next_block(parser, &raw_block)) > 0) { diff -up dovecot-2.2.36/src/lib-imap/test-imap-bodystructure.c.CVE_2020_12100 dovecot-2.2.36/src/lib-imap/test-imap-bodystructure.c --- dovecot-2.2.36/src/lib-imap/test-imap-bodystructure.c.CVE_2020_12100 2018-04-30 15:52:05.000000000 +0200 +++ dovecot-2.2.36/src/lib-imap/test-imap-bodystructure.c 2020-08-08 13:57:49.295662113 +0200 @@ -381,6 +381,11 @@ static const unsigned int normalize_test static struct message_part * msg_parse(pool_t pool, const char *message, bool parse_bodystructure) { + const struct message_parser_settings parser_set = { + .hdr_flags = MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP | + MESSAGE_HEADER_PARSER_FLAG_DROP_CR, + .flags = MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK, + }; struct message_parser_ctx *parser; struct istream *input; struct message_block block; @@ -388,10 +393,7 @@ msg_parse(pool_t pool, const char *messa int ret; input = i_stream_create_from_data(message, strlen(message)); - parser = message_parser_init(pool, input, - MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP | - MESSAGE_HEADER_PARSER_FLAG_DROP_CR, - MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK); + parser = message_parser_init(pool, input, &parser_set); while ((ret = message_parser_parse_next_block(parser, &block)) > 0) { if (parse_bodystructure) { message_part_data_parse_from_header(pool, block.part, diff -up dovecot-2.2.36/src/lib-imap/test-imap-envelope.c.CVE_2020_12100 dovecot-2.2.36/src/lib-imap/test-imap-envelope.c --- dovecot-2.2.36/src/lib-imap/test-imap-envelope.c.CVE_2020_12100 2018-04-30 15:52:05.000000000 +0200 +++ dovecot-2.2.36/src/lib-imap/test-imap-envelope.c 2020-08-08 13:57:49.295662113 +0200 @@ -118,6 +118,11 @@ static const unsigned int parse_tests_co static struct message_part_envelope * msg_parse(pool_t pool, const char *message) { + const struct message_parser_settings parser_set = { + .hdr_flags = MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP | + MESSAGE_HEADER_PARSER_FLAG_DROP_CR, + .flags = MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK, + }; struct message_parser_ctx *parser; struct message_part_envelope *envlp = NULL; struct istream *input; @@ -126,10 +131,7 @@ msg_parse(pool_t pool, const char *messa int ret; input = i_stream_create_from_data(message, strlen(message)); - parser = message_parser_init(pool, input, - MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP | - MESSAGE_HEADER_PARSER_FLAG_DROP_CR, - MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK); + parser = message_parser_init(pool, input, &parser_set); while ((ret = message_parser_parse_next_block(parser, &block)) > 0) { i_assert(block.part->parent == NULL); message_part_envelope_parse_from_header(pool, &envlp, block.hdr); diff -up dovecot-2.2.36/src/lib-mail/istream-attachment-extractor.c.CVE_2020_12100 dovecot-2.2.36/src/lib-mail/istream-attachment-extractor.c --- dovecot-2.2.36/src/lib-mail/istream-attachment-extractor.c.CVE_2020_12100 2018-04-30 15:52:05.000000000 +0200 +++ dovecot-2.2.36/src/lib-mail/istream-attachment-extractor.c 2020-08-08 13:59:10.665535546 +0200 @@ -691,6 +691,10 @@ i_stream_create_attachment_extractor(str struct istream_attachment_settings *set, void *context) { + const struct message_parser_settings parser_set = { + .flags = MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS | + MESSAGE_PARSER_FLAG_INCLUDE_BOUNDARIES, + }; struct attachment_istream *astream; i_assert(set->min_size > 0); @@ -717,9 +721,7 @@ i_stream_create_attachment_extractor(str astream->istream.istream.seekable = FALSE; astream->pool = pool_alloconly_create("istream attachment", 1024); - astream->parser = message_parser_init(astream->pool, input, 0, - MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS | - MESSAGE_PARSER_FLAG_INCLUDE_BOUNDARIES); + astream->parser = message_parser_init(astream->pool, input, &parser_set); return i_stream_create(&astream->istream, input, i_stream_get_fd(input)); } diff -up dovecot-2.2.36/src/lib-mail/istream-binary-converter.c.CVE_2020_12100 dovecot-2.2.36/src/lib-mail/istream-binary-converter.c --- dovecot-2.2.36/src/lib-mail/istream-binary-converter.c.CVE_2020_12100 2018-04-30 15:52:05.000000000 +0200 +++ dovecot-2.2.36/src/lib-mail/istream-binary-converter.c 2020-08-08 14:00:06.560761661 +0200 @@ -286,6 +286,10 @@ static void i_stream_binary_converter_cl struct istream *i_stream_create_binary_converter(struct istream *input) { + const struct message_parser_settings parser_set = { + .flags = MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS | + MESSAGE_PARSER_FLAG_INCLUDE_BOUNDARIES, + }; struct binary_converter_istream *bstream; bstream = i_new(struct binary_converter_istream, 1); @@ -299,9 +303,7 @@ struct istream *i_stream_create_binary_c bstream->istream.istream.seekable = FALSE; bstream->pool = pool_alloconly_create("istream binary converter", 128); - bstream->parser = message_parser_init(bstream->pool, input, 0, - MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS | - MESSAGE_PARSER_FLAG_INCLUDE_BOUNDARIES); + bstream->parser = message_parser_init(bstream->pool, input, &parser_set); return i_stream_create(&bstream->istream, input, i_stream_get_fd(input)); } diff -up dovecot-2.2.36/src/lib-mail/Makefile.am.CVE_2020_12100 dovecot-2.2.36/src/lib-mail/Makefile.am --- dovecot-2.2.36/src/lib-mail/Makefile.am.CVE_2020_12100 2018-04-30 15:52:05.000000000 +0200 +++ dovecot-2.2.36/src/lib-mail/Makefile.am 2020-08-08 13:57:49.296662100 +0200 @@ -27,6 +27,7 @@ libmail_la_SOURCES = \ message-header-parser.c \ message-id.c \ message-parser.c \ + message-parser-from-parts.c \ message-part.c \ message-part-data.c \ message-part-serialize.c \ @@ -41,7 +42,8 @@ libmail_la_SOURCES = \ rfc822-parser.c noinst_HEADERS = \ - html-entities.h + html-entities.h \ + message-parser-private.h headers = \ istream-attachment-connector.h \ diff -up dovecot-2.2.36/src/lib-mail/message-parser.c.CVE_2020_12100 dovecot-2.2.36/src/lib-mail/message-parser.c --- dovecot-2.2.36/src/lib-mail/message-parser.c.CVE_2020_12100 2018-04-30 15:52:05.000000000 +0200 +++ dovecot-2.2.36/src/lib-mail/message-parser.c 2020-08-08 14:06:41.696290855 +0200 @@ -1,53 +1,12 @@ /* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ #include "lib.h" -#include "buffer.h" +#include "array.h" #include "str.h" #include "istream.h" #include "rfc822-parser.h" #include "rfc2231-parser.h" -#include "message-parser.h" - -/* RFC-2046 requires boundaries are max. 70 chars + "--" prefix + "--" suffix. - We'll add a bit more just in case. */ -#define BOUNDARY_END_MAX_LEN (70 + 2 + 2 + 10) - -struct message_boundary { - struct message_boundary *next; - - struct message_part *part; - const char *boundary; - size_t len; - - unsigned int epilogue_found:1; -}; - -struct message_parser_ctx { - pool_t parser_pool, part_pool; - struct istream *input; - struct message_part *parts, *part; - const char *broken_reason; - - enum message_header_parser_flags hdr_flags; - enum message_parser_flags flags; - - const char *last_boundary; - struct message_boundary *boundaries; - - size_t skip; - char last_chr; - unsigned int want_count; - - struct message_header_parser_ctx *hdr_parser_ctx; - unsigned int prev_hdr_newline_size; - - int (*parse_next_block)(struct message_parser_ctx *ctx, - struct message_block *block_r); - - unsigned int part_seen_content_type:1; - unsigned int multipart:1; - unsigned int eof:1; -}; +#include "message-parser-private.h" message_part_header_callback_t *null_message_part_header_callback = NULL; @@ -57,14 +16,10 @@ static int parse_next_body_to_boundary(s struct message_block *block_r); static int parse_next_body_to_eof(struct message_parser_ctx *ctx, struct message_block *block_r); -static int preparsed_parse_epilogue_init(struct message_parser_ctx *ctx, - struct message_block *block_r); -static int preparsed_parse_next_header_init(struct message_parser_ctx *ctx, - struct message_block *block_r); static struct message_boundary * boundary_find(struct message_boundary *boundaries, - const unsigned char *data, size_t len) + const unsigned char *data, size_t len, bool trailing_dashes) { struct message_boundary *best = NULL; @@ -76,8 +31,18 @@ boundary_find(struct message_boundary *b while (boundaries != NULL) { if (boundaries->len <= len && memcmp(boundaries->boundary, data, boundaries->len) == 0 && - (best == NULL || best->len < boundaries->len)) + (best == NULL || best->len < boundaries->len)) { best = boundaries; + /* If we see "foo--", it could either mean that there + is a boundary named "foo" that ends now or there's + a boundary "foo--" which continues. */ + if (best->len == len || + (best->len == len-2 && trailing_dashes)) { + /* This is exactly the wanted boundary. There + can't be a better one. */ + break; + } + } boundaries = boundaries->next; } @@ -121,8 +86,8 @@ static void parse_body_add_block(struct ctx->part->body_size.virtual_size += block->size + missing_cr_count; } -static int message_parser_read_more(struct message_parser_ctx *ctx, - struct message_block *block_r, bool *full_r) +int message_parser_read_more(struct message_parser_ctx *ctx, + struct message_block *block_r, bool *full_r) { int ret; @@ -167,19 +132,18 @@ static int message_parser_read_more(stru return 1; } -static struct message_part * -message_part_append(pool_t pool, struct message_part *parent) +static void +message_part_append(struct message_parser_ctx *ctx) { - struct message_part *p, *part, **list; + struct message_part *parent = ctx->part; + struct message_part *part; i_assert(parent != NULL); i_assert((parent->flags & (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_MESSAGE_RFC822)) != 0); - part = p_new(pool, struct message_part, 1); + part = p_new(ctx->part_pool, struct message_part, 1); part->parent = parent; - for (p = parent; p != NULL; p = p->parent) - p->children_count++; /* set child position */ part->physical_pos = @@ -187,33 +151,78 @@ message_part_append(pool_t pool, struct parent->body_size.physical_size + parent->header_size.physical_size; - list = &part->parent->children; - while (*list != NULL) - list = &(*list)->next; + /* add to parent's linked list */ + *ctx->next_part = part; + /* update the parent's end-of-linked-list pointer */ + struct message_part **next_part = &part->next; + array_push_back(&ctx->next_part_stack, &next_part); + /* This part is now the new parent for the next message_part_append() + call. Its linked list begins with the children pointer. */ + ctx->next_part = &part->children; + + ctx->part = part; + ctx->nested_parts_count++; + ctx->total_parts_count++; + i_assert(ctx->nested_parts_count < ctx->max_nested_mime_parts); + i_assert(ctx->total_parts_count <= ctx->max_total_mime_parts); +} + +static void message_part_finish(struct message_parser_ctx *ctx) +{ + struct message_part **const *parent_next_partp; + + i_assert(ctx->nested_parts_count > 0); + ctx->nested_parts_count--; + + parent_next_partp = array_back(&ctx->next_part_stack); + array_pop_back(&ctx->next_part_stack); + ctx->next_part = *parent_next_partp; + + message_size_add(&ctx->part->parent->body_size, &ctx->part->body_size); + message_size_add(&ctx->part->parent->body_size, &ctx->part->header_size); + ctx->part->parent->children_count += 1 + ctx->part->children_count; + ctx->part = ctx->part->parent; +} + +static void message_boundary_free(struct message_boundary *b) +{ + i_free(b->boundary); + i_free(b); +} + +static void +boundary_remove_until(struct message_parser_ctx *ctx, + struct message_boundary *boundary) +{ + while (ctx->boundaries != boundary) { + struct message_boundary *cur = ctx->boundaries; - *list = part; - return part; + i_assert(cur != NULL); + ctx->boundaries = cur->next; + message_boundary_free(cur); + + } + ctx->boundaries = boundary; } static void parse_next_body_multipart_init(struct message_parser_ctx *ctx) { struct message_boundary *b; - b = p_new(ctx->parser_pool, struct message_boundary, 1); + b = i_new(struct message_boundary, 1); b->part = ctx->part; b->boundary = ctx->last_boundary; + ctx->last_boundary = NULL; b->len = strlen(b->boundary); b->next = ctx->boundaries; ctx->boundaries = b; - - ctx->last_boundary = NULL; } static int parse_next_body_message_rfc822_init(struct message_parser_ctx *ctx, struct message_block *block_r) { - ctx->part = message_part_append(ctx->part_pool, ctx->part); + message_part_append(ctx); return parse_next_header_init(ctx, block_r); } @@ -238,19 +247,38 @@ boundary_line_find(struct message_parser return -1; } + if (ctx->total_parts_count >= ctx->max_total_mime_parts) { + /* can't add any more MIME parts. just stop trying to find + more boundaries. */ + return -1; + } + /* need to find the end of line */ - if (memchr(data + 2, '\n', size - 2) == NULL && - size < BOUNDARY_END_MAX_LEN && + data += 2; + size -= 2; + const unsigned char *lf_pos = memchr(data, '\n', size); + if (lf_pos == NULL && + size+2 < BOUNDARY_END_MAX_LEN && !ctx->input->eof && !full) { /* no LF found */ ctx->want_count = BOUNDARY_END_MAX_LEN; return 0; } + size_t find_size = size; + bool trailing_dashes = FALSE; - data += 2; - size -= 2; + if (lf_pos != NULL) { + find_size = lf_pos - data; + if (find_size > 0 && data[find_size-1] == '\r') + find_size--; + if (find_size > 2 && data[find_size-1] == '-' && + data[find_size-2] == '-') + trailing_dashes = TRUE; + } else if (find_size > BOUNDARY_END_MAX_LEN) + find_size = BOUNDARY_END_MAX_LEN; - *boundary_r = boundary_find(ctx->boundaries, data, size); + *boundary_r = boundary_find(ctx->boundaries, data, find_size, + trailing_dashes); if (*boundary_r == NULL) return -1; @@ -263,7 +291,7 @@ boundary_line_find(struct message_parser static int parse_next_mime_header_init(struct message_parser_ctx *ctx, struct message_block *block_r) { - ctx->part = message_part_append(ctx->part_pool, ctx->part); + message_part_append(ctx); ctx->part->flags |= MESSAGE_PART_FLAG_IS_MIME; return parse_next_header_init(ctx, block_r); @@ -312,26 +340,25 @@ static int parse_part_finish(struct mess struct message_boundary *boundary, struct message_block *block_r, bool first_line) { - struct message_part *part; size_t line_size; + size_t boundary_len = boundary->len; + bool boundary_epilogue_found = boundary->epilogue_found; i_assert(ctx->last_boundary == NULL); /* get back to parent MIME part, summing the child MIME part sizes into parent's body sizes */ - for (part = ctx->part; part != boundary->part; part = part->parent) { - message_size_add(&part->parent->body_size, &part->body_size); - message_size_add(&part->parent->body_size, &part->header_size); + while (ctx->part != boundary->part) { + message_part_finish(ctx); + i_assert(ctx->part != NULL); } - i_assert(part != NULL); - ctx->part = part; if (boundary->epilogue_found) { /* this boundary isn't needed anymore */ - ctx->boundaries = boundary->next; + boundary_remove_until(ctx, boundary->next); } else { /* forget about the boundaries we possibly skipped */ - ctx->boundaries = boundary; + boundary_remove_until(ctx, boundary); } /* the boundary itself should already be in buffer. add that. */ @@ -348,7 +375,7 @@ static int parse_part_finish(struct mess i_assert(block_r->data[0] == '\n'); line_size = 1; } - line_size += 2 + boundary->len + (boundary->epilogue_found ? 2 : 0); + line_size += 2 + boundary_len + (boundary_epilogue_found ? 2 : 0); i_assert(block_r->size >= ctx->skip + line_size); block_r->size = line_size; parse_body_add_block(ctx, block_r); @@ -509,8 +536,10 @@ static void parse_content_type(struct me rfc2231_parse(&parser, &results); for (; *results != NULL; results += 2) { if (strcasecmp(results[0], "boundary") == 0) { + /* truncate excessively long boundaries */ + i_free(ctx->last_boundary); ctx->last_boundary = - p_strdup(ctx->parser_pool, results[1]); + i_strndup(results[1], BOUNDARY_STRING_MAX_LEN); break; } } @@ -532,6 +561,11 @@ static bool block_is_at_eoh(const struct return FALSE; } +static bool parse_too_many_nested_mime_parts(struct message_parser_ctx *ctx) +{ + return ctx->nested_parts_count+1 >= ctx->max_nested_mime_parts; +} + #define MUTEX_FLAGS \ (MESSAGE_PART_FLAG_MESSAGE_RFC822 | MESSAGE_PART_FLAG_MULTIPART) @@ -556,8 +590,12 @@ static int parse_next_header(struct mess "\n--boundary" belongs to us or to a previous boundary. this is a problem if the boundary prefixes are identical, because MIME requires only the prefix to match. */ - parse_next_body_multipart_init(ctx); - ctx->multipart = TRUE; + if (!parse_too_many_nested_mime_parts(ctx)) { + parse_next_body_multipart_init(ctx); + ctx->multipart = TRUE; + } else { + part->flags &= ~MESSAGE_PART_FLAG_MULTIPART; + } } /* before parsing the header see if we can find a --boundary from here. @@ -633,7 +671,7 @@ static int parse_next_header(struct mess i_assert(!ctx->multipart); part->flags = 0; } - ctx->last_boundary = NULL; + i_free(ctx->last_boundary); if (!ctx->part_seen_content_type || (part->flags & MESSAGE_PART_FLAG_IS_MIME) == 0) { @@ -661,12 +699,16 @@ static int parse_next_header(struct mess i_assert(ctx->last_boundary == NULL); ctx->multipart = FALSE; ctx->parse_next_block = parse_next_body_to_boundary; - } else if (part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) + } else if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0 && + !parse_too_many_nested_mime_parts(ctx)) { ctx->parse_next_block = parse_next_body_message_rfc822_init; - else if (ctx->boundaries != NULL) - ctx->parse_next_block = parse_next_body_to_boundary; - else - ctx->parse_next_block = parse_next_body_to_eof; + } else { + part->flags &= ~MESSAGE_PART_FLAG_MESSAGE_RFC822; + if (ctx->boundaries != NULL) + ctx->parse_next_block = parse_next_body_to_boundary; + else + ctx->parse_next_block = parse_next_body_to_eof; + } ctx->want_count = 1; @@ -691,358 +733,21 @@ static int parse_next_header_init(struct return parse_next_header(ctx, block_r); } -static int preparsed_parse_eof(struct message_parser_ctx *ctx ATTR_UNUSED, - struct message_block *block_r ATTR_UNUSED) -{ - return -1; -} - -static void preparsed_skip_to_next(struct message_parser_ctx *ctx) -{ - ctx->parse_next_block = preparsed_parse_next_header_init; - while (ctx->part != NULL) { - if (ctx->part->next != NULL) { - ctx->part = ctx->part->next; - break; - } - - /* parse epilogue of multipart parent if requested */ - if (ctx->part->parent != NULL && - (ctx->part->parent->flags & MESSAGE_PART_FLAG_MULTIPART) != 0 && - (ctx->flags & MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS) != 0) { - /* check for presence of epilogue */ - uoff_t part_end = ctx->part->physical_pos + - ctx->part->header_size.physical_size + - ctx->part->body_size.physical_size; - uoff_t parent_end = ctx->part->parent->physical_pos + - ctx->part->parent->header_size.physical_size + - ctx->part->parent->body_size.physical_size; - - if (parent_end > part_end) { - ctx->parse_next_block = preparsed_parse_epilogue_init; - break; - } - } - ctx->part = ctx->part->parent; - } - if (ctx->part == NULL) - ctx->parse_next_block = preparsed_parse_eof; -} - -static int preparsed_parse_body_finish(struct message_parser_ctx *ctx, - struct message_block *block_r) -{ - i_stream_skip(ctx->input, ctx->skip); - ctx->skip = 0; - - preparsed_skip_to_next(ctx); - return ctx->parse_next_block(ctx, block_r); -} - -static int preparsed_parse_prologue_finish(struct message_parser_ctx *ctx, - struct message_block *block_r) -{ - i_stream_skip(ctx->input, ctx->skip); - ctx->skip = 0; - - ctx->parse_next_block = preparsed_parse_next_header_init; - ctx->part = ctx->part->children; - return ctx->parse_next_block(ctx, block_r); -} - -static int preparsed_parse_body_more(struct message_parser_ctx *ctx, - struct message_block *block_r) -{ - uoff_t end_offset = ctx->part->physical_pos + - ctx->part->header_size.physical_size + - ctx->part->body_size.physical_size; - bool full; - int ret; - - if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0) - return ret; - - if (ctx->input->v_offset + block_r->size >= end_offset) { - block_r->size = end_offset - ctx->input->v_offset; - ctx->parse_next_block = preparsed_parse_body_finish; - } - ctx->skip = block_r->size; - return 1; -} - -static int preparsed_parse_prologue_more(struct message_parser_ctx *ctx, - struct message_block *block_r) -{ - uoff_t boundary_min_start, end_offset; - const unsigned char *cur; - bool full; - int ret; - - i_assert(ctx->part->children != NULL); - end_offset = ctx->part->children->physical_pos; - - if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0) - return ret; - - if (ctx->input->v_offset + block_r->size >= end_offset) { - /* we've got the full prologue: clip off the initial boundary */ - block_r->size = end_offset - ctx->input->v_offset; - cur = block_r->data + block_r->size - 1; - - /* [\r]\n--boundary[\r]\n */ - if (block_r->size < 5 || *cur != '\n') { - ctx->broken_reason = "Prologue boundary end not at expected position"; - return -1; - } - - cur--; - if (*cur == '\r') cur--; - - /* find newline just before boundary */ - for (; cur >= block_r->data; cur--) { - if (*cur == '\n') break; - } - - if (cur[0] != '\n' || cur[1] != '-' || cur[2] != '-') { - ctx->broken_reason = "Prologue boundary beginning not at expected position"; - return -1; - } - - if (cur != block_r->data && cur[-1] == '\r') cur--; - - /* clip boundary */ - block_r->size = cur - block_r->data; - - ctx->parse_next_block = preparsed_parse_prologue_finish; - ctx->skip = block_r->size; - return 1; - } - - /* retain enough data in the stream buffer to contain initial boundary */ - if (end_offset > BOUNDARY_END_MAX_LEN) - boundary_min_start = end_offset - BOUNDARY_END_MAX_LEN; - else - boundary_min_start = 0; - - if (ctx->input->v_offset + block_r->size >= boundary_min_start) { - if (boundary_min_start <= ctx->input->v_offset) - return 0; - block_r->size = boundary_min_start - ctx->input->v_offset; - } - ctx->skip = block_r->size; - return 1; -} - -static int preparsed_parse_epilogue_more(struct message_parser_ctx *ctx, - struct message_block *block_r) -{ - uoff_t end_offset = ctx->part->physical_pos + - ctx->part->header_size.physical_size + - ctx->part->body_size.physical_size; - bool full; - int ret; - - if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0) - return ret; - - if (ctx->input->v_offset + block_r->size >= end_offset) { - block_r->size = end_offset - ctx->input->v_offset; - ctx->parse_next_block = preparsed_parse_body_finish; - } - ctx->skip = block_r->size; - return 1; -} - -static int preparsed_parse_epilogue_boundary(struct message_parser_ctx *ctx, - struct message_block *block_r) -{ - uoff_t end_offset = ctx->part->physical_pos + - ctx->part->header_size.physical_size + - ctx->part->body_size.physical_size; - const unsigned char *data, *cur; - size_t size; - bool full; - int ret; - - if (end_offset - ctx->input->v_offset < 7) { - ctx->broken_reason = "Epilogue position is wrong"; - return -1; - } - - if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0) - return ret; - - /* [\r]\n--boundary--[\r]\n */ - if (block_r->size < 7) { - ctx->want_count = 7; - return 0; - } - - data = block_r->data; - size = block_r->size; - cur = data; - - if (*cur == '\r') cur++; - - if (cur[0] != '\n' || cur[1] != '-' || data[2] != '-') { - ctx->broken_reason = "Epilogue boundary start not at expected position"; - return -1; - } - - /* find the end of the line */ - cur += 3; - if ((cur = memchr(cur, '\n', size - (cur-data))) == NULL) { - if (end_offset < ctx->input->v_offset + size) { - ctx->broken_reason = "Epilogue boundary end not at expected position"; - return -1; - } else if (ctx->input->v_offset + size < end_offset && - size < BOUNDARY_END_MAX_LEN && - !ctx->input->eof && !full) { - ctx->want_count = BOUNDARY_END_MAX_LEN; - return 0; - } - } - - block_r->size = 0; - ctx->parse_next_block = preparsed_parse_epilogue_more; - ctx->skip = cur - data + 1; - return 0; -} - -static int preparsed_parse_body_init(struct message_parser_ctx *ctx, - struct message_block *block_r) -{ - uoff_t offset = ctx->part->physical_pos + - ctx->part->header_size.physical_size; - - if (offset < ctx->input->v_offset) { - /* header was actually larger than the cached size suggested */ - ctx->broken_reason = "Header larger than its cached size"; - return -1; - } - i_stream_skip(ctx->input, offset - ctx->input->v_offset); - - /* multipart messages may begin with --boundary--, which makes them - not have any children. */ - if ((ctx->part->flags & MESSAGE_PART_FLAG_MULTIPART) == 0 || - ctx->part->children == NULL) - ctx->parse_next_block = preparsed_parse_body_more; - else - ctx->parse_next_block = preparsed_parse_prologue_more; - return ctx->parse_next_block(ctx, block_r); -} - -static int preparsed_parse_epilogue_init(struct message_parser_ctx *ctx, - struct message_block *block_r) -{ - uoff_t offset = ctx->part->physical_pos + - ctx->part->header_size.physical_size + - ctx->part->body_size.physical_size; - - ctx->part = ctx->part->parent; - - if (offset < ctx->input->v_offset) { - /* last child was actually larger than the cached size - suggested */ - ctx->broken_reason = "Part larger than its cached size"; - return -1; - } - i_stream_skip(ctx->input, offset - ctx->input->v_offset); - - ctx->parse_next_block = preparsed_parse_epilogue_boundary; - return ctx->parse_next_block(ctx, block_r); -} - -static int preparsed_parse_finish_header(struct message_parser_ctx *ctx, - struct message_block *block_r) -{ - if (ctx->part->children != NULL) { - if ((ctx->part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0 && - (ctx->flags & MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS) != 0) - ctx->parse_next_block = preparsed_parse_body_init; - else { - ctx->parse_next_block = preparsed_parse_next_header_init; - ctx->part = ctx->part->children; - } - } else if ((ctx->flags & MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK) == 0) { - ctx->parse_next_block = preparsed_parse_body_init; - } else { - preparsed_skip_to_next(ctx); - } - return ctx->parse_next_block(ctx, block_r); -} - -static int preparsed_parse_next_header(struct message_parser_ctx *ctx, - struct message_block *block_r) -{ - struct message_header_line *hdr; - int ret; - - ret = message_parse_header_next(ctx->hdr_parser_ctx, &hdr); - if (ret == 0 || (ret < 0 && ctx->input->stream_errno != 0)) { - ctx->want_count = i_stream_get_data_size(ctx->input) + 1; - return ret; - } - - if (hdr != NULL) { - block_r->hdr = hdr; - block_r->size = 0; - return 1; - } - message_parse_header_deinit(&ctx->hdr_parser_ctx); - - ctx->parse_next_block = preparsed_parse_finish_header; - - /* return empty block as end of headers */ - block_r->hdr = NULL; - block_r->size = 0; - - i_assert(ctx->skip == 0); - if (ctx->input->v_offset != ctx->part->physical_pos + - ctx->part->header_size.physical_size) { - ctx->broken_reason = "Cached header size mismatch"; - return -1; - } - return 1; -} - -static int preparsed_parse_next_header_init(struct message_parser_ctx *ctx, - struct message_block *block_r) -{ - struct istream *hdr_input; - - i_assert(ctx->hdr_parser_ctx == NULL); - - i_assert(ctx->part->physical_pos >= ctx->input->v_offset); - i_stream_skip(ctx->input, ctx->part->physical_pos - - ctx->input->v_offset); - - /* the header may become truncated by --boundaries. limit the header - stream's size to what it's supposed to be to avoid duplicating (and - keeping in sync!) all the same complicated logic as in - parse_next_header(). */ - hdr_input = i_stream_create_limit(ctx->input, ctx->part->header_size.physical_size); - ctx->hdr_parser_ctx = - message_parse_header_init(hdr_input, NULL, ctx->hdr_flags); - i_stream_unref(&hdr_input); - - ctx->parse_next_block = preparsed_parse_next_header; - return preparsed_parse_next_header(ctx, block_r); -} - -static struct message_parser_ctx * +struct message_parser_ctx * message_parser_init_int(struct istream *input, - enum message_header_parser_flags hdr_flags, - enum message_parser_flags flags) + const struct message_parser_settings *set) { struct message_parser_ctx *ctx; - pool_t pool; - pool = pool_alloconly_create("Message Parser", 1024); - ctx = p_new(pool, struct message_parser_ctx, 1); - ctx->parser_pool = pool; - ctx->hdr_flags = hdr_flags; - ctx->flags = flags; + ctx = i_new(struct message_parser_ctx, 1); + ctx->hdr_flags = set->hdr_flags; + ctx->flags = set->flags; + ctx->max_nested_mime_parts = set->max_nested_mime_parts != 0 ? + set->max_nested_mime_parts : + MESSAGE_PARSER_DEFAULT_MAX_NESTED_MIME_PARTS; + ctx->max_total_mime_parts = set->max_total_mime_parts != 0 ? + set->max_total_mime_parts : + MESSAGE_PARSER_DEFAULT_MAX_TOTAL_MIME_PARTS; ctx->input = input; i_stream_ref(input); return ctx; @@ -1050,31 +755,17 @@ message_parser_init_int(struct istream * struct message_parser_ctx * message_parser_init(pool_t part_pool, struct istream *input, - enum message_header_parser_flags hdr_flags, - enum message_parser_flags flags) + const struct message_parser_settings *set) { struct message_parser_ctx *ctx; - ctx = message_parser_init_int(input, hdr_flags, flags); + ctx = message_parser_init_int(input, set); ctx->part_pool = part_pool; ctx->parts = ctx->part = p_new(part_pool, struct message_part, 1); + ctx->next_part = &ctx->part->children; ctx->parse_next_block = parse_next_header_init; - return ctx; -} - -struct message_parser_ctx * -message_parser_init_from_parts(struct message_part *parts, - struct istream *input, - enum message_header_parser_flags hdr_flags, - enum message_parser_flags flags) -{ - struct message_parser_ctx *ctx; - - i_assert(parts != NULL); - - ctx = message_parser_init_int(input, hdr_flags, flags); - ctx->parts = ctx->part = parts; - ctx->parse_next_block = preparsed_parse_next_header_init; + ctx->total_parts_count = 1; + i_array_init(&ctx->next_part_stack, 4); return ctx; } @@ -1099,8 +790,15 @@ int message_parser_deinit_from_parts(str if (ctx->hdr_parser_ctx != NULL) message_parse_header_deinit(&ctx->hdr_parser_ctx); + boundary_remove_until(ctx, NULL); + /* caller might have stopped the parsing early */ + i_assert(ctx->nested_parts_count == 0 || + i_stream_have_bytes_left(ctx->input)); + i_stream_unref(&ctx->input); - pool_unref(&ctx->parser_pool); + array_free(&ctx->next_part_stack); + i_free(ctx->last_boundary); + i_free(ctx); i_assert(ret < 0 || *parts_r != NULL); return ret; } @@ -1132,13 +830,8 @@ int message_parser_parse_next_block(stru i_assert(ctx->input->eof || ctx->input->closed || ctx->input->stream_errno != 0 || ctx->broken_reason != NULL); - while (ctx->part->parent != NULL) { - message_size_add(&ctx->part->parent->body_size, - &ctx->part->body_size); - message_size_add(&ctx->part->parent->body_size, - &ctx->part->header_size); - ctx->part = ctx->part->parent; - } + while (ctx->part->parent != NULL) + message_part_finish(ctx); } if (block_r->size == 0) { diff -up dovecot-2.2.36/src/lib-mail/message-parser-from-parts.c.CVE_2020_12100 dovecot-2.2.36/src/lib-mail/message-parser-from-parts.c --- dovecot-2.2.36/src/lib-mail/message-parser-from-parts.c.CVE_2020_12100 2020-08-08 13:57:49.296662100 +0200 +++ dovecot-2.2.36/src/lib-mail/message-parser-from-parts.c 2020-08-08 13:57:49.296662100 +0200 @@ -0,0 +1,365 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "istream.h" +#include "message-parser-private.h" + +static int preparsed_parse_epilogue_init(struct message_parser_ctx *ctx, + struct message_block *block_r); +static int preparsed_parse_next_header_init(struct message_parser_ctx *ctx, + struct message_block *block_r); + +static int preparsed_parse_eof(struct message_parser_ctx *ctx ATTR_UNUSED, + struct message_block *block_r ATTR_UNUSED) +{ + return -1; +} + +static void preparsed_skip_to_next(struct message_parser_ctx *ctx) +{ + ctx->parse_next_block = preparsed_parse_next_header_init; + while (ctx->part != NULL) { + if (ctx->part->next != NULL) { + ctx->part = ctx->part->next; + break; + } + + /* parse epilogue of multipart parent if requested */ + if (ctx->part->parent != NULL && + (ctx->part->parent->flags & MESSAGE_PART_FLAG_MULTIPART) != 0 && + (ctx->flags & MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS) != 0) { + /* check for presence of epilogue */ + uoff_t part_end = ctx->part->physical_pos + + ctx->part->header_size.physical_size + + ctx->part->body_size.physical_size; + uoff_t parent_end = ctx->part->parent->physical_pos + + ctx->part->parent->header_size.physical_size + + ctx->part->parent->body_size.physical_size; + + if (parent_end > part_end) { + ctx->parse_next_block = preparsed_parse_epilogue_init; + break; + } + } + ctx->part = ctx->part->parent; + } + if (ctx->part == NULL) + ctx->parse_next_block = preparsed_parse_eof; +} + +static int preparsed_parse_body_finish(struct message_parser_ctx *ctx, + struct message_block *block_r) +{ + i_stream_skip(ctx->input, ctx->skip); + ctx->skip = 0; + + preparsed_skip_to_next(ctx); + return ctx->parse_next_block(ctx, block_r); +} + +static int preparsed_parse_prologue_finish(struct message_parser_ctx *ctx, + struct message_block *block_r) +{ + i_stream_skip(ctx->input, ctx->skip); + ctx->skip = 0; + + ctx->parse_next_block = preparsed_parse_next_header_init; + ctx->part = ctx->part->children; + return ctx->parse_next_block(ctx, block_r); +} + +static int preparsed_parse_body_more(struct message_parser_ctx *ctx, + struct message_block *block_r) +{ + uoff_t end_offset = ctx->part->physical_pos + + ctx->part->header_size.physical_size + + ctx->part->body_size.physical_size; + bool full; + int ret; + + if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0) + return ret; + + if (ctx->input->v_offset + block_r->size >= end_offset) { + block_r->size = end_offset - ctx->input->v_offset; + ctx->parse_next_block = preparsed_parse_body_finish; + } + ctx->skip = block_r->size; + return 1; +} + +static int preparsed_parse_prologue_more(struct message_parser_ctx *ctx, + struct message_block *block_r) +{ + uoff_t boundary_min_start, end_offset; + const unsigned char *cur; + bool full; + int ret; + + i_assert(ctx->part->children != NULL); + end_offset = ctx->part->children->physical_pos; + + if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0) + return ret; + + if (ctx->input->v_offset + block_r->size >= end_offset) { + /* we've got the full prologue: clip off the initial boundary */ + block_r->size = end_offset - ctx->input->v_offset; + cur = block_r->data + block_r->size - 1; + + /* [\r]\n--boundary[\r]\n */ + if (block_r->size < 5 || *cur != '\n') { + ctx->broken_reason = "Prologue boundary end not at expected position"; + return -1; + } + + cur--; + if (*cur == '\r') cur--; + + /* find newline just before boundary */ + for (; cur >= block_r->data; cur--) { + if (*cur == '\n') break; + } + + if (cur[0] != '\n' || cur[1] != '-' || cur[2] != '-') { + ctx->broken_reason = "Prologue boundary beginning not at expected position"; + return -1; + } + + if (cur != block_r->data && cur[-1] == '\r') cur--; + + /* clip boundary */ + block_r->size = cur - block_r->data; + + ctx->parse_next_block = preparsed_parse_prologue_finish; + ctx->skip = block_r->size; + return 1; + } + + /* retain enough data in the stream buffer to contain initial boundary */ + if (end_offset > BOUNDARY_END_MAX_LEN) + boundary_min_start = end_offset - BOUNDARY_END_MAX_LEN; + else + boundary_min_start = 0; + + if (ctx->input->v_offset + block_r->size >= boundary_min_start) { + if (boundary_min_start <= ctx->input->v_offset) + return 0; + block_r->size = boundary_min_start - ctx->input->v_offset; + } + ctx->skip = block_r->size; + return 1; +} + +static int preparsed_parse_epilogue_more(struct message_parser_ctx *ctx, + struct message_block *block_r) +{ + uoff_t end_offset = ctx->part->physical_pos + + ctx->part->header_size.physical_size + + ctx->part->body_size.physical_size; + bool full; + int ret; + + if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0) + return ret; + + if (ctx->input->v_offset + block_r->size >= end_offset) { + block_r->size = end_offset - ctx->input->v_offset; + ctx->parse_next_block = preparsed_parse_body_finish; + } + ctx->skip = block_r->size; + return 1; +} + +static int preparsed_parse_epilogue_boundary(struct message_parser_ctx *ctx, + struct message_block *block_r) +{ + uoff_t end_offset = ctx->part->physical_pos + + ctx->part->header_size.physical_size + + ctx->part->body_size.physical_size; + const unsigned char *data, *cur; + size_t size; + bool full; + int ret; + + if (end_offset - ctx->input->v_offset < 7) { + ctx->broken_reason = "Epilogue position is wrong"; + return -1; + } + + if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0) + return ret; + + /* [\r]\n--boundary--[\r]\n */ + if (block_r->size < 7) { + ctx->want_count = 7; + return 0; + } + + data = block_r->data; + size = block_r->size; + cur = data; + + if (*cur == '\r') cur++; + + if (cur[0] != '\n' || cur[1] != '-' || data[2] != '-') { + ctx->broken_reason = "Epilogue boundary start not at expected position"; + return -1; + } + + /* find the end of the line */ + cur += 3; + if ((cur = memchr(cur, '\n', size - (cur-data))) == NULL) { + if (end_offset < ctx->input->v_offset + size) { + ctx->broken_reason = "Epilogue boundary end not at expected position"; + return -1; + } else if (ctx->input->v_offset + size < end_offset && + size < BOUNDARY_END_MAX_LEN && + !ctx->input->eof && !full) { + ctx->want_count = BOUNDARY_END_MAX_LEN; + return 0; + } + } + + block_r->size = 0; + ctx->parse_next_block = preparsed_parse_epilogue_more; + ctx->skip = cur - data + 1; + return 0; +} + +static int preparsed_parse_body_init(struct message_parser_ctx *ctx, + struct message_block *block_r) +{ + uoff_t offset = ctx->part->physical_pos + + ctx->part->header_size.physical_size; + + if (offset < ctx->input->v_offset) { + /* header was actually larger than the cached size suggested */ + ctx->broken_reason = "Header larger than its cached size"; + return -1; + } + i_stream_skip(ctx->input, offset - ctx->input->v_offset); + + /* multipart messages may begin with --boundary--, which makes them + not have any children. */ + if ((ctx->part->flags & MESSAGE_PART_FLAG_MULTIPART) == 0 || + ctx->part->children == NULL) + ctx->parse_next_block = preparsed_parse_body_more; + else + ctx->parse_next_block = preparsed_parse_prologue_more; + return ctx->parse_next_block(ctx, block_r); +} + +static int preparsed_parse_epilogue_init(struct message_parser_ctx *ctx, + struct message_block *block_r) +{ + uoff_t offset = ctx->part->physical_pos + + ctx->part->header_size.physical_size + + ctx->part->body_size.physical_size; + + ctx->part = ctx->part->parent; + + if (offset < ctx->input->v_offset) { + /* last child was actually larger than the cached size + suggested */ + ctx->broken_reason = "Part larger than its cached size"; + return -1; + } + i_stream_skip(ctx->input, offset - ctx->input->v_offset); + + ctx->parse_next_block = preparsed_parse_epilogue_boundary; + return ctx->parse_next_block(ctx, block_r); +} + +static int preparsed_parse_finish_header(struct message_parser_ctx *ctx, + struct message_block *block_r) +{ + if (ctx->part->children != NULL) { + if ((ctx->part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0 && + (ctx->flags & MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS) != 0) + ctx->parse_next_block = preparsed_parse_body_init; + else { + ctx->parse_next_block = preparsed_parse_next_header_init; + ctx->part = ctx->part->children; + } + } else if ((ctx->flags & MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK) == 0) { + ctx->parse_next_block = preparsed_parse_body_init; + } else { + preparsed_skip_to_next(ctx); + } + return ctx->parse_next_block(ctx, block_r); +} + +static int preparsed_parse_next_header(struct message_parser_ctx *ctx, + struct message_block *block_r) +{ + struct message_header_line *hdr; + int ret; + + ret = message_parse_header_next(ctx->hdr_parser_ctx, &hdr); + if (ret == 0 || (ret < 0 && ctx->input->stream_errno != 0)) { + ctx->want_count = i_stream_get_data_size(ctx->input) + 1; + return ret; + } + + if (hdr != NULL) { + block_r->hdr = hdr; + block_r->size = 0; + return 1; + } + message_parse_header_deinit(&ctx->hdr_parser_ctx); + + ctx->parse_next_block = preparsed_parse_finish_header; + + /* return empty block as end of headers */ + block_r->hdr = NULL; + block_r->size = 0; + + i_assert(ctx->skip == 0); + if (ctx->input->v_offset != ctx->part->physical_pos + + ctx->part->header_size.physical_size) { + ctx->broken_reason = "Cached header size mismatch"; + return -1; + } + return 1; +} + +static int preparsed_parse_next_header_init(struct message_parser_ctx *ctx, + struct message_block *block_r) +{ + struct istream *hdr_input; + + i_assert(ctx->hdr_parser_ctx == NULL); + + i_assert(ctx->part->physical_pos >= ctx->input->v_offset); + i_stream_skip(ctx->input, ctx->part->physical_pos - + ctx->input->v_offset); + + /* the header may become truncated by --boundaries. limit the header + stream's size to what it's supposed to be to avoid duplicating (and + keeping in sync!) all the same complicated logic as in + parse_next_header(). */ + hdr_input = i_stream_create_limit(ctx->input, ctx->part->header_size.physical_size); + ctx->hdr_parser_ctx = + message_parse_header_init(hdr_input, NULL, ctx->hdr_flags); + i_stream_unref(&hdr_input); + + ctx->parse_next_block = preparsed_parse_next_header; + return preparsed_parse_next_header(ctx, block_r); +} + +struct message_parser_ctx * +message_parser_init_from_parts(struct message_part *parts, + struct istream *input, + const struct message_parser_settings *set) +{ + struct message_parser_ctx *ctx; + + i_assert(parts != NULL); + + ctx = message_parser_init_int(input, set); + ctx->preparsed = TRUE; + ctx->parts = ctx->part = parts; + ctx->parse_next_block = preparsed_parse_next_header_init; + return ctx; +} diff -up dovecot-2.2.36/src/lib-mail/message-parser.h.CVE_2020_12100 dovecot-2.2.36/src/lib-mail/message-parser.h --- dovecot-2.2.36/src/lib-mail/message-parser.h.CVE_2020_12100 2018-04-30 15:52:05.000000000 +0200 +++ dovecot-2.2.36/src/lib-mail/message-parser.h 2020-08-08 14:07:36.351534219 +0200 @@ -17,6 +17,21 @@ enum message_parser_flags { MESSAGE_PARSER_FLAG_INCLUDE_BOUNDARIES = 0x08 }; +#define MESSAGE_PARSER_DEFAULT_MAX_NESTED_MIME_PARTS 100 +#define MESSAGE_PARSER_DEFAULT_MAX_TOTAL_MIME_PARTS 10000 + +struct message_parser_settings { + enum message_header_parser_flags hdr_flags; + enum message_parser_flags flags; + + /* Maximum nested MIME parts. + 0 = MESSAGE_PARSER_DEFAULT_MAX_NESTED_MIME_PARTS. */ + unsigned int max_nested_mime_parts; + /* Maximum MIME parts in total. + 0 = MESSAGE_PARSER_DEFAULT_MAX_TOTAL_MIME_PARTS. */ + unsigned int max_total_mime_parts; +}; + struct message_parser_ctx; struct message_block { @@ -45,14 +60,12 @@ extern message_part_header_callback_t *n are allocated from. */ struct message_parser_ctx * message_parser_init(pool_t part_pool, struct istream *input, - enum message_header_parser_flags hdr_flags, - enum message_parser_flags flags); + const struct message_parser_settings *set); /* Use preparsed parts to speed up parsing. */ struct message_parser_ctx * message_parser_init_from_parts(struct message_part *parts, struct istream *input, - enum message_header_parser_flags hdr_flags, - enum message_parser_flags flags); + const struct message_parser_settings *set); /* Returns 0 if parts were returned, -1 we used preparsed parts and they didn't match the current message */ int message_parser_deinit(struct message_parser_ctx **ctx, diff -up dovecot-2.2.36/src/lib-mail/message-parser-private.h.CVE_2020_12100 dovecot-2.2.36/src/lib-mail/message-parser-private.h --- dovecot-2.2.36/src/lib-mail/message-parser-private.h.CVE_2020_12100 2020-08-08 13:57:49.297662085 +0200 +++ dovecot-2.2.36/src/lib-mail/message-parser-private.h 2020-08-08 13:57:49.297662085 +0200 @@ -0,0 +1,62 @@ +#ifndef MESSAGE_PARSER_PRIVATE_H +#define MESSAGE_PARSER_PRIVATE_H + +#include "message-parser.h" + +/* RFC-2046 requires boundaries are max. 70 chars + "--" prefix + "--" suffix. + We'll add a bit more just in case. */ +#define BOUNDARY_STRING_MAX_LEN (70 + 10) +#define BOUNDARY_END_MAX_LEN (BOUNDARY_STRING_MAX_LEN + 2 + 2) + +struct message_boundary { + struct message_boundary *next; + + struct message_part *part; + char *boundary; + size_t len; + + bool epilogue_found:1; +}; + +struct message_parser_ctx { + pool_t part_pool; + struct istream *input; + struct message_part *parts, *part; + const char *broken_reason; + unsigned int nested_parts_count; + unsigned int total_parts_count; + + enum message_header_parser_flags hdr_flags; + enum message_parser_flags flags; + unsigned int max_nested_mime_parts; + unsigned int max_total_mime_parts; + + char *last_boundary; + struct message_boundary *boundaries; + + struct message_part **next_part; + ARRAY(struct message_part **) next_part_stack; + + size_t skip; + char last_chr; + unsigned int want_count; + + struct message_header_parser_ctx *hdr_parser_ctx; + unsigned int prev_hdr_newline_size; + + int (*parse_next_block)(struct message_parser_ctx *ctx, + struct message_block *block_r); + + bool part_seen_content_type:1; + bool multipart:1; + bool preparsed:1; + bool eof:1; +}; + +struct message_parser_ctx * +message_parser_init_int(struct istream *input, + const struct message_parser_settings *set); +int message_parser_read_more(struct message_parser_ctx *ctx, + struct message_block *block_r, bool *full_r); + +#endif diff -up dovecot-2.2.36/src/lib-mail/message-search.c.CVE_2020_12100 dovecot-2.2.36/src/lib-mail/message-search.c --- dovecot-2.2.36/src/lib-mail/message-search.c.CVE_2020_12100 2018-04-30 15:52:05.000000000 +0200 +++ dovecot-2.2.36/src/lib-mail/message-search.c 2020-08-08 13:57:49.297662085 +0200 @@ -196,8 +196,9 @@ message_search_msg_real(struct message_s struct istream *input, struct message_part *parts, const char **error_r) { - const enum message_header_parser_flags hdr_parser_flags = - MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE; + const struct message_parser_settings parser_set = { + .hdr_flags = MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE, + }; struct message_parser_ctx *parser_ctx; struct message_block raw_block; struct message_part *new_parts; @@ -207,10 +208,10 @@ message_search_msg_real(struct message_s if (parts != NULL) { parser_ctx = message_parser_init_from_parts(parts, - input, hdr_parser_flags, 0); + input, &parser_set); } else { parser_ctx = message_parser_init(pool_datastack_create(), - input, hdr_parser_flags, 0); + input, &parser_set); } while ((ret = message_parser_parse_next_block(parser_ctx, diff -up dovecot-2.2.36/src/lib-mail/message-snippet.c.CVE_2020_12100 dovecot-2.2.36/src/lib-mail/message-snippet.c --- dovecot-2.2.36/src/lib-mail/message-snippet.c.CVE_2020_12100 2018-04-30 15:52:05.000000000 +0200 +++ dovecot-2.2.36/src/lib-mail/message-snippet.c 2020-08-08 13:57:49.297662085 +0200 @@ -95,6 +95,7 @@ int message_snippet_generate(struct istr unsigned int max_snippet_chars, string_t *snippet) { + const struct message_parser_settings parser_set = { .flags = 0 }; struct message_parser_ctx *parser; struct message_part *parts; struct message_decoder_context *decoder; @@ -108,7 +109,7 @@ int message_snippet_generate(struct istr ctx.snippet = snippet; ctx.chars_left = max_snippet_chars; - parser = message_parser_init(pool_datastack_create(), input, 0, 0); + parser = message_parser_init(pool_datastack_create(), input, &parser_set); decoder = message_decoder_init(NULL, 0); while ((ret = message_parser_parse_next_block(parser, &raw_block)) > 0) { if (!message_decoder_decode_next_block(decoder, &raw_block, &block)) diff -up dovecot-2.2.36/src/lib-mail/test-message-decoder.c.CVE_2020_12100 dovecot-2.2.36/src/lib-mail/test-message-decoder.c --- dovecot-2.2.36/src/lib-mail/test-message-decoder.c.CVE_2020_12100 2020-08-08 13:57:49.294662127 +0200 +++ dovecot-2.2.36/src/lib-mail/test-message-decoder.c 2020-08-08 13:57:49.297662085 +0200 @@ -105,6 +105,7 @@ static void test_message_decoder_multipa "\n" "?garbage\n" "--foo--\n"; + const struct message_parser_settings parser_set = { .flags = 0, }; struct message_parser_ctx *parser; struct message_decoder_context *decoder; struct message_part *parts; @@ -116,7 +117,8 @@ static void test_message_decoder_multipa test_begin("message decoder multipart"); istream = test_istream_create(test_message_input); - parser = message_parser_init(pool_datastack_create(), istream, 0, 0); + parser = message_parser_init(pool_datastack_create(), istream, + &parser_set); decoder = message_decoder_init(NULL, 0); test_istream_set_allow_eof(istream, FALSE); diff -up dovecot-2.2.36/src/lib-mail/test-message-parser.c.CVE_2020_12100 dovecot-2.2.36/src/lib-mail/test-message-parser.c --- dovecot-2.2.36/src/lib-mail/test-message-parser.c.CVE_2020_12100 2018-04-30 15:52:05.000000000 +0200 +++ dovecot-2.2.36/src/lib-mail/test-message-parser.c 2020-08-08 14:17:25.070385831 +0200 @@ -39,6 +39,8 @@ static const char test_msg[] = "\n"; #define TEST_MSG_LEN (sizeof(test_msg)-1) +static const struct message_parser_settings set_empty = { .flags = 0 }; + static bool msg_parts_cmp(struct message_part *p1, struct message_part *p2) { while (p1 != NULL || p2 != NULL) { @@ -59,6 +61,7 @@ static bool msg_parts_cmp(struct message p1->body_size.physical_size != p2->body_size.physical_size || p1->body_size.virtual_size != p2->body_size.virtual_size || p1->body_size.lines != p2->body_size.lines || + p1->children_count != p2->children_count || p1->flags != p2->flags) return FALSE; @@ -70,6 +73,9 @@ static bool msg_parts_cmp(struct message static void test_parsed_parts(struct istream *input, struct message_part *parts) { + const struct message_parser_settings parser_set = { + .flags = MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK, + }; struct message_parser_ctx *parser; struct message_block block; struct message_part *parts2; @@ -81,8 +87,7 @@ static void test_parsed_parts(struct ist if (i_stream_get_size(input, TRUE, &input_size) < 0) i_unreached(); - parser = message_parser_init_from_parts(parts, input, 0, - MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK); + parser = message_parser_init_from_parts(parts, input, &parser_set); for (i = 1; i <= input_size*2+1; i++) { test_istream_set_size(input, i/2); if (i > TEST_MSG_LEN*2) @@ -111,9 +116,11 @@ static void test_message_parser_small_bl output = t_str_new(128); /* full parsing */ - parser = message_parser_init(pool, input, 0, - MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS | - MESSAGE_PARSER_FLAG_INCLUDE_BOUNDARIES); + const struct message_parser_settings full_parser_set = { + .flags = MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS | + MESSAGE_PARSER_FLAG_INCLUDE_BOUNDARIES, + }; + parser = message_parser_init(pool, input, &full_parser_set); while ((ret = message_parser_parse_next_block(parser, &block)) > 0) { if (block.hdr != NULL) message_header_line_write(output, block.hdr); @@ -129,7 +136,7 @@ static void test_message_parser_small_bl i_stream_seek(input, 0); test_istream_set_allow_eof(input, FALSE); - parser = message_parser_init(pool, input, 0, 0); + parser = message_parser_init(pool, input, &set_empty); for (i = 1; i <= TEST_MSG_LEN*2+1; i++) { test_istream_set_size(input, i/2); if (i > TEST_MSG_LEN*2) @@ -147,8 +154,11 @@ static void test_message_parser_small_bl test_istream_set_allow_eof(input, FALSE); end_of_headers_idx = (strstr(test_msg, "\n-----") - test_msg); - parser = message_parser_init_from_parts(parts, input, 0, - MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK); + const struct message_parser_settings preparsed_parser_set = { + .flags = MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK, + }; + parser = message_parser_init_from_parts(parts, input, + &preparsed_parser_set); for (i = 1; i <= TEST_MSG_LEN*2+1; i++) { test_istream_set_size(input, i/2); if (i > TEST_MSG_LEN*2) @@ -166,6 +176,36 @@ static void test_message_parser_small_bl test_end(); } +static void test_message_parser_stop_early(void) +{ + struct message_parser_ctx *parser; + struct istream *input; + struct message_part *parts; + struct message_block block; + unsigned int i; + pool_t pool; + int ret; + + test_begin("message parser in stop early"); + pool = pool_alloconly_create("message parser", 10240); + input = test_istream_create(test_msg); + + test_istream_set_allow_eof(input, FALSE); + for (i = 1; i <= TEST_MSG_LEN+1; i++) { + i_stream_seek(input, 0); + test_istream_set_size(input, i); + parser = message_parser_init(pool, input, &set_empty); + while ((ret = message_parser_parse_next_block(parser, + &block)) > 0) ; + test_assert(ret == 0); + message_parser_deinit(&parser, &parts); + } + + i_stream_unref(&input); + pool_unref(&pool); + test_end(); +} + static void test_message_parser_truncated_mime_headers(void) { static const char input_msg[] = @@ -190,12 +230,13 @@ static const char input_msg[] = pool = pool_alloconly_create("message parser", 10240); input = test_istream_create(input_msg); - parser = message_parser_init(pool, input, 0, 0); + parser = message_parser_init(pool, input, &set_empty); while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; test_assert(ret < 0); test_assert(message_parser_deinit(&parser, &parts) == 0); test_assert((parts->flags & MESSAGE_PART_FLAG_MULTIPART) != 0); + test_assert(parts->children_count == 4); test_assert(parts->header_size.lines == 2); test_assert(parts->header_size.physical_size == 48); test_assert(parts->header_size.virtual_size == 48+2); @@ -219,6 +260,7 @@ static const char input_msg[] = test_assert(parts->children->next->next->next->header_size.virtual_size == 23); test_assert(parts->children->next->next->next->header_size.lines == 0); for (part = parts->children; part != NULL; part = part->next) { + test_assert(part->children_count == 0); test_assert(part->body_size.physical_size == 0); test_assert(part->body_size.virtual_size == 0); } @@ -253,12 +295,13 @@ static const char input_msg[] = pool = pool_alloconly_create("message parser", 10240); input = test_istream_create(input_msg); - parser = message_parser_init(pool, input, 0, 0); + parser = message_parser_init(pool, input, &set_empty); while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; test_assert(ret < 0); test_assert(message_parser_deinit(&parser, &parts) == 0); test_assert(parts->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(parts->children_count == 2); test_assert(parts->header_size.lines == 2); test_assert(parts->header_size.physical_size == 46); test_assert(parts->header_size.virtual_size == 46+2); @@ -266,6 +309,7 @@ static const char input_msg[] = test_assert(parts->body_size.physical_size == 86); test_assert(parts->body_size.virtual_size == 86+8); + test_assert(parts->children->children_count == 0); test_assert(parts->children->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); test_assert(parts->children->physical_pos == 51); test_assert(parts->children->header_size.lines == 1); @@ -275,6 +319,7 @@ static const char input_msg[] = test_assert(parts->children->body_size.physical_size == 0); test_assert(parts->children->children == NULL); + test_assert(parts->children->next->children_count == 0); test_assert(parts->children->next->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME)); test_assert(parts->children->next->physical_pos == 101); test_assert(parts->children->next->header_size.lines == 2); @@ -306,11 +351,12 @@ static const char input_msg[] = pool = pool_alloconly_create("message parser", 10240); input = test_istream_create(input_msg); - parser = message_parser_init(pool, input, 0, 0); + parser = message_parser_init(pool, input, &set_empty); while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; test_assert(ret < 0); test_assert(message_parser_deinit(&parser, &parts) == 0); + test_assert(parts->children_count == 0); test_assert(parts->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); test_assert(parts->header_size.lines == 1); test_assert(parts->header_size.physical_size == 45); @@ -343,11 +389,12 @@ static const char input_msg[] = pool = pool_alloconly_create("message parser", 10240); input = test_istream_create(input_msg); - parser = message_parser_init(pool, input, 0, 0); + parser = message_parser_init(pool, input, &set_empty); while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; test_assert(ret < 0); test_assert(message_parser_deinit(&parser, &parts) == 0); + test_assert(parts->children_count == 0); test_assert(parts->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); test_assert(parts->header_size.lines == 2); test_assert(parts->header_size.physical_size == 46); @@ -387,11 +434,12 @@ static const char input_msg[] = pool = pool_alloconly_create("message parser", 10240); input = test_istream_create(input_msg); - parser = message_parser_init(pool, input, 0, 0); + parser = message_parser_init(pool, input, &set_empty); while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; test_assert(ret < 0); test_assert(message_parser_deinit(&parser, &parts) == 0); + test_assert(parts->children_count == 2); test_assert(parts->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); test_assert(parts->header_size.lines == 2); test_assert(parts->header_size.physical_size == 45); @@ -399,6 +447,7 @@ static const char input_msg[] = test_assert(parts->body_size.lines == 7); test_assert(parts->body_size.physical_size == 84); test_assert(parts->body_size.virtual_size == 84+7); + test_assert(parts->children->children_count == 1); test_assert(parts->children->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); test_assert(parts->children->physical_pos == 49); test_assert(parts->children->header_size.lines == 2); @@ -407,6 +456,7 @@ static const char input_msg[] = test_assert(parts->children->body_size.lines == 4); test_assert(parts->children->body_size.physical_size == 35); test_assert(parts->children->body_size.virtual_size == 35+4); + test_assert(parts->children->children->children_count == 0); test_assert(parts->children->children->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME)); test_assert(parts->children->children->physical_pos == 98); test_assert(parts->children->children->header_size.lines == 2); @@ -445,11 +495,12 @@ static const char input_msg[] = pool = pool_alloconly_create("message parser", 10240); input = test_istream_create(input_msg); - parser = message_parser_init(pool, input, 0, 0); + parser = message_parser_init(pool, input, &set_empty); while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; test_assert(ret < 0); test_assert(message_parser_deinit(&parser, &parts) == 0); + test_assert(parts->children_count == 2); test_assert(parts->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); test_assert(parts->header_size.lines == 2); test_assert(parts->header_size.physical_size == 45); @@ -457,6 +508,7 @@ static const char input_msg[] = test_assert(parts->body_size.lines == 7); test_assert(parts->body_size.physical_size == 86); test_assert(parts->body_size.virtual_size == 86+7); + test_assert(parts->children->children_count == 1); test_assert(parts->children->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); test_assert(parts->children->physical_pos == 50); test_assert(parts->children->header_size.lines == 2); @@ -465,6 +517,7 @@ static const char input_msg[] = test_assert(parts->children->body_size.lines == 4); test_assert(parts->children->body_size.physical_size == 36); test_assert(parts->children->body_size.virtual_size == 36+4); + test_assert(parts->children->children->children_count == 0); test_assert(parts->children->children->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME)); test_assert(parts->children->children->physical_pos == 100); test_assert(parts->children->children->header_size.lines == 2); @@ -480,6 +533,51 @@ static const char input_msg[] = test_end(); } +static void test_message_parser_trailing_dashes(void) +{ +static const char input_msg[] = +"Content-Type: multipart/mixed; boundary=\"a--\"\n" +"\n" +"--a--\n" +"Content-Type: multipart/mixed; boundary=\"a----\"\n" +"\n" +"--a----\n" +"Content-Type: text/plain\n" +"\n" +"body\n" +"--a------\n" +"Content-Type: text/html\n" +"\n" +"body2\n" +"--a----"; + struct message_parser_ctx *parser; + struct istream *input; + struct message_part *parts; + struct message_block block; + pool_t pool; + int ret; + + test_begin("message parser trailing dashes"); + pool = pool_alloconly_create("message parser", 10240); + input = test_istream_create(input_msg); + + parser = message_parser_init(pool, input, &set_empty); + while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; + test_assert(ret < 0); + message_parser_deinit(&parser, &parts); + + test_assert(parts->children_count == 2); + test_assert(parts->children->next == NULL); + test_assert(parts->children->children_count == 1); + test_assert(parts->children->children->next == NULL); + test_assert(parts->children->children->children_count == 0); + + test_parsed_parts(input, parts); + i_stream_unref(&input); + pool_unref(&pool); + test_end(); +} + static void test_message_parser_continuing_mime_boundary(void) { static const char input_msg[] = @@ -503,11 +601,12 @@ static const char input_msg[] = pool = pool_alloconly_create("message parser", 10240); input = test_istream_create(input_msg); - parser = message_parser_init(pool, input, 0, 0); + parser = message_parser_init(pool, input, &set_empty); while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; test_assert(ret < 0); test_assert(message_parser_deinit(&parser, &parts) == 0); + test_assert(parts->children_count == 2); test_assert(parts->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); test_assert(parts->header_size.lines == 2); test_assert(parts->header_size.physical_size == 45); @@ -515,6 +614,7 @@ static const char input_msg[] = test_assert(parts->body_size.lines == 7); test_assert(parts->body_size.physical_size == 86); test_assert(parts->body_size.virtual_size == 86+7); + test_assert(parts->children->children_count == 1); test_assert(parts->children->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); test_assert(parts->children->physical_pos == 49); test_assert(parts->children->header_size.lines == 2); @@ -523,6 +623,7 @@ static const char input_msg[] = test_assert(parts->children->body_size.lines == 4); test_assert(parts->children->body_size.physical_size == 36); test_assert(parts->children->body_size.virtual_size == 36+4); + test_assert(parts->children->children->children_count == 0); test_assert(parts->children->children->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME)); test_assert(parts->children->children->physical_pos == 100); test_assert(parts->children->children->header_size.lines == 2); @@ -562,12 +663,13 @@ static const char input_msg[] = pool = pool_alloconly_create("message parser", 10240); input = test_istream_create(input_msg); - parser = message_parser_init(pool, input, 0, 0); + parser = message_parser_init(pool, input, &set_empty); while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; test_assert(ret < 0); message_parser_deinit(&parser, &parts); part = parts; + test_assert(part->children_count == 3); test_assert(part->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); test_assert(part->header_size.lines == 2); test_assert(part->header_size.physical_size == 45); @@ -577,6 +679,7 @@ static const char input_msg[] = test_assert(part->body_size.virtual_size == 112+9); part = parts->children; + test_assert(part->children_count == 0); test_assert(part->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); test_assert(part->physical_pos == 49); test_assert(part->header_size.lines == 1); @@ -590,6 +693,7 @@ static const char input_msg[] = we could make it, but it would complicate the message-parser even more. */ part = parts->children->next; + test_assert(part->children_count == 0); test_assert(part->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME)); test_assert(part->physical_pos == 117); test_assert(part->header_size.lines == 1); @@ -600,6 +704,7 @@ static const char input_msg[] = test_assert(part->children == NULL); part = parts->children->next->next; + test_assert(part->children_count == 0); test_assert(part->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME)); test_assert(part->header_size.lines == 0); test_assert(part->header_size.physical_size == 0); @@ -614,6 +719,80 @@ static const char input_msg[] = test_end(); } +static void test_message_parser_continuing_mime_boundary_reverse(void) +{ +static const char input_msg[] = +"Content-Type: multipart/mixed; boundary=\"ab\"\n" +"\n" +"--ab\n" +"Content-Type: multipart/mixed; boundary=\"a\"\n" +"\n" +"--a\n" +"Content-Type: text/plain\n" +"\n" +"body\n" +"--ab\n" +"Content-Type: text/html\n" +"\n" +"body2\n"; + struct message_parser_ctx *parser; + struct istream *input; + struct message_part *parts; + struct message_block block; + pool_t pool; + int ret; + + test_begin("message parser continuing mime boundary reverse"); + pool = pool_alloconly_create("message parser", 10240); + input = test_istream_create(input_msg); + + parser = message_parser_init(pool, input, &set_empty); + while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; + test_assert(ret < 0); + message_parser_deinit(&parser, &parts); + + test_assert(parts->children_count == 3); + test_assert(parts->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(parts->header_size.lines == 2); + test_assert(parts->header_size.physical_size == 46); + test_assert(parts->header_size.virtual_size == 46+2); + test_assert(parts->body_size.lines == 11); + test_assert(parts->body_size.physical_size == 121); + test_assert(parts->body_size.virtual_size == 121+11); + test_assert(parts->children->children_count == 1); + test_assert(parts->children->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(parts->children->physical_pos == 51); + test_assert(parts->children->header_size.lines == 2); + test_assert(parts->children->header_size.physical_size == 45); + test_assert(parts->children->header_size.virtual_size == 45+2); + test_assert(parts->children->body_size.lines == 3); + test_assert(parts->children->body_size.physical_size == 34); + test_assert(parts->children->body_size.virtual_size == 34+3); + test_assert(parts->children->children->children_count == 0); + test_assert(parts->children->children->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(parts->children->children->physical_pos == 100); + test_assert(parts->children->children->header_size.lines == 2); + test_assert(parts->children->children->header_size.physical_size == 26); + test_assert(parts->children->children->header_size.virtual_size == 26+2); + test_assert(parts->children->children->body_size.lines == 0); + test_assert(parts->children->children->body_size.physical_size == 4); + test_assert(parts->children->children->body_size.virtual_size == 4); + test_assert(parts->children->next->children_count == 0); + test_assert(parts->children->next->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(parts->children->next->physical_pos == 136); + test_assert(parts->children->next->header_size.lines == 2); + test_assert(parts->children->next->header_size.physical_size == 25); + test_assert(parts->children->next->header_size.virtual_size == 25+2); + test_assert(parts->children->next->body_size.lines == 1); + test_assert(parts->children->next->body_size.physical_size == 6); + test_assert(parts->children->next->body_size.virtual_size == 6+1); + + test_parsed_parts(input, parts); + i_stream_unref(&input); + pool_unref(&pool); + test_end(); +} + static void test_message_parser_no_eoh(void) { static const char input_msg[] = "a:b\n"; @@ -627,7 +806,7 @@ static void test_message_parser_no_eoh(v pool = pool_alloconly_create("message parser", 10240); input = test_istream_create(input_msg); - parser = message_parser_init(pool, input, 0, 0); + parser = message_parser_init(pool, input, &set_empty); test_assert(message_parser_parse_next_block(parser, &block) > 0 && block.hdr != NULL && strcmp(block.hdr->name, "a") == 0 && block.hdr->value_len == 1 && block.hdr->value[0] == 'b'); @@ -642,19 +821,335 @@ static void test_message_parser_no_eoh(v test_end(); } +static void test_message_parser_long_mime_boundary(void) +{ + /* Close the boundaries in wrong reverse order. But because all + boundaries are actually truncated to the same size (..890) it + works the same as if all of them were duplicate boundaries. */ +static const char input_msg[] = +"Content-Type: multipart/mixed; boundary=\"1234567890123456789012345678901234567890123456789012345678901234567890123456789012\"\n" +"\n" +"--1234567890123456789012345678901234567890123456789012345678901234567890123456789012\n" +"Content-Type: multipart/mixed; boundary=\"123456789012345678901234567890123456789012345678901234567890123456789012345678901\"\n" +"\n" +"--123456789012345678901234567890123456789012345678901234567890123456789012345678901\n" +"Content-Type: multipart/mixed; boundary=\"12345678901234567890123456789012345678901234567890123456789012345678901234567890\"\n" +"\n" +"--12345678901234567890123456789012345678901234567890123456789012345678901234567890\n" +"Content-Type: text/plain\n" +"\n" +"1\n" +"--1234567890123456789012345678901234567890123456789012345678901234567890123456789012\n" +"Content-Type: text/plain\n" +"\n" +"22\n" +"--123456789012345678901234567890123456789012345678901234567890123456789012345678901\n" +"Content-Type: text/plain\n" +"\n" +"333\n" +"--12345678901234567890123456789012345678901234567890123456789012345678901234567890\n" +"Content-Type: text/plain\n" +"\n" +"4444\n"; + struct message_parser_ctx *parser; + struct istream *input; + struct message_part *parts, *part; + struct message_block block; + pool_t pool; + int ret; + + test_begin("message parser long mime boundary"); + pool = pool_alloconly_create("message parser", 10240); + input = test_istream_create(input_msg); + + parser = message_parser_init(pool, input, &set_empty); + while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; + test_assert(ret < 0); + message_parser_deinit(&parser, &parts); + + part = parts; + test_assert(part->children_count == 6); + test_assert(part->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(part->header_size.lines == 2); + test_assert(part->header_size.physical_size == 126); + test_assert(part->header_size.virtual_size == 126+2); + test_assert(part->body_size.lines == 22); + test_assert(part->body_size.physical_size == 871); + test_assert(part->body_size.virtual_size == 871+22); + + part = parts->children; + test_assert(part->children_count == 5); + test_assert(part->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(part->header_size.lines == 2); + test_assert(part->header_size.physical_size == 125); + test_assert(part->header_size.virtual_size == 125+2); + test_assert(part->body_size.lines == 19); + test_assert(part->body_size.physical_size == 661); + test_assert(part->body_size.virtual_size == 661+19); + + part = parts->children->children; + test_assert(part->children_count == 4); + test_assert(part->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(part->header_size.lines == 2); + test_assert(part->header_size.physical_size == 124); + test_assert(part->header_size.virtual_size == 124+2); + test_assert(part->body_size.lines == 16); + test_assert(part->body_size.physical_size == 453); + test_assert(part->body_size.virtual_size == 453+16); + + part = parts->children->children->children; + for (unsigned int i = 1; i <= 3; i++, part = part->next) { + test_assert(part->children_count == 0); + test_assert(part->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(part->header_size.lines == 2); + test_assert(part->header_size.physical_size == 26); + test_assert(part->header_size.virtual_size == 26+2); + test_assert(part->body_size.lines == 0); + test_assert(part->body_size.physical_size == i); + test_assert(part->body_size.virtual_size == i); + } + + test_parsed_parts(input, parts); + i_stream_unref(&input); + pool_unref(&pool); + test_end(); +} + +static void test_message_parser_mime_part_nested_limit(void) +{ +static const char input_msg[] = +"Content-Type: multipart/mixed; boundary=\"1\"\n" +"\n" +"--1\n" +"Content-Type: multipart/mixed; boundary=\"2\"\n" +"\n" +"--2\n" +"Content-Type: text/plain\n" +"\n" +"1\n" +"--2\n" +"Content-Type: text/plain\n" +"\n" +"22\n" +"--1\n" +"Content-Type: text/plain\n" +"\n" +"333\n"; + const struct message_parser_settings parser_set = { + .max_nested_mime_parts = 2, + }; + struct message_parser_ctx *parser; + struct istream *input; + struct message_part *parts, *part; + struct message_block block; + pool_t pool; + int ret; + + test_begin("message parser mime part nested limit"); + pool = pool_alloconly_create("message parser", 10240); + input = test_istream_create(input_msg); + + parser = message_parser_init(pool, input, &parser_set); + while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; + test_assert(ret < 0); + message_parser_deinit(&parser, &parts); + + part = parts; + test_assert(part->children_count == 2); + test_assert(part->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(part->header_size.lines == 2); + test_assert(part->header_size.physical_size == 45); + test_assert(part->header_size.virtual_size == 45+2); + test_assert(part->body_size.lines == 15); + test_assert(part->body_size.physical_size == 148); + test_assert(part->body_size.virtual_size == 148+15); + + part = parts->children; + test_assert(part->children_count == 0); + test_assert(part->flags == MESSAGE_PART_FLAG_IS_MIME); + test_assert(part->header_size.lines == 2); + test_assert(part->header_size.physical_size == 45); + test_assert(part->header_size.virtual_size == 45+2); + test_assert(part->body_size.lines == 7); + test_assert(part->body_size.physical_size == 64); + test_assert(part->body_size.virtual_size == 64+7); + + part = parts->children->next; + test_assert(part->children_count == 0); + test_assert(part->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(part->header_size.lines == 2); + test_assert(part->header_size.physical_size == 26); + test_assert(part->header_size.virtual_size == 26+2); + test_assert(part->body_size.lines == 1); + test_assert(part->body_size.physical_size == 4); + test_assert(part->body_size.virtual_size == 4+1); + + test_parsed_parts(input, parts); + i_stream_unref(&input); + pool_unref(&pool); + test_end(); +} + +static void test_message_parser_mime_part_nested_limit_rfc822(void) +{ +static const char input_msg[] = +"Content-Type: message/rfc822\n" +"\n" +"Content-Type: message/rfc822\n" +"\n" +"Content-Type: text/plain\n" +"\n" +"1\n"; + const struct message_parser_settings parser_set = { + .max_nested_mime_parts = 2, + }; + struct message_parser_ctx *parser; + struct istream *input; + struct message_part *parts, *part; + struct message_block block; + pool_t pool; + int ret; + + test_begin("message parser mime part nested limit rfc822"); + pool = pool_alloconly_create("message parser", 10240); + input = test_istream_create(input_msg); + + parser = message_parser_init(pool, input, &parser_set); + while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; + test_assert(ret < 0); + message_parser_deinit(&parser, &parts); + + part = parts; + test_assert(part->children_count == 1); + test_assert(part->flags == (MESSAGE_PART_FLAG_MESSAGE_RFC822 | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(part->header_size.lines == 2); + test_assert(part->header_size.physical_size == 30); + test_assert(part->header_size.virtual_size == 30+2); + test_assert(part->body_size.lines == 5); + test_assert(part->body_size.physical_size == 58); + test_assert(part->body_size.virtual_size == 58+5); + + part = parts->children; + test_assert(part->children_count == 0); + test_assert(part->flags == MESSAGE_PART_FLAG_IS_MIME); + test_assert(part->header_size.lines == 2); + test_assert(part->header_size.physical_size == 30); + test_assert(part->header_size.virtual_size == 30+2); + test_assert(part->body_size.lines == 3); + test_assert(part->body_size.physical_size == 28); + test_assert(part->body_size.virtual_size == 28+3); + + test_parsed_parts(input, parts); + i_stream_unref(&input); + pool_unref(&pool); + test_end(); +} + +static void test_message_parser_mime_part_limit(void) +{ +static const char input_msg[] = +"Content-Type: multipart/mixed; boundary=\"1\"\n" +"\n" +"--1\n" +"Content-Type: multipart/mixed; boundary=\"2\"\n" +"\n" +"--2\n" +"Content-Type: text/plain\n" +"\n" +"1\n" +"--2\n" +"Content-Type: text/plain\n" +"\n" +"22\n" +"--1\n" +"Content-Type: text/plain\n" +"\n" +"333\n"; + const struct message_parser_settings parser_set = { + .max_total_mime_parts = 4, + }; + struct message_parser_ctx *parser; + struct istream *input; + struct message_part *parts, *part; + struct message_block block; + pool_t pool; + int ret; + + test_begin("message parser mime part limit"); + pool = pool_alloconly_create("message parser", 10240); + input = test_istream_create(input_msg); + + parser = message_parser_init(pool, input, &parser_set); + while ((ret = message_parser_parse_next_block(parser, &block)) > 0) ; + test_assert(ret < 0); + message_parser_deinit(&parser, &parts); + + part = parts; + test_assert(part->children_count == 3); + test_assert(part->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(part->header_size.lines == 2); + test_assert(part->header_size.physical_size == 45); + test_assert(part->header_size.virtual_size == 45+2); + test_assert(part->body_size.lines == 15); + test_assert(part->body_size.physical_size == 148); + test_assert(part->body_size.virtual_size == 148+15); + + part = parts->children; + test_assert(part->children_count == 2); + test_assert(part->flags == (MESSAGE_PART_FLAG_MULTIPART | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(part->header_size.lines == 2); + test_assert(part->header_size.physical_size == 45); + test_assert(part->header_size.virtual_size == 45+2); + test_assert(part->body_size.lines == 12); + test_assert(part->body_size.physical_size == 99); + test_assert(part->body_size.virtual_size == 99+12); + + part = parts->children->children; + test_assert(part->children_count == 0); + test_assert(part->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(part->header_size.lines == 2); + test_assert(part->header_size.physical_size == 26); + test_assert(part->header_size.virtual_size == 26+2); + test_assert(part->body_size.lines == 0); + test_assert(part->body_size.physical_size == 1); + test_assert(part->body_size.virtual_size == 1); + + part = parts->children->children->next; + test_assert(part->children_count == 0); + test_assert(part->flags == (MESSAGE_PART_FLAG_TEXT | MESSAGE_PART_FLAG_IS_MIME)); + test_assert(part->header_size.lines == 2); + test_assert(part->header_size.physical_size == 26); + test_assert(part->header_size.virtual_size == 26+2); + test_assert(part->body_size.lines == 5); + test_assert(part->body_size.physical_size == 37); + test_assert(part->body_size.virtual_size == 37+5); + + test_parsed_parts(input, parts); + i_stream_unref(&input); + pool_unref(&pool); + test_end(); +} + int main(void) { static void (*test_functions[])(void) = { test_message_parser_small_blocks, + test_message_parser_stop_early, test_message_parser_truncated_mime_headers, test_message_parser_truncated_mime_headers2, test_message_parser_truncated_mime_headers3, test_message_parser_empty_multipart, test_message_parser_duplicate_mime_boundary, test_message_parser_garbage_suffix_mime_boundary, + test_message_parser_trailing_dashes, test_message_parser_continuing_mime_boundary, test_message_parser_continuing_truncated_mime_boundary, + test_message_parser_continuing_mime_boundary_reverse, + test_message_parser_long_mime_boundary, test_message_parser_no_eoh, + test_message_parser_mime_part_nested_limit, + test_message_parser_mime_part_nested_limit_rfc822, + test_message_parser_mime_part_limit, NULL }; return test_run(test_functions); diff -up dovecot-2.2.36/src/lib-mail/test-message-part.c.CVE_2020_12100 dovecot-2.2.36/src/lib-mail/test-message-part.c --- dovecot-2.2.36/src/lib-mail/test-message-part.c.CVE_2020_12100 2018-04-30 15:52:05.000000000 +0200 +++ dovecot-2.2.36/src/lib-mail/test-message-part.c 2020-08-08 13:57:49.297662085 +0200 @@ -65,6 +65,7 @@ static const char test_msg[] = static void test_message_part_idx(void) { + const struct message_parser_settings set = { .flags = 0 }; struct message_parser_ctx *parser; struct istream *input; struct message_part *parts, *part, *prev_part; @@ -77,7 +78,7 @@ static void test_message_part_idx(void) pool = pool_alloconly_create("message parser", 10240); input = i_stream_create_from_data(test_msg, TEST_MSG_LEN); - parser = message_parser_init(pool, input, 0, 0); + parser = message_parser_init(pool, input, &set); while ((ret = message_parser_parse_next_block(parser, &block)) > 0) { part_idx = message_part_to_idx(block.part); test_assert(part_idx >= prev_idx); diff -up dovecot-2.2.36/src/lib-storage/index/index-mail-headers.c.CVE_2020_12100 dovecot-2.2.36/src/lib-storage/index/index-mail-headers.c --- dovecot-2.2.36/src/lib-storage/index/index-mail-headers.c.CVE_2020_12100 2018-04-30 15:52:05.000000000 +0200 +++ dovecot-2.2.36/src/lib-storage/index/index-mail-headers.c 2020-08-08 13:57:49.298662071 +0200 @@ -16,11 +16,11 @@ #include "index-storage.h" #include "index-mail.h" -static const enum message_header_parser_flags hdr_parser_flags = - MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP | - MESSAGE_HEADER_PARSER_FLAG_DROP_CR; -static const enum message_parser_flags msg_parser_flags = - MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK; +static const struct message_parser_settings msg_parser_set = { + .hdr_flags = MESSAGE_HEADER_PARSER_FLAG_SKIP_INITIAL_LWSP | + MESSAGE_HEADER_PARSER_FLAG_DROP_CR, + .flags = MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK, +}; static int header_line_cmp(const struct index_mail_line *l1, const struct index_mail_line *l2) @@ -399,7 +399,7 @@ index_mail_cache_parse_init(struct mail mail->data.parser_input = input; mail->data.parser_ctx = message_parser_init(mail->mail.data_pool, input, - hdr_parser_flags, msg_parser_flags); + &msg_parser_set); i_stream_unref(&input); return input2; } @@ -422,14 +422,12 @@ static void index_mail_init_parser(struc data->parser_input = data->stream; data->parser_ctx = message_parser_init(mail->mail.data_pool, data->stream, - hdr_parser_flags, - msg_parser_flags); + &msg_parser_set); } else { data->parser_ctx = message_parser_init_from_parts(data->parts, data->stream, - hdr_parser_flags, - msg_parser_flags); + &msg_parser_set); } } @@ -462,7 +460,7 @@ int index_mail_parse_headers(struct inde i_assert(!data->save_bodystructure_body || data->parser_ctx != NULL); message_parse_header(data->stream, &data->hdr_size, - hdr_parser_flags, + msg_parser_set.hdr_flags, index_mail_parse_header_cb, mail); } if (index_mail_stream_check_failure(mail) < 0) @@ -517,7 +515,7 @@ int index_mail_headers_get_envelope(stru if (mail->data.envelope == NULL && stream != NULL) { /* we got the headers from cache - parse them to get the envelope */ - message_parse_header(stream, NULL, hdr_parser_flags, + message_parse_header(stream, NULL, msg_parser_set.hdr_flags, imap_envelope_parse_callback, mail); if (stream->stream_errno != 0) { index_mail_stream_log_failure_for(mail, stream); diff -up dovecot-2.2.36/src/plugins/fts/fts-build-mail.c.CVE_2020_12100 dovecot-2.2.36/src/plugins/fts/fts-build-mail.c --- dovecot-2.2.36/src/plugins/fts/fts-build-mail.c.CVE_2020_12100 2018-04-30 15:52:05.000000000 +0200 +++ dovecot-2.2.36/src/plugins/fts/fts-build-mail.c 2020-08-08 14:18:31.289469311 +0200 @@ -458,6 +458,9 @@ static int fts_build_mail_real(struct fts_backend_update_context *update_ctx, struct mail *mail) { + const struct message_parser_settings parser_set = { + .hdr_flags = MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE, + }; struct fts_mail_build_context ctx; struct istream *input; struct message_parser_ctx *parser; @@ -485,9 +488,7 @@ fts_build_mail_real(struct fts_backend_u ctx.pending_input = buffer_create_dynamic(default_pool, 128); prev_part = NULL; - parser = message_parser_init(pool_datastack_create(), input, - MESSAGE_HEADER_PARSER_FLAG_CLEAN_ONELINE, - 0); + parser = message_parser_init(pool_datastack_create(), input, &parser_set); decoder = message_decoder_init(update_ctx->normalizer, 0); for (;;) {