diff -Nru dovecot-2.3.7.2/debian/changelog dovecot-2.3.7.2/debian/changelog --- dovecot-2.3.7.2/debian/changelog 2020-08-05 14:26:17.000000000 +0000 +++ dovecot-2.3.7.2/debian/changelog 2020-12-28 16:04:43.000000000 +0000 @@ -1,3 +1,24 @@ +dovecot (1:2.3.7.2-1ubuntu3.3) focal-security; urgency=medium + + * SECURITY UPDATE: information disclosure via imap hibernation + - debian/patches/CVE-2020-24386-1.patch: escape tag when sending it to + imap-hibernate process in src/imap/imap-client-hibernate.c. + - debian/patches/CVE-2020-24386-2.patch: add unit test for + imap-client-hibernate in src/imap/Makefile.am, + src/imap/imap-client-hibernate.c, src/imap/imap-client.h, + src/imap/test-imap-client-hibernate.c. + - CVE-2020-24386 + * SECURITY UPDATE: remote DoS via large number of MIME parts + - debian/patches/CVE-2020-25275-1.patch: fix assert-crash when + enforcing MIME part limit in src/lib-mail/message-parser.c, + src/lib-mail/test-message-parser.c. + - debian/patches/CVE-2020-25275-2.patch: don't generate invalid + BODYSTRUCTURE when reaching MIME part limit in + src/lib-imap/imap-bodystructure.c. + - CVE-2020-25275 + + -- Marc Deslauriers Mon, 28 Dec 2020 11:04:43 -0500 + dovecot (1:2.3.7.2-1ubuntu3.2) focal-security; urgency=medium * SECURITY UPDATE: DoS via deeply nested MIME parts diff -Nru dovecot-2.3.7.2/debian/patches/CVE-2020-24386-1.patch dovecot-2.3.7.2/debian/patches/CVE-2020-24386-1.patch --- dovecot-2.3.7.2/debian/patches/CVE-2020-24386-1.patch 1970-01-01 00:00:00.000000000 +0000 +++ dovecot-2.3.7.2/debian/patches/CVE-2020-24386-1.patch 2020-12-28 16:04:43.000000000 +0000 @@ -0,0 +1,25 @@ +From a75e9af4dd58036e7e63af14bf21df5fc335d639 Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +Date: Mon, 17 Aug 2020 18:33:20 +0300 +Subject: [PATCH 1/2] imap: Escape tag when sending it to imap-hibernate + process + +--- + src/imap/imap-client-hibernate.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +--- a/src/imap/imap-client-hibernate.c ++++ b/src/imap/imap-client-hibernate.c +@@ -89,8 +89,10 @@ static void imap_hibernate_write_cmd(str + str_printfa(cmd, "\tuid=%s", dec2str(user->uid)); + if (user->gid != (gid_t)-1) + str_printfa(cmd, "\tgid=%s", dec2str(user->gid)); +- if (tag != NULL) +- str_printfa(cmd, "\ttag=%s", tag); ++ if (tag != NULL) { ++ str_append(cmd, "\ttag="); ++ str_append_tabescaped(cmd, tag); ++ } + str_append(cmd, "\tstats="); + str_append_tabescaped(cmd, client_stats(client)); + if (client->command_queue != NULL && diff -Nru dovecot-2.3.7.2/debian/patches/CVE-2020-24386-2.patch dovecot-2.3.7.2/debian/patches/CVE-2020-24386-2.patch --- dovecot-2.3.7.2/debian/patches/CVE-2020-24386-2.patch 1970-01-01 00:00:00.000000000 +0000 +++ dovecot-2.3.7.2/debian/patches/CVE-2020-24386-2.patch 2020-12-28 16:04:43.000000000 +0000 @@ -0,0 +1,191 @@ +Backport of: + +From ae7c2082d6b07d61e251b1dfad9db0a0661d7435 Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +Date: Mon, 24 Aug 2020 19:12:21 +0300 +Subject: [PATCH 2/2] imap: Add unit test for imap-client-hibernate + +--- + src/imap/Makefile.am | 22 +++++++- + src/imap/imap-client-hibernate.c | 4 +- + src/imap/imap-client.h | 3 ++ + src/imap/test-imap-client-hibernate.c | 99 +++++++++++++++++++++++++++++++++++ + 4 files changed, 124 insertions(+), 4 deletions(-) + create mode 100644 src/imap/test-imap-client-hibernate.c + +--- a/src/imap/Makefile.am ++++ b/src/imap/Makefile.am +@@ -4,6 +4,7 @@ pkglibexec_PROGRAMS = imap + + AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ ++ -I$(top_srcdir)/src/lib-test \ + -I$(top_srcdir)/src/lib-settings \ + -I$(top_srcdir)/src/lib-dict \ + -I$(top_srcdir)/src/lib-master \ +@@ -67,7 +68,7 @@ cmds = \ + cmd-x-cancel.c \ + cmd-x-state.c + +-imap_SOURCES = \ ++common_sources = \ + $(cmds) \ + imap-client.c \ + imap-client-hibernate.c \ +@@ -86,7 +87,10 @@ imap_SOURCES = \ + imap-status.c \ + imap-state.c \ + imap-sync.c \ +- mail-storage-callbacks.c \ ++ mail-storage-callbacks.c ++ ++imap_SOURCES = \ ++ $(common_sources) \ + main.c + + headers = \ +@@ -110,3 +114,17 @@ headers = \ + + pkginc_libdir=$(pkgincludedir) + pkginc_lib_HEADERS = $(headers) ++ ++test_programs = \ ++ test-imap-client-hibernate ++noinst_PROGRAMS = $(test_programs) ++ ++test_imap_client_hibernate_SOURCES = \ ++ test-imap-client-hibernate.c $(common_sources) ++test_imap_client_hibernate_LDADD = $(imap_LDADD) ++test_imap_client_hibernate_DEPENDENCIES = $(imap_DEPENDENCIES) ++ ++check-local: ++ for bin in $(test_programs); do \ ++ if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ ++ done +--- a/src/imap/imap-client-hibernate.c ++++ b/src/imap/imap-client-hibernate.c +@@ -40,8 +40,8 @@ static int imap_hibernate_handshake(int + return -1; + } + +-static void imap_hibernate_write_cmd(struct client *client, string_t *cmd, +- const buffer_t *state, int fd_notify) ++void imap_hibernate_write_cmd(struct client *client, string_t *cmd, ++ const buffer_t *state, int fd_notify) + { + struct mail_user *user = client->user; + struct stat peer_st; +--- a/src/imap/imap-client.h ++++ b/src/imap/imap-client.h +@@ -343,6 +343,9 @@ void client_continue_pending_input(struc + void client_add_missing_io(struct client *client); + const char *client_stats(struct client *client); + ++void imap_hibernate_write_cmd(struct client *client, string_t *cmd, ++ const buffer_t *state, int fd_notify); ++ + void client_input(struct client *client); + bool client_handle_input(struct client *client); + int client_output(struct client *client); +--- /dev/null ++++ b/src/imap/test-imap-client-hibernate.c +@@ -0,0 +1,99 @@ ++/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */ ++ ++#include "lib.h" ++#include "test-common.h" ++#include "istream.h" ++#include "ostream.h" ++#include "str.h" ++#include "strescape.h" ++#include "mail-storage-private.h" ++#include "imap-common.h" ++#include "imap-client.h" ++ ++#define EVILSTR "\t\r\n\001" ++#define EVILSTR_ESCAPED "\001t\001r\001n\0011" ++ ++imap_client_created_func_t *hook_client_created = NULL; ++bool imap_debug = FALSE; ++ ++void imap_refresh_proctitle(void) { } ++int client_create_from_input(const struct mail_storage_service_input *input ATTR_UNUSED, ++ int fd_in ATTR_UNUSED, int fd_out ATTR_UNUSED, ++ struct client **client_r ATTR_UNUSED, ++ const char **error_r ATTR_UNUSED) { return -1; } ++ ++static void test_imap_client_hibernate(void) ++{ ++ buffer_t *state = buffer_create_dynamic(pool_datastack_create(), 0); ++ struct mail_user_settings mail_set = { ++ .mail_log_prefix = EVILSTR"%u", ++ }; ++ struct mail_user mail_user = { ++ .set = &mail_set, ++ .conn = { ++ .local_ip = t_new(struct ip_addr, 1), ++ .remote_ip = t_new(struct ip_addr, 1), ++ }, ++ .username = EVILSTR"testuser", ++ .session_create_time = 1234567, ++ .pool = pool_datastack_create(), ++ .uid = 4000, ++ .gid = 4001, ++ }; ++ struct imap_settings imap_set = { ++ .imap_idle_notify_interval = 120, ++ .imap_logout_format = "", ++ }; ++ struct client_command_context queue = { ++ .tag = EVILSTR"tag", ++ .name = "IDLE", ++ }; ++ struct client client = { ++ .user = &mail_user, ++ .set = &imap_set, ++ .fd_in = dev_null_fd, ++ .input = i_stream_create_from_data("", 0), ++ .output = o_stream_create_buffer(state), ++ .session_id = EVILSTR"session", ++ .command_queue = &queue, ++ }; ++ test_begin("imap client hibernate"); ++ test_assert(net_addr2ip("127.0.0.1", mail_user.conn.local_ip) == 0); ++ test_assert(net_addr2ip("127.0.0.2", mail_user.conn.remote_ip) == 0); ++ ++ string_t *cmd = t_str_new(256); ++ imap_hibernate_write_cmd(&client, cmd, state, -1); ++ ++ const char *const *args = t_strsplit(str_c(cmd), "\t"); ++ unsigned int i = 0; ++ test_assert_strcmp(args[i++], EVILSTR_ESCAPED"testuser"); ++ test_assert_strcmp(args[i++], EVILSTR_ESCAPED"%u"); ++ test_assert_strcmp(args[i++], "idle_notify_interval=120"); ++ test_assert(strncmp(args[i++], "peer_dev_major=", 15) == 0); ++ test_assert(strncmp(args[i++], "peer_dev_minor=", 15) == 0); ++ test_assert(strncmp(args[i++], "peer_ino=", 9) == 0); ++ test_assert_strcmp(args[i++], "session="EVILSTR_ESCAPED"session"); ++ test_assert_strcmp(args[i++], "session_created=1234567"); ++ test_assert_strcmp(args[i++], "lip=127.0.0.1"); ++ test_assert_strcmp(args[i++], "rip=127.0.0.2"); ++ test_assert_strcmp(args[i++], "uid=4000"); ++ test_assert_strcmp(args[i++], "gid=4001"); ++ test_assert_strcmp(args[i++], "tag="EVILSTR_ESCAPED"tag"); ++ test_assert(strncmp(args[i++], "stats=", 6) == 0); ++ test_assert_strcmp(args[i++], "idle-cmd"); ++ test_assert(strncmp(args[i++], "state=", 6) == 0); ++ test_assert(args[i] == NULL); ++ ++ i_stream_destroy(&client.input); ++ o_stream_destroy(&client.output); ++ test_end(); ++} ++ ++int main(void) ++{ ++ static void (*test_functions[])(void) = { ++ test_imap_client_hibernate, ++ NULL ++ }; ++ return test_run(test_functions); ++} diff -Nru dovecot-2.3.7.2/debian/patches/CVE-2020-25275-1.patch dovecot-2.3.7.2/debian/patches/CVE-2020-25275-1.patch --- dovecot-2.3.7.2/debian/patches/CVE-2020-25275-1.patch 1970-01-01 00:00:00.000000000 +0000 +++ dovecot-2.3.7.2/debian/patches/CVE-2020-25275-1.patch 2020-12-28 16:04:36.000000000 +0000 @@ -0,0 +1,122 @@ +From 850d95348a514b8b0682af900101509542c5f61d Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +Date: Fri, 11 Sep 2020 09:53:03 +0300 +Subject: [PATCH 1/2] lib-mail: message-parser - Fix assert-crash when + enforcing MIME part limit + +The limit could have been exceeded with message/rfc822 parts. +--- + src/lib-mail/message-parser.c | 3 +- + src/lib-mail/test-message-parser.c | 82 ++++++++++++++++++++++++++++++++++++++ + 2 files changed, 84 insertions(+), 1 deletion(-) + +--- a/src/lib-mail/message-parser.c ++++ b/src/lib-mail/message-parser.c +@@ -700,7 +700,8 @@ static int parse_next_header(struct mess + ctx->multipart = FALSE; + ctx->parse_next_block = parse_next_body_to_boundary; + } else if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0 && +- !parse_too_many_nested_mime_parts(ctx)) { ++ !parse_too_many_nested_mime_parts(ctx) && ++ ctx->total_parts_count < ctx->max_total_mime_parts) { + ctx->parse_next_block = parse_next_body_message_rfc822_init; + } else { + part->flags &= ~MESSAGE_PART_FLAG_MESSAGE_RFC822; +--- a/src/lib-mail/test-message-parser.c ++++ b/src/lib-mail/test-message-parser.c +@@ -1131,6 +1131,87 @@ static const char input_msg[] = + test_end(); + } + ++static void test_message_parser_mime_part_limit_rfc822(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: message/rfc822\n" ++"\n" ++"Content-Type: text/plain\n" ++"\n" ++"1\n" ++"--2\n" ++"Content-Type: message/rfc822\n" ++"\n" ++"Content-Type: text/plain\n" ++"\n" ++"22\n" ++"--1\n" ++"Content-Type: message/rfc822\n" ++"\n" ++"Content-Type: text/plain\n" ++"\n" ++"333\n"; ++ const struct message_parser_settings parser_set = { ++ .max_total_mime_parts = 3, ++ }; ++ 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 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 == 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 == 21); ++ test_assert(part->body_size.physical_size == 238); ++ test_assert(part->body_size.virtual_size == 238+21); ++ ++ part = parts->children; ++ test_assert(part->children_count == 1); ++ 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 == 18); ++ test_assert(part->body_size.physical_size == 189); ++ test_assert(part->body_size.virtual_size == 189+18); ++ ++ part = parts->children->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 == 15); ++ test_assert(part->body_size.physical_size == 155); ++ test_assert(part->body_size.virtual_size == 155+15); ++ ++ test_parsed_parts(input, parts); ++ i_stream_unref(&input); ++ pool_unref(&pool); ++ test_end(); ++} ++ + int main(void) + { + static void (*const test_functions[])(void) = { +@@ -1151,6 +1232,7 @@ int main(void) + test_message_parser_mime_part_nested_limit, + test_message_parser_mime_part_nested_limit_rfc822, + test_message_parser_mime_part_limit, ++ test_message_parser_mime_part_limit_rfc822, + NULL + }; + return test_run(test_functions); diff -Nru dovecot-2.3.7.2/debian/patches/CVE-2020-25275-2.patch dovecot-2.3.7.2/debian/patches/CVE-2020-25275-2.patch --- dovecot-2.3.7.2/debian/patches/CVE-2020-25275-2.patch 1970-01-01 00:00:00.000000000 +0000 +++ dovecot-2.3.7.2/debian/patches/CVE-2020-25275-2.patch 2020-12-28 16:04:38.000000000 +0000 @@ -0,0 +1,66 @@ +From 1c8bc16581a95061c7436ef979ba4d5c77004f14 Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +Date: Fri, 11 Sep 2020 10:57:51 +0300 +Subject: [PATCH 2/2] lib-imap: Don't generate invalid BODYSTRUCTURE when + reaching MIME part limit + +If the last MIME part was message/rfc822 and its child was truncated away, +BODYSTRUCTURE was missing the ENVELOPE and BODY[STRUCTURE] parts. Fixed by +writing empty dummy ones. +--- + src/lib-imap/imap-bodystructure.c | 29 +++++++++++++++++++++++++++-- + 1 file changed, 27 insertions(+), 2 deletions(-) + +diff --git a/src/lib-imap/imap-bodystructure.c b/src/lib-imap/imap-bodystructure.c +index f98b061b5a..60dfaa119f 100644 +--- a/src/lib-imap/imap-bodystructure.c ++++ b/src/lib-imap/imap-bodystructure.c +@@ -146,11 +146,25 @@ static void part_write_body(const struct message_part *part, + string_t *str, bool extended) + { + const struct message_part_data *data = part->data; +- bool text; ++ bool text, message_rfc822; + + i_assert(part->data != NULL); + +- if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0) { ++ if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0) ++ message_rfc822 = TRUE; ++ else if (data->content_type != NULL && ++ strcasecmp(data->content_type, "message") == 0 && ++ strcasecmp(data->content_subtype, "rfc822") == 0) { ++ /* It's message/rfc822, but without ++ MESSAGE_PART_FLAG_MESSAGE_RFC822. That likely means maximum ++ MIME part count was reached while parsing the mail. Write ++ the missing child mail's ENVELOPE and BODY as empty dummy ++ values. */ ++ message_rfc822 = TRUE; ++ } else ++ message_rfc822 = FALSE; ++ ++ if (message_rfc822) { + str_append(str, "\"message\" \"rfc822\""); + text = FALSE; + } else { +@@ -200,6 +214,17 @@ static void part_write_body(const struct message_part *part, + + part_write_bodystructure_siblings(part->children, str, extended); + str_printfa(str, " %u", part->body_size.lines); ++ } else if (message_rfc822) { ++ /* truncated MIME part - write out dummy values */ ++ i_assert(part->children == NULL); ++ ++ str_append(str, " (NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL) "); ++ ++ if (!extended) ++ str_append(str, EMPTY_BODY); ++ else ++ str_append(str, EMPTY_BODYSTRUCTURE); ++ str_printfa(str, " %u", part->body_size.lines); + } + + if (!extended) +-- +2.11.0 + diff -Nru dovecot-2.3.7.2/debian/patches/series dovecot-2.3.7.2/debian/patches/series --- dovecot-2.3.7.2/debian/patches/series 2020-08-05 14:26:17.000000000 +0000 +++ dovecot-2.3.7.2/debian/patches/series 2020-12-28 16:04:43.000000000 +0000 @@ -42,3 +42,7 @@ CVE-2020-12100/0001-lib-sieve-Adjust-to-message_parser_init-API-change.patch CVE-2020-12673/0002-lib-ntlm-Check-buffer-length-on-responses.patch CVE-2020-12674/0001-auth-mech-rpa-Fail-on-zero-len-buffer.patch +CVE-2020-24386-1.patch +CVE-2020-24386-2.patch +CVE-2020-25275-1.patch +CVE-2020-25275-2.patch