On Thu, May 14, 2026 at 03:08:10PM -0400, Noah Meyerhans wrote: > Upstream has fixed these issues with 2.4.4, which I am preparing for > upload now. Changes at > https://salsa.debian.org/noahm/dovecot/-/commits/master
2.4.4 is in unstable now. > These issues also impact trixie and most likely bookworm in at least > some cases. Bookworm seems to be impacted by all except CVE-2026-27851, but I still need to look deeper. > I have a targeted fix for trixie staged at > https://salsa.debian.org/noahm/dovecot/-/commits/trixie-security-wip. > I'd love some additional eyes on it. Debdiff for this is attached. Given that some of these issues are due to incomplete or otherwise incorrect fixes to the previous round of CVEs, for which we did release a DSA, my inclination is to publish one for these fixes as well. But let's wait until I have completed the bookworm investigation/backport. noah
diff -Nru dovecot-2.4.1+dfsg1/debian/changelog dovecot-2.4.1+dfsg1/debian/changelog --- dovecot-2.4.1+dfsg1/debian/changelog 2026-05-06 15:18:43.000000000 -0400 +++ dovecot-2.4.1+dfsg1/debian/changelog 2026-05-12 21:50:03.000000000 -0400 @@ -1,3 +1,16 @@ +dovecot (1:2.4.1+dfsg1-6+deb13u6) trixie-security; urgency=medium + + * [76ceed4] CVE-2026-27851: lib-var-expand: Reset safe state when + transfer is unset + * [4af6fb3] CVE-2026-40016: lib-sieve: Enforce CPU time limit within + :contains and :matches matcher loops + * [366ef61] CVE-2026-33603: login-common: Only accept base64 in sasl + * [26bd41e] CVE-2026-40020: IMAP folders can be shared-spammed to + everyone. + * [b6f5bac] CVE-2026-42006: imap-login: Excessive memory usage DoS + + -- Noah Meyerhans <[email protected]> Tue, 12 May 2026 21:50:03 -0400 + dovecot (1:2.4.1+dfsg1-6+deb13u5) trixie; urgency=medium * [b357180] autopkgtests: Add managesieved authentication test diff -Nru dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-27851.patch dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-27851.patch --- dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-27851.patch 1969-12-31 19:00:00.000000000 -0500 +++ dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-27851.patch 2026-05-12 21:50:03.000000000 -0400 @@ -0,0 +1,46 @@ +From d75c04e8ccbeb70d66d05938665fe145175ac1b5 Mon Sep 17 00:00:00 2001 +From: Aki Tuomi <[email protected]> +Date: Sun, 29 Mar 2026 19:33:45 +0300 +Subject: [PATCH] lib-var-expand: Reset safe state when transfer is unset + +Otherwise unsafe content is treated safe. +--- + src/lib-var-expand/test-var-expand.c | 5 ++++- + src/lib-var-expand/var-expand.c | 1 + + 2 files changed, 5 insertions(+), 1 deletion(-) + +Index: dovecot/src/lib-var-expand/test-var-expand.c +=================================================================== +--- dovecot.orig/src/lib-var-expand/test-var-expand.c ++++ dovecot/src/lib-var-expand/test-var-expand.c +@@ -611,6 +611,7 @@ static void test_var_expand_escape(void) + { .key = "escape", .value = "'hello' \"world\"", }, + { .key = "first", .value = "bobby" }, + { .key = "nasty", .value = "\';-- SELECT * FROM bobby.tables" }, ++ { .key = "feisty", .value = "' OR '1'='1" }, + VAR_EXPAND_TABLE_END + }; + +@@ -653,6 +654,10 @@ static void test_var_expand_escape(void) + { .in = "%{literal(\"\\\"\\\\hello\\\\world\\\"\")}", .out = "'\"\\hello\\world\"'", .ret = 0 }, + /* Unsupported escape sequence */ + { .in = "%{literal('\\z')}", .out = "Invalid character escape", .ret = -1 }, ++ ++ /* safe filter */ ++ { .in = "%{feisty}", "'\\' OR \\'1\\'=\\'1'", .ret = 0 }, ++ { .in = "%{clean|safe} and %{feisty}", "hello world and '\\' OR \\'1\\'=\\'1'", .ret = 0 }, + }; + + const struct var_expand_params params = { +Index: dovecot/src/lib-var-expand/var-expand.c +=================================================================== +--- dovecot.orig/src/lib-var-expand/var-expand.c ++++ dovecot/src/lib-var-expand/var-expand.c +@@ -338,6 +338,7 @@ void var_expand_state_set_transfer(struc + void var_expand_state_unset_transfer(struct var_expand_state *state) + { + str_truncate(state->transfer, 0); ++ state->transfer_safe = FALSE; + state->transfer_set = FALSE; + } + diff -Nru dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-33603.patch dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-33603.patch --- dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-33603.patch 1969-12-31 19:00:00.000000000 -0500 +++ dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-33603.patch 2026-05-12 21:50:03.000000000 -0400 @@ -0,0 +1,36 @@ +From c1c53885bda550632b944dd305013cd010e0e058 Mon Sep 17 00:00:00 2001 +From: Aki Tuomi <[email protected]> +Date: Wed, 8 Apr 2026 11:33:11 +0300 +Subject: [PATCH] login-common: Only accept base64 in sasl + +--- + src/login-common/client-common-auth.c | 9 +++++++++ + 1 file changed, 9 insertions(+) + +Index: dovecot/src/login-common/client-common-auth.c +=================================================================== +--- dovecot.orig/src/login-common/client-common-auth.c ++++ dovecot/src/login-common/client-common-auth.c +@@ -3,6 +3,7 @@ + #include "hostpid.h" + #include "login-common.h" + #include "array.h" ++#include "base64.h" + #include "iostream.h" + #include "istream.h" + #include "ostream.h" +@@ -865,6 +866,14 @@ void client_auth_respond(struct client * + return; + } + ++ /* Only accept base64 */ ++ for (size_t i = 0; response[i] != '\0'; i++) { ++ if (!base64_is_valid_char(response[i]) && response[i] != '=') { ++ client_auth_fail(client, "Invalid base64 in response"); ++ return; ++ } ++ } ++ + client->auth_client_continue_pending = FALSE; + client_set_auth_waiting(client); + sasl_server_auth_continue(client, response); diff -Nru dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-40016.patch dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-40016.patch --- dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-40016.patch 1969-12-31 19:00:00.000000000 -0500 +++ dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-40016.patch 2026-05-12 21:50:03.000000000 -0400 @@ -0,0 +1,255 @@ +From 5b0ed9d1034c023d3daf218b6b8656f0cdd383dc Mon Sep 17 00:00:00 2001 +From: Timo Sirainen <[email protected]> +Date: Sun, 19 Apr 2026 23:10:29 +0000 +Subject: [PATCH] lib-sieve: Enforce CPU time limit within :contains and + :matches matcher loops + +The naive O(N*M) substring search in mcht-contains.c and the naive find loop +in mcht-matches.c can run for hours on a large value (e.g. a message body), +completely bypassing sieve_max_cpu_time because that limit was only checked +between bytecode operations. + +Expose the active CPU limit via sieve_runtime_cpu_limit_exceeded() and poll +it every 4096 inner iterations. When the limit is hit the match returns +SIEVE_EXEC_RESOURCE_LIMIT, matching the existing behavior at the bytecode +boundary. This is a minimal safety net ahead of switching the matchers to +algorithms that do not require it. +--- + src/lib-sieve/mcht-contains.c | 21 ++++++++++++ + src/lib-sieve/mcht-matches.c | 57 +++++++++++++++++++++++++++---- + src/lib-sieve/sieve-interpreter.c | 19 +++++++++++ + src/lib-sieve/sieve-interpreter.h | 12 +++++++ + 4 files changed, 103 insertions(+), 6 deletions(-) + +Index: dovecot/pigeonhole/src/lib-sieve/mcht-contains.c +=================================================================== +--- dovecot.orig/pigeonhole/src/lib-sieve/mcht-contains.c ++++ dovecot/pigeonhole/src/lib-sieve/mcht-contains.c +@@ -8,6 +8,7 @@ + + #include "sieve-match-types.h" + #include "sieve-comparators.h" ++#include "sieve-interpreter.h" + #include "sieve-match.h" + + #include <string.h> +@@ -38,7 +39,14 @@ const struct sieve_match_type_def contai + + /* FIXME: Naive substring match implementation. Should switch to more + * efficient algorithm if large values need to be searched (e.g. message body). ++ * ++ * The inner loop polls the interpreter CPU time limit periodically so that a ++ * single O(N*M) match on a large value cannot run for many times the ++ * configured sieve_max_cpu_time (which is otherwise only checked between ++ * bytecode operations). + */ ++#define SIEVE_CONTAINS_CPU_CHECK_INTERVAL 4096 ++ + static int mcht_contains_match_key + (struct sieve_match_context *mctx, const char *val, size_t val_size, + const char *key, size_t key_size) +@@ -48,6 +56,7 @@ static int mcht_contains_match_key + const char *kend = (const char *) key + key_size; + const char *vp = val; + const char *kp = key; ++ unsigned int counter = 0; + + if ( val_size == 0 ) + return ( key_size == 0 ? 1 : 0 ); +@@ -58,6 +67,18 @@ static int mcht_contains_match_key + while ( (vp < vend) && (kp < kend) ) { + if ( !cmp->def->char_match(cmp, &vp, vend, &kp, kend) ) + vp++; ++ ++ if ( ++counter >= SIEVE_CONTAINS_CPU_CHECK_INTERVAL ) { ++ counter = 0; ++ if ( sieve_runtime_cpu_limit_exceeded(mctx->runenv) ) { ++ sieve_runtime_error( ++ mctx->runenv, NULL, ++ "execution exceeded CPU time limit"); ++ mctx->exec_status = ++ SIEVE_EXEC_RESOURCE_LIMIT; ++ return -1; ++ } ++ } + } + + return ( kp == kend ? 1 : 0 ); +Index: dovecot/pigeonhole/src/lib-sieve/mcht-matches.c +=================================================================== +--- dovecot.orig/pigeonhole/src/lib-sieve/mcht-matches.c ++++ dovecot/pigeonhole/src/lib-sieve/mcht-matches.c +@@ -9,6 +9,7 @@ + + #include "sieve-match-types.h" + #include "sieve-comparators.h" ++#include "sieve-interpreter.h" + #include "sieve-match.h" + + #include <string.h> +@@ -46,16 +47,38 @@ const struct sieve_match_type_def matche + #endif + + /* FIXME: Naive implementation, substitute this with dovecot src/lib/str-find.c ++ * ++ * The inner loop polls the interpreter CPU time limit periodically so that a ++ * single O(N*M) search on a large value cannot run for many times the ++ * configured sieve_max_cpu_time. Returns 1 on match, 0 on exhaustion, or -1 ++ * when the CPU time limit was exceeded (mctx->exec_status is set). + */ +-static inline bool _string_find(const struct sieve_comparator *cmp, +- const char **valp, const char *vend, const char **keyp, const char *kend) ++#define SIEVE_MATCHES_CPU_CHECK_INTERVAL 4096 ++ ++static int ++_string_find(struct sieve_match_context *mctx, ++ const struct sieve_comparator *cmp, ++ const char **valp, const char *vend, ++ const char **keyp, const char *kend, ++ unsigned int *counter) + { + while ( (*valp < vend) && (*keyp < kend) ) { + if ( !cmp->def->char_match(cmp, valp, vend, keyp, kend) ) + (*valp)++; ++ if (++(*counter) >= SIEVE_MATCHES_CPU_CHECK_INTERVAL) { ++ *counter = 0; ++ if (sieve_runtime_cpu_limit_exceeded(mctx->runenv)) { ++ sieve_runtime_error( ++ mctx->runenv, NULL, ++ "execution exceeded CPU time limit"); ++ mctx->exec_status = ++ SIEVE_EXEC_RESOURCE_LIMIT; ++ return -1; ++ } ++ } + } + +- return (*keyp == kend); ++ return (*keyp == kend ? 1 : 0); + } + + static char _scan_key_section +@@ -93,6 +116,7 @@ static int mcht_matches_match_key + char wcard = '\0'; /* Current wildcard */ + char next_wcard = '\0'; /* Next widlcard */ + unsigned int key_offset = 0; ++ unsigned int counter = 0; + + if ( cmp->def == NULL || cmp->def->char_match == NULL ) + return 0; +@@ -134,6 +158,19 @@ static int mcht_matches_match_key + while (kp < kend && vp < vend ) { + const char *needle, *nend; + ++ if (++counter >= SIEVE_MATCHES_CPU_CHECK_INTERVAL) { ++ counter = 0; ++ if (sieve_runtime_cpu_limit_exceeded(mctx->runenv)) { ++ sieve_runtime_error( ++ mctx->runenv, NULL, ++ "execution exceeded CPU time limit"); ++ mctx->exec_status = ++ SIEVE_EXEC_RESOURCE_LIMIT; ++ sieve_match_values_abort(&mvalues); ++ return -1; ++ } ++ } ++ + if ( !backtrack ) { + /* Search the next '*' wildcard in the key string */ + +@@ -268,10 +305,20 @@ static int mcht_matches_match_key + + /* Match may happen at any offset (>= key offset): find substring */ + vp += key_offset; +- if ( (vp >= vend) || !_string_find(cmp, &vp, vend, &needle, nend) ) { ++ if (vp >= vend) { + debug_printf(" failed to find needle at an offset\n"); + break; + } ++ int fres = _string_find(mctx, cmp, &vp, vend, ++ &needle, nend, &counter); ++ if (fres < 0) { ++ sieve_match_values_abort(&mvalues); ++ return -1; ++ } ++ if (fres == 0) { ++ debug_printf(" failed to find needle at an offset\n"); ++ break; ++ } + + prv = vp - str_len(section); + prk = kp; +Index: dovecot/pigeonhole/src/lib-sieve/sieve-interpreter.c +=================================================================== +--- dovecot.orig/pigeonhole/src/lib-sieve/sieve-interpreter.c ++++ dovecot/pigeonhole/src/lib-sieve/sieve-interpreter.c +@@ -85,6 +85,13 @@ struct sieve_interpreter { + struct sieve_runtime_trace trace; + struct sieve_resource_usage rusage; + ++ /* CPU time limit for the current sieve_interpreter_continue() call; ++ NULL when no limit is configured or not currently executing. Exposed ++ via sieve_runtime_cpu_limit_exceeded() so long-running runtime code ++ can enforce the limit without waiting for the next bytecode ++ boundary. */ ++ struct cpu_limit *climit; ++ + /* Current operation */ + struct sieve_operation oprtn; + +@@ -362,6 +369,15 @@ sieve_interpreter_svinst(struct sieve_in + return interp->runenv.exec_env->svinst; + } + ++bool sieve_runtime_cpu_limit_exceeded(const struct sieve_runtime_env *renv) ++{ ++ struct sieve_interpreter *interp = renv->interp; ++ ++ if (interp->climit == NULL) ++ return FALSE; ++ return cpu_limit_exceeded(interp->climit); ++} ++ + /* Do not use this function for normal sieve extensions. This is intended for + * the testsuite only. + */ +@@ -939,6 +955,7 @@ int sieve_interpreter_continue(struct si + climit = cpu_limit_init(svinst->set->max_cpu_time, + CPU_LIMIT_TYPE_USER); + } ++ interp->climit = climit; + + while (ret == SIEVE_EXEC_OK && !interp->interrupted && + *address < sieve_binary_block_get_size(renv->sblock)) { +@@ -959,6 +976,8 @@ int sieve_interpreter_continue(struct si + ret = sieve_interpreter_operation_execute(interp); + } + ++ interp->climit = NULL; ++ + if (climit != NULL) { + sieve_resource_usage_init(&rusage); + rusage.cpu_time_msecs = +Index: dovecot/pigeonhole/src/lib-sieve/sieve-interpreter.h +=================================================================== +--- dovecot.orig/pigeonhole/src/lib-sieve/sieve-interpreter.h ++++ dovecot/pigeonhole/src/lib-sieve/sieve-interpreter.h +@@ -165,6 +165,18 @@ int sieve_interpreter_run(struct sieve_i + struct sieve_result *result); + + /* ++ * CPU limit ++ */ ++ ++/* Returns TRUE if the current interpreter execution has exceeded its CPU ++ time limit (sieve_max_cpu_time). Callable from within long-running runtime ++ code (e.g. matcher inner loops) so that limit enforcement is not deferred ++ until the next bytecode boundary. Returns FALSE if no limit is active or ++ no execution is currently in progress. Cheap: does not call getrusage() ++ on each invocation. */ ++bool sieve_runtime_cpu_limit_exceeded(const struct sieve_runtime_env *renv); ++ ++/* + * Error handling + */ + diff -Nru dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-40020-1.patch dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-40020-1.patch --- dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-40020-1.patch 1969-12-31 19:00:00.000000000 -0500 +++ dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-40020-1.patch 2026-05-12 21:50:03.000000000 -0400 @@ -0,0 +1,78 @@ +From b7daa4104ff064c1fb549540cc9d96c2d9e2509c Mon Sep 17 00:00:00 2001 +From: Timo Sirainen <[email protected]> +Date: Wed, 22 Apr 2026 15:43:58 +0300 +Subject: [PATCH 1/3] acl: Add acl_id_is_valid() + +Returns TRUE if the ACL identifier string is at most ACL_ID_MAX_LEN +(1024) bytes long, contains no control characters and is valid UTF-8. +--- + src/plugins/acl/acl-rights.c | 16 ++++++++++++++++ + src/plugins/acl/acl-rights.h | 6 ++++++ + 2 files changed, 22 insertions(+) + +diff --git a/src/plugins/acl/acl-rights.c b/src/plugins/acl/acl-rights.c +index 654ee00aef..5729f8b52a 100644 +--- a/src/plugins/acl/acl-rights.c ++++ b/src/plugins/acl/acl-rights.c +@@ -3,11 +3,14 @@ + #include "lib.h" + #include "array.h" + #include "str.h" ++#include "unichar.h" + /* <settings checks> */ + #include "strescape.h" + /* </settings checks> */ + #include "acl-api-private.h" + ++#include <ctype.h> ++ + /* <settings checks> */ + const struct acl_letter_map acl_letter_map[] = { + { 'l', MAIL_ACL_LOOKUP }, +@@ -44,6 +47,19 @@ static_assert(N_ELEMENTS(acl_letter_map) == N_ELEMENTS(all_mailbox_rights), + + /* </settings checks> */ + ++bool acl_id_is_valid(const char *id) ++{ ++ size_t len = strlen(id); ++ ++ if (len > ACL_ID_MAX_LEN) ++ return FALSE; ++ for (size_t i = 0; i < len; i++) { ++ if (i_iscntrl(id[i])) ++ return FALSE; ++ } ++ return uni_utf8_data_is_valid((const unsigned char *)id, len); ++} ++ + void acl_rights_write_id(string_t *dest, const struct acl_rights *right) + { + switch (right->id_type) { +diff --git a/src/plugins/acl/acl-rights.h b/src/plugins/acl/acl-rights.h +index 88ef73e3b4..32163e7c3d 100644 +--- a/src/plugins/acl/acl-rights.h ++++ b/src/plugins/acl/acl-rights.h +@@ -34,6 +34,8 @@ + #define ACL_ID_NAME_GROUP_PREFIX "group=" + #define ACL_ID_NAME_GROUP_OVERRIDE_PREFIX "group-override=" + ++#define ACL_ID_MAX_LEN 1024 ++ + struct acl_letter_map { + const char letter; + const char *name; +@@ -104,6 +106,10 @@ struct acl_rights_update { + time_t last_change; + }; + ++/* Returns TRUE if the ACL identifier string is valid: no longer than ++ ACL_ID_MAX_LEN bytes, no control characters and valid UTF-8. */ ++bool acl_id_is_valid(const char *id); ++ + /* Returns the canonical ID for the right. */ + const char *acl_rights_get_id(const struct acl_rights *right); + +-- +2.47.3 + diff -Nru dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-40020-2.patch dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-40020-2.patch --- dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-40020-2.patch 1969-12-31 19:00:00.000000000 -0500 +++ dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-40020-2.patch 2026-05-12 21:50:03.000000000 -0400 @@ -0,0 +1,28 @@ +From 20b48c3db5fed7ccaa8e0a4c10ca54f6dc36a63d Mon Sep 17 00:00:00 2001 +From: Timo Sirainen <[email protected]> +Date: Wed, 22 Apr 2026 15:44:24 +0300 +Subject: [PATCH 2/3] imap-acl: Fail if ACL identifier is invalid + +Reject invalid identifiers early in imap_acl_identifier_parse() using +acl_id_is_valid(). This prevents CR/LF injection and rejects identifiers +that are too long, contain control characters or are not valid UTF-8. +--- + src/plugins/imap-acl/imap-acl-plugin.c | 5 +++++ + 1 file changed, 5 insertions(+) + +Index: dovecot/src/plugins/imap-acl/imap-acl-plugin.c +=================================================================== +--- dovecot.orig/src/plugins/imap-acl/imap-acl-plugin.c ++++ dovecot/src/plugins/imap-acl/imap-acl-plugin.c +@@ -872,6 +872,11 @@ imap_acl_identifier_parse(struct client_ + allow_anyone = set->allow_anyone; + settings_free(set); + ++ if (!acl_id_is_valid(id)) { ++ *client_error_r = "Invalid identifier"; ++ return -1; ++ } ++ + if (str_begins_with(id, IMAP_ACL_GLOBAL_PREFIX)) { + *client_error_r = t_strdup_printf( + "Global ACLs can't be modified: %s", id); diff -Nru dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-40020-3.patch dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-40020-3.patch --- dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-40020-3.patch 1969-12-31 19:00:00.000000000 -0500 +++ dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-40020-3.patch 2026-05-12 21:50:03.000000000 -0400 @@ -0,0 +1,23 @@ +From 1cf6ad1a119e5dace816e401e73ba6cc11d1472e Mon Sep 17 00:00:00 2001 +From: Timo Sirainen <[email protected]> +Date: Wed, 22 Apr 2026 15:45:00 +0300 +Subject: [PATCH 3/3] acl: Assert-crash if ACL identifier is invalid before + writing it + +It should have been checked earlier already. +--- + src/plugins/acl/acl-backend-vfile-update.c | 1 + + 1 file changed, 1 insertion(+) + +Index: dovecot/src/plugins/acl/acl-backend-vfile-update.c +=================================================================== +--- dovecot.orig/src/plugins/acl/acl-backend-vfile-update.c ++++ dovecot/src/plugins/acl/acl-backend-vfile-update.c +@@ -119,6 +119,7 @@ vfile_write_right(string_t *dest, const + if (neg) str_append_c(dest,'-'); + acl_rights_write_id(dest, right); + ++ i_assert(acl_id_is_valid(str_c(dest))); + if (strchr(str_c(dest), ' ') != NULL) T_BEGIN { + /* need to escape it */ + const char *escaped = t_strdup(str_escape(str_c(dest))); diff -Nru dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-42006.patch dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-42006.patch --- dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-42006.patch 1969-12-31 19:00:00.000000000 -0500 +++ dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-42006.patch 2026-05-12 21:50:03.000000000 -0400 @@ -0,0 +1,101 @@ +From da1438c76b797f055d4ad7f0eaa17e5e29ca31ee Mon Sep 17 00:00:00 2001 +From: Timo Sirainen <[email protected]> +Date: Mon, 27 Apr 2026 17:40:46 +0300 +Subject: [PATCH] lib-imap: Fix imap_parser_params.list_count_limit to actually + work + +The previous fix in d0f67b52914565a35f3817335ab9633cb291513c was +accidentally limiting the number of ')', not the number of '('. +--- + src/lib-imap/imap-parser.c | 19 +++++++++++-------- + src/lib-imap/test-imap-parser.c | 14 ++++++++++++-- + 2 files changed, 23 insertions(+), 10 deletions(-) + +diff --git a/src/lib-imap/imap-parser.c b/src/lib-imap/imap-parser.c +index 6212aed33d..b1df3d7b78 100644 +--- a/src/lib-imap/imap-parser.c ++++ b/src/lib-imap/imap-parser.c +@@ -191,8 +191,15 @@ static struct imap_arg *imap_arg_create(struct imap_parser *parser) + return arg; + } + +-static void imap_parser_open_list(struct imap_parser *parser) ++static bool imap_parser_open_list(struct imap_parser *parser) + { ++ if (parser->list_count >= parser->list_count_limit) { ++ parser->error_msg = "Too many '('"; ++ parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX; ++ return FALSE; ++ } ++ parser->list_count++; ++ + parser->list_arg = imap_arg_create(parser); + parser->list_arg->type = IMAP_ARG_LIST; + p_array_init(&parser->list_arg->_data.list, parser->pool, +@@ -200,6 +207,7 @@ static void imap_parser_open_list(struct imap_parser *parser) + parser->cur_list = &parser->list_arg->_data.list; + + parser->cur_type = ARG_PARSE_NONE; ++ return TRUE; + } + + static bool imap_parser_close_list(struct imap_parser *parser) +@@ -217,12 +225,6 @@ static bool imap_parser_close_list(struct imap_parser *parser) + parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX; + return FALSE; + } +- if (parser->list_count >= parser->list_count_limit) { +- parser->error_msg = "Too many '('"; +- parser->error = IMAP_PARSE_ERROR_BAD_SYNTAX; +- return FALSE; +- } +- parser->list_count++; + + arg = imap_arg_create(parser); + arg->type = IMAP_ARG_EOL; +@@ -673,7 +675,8 @@ static bool imap_parser_read_arg(struct imap_parser *parser) + parser->literal8 = FALSE; + break; + case '(': +- imap_parser_open_list(parser); ++ if (!imap_parser_open_list(parser)) ++ return FALSE; + if ((parser->flags & IMAP_PARSE_FLAG_STOP_AT_LIST) != 0) { + i_stream_skip(parser->input, 1); + return FALSE; +diff --git a/src/lib-imap/test-imap-parser.c b/src/lib-imap/test-imap-parser.c +index fb7c308a23..d509c8176d 100644 +--- a/src/lib-imap/test-imap-parser.c ++++ b/src/lib-imap/test-imap-parser.c +@@ -85,9 +85,15 @@ static void test_imap_parser_list_limit(void) + struct { + const char *input; + int ret; ++ const char *error; + } tests[] = { +- { "(())\r\n", 1 }, +- { "((()))\r\n", -1 }, ++ { "(())\r\n", 1, NULL }, ++ { "((\r\n", -1, "Missing ')'" }, ++ { "(()\r\n", -1, "Missing ')'" }, ++ { "(()))\r\n", -1, "Unexpected ')'" }, ++ { "((()))\r\n", -1, "Too many '('" }, ++ { "(({10}\r\n", -2, NULL }, ++ { "((({10}\r\n", -1, "Too many '('" }, + }; + struct istream_chain *chain; + struct istream *chain_input; +@@ -112,6 +118,10 @@ static void test_imap_parser_list_limit(void) + (void)i_stream_read(chain_input); + + test_assert_cmp(imap_parser_read_args(parser, 0, 0, &args), ==, tests[i].ret); ++ if (tests[i].ret == -1) { ++ enum imap_parser_error err; ++ test_assert_strcmp_idx(imap_parser_get_error(parser, &err), tests[i].error, i); ++ } + /* skip over CRLF */ + i_stream_skip(chain_input, i_stream_get_data_size(chain_input)); + +-- +2.47.3 + diff -Nru dovecot-2.4.1+dfsg1/debian/patches/series dovecot-2.4.1+dfsg1/debian/patches/series --- dovecot-2.4.1+dfsg1/debian/patches/series 2026-05-05 20:12:19.000000000 -0400 +++ dovecot-2.4.1+dfsg1/debian/patches/series 2026-05-12 21:50:03.000000000 -0400 @@ -57,3 +57,10 @@ CVE-2026-27857-5.patch CVE-2026-27858.patch CVE-2026-27859.patch +CVE-2026-27851.patch +CVE-2026-40016.patch +CVE-2026-33603.patch +CVE-2026-40020-1.patch +CVE-2026-40020-2.patch +CVE-2026-40020-3.patch +CVE-2026-42006.patch

