Bug#1142925: trixie-pu: package cyrus-imapd/3.10.2-1+deb13u2
Edmund Lodewijks <[email protected]>
| Newsgroups | gmane.linux.debian.devel.release |
|---|---|
| Message-ID | <7dc24e89-f6bc-4095-84bd-b428d7e3f0dc__22374.1850511701$1788257997$gmane$org@proteamail.com> |
Control: tags -1 + patch
Hi Release Team,
Update / ping on this issue: trixie is still vulnerable to all nine
CVEs, and the upload has not been made. Attached is an updated debdiff
against 3.10.2-1+deb13u1 (current version in trixie-proposed-updates).
The maintainer, Xavier Guimard, has reviewed and merged this into the
packaging repository:
https://salsa.debian.org/debian/cyrus-imapd/-/merge_requests/14
Changes since the debdiff Bastian posted on 2026-07-28: DEP-3 metadata
on all nine patches, and the upstream Cassandane regression tests that
the original omitted (it carried tests for only three of the nine
fixes).
* Risks
Low. All nine are upstream fixes cherry-picked from 3.10.3, each adding
a single missing ACL check (or a bounds check, for CVE-2026-47088). No
packaging, ABI or configuration changes. Diff is +1065/-0, confined to
debian/changelog, debian/patches/series and nine new patches.
* Tests
Built in a clean trixie chroot (sbuild) on amd64. No runtime testing was
done: Each fix ships its upstream Cassandane regression test, but those
are not exercised yet, because the autopkgtest pulls the archived
upstream cassandane repo rather than the in-tree suite (#1145447, being
fixed separately).
Fixed in unstable since 3.12.3-4.
Thank you,
Edmund
--
Edmund Lodewijks <[email protected]>
TZ: UTC+2 / GMT+2
cyrus-imapd_deb13u2.debdiff
(text/plain, 45.3 KB)
diff -Nru cyrus-imapd-3.10.2/debian/changelog cyrus-imapd-3.10.2/debian/changelog --- cyrus-imapd-3.10.2/debian/changelog 2026-07-12 21:29:05.000000000 +0200 +++ cyrus-imapd-3.10.2/debian/changelog 2026-08-02 12:00:00.000000000 +0200 @@ -1,3 +1,31 @@ +cyrus-imapd (3.10.2-1+deb13u2) trixie; urgency=high + + * Backport security fixes from upstream 3.10.3 (Closes: #1142925) + (https://www.cyrusimap.org/imap/download/release-notes/3.10/x/3.10.3.html): + - CVE-2026-47084: LOCALDELETE bypassed ACL checks, allowing non-admin + users to delete mailboxes without permission. + - CVE-2026-47086: GENURLAUTH issued URLAUTH tokens without checking + ACL_READ on the target mailbox. + - CVE-2026-47087: URLAUTH tokens kept working after the authorizer's + access was revoked. + - CVE-2026-47081: XAPPLEPUSHSERVICE allowed probing for mailbox + existence and hijacking push notifications on other users' folders. + - CVE-2026-47089: LISTRIGHTS was not restricted to users with admin + access on the target mailbox. + - CVE-2026-47085: URLAUTH tokens could be forged via a predictable + empty mboxkey. + - CVE-2026-47083: MULTISEARCH/ESEARCH allowed a cross-user folder and + content enumeration oracle. + - CVE-2026-47088: heap out-of-bounds read when parsing nested MIME + comments in RFC 822 headers. + - CVE-2026-47082: vacation "fcc" delivery skipped the ACL check on the + destination mailbox. + * This revision adds DEP-3 metadata to the new patches and restores + upstream Cassandane regression tests that were missing from the initial + debdiff (thanks, Codin!) for several of the fixes. + + -- Edmund Lodewijks <[email protected]> Sun, 02 Aug 2026 12:00:00 +0200 + cyrus-imapd (3.10.2-1+deb13u1) trixie; urgency=medium * http_jmap: allow JMAP EventSource without WebSocket/wslay; backport diff -Nru cyrus-imapd-3.10.2/debian/patches/0020-lmtp_sieve.c-enforce-ACL-on-vacation-fcc-destination.patch cyrus-imapd-3.10.2/debian/patches/0020-lmtp_sieve.c-enforce-ACL-on-vacation-fcc-destination.patch --- cyrus-imapd-3.10.2/debian/patches/0020-lmtp_sieve.c-enforce-ACL-on-vacation-fcc-destination.patch 1970-01-01 02:00:00.000000000 +0200 +++ cyrus-imapd-3.10.2/debian/patches/0020-lmtp_sieve.c-enforce-ACL-on-vacation-fcc-destination.patch 2026-08-02 12:00:00.000000000 +0200 @@ -0,0 +1,101 @@ +Description: lmtp_sieve.c: enforce ACL on vacation :fcc destination (CVE-2026-47082) + An initial debdiff for this CVE batch was submitted by Codin + <[email protected]> and sponsored/reviewed by Bastian Germann + <[email protected]> in Debian bug #1142925. This revision adds DEP-3 + metadata and, where missing from that debdiff, folds in the matching + upstream Cassandane regression test(s). + . + This is CYR-2892, CVE-2026-47082. + . + Enforce *some* ACL check when delivering the fcc for a vacation message! + Rather than pass aclcheck=0, pass ACL_INSERT: the script owner must be + able to write to the target mailbox. + . + This problem was reported by Michael Lynch (mtlynch.io). + . + Co-authored-by: Claude <[email protected]> +Author: Ricardo Signes <[email protected]> +Origin: upstream, https://github.com/cyrusimap/cyrus-imapd/commit/db6d646d00 +Bug: https://security-tracker.debian.org/tracker/CVE-2026-47082 +Bug-Debian: https://bugs.debian.org/1142925 +Bug-CVE: CVE-2026-47082 +Forwarded: not-needed +Last-Update: 2026-07-25 + +diff --git a/cassandane/tiny-tests/Sieve/vacation_with_fcc_otheruser_denied b/cassandane/tiny-tests/Sieve/vacation_with_fcc_otheruser_denied +new file mode 100644 +index 0000000..ed9ff9a +--- /dev/null ++++ b/cassandane/tiny-tests/Sieve/vacation_with_fcc_otheruser_denied +@@ -0,0 +1,58 @@ ++#!perl ++use Cassandane::Tiny; ++ ++sub test_vacation_with_fcc_otheruser_denied ++ :min_version_3_1 ++ :NoAltNameSpace ++ :want_smtpdaemon ++{ ++ my ($self) = @_; ++ ++ my $target = "user.other.target"; ++ ++ xlog $self, "Create victim user 'other' and a target folder"; ++ $self->{instance}->create_user('other'); ++ ++ my $admintalk = $self->{adminstore}->get_client(); ++ $admintalk->create($target) ++ or die "Cannot create $target: $@"; ++ ++ xlog $self, "Give cassandane read-only access (no 'i'/'p' rights)"; ++ $admintalk->setacl($target, 'cassandane' => 'lrs'); ++ $self->assert_str_equals('ok', ++ $admintalk->get_last_completion_response()); ++ ++ xlog $self, "Confirm cassandane cannot APPEND directly to $target"; ++ my $talk = $self->{store}->get_client(); ++ $talk->append($target, ++ "From: nobody\@example.com\r\n" ++ . "Subject: direct-append\r\n\r\n" ++ . "denied\r\n"); ++ $self->assert_str_equals('no', ++ $talk->get_last_completion_response()); ++ ++ xlog $self, "Install a vacation rule that :fcc's into the victim's folder"; ++ $self->{instance}->install_sieve_script(<<EOF ++require ["vacation", "fcc"]; ++ ++vacation :fcc "$target" :days 1 :addresses ["cassandane\@example.com"] text: ++I am out of the office today. I will answer your email as soon as I can. ++. ++; ++EOF ++ ); ++ ++ xlog $self, "Deliver a message to trigger the vacation auto-reply"; ++ my $msg1 = $self->{gen}->generate( ++ subject => "Message 1", ++ to => Cassandane::Address->new(localpart => 'cassandane', ++ domain => 'example.com'), ++ ); ++ $self->{instance}->deliver($msg1); ++ ++ xlog $self, "Check that nothing was filed into the victim's folder"; ++ $admintalk->select($target); ++ $self->assert_str_equals('ok', ++ $admintalk->get_last_completion_response()); ++ $self->assert_num_equals(0, $admintalk->get_response_code('exists')); ++} +diff --git a/imap/lmtp_sieve.c b/imap/lmtp_sieve.c +index f13a266..b85bf1a 100644 +--- a/imap/lmtp_sieve.c ++++ b/imap/lmtp_sieve.c +@@ -1931,7 +1931,7 @@ static void do_fcc(script_data_t *sdata, sieve_fileinto_context_t *fcc, + } + if (!r) { + r = append_setup(&as, intname, userid, sdata->authstate, +- 0, NULL, NULL, 0, EVENT_MESSAGE_APPEND); ++ ACL_INSERT, NULL, NULL, 0, EVENT_MESSAGE_APPEND); + } + if (!r) { + struct stagemsg *stage; diff -Nru cyrus-imapd-3.10.2/debian/patches/0021-imap-message.c-fix-heap-exposure-in-nested-MIME-comm.patch cyrus-imapd-3.10.2/debian/patches/0021-imap-message.c-fix-heap-exposure-in-nested-MIME-comm.patch --- cyrus-imapd-3.10.2/debian/patches/0021-imap-message.c-fix-heap-exposure-in-nested-MIME-comm.patch 1970-01-01 02:00:00.000000000 +0200 +++ cyrus-imapd-3.10.2/debian/patches/0021-imap-message.c-fix-heap-exposure-in-nested-MIME-comm.patch 2026-08-02 12:00:00.000000000 +0200 @@ -0,0 +1,145 @@ +Description: imap/message.c: fix heap exposure in nested MIME comment parsing (CVE-2026-47088) + An initial debdiff for this CVE batch was submitted by Codin + <[email protected]> and sponsored/reviewed by Bastian Germann + <[email protected]> in Debian bug #1142925. This revision adds DEP-3 + metadata and, where missing from that debdiff, folds in the matching + upstream Cassandane regression test(s). + . + This is CYR-2882, CVE-2026-47088. + . + Fix out-of-bounds read in message_parse_rfc822space() when parsing RFC + 822 comments that end with backslash. The backslash case would increment + p past a NUL terminator, and the subsequent loop increment would read + into adjacent heap memory. + . + Add bounds check after backslash handling to prevent reading past the + end of the buffer. + . + This problem was reported by Michael Lynch (mtlynch.io). + . + Co-Authored-By: Claude <[email protected]> +Author: Ricardo Signes <[email protected]> +Origin: upstream, https://github.com/cyrusimap/cyrus-imapd/commit/23495997b5 +Bug: https://security-tracker.debian.org/tracker/CVE-2026-47088 +Bug-Debian: https://bugs.debian.org/1142925 +Bug-CVE: CVE-2026-47088 +Forwarded: not-needed +Last-Update: 2026-07-25 + +diff --git a/cassandane/Cassandane/Cyrus/MIME.pm b/cassandane/Cassandane/Cyrus/MIME.pm +new file mode 100644 +index 0000000..58df5a2 +--- /dev/null ++++ b/cassandane/Cassandane/Cyrus/MIME.pm +@@ -0,0 +1,40 @@ ++# SPDX-License-Identifier: BSD-3-Clause-CMU ++# See COPYING file at the root of the distribution for more details. ++ ++package Cassandane::Cyrus::MIME; ++use strict; ++use warnings; ++use Data::Dumper; ++ ++use base qw(Cassandane::Cyrus::TestCase); ++use Cassandane::Util::Log; ++ ++sub new ++{ ++ my ($class, @args) = @_; ++ ++ my $config = Cassandane::Config->default()->clone(); ++ ++ my $self = $class->SUPER::new({ ++ config => $config, ++ services => [ 'imap' ] ++ }, @args); ++ ++ return $self; ++} ++ ++sub set_up ++{ ++ my ($self) = @_; ++ $self->SUPER::set_up(); ++} ++ ++sub tear_down ++{ ++ my ($self) = @_; ++ $self->SUPER::tear_down(); ++} ++ ++use Cassandane::Tiny::Loader 'tiny-tests/MIME'; ++ ++1; +diff --git a/cassandane/tiny-tests/MIME/nested_comment_oob b/cassandane/tiny-tests/MIME/nested_comment_oob +new file mode 100644 +index 0000000..b47f6b1 +--- /dev/null ++++ b/cassandane/tiny-tests/MIME/nested_comment_oob +@@ -0,0 +1,50 @@ ++#!perl ++use Cassandane::Tiny; ++ ++sub test_nested_comment_oob ++{ ++ my ($self) = @_; ++ ++ my $talk = $self->{store}->get_client(); ++ ++ # Create a multipart message where the vulnerability is in a nested part. ++ # Based on the PoC description, this reproduces the backslash handling bug ++ # in message_parse_rfc822space() when parsing nested Content-Type headers. ++ # The malformed header must be the last content to ensure backslash is final byte. ++ my $msg = <<'EOF'; ++From: [email protected] ++To: [email protected] ++Subject: Test heap disclosure ++Content-Type: multipart/mixed; boundary="BOUNDARY" ++ ++--BOUNDARY ++Content-Type: text/plain ++ ++This is a normal part. ++ ++--BOUNDARY ++Content-Type: text/plain (\ ++EOF ++ ++ # Remove trailing newline so backslash is the final byte before NUL ++ chomp $msg; ++ $msg =~ s/\r?\n/\r\n/gs; ++ ++ # Append the message ++ $talk->append('INBOX', $msg) || die $@; ++ ++ # Select the mailbox ++ $talk->select('INBOX'); ++ $self->assert_str_equals('ok', $talk->get_last_completion_response()); ++ ++ # Fetch BODYSTRUCTURE - this exercises the vulnerable MIME parsing code ++ # In vulnerable versions, this may crash or leak heap data as MIME parameters ++ my $response = $talk->fetch('1', 'BODYSTRUCTURE'); ++ $self->assert_str_equals('ok', $talk->get_last_completion_response()); ++ ++ # Log the bodystructure for manual inspection of any anomalies ++ # In a release build with heap spray, unexpected parameters may appear ++ if ($response && $response->{1} && $response->{1}->{bodystructure}) { ++ xlog $self, "BODYSTRUCTURE: " . Data::Dumper::Dumper($response->{1}->{bodystructure}); ++ } ++} +diff --git a/imap/message.c b/imap/message.c +index a5b633e..f18b1c6 100644 +--- a/imap/message.c ++++ b/imap/message.c +@@ -1768,6 +1768,10 @@ static void message_parse_rfc822space(const char **s) + + case '\\': + p++; ++ if (*p == '\0') { ++ *s = 0; /* backslash at end of string */ ++ return; ++ } + break; + + case '(': diff -Nru cyrus-imapd-3.10.2/debian/patches/0022-imapd-MULTISEARCH-must-check-ACL_READ-on-each-mailbo.patch cyrus-imapd-3.10.2/debian/patches/0022-imapd-MULTISEARCH-must-check-ACL_READ-on-each-mailbo.patch --- cyrus-imapd-3.10.2/debian/patches/0022-imapd-MULTISEARCH-must-check-ACL_READ-on-each-mailbo.patch 1970-01-01 02:00:00.000000000 +0200 +++ cyrus-imapd-3.10.2/debian/patches/0022-imapd-MULTISEARCH-must-check-ACL_READ-on-each-mailbo.patch 2026-08-02 12:00:00.000000000 +0200 @@ -0,0 +1,96 @@ +Description: imapd: MULTISEARCH must check ACL_READ on each mailbox (CVE-2026-47083) + An initial debdiff for this CVE batch was submitted by Codin + <[email protected]> and sponsored/reviewed by Bastian Germann + <[email protected]> in Debian bug #1142925. This revision adds DEP-3 + metadata and, where missing from that debdiff, folds in the matching + upstream Cassandane regression test(s). + . + This is CYR-2881, CVE-2026-47083. + . + multisearch_cb opened caller-supplied mailboxes from + MAILBOXES/SUBTREE/SUBTREE-ONE and ran index_search on them without + checking whether the caller could read the mailbox. SELECT has the + equivalent gate (imapd.c:5001); MULTISEARCH did not. + . + The effect is a cross-user read oracle: SUBTREE on another user's + account returns one `* ESEARCH ... MAILBOX "..."` line per folder + (folder enumeration for free), and BODY/SUBJECT predicates then probe + the contents one search at a time. An attacker only needs to be able + to name a target mailbox. + . + This problem was reported by Michael Lynch (mtlynch.io). + . + Co-Authored-By: Claude <[email protected]> +Author: Ricardo Signes <[email protected]> +Origin: upstream, https://github.com/cyrusimap/cyrus-imapd/commit/0534c4e30a +Bug: https://security-tracker.debian.org/tracker/CVE-2026-47083 +Bug-Debian: https://bugs.debian.org/1142925 +Bug-CVE: CVE-2026-47083 +Forwarded: not-needed +Last-Update: 2026-07-25 + +diff --git a/cassandane/Cassandane/Cyrus/Search.pm b/cassandane/Cassandane/Cyrus/Search.pm +index 19e16fa..d9e5e9e 100644 +--- a/cassandane/Cassandane/Cyrus/Search.pm ++++ b/cassandane/Cassandane/Cyrus/Search.pm +@@ -757,4 +757,44 @@ sub test_uidsearch_empty + $self->assert_str_equals('0', $results[0][3]); + } + ++sub test_multisearch_cross_user ++ :NoAltNamespace ++{ ++ my ($self) = @_; ++ ++ my $admintalk = $self->{adminstore}->get_client; ++ my $talk = $self->{store}->get_client; ++ ++ # victim has a private subfolder with a message. cassandane has no ++ # explicit grant on it, only whatever the "anyone p" default provides -- ++ # i.e. ACL_POST, which is not enough to run a search. ++ $self->{instance}->create_user('victim'); ++ ++ $admintalk->create('user.victim.private') ++ || die "create: " . $admintalk->get_last_error; ++ ++ $admintalk->append( ++ 'user.victim.private', ++ "From: t\@example.com\r\nSubject: secret\r\n\r\nconfidential body", ++ ) || die "append: " . $admintalk->get_last_error; ++ ++ # Check our assumptions: default user has no access to victim's private ++ # folder. ++ $talk->select('user.victim.private'); ++ $self->assert_str_equals('no', $talk->get_last_completion_response); ++ ++ # The actual test: assert that cassandane user can't find anything by ++ # ESEARCHing victim's private mailbox. ++ my @esearch_responses; ++ $talk->_imap_cmd( ++ 'ESEARCH', 0, ++ { esearch => sub { push @esearch_responses, $_[1] } }, ++ 'IN', '(MAILBOXES "user.victim.private")', ++ 'RETURN', '(ALL)', 'ALL', ++ ); ++ ++ $self->assert_str_equals('ok', $talk->get_last_completion_response); ++ $self->assert_deep_equals([], \@esearch_responses); ++} ++ + 1; +diff --git a/imap/imapd.c b/imap/imapd.c +index f7be33e..4edd0bf 100644 +--- a/imap/imapd.c ++++ b/imap/imapd.c +@@ -6076,6 +6076,11 @@ static int multisearch_cb(const mbentry_t *mbentry, void *rock) + hash_lookup(mbentry->name, &mrock->mailboxes)) + return 0; + ++ /* Skip mailboxes the caller can't read. */ ++ if (!imapd_userisadmin && ++ !(cyrus_acl_myrights(imapd_authstate, mbentry->acl) & ACL_READ)) ++ return 0; ++ + switch (mrock->filter) { + case SEARCH_SOURCE_INBOXES: + /* Only allow user's INBOX or those postable by anonymous */ diff -Nru cyrus-imapd-3.10.2/debian/patches/0023-imapd-reject-URLFETCH-when-no-mboxkey-exists-for-the.patch cyrus-imapd-3.10.2/debian/patches/0023-imapd-reject-URLFETCH-when-no-mboxkey-exists-for-the.patch --- cyrus-imapd-3.10.2/debian/patches/0023-imapd-reject-URLFETCH-when-no-mboxkey-exists-for-the.patch 1970-01-01 02:00:00.000000000 +0200 +++ cyrus-imapd-3.10.2/debian/patches/0023-imapd-reject-URLFETCH-when-no-mboxkey-exists-for-the.patch 2026-08-02 12:00:00.000000000 +0200 @@ -0,0 +1,121 @@ +Description: imapd: reject URLFETCH when no mboxkey exists for the mailbox (CVE-2026-47085) + An initial debdiff for this CVE batch was submitted by Codin + <[email protected]> and sponsored/reviewed by Bastian Germann + <[email protected]> in Debian bug #1142925. This revision adds DEP-3 + metadata and, where missing from that debdiff, folds in the matching + upstream Cassandane regression test(s). + . + This is CYR-2876, CVE-2026-47085. + . + Instead of treating "there's no mboxkey.db entry for this folder" as + indicating a zero-length key, treat a missing mboxkey entry as a + validation failure. + . + (Also close the mboxkey db on the previously-unclosed error paths.) + . + This problem reported by Matthew Horsfall. + . + Co-Authored-By: Claude <[email protected]> + . + Cherry-picked from upstream commits: + e5a1055614 cassandane: test for URLFETCH token forgery with empty mboxkey + 1d39d32c3b imapd: reject URLFETCH when no mboxkey exists for the mailbox +Author: Ricardo Signes <[email protected]> +Origin: upstream, https://github.com/cyrusimap/cyrus-imapd/commit/1d39d32c3b +Bug: https://security-tracker.debian.org/tracker/CVE-2026-47085 +Bug-Debian: https://bugs.debian.org/1142925 +Bug-CVE: CVE-2026-47085 +Forwarded: not-needed +Last-Update: 2026-07-25 + +Index: cyrus-imapd/cassandane/Cassandane/Cyrus/URLAuth.pm +=================================================================== +--- cyrus-imapd.orig/cassandane/Cassandane/Cyrus/URLAuth.pm ++++ cyrus-imapd/cassandane/Cassandane/Cyrus/URLAuth.pm +@@ -44,6 +44,7 @@ use Cwd qw(abs_path); + use File::Path qw(mkpath); + use DateTime; + use Data::Dumper; ++use Digest::SHA qw(hmac_sha1_hex); + + use lib '.'; + use base qw(Cassandane::Cyrus::TestCase); +@@ -161,4 +162,57 @@ sub test_urlfetch + $self->assert_str_equals($data, "ody"); + } + ++sub test_forged_empty_key ++ :UnixHierarchySep :AltNamespace ++{ ++ my ($self) = @_; ++ ++ # The victim has an INBOX and a second folder. The secret we don't want ++ # leaked lives in the second folder. ++ my $talk = $self->{store}->get_client; ++ $talk->create("Archive") or die "create Archive: " . $talk->get_last_error; ++ ++ $self->{store}->set_folder("INBOX"); ++ $self->make_message("Public INBOX message"); ++ ++ $self->{store}->set_folder("Archive"); ++ $self->make_message("Secret archived message"); ++ ++ # Cause the victim's mboxkey.db to be created, but with an entry only for ++ # the INBOX -- *not* for the Archive folder we're about to attack. An ++ # mboxkey.db appears in the wild whenever a user has ever issued a ++ # GENURLAUTH (or a RESETKEY for a specific mailbox). ++ my $seed; ++ $talk->_imap_cmd( ++ 'genurlauth', 0, ++ { genurlauth => sub { $seed = $_[1]->[0] } }, ++ 'imap://[email protected]/INBOX/;uid=1;urlauth=user+cassandane', ++ 'INTERNAL', ++ ); ++ $self->assert_not_null($seed, 'victim should be able to URLAUTH their own INBOX'); ++ ++ # Now the attacker forges a token for the *Archive* folder. Because ++ # mboxkey_read() returns {key=NULL, keylen=0} for any mailbox it has never ++ # stored, cmd_urlfetch computes HMAC-SHA1 with an empty key -- which is ++ # entirely attacker-computable. ++ $self->{instance}->create_user('attacker'); ++ ++ my $rump = 'imap://[email protected]/Archive/;uid=1;urlauth=user+attacker'; ++ my $token = '00' . hmac_sha1_hex($rump, ''); ++ my $forged = "$rump:internal:$token"; ++ ++ my $svc = $self->{instance}->get_service('imap'); ++ my $store = $svc->create_store(username => 'attacker'); ++ my $attacker = $store->get_client(); ++ ++ my $stolen; ++ $attacker->_imap_cmd( ++ 'urlfetch', 0, ++ { urlfetch => sub { $stolen = $_[1]->[1] } }, ++ $forged, ++ ); ++ ++ $self->assert_null($stolen); ++} ++ + 1; +Index: cyrus-imapd/imap/imapd.c +=================================================================== +--- cyrus-imapd.orig/imap/imapd.c ++++ cyrus-imapd/imap/imapd.c +@@ -14061,7 +14061,15 @@ static void cmd_urlfetch(char *tag) + if (r) break; + + r = mboxkey_read(mboxkey_db, intname, &key, &keylen); +- if (r) break; ++ if (!r && (!key || !keylen)) { ++ /* If there's no key, we can't possibly validate against ++ * it! */ ++ r = IMAP_BADURL; ++ } ++ if (r) { ++ mboxkey_close(mboxkey_db); ++ break; ++ } + + HMAC(EVP_sha1(), key, keylen, (unsigned char *) arg.s, + url.urlauth.rump_len, vtoken, &vtoken_len); diff -Nru cyrus-imapd-3.10.2/debian/patches/0024-imapd-LISTRIGHTS-requires-admin-rights.patch cyrus-imapd-3.10.2/debian/patches/0024-imapd-LISTRIGHTS-requires-admin-rights.patch --- cyrus-imapd-3.10.2/debian/patches/0024-imapd-LISTRIGHTS-requires-admin-rights.patch 1970-01-01 02:00:00.000000000 +0200 +++ cyrus-imapd-3.10.2/debian/patches/0024-imapd-LISTRIGHTS-requires-admin-rights.patch 2026-08-02 12:00:00.000000000 +0200 @@ -0,0 +1,85 @@ +Description: imapd: LISTRIGHTS requires admin rights (CVE-2026-47089) + An initial debdiff for this CVE batch was submitted by Codin + <[email protected]> and sponsored/reviewed by Bastian Germann + <[email protected]> in Debian bug #1142925. This revision adds DEP-3 + metadata and, where missing from that debdiff, folds in the matching + upstream Cassandane regression test(s). + . + This is CYR-2872, CVE-2026-47089. + . + cmd_listrights was refusing only when the caller had *no* rights at + all on the target mailbox, when it should've been requiring admin + rights. + . + With this patch, we'll properly require ACL_ADMIN (owners and admins + still pass through), matching the check cmd_getacl already uses. When + the caller has ACL_LOOKUP but not ACL_ADMIN, return PERMISSION_DENIED; + otherwise hide the mailbox as NONEXISTENT. + . + This problem reported by Matthew Horsfall. + . + Co-Authored-By: Claude <[email protected]> + . + Cherry-picked from upstream commits: + 5f406195f7 cassandane: test that LISTRIGHTS requires admin rights + 5c97794e52 imapd: LISTRIGHTS requires admin rights +Author: Ricardo Signes <[email protected]> +Origin: upstream, https://github.com/cyrusimap/cyrus-imapd/commit/5c97794e52 +Bug: https://security-tracker.debian.org/tracker/CVE-2026-47089 +Bug-Debian: https://bugs.debian.org/1142925 +Bug-CVE: CVE-2026-47089 +Forwarded: not-needed +Last-Update: 2026-07-25 + +diff --git a/cassandane/Cassandane/Cyrus/ACL.pm b/cassandane/Cassandane/Cyrus/ACL.pm +index 62fef8e..64ca817 100644 +--- a/cassandane/Cassandane/Cyrus/ACL.pm ++++ b/cassandane/Cassandane/Cyrus/ACL.pm +@@ -270,6 +270,30 @@ sub test_setacl_badrights + $self->assert_deep_equals($origacl, $newacl); + } + ++sub test_listrights_other_user ++{ ++ my ($self) = @_; ++ ++ my $talk = $self->{store}->get_client; ++ ++ # Create a victim user. We grant the attacker no explicit rights on the ++ # victim's mailbox: the default ACL "anyone p" is enough to expose the ++ # bug. (LISTRIGHTS gates only on "has any right at all", where it ought ++ # to require ACL_ADMIN; ACL_POST from the anyone grant is sufficient to ++ # slip past.) ++ $self->{instance}->create_user('victim'); ++ ++ my $seen; ++ $talk->_imap_cmd( ++ 'LISTRIGHTS', 0, ++ { listrights => sub { $seen = 1 } }, ++ 'Other Users.victim', 'victim', ++ ); ++ ++ $self->assert_str_equals('no', $talk->get_last_completion_response); ++ $self->assert_null($seen); ++} ++ + # see also LDAP.pm for groupid tests + + 1; +diff --git a/imap/imapd.c b/imap/imapd.c +index 6aca6f4..703d835 100644 +--- a/imap/imapd.c ++++ b/imap/imapd.c +@@ -8520,9 +8520,10 @@ static void cmd_listrights(char *tag, char *name, char *identifier) + if (!r) { + rights = cyrus_acl_myrights(imapd_authstate, mbentry->acl); + +- if (!rights && !imapd_userisadmin && ++ if (!(rights & ACL_ADMIN) && !imapd_userisadmin && + !mboxname_userownsmailbox(imapd_userid, intname)) { +- r = IMAP_MAILBOX_NONEXISTENT; ++ r = (rights & ACL_LOOKUP) ? ++ IMAP_PERMISSION_DENIED : IMAP_MAILBOX_NONEXISTENT; + } + } + diff -Nru cyrus-imapd-3.10.2/debian/patches/0025-imapd-require-read-access-for-XAPPLEPUSHSERVICE-mail.patch cyrus-imapd-3.10.2/debian/patches/0025-imapd-require-read-access-for-XAPPLEPUSHSERVICE-mail.patch --- cyrus-imapd-3.10.2/debian/patches/0025-imapd-require-read-access-for-XAPPLEPUSHSERVICE-mail.patch 1970-01-01 02:00:00.000000000 +0200 +++ cyrus-imapd-3.10.2/debian/patches/0025-imapd-require-read-access-for-XAPPLEPUSHSERVICE-mail.patch 2026-08-02 12:00:00.000000000 +0200 @@ -0,0 +1,130 @@ +Description: imapd: require read access for XAPPLEPUSHSERVICE mailboxes (CVE-2026-47081) + An initial debdiff for this CVE batch was submitted by Codin + <[email protected]> and sponsored/reviewed by Bastian Germann + <[email protected]> in Debian bug #1142925. This revision adds DEP-3 + metadata and, where missing from that debdiff, folds in the matching + upstream Cassandane regression test(s). + . + This is CYR-2871, CVE-2026-47081. + . + Only accept a mailbox if the caller is its owner, an admin, or holds + ACL_READ. Anything else is silently skipped (no echo, no event), which + also preserves the existing "missing" behaviour for non-existent names. + . + This problem reported by Matthew Horsfall. + . + Co-Authored-By: Claude <[email protected]> + . + Cherry-picked from upstream commits: + 1044c0b13b cassandane: test XAPPLEPUSHSERVICE leaks other users' folders + 50f77bad30 imapd: require read access for XAPPLEPUSHSERVICE mailboxes +Author: Ricardo Signes <[email protected]> +Origin: upstream, https://github.com/cyrusimap/cyrus-imapd/commit/50f77bad30 +Bug: https://security-tracker.debian.org/tracker/CVE-2026-47081 +Bug-Debian: https://bugs.debian.org/1142925 +Bug-CVE: CVE-2026-47081 +Forwarded: not-needed +Last-Update: 2026-07-25 + +diff --git a/cassandane/Cassandane/Cyrus/XApplePushService.pm b/cassandane/Cassandane/Cyrus/XApplePushService.pm +new file mode 100644 +index 0000000..497d505 +--- /dev/null ++++ b/cassandane/Cassandane/Cyrus/XApplePushService.pm +@@ -0,0 +1,40 @@ ++# SPDX-License-Identifier: BSD-3-Clause-CMU ++# See COPYING file at the root of the distribution for more details. ++ ++package Cassandane::Cyrus::XApplePushService; ++use strict; ++use warnings; ++ ++use base qw(Cassandane::Cyrus::TestCase); ++use Cassandane::Util::Log; ++ ++sub new ++{ ++ my ($class, @args) = @_; ++ ++ my $config = Cassandane::Config->default()->clone(); ++ $config->set(event_groups => 'applepushservice'); ++ $config->set(aps_topic => 'mail'); ++ ++ return $class->SUPER::new({ ++ adminstore => 1, ++ config => $config, ++ services => ['imap'], ++ }, @args); ++} ++ ++sub set_up ++{ ++ my ($self) = @_; ++ $self->SUPER::set_up(); ++} ++ ++sub tear_down ++{ ++ my ($self) = @_; ++ $self->SUPER::tear_down(); ++} ++ ++use Cassandane::Tiny::Loader 'tiny-tests/XApplePushService'; ++ ++1; +diff --git a/cassandane/tiny-tests/XApplePushService/leak_other_user b/cassandane/tiny-tests/XApplePushService/leak_other_user +new file mode 100644 +index 0000000..ec75872 +--- /dev/null ++++ b/cassandane/tiny-tests/XApplePushService/leak_other_user +@@ -0,0 +1,34 @@ ++#!perl ++use Cassandane::Tiny; ++ ++sub test_leak_other_user ++{ ++ my ($self) = @_; ++ ++ my $admintalk = $self->{adminstore}->get_client; ++ my $talk = $self->{store}->get_client; ++ ++ # Create a victim user and a subfolder on their account. The attacker ++ # holds nothing on it beyond the "anyone p" default ACL. ++ $self->{instance}->create_user('victim'); ++ $admintalk->create('user.victim.bah') or die "create: " . $admintalk->get_last_error; ++ ++ my @responses; ++ $talk->_imap_cmd( ++ 'XAPPLEPUSHSERVICE', 0, ++ { xapplepushservice => sub { push @responses, [ @{$_[1]} ] } }, ++ 'mailboxes', [ 'Other Users.victim.bah' ], ++ 'aps-version', '2', ++ 'aps-account-id', 'accountid', ++ 'aps-device-token', 'devtoken', ++ 'aps-subtopic', 'subtopic', ++ ); ++ ++ # Every invocation of XAPPLEPUSHSERVICE gets the trailing "aps-version" ++ # status response; that's fine. What must not appear is a ++ # * XAPPLEPUSHSERVICE "mailbox" "<name>" echo, which both confirms the ++ # mailbox exists and (via mboxevent_set_applepushservice) subscribes ++ # the attacker's APNS device to the victim's mail. ++ my @mailbox_echoes = grep { ($_->[0] // '') eq 'mailbox' } @responses; ++ $self->assert_deep_equals([], \@mailbox_echoes); ++} +diff --git a/imap/imapd.c b/imap/imapd.c +index 3b3d36c..6aca6f4 100644 +--- a/imap/imapd.c ++++ b/imap/imapd.c +@@ -14794,7 +14794,10 @@ static void cmd_xapplepushservice(const char *tag, + char *intname = + mboxname_from_external(name, &imapd_namespace, imapd_userid); + r = mlookup(tag, name, intname, &mbentry); +- if (!r && mbtype_isa(mbentry->mbtype) == MBTYPE_EMAIL) { ++ if (!r && mbtype_isa(mbentry->mbtype) == MBTYPE_EMAIL && ++ (imapd_userisadmin || ++ mboxname_userownsmailbox(imapd_userid, intname) || ++ (cyrus_acl_myrights(imapd_authstate, mbentry->acl) & ACL_READ))) { + strarray_push(¬if_mailboxes, name); + if (applepushserviceargs->aps_version >= 2) { + prot_puts(imapd_out, "* XAPPLEPUSHSERVICE \"mailbox\" "); diff -Nru cyrus-imapd-3.10.2/debian/patches/0026-imapd.c-re-check-URLAUTH-authorizer-access-just-in-t.patch cyrus-imapd-3.10.2/debian/patches/0026-imapd.c-re-check-URLAUTH-authorizer-access-just-in-t.patch --- cyrus-imapd-3.10.2/debian/patches/0026-imapd.c-re-check-URLAUTH-authorizer-access-just-in-t.patch 1970-01-01 02:00:00.000000000 +0200 +++ cyrus-imapd-3.10.2/debian/patches/0026-imapd.c-re-check-URLAUTH-authorizer-access-just-in-t.patch 2026-08-02 12:00:00.000000000 +0200 @@ -0,0 +1,127 @@ +Description: imapd.c: re-check URLAUTH authorizer access just in time (CVE-2026-47087) + An initial debdiff for this CVE batch was submitted by Codin + <[email protected]> and sponsored/reviewed by Bastian Germann + <[email protected]> in Debian bug #1142925. This revision adds DEP-3 + metadata and, where missing from that debdiff, folds in the matching + upstream Cassandane regression test(s). + . + This is CYR-2868, CVE-2026-47087. + . + cmd_urlfetch validated the HMAC token against the authorizer's mboxkey, + but skipped any ACL check whenever url.urlauth.access was set. Sure, + the rights existed right at that moment, but if access is revoked after + the URL is created, the URL needs to stop working. We need to just + check access JIT. + . + Co-Authored-By: Claude <[email protected]> + . + Cherry-picked from upstream commits: + be5eabb4df cassandane: tighten URLAuth ACL bypass test + fbb130b339 cassandane: fix indenting in cassandane/tiny-tests/URLAuth/perms + bdf709102c cassandane: URLAUTH must re-check authorizer ACLs at fetch time + 8380d0fb3d imapd.c: re-check URLAUTH authorizer access just in time +Author: Ricardo Signes <[email protected]> +Origin: upstream, https://github.com/cyrusimap/cyrus-imapd/commit/8380d0fb3d +Bug: https://security-tracker.debian.org/tracker/CVE-2026-47087 +Bug-Debian: https://bugs.debian.org/1142925 +Bug-CVE: CVE-2026-47087 +Forwarded: not-needed +Last-Update: 2026-07-25 + +Index: cyrus-imapd/cassandane/Cassandane/Cyrus/URLAuth.pm +=================================================================== +--- cyrus-imapd.orig/cassandane/Cassandane/Cyrus/URLAuth.pm ++++ cyrus-imapd/cassandane/Cassandane/Cyrus/URLAuth.pm +@@ -215,4 +215,61 @@ sub test_forged_empty_key + $self->assert_null($stolen); + } + ++sub test_revoked_access_invalidates_url ++ :UnixHierarchySep :AltNamespace ++{ ++ my ($self) = @_; ++ ++ my $admintalk = $self->{adminstore}->get_client; ++ ++ $self->{instance}->create_user('other'); ++ ++ $self->make_message("Secret Message"); ++ ++ # Grant 'other' read+lookup so that genurlauth will mint a token. ++ $admintalk->setacl('user/cassandane', 'other', 'lrs') ++ or die "setacl: " . $admintalk->get_last_error; ++ ++ my $svc = $self->{instance}->get_service('imap'); ++ my $store = $svc->create_store(username => 'other'); ++ my $other_talk = $store->get_client(); ++ ++ # 'other' mints a URLAUTH token while authorized. ++ my $url; ++ $other_talk->_imap_cmd( ++ 'genurlauth', 0, ++ { genurlauth => sub { $url = $_[1]->[0] } }, ++ 'imap://[email protected]/Other%20Users%2fcassandane/;uid=1;urlauth=user+other', ++ 'INTERNAL', ++ ); ++ $self->assert_not_null($url, 'genurlauth should succeed while authorized'); ++ ++ # Captured value is wrapped in quotes; strip them for urlfetch. ++ (my $bare_url = $url) =~ s/\A"(.*)"\z/$1/; ++ ++ # Sanity check: the token works while access is in place. ++ my $data; ++ $other_talk->_imap_cmd( ++ 'urlfetch', 0, ++ { urlfetch => sub { $data = $_[1]->[1] } }, ++ $bare_url, ++ ); ++ $self->assert_not_null($data, 'URL should fetch while authorizer is authorized'); ++ ++ # Revoke 'other's access entirely. ++ $admintalk->setacl('user/cassandane', 'other', '') ++ or die "setacl: " . $admintalk->get_last_error; ++ ++ # The token is cryptographically intact, but the authorizer's right to ++ # delegate the message is gone. cmd_urlfetch must re-check ACLs at fetch ++ # time and refuse, instead of trusting the token alone. ++ my $stolen; ++ $other_talk->_imap_cmd( ++ 'urlfetch', 0, ++ { urlfetch => sub { $stolen = $_[1]->[1] } }, ++ $bare_url, ++ ); ++ $self->assert_null($stolen, 'URL must not fetch after authorizer access revoked'); ++} ++ + 1; +Index: cyrus-imapd/imap/imapd.c +=================================================================== +--- cyrus-imapd.orig/imap/imapd.c ++++ cyrus-imapd/imap/imapd.c +@@ -14108,6 +14108,26 @@ static void cmd_urlfetch(char *tag) + } + if (r) goto err; + ++ /* For URLAUTH-protected URLs, re-check the authorizer's ACL. The ++ * HMAC asserts that authorization DID exist, but may have since been ++ * revoked. We can't read the ACL via state->mailbox here: when the ++ * URL targets the currently-selected mailbox we reuse imapd_index, ++ * whose mailbox handle is closed between commands. */ ++ if (url.urlauth.access) { ++ mbentry_t *authz_mbentry = NULL; ++ r = mlookup(NULL, NULL, intname, &authz_mbentry); ++ if (!r) { ++ struct auth_state *authzstate = auth_newstate(url.user); ++ int authz_rights = ++ cyrus_acl_myrights(authzstate, authz_mbentry->acl); ++ auth_freestate(authzstate); ++ if (!(authz_rights & ACL_READ)) ++ r = IMAP_BADURL; ++ } ++ mboxlist_entry_free(&authz_mbentry); ++ if (r) goto err; ++ } ++ + if (url.uidvalidity && + (state->mailbox->i.uidvalidity != url.uidvalidity)) { + r = IMAP_BADURL; diff -Nru cyrus-imapd-3.10.2/debian/patches/0027-imapd-require-read-access-on-mailbox-in-GENURLAUTH.patch cyrus-imapd-3.10.2/debian/patches/0027-imapd-require-read-access-on-mailbox-in-GENURLAUTH.patch --- cyrus-imapd-3.10.2/debian/patches/0027-imapd-require-read-access-on-mailbox-in-GENURLAUTH.patch 1970-01-01 02:00:00.000000000 +0200 +++ cyrus-imapd-3.10.2/debian/patches/0027-imapd-require-read-access-on-mailbox-in-GENURLAUTH.patch 2026-08-02 12:00:00.000000000 +0200 @@ -0,0 +1,107 @@ +Description: imapd: require read access on mailbox in GENURLAUTH (CVE-2026-47086) + An initial debdiff for this CVE batch was submitted by Codin + <[email protected]> and sponsored/reviewed by Bastian Germann + <[email protected]> in Debian bug #1142925. This revision adds DEP-3 + metadata and, where missing from that debdiff, folds in the matching + upstream Cassandane regression test(s). + . + This is CYR-2868, CVE-2026-47086. + . + You can't grant access to things you can't access! Without this check, + any authenticated user could mint a URLAUTH token for any mailbox they + could name, because cmd_urlfetch skipped the ACL check whenever + urlauth.access is set -- it trusts that the presence of a valid HMAC + means the authorizing user was entitled to delegate. + . + Require the authorizer to have at least ACL_READ on the mailbox before + issuing a token. + . + This problem reported by Matthew Horsfall. + . + Co-Authored-By: Claude <[email protected]> + . + Cherry-picked from upstream commits: + 88d9bf6e7e cassandane: test for ACL bypass in URLAuth + e7a28ccf9b imapd: require read access on mailbox in GENURLAUTH +Author: Ricardo Signes <[email protected]> +Origin: upstream, https://github.com/cyrusimap/cyrus-imapd/commit/e7a28ccf9b +Bug: https://security-tracker.debian.org/tracker/CVE-2026-47086 +Bug-Debian: https://bugs.debian.org/1142925 +Bug-CVE: CVE-2026-47086 +Forwarded: not-needed +Last-Update: 2026-07-25 + +Index: cyrus-imapd/cassandane/Cassandane/Cyrus/URLAuth.pm +=================================================================== +--- cyrus-imapd.orig/cassandane/Cassandane/Cyrus/URLAuth.pm ++++ cyrus-imapd/cassandane/Cassandane/Cyrus/URLAuth.pm +@@ -272,4 +272,50 @@ sub test_revoked_access_invalidates_url + $self->assert_null($stolen, 'URL must not fetch after authorizer access revoked'); + } + ++sub test_perms ++ :UnixHierarchySep :AltNamespace ++{ ++ my ($self) = @_; ++ ++ $self->{instance}->create_user('other'); ++ ++ my $msg = $self->make_message("Message A"); ++ ++ my $url; ++ my %handlers = ( ++ genurlauth => sub { ++ my ($cmd, $params) = @_; ++ $url = $params->[0]; ++ } ++ ); ++ ++ my $svc = $self->{instance}->get_service('imap'); ++ my $store = $svc->create_store(username => 'other'); ++ my $talk = $store->get_client(); ++ ++ $talk->_imap_cmd( ++ 'genurlauth', 0, \%handlers, ++ "imap://other\@127.0.0.1/Other%20Users%2fcassandane/;uid=1;urlauth=user+other", ++ "INTERNAL", ++ ); ++ ++ # This should fail in some way, commented out to show we really do fetch the ++ # email ++# $self->assert_null($url); ++ ++ my $email; ++ %handlers = ( ++ urlfetch => sub { ++ my ($cmd, $params) = @_; ++ $email = ${$params}[1]; ++ }, ++ ); ++ ++ my $res = $talk->_imap_cmd('urlfetch', 0, \%handlers, $url); ++ ++ warn $email if $email; ++ ++ $self->assert_null($email); ++} ++ + 1; +Index: cyrus-imapd/imap/imapd.c +=================================================================== +--- cyrus-imapd.orig/imap/imapd.c ++++ cyrus-imapd/imap/imapd.c +@@ -14261,6 +14261,14 @@ static void cmd_genurlauth(char *tag) + continue; + } + ++ /* You can't give permission you don't have! */ ++ if (!imapd_userisadmin && ++ !(cyrus_acl_myrights(imapd_authstate, mbentry->acl) & ACL_READ)) { ++ mboxlist_entry_free(&mbentry); ++ r = IMAP_BADURL; ++ goto err; ++ } ++ + mboxlist_entry_free(&mbentry); + + /* lookup key */ diff -Nru cyrus-imapd-3.10.2/debian/patches/0028-imapd-fix-cmd_delete-force-handling.patch cyrus-imapd-3.10.2/debian/patches/0028-imapd-fix-cmd_delete-force-handling.patch --- cyrus-imapd-3.10.2/debian/patches/0028-imapd-fix-cmd_delete-force-handling.patch 1970-01-01 02:00:00.000000000 +0200 +++ cyrus-imapd-3.10.2/debian/patches/0028-imapd-fix-cmd_delete-force-handling.patch 2026-08-02 12:00:00.000000000 +0200 @@ -0,0 +1,116 @@ +Description: imapd: fix cmd_delete 'force' handling (LOCALDELETE ACL bypass) (CVE-2026-47084) + An initial debdiff for this CVE batch was submitted by Codin + <[email protected]> and sponsored/reviewed by Bastian Germann + <[email protected]> in Debian bug #1142925. This revision adds DEP-3 + metadata and, where missing from that debdiff, folds in the matching + upstream Cassandane regression test(s). + . + This is CYR-2867, CVE-2026-47084. + . + Non-admin users could invoke the admin-only LOCALDELETE command + and delete mailboxes without permission, because MBOXLIST_DELETE_FORCE + wasn't being plumbed through to mboxlist_deletemailbox. + . + Reported by Michael Lynch (mtlynch.io). + . + Cherry-picked from upstream commits: + 2c871deac5 Delete: test localdelete of other user's mailbox + 304255f5be imapd: fix cmd_delete 'force' handling + 7dfc43c681 MurderIMAP: localdelete requires admin privs +Author: ellie timoney <[email protected]> +Origin: upstream, https://github.com/cyrusimap/cyrus-imapd/commit/304255f5be +Bug: https://security-tracker.debian.org/tracker/CVE-2026-47084 +Bug-Debian: https://bugs.debian.org/1142925 +Bug-CVE: CVE-2026-47084 +Forwarded: not-needed +Last-Update: 2026-07-25 + +diff --git a/cassandane/Cassandane/Cyrus/Delete.pm b/cassandane/Cassandane/Cyrus/Delete.pm +index 10f9fb6..fc778af 100644 +--- a/cassandane/Cassandane/Cyrus/Delete.pm ++++ b/cassandane/Cassandane/Cyrus/Delete.pm +@@ -1085,4 +1085,30 @@ sub test_cyr_expire_delete_noexpire + $self->check_syslog($self->{instance}); + } + ++sub test_localdelete_other ++ :UnixHierarchySep :AltNamespace ++{ ++ my ($self) = @_; ++ ++ $self->{instance}->create_user("other"); ++ ++ my $admintalk = $self->{adminstore}->get_client(); ++ $admintalk->create('user/other/foo'); ++ $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); ++ $admintalk->setacl('user/other/foo', 'cassandane' => 'l'); ++ $self->assert_str_equals('ok', $admintalk->get_last_completion_response()); ++ ++ my $imaptalk = $self->{store}->get_client(); ++ ++ # shouldn't be able to delete another user's mailbox ++ $imaptalk->delete('Other Users/other/foo'); ++ $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); ++ $self->assert_matches(qr{Permission denied}, $imaptalk->get_last_error()); ++ ++ # shouldn't be able to localdelete it either! ++ $imaptalk->localdelete('Other Users/other/foo'); ++ $self->assert_str_equals('no', $imaptalk->get_last_completion_response()); ++ $self->assert_matches(qr{Permission denied}, $imaptalk->get_last_error()); ++} ++ + 1; +diff --git a/cassandane/Cassandane/Cyrus/MurderIMAP.pm b/cassandane/Cassandane/Cyrus/MurderIMAP.pm +index fbf631b..0a83897 100644 +--- a/cassandane/Cassandane/Cyrus/MurderIMAP.pm ++++ b/cassandane/Cassandane/Cyrus/MurderIMAP.pm +@@ -294,14 +294,18 @@ sub test_move_to_backend_nonexistent + + my $frontend = $self->{frontend_store}->get_client(); + my $backend = $self->{backend1_store}->get_client(); ++ my $backend_admin = $self->{backend1_adminstore}->get_client(); + + # create a destination folder (on both frontend and backend) + $frontend->create($dest_folder); + $self->assert_str_equals('ok', $frontend->get_last_completion_response()); + + # nuke the destination folder (on the backend only) +- $backend->localdelete($dest_folder); +- $self->assert_str_equals('ok', $backend->get_last_completion_response()); ++ # n.b. 'INBOX' is part of the mailbox name, it's not just the cassandane ++ # user's inbox, hence the weird admin path here. Not sure why I did it ++ # that way, might have been just what was in the bug report. ++ $backend_admin->localdelete('user.cassandane.INBOX.dest'); ++ $self->assert_str_equals('ok', $backend_admin->get_last_completion_response()); + + my $f_folders = $frontend->list('', '*'); + $self->assert_deep_equals( +diff --git a/imap/imapd.c b/imap/imapd.c +index 2cb5126..51aecc7 100644 +--- a/imap/imapd.c ++++ b/imap/imapd.c +@@ -7346,19 +7346,22 @@ static void cmd_delete(char *tag, char *name, int localonly, int force) + /* local mailbox */ + if (!r) { + int isadmin = imapd_userisadmin || imapd_userisproxyadmin; ++ int delflags = force ? MBOXLIST_DELETE_FORCE : 0; + + if (mbname_isdeleted(mbname)) { + r = mboxlist_deletemailbox(mbname_intname(mbname), + isadmin, imapd_userid, + imapd_authstate, mboxevent, +- MBOXLIST_DELETE_LOCALONLY); ++ delflags | MBOXLIST_DELETE_LOCALONLY); + } + else if (!isadmin && mbname_issystem(mbname)) { + r = IMAP_PERMISSION_DENIED; + } + else { + delete_user = mboxname_isusermailbox(mbname_intname(mbname), 1); +- int delflags = (1-force) ? MBOXLIST_DELETE_CHECKACL : 0; ++ ++ if (!force) ++ delflags |= MBOXLIST_DELETE_CHECKACL; + + if (!delete_user && mboxlist_haschildren(mbname_intname(mbname))) { + r = IMAP_MAILBOX_HASCHILDREN; diff -Nru cyrus-imapd-3.10.2/debian/patches/series cyrus-imapd-3.10.2/debian/patches/series --- cyrus-imapd-3.10.2/debian/patches/series 2026-07-12 21:29:05.000000000 +0200 +++ cyrus-imapd-3.10.2/debian/patches/series 2026-08-02 12:00:00.000000000 +0200 @@ -8,3 +8,12 @@ 0018-increase-test-timeout.patch #0019-propagate-XXFLAGS.patch eventsource-without-websocket.patch +0020-lmtp_sieve.c-enforce-ACL-on-vacation-fcc-destination.patch +0021-imap-message.c-fix-heap-exposure-in-nested-MIME-comm.patch +0022-imapd-MULTISEARCH-must-check-ACL_READ-on-each-mailbo.patch +0023-imapd-reject-URLFETCH-when-no-mboxkey-exists-for-the.patch +0024-imapd-LISTRIGHTS-requires-admin-rights.patch +0025-imapd-require-read-access-for-XAPPLEPUSHSERVICE-mail.patch +0026-imapd.c-re-check-URLAUTH-authorizer-access-just-in-t.patch +0027-imapd-require-read-access-on-mailbox-in-GENURLAUTH.patch +0028-imapd-fix-cmd_delete-force-handling.patch