[packages/dovecot-pigeonhole] Backport 2.4.x security fixes; rel 2

arekm arekm at pld-linux.org
Wed Jul 29 21:46:34 CEST 2026


commit bb3c715894de4111c74f9e5f7dc24916630715e4
Author: Arkadiusz Miśkiewicz <arekm at maven.pl>
Date:   Wed Jul 29 21:43:20 2026 +0200

    Backport 2.4.x security fixes; rel 2
    
    4 CVEs + unreleased :matches panic fix; distro CVE-2026-40016 dropped
    "vp += key_offset;" and broke :matches - restored to match 2.4.

 CVE-2025-59032.patch         |  29 +++++
 CVE-2026-27857.patch         |  28 +++++
 CVE-2026-27858.patch         |  27 +++++
 CVE-2026-40016.patch         | 252 +++++++++++++++++++++++++++++++++++++++++++
 dovecot-pigeonhole-git.patch |  71 ++++++++++++
 dovecot-pigeonhole.spec      |  17 ++-
 6 files changed, 422 insertions(+), 2 deletions(-)
---
diff --git a/dovecot-pigeonhole.spec b/dovecot-pigeonhole.spec
index 13d7946..ab12d34 100644
--- a/dovecot-pigeonhole.spec
+++ b/dovecot-pigeonhole.spec
@@ -7,17 +7,25 @@ Summary:	Sieve plugin for dovecot
 Summary(pl.UTF-8):	Wtyczka Sieve i Managesieve dla dovecota
 Name:		dovecot-pigeonhole
 Version:	%{dovecot_series}_%{pigeonhole_version}
-Release:	1
+Release:	2
 License:	LGPL
 Group:		Daemons
 Source0:	https://pigeonhole.dovecot.org/releases/%{dovecot_series}/dovecot-%{dovecot_series}-pigeonhole-%{pigeonhole_version}.tar.gz
 # Source0-md5:	d2b811648a3e2942736a5e1755bfe872
+# unreleased fix from upstream's release-0.5 branch (dead since 2025-04-10)
+Patch100:	%{name}-git.patch
+# 2.3 is EOL upstream; security fixes backported from 2.4.x via Debian/Ubuntu
+Patch0:		CVE-2025-59032.patch
+Patch1:		CVE-2026-27857.patch
+Patch2:		CVE-2026-27858.patch
+Patch3:		CVE-2026-40016.patch
 URL:		http://pigeonhole.dovecot.org/
 BuildRequires:	autoconf
 BuildRequires:	automake
 BuildRequires:	bison
 #BuildRequires:	dovecot-devel >= 1:%{dovecot_series}
-BuildRequires:	dovecot-devel >= 1:2.3.21
+# CVE-2026-27857 changed the imap_parser_create() API in lib-imap
+BuildRequires:	dovecot-devel >= 1:2.3.21.1-9
 BuildRequires:	flex
 BuildRequires:	libtool
 %requires_eq_to	dovecot dovecot-devel
@@ -56,6 +64,11 @@ Ten pakiet zawiera demona Manage Sieve dla dovecot.
 
 %prep
 %setup -q -n dovecot-%{dovecot_series}-pigeonhole-%{pigeonhole_version}
+%patch -P100 -p1
+%patch -P0 -p1
+%patch -P1 -p1
+%patch -P2 -p1
+%patch -P3 -p1
 
 %build
 %configure \
diff --git a/CVE-2025-59032.patch b/CVE-2025-59032.patch
new file mode 100644
index 0000000..7d372f3
--- /dev/null
+++ b/CVE-2025-59032.patch
@@ -0,0 +1,29 @@
+From efb68fac3a9d2d04d38c4ab14dd570cf0c23923c Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen at open-xchange.com>
+Date: Mon, 22 Dec 2025 22:25:04 +0200
+Subject: [PATCH] managesieve-login: Fix crash when command didn't finish on
+ the first call
+
+---
+ src/managesieve-login/client.c | 9 +++++----
+ 1 file changed, 5 insertions(+), 4 deletions(-)
+
+--- a/src/managesieve-login/client.c
++++ b/src/managesieve-login/client.c
+@@ -330,10 +330,12 @@ static bool managesieve_client_input_nex
+ 			if (args[0].type != MANAGESIEVE_ARG_EOL)
+ 				ret = -1;
+ 		}
+-	}
+-	if (ret > 0) {
++		if (ret > 0)
++			ret = client->cmd->func(client, args);
++	} else {
++		/* Continue unfinished command */
+ 		i_assert(client->cmd != NULL);
+-		ret = client->cmd->func(client, args);
++		ret = client->cmd->func(client, NULL);
+ 	}
+ 
+ 	if (ret != 0)
+
diff --git a/CVE-2026-27857.patch b/CVE-2026-27857.patch
new file mode 100644
index 0000000..89071ed
--- /dev/null
+++ b/CVE-2026-27857.patch
@@ -0,0 +1,28 @@
+From bc91df5f91e8b83c58f138799d39be687271106d Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen at open-xchange.com>
+Date: Fri, 6 Mar 2026 17:06:45 +0200
+Subject: [PATCH 1/2] plugins: imap-filter-sieve: imap-filter-sieve - Adjust to
+ imap_parser_create() API change
+
+---
+ src/plugins/imap-filter-sieve/cmd-filter-sieve.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/pigeonhole/src/plugins/imap-filter-sieve/cmd-filter-sieve.c b/pigeonhole/src/plugins/imap-filter-sieve/cmd-filter-sieve.c
+index 6965b511..33d6804c 100644
+--- a/src/plugins/imap-filter-sieve/cmd-filter-sieve.c
++++ b/src/plugins/imap-filter-sieve/cmd-filter-sieve.c
+@@ -379,7 +379,8 @@ bool cmd_filter_sieve(struct client_command_context *cmd)
+ 	   asynchronously the same way as APPEND does. */
+ 	client->input_lock = cmd;
+ 	ctx->parser = imap_parser_create(client->input, client->output,
+-					 client->set->imap_max_line_length);
++					 client->set->imap_max_line_length,
++					 NULL);
+ 	if (client->set->imap_literal_minus)
+ 		imap_parser_enable_literal_minus(ctx->parser);
+ 	o_stream_unset_flush_callback(client->output);
+-- 
+2.39.5
+
+
diff --git a/CVE-2026-27858.patch b/CVE-2026-27858.patch
new file mode 100644
index 0000000..afacfc2
--- /dev/null
+++ b/CVE-2026-27858.patch
@@ -0,0 +1,27 @@
+From fcb339d39bfb19abf1bb88711112f49c1c589b1a Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen at open-xchange.com>
+Date: Mon, 2 Mar 2026 14:40:57 +0200
+Subject: [PATCH 2/2] managesieve-login: Verify AUTHENTICATE initial response
+ size isn't too large
+
+This prevents DoSing the managesieve-login by sending an excessively large
+initial response size, which causes a huge memory allocation.
+---
+ src/managesieve-login/client-authenticate.c | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+--- a/src/managesieve-login/client-authenticate.c
++++ b/src/managesieve-login/client-authenticate.c
+@@ -189,6 +189,11 @@ static int managesieve_client_auth_read_
+ 		if ( i_stream_get_size
+ 			(msieve_client->auth_response_input, FALSE, &resp_size) <= 0 )
+ 			resp_size = 0;
++		else if (resp_size > LOGIN_MAX_AUTH_BUF_SIZE) {
++			client_destroy(client,
++				       "Authentication response too large");
++			return -1;
++		}
+ 
+ 		if (client->auth_response == NULL)
+ 			client->auth_response = str_new(default_pool, I_MAX(resp_size+1, 256));
+
diff --git a/CVE-2026-40016.patch b/CVE-2026-40016.patch
new file mode 100644
index 0000000..b946ecf
--- /dev/null
+++ b/CVE-2026-40016.patch
@@ -0,0 +1,252 @@
+Backport of:
+
+From 5b0ed9d1034c023d3daf218b6b8656f0cdd383dc Mon Sep 17 00:00:00 2001
+From: Timo Sirainen <timo.sirainen at open-xchange.com>
+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(-)
+
+PLD: the Debian/Ubuntu backport dropped the "vp += key_offset;" line when it
+rewrote the _string_find() call site, which made :matches search from the wrong
+offset (tests/match-types/matches.svtest test 18 regressed). Restored here to
+match upstream 2.4, which keeps that line.
+
+--- a/src/lib-sieve/mcht-contains.c
++++ b/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 @@
+ 
+ /* 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 @@
+ 	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 @@
+ 	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 );
+--- a/src/lib-sieve/mcht-matches.c
++++ b/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,39 @@
+ #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 +117,7 @@
+ 	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 +159,19 @@
+ 	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,7 +306,17 @@
+ 
+ 				/* 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;
+ 				}
+--- a/src/lib-sieve/sieve-interpreter.c
++++ b/src/lib-sieve/sieve-interpreter.c
+@@ -85,6 +85,13 @@
+ 	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 @@
+ 	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 @@
+ 		climit = cpu_limit_init(svinst->max_cpu_time_secs,
+ 					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 @@
+ 		ret = sieve_interpreter_operation_execute(interp);
+ 	}
+ 
++	interp->climit = NULL;
++
+ 	if (climit != NULL) {
+ 		sieve_resource_usage_init(&rusage);
+ 		rusage.cpu_time_msecs =
+--- a/src/lib-sieve/sieve-interpreter.h
++++ b/src/lib-sieve/sieve-interpreter.h
+@@ -165,6 +165,18 @@
+ 			  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 --git a/dovecot-pigeonhole-git.patch b/dovecot-pigeonhole-git.patch
new file mode 100644
index 0000000..5ffab2d
--- /dev/null
+++ b/dovecot-pigeonhole-git.patch
@@ -0,0 +1,71 @@
+From 5055e3afd38fbdaff422fd814b5c458a67bbd2b8 Mon Sep 17 00:00:00 2001
+From: Stephan Bosch <stephan.bosch at open-xchange.com>
+Date: Thu, 10 Apr 2025 01:43:08 +0200
+Subject: [PATCH] lib-sieve: mcht-matches - Fix panics occurring with initial
+ *? wildcards with variables enabled
+
+Panic: file bits.c: line 17 (nearest_power): assertion failed:
+(num <= ((size_t)1 << (CHAR_BIT*sizeof(size_t) - 1)))
+
+Rebased onto 0.5.21.1, which predates the upstream mcht-matches.c reformat.
+--- a/src/lib-sieve/mcht-matches.c
++++ b/src/lib-sieve/mcht-matches.c
+@@ -212,9 +212,6 @@
+ 				qend = vp;
+ 				qp = vp - key_offset;
+ 
+-				if ( mvalues != NULL )
+-					str_append_data(mvalue, pvp, qp-pvp);
+-
+ 				/* Compare needle to end of value string */
+ 				if ( !cmp->def->char_match(cmp, &vp, vend, &needle, nend) ) {
+ 					debug_printf("  match at end failed\n");
+@@ -223,6 +220,9 @@
+ 
+ 				/* Add match values */
+ 				if ( mvalues != NULL ) {
++					i_assert(qp >= pvp);
++					str_append_data(mvalue, pvp, qp-pvp);
++
+ 					/* Append '*' match value */
+ 					sieve_match_values_add(mvalues, mvalue);
+ 
+--- a/tests/extensions/variables/match.svtest
++++ b/tests/extensions/variables/match.svtest
+@@ -363,3 +363,36 @@
+ 		test_fail "incorrect match values: ${1}${2}";
+ 	}
+ }
++
++test "Initial *??" {
++  if string :matches "foo1234 at example.com" ["*??@foobar.example.com"] {
++    test_fail "Unexpectedly matched";
++  }
++  if not string :matches "foo1234 at foobar.example.com" ["*??@foobar.example.com"] {
++    test_fail "Failed to match";
++  }
++  if not string "${1}" "foo12" {
++    test_fail "Match value 1 is wrong: `${1}`";
++  }
++  if not string "${2}" "3" {
++    test_fail "Match value 1 is wrong: `${2}`";
++  }
++  if not string "${3}" "4" {
++    test_fail "Match value 1 is wrong: `${3}`";
++  }
++}
++
++test "Intiial *?" {
++  if string :matches "d at example.jp" ["*?@example.com"] {
++    test_fail "Unexpectedly matched";
++  }
++  if not string :matches "d at example.com" ["*?@example.com"] {
++    test_fail "Failed to match";
++  }
++  if not string "${1}" "" {
++    test_fail "Match value 1 is wrong: `${1}`";
++  }
++  if not string "${2}" "d" {
++    test_fail "Match value 1 is wrong: `${2}`";
++  }
++}
================================================================

---- gitweb:

http://git.pld-linux.org/gitweb.cgi/packages/dovecot-pigeonhole.git/commitdiff/bb3c715894de4111c74f9e5f7dc24916630715e4



More information about the pld-cvs-commit mailing list