From: Marco Bettini Date: Fri, 12 Apr 2024 15:06:43 +0000 (+0000) Subject: lib-mail: message-header-parser - Limit header block to 10MB by default X-Git-Tag: 2.3.21.1~3 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=f020e139c519121d9630a966310ea8e100ee33b7;p=thirdparty%2Fdovecot%2Fcore.git lib-mail: message-header-parser - Limit header block to 10MB by default --- diff --git a/src/lib-mail/message-header-parser.c b/src/lib-mail/message-header-parser.c index c5026f1bb7..5e020bbeb3 100644 --- a/src/lib-mail/message-header-parser.c +++ b/src/lib-mail/message-header-parser.c @@ -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 { diff --git a/src/lib-mail/message-header-parser.h b/src/lib-mail/message-header-parser.h index ce0825c8e5..43cf95e56a 100644 --- a/src/lib-mail/message-header-parser.h +++ b/src/lib-mail/message-header-parser.h @@ -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). */ diff --git a/src/lib-mail/test-message-header-parser.c b/src/lib-mail/test-message-header-parser.c index 700d3413f1..93d8842002 100644 --- a/src/lib-mail/test-message-header-parser.c +++ b/src/lib-mail/test-message-header-parser.c @@ -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);