Version in base suite: 2.4.1+dfsg1-6+deb13u5 Base version: dovecot_2.4.1+dfsg1-6+deb13u5 Target version: dovecot_2.4.1+dfsg1-6+deb13u6 Base file: /srv/ftp-master.debian.org/ftp/pool/main/d/dovecot/dovecot_2.4.1+dfsg1-6+deb13u5.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/d/dovecot/dovecot_2.4.1+dfsg1-6+deb13u6.dsc changelog | 14 ++ patches/CVE-2026-27851.patch | 46 +++++++ patches/CVE-2026-33603.patch | 36 +++++ patches/CVE-2026-40016.patch | 255 +++++++++++++++++++++++++++++++++++++++++ patches/CVE-2026-40020-1.patch | 78 ++++++++++++ patches/CVE-2026-40020-2.patch | 28 ++++ patches/CVE-2026-40020-3.patch | 23 +++ patches/CVE-2026-42006.patch | 101 ++++++++++++++++ patches/series | 7 + 9 files changed, 588 insertions(+) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmp1i6u_no2/dovecot_2.4.1+dfsg1-6+deb13u5.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmp1i6u_no2/dovecot_2.4.1+dfsg1-6+deb13u6.dsc: no acceptable signature found 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 19:18:43.000000000 +0000 +++ dovecot-2.4.1+dfsg1/debian/changelog 2026-05-18 20:03:51.000000000 +0000 @@ -1,3 +1,17 @@ +dovecot (1:2.4.1+dfsg1-6+deb13u6) trixie-security; urgency=medium + + * Security update (Closes: #1136444) + * [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 Mon, 18 May 2026 16:03:51 -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 1970-01-01 00:00:00.000000000 +0000 +++ dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-27851.patch 2026-05-18 20:03:51.000000000 +0000 @@ -0,0 +1,46 @@ +From d75c04e8ccbeb70d66d05938665fe145175ac1b5 Mon Sep 17 00:00:00 2001 +From: Aki Tuomi +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 1970-01-01 00:00:00.000000000 +0000 +++ dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-33603.patch 2026-05-18 20:03:51.000000000 +0000 @@ -0,0 +1,36 @@ +From c1c53885bda550632b944dd305013cd010e0e058 Mon Sep 17 00:00:00 2001 +From: Aki Tuomi +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 1970-01-01 00:00:00.000000000 +0000 +++ dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-40016.patch 2026-05-18 20:03:51.000000000 +0000 @@ -0,0 +1,255 @@ +From 5b0ed9d1034c023d3daf218b6b8656f0cdd383dc Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +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 +@@ -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 +@@ -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 1970-01-01 00:00:00.000000000 +0000 +++ dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-40020-1.patch 2026-05-18 20:03:51.000000000 +0000 @@ -0,0 +1,78 @@ +From b7daa4104ff064c1fb549540cc9d96c2d9e2509c Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +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" + /* */ + #include "strescape.h" + /* */ + #include "acl-api-private.h" + ++#include ++ + /* */ + 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), + + /* */ + ++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 1970-01-01 00:00:00.000000000 +0000 +++ dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-40020-2.patch 2026-05-18 20:03:51.000000000 +0000 @@ -0,0 +1,28 @@ +From 20b48c3db5fed7ccaa8e0a4c10ca54f6dc36a63d Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +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 1970-01-01 00:00:00.000000000 +0000 +++ dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-40020-3.patch 2026-05-18 20:03:51.000000000 +0000 @@ -0,0 +1,23 @@ +From 1cf6ad1a119e5dace816e401e73ba6cc11d1472e Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +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 1970-01-01 00:00:00.000000000 +0000 +++ dovecot-2.4.1+dfsg1/debian/patches/CVE-2026-42006.patch 2026-05-18 20:03:51.000000000 +0000 @@ -0,0 +1,101 @@ +From da1438c76b797f055d4ad7f0eaa17e5e29ca31ee Mon Sep 17 00:00:00 2001 +From: Timo Sirainen +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-06 00:12:19.000000000 +0000 +++ dovecot-2.4.1+dfsg1/debian/patches/series 2026-05-18 20:03:51.000000000 +0000 @@ -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