From fd9253fb280f321bf5c256449e3f129e801e4a1a Mon Sep 17 00:00:00 2001 From: Michal Hlavinka Date: Thu, 29 Aug 2024 18:44:36 +0200 Subject: [PATCH] fix CVE-2024-23185: very large headers can cause resource exhaustion when parsing message (RHEL-55218) fix CVE-2024-23184: using a large number of address headers may trigger a denial of service (RHEL-55205) Resolves: RHEL-55218 --- dovecot-2.3.21.1-CVE-2024-23184.patch | 976 ++++++++++++++++++++++++++ dovecot-2.3.21.1-CVE-2024-23185.patch | 493 +++++++++++++ dovecot.spec | 18 +- 3 files changed, 1486 insertions(+), 1 deletion(-) create mode 100644 dovecot-2.3.21.1-CVE-2024-23184.patch create mode 100644 dovecot-2.3.21.1-CVE-2024-23185.patch diff --git a/dovecot-2.3.21.1-CVE-2024-23184.patch b/dovecot-2.3.21.1-CVE-2024-23184.patch new file mode 100644 index 0000000..0de58c7 --- /dev/null +++ b/dovecot-2.3.21.1-CVE-2024-23184.patch @@ -0,0 +1,976 @@ +From 8e4c42dbb3c770fcdbc396f2abcf1bc228ec548d Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +Date: Fri, 9 Feb 2024 00:32:39 +0200 +Subject: [PATCH 1/6] lib: test-llist - Fix dllist2 test name + +--- + src/lib/test-llist.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/lib/test-llist.c b/src/lib/test-llist.c +index d57006ce2aa..ed584318fa3 100644 +--- a/src/lib/test-llist.c ++++ b/src/lib/test-llist.c +@@ -71,7 +71,7 @@ static void test_dllist2(void) + l2 = t_new(struct dllist, 1); + l1 = t_new(struct dllist, 1); + +- test_begin("dllist"); ++ test_begin("dllist2"); + /* prepend to empty */ + DLLIST2_PREPEND(&head, &tail, l3); + test_assert(head == l3 && tail == l3); + +From cee08202c759a3bdf185d998dcf888ebd1bc6e36 Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +Date: Fri, 9 Feb 2024 00:33:00 +0200 +Subject: [PATCH 2/6] lib: Add DLLIST2_JOIN() + +--- + src/lib/llist.h | 14 ++++++++++++++ + src/lib/test-llist.c | 39 +++++++++++++++++++++++++++++++++++++++ + 2 files changed, 53 insertions(+) + +diff --git a/src/lib/llist.h b/src/lib/llist.h +index 8a52e873352..5ad5d75c0df 100644 +--- a/src/lib/llist.h ++++ b/src/lib/llist.h +@@ -78,4 +78,18 @@ + #define DLLIST2_REMOVE(head, tail, item) \ + DLLIST2_REMOVE_FULL(head, tail, item, prev, next) + ++#define DLLIST2_JOIN_FULL(head1, tail1, head2, tail2, prev, next) STMT_START { \ ++ if (*(head1) == NULL) { \ ++ *(head1) = *(head2); \ ++ *(tail1) = *(tail2); \ ++ } else if (*(head2) != NULL) { \ ++ (*(tail1))->next = *(head2); \ ++ (*(head2))->prev = *(tail1); \ ++ (*tail1) = (*tail2); \ ++ } \ ++ } STMT_END ++ ++#define DLLIST2_JOIN(head1, tail1, head2, tail2) \ ++ DLLIST2_JOIN_FULL(head1, tail1, head2, tail2, prev, next) ++ + #endif +diff --git a/src/lib/test-llist.c b/src/lib/test-llist.c +index ed584318fa3..e293eb6a603 100644 +--- a/src/lib/test-llist.c ++++ b/src/lib/test-llist.c +@@ -131,8 +131,47 @@ static void test_dllist2(void) + test_end(); + } + ++static void test_dllist2_join(void) ++{ ++ struct dllist *head, *tail, *elem[4]; ++ struct dllist *head2, *tail2, *elem2[N_ELEMENTS(elem)]; ++ ++ test_begin("dllist2 join"); ++ for (unsigned int i = 0; i < N_ELEMENTS(elem); i++) { ++ elem[i] = t_new(struct dllist, 1); ++ elem2[i] = t_new(struct dllist, 1); ++ } ++ for (unsigned int i = 0; i < N_ELEMENTS(elem); i++) { ++ for (unsigned int j = 0; j < N_ELEMENTS(elem2); j++) { ++ head = tail = head2 = tail2 = NULL; ++ for (unsigned int n = 0; n < i; n++) ++ DLLIST2_APPEND(&head, &tail, elem[n]); ++ for (unsigned int n = 0; n < j; n++) ++ DLLIST2_APPEND(&head2, &tail2, elem2[n]); ++ DLLIST2_JOIN(&head, &tail, &head2, &tail2); ++ ++ /* verify */ ++ struct dllist *tmp = head, *last = NULL; ++ for (unsigned int n = 0; n < i; n++) { ++ test_assert(tmp == elem[n]); ++ last = tmp; ++ tmp = tmp->next; ++ } ++ for (unsigned int n = 0; n < j; n++) { ++ test_assert(tmp == elem2[n]); ++ last = tmp; ++ tmp = tmp->next; ++ } ++ test_assert(tmp == NULL); ++ test_assert(tail == last); ++ } ++ } ++ test_end(); ++} ++ + void test_llist(void) + { + test_dllist(); + test_dllist2(); ++ test_dllist2_join(); + } + +From 0bae091859c905dc335f21eed01347e6b8338672 Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +Date: Tue, 30 Jan 2024 22:42:50 +0200 +Subject: [PATCH 3/6] lib-mail: test-imap-envelope - Use test_assert_idx() + where possible + +--- + src/lib-imap/test-imap-envelope.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/src/lib-imap/test-imap-envelope.c b/src/lib-imap/test-imap-envelope.c +index 1f295e58bab..c9b92b4be2b 100644 +--- a/src/lib-imap/test-imap-envelope.c ++++ b/src/lib-imap/test-imap-envelope.c +@@ -157,7 +157,7 @@ static void test_imap_envelope_write(void) + envlp = msg_parse(pool, test->message); + + imap_envelope_write(envlp, str); +- test_assert(strcmp(str_c(str), test->envelope) == 0); ++ test_assert_idx(strcmp(str_c(str), test->envelope) == 0, i); + + pool_unref(&pool); + test_end(); +@@ -179,12 +179,12 @@ static void test_imap_envelope_parse(void) + test_begin(t_strdup_printf("imap envelope parser [%u]", i)); + + ret = imap_envelope_parse(test->envelope, pool, &envlp, &error); +- test_assert(ret); ++ test_assert_idx(ret, i); + + if (ret) { + str_truncate(str, 0); + imap_envelope_write(envlp, str); +- test_assert(strcmp(str_c(str), test->envelope) == 0); ++ test_assert_idx(strcmp(str_c(str), test->envelope) == 0, i); + } else { + i_error("Invalid envelope: %s", error); + } + +From a1c9b0409454e45937bf7e9c3685f5e91d6a5a43 Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +Date: Sun, 4 Feb 2024 00:26:57 +0200 +Subject: [PATCH 4/6] lib-mail: Change message_address to be doubly linked list + +--- + src/lib-imap/imap-envelope.c | 11 +- + src/lib-mail/message-address.c | 8 +- + src/lib-mail/message-address.h | 2 +- + src/lib-mail/test-message-address.c | 226 ++++++++++++++-------------- + 4 files changed, 121 insertions(+), 126 deletions(-) + +diff --git a/src/lib-imap/imap-envelope.c b/src/lib-imap/imap-envelope.c +index 87297f4f691..1312eae2ff3 100644 +--- a/src/lib-imap/imap-envelope.c ++++ b/src/lib-imap/imap-envelope.c +@@ -1,6 +1,7 @@ + /* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + + #include "lib.h" ++#include "llist.h" + #include "istream.h" + #include "str.h" + #include "message-address.h" +@@ -127,7 +128,7 @@ static bool + imap_envelope_parse_addresses(const struct imap_arg *arg, + pool_t pool, struct message_address **addrs_r) + { +- struct message_address *first, *addr, *prev; ++ struct message_address *first, *last, *addr; + const struct imap_arg *list_args; + + if (arg->type == IMAP_ARG_NIL) { +@@ -138,16 +139,12 @@ imap_envelope_parse_addresses(const struct imap_arg *arg, + if (!imap_arg_get_list(arg, &list_args)) + return FALSE; + +- first = addr = prev = NULL; ++ first = last = addr = NULL; + for (; !IMAP_ARG_IS_EOL(list_args); list_args++) { + if (!imap_envelope_parse_address + (list_args, pool, &addr)) + return FALSE; +- if (first == NULL) +- first = addr; +- if (prev != NULL) +- prev->next = addr; +- prev = addr; ++ DLLIST2_APPEND(&first, &last, addr); + } + + *addrs_r = first; +diff --git a/src/lib-mail/message-address.c b/src/lib-mail/message-address.c +index fb06afae7b7..9d192799468 100644 +--- a/src/lib-mail/message-address.c ++++ b/src/lib-mail/message-address.c +@@ -1,6 +1,7 @@ + /* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + + #include "lib.h" ++#include "llist.h" + #include "str.h" + #include "strescape.h" + #include "smtp-address.h" +@@ -27,11 +28,7 @@ static void add_address(struct message_address_parser_context *ctx) + memcpy(addr, &ctx->addr, sizeof(ctx->addr)); + i_zero(&ctx->addr); + +- if (ctx->first_addr == NULL) +- ctx->first_addr = addr; +- else +- ctx->last_addr->next = addr; +- ctx->last_addr = addr; ++ DLLIST2_APPEND(&ctx->first_addr, &ctx->last_addr, addr); + } + + /* quote with "" and escape all '\', '"' and "'" characters if need */ +@@ -631,6 +628,7 @@ const char *message_address_first_to_string(const struct message_address *addr) + struct message_address first_addr; + + first_addr = *addr; ++ first_addr.prev = NULL; + first_addr.next = NULL; + first_addr.route = NULL; + return message_address_to_string(&first_addr); +diff --git a/src/lib-mail/message-address.h b/src/lib-mail/message-address.h +index 8370397741c..85cff3dcc6f 100644 +--- a/src/lib-mail/message-address.h ++++ b/src/lib-mail/message-address.h +@@ -18,7 +18,7 @@ enum message_address_parse_flags { + {name = NULL, NULL, "group", NULL}, ..., {NULL, NULL, NULL, NULL} + */ + struct message_address { +- struct message_address *next; ++ struct message_address *prev, *next; + + /* display-name */ + const char *name; +diff --git a/src/lib-mail/test-message-address.c b/src/lib-mail/test-message-address.c +index e6204bb0588..261cbfba70a 100644 +--- a/src/lib-mail/test-message-address.c ++++ b/src/lib-mail/test-message-address.c +@@ -47,174 +47,174 @@ static void test_message_address(void) + } tests[] = { + /* user@domain -> */ + { "user@domain", "", NULL, +- { NULL, NULL, NULL, "user", "domain", FALSE }, +- { NULL, NULL, NULL, "user", "domain", FALSE }, 0 }, ++ { NULL, NULL, NULL, NULL, "user", "domain", FALSE }, ++ { NULL, NULL, NULL, NULL, "user", "domain", FALSE }, 0 }, + { "\"user\"@domain", "", NULL, +- { NULL, NULL, NULL, "user", "domain", FALSE }, +- { NULL, NULL, NULL, "user", "domain", FALSE }, 0 }, ++ { NULL, NULL, NULL, NULL, "user", "domain", FALSE }, ++ { NULL, NULL, NULL, NULL, "user", "domain", FALSE }, 0 }, + { "\"user name\"@domain", "<\"user name\"@domain>", NULL, +- { NULL, NULL, NULL, "user name", "domain", FALSE }, +- { NULL, NULL, NULL, "user name", "domain", FALSE }, 0 }, ++ { NULL, NULL, NULL, NULL, "user name", "domain", FALSE }, ++ { NULL, NULL, NULL, NULL, "user name", "domain", FALSE }, 0 }, + { "\"user@na\\\\me\"@domain", "<\"user@na\\\\me\"@domain>", NULL, +- { NULL, NULL, NULL, "user@na\\me", "domain", FALSE }, +- { NULL, NULL, NULL, "user@na\\me", "domain", FALSE }, 0 }, ++ { NULL, NULL, NULL, NULL, "user@na\\me", "domain", FALSE }, ++ { NULL, NULL, NULL, NULL, "user@na\\me", "domain", FALSE }, 0 }, + { "\"user\\\"name\"@domain", "<\"user\\\"name\"@domain>", NULL, +- { NULL, NULL, NULL, "user\"name", "domain", FALSE }, +- { NULL, NULL, NULL, "user\"name", "domain", FALSE }, 0 }, ++ { NULL, NULL, NULL, NULL, "user\"name", "domain", FALSE }, ++ { NULL, NULL, NULL, NULL, "user\"name", "domain", FALSE }, 0 }, + { "\"\"@domain", "<\"\"@domain>", NULL, +- { NULL, NULL, NULL, "", "domain", FALSE }, +- { NULL, NULL, NULL, "", "domain", FALSE }, 0 }, ++ { NULL, NULL, NULL, NULL, "", "domain", FALSE }, ++ { NULL, NULL, NULL, NULL, "", "domain", FALSE }, 0 }, + { "user", "", "", +- { NULL, NULL, NULL, "user", "", TRUE }, +- { NULL, NULL, NULL, "user", "MISSING_DOMAIN", TRUE }, 0 }, ++ { NULL, NULL, NULL, NULL, "user", "", TRUE }, ++ { NULL, NULL, NULL, NULL, "user", "MISSING_DOMAIN", TRUE }, 0 }, + { "@domain", "<\"\"@domain>", "", +- { NULL, NULL, NULL, "", "domain", TRUE }, +- { NULL, NULL, NULL, "MISSING_MAILBOX", "domain", TRUE }, 0 }, ++ { NULL, NULL, NULL, NULL, "", "domain", TRUE }, ++ { NULL, NULL, NULL, NULL, "MISSING_MAILBOX", "domain", TRUE }, 0 }, + + /* Display Name -> Display Name */ + { "Display Name", "\"Display Name\"", "\"Display Name\" ", +- { NULL, "Display Name", NULL, "", "", TRUE }, +- { NULL, "Display Name", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, ++ { NULL, NULL, "Display Name", NULL, "", "", TRUE }, ++ { NULL, NULL, "Display Name", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, + { "\"Display Name\"", "\"Display Name\"", "\"Display Name\" ", +- { NULL, "Display Name", NULL, "", "", TRUE }, +- { NULL, "Display Name", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, ++ { NULL, NULL, "Display Name", NULL, "", "", TRUE }, ++ { NULL, NULL, "Display Name", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, + { "Display \"Name\"", "\"Display Name\"", "\"Display Name\" ", +- { NULL, "Display Name", NULL, "", "", TRUE }, +- { NULL, "Display Name", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, ++ { NULL, NULL, "Display Name", NULL, "", "", TRUE }, ++ { NULL, NULL, "Display Name", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, + { "\"Display\" \"Name\"", "\"Display Name\"", "\"Display Name\" ", +- { NULL, "Display Name", NULL, "", "", TRUE }, +- { NULL, "Display Name", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, ++ { NULL, NULL, "Display Name", NULL, "", "", TRUE }, ++ { NULL, NULL, "Display Name", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, + { "\"\"", "", "", +- { NULL, "", NULL, "", "", TRUE }, +- { NULL, "", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, ++ { NULL, NULL, "", NULL, "", "", TRUE }, ++ { NULL, NULL, "", NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, + + /* -> */ + { "", NULL, NULL, +- { NULL, NULL, NULL, "user", "domain", FALSE }, +- { NULL, NULL, NULL, "user", "domain", FALSE }, 0 }, ++ { NULL, NULL, NULL, NULL, "user", "domain", FALSE }, ++ { NULL, NULL, NULL, NULL, "user", "domain", FALSE }, 0 }, + { "<\"user\"@domain>", "", NULL, +- { NULL, NULL, NULL, "user", "domain", FALSE }, +- { NULL, NULL, NULL, "user", "domain", FALSE }, 0 }, ++ { NULL, NULL, NULL, NULL, "user", "domain", FALSE }, ++ { NULL, NULL, NULL, NULL, "user", "domain", FALSE }, 0 }, + { "<\"user name\"@domain>", NULL, NULL, +- { NULL, NULL, NULL, "user name", "domain", FALSE }, +- { NULL, NULL, NULL, "user name", "domain", FALSE }, 0 }, ++ { NULL, NULL, NULL, NULL, "user name", "domain", FALSE }, ++ { NULL, NULL, NULL, NULL, "user name", "domain", FALSE }, 0 }, + { "<\"user@na\\\\me\"@domain>", NULL, NULL, +- { NULL, NULL, NULL, "user@na\\me", "domain", FALSE }, +- { NULL, NULL, NULL, "user@na\\me", "domain", FALSE }, 0 }, ++ { NULL, NULL, NULL, NULL, "user@na\\me", "domain", FALSE }, ++ { NULL, NULL, NULL, NULL, "user@na\\me", "domain", FALSE }, 0 }, + { "<\"user\\\"name\"@domain>", NULL, NULL, +- { NULL, NULL, NULL, "user\"name", "domain", FALSE }, +- { NULL, NULL, NULL, "user\"name", "domain", FALSE }, 0 }, ++ { NULL, NULL, NULL, NULL, "user\"name", "domain", FALSE }, ++ { NULL, NULL, NULL, NULL, "user\"name", "domain", FALSE }, 0 }, + { "<\"\"@domain>", NULL, NULL, +- { NULL, NULL, NULL, "", "domain", FALSE }, +- { NULL, NULL, NULL, "", "domain", FALSE }, 0 }, ++ { NULL, NULL, NULL, NULL, "", "domain", FALSE }, ++ { NULL, NULL, NULL, NULL, "", "domain", FALSE }, 0 }, + { "", NULL, "", +- { NULL, NULL, NULL, "user", "", TRUE }, +- { NULL, NULL, NULL, "user", "MISSING_DOMAIN", TRUE }, 0 }, ++ { NULL, NULL, NULL, NULL, "user", "", TRUE }, ++ { NULL, NULL, NULL, NULL, "user", "MISSING_DOMAIN", TRUE }, 0 }, + { "<@route>", "<@route:\"\">", "", +- { NULL, NULL, "@route", "", "", TRUE }, +- { NULL, NULL, "INVALID_ROUTE", "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, ++ { NULL, NULL, NULL, "@route", "", "", TRUE }, ++ { NULL, NULL, NULL, "INVALID_ROUTE", "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, + + /* user@domain (Display Name) -> "Display Name" */ + { "user@domain (DisplayName)", "DisplayName ", NULL, +- { NULL, "DisplayName", NULL, "user", "domain", FALSE }, +- { NULL, "DisplayName", NULL, "user", "domain", FALSE }, 0 }, ++ { NULL, NULL, "DisplayName", NULL, "user", "domain", FALSE }, ++ { NULL, NULL, "DisplayName", NULL, "user", "domain", FALSE }, 0 }, + { "user@domain (Display Name)", "\"Display Name\" ", NULL, +- { NULL, "Display Name", NULL, "user", "domain", FALSE }, +- { NULL, "Display Name", NULL, "user", "domain", FALSE }, 0 }, ++ { NULL, NULL, "Display Name", NULL, "user", "domain", FALSE }, ++ { NULL, NULL, "Display Name", NULL, "user", "domain", FALSE }, 0 }, + { "user@domain (Display\"Name)", "\"Display\\\"Name\" ", NULL, +- { NULL, "Display\"Name", NULL, "user", "domain", FALSE }, +- { NULL, "Display\"Name", NULL, "user", "domain", FALSE }, 0 }, ++ { NULL, NULL, "Display\"Name", NULL, "user", "domain", FALSE }, ++ { NULL, NULL, "Display\"Name", NULL, "user", "domain", FALSE }, 0 }, + { "user (Display Name)", "\"Display Name\" ", "\"Display Name\" ", +- { NULL, "Display Name", NULL, "user", "", TRUE }, +- { NULL, "Display Name", NULL, "user", "MISSING_DOMAIN", TRUE }, 0 }, ++ { NULL, NULL, "Display Name", NULL, "user", "", TRUE }, ++ { NULL, NULL, "Display Name", NULL, "user", "MISSING_DOMAIN", TRUE }, 0 }, + { "@domain (Display Name)", "\"Display Name\" <\"\"@domain>", "\"Display Name\" ", +- { NULL, "Display Name", NULL, "", "domain", TRUE }, +- { NULL, "Display Name", NULL, "MISSING_MAILBOX", "domain", TRUE }, 0 }, ++ { NULL, NULL, "Display Name", NULL, "", "domain", TRUE }, ++ { NULL, NULL, "Display Name", NULL, "MISSING_MAILBOX", "domain", TRUE }, 0 }, + { "user@domain ()", "", NULL, +- { NULL, NULL, NULL, "user", "domain", FALSE }, +- { NULL, NULL, NULL, "user", "domain", FALSE }, 0 }, ++ { NULL, NULL, NULL, NULL, "user", "domain", FALSE }, ++ { NULL, NULL, NULL, NULL, "user", "domain", FALSE }, 0 }, + + /* Display Name -> "Display Name" */ + { "DisplayName ", NULL, NULL, +- { NULL, "DisplayName", NULL, "user", "domain", FALSE }, +- { NULL, "DisplayName", NULL, "user", "domain", FALSE }, 0 }, ++ { NULL, NULL, "DisplayName", NULL, "user", "domain", FALSE }, ++ { NULL, NULL, "DisplayName", NULL, "user", "domain", FALSE }, 0 }, + { "Display Name ", "\"Display Name\" ", NULL, +- { NULL, "Display Name", NULL, "user", "domain", FALSE }, +- { NULL, "Display Name", NULL, "user", "domain", FALSE }, 0 }, ++ { NULL, NULL, "Display Name", NULL, "user", "domain", FALSE }, ++ { NULL, NULL, "Display Name", NULL, "user", "domain", FALSE }, 0 }, + { "\"Display Name\" ", NULL, NULL, +- { NULL, "Display Name", NULL, "user", "domain", FALSE }, +- { NULL, "Display Name", NULL, "user", "domain", FALSE }, 0 }, ++ { NULL, NULL, "Display Name", NULL, "user", "domain", FALSE }, ++ { NULL, NULL, "Display Name", NULL, "user", "domain", FALSE }, 0 }, + { "\"Display\\\"Name\" ", NULL, NULL, +- { NULL, "Display\"Name", NULL, "user", "domain", FALSE }, +- { NULL, "Display\"Name", NULL, "user", "domain", FALSE }, 0 }, ++ { NULL, NULL, "Display\"Name", NULL, "user", "domain", FALSE }, ++ { NULL, NULL, "Display\"Name", NULL, "user", "domain", FALSE }, 0 }, + { "Display Name ", "\"Display Name\" ", "\"Display Name\" ", +- { NULL, "Display Name", NULL, "user", "", TRUE }, +- { NULL, "Display Name", NULL, "user", "MISSING_DOMAIN", TRUE }, 0 }, ++ { NULL, NULL, "Display Name", NULL, "user", "", TRUE }, ++ { NULL, NULL, "Display Name", NULL, "user", "MISSING_DOMAIN", TRUE }, 0 }, + { "\"\" ", "", NULL, +- { NULL, NULL, NULL, "user", "domain", FALSE }, +- { NULL, NULL, NULL, "user", "domain", FALSE }, 0 }, ++ { NULL, NULL, NULL, NULL, "user", "domain", FALSE }, ++ { NULL, NULL, NULL, NULL, "user", "domain", FALSE }, 0 }, + + /* <@route:user@domain> -> <@route:user@domain> */ + { "<@route:user@domain>", NULL, NULL, +- { NULL, NULL, "@route", "user", "domain", FALSE }, +- { NULL, NULL, "@route", "user", "domain", FALSE }, 0 }, ++ { NULL, NULL, NULL, "@route", "user", "domain", FALSE }, ++ { NULL, NULL, NULL, "@route", "user", "domain", FALSE }, 0 }, + { "<@route,@route2:user@domain>", NULL, NULL, +- { NULL, NULL, "@route,@route2", "user", "domain", FALSE }, +- { NULL, NULL, "@route,@route2", "user", "domain", FALSE }, 0 }, ++ { NULL, NULL, NULL, "@route,@route2", "user", "domain", FALSE }, ++ { NULL, NULL, NULL, "@route,@route2", "user", "domain", FALSE }, 0 }, + { "<@route@route2:user@domain>", "<@route,@route2:user@domain>", NULL, +- { NULL, NULL, "@route,@route2", "user", "domain", FALSE }, +- { NULL, NULL, "@route,@route2", "user", "domain", FALSE }, 0 }, ++ { NULL, NULL, NULL, "@route,@route2", "user", "domain", FALSE }, ++ { NULL, NULL, NULL, "@route,@route2", "user", "domain", FALSE }, 0 }, + { "<@route@route2:user>", "<@route,@route2:user>", "<@route,@route2:user@MISSING_DOMAIN>", +- { NULL, NULL, "@route,@route2", "user", "", TRUE }, +- { NULL, NULL, "@route,@route2", "user", "MISSING_DOMAIN", TRUE }, 0 }, ++ { NULL, NULL, NULL, "@route,@route2", "user", "", TRUE }, ++ { NULL, NULL, NULL, "@route,@route2", "user", "MISSING_DOMAIN", TRUE }, 0 }, + { "<@route@route2:\"\"@domain>", "<@route,@route2:\"\"@domain>", NULL, +- { NULL, NULL, "@route,@route2", "", "domain", FALSE }, +- { NULL, NULL, "@route,@route2", "", "domain", FALSE }, 0 }, ++ { NULL, NULL, NULL, "@route,@route2", "", "domain", FALSE }, ++ { NULL, NULL, NULL, "@route,@route2", "", "domain", FALSE }, 0 }, + + /* Display Name <@route:user@domain> -> + "Display Name" <@route:user@domain> */ + { "Display Name <@route:user@domain>", "\"Display Name\" <@route:user@domain>", NULL, +- { NULL, "Display Name", "@route", "user", "domain", FALSE }, +- { NULL, "Display Name", "@route", "user", "domain", FALSE }, 0 }, ++ { NULL, NULL, "Display Name", "@route", "user", "domain", FALSE }, ++ { NULL, NULL, "Display Name", "@route", "user", "domain", FALSE }, 0 }, + { "Display Name <@route,@route2:user@domain>", "\"Display Name\" <@route,@route2:user@domain>", NULL, +- { NULL, "Display Name", "@route,@route2", "user", "domain", FALSE }, +- { NULL, "Display Name", "@route,@route2", "user", "domain", FALSE }, 0 }, ++ { NULL, NULL, "Display Name", "@route,@route2", "user", "domain", FALSE }, ++ { NULL, NULL, "Display Name", "@route,@route2", "user", "domain", FALSE }, 0 }, + { "Display Name <@route@route2:user@domain>", "\"Display Name\" <@route,@route2:user@domain>", NULL, +- { NULL, "Display Name", "@route,@route2", "user", "domain", FALSE }, +- { NULL, "Display Name", "@route,@route2", "user", "domain", FALSE }, 0 }, ++ { NULL, NULL, "Display Name", "@route,@route2", "user", "domain", FALSE }, ++ { NULL, NULL, "Display Name", "@route,@route2", "user", "domain", FALSE }, 0 }, + { "Display Name <@route@route2:user>", "\"Display Name\" <@route,@route2:user>", "\"Display Name\" <@route,@route2:user@MISSING_DOMAIN>", +- { NULL, "Display Name", "@route,@route2", "user", "", TRUE }, +- { NULL, "Display Name", "@route,@route2", "user", "MISSING_DOMAIN", TRUE }, 0 }, ++ { NULL, NULL, "Display Name", "@route,@route2", "user", "", TRUE }, ++ { NULL, NULL, "Display Name", "@route,@route2", "user", "MISSING_DOMAIN", TRUE }, 0 }, + { "Display Name <@route@route2:\"\"@domain>", "\"Display Name\" <@route,@route2:\"\"@domain>", NULL, +- { NULL, "Display Name", "@route,@route2", "", "domain", FALSE }, +- { NULL, "Display Name", "@route,@route2", "", "domain", FALSE }, 0 }, ++ { NULL, NULL, "Display Name", "@route,@route2", "", "domain", FALSE }, ++ { NULL, NULL, "Display Name", "@route,@route2", "", "domain", FALSE }, 0 }, + + /* other tests: */ + { "\"foo: ;,\" ", NULL, NULL, +- { NULL, "foo: ;,", NULL, "user", "domain", FALSE }, +- { NULL, "foo: ;,", NULL, "user", "domain", FALSE }, 0 }, ++ { NULL, NULL, "foo: ;,", NULL, "user", "domain", FALSE }, ++ { NULL, NULL, "foo: ;,", NULL, "user", "domain", FALSE }, 0 }, + { "<>", "", "", +- { NULL, NULL, NULL, "", "", TRUE }, +- { NULL, NULL, NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, ++ { NULL, NULL, NULL, NULL, "", "", TRUE }, ++ { NULL, NULL, NULL, NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, + { "<@>", "", "", +- { NULL, NULL, NULL, "", "", TRUE }, +- { NULL, NULL, "INVALID_ROUTE", "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, ++ { NULL, NULL, NULL, NULL, "", "", TRUE }, ++ { NULL, NULL, NULL, "INVALID_ROUTE", "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, 0 }, + + /* Test against a out-of-bounds read bug - keep these two tests + together in this same order: */ + { "aaaa@", "", "", +- { NULL, NULL, NULL, "aaaa", "", TRUE }, +- { NULL, NULL, NULL, "aaaa", "MISSING_DOMAIN", TRUE }, 0 }, ++ { NULL, NULL, NULL, NULL, "aaaa", "", TRUE }, ++ { NULL, NULL, NULL, NULL, "aaaa", "MISSING_DOMAIN", TRUE }, 0 }, + { "a(aa", "", "", +- { NULL, NULL, NULL, "", "", TRUE }, +- { NULL, NULL, NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, ++ { NULL, NULL, NULL, NULL, "", "", TRUE }, ++ { NULL, NULL, NULL, NULL, "MISSING_MAILBOX", "MISSING_DOMAIN", TRUE }, + TEST_MESSAGE_ADDRESS_FLAG_SKIP_LIST }, + }; + static struct message_address group_prefix = { +- NULL, NULL, NULL, "group", NULL, FALSE ++ NULL, NULL, NULL, NULL, "group", NULL, FALSE + }; + static struct message_address group_suffix = { +- NULL, NULL, NULL, NULL, NULL, FALSE ++ NULL, NULL, NULL, NULL, NULL, NULL, FALSE + }; + const struct message_address *addr; + string_t *str, *group; +@@ -327,7 +327,7 @@ static void test_message_address_nuls(void) + const unsigned char input[] = + "\"user\0nuls\\\0-esc\"@[domain\0nuls\\\0-esc] (comment\0nuls\\\0-esc)"; + const struct message_address output = { +- NULL, "comment\xEF\xBF\xBDnuls\\\xEF\xBF\xBD-esc", NULL, ++ NULL, NULL, "comment\xEF\xBF\xBDnuls\\\xEF\xBF\xBD-esc", NULL, + "user\xEF\xBF\xBDnuls\\\xEF\xBF\xBD-esc", + "[domain\xEF\xBF\xBDnuls\\\xEF\xBF\xBD-esc]", FALSE + }; +@@ -345,7 +345,7 @@ static void test_message_address_nuls_display_name(void) + const unsigned char input[] = + "\"displayname\0nuls\\\0-esc\" <\"user\0nuls\\\0-esc\"@[domain\0nuls\\\0-esc]>"; + const struct message_address output = { +- NULL, "displayname\xEF\xBF\xBDnuls\\\xEF\xBF\xBD-esc", NULL, ++ NULL, NULL, "displayname\xEF\xBF\xBDnuls\\\xEF\xBF\xBD-esc", NULL, + "user\xEF\xBF\xBDnuls\\\xEF\xBF\xBD-esc", + "[domain\xEF\xBF\xBDnuls\\\xEF\xBF\xBD-esc]", FALSE + }; +@@ -369,7 +369,7 @@ static void test_message_address_non_strict_dots(void) + }; + const struct message_address *addr; + struct message_address output = { +- NULL, NULL, NULL, "local-part", ++ NULL, NULL, NULL, NULL, "local-part", + "example.com", FALSE + }; + +@@ -421,29 +421,29 @@ static void test_message_address_path(void) + struct message_address addr; + } tests[] = { + { "<>", NULL, +- { NULL, NULL, NULL, NULL, NULL, FALSE } }, ++ { NULL, NULL, NULL, NULL, NULL, NULL, FALSE } }, + { " < > ", "<>", +- { NULL, NULL, NULL, NULL, NULL, FALSE } }, ++ { NULL, NULL, NULL, NULL, NULL, NULL, FALSE } }, + { "", NULL, +- { NULL, NULL, NULL, "user", "domain", FALSE } }, ++ { NULL, NULL, NULL, NULL, "user", "domain", FALSE } }, + { " ", "", +- { NULL, NULL, NULL, "user", "domain", FALSE } }, ++ { NULL, NULL, NULL, NULL, "user", "domain", FALSE } }, + { "user@domain", "", +- { NULL, NULL, NULL, "user", "domain", FALSE } }, ++ { NULL, NULL, NULL, NULL, "user", "domain", FALSE } }, + { " user@domain ", "", +- { NULL, NULL, NULL, "user", "domain", FALSE } }, ++ { NULL, NULL, NULL, NULL, "user", "domain", FALSE } }, + { "<\"user\"@domain>", "", +- { NULL, NULL, NULL, "user", "domain", FALSE } }, ++ { NULL, NULL, NULL, NULL, "user", "domain", FALSE } }, + { "<\"user name\"@domain>", NULL, +- { NULL, NULL, NULL, "user name", "domain", FALSE } }, ++ { NULL, NULL, NULL, NULL, "user name", "domain", FALSE } }, + { "<\"user@na\\\\me\"@domain>", NULL, +- { NULL, NULL, NULL, "user@na\\me", "domain", FALSE } }, ++ { NULL, NULL, NULL, NULL, "user@na\\me", "domain", FALSE } }, + { "<\"user\\\"name\"@domain>", NULL, +- { NULL, NULL, NULL, "user\"name", "domain", FALSE } }, ++ { NULL, NULL, NULL, NULL, "user\"name", "domain", FALSE } }, + { "<\"\"@domain>", NULL, +- { NULL, NULL, NULL, "", "domain", FALSE } }, ++ { NULL, NULL, NULL, NULL, "", "domain", FALSE } }, + { "<@source", "<>", +- { NULL, NULL, NULL, NULL, NULL, TRUE } }, ++ { NULL, NULL, NULL, NULL, NULL, NULL, TRUE } }, + }; + const struct message_address *addr; + string_t *str; + +From da61d20311da34f22944c6111a0b97ea2a1f8a47 Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +Date: Tue, 30 Jan 2024 22:17:38 +0200 +Subject: [PATCH 5/6] lib-mail: Add message_address_parse_full() and struct + message_address_list + +--- + src/lib-mail/message-address.c | 37 +++++++++++++--------- + src/lib-mail/message-address.h | 10 ++++++ + src/lib-mail/test-message-address.c | 48 +++++++++++++++++++++++++---- + 3 files changed, 75 insertions(+), 20 deletions(-) + +diff --git a/src/lib-mail/message-address.c b/src/lib-mail/message-address.c +index 9d192799468..ae37014079a 100644 +--- a/src/lib-mail/message-address.c ++++ b/src/lib-mail/message-address.c +@@ -13,7 +13,8 @@ struct message_address_parser_context { + pool_t pool; + struct rfc822_parser_context parser; + +- struct message_address *first_addr, *last_addr, addr; ++ struct message_address addr; ++ struct message_address_list addr_list; + string_t *str; + + bool fill_missing, non_strict_dots; +@@ -28,7 +29,7 @@ static void add_address(struct message_address_parser_context *ctx) + memcpy(addr, &ctx->addr, sizeof(ctx->addr)); + i_zero(&ctx->addr); + +- DLLIST2_APPEND(&ctx->first_addr, &ctx->last_addr, addr); ++ DLLIST2_APPEND(&ctx->addr_list.head, &ctx->addr_list.tail, addr); + } + + /* quote with "" and escape all '\', '"' and "'" characters if need */ +@@ -439,10 +440,11 @@ static int parse_path(struct message_address_parser_context *ctx) + return ret; + } + +-static struct message_address * ++static void + message_address_parse_real(pool_t pool, const unsigned char *data, size_t size, + unsigned int max_addresses, +- enum message_address_parse_flags flags) ++ enum message_address_parse_flags flags, ++ struct message_address_list *list_r) + { + struct message_address_parser_context ctx; + +@@ -461,7 +463,7 @@ message_address_parse_real(pool_t pool, const unsigned char *data, size_t size, + (void)parse_address_list(&ctx, max_addresses); + } + rfc822_parser_deinit(&ctx.parser); +- return ctx.first_addr; ++ *list_r = ctx.addr_list; + } + + static int +@@ -481,7 +483,7 @@ message_address_parse_path_real(pool_t pool, const unsigned char *data, + ret = parse_path(&ctx); + + rfc822_parser_deinit(&ctx.parser); +- *addr_r = ctx.first_addr; ++ *addr_r = ctx.addr_list.head; + return (ret < 0 ? -1 : 0); + } + +@@ -490,17 +492,24 @@ message_address_parse(pool_t pool, const unsigned char *data, size_t size, + unsigned int max_addresses, + enum message_address_parse_flags flags) + { +- struct message_address *addr; ++ struct message_address_list list; ++ message_address_parse_full(pool, data, size, max_addresses, flags, ++ &list); ++ return list.head; ++} + ++void message_address_parse_full(pool_t pool, const unsigned char *data, ++ size_t size, unsigned int max_addresses, ++ enum message_address_parse_flags flags, ++ struct message_address_list *list_r) ++{ + if (pool->datastack_pool) { +- return message_address_parse_real(pool, data, size, +- max_addresses, flags); +- } +- T_BEGIN { +- addr = message_address_parse_real(pool, data, size, +- max_addresses, flags); ++ message_address_parse_real(pool, data, size, ++ max_addresses, flags, list_r); ++ } else T_BEGIN { ++ message_address_parse_real(pool, data, size, ++ max_addresses, flags, list_r); + } T_END; +- return addr; + } + + int message_address_parse_path(pool_t pool, const unsigned char *data, +diff --git a/src/lib-mail/message-address.h b/src/lib-mail/message-address.h +index 85cff3dcc6f..224f7a75605 100644 +--- a/src/lib-mail/message-address.h ++++ b/src/lib-mail/message-address.h +@@ -31,12 +31,22 @@ struct message_address { + bool invalid_syntax; + }; + ++struct message_address_list { ++ struct message_address *head, *tail; ++}; ++ + /* Parse message addresses from given data. Note that giving an empty string + will return NULL since there are no addresses. */ + struct message_address * + message_address_parse(pool_t pool, const unsigned char *data, size_t size, + unsigned int max_addresses, + enum message_address_parse_flags flags); ++/* Same as message_address_parse(), but return message_address_list containing ++ both the first and the last address in the linked list. */ ++void message_address_parse_full(pool_t pool, const unsigned char *data, ++ size_t size, unsigned int max_addresses, ++ enum message_address_parse_flags flags, ++ struct message_address_list *list_r); + + /* Parse RFC 5322 "path" (Return-Path header) from given data. Returns -1 if + the path is invalid and 0 otherwise. +diff --git a/src/lib-mail/test-message-address.c b/src/lib-mail/test-message-address.c +index 261cbfba70a..54aa9a83101 100644 +--- a/src/lib-mail/test-message-address.c ++++ b/src/lib-mail/test-message-address.c +@@ -19,8 +19,9 @@ static bool cmp_addr(const struct message_address *a1, + a1->invalid_syntax == a2->invalid_syntax; + } + +-static const struct message_address * +-test_parse_address(const char *input, bool fill_missing) ++static void ++test_parse_address_full(const char *input, bool fill_missing, ++ struct message_address_list *list_r) + { + const enum message_address_parse_flags flags = + fill_missing ? MESSAGE_ADDRESS_PARSE_FLAG_FILL_MISSING : 0; +@@ -28,11 +29,18 @@ test_parse_address(const char *input, bool fill_missing) + if there's any out-of-bounds access */ + size_t input_len = strlen(input); + unsigned char *input_dup = i_memdup(input, input_len); +- const struct message_address *addr = +- message_address_parse(pool_datastack_create(), +- input_dup, input_len, UINT_MAX, flags); ++ message_address_parse_full(pool_datastack_create(), ++ input_dup, input_len, UINT_MAX, flags, ++ list_r); + i_free(input_dup); +- return addr; ++} ++ ++static const struct message_address * ++test_parse_address(const char *input, bool fill_missing) ++{ ++ struct message_address_list list; ++ test_parse_address_full(input, fill_missing, &list); ++ return list.head; + } + + static void test_message_address(void) +@@ -322,6 +330,33 @@ static void test_message_address(void) + test_end(); + } + ++static void test_message_address_list(void) ++{ ++ test_begin("message address list"); ++ ++ const char *test_input = ++ "user1@example1.com, user2@example2.com, user3@example3.com"; ++ const struct message_address wanted_addrs[] = { ++ { NULL, NULL, NULL, NULL, "user1", "example1.com", FALSE }, ++ { NULL, NULL, NULL, NULL, "user2", "example2.com", FALSE }, ++ { NULL, NULL, NULL, NULL, "user3", "example3.com", FALSE }, ++ }; ++ ++ struct message_address_list list; ++ struct message_address *addr, *scanned_last_addr; ++ test_parse_address_full(test_input, FALSE, &list); ++ addr = list.head; ++ for (unsigned int i = 0; i < N_ELEMENTS(wanted_addrs); i++) { ++ test_assert_idx(cmp_addr(addr, &wanted_addrs[i]), i); ++ scanned_last_addr = addr; ++ addr = addr->next; ++ } ++ test_assert(list.tail == scanned_last_addr); ++ test_assert(addr == NULL); ++ ++ test_end(); ++} ++ + static void test_message_address_nuls(void) + { + const unsigned char input[] = +@@ -521,6 +556,7 @@ int main(void) + { + static void (*const test_functions[])(void) = { + test_message_address, ++ test_message_address_list, + test_message_address_nuls, + test_message_address_nuls_display_name, + test_message_address_non_strict_dots, + +From 1481c04f02df7647f520df65d63df7626bf0ee32 Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +Date: Fri, 9 Feb 2024 00:57:12 +0200 +Subject: [PATCH 6/6] lib-mail, lib-imap: Optimize parsing large number of + address headers + +Every header was appended to a linked list by walking through the whole +list, causing excessive CPU usage when the list became large enough. +Fixed by changing struct message_part_envelope to use struct +message_address_list, which stores also linked list tail pointers. This +allows quickly appending to the end of the linked list. +--- + src/lib-imap/imap-envelope.c | 27 ++++++++++------------- + src/lib-mail/message-part-data.c | 17 +++++++------- + src/lib-mail/message-part-data.h | 6 +++-- + src/lib-storage/index/index-search-mime.c | 4 ++-- + 4 files changed, 27 insertions(+), 27 deletions(-) + +diff --git a/src/lib-imap/imap-envelope.c b/src/lib-imap/imap-envelope.c +index 1312eae2ff3..da3177025a5 100644 +--- a/src/lib-imap/imap-envelope.c ++++ b/src/lib-imap/imap-envelope.c +@@ -67,17 +67,17 @@ void imap_envelope_write(struct message_part_envelope *data, + } + + str_append_c(str, ' '); +- imap_write_address(str, data->from); ++ imap_write_address(str, data->from.head); + str_append_c(str, ' '); +- imap_write_address(str, NVL(data->sender, data->from)); ++ imap_write_address(str, NVL(data->sender.head, data->from.head)); + str_append_c(str, ' '); +- imap_write_address(str, NVL(data->reply_to, data->from)); ++ imap_write_address(str, NVL(data->reply_to.head, data->from.head)); + str_append_c(str, ' '); +- imap_write_address(str, data->to); ++ imap_write_address(str, data->to.head); + str_append_c(str, ' '); +- imap_write_address(str, data->cc); ++ imap_write_address(str, data->cc.head); + str_append_c(str, ' '); +- imap_write_address(str, data->bcc); ++ imap_write_address(str, data->bcc.head); + + str_append_c(str, ' '); + imap_append_nstring_nolf(str, data->in_reply_to); +@@ -126,28 +126,25 @@ imap_envelope_parse_address(const struct imap_arg *arg, + + static bool + imap_envelope_parse_addresses(const struct imap_arg *arg, +- pool_t pool, struct message_address **addrs_r) ++ pool_t pool, struct message_address_list *addrs_r) + { +- struct message_address *first, *last, *addr; ++ struct message_address *addr; + const struct imap_arg *list_args; + +- if (arg->type == IMAP_ARG_NIL) { +- *addrs_r = NULL; ++ i_zero(addrs_r); ++ if (arg->type == IMAP_ARG_NIL) + return TRUE; +- } + + if (!imap_arg_get_list(arg, &list_args)) + return FALSE; + +- first = last = addr = NULL; ++ addr = NULL; + for (; !IMAP_ARG_IS_EOL(list_args); list_args++) { + if (!imap_envelope_parse_address + (list_args, pool, &addr)) + return FALSE; +- DLLIST2_APPEND(&first, &last, addr); ++ DLLIST2_APPEND(&addrs_r->head, &addrs_r->tail, addr); + } +- +- *addrs_r = first; + return TRUE; + } + +diff --git a/src/lib-mail/message-part-data.c b/src/lib-mail/message-part-data.c +index a5771f87e2e..25019ab432d 100644 +--- a/src/lib-mail/message-part-data.c ++++ b/src/lib-mail/message-part-data.c +@@ -4,6 +4,7 @@ + #include "str.h" + #include "wildcard-match.h" + #include "array.h" ++#include "llist.h" + #include "rfc822-parser.h" + #include "rfc2231-parser.h" + #include "message-address.h" +@@ -176,7 +177,7 @@ void message_part_envelope_parse_from_header(pool_t pool, + { + struct message_part_envelope *d; + enum envelope_field field; +- struct message_address **addr_p, *addr; ++ struct message_address_list *addr_p, new_addr; + const char **str_p; + + if (*data == NULL) { +@@ -234,18 +235,18 @@ void message_part_envelope_parse_from_header(pool_t pool, + } + + if (addr_p != NULL) { +- addr = message_address_parse(pool, hdr->full_value, +- hdr->full_value_len, +- UINT_MAX, +- MESSAGE_ADDRESS_PARSE_FLAG_FILL_MISSING); ++ message_address_parse_full(pool, hdr->full_value, ++ hdr->full_value_len, ++ UINT_MAX, ++ MESSAGE_ADDRESS_PARSE_FLAG_FILL_MISSING, ++ &new_addr); + /* Merge multiple headers the same as if they were comma + separated in a single line. This is better from security + point of view, because attacker could intentionally write + addresses in a way that e.g. the first From header is + validated while MUA only shows the second From header. */ +- while (*addr_p != NULL) +- addr_p = &(*addr_p)->next; +- *addr_p = addr; ++ DLLIST2_JOIN(&addr_p->head, &addr_p->tail, ++ &new_addr.head, &new_addr.tail); + } else if (str_p != NULL) { + *str_p = message_header_strdup(pool, hdr->full_value, + hdr->full_value_len); +diff --git a/src/lib-mail/message-part-data.h b/src/lib-mail/message-part-data.h +index 5ff9ffe1bc6..7ec878de68e 100644 +--- a/src/lib-mail/message-part-data.h ++++ b/src/lib-mail/message-part-data.h +@@ -2,6 +2,7 @@ + #define MESSAGE_PART_DATA_H + + #include "message-part.h" ++#include "message-address.h" + + #define MESSAGE_PART_DEFAULT_CHARSET "us-ascii" + +@@ -14,8 +15,9 @@ struct message_part_param { + + struct message_part_envelope { + const char *date, *subject; +- struct message_address *from, *sender, *reply_to; +- struct message_address *to, *cc, *bcc; ++ ++ struct message_address_list from, sender, reply_to; ++ struct message_address_list to, cc, bcc; + + const char *in_reply_to, *message_id; + }; +diff --git a/src/lib-storage/index/index-search-mime.c b/src/lib-storage/index/index-search-mime.c +index da7e5e17092..3328ce98af1 100644 +--- a/src/lib-storage/index/index-search-mime.c ++++ b/src/lib-storage/index/index-search-mime.c +@@ -205,7 +205,7 @@ seach_arg_mime_envelope_address_match( + enum mail_search_mime_arg_type type, const char *key, + const struct message_part_envelope *envelope) + { +- const struct message_address *addrs; ++ struct message_address_list addrs; + string_t *addrs_enc; + + if (envelope == NULL) +@@ -239,7 +239,7 @@ seach_arg_mime_envelope_address_match( + probably be normalized directly in the struct message_address. */ + + addrs_enc = t_str_new(128); +- message_address_write(addrs_enc, addrs); ++ message_address_write(addrs_enc, addrs.head); + return (strstr(str_c(addrs_enc), key) != NULL ? 1 : 0); + } + diff --git a/dovecot-2.3.21.1-CVE-2024-23185.patch b/dovecot-2.3.21.1-CVE-2024-23185.patch new file mode 100644 index 0000000..794425a --- /dev/null +++ b/dovecot-2.3.21.1-CVE-2024-23185.patch @@ -0,0 +1,493 @@ +From f020e139c519121d9630a966310ea8e100ee33b7 Mon Sep 17 00:00:00 2001 +From: Marco Bettini +Date: Fri, 12 Apr 2024 15:06:43 +0000 +Subject: [PATCH 1/2] lib-mail: message-header-parser - Limit header block to + 10MB by default + +--- + src/lib-mail/message-header-parser.c | 48 ++++++++++++---- + src/lib-mail/message-header-parser.h | 10 ++++ + src/lib-mail/test-message-header-parser.c | 67 +++++++++++++++++++++++ + 3 files changed, 114 insertions(+), 11 deletions(-) + +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); + +From ce88c33abc37e408592eff70aeefa28f803effb9 Mon Sep 17 00:00:00 2001 +From: Marco Bettini +Date: Wed, 24 Apr 2024 10:45:46 +0000 +Subject: [PATCH 2/2] lib-mail: message-parser - Limit headers total count to + 50MB by default + +(including top headers and all mime-sections headers) +--- + src/lib-mail/message-parser-private.h | 2 + + src/lib-mail/message-parser.c | 15 +++ + src/lib-mail/message-parser.h | 6 + + src/lib-mail/test-message-parser.c | 154 ++++++++++++++++++++++++++ + 4 files changed, 177 insertions(+) + +diff --git a/src/lib-mail/message-parser-private.h b/src/lib-mail/message-parser-private.h +index 41c32daf3a..8b362a9e71 100644 +--- a/src/lib-mail/message-parser-private.h ++++ b/src/lib-mail/message-parser-private.h +@@ -30,6 +30,8 @@ struct message_parser_ctx { + enum message_parser_flags flags; + unsigned int max_nested_mime_parts; + unsigned int max_total_mime_parts; ++ size_t all_headers_max_size; ++ size_t all_headers_total_size; + + char *last_boundary; + struct message_boundary *boundaries; +diff --git a/src/lib-mail/message-parser.c b/src/lib-mail/message-parser.c +index 9a9c9a3515..c7e3b1e96a 100644 +--- a/src/lib-mail/message-parser.c ++++ b/src/lib-mail/message-parser.c +@@ -617,7 +617,18 @@ static int parse_next_header(struct message_parser_ctx *ctx, + } + if (ret < 0) { + /* no boundary */ ++ size_t headers_available = ++ ctx->all_headers_max_size > ctx->all_headers_total_size ? ++ ctx->all_headers_max_size - ctx->all_headers_total_size : 0; ++ message_parse_header_lower_limit(ctx->hdr_parser_ctx, headers_available); + ret = message_parse_header_next(ctx->hdr_parser_ctx, &hdr); ++ if (ret > 0) { ++ if (!hdr->continues) { ++ ctx->all_headers_total_size += hdr->name_len; ++ ctx->all_headers_total_size += hdr->middle_len; ++ } ++ ctx->all_headers_total_size += hdr->value_len; ++ } + if (ret == 0 || (ret < 0 && ctx->input->stream_errno != 0)) { + ctx->want_count = i_stream_get_data_size(ctx->input) + 1; + return ret; +@@ -762,6 +773,9 @@ message_parser_init_int(struct istream *input, + ctx->max_total_mime_parts = set->max_total_mime_parts != 0 ? + set->max_total_mime_parts : + MESSAGE_PARSER_DEFAULT_MAX_TOTAL_MIME_PARTS; ++ ctx->all_headers_max_size = set->all_headers_max_size != 0 ? ++ set->all_headers_max_size : ++ MESSAGE_PARSER_DEFAULT_ALL_HEADERS_MAX_SIZE; + ctx->input = input; + i_stream_ref(input); + return ctx; +@@ -779,6 +793,7 @@ message_parser_init(pool_t part_pool, struct istream *input, + ctx->next_part = &ctx->part->children; + ctx->parse_next_block = parse_next_header_init; + ctx->total_parts_count = 1; ++ ctx->all_headers_total_size = 0; + i_array_init(&ctx->next_part_stack, 4); + return ctx; + } +diff --git a/src/lib-mail/message-parser.h b/src/lib-mail/message-parser.h +index f19e526284..8d70d73f05 100644 +--- a/src/lib-mail/message-parser.h ++++ b/src/lib-mail/message-parser.h +@@ -19,6 +19,7 @@ enum message_parser_flags { + + #define MESSAGE_PARSER_DEFAULT_MAX_NESTED_MIME_PARTS 100 + #define MESSAGE_PARSER_DEFAULT_MAX_TOTAL_MIME_PARTS 10000 ++#define MESSAGE_PARSER_DEFAULT_ALL_HEADERS_MAX_SIZE ((size_t) 50 * 1024*1024) + + struct message_parser_settings { + enum message_header_parser_flags hdr_flags; +@@ -30,6 +31,11 @@ struct message_parser_settings { + /* Maximum MIME parts in total. + 0 = MESSAGE_PARSER_DEFAULT_MAX_TOTAL_MIME_PARTS. */ + unsigned int max_total_mime_parts; ++ ++ /* Maximum bytes fore headers in top header plus all ++ MIME sections headers ++ 0 = MESSAGE_PARSER_DEFAULT_ALL_HEADERS_MAX_SIZE */ ++ size_t all_headers_max_size; + }; + + struct message_parser_ctx; +diff --git a/src/lib-mail/test-message-parser.c b/src/lib-mail/test-message-parser.c +index 663bfe8c5a..b6bada2303 100644 +--- a/src/lib-mail/test-message-parser.c ++++ b/src/lib-mail/test-message-parser.c +@@ -1369,6 +1369,158 @@ static const char input_msg[] = + test_end(); + } + ++#define test_assert_virtual_size(part) \ ++ test_assert((part).virtual_size == (part).lines + (part).physical_size) ++ ++#define test_assert_part(part, flags_, children, h_lines, h_size, b_lines, b_size ) \ ++STMT_START { \ ++ test_assert((part)->flags == (flags_)); \ ++ test_assert((part)->children_count == children); \ ++ test_assert((part)->header_size.lines == h_lines); \ ++ test_assert((part)->header_size.physical_size == h_size); \ ++ test_assert((part)->body_size.lines == b_lines); \ ++ test_assert((part)->body_size.physical_size == b_size); \ ++ test_assert_virtual_size((part)->header_size); \ ++ test_assert_virtual_size((part)->body_size); \ ++} STMT_END ++ ++static const enum message_part_flags FLAGS_MULTIPART = ++ MESSAGE_PART_FLAG_IS_MIME | MESSAGE_PART_FLAG_MULTIPART; ++static const enum message_part_flags FLAGS_RFC822 = ++ MESSAGE_PART_FLAG_IS_MIME | MESSAGE_PART_FLAG_MESSAGE_RFC822; ++static const enum message_part_flags FLAGS_TEXT = ++ MESSAGE_PART_FLAG_IS_MIME | MESSAGE_PART_FLAG_TEXT; ++ ++static const char too_many_header_bytes_input_msg[] = ++ "Content-Type: multipart/mixed; boundary=\"1\"\n\n" ++ "--1\n" ++ "Content-Type: multipart/mixed; boundary=\"2\"\n\n" ++ "--2\n" ++ "Content-Type: message/rfc822\n\n" ++ "Content-Type: text/plain\n\n1-rfc822\n" ++ "--2\n" ++ "Content-Type: message/rfc822\n\n" ++ "Content-Type: text/plain\n\n2-rfc822\n" ++ "--1\n" ++ "Content-Type: message/rfc822\n\n" ++ "Content-Type: text/plain\n\n3-rfc822\n"; ++ ++static void test_message_parser_too_many_header_bytes_run( ++ const struct message_parser_settings *parser_set, ++ pool_t *pool_r, struct istream **input_r, ++ struct message_part **parts_r) ++{ ++ *pool_r = pool_alloconly_create("message parser", 10240); ++ *input_r = test_istream_create(too_many_header_bytes_input_msg); ++ struct message_parser_ctx *parser = message_parser_init(*pool_r, *input_r, parser_set); ++ ++ int ret; ++ struct message_block block ATTR_UNUSED; ++ while ((ret = message_parser_parse_next_block(parser, &block)) > 0); ++ test_assert(ret < 0); ++ ++ message_parser_deinit(&parser, parts_r); ++} ++ ++static void test_message_parser_too_many_header_bytes_default(void) ++{ ++ test_begin("message parser too many header bytes default"); ++ ++ pool_t pool; ++ struct istream *input; ++ struct message_part *part_root; ++ const struct message_parser_settings parser_set = { .all_headers_max_size = 0 }; ++ ++ test_message_parser_too_many_header_bytes_run(&parser_set, &pool, &input, &part_root); ++ ++ // test_assert_part(part, flags_, children, h_lines, h_size, b_lines, b_size ) ++ ++ test_assert_part(part_root, FLAGS_MULTIPART, 7, 2, 45, 21, 256); ++ test_assert(part_root->parent == NULL); ++ ++ struct message_part *part_1 = part_root->children; ++ test_assert_part(part_1, FLAGS_MULTIPART, 4, 2, 45, 11, 137); ++ ++ struct message_part *part_1_1 = part_1->children; ++ test_assert_part(part_1_1, FLAGS_RFC822, 1, 2, 30, 2, 34); ++ ++ struct message_part *part_1_1_1 = part_1_1->children; ++ test_assert_part(part_1_1_1, FLAGS_TEXT, 0, 2, 26, 0, 8); ++ ++ test_assert(part_1_1_1->next == NULL); ++ ++ struct message_part *part_1_2 = part_1_1->next; ++ test_assert_part(part_1_2, FLAGS_RFC822, 1, 2, 30, 2, 34); ++ ++ struct message_part *part_1_2_1 = part_1_2->children; ++ test_assert_part(part_1_2_1, FLAGS_TEXT, 0, 2, 26, 0, 8); ++ ++ test_assert(part_1_2_1->next == NULL); ++ ++ test_assert(part_1_2->next == NULL); ++ ++ struct message_part *part_2 = part_1->next; ++ test_assert_part(part_2, FLAGS_RFC822, 1, 2, 30, 3, 35); ++ ++ struct message_part *part_2_1 = part_2->children; ++ test_assert_part(part_2_1, FLAGS_TEXT, 0, 2, 26, 1, 9); ++ test_assert(part_2_1->next == NULL); ++ ++ test_assert(part_2->next == NULL); ++ ++ test_assert(part_root->next == NULL); ++ ++ test_parsed_parts(input, part_root); ++ i_stream_unref(&input); ++ pool_unref(&pool); ++ test_end(); ++} ++ ++static void test_message_parser_too_many_header_bytes_100(void) ++{ ++ test_begin("message parser too many header bytes 100"); ++ ++ pool_t pool; ++ struct istream *input; ++ struct message_part *part_root; ++ const struct message_parser_settings parser_set = { .all_headers_max_size = 100 }; ++ ++ test_message_parser_too_many_header_bytes_run(&parser_set, &pool, &input, &part_root); ++ ++ // test_assert_part(part, flags_, children, h_lines, h_size, b_lines, b_size ) ++ ++ test_assert_part(part_root, FLAGS_MULTIPART, 5, 2, 45, 21, 256); ++ test_assert(part_root->parent == NULL); ++ ++ struct message_part *part_1 = part_root->children; ++ test_assert_part(part_1, FLAGS_MULTIPART, 3, 2, 45, 11, 137); ++ ++ struct message_part *part_1_1 = part_1->children; ++ test_assert_part(part_1_1, FLAGS_RFC822, 1, 2, 30, 2, 34); ++ ++ struct message_part *part_1_1_1 = part_1_1->children; ++ test_assert_part(part_1_1_1, MESSAGE_PART_FLAG_IS_MIME, 0, 2, 26, 0, 8); ++ ++ test_assert(part_1_1_1->next == NULL); ++ ++ struct message_part *part_1_2 = part_1_1->next; ++ test_assert_part(part_1_2, MESSAGE_PART_FLAG_IS_MIME, 0, 2, 30, 2, 34); ++ ++ test_assert(part_1_2->next == NULL); ++ ++ struct message_part *part_2 = part_1->next; ++ test_assert_part(part_2, MESSAGE_PART_FLAG_IS_MIME, 0, 2, 30, 3, 35); ++ ++ test_assert(part_2->next == NULL); ++ ++ test_assert(part_root->next == NULL); ++ ++ test_parsed_parts(input, part_root); ++ i_stream_unref(&input); ++ pool_unref(&pool); ++ test_end(); ++} ++ + int main(void) + { + static void (*const test_functions[])(void) = { +@@ -1392,6 +1544,8 @@ int main(void) + test_message_parser_mime_part_limit_rfc822, + test_message_parser_mime_version, + test_message_parser_mime_version_missing, ++ test_message_parser_too_many_header_bytes_default, ++ test_message_parser_too_many_header_bytes_100, + NULL + }; + return test_run(test_functions); diff --git a/dovecot.spec b/dovecot.spec index 6ccdb52..af577ad 100644 --- a/dovecot.spec +++ b/dovecot.spec @@ -6,7 +6,7 @@ Name: dovecot Epoch: 1 Version: 2.3.21 %global prever %{nil} -Release: 13%{?dist} +Release: 14%{?dist} #dovecot itself is MIT, a few sources are PD, pigeonhole is LGPLv2 License: MIT AND LGPL-2.1-only @@ -62,6 +62,14 @@ Patch26: dovecot-2.3-ph_scriptcmp.patch # fix test failing due to too long path with all the mock path prefixes Patch27: dovecot-2.3.21-test-socket-path.patch +# from upstream for < 2.3.21.1, RHEL-55205 +# https://github.com/dovecot/core/compare/8e4c42d%5E...1481c04.patch +Patch28: dovecot-2.3.21.1-CVE-2024-23184.patch + +# from upstream for < 2.3.21.1, RHEL-55218 +# https://github.com/dovecot/core/compare/f020e13%5E...ce88c33.patch +Patch29: dovecot-2.3.21.1-CVE-2024-23185.patch + BuildRequires: gcc, gcc-c++, openssl-devel, pam-devel, zlib-devel, bzip2-devel, libcap-devel BuildRequires: libtool, autoconf, automake, pkgconfig BuildRequires: sqlite-devel @@ -166,6 +174,8 @@ mv dovecot-2.3-pigeonhole-%{pigeonholever} dovecot-pigeonhole %patch -P 25 -p1 -b .ph_optglob %patch -P 26 -p1 -b .ph_scriptcmp %patch -P 27 -p1 -b .test-socket-path +%patch -P 28 -p1 -b .CVE-2024-23184 +%patch -P 29 -p1 -b .CVE-2024-23185 cp run-test-valgrind.supp dovecot-pigeonhole/ # valgrind would fail with shell wrapper echo "testsuite" >dovecot-pigeonhole/run-test-valgrind.exclude @@ -526,6 +536,12 @@ make check %{_libdir}/%{name}/dict/libdriver_pgsql.so %changelog +* Tue Aug 20 2024 Michal Hlavinka - 1:2.3.21-14 +- fix CVE-2024-23185: very large headers can cause resource exhaustion + when parsing message (RHEL-55218) +- fix CVE-2024-23184: using a large number of address headers may trigger + a denial of service (RHEL-55205) + * Mon Aug 05 2024 Michal Hlavinka - 1:2.3.21-13 - fix crash when user has sieve script that includes two missing scripts (RHEL-52541)