Hi, Here are tested debdiffs for bookworm- and trixie-security fixing these issues. As for the previous uploads, I suggest to follow 1.6.x for trixie-security (the upstream diff [0] is pretty targeted already) and backport targeted fixes for bookworm-security.
There is a lot of added complexity in the custom patch d/p/Avoid-dependency-on-new-package-mlocati-ip-lib.patch (which fixes CVE-2026-35540 and its follow-up CVE-2026-48843 in a native fashion), but I also added a lot of unit tests for the new function and tested it on 32-bits platform (where 2³²-1 exceeds PHP_INT_MAX) and on both big and little endian platforms. The reason for the added complexity is to cover IPv4 that are not formatted in the dotted-quad and dotted-decimal notation, see inet_aton(3). For instance http://192.11010306, http://0xc0A80102, and http://192.0250.1.2 all escape the original fix for CVE-2026-35540, but GuzzleHTTP (and also ping(1) and Firefox) would happily normalize to http://192.168.1.2 thereby causing SSRF. -- Guilhem. [0] https://github.com/roundcube/roundcubemail/compare/1.6.15...1.6.16
diffstat for roundcube-1.6.15+dfsg roundcube-1.6.16+dfsg
.github/workflows/browser_tests.yml | 2
CHANGELOG.md | 12
config/defaults.inc.php | 3
debian/changelog | 27
debian/patches/Avoid-dependency-on-new-package-mlocati-ip-lib.patch | 275
++++++++--
debian/patches/Fix-FTBFS-with-phpunit-11.patch | 98
+--
debian/patches/default-charset-utf8.patch | 4
debian/patches/use-enchant.patch | 2
plugins/filesystem_attachments/filesystem_attachments.php | 2
plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php | 2
plugins/redundant_attachments/redundant_attachments.php | 4
plugins/virtuser_query/virtuser_query.php | 10
program/include/rcmail_attachment_handler.php | 11
program/include/rcmail_sendmail.php | 12
program/js/app.js | 2
program/lib/Roundcube/rcube_imap.php | 2
program/lib/Roundcube/rcube_ldap.php | 32 -
program/lib/Roundcube/rcube_utils.php | 36 +
program/lib/Roundcube/rcube_washtml.php | 16
public_html/plugins/filesystem_attachments/filesystem_attachments.php | 2
public_html/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php | 2
public_html/plugins/redundant_attachments/redundant_attachments.php | 4
public_html/plugins/virtuser_query/virtuser_query.php | 10
public_html/program/js/app.js | 2
tests/Framework/Utils.php | 15
tests/Framework/Washtml.php | 31 -
26 files changed, 453 insertions(+), 165 deletions(-)
diff -Nru roundcube-1.6.15+dfsg/CHANGELOG.md roundcube-1.6.16+dfsg/CHANGELOG.md
--- roundcube-1.6.15+dfsg/CHANGELOG.md 2026-03-29 11:45:29.000000000 +0200
+++ roundcube-1.6.16+dfsg/CHANGELOG.md 2026-05-24 09:40:12.000000000 +0200
@@ -2,6 +2,18 @@
## Unreleased
+- Fix potential too long value in IMAP ID command (#10136)
+- Security: Fix stored XSS/HTML/CSS injection in subject field of the draft
restore dialog
+- Security: Fix CSS injection bypass in HTML sanitizer via SVG `<animate
attributeName="style">`
+- Security: Fix pre-auth SQL injection in `virtuser_query` plugin via
preg_replace backslash escape bypass
+- Security: Fix SSRF bypass via specific local address URLs
+- Security: Fix bypass of remote image blocking via CSS var()
+- Security: Fix local/private URL fetch bypass when remote resources were not
allowed
+- Security: Fix pre-auth arbitrary file delete via redis/memcache session
poisoning bypass
+- Security: Fix code injection vulnerability - remove support for code
evaluation in LDAP `autovalues` option
+
+## Release 1.6.15
+
- Fix regression where mail search would fail on non-ascii search criteria
(#10121)
- Fix regression where some data url images could get ignored/lost (#10128)
- Fix SVG Animate FUNCIRI Attribute Bypass — Remote Image Loading via
fill/filter/stroke
diff -Nru roundcube-1.6.15+dfsg/config/defaults.inc.php
roundcube-1.6.16+dfsg/config/defaults.inc.php
--- roundcube-1.6.15+dfsg/config/defaults.inc.php 2026-03-29
11:45:29.000000000 +0200
+++ roundcube-1.6.16+dfsg/config/defaults.inc.php 2026-05-24
09:40:12.000000000 +0200
@@ -1150,8 +1150,7 @@
'sub_fields' => [],
// Generate values for the following LDAP attributes automatically when
creating a new record
'autovalues' => [
- // 'uid' => 'md5(microtime())', // You may specify PHP code
snippets which are then eval'ed
- // 'mail' => '{givenname}.{sn}@mydomain.com', // or composite strings
with placeholders for existing attributes
+ // 'mail' => '{givenname}.{sn}@mydomain.com', // composite strings with
placeholders for existing attributes
],
'sort' => 'cn', // The field to sort the listing by.
'scope' => 'sub', // search mode: sub|base|list
diff -Nru roundcube-1.6.15+dfsg/debian/changelog
roundcube-1.6.16+dfsg/debian/changelog
--- roundcube-1.6.15+dfsg/debian/changelog 2026-03-30 13:40:22.000000000
+0200
+++ roundcube-1.6.16+dfsg/debian/changelog 2026-05-25 23:06:33.000000000
+0200
@@ -1,3 +1,30 @@
+roundcube (1.6.16+dfsg-0+deb13u1) trixie-security; urgency=high
+
+ * New upstream security and bugfix release (closes: #1137507).
+ + Fix CVE-2026-48842: pre-auth SQL injection in `virtuser_query plugin`
+ via `preg_replace()` backslash escape bypass.
+ + Fix CVE-2026-48843: SSRF bypass via specific local address URLs. Add
+ support non quad-dotted IPs and non-decimal fields to
+ d/p/Avoid-dependency-on-new-package-mlocati-ip-lib.patch in order to
+ match the new upstream behavior.
+ + Fix CVE-2026-48844: Code injection vulnerability via code evaluation
+ support in LDAP autovalues option. Code evaluation support has now been
+ removed.
+ + Fix CVE-2026-48845: Local/private URL fetch bypass when remote resources
+ were not allowed.
+ + Fix CVE-2026-48846: Bypass of remote image blocking via CSS `var()`.
+ + Fix CVE-2026-48847: Pre-auth arbitrary file delete via redis/memcache
+ session poisoning bypass.
+ + Fix CVE-2026-48848: CSS injection bypass in HTML sanitizer via SVG
+ <animate attributeName="style">.
+ + Fix CVE-2026-48849: Stored XSS/HTML/CSS injection in subject field of
+ the draft restore dialog.
+ + Fix PHP8 warnings.
+ + Fix potential too long value in IMAP ID command.
+ * Refresh d/patches.
+
+ -- Guilhem Moulin <[email protected]> Mon, 25 May 2026 23:06:33 +0200
+
roundcube (1.6.15+dfsg-0+deb13u1) trixie-security; urgency=high
* New upstream security and bugfix release (closes: #1131182, #1132268).
diff -Nru
roundcube-1.6.15+dfsg/debian/patches/Avoid-dependency-on-new-package-mlocati-ip-lib.patch
roundcube-1.6.16+dfsg/debian/patches/Avoid-dependency-on-new-package-mlocati-ip-lib.patch
---
roundcube-1.6.15+dfsg/debian/patches/Avoid-dependency-on-new-package-mlocati-ip-lib.patch
2026-03-30 13:40:22.000000000 +0200
+++
roundcube-1.6.16+dfsg/debian/patches/Avoid-dependency-on-new-package-mlocati-ip-lib.patch
2026-05-25 23:06:33.000000000 +0200
@@ -4,15 +4,16 @@
Which as of today is not present in Debian. The dependency was
introduced in 27ec6cc9cb25e1ef8b4d4ef39ce76d619caa6870 in order to fix a
-security issue. While it can be uploaded to sid, we need another
+CVE-2026-35540. While it can be uploaded to sid, we need another
solution to fix the vulnerability for older suites.
+Bug-Debian: https://bugs.debian.org/1131182
Forwarded: not-needed
---
- composer.json-dist | 3 +--
- program/lib/Roundcube/rcube_utils.php | 45 ++++++++++++++++++++++++-----------
- tests/Framework/Utils.php | 6 +++++
- 3 files changed, 38 insertions(+), 16 deletions(-)
+ composer.json-dist | 3 +-
+ program/lib/Roundcube/rcube_utils.php | 160 +++++++++++++++++++++++++++++-----
+ tests/Framework/Utils.php | 85 ++++++++++++++++++
+ 3 files changed, 224 insertions(+), 24 deletions(-)
diff --git a/composer.json-dist b/composer.json-dist
index 1807004..ca3de26 100644
@@ -29,35 +30,164 @@
"require-dev": {
"phpunit/phpunit": "^9"
diff --git a/program/lib/Roundcube/rcube_utils.php
b/program/lib/Roundcube/rcube_utils.php
-index 5e8ac84..d20a509 100644
+index be28a85..e9ebf54 100644
--- a/program/lib/Roundcube/rcube_utils.php
+++ b/program/lib/Roundcube/rcube_utils.php
-@@ -1,7 +1,5 @@
+@@ -1,8 +1,5 @@
<?php
-use IPLib\Factory;
+-use IPLib\ParseStringFlag;
-
/*
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
-@@ -435,24 +433,43 @@ class rcube_utils
- if (is_string($host)) {
- // TODO: This is pretty fast, but a single message can contain
multiple links
- // to the same target, maybe we should do some in-memory caching.
-- if ($address = Factory::parseAddressString($host = trim($host,
'[]'))) {
-+ $address = trim($host, '[]');
-+ if ((bool)preg_match('/^([0-9a-f:]*:)?
-+
((?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[1-9][0-9]|0{0,2}[0-9])\.){3}
-+
(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[1-9][0-9]|0{0,2}[0-9]))$/Dix',
-+ $address, $matches)) {
-+ /* trim leading zeros from IPv4 octets (GuzzleHTTP sanitizes
such invalid addresses) */
-+ $address = @inet_pton($matches[1] .
preg_replace('/\b0+(?=\d)/', '', $matches[2]));
+@@ -422,6 +419,116 @@ class rcube_utils
+ return asciiwords($str, true, '_');
+ }
+
++ /**
++ * Converts a human readable IP address to its packed in_addr
representation
++ * Like the builtin inet_pton() function, but fallback to
++ * inet_aton(3)-behavior for IPv4.
++ *
++ * @param string $ip A human readable IPv4 or IPv6 address.
++ *
++ * @return string|false
++ */
++ public static function inet_pton2(string $ip) : string|bool {
++ $address = @inet_pton($ip);
++ if (is_string($address)) {
++ return $address;
++ }
++
++ /* emulate inet_aton(3) behavior */
++ $fields = explode('.', $ip, 4); /* at most 4 dot-separated fields */
++ $field_last = array_pop($fields);
++ if (!is_string($field_last)) {
++ return false;
++ }
++ $address = "\x00\x00\x00\x00";
++ foreach ($fields as $i => $field) {
++ /* process each field except the last one; values must not exceed
0xFF */
++ if (preg_match('/^0[xX]0*([0-9A-Fa-f]{0,2})$/D', $field,
$matches)) {
++ /* hexadecimal field, 0x00 to 0xFF */
++ $b = $matches[1] === '' ? 0 : hexdec($matches[1]);
++ } elseif (preg_match('/^0+([1-3][0-7][0-7]|[1-7][0-7]?|)$/D',
$field, $matches)) {
++ /* octal field, o0 to o377 */
++ $b = $matches[1] === '' ? 0 : intval($matches[1], 8);
++ } elseif
(preg_match('/^(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?)$/D', $field)) {
++ /* decimal field, 1 to 255 */
++ $b = intval($field, 10);
+ } else {
-+ $address = @inet_pton($address);
++ /* invalid field */
++ return false;
+ }
++ $address[$i] = chr($b);
++ }
+
-+ if (is_string($address)) {
- $nets = [
++ /* split into 2 groups of 16 bits to avoid overflowing PHP_INT_MAX on
32-bits platforms */
++ $hi = $li = 0x0000;
++ $carry = null;
++ if (preg_match('/^0[xX]0*([0-9A-Fa-f]{0,8})$/D', $field_last,
$matches)) {
++ /* hexadecimal field, 0x00 to 0xFFFFFFFF */
++ if (strlen($matches[1]) > 4) {
++ $hi = hexdec(substr($matches[1], 0, -4));
++ $li = hexdec(substr($matches[1], -4));
++ } elseif ($matches[1] !== '') {
++ $li = hexdec($matches[1]);
++ }
++ } elseif (preg_match('/^0+([1-3][0-7]{10}|[1-7][0-7]{0,9}|)$/D',
++ $field_last, $matches)) {
++ /* octal field, o0 to o37777777777 */
++ if ($matches[1] !== '') {
++ $base = 8;
++ $hi = intval(substr($matches[1], 0, 10), $base);
++ $li = $hi % (1 << 16);
++ $hi = intdiv($hi, 1 << 16); /* <= 65535 */
++ if (strlen($matches[1]) > 10) {
++ $carry = substr($matches[1], -1); /* last digit */
++ }
++ }
++ } elseif (preg_match('/^[1-9][0-9]{0,9}$/D', $field_last)) {
++ /* decimal field, 1 to 9999999999 (values >=2^32 are rejected
later) */
++ $base = 10;
++ $hi = intval(substr($field_last, 0, 9), $base);
++ $li = $hi % (1 << 16);
++ $hi = intdiv($hi, 1 << 16); /* <= 152587 */
++ if (strlen($field_last) > 9) {
++ $carry = substr($field_last, -1); /* last digit */
++ }
++ } else {
++ /* invalid field */
++ return false;
++ }
++ if ($carry !== null) {
++ /* carry over the last digit; there won't be any overflow
++ * since the value won't exceed 152587 * $base + $base-1 */
++ $li = $li * $base + intval($carry, $base);
++ $hi = $hi * $base + intdiv($li, 1 << 16);
++ $li %= 1 << 16;
++ }
++
++ $i = count($fields);
++ if ($hi === 0x0000 && $li <= 0x00ff && $i <= 3) {
++ /* first 0-3 bytes have been set already, now set the last byte */
++ $address[3] = chr($li);
++ } elseif ($hi === 0x0000 && $li <= 0xffff && $i <= 2) {
++ /* first 0-2 bytes have been set already, now set the last 2
bytes */
++ $address[2] = chr( intdiv($li, 1 << 8) );
++ $address[3] = chr( $li % (1 << 8) );
++ } elseif ($hi <= 0x00ff && $li <= 0xffff && $i <= 1) {
++ /* first 0-1 bytes have been set already, now set the last 3
bytes */
++ $address[1] = chr( $hi );
++ $address[2] = chr( intdiv($li, 1 << 8) );
++ $address[3] = chr( $li % (1 << 8) );
++ } elseif ($hi <= 0xffff && $li <= 0xffff && $i === 0) {
++ /* set all 4 bytes */
++ $address[0] = chr( intdiv($hi, 1 << 8) );
++ $address[1] = chr( $hi % (1 << 8) );
++ $address[2] = chr( intdiv($li, 1 << 8) );
++ $address[3] = chr( $li % (1 << 8) );
++ } else {
++ /* overflow, all numeric values must be <2^32 */
++ return false;
++ }
++ return $address;
++ }
++
+ /**
+ * Check if an URL point to a local network location.
+ *
+@@ -434,37 +541,46 @@ class rcube_utils
+ $host = parse_url($url, \PHP_URL_HOST);
+
+ if (is_string($host)) {
+- $options = ParseStringFlag::IPV4_MAYBE_NON_DECIMAL
+- | ParseStringFlag::IPV4SUBNET_MAYBE_COMPACT
+- | ParseStringFlag::IPV4ADDRESS_MAYBE_NON_QUAD_DOTTED
+- | ParseStringFlag::MAY_INCLUDE_ZONEID;
+-
+ $host = trim($host, '[]');
+
+- // IPLib does not seem to work with IPv6 syntax for IPv4 addresses
++ /* IPv4-mapped IPv6 addresses (RFC4291 2.5.5) */
+ $host = preg_replace('/^::ffff:/i', '', $host);
+
+ if (preg_match('/([0-9a-f.-]+)\.nip\.io$/i', $host, $matches)) {
+ $host = trim($matches[1], '-.');
+ }
+
++ if (strpos($host, ':') !== false && ($n = strpos($host, '%')) >
0) {
++ /* drop the zone ID */
++ $host = substr($host, 0, $n);
++ }
++
+ // TODO: This is pretty fast, but a single message can contain
multiple links
+ // to the same target, maybe we should do some in-memory caching.
+- if ($address = Factory::parseAddressString($host, $options)) {
+- $nets = [
+- '0.0.0.0',
- '127.0.0.0/8', // loopback
- '10.0.0.0/8', // RFC1918
- '172.16.0.0/12', // RFC1918
@@ -65,6 +195,9 @@
- '169.254.0.0/16', // link-local / cloud metadata
- '::1/128',
- 'fc00::/7',
++ if (is_string($address = \rcube_utils::inet_pton2($host))) {
++ $nets = [
++ ['0.0.0.0', '0.0.0.0'],
+ ['127.0.0.0', '127.255.255.255'], // loopback
+ ['10.0.0.0', '10.255.255.255'], // RFC1918
+ ['172.16.0.0', '172.31.255.255'], // RFC1918
@@ -92,15 +225,98 @@
return true;
}
}
--
- return false;
- }
-
diff --git a/tests/Framework/Utils.php b/tests/Framework/Utils.php
-index a27829c..399cea7 100644
+index fe9f435..66e856a 100644
--- a/tests/Framework/Utils.php
+++ b/tests/Framework/Utils.php
-@@ -585,12 +585,18 @@ class Framework_Utils extends TestCase
+@@ -571,6 +571,86 @@ class Framework_Utils extends TestCase
+ }
+ }
+
++ /**
++ * Test inet_pton2()
++ *
++ * @dataProvider provide_inet_pton2_cases
++ */
++ #[DataProvider('provide_inet_pton2_cases')]
++ public function test_inet_pton2($input, $output)
++ {
++ $r = \rcube_utils::inet_pton2($input);
++ if (is_bool($output)) {
++ $this->assertSame($output, $r);
++ } else {
++ $this->assertTrue(is_string($r));
++ $addr = @inet_pton($output);
++ $this->assertSame($r, $addr, bin2hex($r) . " != " .
bin2hex($addr));
++ }
++ }
++
++ /**
++ * Test-Cases for inet_pton2() test
++ */
++ public static function provide_inet_pton2_cases(): iterable
++ {
++ return [
++ ['', false],
++ ['0xx', false],
++ ['08', false],
++ ['a', false],
++ ['0.0.0.0.0', false],
++ ['0..0', false],
++ ['0.', false],
++ ['.0', false],
++ ['256.0', false],
++ ['0x100.0', false],
++ ['0400.0', false],
++ ['4294967296', false],
++ ['9999999999', false],
++ ['18446744073709551616', false],
++ ['040000000000', false],
++ ['077777777777', false],
++ ['0x100000000', false],
++ [' 123', false],
++ ['123 ', false],
++ ['-1', false],
++
++ ['0', '0.0.0.0'],
++ ['0x0', '0.0.0.0'],
++ ['0x', '0.0.0.0'],
++ ['00', '0.0.0.0'],
++ ['123', '0.0.0.123'],
++ ['0xF', '0.0.0.15'],
++ ['0xFA', '0.0.0.250'],
++ ['061', '0.0.0.49'],
++ ['12345', '0.0.48.57'],
++ ['0X89AB', '0.0.137.171'],
++ ['0x89ABC', '0.8.154.188'],
++ ['012345', '0.0.20.229'],
++ ['1234567', '0.18.214.135'],
++ ['0xabcde', '0.10.188.222'],
++ ['01234567', '0.5.57.119'],
++ ['123456789', '7.91.205.21'],
++ ['0xdeadbeef', '222.173.190.239'],
++ ['07654321012', '62.177.162.10'],
++ ['4294967295', '255.255.255.255'],
++ ['2147483648', '128.0.0.0'],
++ ['0xfffefdfc', '255.254.253.252'],
++ ['037777777777', '255.255.255.255'],
++ ['020000000000', '128.0.0.0'],
++ ['226.000.000.037', '226.0.0.31'],
++ ['0x7f.1', '127.0.0.1'],
++ ['0x7f.256', '127.0.1.0'],
++ ['0x7f.0.256', '127.0.1.0'],
++ ['0377.0xfedc', '255.0.254.220'],
++ ['0x7f.0377.12345', '127.255.48.57'],
++ ['1.2.3.4', '1.2.3.4'],
++ ['0.1.2.3', '0.1.2.3'],
++ ['7.010.0x.0xa', '7.8.0.10'],
++ ];
++ }
++
+ /**
+ * Test is_local_url()
+ *
+@@ -590,12 +670,17 @@ class Framework_Utils extends TestCase
return [
// Local hosts
['https://127.0.0.1', true],
@@ -110,12 +326,11 @@
['https://192.168.0.100', true],
['https://169.254.0.200', true],
['http://[fc00::1]', true],
++ ['http://[fc00::1%1]', true],
['ftp://[::1]:8080', true],
+ ['https://[127.0.0.1]', true],
+ ['https://[::127.0.0.1]', true],
-+ ['https://[::127.0.0.001]', true],
+ ['https://[::ffff:192.168.1.2]', true],
-+ ['https://[::ffff:192.168.01.002]', true],
['//127.0.0.1', true],
['http://localhost', true],
['http://localhost.localdomain', true],
diff -Nru roundcube-1.6.15+dfsg/debian/patches/default-charset-utf8.patch
roundcube-1.6.16+dfsg/debian/patches/default-charset-utf8.patch
--- roundcube-1.6.15+dfsg/debian/patches/default-charset-utf8.patch
2026-03-30 13:40:22.000000000 +0200
+++ roundcube-1.6.16+dfsg/debian/patches/default-charset-utf8.patch
2026-05-25 23:06:33.000000000 +0200
@@ -8,10 +8,10 @@
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/config/defaults.inc.php b/config/defaults.inc.php
-index 282d502..71fb855 100644
+index 6e80aa9..36a5731 100644
--- a/config/defaults.inc.php
+++ b/config/defaults.inc.php
-@@ -1268,7 +1268,7 @@ $config['collected_senders'] = true;
+@@ -1267,7 +1267,7 @@ $config['collected_senders'] = true;
// ----------------------------------
// Use this charset as fallback for message decoding
diff -Nru roundcube-1.6.15+dfsg/debian/patches/Fix-FTBFS-with-phpunit-11.patch
roundcube-1.6.16+dfsg/debian/patches/Fix-FTBFS-with-phpunit-11.patch
--- roundcube-1.6.15+dfsg/debian/patches/Fix-FTBFS-with-phpunit-11.patch
2026-03-30 13:40:22.000000000 +0200
+++ roundcube-1.6.16+dfsg/debian/patches/Fix-FTBFS-with-phpunit-11.patch
2026-05-25 23:06:33.000000000 +0200
@@ -9996,7 +9996,7 @@
$idents = $user->list_identities();
diff --git a/tests/Framework/Utils.php b/tests/Framework/Utils.php
-index 3baa861..a27829c 100644
+index e4e05e0..fe9f435 100644
--- a/tests/Framework/Utils.php
+++ b/tests/Framework/Utils.php
@@ -1,11 +1,15 @@
@@ -10167,7 +10167,7 @@
$this->assertSame('/* evil! */', $mod);
$mod = \rcube_utils::mod_css_styles("@\\69mport
url('http://localhost/somestuff/css/master.css');", 'rcmbody');
-@@ -261,19 +270,19 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -266,19 +275,19 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
$this->assertSame('#rcmbody p { background: none !important; }',
$mod);
// position: fixed (#5264)
@@ -10193,7 +10193,7 @@
$this->assertEquals("#rcmbody .test { position: absolute; top: 0; }",
$mod, "Replace position:fixed with position:absolute (5)");
// missing closing brace
-@@ -284,27 +293,27 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -289,27 +298,27 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
$this->assertSame('#rcmbody .test { position: absolute; }', $mod,
'Replace position:fixed with position:absolute (7)');
// allow data URIs with images (#5580)
@@ -10227,7 +10227,7 @@
$this->assertSame("#rcmbody { color: red; }", $mod);
$style = 'body { background:url(alert('URL!')); }';
-@@ -338,7 +347,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -343,7 +352,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
:root * { color: red; }
:root > * { top: 0; }
';
@@ -10236,7 +10236,7 @@
$this->assertStringContainsString('#rc .testone', $mod);
$this->assertStringContainsString('#rc .testthree.testfour', $mod);
-@@ -356,24 +365,24 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -361,24 +370,24 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
function test_xss_entity_decode()
{
@@ -10266,7 +10266,7 @@
{
return [
[
-@@ -448,9 +457,10 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -453,9 +462,10 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
*
* @dataProvider data_parse_css_block
*/
@@ -10278,7 +10278,7 @@
}
/**
-@@ -465,7 +475,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -470,7 +480,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
];
foreach ($data as $text => $res) {
@@ -10287,7 +10287,7 @@
$this->assertSame($res, $result);
}
}
-@@ -478,7 +488,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -483,7 +493,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
$data = ['', 'a,b,c', 'a', ',', ',a'];
foreach ($data as $text) {
@@ -10296,7 +10296,7 @@
$this->assertSame(explode(',', $text), $result);
}
}
-@@ -493,7 +503,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -498,7 +508,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
];
foreach ($input as $idx => $value) {
@@ -10305,7 +10305,7 @@
}
$input = [
-@@ -501,7 +511,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -506,7 +516,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
];
foreach ($input as $idx => $value) {
@@ -10314,7 +10314,7 @@
}
}
-@@ -511,13 +521,13 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -516,13 +526,13 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
function test_get_input_string()
{
$_GET = [];
@@ -10331,7 +10331,7 @@
}
/**
-@@ -525,18 +535,18 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -530,18 +540,18 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
*/
function test_is_simple_string()
{
@@ -10362,7 +10362,7 @@
}
/**
-@@ -551,7 +561,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -556,7 +566,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
];
foreach ($test as $v) {
@@ -10371,7 +10371,7 @@
$this->assertSame($v[2], $result);
}
}
-@@ -615,7 +625,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -630,7 +640,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
];
foreach ($test as $datetime => $ts) {
@@ -10380,7 +10380,7 @@
$this->assertSame($ts, $result, "Error parsing date: $datetime");
}
}
-@@ -642,7 +652,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -657,7 +667,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
];
foreach ($test as $datetime => $ts) {
@@ -10389,7 +10389,7 @@
$this->assertSame($ts, $result ? $result->format('Y-m-d') :
false, "Error parsing date: $datetime");
}
-@@ -652,7 +662,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -667,7 +677,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
];
foreach ($test as $datetime => $ts) {
@@ -10398,7 +10398,7 @@
$this->assertSame($ts, $result ? $result->format('Y-m-d H:i:s') :
false, "Error parsing date: $datetime");
}
-@@ -661,7 +671,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -676,7 +686,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
];
foreach ($test as $datetime => $ts) {
@@ -10407,7 +10407,7 @@
$this->assertSame($ts, $result ? $result->format('Y-m-d H:i:s O')
: false, "Error parsing date: $datetime");
}
}
-@@ -671,17 +681,17 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -686,17 +696,17 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
*/
function test_anytodatetime_timezone()
{
@@ -10428,7 +10428,7 @@
if ($result) $result->setTimezone($tz); // move to target
timezone for comparison
$this->assertSame($ts, $result ? $result->format('Y-m-d H:i') :
false, "Error parsing date: $datetime");
}
-@@ -700,7 +710,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -715,7 +725,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
];
foreach ($test as $data) {
@@ -10437,7 +10437,7 @@
$this->assertSame($data[2], $result, "Error formatting date: " .
$data[0]);
}
}
-@@ -719,7 +729,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -734,7 +744,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
];
foreach ($test as $input => $output) {
@@ -10446,7 +10446,7 @@
$this->assertSame($output, $result);
}
}
-@@ -744,7 +754,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -759,7 +769,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
];
foreach ($test as $input => $output) {
@@ -10455,7 +10455,7 @@
$this->assertSame($output, $result, "Error normalizing '$input'");
}
}
-@@ -767,7 +777,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -782,7 +792,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
];
foreach ($test as $idx => $params) {
@@ -10464,7 +10464,7 @@
$this->assertSame($params[2], $result, "words_match() at index
$idx");
}
}
-@@ -793,7 +803,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -808,7 +818,7 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
}
foreach ($test as $input => $output) {
@@ -10473,7 +10473,7 @@
$this->assertSame($output, $result);
}
}
-@@ -803,17 +813,17 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -818,17 +828,17 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
*/
function test_random_bytes()
{
@@ -10497,7 +10497,7 @@
{
/*
-@@ -850,9 +860,10 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -865,9 +875,10 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
* @param string $encoded Encoded email address
* @dataProvider data_idn_convert
*/
@@ -10509,7 +10509,7 @@
}
/**
-@@ -862,9 +873,10 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -877,9 +888,10 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
* @param string $encoded Encoded email address
* @dataProvider data_idn_convert
*/
@@ -10521,7 +10521,7 @@
}
/**
-@@ -872,14 +884,14 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -887,14 +899,14 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
*/
function test_idn_to_ascii_special()
{
@@ -10539,7 +10539,7 @@
{
return [
['%z', 'hostname', 'hostname'],
-@@ -894,15 +906,16 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -909,15 +921,16 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
*
* @dataProvider data_parse_host
*/
@@ -10558,7 +10558,7 @@
{
return [
[['hostname', null, null], ['hostname', null, null]],
-@@ -925,15 +938,16 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -940,15 +953,16 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
*
* @dataProvider data_parse_host_uri
*/
@@ -10577,7 +10577,7 @@
return [
['both', 'Fwd: Re: Test subject both', 'Test subject both'],
['both', 'Re: Fwd: Test subject both', 'Test subject both'],
-@@ -951,8 +965,9 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -966,8 +980,9 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
*
* @dataProvider data_remove_subject_prefix
*/
@@ -10588,7 +10588,7 @@
}
/**
-@@ -960,13 +975,13 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -975,13 +990,13 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
*/
function test_server_name()
{
@@ -10605,7 +10605,7 @@
}
/**
-@@ -976,31 +991,31 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+@@ -991,31 +1006,31 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
{
$_SERVER['test'] = 'test.com';
@@ -10804,7 +10804,7 @@
$this->assertSame($result,
"BEGIN:VCARD\r\nVERSION:3.0\r\nFN:\r\nN:;;;;\r\nEND:VCARD");
diff --git a/tests/Framework/Washtml.php b/tests/Framework/Washtml.php
-index ec1dd5d..99859a3 100644
+index 7d524f4..770ef3a 100644
--- a/tests/Framework/Washtml.php
+++ b/tests/Framework/Washtml.php
@@ -1,11 +1,14 @@
@@ -11005,7 +11005,7 @@
{
$svg1 = "<svg id='x' width='100' height='100'><a
xlink:href='javascript:alert(1)'><rect x='0' y='0' width='100' height='100'
/></a></svg>";
-@@ -533,9 +536,10 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -545,9 +548,10 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
*
* @dataProvider data_wash_svg_tests
*/
@@ -11017,7 +11017,7 @@
$washed = $washer->wash($input);
$this->assertSame($expected, $this->cleanupResult($washed), "SVG
content");
-@@ -544,7 +548,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -556,7 +560,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
/**
* Test cases for various XSS issues
*/
@@ -11026,7 +11026,7 @@
{
return [
[
-@@ -599,9 +603,10 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -611,9 +615,10 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
*
* @dataProvider data_wash_xss_tests
*/
@@ -11038,7 +11038,7 @@
$washed = $washer->wash($input);
$this->assertSame($expected, $this->cleanupResult($washed), "XSS
issues");
-@@ -615,7 +620,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -627,7 +632,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
$html = "<img style='position:fixed' /><img style=\"position:/**/
fixed; top:10px\" />";
$exp = "<img style=\"position: absolute\" /><img style=\"position:
absolute; top: 10px\" />";
@@ -11047,7 +11047,7 @@
$washed = $washer->wash($html);
$this->assertTrue(strpos($washed, $exp) !== false, "Position:fixed
(#5264)");
-@@ -659,7 +664,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -671,7 +676,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
<annotation encoding="TeX">I_D = \frac{1}{2} k_n \frac{W}{L}
(V_{GS}-V_t)^2</annotation>
</semantics></math>';
@@ -11056,7 +11056,7 @@
$washed = $washer->wash($mathml);
// remove whitespace between tags
-@@ -676,7 +681,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -688,7 +693,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
{
$html = "<input type=\"image\" src=\"http://TRACKING_URL/\">";
@@ -11065,7 +11065,7 @@
$washed = $washer->wash($html);
$this->assertTrue($washer->extlinks);
-@@ -684,7 +689,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -696,7 +701,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
$html = "<video src=\"http://TRACKING_URL/\">";
@@ -11074,7 +11074,7 @@
$washed = $washer->wash($html);
$this->assertTrue($washer->extlinks);
-@@ -705,14 +710,14 @@ class Framework_Washtml extends
PHPUnit\Framework\TestCase
+@@ -724,14 +729,14 @@ class Framework_Washtml extends
PHPUnit\Framework\TestCase
];
foreach ($html as $item) {
@@ -11082,7 +11082,7 @@
+ $washer = new \rcube_washtml();
$washed = $washer->wash($item[0]);
- $this->assertSame($item[1], $washer->extlinks);
+ $this->assertSame($item[1], $washer->extlinks, "Failed on:
{$item[0]}");
}
foreach ($html as $item) {
@@ -11091,7 +11091,7 @@
$washed = $washer->wash($item[0]);
$this->assertFalse($washer->extlinks);
-@@ -723,7 +728,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -742,7 +747,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
{
$html = '<textarea><p style="x:</textarea><img src=x
onerror=alert(1)>">';
@@ -11100,7 +11100,7 @@
$washed = $washer->wash($html);
$this->assertStringNotContainsString('onerror=alert(1)>', $washed);
-@@ -735,7 +740,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -754,7 +759,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
*/
function test_css_prefix()
{
@@ -11109,7 +11109,7 @@
$html = '<p id="my-id">'
. '<label for="my-other-id" class="my-class1
my-class2">test</label>'
-@@ -763,14 +768,14 @@ class Framework_Washtml extends
PHPUnit\Framework\TestCase
+@@ -782,14 +787,14 @@ class Framework_Washtml extends
PHPUnit\Framework\TestCase
{
$html = '<p><?xml:namespace prefix = "xsl" /></p>';
@@ -11126,7 +11126,7 @@
$washed = $this->cleanupResult($washer->wash($html));
$this->assertSame($washed, 'HTML');
-@@ -781,7 +786,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -800,7 +805,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
*/
function test_missing_tags()
{
@@ -11135,7 +11135,7 @@
$html = '<head></head>First line<br />Second line';
$washed = $washer->wash($html);
-@@ -823,7 +828,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -842,7 +847,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
{
$html = '<p><![CDATA[<script>alert(document.cookie)</script>]]></p>';
@@ -11144,7 +11144,7 @@
$washed = $washer->wash($html);
$this->assertTrue(strpos($washed, '<script>') === false, "CDATA
content");
-@@ -835,7 +840,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -854,7 +859,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
function test_resolve_base()
{
$html = file_get_contents(TESTS_DIR . 'src/htmlbase.txt');
@@ -11153,7 +11153,7 @@
$this->assertMatchesRegularExpression('|src="http://alec\.pl/dir/img1\.gif"|',
$html, "URI base resolving [1]");
$this->assertMatchesRegularExpression('|src="http://alec\.pl/dir/img2\.gif"|',
$html, "URI base resolving [2]");
-@@ -881,7 +886,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+@@ -900,7 +905,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
<tr><td></td></tr>
</table>';
diff -Nru roundcube-1.6.15+dfsg/debian/patches/use-enchant.patch
roundcube-1.6.16+dfsg/debian/patches/use-enchant.patch
--- roundcube-1.6.15+dfsg/debian/patches/use-enchant.patch 2026-03-30
13:40:22.000000000 +0200
+++ roundcube-1.6.16+dfsg/debian/patches/use-enchant.patch 2026-05-25
23:06:33.000000000 +0200
@@ -10,7 +10,7 @@
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/config/defaults.inc.php b/config/defaults.inc.php
-index 5e2262c..282d502 100644
+index 285a62c..6e80aa9 100644
--- a/config/defaults.inc.php
+++ b/config/defaults.inc.php
@@ -948,7 +948,8 @@ $config['spellcheck_dictionary'] = false;
diff -Nru roundcube-1.6.15+dfsg/.github/workflows/browser_tests.yml
roundcube-1.6.16+dfsg/.github/workflows/browser_tests.yml
--- roundcube-1.6.15+dfsg/.github/workflows/browser_tests.yml 2026-03-29
11:45:29.000000000 +0200
+++ roundcube-1.6.16+dfsg/.github/workflows/browser_tests.yml 2026-05-24
09:40:12.000000000 +0200
@@ -52,7 +52,7 @@
- name: Setup NPM
uses: actions/setup-node@v4
with:
- node-version: '16'
+ node-version: '18'
- name: Setup NPM deps
run: |
diff -Nru
roundcube-1.6.15+dfsg/plugins/filesystem_attachments/filesystem_attachments.php
roundcube-1.6.16+dfsg/plugins/filesystem_attachments/filesystem_attachments.php
---
roundcube-1.6.15+dfsg/plugins/filesystem_attachments/filesystem_attachments.php
2026-03-29 11:45:29.000000000 +0200
+++
roundcube-1.6.16+dfsg/plugins/filesystem_attachments/filesystem_attachments.php
2026-05-24 09:40:12.000000000 +0200
@@ -148,7 +148,7 @@
*/
function get($args)
{
- if (!$this->verify_path($args['path'])) {
+ if (!isset($args['path']) || !$this->verify_path($args['path'])) {
$args['path'] = null;
}
diff -Nru
roundcube-1.6.15+dfsg/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php
roundcube-1.6.16+dfsg/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php
---
roundcube-1.6.15+dfsg/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php
2026-03-29 11:45:29.000000000 +0200
+++
roundcube-1.6.16+dfsg/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php
2026-05-24 09:40:12.000000000 +0200
@@ -1597,7 +1597,7 @@
$this->add_tip('_name', $this->errors['name'], true);
}
- $input_name = $input_name->show(isset($scr) ? $scr['name'] : '');
+ $input_name = $input_name->show($scr['name'] ?? '');
$out .= sprintf("\n" . '<div class="form-group row">'
. '<label for="_name" class="col-sm-4 col-form-label">%s</label>'
diff -Nru
roundcube-1.6.15+dfsg/plugins/redundant_attachments/redundant_attachments.php
roundcube-1.6.16+dfsg/plugins/redundant_attachments/redundant_attachments.php
---
roundcube-1.6.15+dfsg/plugins/redundant_attachments/redundant_attachments.php
2026-03-29 11:45:29.000000000 +0200
+++
roundcube-1.6.16+dfsg/plugins/redundant_attachments/redundant_attachments.php
2026-05-24 09:40:12.000000000 +0200
@@ -198,6 +198,10 @@
return $args;
}
+ if (empty($args['id'])) {
+ return $args;
+ }
+
$this->_load_drivers();
// fetch from database if not found on FS
diff -Nru roundcube-1.6.15+dfsg/plugins/virtuser_query/virtuser_query.php
roundcube-1.6.16+dfsg/plugins/virtuser_query/virtuser_query.php
--- roundcube-1.6.15+dfsg/plugins/virtuser_query/virtuser_query.php
2026-03-29 11:45:29.000000000 +0200
+++ roundcube-1.6.16+dfsg/plugins/virtuser_query/virtuser_query.php
2026-05-24 09:40:12.000000000 +0200
@@ -63,8 +63,8 @@
{
$dbh = $this->get_dbh();
- $sql_result = $dbh->query(preg_replace('/%u/',
$dbh->escape($p['user']), $this->config['email']));
- $result = [];
+ $sql_result = $dbh->query(str_replace('%u', $dbh->escape($p['user']),
$this->config['email']));
+ $result = [];
while ($sql_arr = $dbh->fetch_array($sql_result)) {
if (strpos($sql_arr[0], '@')) {
@@ -101,7 +101,7 @@
{
$dbh = $this->get_dbh();
- $sql_result = $dbh->query(preg_replace('/%m/',
$dbh->escape($p['email']), $this->config['user']));
+ $sql_result = $dbh->query(str_replace('%m', $dbh->escape($p['email']),
$this->config['user']));
if ($sql_arr = $dbh->fetch_array($sql_result)) {
$p['user'] = $sql_arr[0];
@@ -117,7 +117,7 @@
{
$dbh = $this->get_dbh();
- $sql_result = $dbh->query(preg_replace('/%u/',
$dbh->escape($p['user']), $this->config['host']));
+ $sql_result = $dbh->query(str_replace('%u', $dbh->escape($p['user']),
$this->config['host']));
if ($sql_arr = $dbh->fetch_array($sql_result)) {
$p['host'] = $sql_arr[0];
@@ -133,7 +133,7 @@
{
$dbh = $this->get_dbh();
- $sql_result = $dbh->query(preg_replace('/%u/',
$dbh->escape($p['user']), $this->config['alias']));
+ $sql_result = $dbh->query(str_replace('%u', $dbh->escape($p['user']),
$this->config['alias']));
if ($sql_arr = $dbh->fetch_array($sql_result)) {
$p['user'] = $sql_arr[0];
diff -Nru roundcube-1.6.15+dfsg/program/include/rcmail_attachment_handler.php
roundcube-1.6.16+dfsg/program/include/rcmail_attachment_handler.php
--- roundcube-1.6.15+dfsg/program/include/rcmail_attachment_handler.php
2026-03-29 11:45:29.000000000 +0200
+++ roundcube-1.6.16+dfsg/program/include/rcmail_attachment_handler.php
2026-05-24 09:40:12.000000000 +0200
@@ -118,16 +118,6 @@
}
/**
- * Remove temp files, etc.
- */
- public function __destruct()
- {
- if ($this->body_file) {
- @unlink($this->body_file);
- }
- }
-
- /**
* Check if the object is a message part not uploaded file
*
* @return bool True if the object is a message part
@@ -260,6 +250,7 @@
$this->body_file = $filename;
fclose($fp);
@chmod($filename, 0600);
+ rcmail::get_instance()->add_shutdown_function(static function ()
use ($filename) { @unlink($filename); });
return true;
}
diff -Nru roundcube-1.6.15+dfsg/program/include/rcmail_sendmail.php
roundcube-1.6.16+dfsg/program/include/rcmail_sendmail.php
--- roundcube-1.6.15+dfsg/program/include/rcmail_sendmail.php 2026-03-29
11:45:29.000000000 +0200
+++ roundcube-1.6.16+dfsg/program/include/rcmail_sendmail.php 2026-05-24
09:40:12.000000000 +0200
@@ -77,16 +77,6 @@
}
/**
- * Object destructor to cleanup temporary files
- */
- public function __destruct()
- {
- foreach ($this->temp_files as $file) {
- @unlink($file);
- }
- }
-
- /**
* Collect input data for message headers
*
* @return array Message headers
@@ -461,6 +451,7 @@
if ($mailbody_file) {
$this->temp_files[$message->headers()['Message-ID']] =
$mailbody_file;
+ $this->rcmail->add_shutdown_function(static function () use
($mailbody_file) { @unlink($mailbody_file); });
}
// save message sent time
@@ -545,6 +536,7 @@
if (!is_a($msg, 'PEAR_Error')) {
$this->temp_files[$msg_id] = $msg_file;
+ $this->rcmail->add_shutdown_function(static function
() use ($msg_file) { @unlink($msg_file); });
}
}
diff -Nru roundcube-1.6.15+dfsg/program/js/app.js
roundcube-1.6.16+dfsg/program/js/app.js
--- roundcube-1.6.15+dfsg/program/js/app.js 2026-03-29 11:45:29.000000000
+0200
+++ roundcube-1.6.16+dfsg/program/js/app.js 2026-05-24 09:40:12.000000000
+0200
@@ -4716,7 +4716,7 @@
this.show_popup_dialog(
this.get_label('restoresavedcomposedata')
.replace('$date', new Date(formdata.changed).toLocaleString())
- .replace('$subject', formdata._subject)
+ .replace('$subject', $('<span>').text(formdata._subject).html())
.replace(/\n/g, '<br/>'),
this.get_label('restoremessage'),
[{
diff -Nru roundcube-1.6.15+dfsg/program/lib/Roundcube/rcube_imap.php
roundcube-1.6.16+dfsg/program/lib/Roundcube/rcube_imap.php
--- roundcube-1.6.15+dfsg/program/lib/Roundcube/rcube_imap.php 2026-03-29
11:45:29.000000000 +0200
+++ roundcube-1.6.16+dfsg/program/lib/Roundcube/rcube_imap.php 2026-05-24
09:40:12.000000000 +0200
@@ -141,7 +141,7 @@
'version' => RCUBE_VERSION,
'php' => PHP_VERSION,
'os' => PHP_OS,
- 'command' => $_SERVER['REQUEST_URI'] ?? '',
+ 'command' => abbreviate_string($_SERVER['REQUEST_URI'] ?? '',
512, '...', true),
];
}
diff -Nru roundcube-1.6.15+dfsg/program/lib/Roundcube/rcube_ldap.php
roundcube-1.6.16+dfsg/program/lib/Roundcube/rcube_ldap.php
--- roundcube-1.6.15+dfsg/program/lib/Roundcube/rcube_ldap.php 2026-03-29
11:45:29.000000000 +0200
+++ roundcube-1.6.16+dfsg/program/lib/Roundcube/rcube_ldap.php 2026-05-24
09:40:12.000000000 +0200
@@ -1581,34 +1581,14 @@
foreach ($this->prop['autovalues'] as $lf => $templ) {
if (empty($attrs[$lf])) {
- if (strpos($templ, '(') !== false) {
- // replace {attr} placeholders with (escaped!) attribute
values to be safely eval'd
- $code = preg_replace('/\{\w+\}/', '', strtr($templ,
array_map('addslashes', $attrvals)));
- $res = false;
-
- try {
- $res = eval("return ($code);");
- }
- catch (ParseError $e) {
- // ignore
- }
-
- if ($res === false) {
- rcube::raise_error([
- 'code' => 505, 'file' => __FILE__, 'line' =>
__LINE__,
- 'message' => "Expression parse error on:
($code)"
- ], true, false);
- continue;
- }
-
- $attrs[$lf] = $res;
- }
- else {
- // replace {attr} placeholders with concrete attribute
values
- $attrs[$lf] = preg_replace('/\{\w+\}/', '', strtr($templ,
$attrvals));
- }
+ // replace {attr} placeholders with concrete attribute values
+ $attrs[$lf] = preg_replace('/\{\w+\}/', '', strtr($templ,
$attrvals));
}
}
+
+ $rcube = rcube::get_instance();
+ $plugin = $rcube->plugins->exec_hook('ldap_autovalues', ['attrs' =>
$attrs]);
+ $attrs = $plugin['attrs'];
}
/**
diff -Nru roundcube-1.6.15+dfsg/program/lib/Roundcube/rcube_utils.php
roundcube-1.6.16+dfsg/program/lib/Roundcube/rcube_utils.php
--- roundcube-1.6.15+dfsg/program/lib/Roundcube/rcube_utils.php 2026-03-29
11:45:29.000000000 +0200
+++ roundcube-1.6.16+dfsg/program/lib/Roundcube/rcube_utils.php 2026-05-24
09:40:12.000000000 +0200
@@ -1,6 +1,7 @@
<?php
use IPLib\Factory;
+use IPLib\ParseStringFlag;
/*
+-----------------------------------------------------------------------+
@@ -433,10 +434,25 @@
$host = parse_url($url, \PHP_URL_HOST);
if (is_string($host)) {
+ $options = ParseStringFlag::IPV4_MAYBE_NON_DECIMAL
+ | ParseStringFlag::IPV4SUBNET_MAYBE_COMPACT
+ | ParseStringFlag::IPV4ADDRESS_MAYBE_NON_QUAD_DOTTED
+ | ParseStringFlag::MAY_INCLUDE_ZONEID;
+
+ $host = trim($host, '[]');
+
+ // IPLib does not seem to work with IPv6 syntax for IPv4 addresses
+ $host = preg_replace('/^::ffff:/i', '', $host);
+
+ if (preg_match('/([0-9a-f.-]+)\.nip\.io$/i', $host, $matches)) {
+ $host = trim($matches[1], '-.');
+ }
+
// TODO: This is pretty fast, but a single message can contain
multiple links
// to the same target, maybe we should do some in-memory caching.
- if ($address = Factory::parseAddressString($host = trim($host,
'[]'))) {
+ if ($address = Factory::parseAddressString($host, $options)) {
$nets = [
+ '0.0.0.0',
'127.0.0.0/8', // loopback
'10.0.0.0/8', // RFC1918
'172.16.0.0/12', // RFC1918
@@ -457,7 +473,8 @@
}
// FIXME: Should we accept any non-fqdn hostnames?
- return (bool) preg_match('/^localhost(\.localdomain)?$/i', $host);
+ $host = strtolower($host);
+ return $host == 'metadata.google.internal' ||
preg_match('/^localhost(\.localdomain)?\.?$/', $host);
}
return false;
@@ -610,10 +627,17 @@
} else {
$value = '';
foreach (self::explode_css_property_block($rule[1]) as $val) {
- if ($url_callback && preg_match('/^url\s*\(/i', $val)) {
- if
(preg_match('/^url\s*\(\s*[\'"]?([^\'"\)]*)[\'"]?\s*\)/iu', $val, $match)) {
- if ($url = $url_callback($match[1])) {
- $value .= ' url(' . $url . ')';
+ if ($url_callback && preg_match('/\burl\s*\(/i', $val)) {
+ if
(preg_match_all('/(\b)url\s*\(\s*[\'"]?([^\'"\)]*)[\'"]?\s*\)/iu', $val,
$matches)) {
+ foreach ($matches[2] as $idx => $url) {
+ if ($url = $url_callback($url)) {
+ $val = str_replace($matches[0][$idx],
$matches[1][$idx] . "url({$url})", $val);
+ } else {
+ $val = '';
+ }
+ }
+ if (strlen($val)) {
+ $value .= ' ' . $val;
}
}
} elseif (preg_match('/;.+/', $val)) {
diff -Nru roundcube-1.6.15+dfsg/program/lib/Roundcube/rcube_washtml.php
roundcube-1.6.16+dfsg/program/lib/Roundcube/rcube_washtml.php
--- roundcube-1.6.15+dfsg/program/lib/Roundcube/rcube_washtml.php
2026-03-29 11:45:29.000000000 +0200
+++ roundcube-1.6.16+dfsg/program/lib/Roundcube/rcube_washtml.php
2026-05-24 09:40:12.000000000 +0200
@@ -293,11 +293,17 @@
$key = strtolower($name);
$value = $attr->nodeValue;
- if ($key == 'style' && ($style = $this->wash_style($value))) {
- // replace double quotes to prevent syntax error and XSS
issues (#1490227)
- $result .= ' style="' . str_replace('"', '"', $style) .
'"';
+ if ($key == 'style' || ($key == 'values' &&
self::attribute_value($node, 'attributename', '/^style$/i'))) {
+ $style = '';
+ if ($value === '' || ($style = $this->wash_style($value))) {
+ // replace double quotes to prevent syntax error and XSS
issues (#1490227)
+ $result .= ' ' . $attr->nodeName . '="' . str_replace('"',
'"', $style) . '"';
+ }
+ else {
+ $washed[] = htmlspecialchars($attr->nodeName, \ENT_QUOTES,
$this->config['charset']);
+ }
}
- else if (isset($this->_html_attribs[$key]) || in_array($key,
$additional_attribs)) {
+ elseif (isset($this->_html_attribs[$key]) || in_array($key,
$additional_attribs)) {
$value = trim($value);
$out = null;
@@ -393,7 +399,7 @@
}
if (preg_match('/^(http|https|ftp):.+/i', $uri)) {
- if (!empty($this->config['allow_remote']) ||
rcube_utils::is_local_url($uri)) {
+ if (!empty($this->config['allow_remote'])) {
return $uri;
}
diff -Nru
roundcube-1.6.15+dfsg/public_html/plugins/filesystem_attachments/filesystem_attachments.php
roundcube-1.6.16+dfsg/public_html/plugins/filesystem_attachments/filesystem_attachments.php
---
roundcube-1.6.15+dfsg/public_html/plugins/filesystem_attachments/filesystem_attachments.php
2026-03-29 11:45:29.000000000 +0200
+++
roundcube-1.6.16+dfsg/public_html/plugins/filesystem_attachments/filesystem_attachments.php
2026-05-24 09:40:12.000000000 +0200
@@ -148,7 +148,7 @@
*/
function get($args)
{
- if (!$this->verify_path($args['path'])) {
+ if (!isset($args['path']) || !$this->verify_path($args['path'])) {
$args['path'] = null;
}
diff -Nru
roundcube-1.6.15+dfsg/public_html/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php
roundcube-1.6.16+dfsg/public_html/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php
---
roundcube-1.6.15+dfsg/public_html/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php
2026-03-29 11:45:29.000000000 +0200
+++
roundcube-1.6.16+dfsg/public_html/plugins/managesieve/lib/Roundcube/rcube_sieve_engine.php
2026-05-24 09:40:12.000000000 +0200
@@ -1597,7 +1597,7 @@
$this->add_tip('_name', $this->errors['name'], true);
}
- $input_name = $input_name->show(isset($scr) ? $scr['name'] : '');
+ $input_name = $input_name->show($scr['name'] ?? '');
$out .= sprintf("\n" . '<div class="form-group row">'
. '<label for="_name" class="col-sm-4 col-form-label">%s</label>'
diff -Nru
roundcube-1.6.15+dfsg/public_html/plugins/redundant_attachments/redundant_attachments.php
roundcube-1.6.16+dfsg/public_html/plugins/redundant_attachments/redundant_attachments.php
---
roundcube-1.6.15+dfsg/public_html/plugins/redundant_attachments/redundant_attachments.php
2026-03-29 11:45:29.000000000 +0200
+++
roundcube-1.6.16+dfsg/public_html/plugins/redundant_attachments/redundant_attachments.php
2026-05-24 09:40:12.000000000 +0200
@@ -198,6 +198,10 @@
return $args;
}
+ if (empty($args['id'])) {
+ return $args;
+ }
+
$this->_load_drivers();
// fetch from database if not found on FS
diff -Nru
roundcube-1.6.15+dfsg/public_html/plugins/virtuser_query/virtuser_query.php
roundcube-1.6.16+dfsg/public_html/plugins/virtuser_query/virtuser_query.php
--- roundcube-1.6.15+dfsg/public_html/plugins/virtuser_query/virtuser_query.php
2026-03-29 11:45:29.000000000 +0200
+++ roundcube-1.6.16+dfsg/public_html/plugins/virtuser_query/virtuser_query.php
2026-05-24 09:40:12.000000000 +0200
@@ -63,8 +63,8 @@
{
$dbh = $this->get_dbh();
- $sql_result = $dbh->query(preg_replace('/%u/',
$dbh->escape($p['user']), $this->config['email']));
- $result = [];
+ $sql_result = $dbh->query(str_replace('%u', $dbh->escape($p['user']),
$this->config['email']));
+ $result = [];
while ($sql_arr = $dbh->fetch_array($sql_result)) {
if (strpos($sql_arr[0], '@')) {
@@ -101,7 +101,7 @@
{
$dbh = $this->get_dbh();
- $sql_result = $dbh->query(preg_replace('/%m/',
$dbh->escape($p['email']), $this->config['user']));
+ $sql_result = $dbh->query(str_replace('%m', $dbh->escape($p['email']),
$this->config['user']));
if ($sql_arr = $dbh->fetch_array($sql_result)) {
$p['user'] = $sql_arr[0];
@@ -117,7 +117,7 @@
{
$dbh = $this->get_dbh();
- $sql_result = $dbh->query(preg_replace('/%u/',
$dbh->escape($p['user']), $this->config['host']));
+ $sql_result = $dbh->query(str_replace('%u', $dbh->escape($p['user']),
$this->config['host']));
if ($sql_arr = $dbh->fetch_array($sql_result)) {
$p['host'] = $sql_arr[0];
@@ -133,7 +133,7 @@
{
$dbh = $this->get_dbh();
- $sql_result = $dbh->query(preg_replace('/%u/',
$dbh->escape($p['user']), $this->config['alias']));
+ $sql_result = $dbh->query(str_replace('%u', $dbh->escape($p['user']),
$this->config['alias']));
if ($sql_arr = $dbh->fetch_array($sql_result)) {
$p['user'] = $sql_arr[0];
diff -Nru roundcube-1.6.15+dfsg/public_html/program/js/app.js
roundcube-1.6.16+dfsg/public_html/program/js/app.js
--- roundcube-1.6.15+dfsg/public_html/program/js/app.js 2026-03-29
11:45:29.000000000 +0200
+++ roundcube-1.6.16+dfsg/public_html/program/js/app.js 2026-05-24
09:40:12.000000000 +0200
@@ -4716,7 +4716,7 @@
this.show_popup_dialog(
this.get_label('restoresavedcomposedata')
.replace('$date', new Date(formdata.changed).toLocaleString())
- .replace('$subject', formdata._subject)
+ .replace('$subject', $('<span>').text(formdata._subject).html())
.replace(/\n/g, '<br/>'),
this.get_label('restoremessage'),
[{
diff -Nru roundcube-1.6.15+dfsg/tests/Framework/Utils.php
roundcube-1.6.16+dfsg/tests/Framework/Utils.php
--- roundcube-1.6.15+dfsg/tests/Framework/Utils.php 2026-03-29
11:45:29.000000000 +0200
+++ roundcube-1.6.16+dfsg/tests/Framework/Utils.php 2026-05-24
09:40:12.000000000 +0200
@@ -253,6 +253,11 @@
$mod = \rcube_utils::mod_css_styles("p { background:
url('//data:image&leak'); }", 'rcmbody');
$this->assertSame('#rcmbody p {}', $mod);
+ $mod = \rcube_utils::mod_css_styles("p { background-image: var(--x,
url(http://evil.com/1.gif)) }", 'rcmbody');
+ $this->assertSame('#rcmbody p {}', $mod);
+ $mod = \rcube_utils::mod_css_styles("p { background-image: var(--x,
url(http://evil.com/1.gif)) }", 'rcmbody', true);
+ $this->assertSame('#rcmbody p { background-image: var(--x,
url(http://evil.com/1.gif)); }', $mod);
+
// Note: This looks to me like a bug in browsers, for now we don't
allow image-set at all
$mod = \rcube_utils::mod_css_styles("p { background:
image-set('//evil.com/img.png' 1x); }", 'rcmbody');
$this->assertSame('#rcmbody p {}', $mod);
@@ -584,9 +589,19 @@
['//127.0.0.1', true],
['http://localhost', true],
['http://localhost.localdomain', true],
+ ['http://localhost.:8080/', true],
+ ['http://2130706433:8080', true],
+ ['http://0x7f000001:8080', true],
+ ['http://0177.0.0.1:8080', true],
+ ['http://127.1:8080', true],
+ ['http://0.0.0.0:8080', true],
+ ['http://[::ffff:127.0.0.1]:8080', true],
+ ['http://127.0.0.1.nip.io', true],
+ ['http://metadata.google.internal', true],
// Non-local hosts
['http://[2001:470::76:0:0:0:2]', false],
['http://domain.tld', false],
+ ['http://20.0.0.1.nip.io', false],
];
}
diff -Nru roundcube-1.6.15+dfsg/tests/Framework/Washtml.php
roundcube-1.6.16+dfsg/tests/Framework/Washtml.php
--- roundcube-1.6.15+dfsg/tests/Framework/Washtml.php 2026-03-29
11:45:29.000000000 +0200
+++ roundcube-1.6.16+dfsg/tests/Framework/Washtml.php 2026-05-24
09:40:12.000000000 +0200
@@ -525,6 +525,18 @@
'<svg><animate attributeName="fill"
values="url(http://external.site)" dur="1s" begin="0s" fill="freeze" /></svg>',
'<svg><!-- animate blocked --></svg>',
],
+ [
+ '<svg><rect><animate attributeName="style"
values="filter:url(http://attacker.com)" dur="0s" fill="freeze"/></rect></svg>',
+ '<svg><rect><animate attributeName="style" dur="0s"
fill="freeze" x-washed="values" /></rect></svg>',
+ ],
+ [
+ '<svg><rect><animate attributeName="style"
values="width:expression(alert(1))" dur="0s" fill="freeze"/></rect></svg>',
+ '<svg><rect><animate attributeName="style" dur="0s"
fill="freeze" x-washed="values" /></rect></svg>',
+ ],
+ [
+ '<svg><rect><animate attributeName="style"
values="position:fixed;top:0;left:0" dur="0s" fill="freeze"/></rect></svg>',
+ '<svg><rect><animate attributeName="style" values="position:
absolute; top: 0; left: 0" dur="0s" fill="freeze" /></rect></svg>',
+ ],
];
}
@@ -697,18 +709,25 @@
function test_extlinks()
{
$html = [
- ["<link href=\"http://TRACKING_URL/\">", true],
- ["<link href=\"src:abc\">", false],
- ["<img src=\"http://TRACKING_URL/\">", true],
- ["<img src=\"data:image\">", false],
- ['<p style="backgr\\ound-image:
\\ur\\l(\'http://TRACKING_URL\')"></p>', true],
+ ['<link href="http://TRACKING_URL/">', true],
+ ['<link href="src:abc">', false],
+ ['<img src="http://TRACKING_URL/">', true],
+ ['<img src="http://127.0.0.1">', true],
+ ['<img src="data:image">', false],
+ ['<p style="backgr\ound-image:
\ur\l(\'http://TRACKING_URL\')"></p>', true],
+ ['<p style="background-image: var(--x,
url(http://evil.com/1.gif))"></p>', true],
+ ['<p style="cursor: var(--x, url(http://evil.com/5.gif),
auto)"></p>', true],
+ ['<p style="background: var(--a, var(--b,
url(http://evil.com/6.gif)))"></p>', true],
+ ['<p style="color: red; background-image: var(--x,
url(http://evil.com/7.gif)); font-size: 12px"></p>', true],
+ ['<p style="background: image(url(http://evil.com/8.gif))"></p>',
true],
+ ['<p style="background: cross-fade(url(http://evil.com/9a.gif),
url(http://evil.com/9b.gif), 50%)"></p>', true],
];
foreach ($html as $item) {
$washer = new rcube_washtml;
$washed = $washer->wash($item[0]);
- $this->assertSame($item[1], $washer->extlinks);
+ $this->assertSame($item[1], $washer->extlinks, "Failed on:
{$item[0]}");
}
foreach ($html as $item) {
diffstat for roundcube-1.6.5+dfsg roundcube-1.6.5+dfsg changelog | 24 patches/Avoid-dependency-on-new-package-mlocati-ip-lib.patch | 275 +++++++++-- patches/CVE-2026-48842.patch | 55 ++ patches/CVE-2026-48843.patch | 86 +++ patches/CVE-2026-48844.patch | 73 ++ patches/CVE-2026-48845.patch | 39 + patches/CVE-2026-48846.patch | 91 +++ patches/CVE-2026-48847.patch | 80 +++ patches/CVE-2026-48848.patch | 63 ++ patches/CVE-2026-48849.patch | 26 + patches/series | 10 11 files changed, 791 insertions(+), 31 deletions(-) diff -Nru roundcube-1.6.5+dfsg/debian/changelog roundcube-1.6.5+dfsg/debian/changelog --- roundcube-1.6.5+dfsg/debian/changelog 2026-03-20 19:15:19.000000000 +0100 +++ roundcube-1.6.5+dfsg/debian/changelog 2026-05-26 01:08:43.000000000 +0200 @@ -1,3 +1,27 @@ +roundcube (1.6.5+dfsg-1+deb12u9) bookworm-security; urgency=high + + * Cherry pick upstream security fixes from v1.6.16 (closes: #1137507). + + Fix CVE-2026-48842: pre-auth SQL injection in `virtuser_query` plugin + via `preg_replace()` backslash escape bypass. + + Fix CVE-2026-48843: SSRF bypass via specific local address URLs. Add + support non quad-dotted IPs and non-decimal fields to + d/p/Avoid-dependency-on-new-package-mlocati-ip-lib.patch in order to + match the new upstream behavior. + + Fix CVE-2026-48844: Code injection vulnerability via code evaluation + support in LDAP autovalues option. Code evaluation support has now been + removed. + + Fix CVE-2026-48845: Local/private URL fetch bypass when remote resources + were not allowed. + + Fix CVE-2026-48846: Bypass of remote image blocking via CSS `var()`. + + Fix CVE-2026-48847: Pre-auth arbitrary file delete via redis/memcache + session poisoning bypass. + + Fix CVE-2026-48848: CSS injection bypass in HTML sanitizer via SVG + <animate attributeName="style">. + + Fix CVE-2026-48849: Stored XSS/HTML/CSS injection in subject field of + the draft restore dialog. + + -- Guilhem Moulin <[email protected]> Tue, 26 May 2026 01:08:43 +0200 + roundcube (1.6.5+dfsg-1+deb12u8) bookworm-security; urgency=high * Cherry pick upstream security fixes from v1.6.14 and v1.6.15 (closes: diff -Nru roundcube-1.6.5+dfsg/debian/patches/Avoid-dependency-on-new-package-mlocati-ip-lib.patch roundcube-1.6.5+dfsg/debian/patches/Avoid-dependency-on-new-package-mlocati-ip-lib.patch --- roundcube-1.6.5+dfsg/debian/patches/Avoid-dependency-on-new-package-mlocati-ip-lib.patch 2026-03-20 19:15:19.000000000 +0100 +++ roundcube-1.6.5+dfsg/debian/patches/Avoid-dependency-on-new-package-mlocati-ip-lib.patch 2026-05-26 01:08:43.000000000 +0200 @@ -4,46 +4,176 @@ Which as of today is not present in Debian. The dependency was introduced in 27ec6cc9cb25e1ef8b4d4ef39ce76d619caa6870 in order to fix a -CVE-2026-35540. While it can be uploaded to sid, we need another -solution to fix the vulnerability for older suites. +CVE-2026-35540 and its follow-up CVE-2026-48843. While it can be +uploaded to sid, we need another solution to fix the vulnerability for +older suites. Bug-Debian: https://bugs.debian.org/1131182 Forwarded: not-needed --- - program/lib/Roundcube/rcube_utils.php | 45 ++++++++++++++++++++++++----------- - tests/Framework/Utils.php | 6 +++++ - 2 files changed, 37 insertions(+), 14 deletions(-) + program/lib/Roundcube/rcube_utils.php | 160 +++++++++++++++++++++++++++++----- + tests/Framework/Utils.php | 85 ++++++++++++++++++ + 2 files changed, 223 insertions(+), 22 deletions(-) diff --git a/program/lib/Roundcube/rcube_utils.php b/program/lib/Roundcube/rcube_utils.php -index 8ff1db8..c5532be 100644 +index 0ddd910..43be2af 100644 --- a/program/lib/Roundcube/rcube_utils.php +++ b/program/lib/Roundcube/rcube_utils.php -@@ -1,7 +1,5 @@ +@@ -1,8 +1,5 @@ <?php -use IPLib\Factory; +-use IPLib\ParseStringFlag; - /* +-----------------------------------------------------------------------+ | This file is part of the Roundcube Webmail client | -@@ -435,24 +433,43 @@ class rcube_utils - if (is_string($host)) { - // TODO: This is pretty fast, but a single message can contain multiple links - // to the same target, maybe we should do some in-memory caching. -- if ($address = Factory::parseAddressString($host = trim($host, '[]'))) { -+ $address = trim($host, '[]'); -+ if ((bool)preg_match('/^([0-9a-f:]*:)? -+ ((?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[1-9][0-9]|0{0,2}[0-9])\.){3} -+ (?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|0?[1-9][0-9]|0{0,2}[0-9]))$/Dix', -+ $address, $matches)) { -+ /* trim leading zeros from IPv4 octets (GuzzleHTTP sanitizes such invalid addresses) */ -+ $address = @inet_pton($matches[1] . preg_replace('/\b0+(?=\d)/', '', $matches[2])); +@@ -422,6 +419,116 @@ class rcube_utils + return asciiwords($str, true, '_'); + } + ++ /** ++ * Converts a human readable IP address to its packed in_addr representation ++ * Like the builtin inet_pton() function, but fallback to ++ * inet_aton(3)-behavior for IPv4. ++ * ++ * @param string $ip A human readable IPv4 or IPv6 address. ++ * ++ * @return string|false ++ */ ++ public static function inet_pton2(string $ip) : string|bool { ++ $address = @inet_pton($ip); ++ if (is_string($address)) { ++ return $address; ++ } ++ ++ /* emulate inet_aton(3) behavior */ ++ $fields = explode('.', $ip, 4); /* at most 4 dot-separated fields */ ++ $field_last = array_pop($fields); ++ if (!is_string($field_last)) { ++ return false; ++ } ++ $address = "\x00\x00\x00\x00"; ++ foreach ($fields as $i => $field) { ++ /* process each field except the last one; values must not exceed 0xFF */ ++ if (preg_match('/^0[xX]0*([0-9A-Fa-f]{0,2})$/D', $field, $matches)) { ++ /* hexadecimal field, 0x00 to 0xFF */ ++ $b = $matches[1] === '' ? 0 : hexdec($matches[1]); ++ } elseif (preg_match('/^0+([1-3][0-7][0-7]|[1-7][0-7]?|)$/D', $field, $matches)) { ++ /* octal field, o0 to o377 */ ++ $b = $matches[1] === '' ? 0 : intval($matches[1], 8); ++ } elseif (preg_match('/^(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?)$/D', $field)) { ++ /* decimal field, 1 to 255 */ ++ $b = intval($field, 10); + } else { -+ $address = @inet_pton($address); ++ /* invalid field */ ++ return false; + } ++ $address[$i] = chr($b); ++ } + -+ if (is_string($address)) { - $nets = [ ++ /* split into 2 groups of 16 bits to avoid overflowing PHP_INT_MAX on 32-bits platforms */ ++ $hi = $li = 0x0000; ++ $carry = null; ++ if (preg_match('/^0[xX]0*([0-9A-Fa-f]{0,8})$/D', $field_last, $matches)) { ++ /* hexadecimal field, 0x00 to 0xFFFFFFFF */ ++ if (strlen($matches[1]) > 4) { ++ $hi = hexdec(substr($matches[1], 0, -4)); ++ $li = hexdec(substr($matches[1], -4)); ++ } elseif ($matches[1] !== '') { ++ $li = hexdec($matches[1]); ++ } ++ } elseif (preg_match('/^0+([1-3][0-7]{10}|[1-7][0-7]{0,9}|)$/D', ++ $field_last, $matches)) { ++ /* octal field, o0 to o37777777777 */ ++ if ($matches[1] !== '') { ++ $base = 8; ++ $hi = intval(substr($matches[1], 0, 10), $base); ++ $li = $hi % (1 << 16); ++ $hi = intdiv($hi, 1 << 16); /* <= 65535 */ ++ if (strlen($matches[1]) > 10) { ++ $carry = substr($matches[1], -1); /* last digit */ ++ } ++ } ++ } elseif (preg_match('/^[1-9][0-9]{0,9}$/D', $field_last)) { ++ /* decimal field, 1 to 9999999999 (values >=2^32 are rejected later) */ ++ $base = 10; ++ $hi = intval(substr($field_last, 0, 9), $base); ++ $li = $hi % (1 << 16); ++ $hi = intdiv($hi, 1 << 16); /* <= 152587 */ ++ if (strlen($field_last) > 9) { ++ $carry = substr($field_last, -1); /* last digit */ ++ } ++ } else { ++ /* invalid field */ ++ return false; ++ } ++ if ($carry !== null) { ++ /* carry over the last digit; there won't be any overflow ++ * since the value won't exceed 152587 * $base + $base-1 */ ++ $li = $li * $base + intval($carry, $base); ++ $hi = $hi * $base + intdiv($li, 1 << 16); ++ $li %= 1 << 16; ++ } ++ ++ $i = count($fields); ++ if ($hi === 0x0000 && $li <= 0x00ff && $i <= 3) { ++ /* first 0-3 bytes have been set already, now set the last byte */ ++ $address[3] = chr($li); ++ } elseif ($hi === 0x0000 && $li <= 0xffff && $i <= 2) { ++ /* first 0-2 bytes have been set already, now set the last 2 bytes */ ++ $address[2] = chr( intdiv($li, 1 << 8) ); ++ $address[3] = chr( $li % (1 << 8) ); ++ } elseif ($hi <= 0x00ff && $li <= 0xffff && $i <= 1) { ++ /* first 0-1 bytes have been set already, now set the last 3 bytes */ ++ $address[1] = chr( $hi ); ++ $address[2] = chr( intdiv($li, 1 << 8) ); ++ $address[3] = chr( $li % (1 << 8) ); ++ } elseif ($hi <= 0xffff && $li <= 0xffff && $i === 0) { ++ /* set all 4 bytes */ ++ $address[0] = chr( intdiv($hi, 1 << 8) ); ++ $address[1] = chr( $hi % (1 << 8) ); ++ $address[2] = chr( intdiv($li, 1 << 8) ); ++ $address[3] = chr( $li % (1 << 8) ); ++ } else { ++ /* overflow, all numeric values must be <2^32 */ ++ return false; ++ } ++ return $address; ++ } ++ + /** + * Check if an URL point to a local network location. + * +@@ -434,37 +541,46 @@ class rcube_utils + $host = parse_url($url, \PHP_URL_HOST); + + if (is_string($host)) { +- $options = ParseStringFlag::IPV4_MAYBE_NON_DECIMAL +- | ParseStringFlag::IPV4SUBNET_MAYBE_COMPACT +- | ParseStringFlag::IPV4ADDRESS_MAYBE_NON_QUAD_DOTTED +- | ParseStringFlag::MAY_INCLUDE_ZONEID; +- + $host = trim($host, '[]'); + +- // IPLib does not seem to work with IPv6 syntax for IPv4 addresses ++ /* IPv4-mapped IPv6 addresses (RFC4291 2.5.5) */ + $host = preg_replace('/^::ffff:/i', '', $host); + + if (preg_match('/([0-9a-f.-]+)\.nip\.io$/i', $host, $matches)) { + $host = trim($matches[1], '-.'); + } + ++ if (strpos($host, ':') !== false && ($n = strpos($host, '%')) > 0) { ++ /* drop the zone ID */ ++ $host = substr($host, 0, $n); ++ } ++ + // TODO: This is pretty fast, but a single message can contain multiple links + // to the same target, maybe we should do some in-memory caching. +- if ($address = Factory::parseAddressString($host, $options)) { +- $nets = [ +- '0.0.0.0', - '127.0.0.0/8', // loopback - '10.0.0.0/8', // RFC1918 - '172.16.0.0/12', // RFC1918 @@ -51,6 +181,9 @@ - '169.254.0.0/16', // link-local / cloud metadata - '::1/128', - 'fc00::/7', ++ if (is_string($address = \rcube_utils::inet_pton2($host))) { ++ $nets = [ ++ ['0.0.0.0', '0.0.0.0'], + ['127.0.0.0', '127.255.255.255'], // loopback + ['10.0.0.0', '10.255.255.255'], // RFC1918 + ['172.16.0.0', '172.31.255.255'], // RFC1918 @@ -78,15 +211,98 @@ return true; } } -- - return false; - } - diff --git a/tests/Framework/Utils.php b/tests/Framework/Utils.php -index 8a95001..1a24826 100644 +index d654802..a74249d 100644 --- a/tests/Framework/Utils.php +++ b/tests/Framework/Utils.php -@@ -571,12 +571,18 @@ class Framework_Utils extends PHPUnit\Framework\TestCase +@@ -557,6 +557,86 @@ class Framework_Utils extends PHPUnit\Framework\TestCase + } + } + ++ /** ++ * Test inet_pton2() ++ * ++ * @dataProvider provide_inet_pton2_cases ++ */ ++ #[DataProvider('provide_inet_pton2_cases')] ++ public function test_inet_pton2($input, $output) ++ { ++ $r = \rcube_utils::inet_pton2($input); ++ if (is_bool($output)) { ++ $this->assertSame($output, $r); ++ } else { ++ $this->assertTrue(is_string($r)); ++ $addr = @inet_pton($output); ++ $this->assertSame($r, $addr, bin2hex($r) . " != " . bin2hex($addr)); ++ } ++ } ++ ++ /** ++ * Test-Cases for inet_pton2() test ++ */ ++ public static function provide_inet_pton2_cases(): iterable ++ { ++ return [ ++ ['', false], ++ ['0xx', false], ++ ['08', false], ++ ['a', false], ++ ['0.0.0.0.0', false], ++ ['0..0', false], ++ ['0.', false], ++ ['.0', false], ++ ['256.0', false], ++ ['0x100.0', false], ++ ['0400.0', false], ++ ['4294967296', false], ++ ['9999999999', false], ++ ['18446744073709551616', false], ++ ['040000000000', false], ++ ['077777777777', false], ++ ['0x100000000', false], ++ [' 123', false], ++ ['123 ', false], ++ ['-1', false], ++ ++ ['0', '0.0.0.0'], ++ ['0x0', '0.0.0.0'], ++ ['0x', '0.0.0.0'], ++ ['00', '0.0.0.0'], ++ ['123', '0.0.0.123'], ++ ['0xF', '0.0.0.15'], ++ ['0xFA', '0.0.0.250'], ++ ['061', '0.0.0.49'], ++ ['12345', '0.0.48.57'], ++ ['0X89AB', '0.0.137.171'], ++ ['0x89ABC', '0.8.154.188'], ++ ['012345', '0.0.20.229'], ++ ['1234567', '0.18.214.135'], ++ ['0xabcde', '0.10.188.222'], ++ ['01234567', '0.5.57.119'], ++ ['123456789', '7.91.205.21'], ++ ['0xdeadbeef', '222.173.190.239'], ++ ['07654321012', '62.177.162.10'], ++ ['4294967295', '255.255.255.255'], ++ ['2147483648', '128.0.0.0'], ++ ['0xfffefdfc', '255.254.253.252'], ++ ['037777777777', '255.255.255.255'], ++ ['020000000000', '128.0.0.0'], ++ ['226.000.000.037', '226.0.0.31'], ++ ['0x7f.1', '127.0.0.1'], ++ ['0x7f.256', '127.0.1.0'], ++ ['0x7f.0.256', '127.0.1.0'], ++ ['0377.0xfedc', '255.0.254.220'], ++ ['0x7f.0377.12345', '127.255.48.57'], ++ ['1.2.3.4', '1.2.3.4'], ++ ['0.1.2.3', '0.1.2.3'], ++ ['7.010.0x.0xa', '7.8.0.10'], ++ ]; ++ } ++ + /** + * Test is_local_url() + * +@@ -576,12 +656,17 @@ class Framework_Utils extends PHPUnit\Framework\TestCase return [ // Local hosts ['https://127.0.0.1', true], @@ -96,12 +312,11 @@ ['https://192.168.0.100', true], ['https://169.254.0.200', true], ['http://[fc00::1]', true], ++ ['http://[fc00::1%1]', true], ['ftp://[::1]:8080', true], + ['https://[127.0.0.1]', true], + ['https://[::127.0.0.1]', true], -+ ['https://[::127.0.0.001]', true], + ['https://[::ffff:192.168.1.2]', true], -+ ['https://[::ffff:192.168.01.002]', true], ['//127.0.0.1', true], ['http://localhost', true], ['http://localhost.localdomain', true], diff -Nru roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48842.patch roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48842.patch --- roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48842.patch 1970-01-01 01:00:00.000000000 +0100 +++ roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48842.patch 2026-05-26 01:08:43.000000000 +0200 @@ -0,0 +1,55 @@ +From: Aleksander Machniak <[email protected]> +Date: Sun, 24 May 2026 09:16:47 +0200 +Subject: Fix pre-auth SQL injection in virtuser_query plugin via preg_replace + backslash escape bypass + +Origin: https://github.com/roundcube/roundcubemail/commit/87124cc7136a48b5fa9d2b40dfead6e9dcaeaf4b +Bug: https://roundcube.net/news/2026/05/24/security-updates-1.6.16-and-1.7.1 +Bug-Debian: https://bugs.debian.org/1137507 +Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2026-48842 +--- + plugins/virtuser_query/virtuser_query.php | 10 +++++----- + 1 file changed, 5 insertions(+), 5 deletions(-) + +diff --git a/plugins/virtuser_query/virtuser_query.php b/plugins/virtuser_query/virtuser_query.php +index 11698c3..7414302 100644 +--- a/plugins/virtuser_query/virtuser_query.php ++++ b/plugins/virtuser_query/virtuser_query.php +@@ -63,8 +63,8 @@ class virtuser_query extends rcube_plugin + { + $dbh = $this->get_dbh(); + +- $sql_result = $dbh->query(preg_replace('/%u/', $dbh->escape($p['user']), $this->config['email'])); +- $result = []; ++ $sql_result = $dbh->query(str_replace('%u', $dbh->escape($p['user']), $this->config['email'])); ++ $result = []; + + while ($sql_arr = $dbh->fetch_array($sql_result)) { + if (strpos($sql_arr[0], '@')) { +@@ -101,7 +101,7 @@ class virtuser_query extends rcube_plugin + { + $dbh = $this->get_dbh(); + +- $sql_result = $dbh->query(preg_replace('/%m/', $dbh->escape($p['email']), $this->config['user'])); ++ $sql_result = $dbh->query(str_replace('%m', $dbh->escape($p['email']), $this->config['user'])); + + if ($sql_arr = $dbh->fetch_array($sql_result)) { + $p['user'] = $sql_arr[0]; +@@ -117,7 +117,7 @@ class virtuser_query extends rcube_plugin + { + $dbh = $this->get_dbh(); + +- $sql_result = $dbh->query(preg_replace('/%u/', $dbh->escape($p['user']), $this->config['host'])); ++ $sql_result = $dbh->query(str_replace('%u', $dbh->escape($p['user']), $this->config['host'])); + + if ($sql_arr = $dbh->fetch_array($sql_result)) { + $p['host'] = $sql_arr[0]; +@@ -133,7 +133,7 @@ class virtuser_query extends rcube_plugin + { + $dbh = $this->get_dbh(); + +- $sql_result = $dbh->query(preg_replace('/%u/', $dbh->escape($p['user']), $this->config['alias'])); ++ $sql_result = $dbh->query(str_replace('%u', $dbh->escape($p['user']), $this->config['alias'])); + + if ($sql_arr = $dbh->fetch_array($sql_result)) { + $p['user'] = $sql_arr[0]; diff -Nru roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48843.patch roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48843.patch --- roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48843.patch 1970-01-01 01:00:00.000000000 +0100 +++ roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48843.patch 2026-05-26 01:08:43.000000000 +0200 @@ -0,0 +1,86 @@ +From: Aleksander Machniak <[email protected]> +Date: Sun, 24 May 2026 09:19:25 +0200 +Subject: Fix SSRF bypass via specific local address URLs + +Origin: https://github.com/roundcube/roundcubemail/commit/cb3fc9041e91640ba9ba49ee7b2147c176ebf5a1 +Bug: https://roundcube.net/news/2026/05/24/security-updates-1.6.16-and-1.7.1 +Bug-Debian: https://bugs.debian.org/1137507 +Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2026-48843 +--- + program/lib/Roundcube/rcube_utils.php | 21 +++++++++++++++++++-- + tests/Framework/Utils.php | 10 ++++++++++ + 2 files changed, 29 insertions(+), 2 deletions(-) + +diff --git a/program/lib/Roundcube/rcube_utils.php b/program/lib/Roundcube/rcube_utils.php +index 8ff1db8..d1fbbb2 100644 +--- a/program/lib/Roundcube/rcube_utils.php ++++ b/program/lib/Roundcube/rcube_utils.php +@@ -1,6 +1,7 @@ + <?php + + use IPLib\Factory; ++use IPLib\ParseStringFlag; + + /* + +-----------------------------------------------------------------------+ +@@ -433,10 +434,25 @@ class rcube_utils + $host = parse_url($url, \PHP_URL_HOST); + + if (is_string($host)) { ++ $options = ParseStringFlag::IPV4_MAYBE_NON_DECIMAL ++ | ParseStringFlag::IPV4SUBNET_MAYBE_COMPACT ++ | ParseStringFlag::IPV4ADDRESS_MAYBE_NON_QUAD_DOTTED ++ | ParseStringFlag::MAY_INCLUDE_ZONEID; ++ ++ $host = trim($host, '[]'); ++ ++ // IPLib does not seem to work with IPv6 syntax for IPv4 addresses ++ $host = preg_replace('/^::ffff:/i', '', $host); ++ ++ if (preg_match('/([0-9a-f.-]+)\.nip\.io$/i', $host, $matches)) { ++ $host = trim($matches[1], '-.'); ++ } ++ + // TODO: This is pretty fast, but a single message can contain multiple links + // to the same target, maybe we should do some in-memory caching. +- if ($address = Factory::parseAddressString($host = trim($host, '[]'))) { ++ if ($address = Factory::parseAddressString($host, $options)) { + $nets = [ ++ '0.0.0.0', + '127.0.0.0/8', // loopback + '10.0.0.0/8', // RFC1918 + '172.16.0.0/12', // RFC1918 +@@ -457,7 +473,8 @@ class rcube_utils + } + + // FIXME: Should we accept any non-fqdn hostnames? +- return (bool) preg_match('/^localhost(\.localdomain)?$/i', $host); ++ $host = strtolower($host); ++ return $host == 'metadata.google.internal' || preg_match('/^localhost(\.localdomain)?\.?$/', $host); + } + + return false; +diff --git a/tests/Framework/Utils.php b/tests/Framework/Utils.php +index 8a95001..d5da478 100644 +--- a/tests/Framework/Utils.php ++++ b/tests/Framework/Utils.php +@@ -580,9 +580,19 @@ class Framework_Utils extends PHPUnit\Framework\TestCase + ['//127.0.0.1', true], + ['http://localhost', true], + ['http://localhost.localdomain', true], ++ ['http://localhost.:8080/', true], ++ ['http://2130706433:8080', true], ++ ['http://0x7f000001:8080', true], ++ ['http://0177.0.0.1:8080', true], ++ ['http://127.1:8080', true], ++ ['http://0.0.0.0:8080', true], ++ ['http://[::ffff:127.0.0.1]:8080', true], ++ ['http://127.0.0.1.nip.io', true], ++ ['http://metadata.google.internal', true], + // Non-local hosts + ['http://[2001:470::76:0:0:0:2]', false], + ['http://domain.tld', false], ++ ['http://20.0.0.1.nip.io', false], + ]; + } + diff -Nru roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48844.patch roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48844.patch --- roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48844.patch 1970-01-01 01:00:00.000000000 +0100 +++ roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48844.patch 2026-05-26 01:08:43.000000000 +0200 @@ -0,0 +1,73 @@ +From: Aleksander Machniak <[email protected]> +Date: Sun, 24 May 2026 09:24:18 +0200 +Subject: Fix code injection vulnerability - remove support for code + evaluation in LDAP `autovalues` option + +Origin: https://github.com/roundcube/roundcubemail/commit/ea1798a6fbf060abcc0ba73b2435036bf8016a5a +Bug: https://roundcube.net/news/2026/05/24/security-updates-1.6.16-and-1.7.1 +Bug-Debian: https://bugs.debian.org/1137507 +Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2026-48844 +--- + config/defaults.inc.php | 3 +-- + program/lib/Roundcube/rcube_ldap.php | 32 ++++++-------------------------- + 2 files changed, 7 insertions(+), 28 deletions(-) + +diff --git a/config/defaults.inc.php b/config/defaults.inc.php +index 65a8214..0a691a1 100644 +--- a/config/defaults.inc.php ++++ b/config/defaults.inc.php +@@ -1142,8 +1142,7 @@ $config['ldap_public']['Verisign'] = [ + 'sub_fields' => [], + // Generate values for the following LDAP attributes automatically when creating a new record + 'autovalues' => [ +- // 'uid' => 'md5(microtime())', // You may specify PHP code snippets which are then eval'ed +- // 'mail' => '{givenname}.{sn}@mydomain.com', // or composite strings with placeholders for existing attributes ++ // 'mail' => '{givenname}.{sn}@mydomain.com', // composite strings with placeholders for existing attributes + ], + 'sort' => 'cn', // The field to sort the listing by. + 'scope' => 'sub', // search mode: sub|base|list +diff --git a/program/lib/Roundcube/rcube_ldap.php b/program/lib/Roundcube/rcube_ldap.php +index cee84f8..82d3710 100644 +--- a/program/lib/Roundcube/rcube_ldap.php ++++ b/program/lib/Roundcube/rcube_ldap.php +@@ -1581,34 +1581,14 @@ class rcube_ldap extends rcube_addressbook + + foreach ($this->prop['autovalues'] as $lf => $templ) { + if (empty($attrs[$lf])) { +- if (strpos($templ, '(') !== false) { +- // replace {attr} placeholders with (escaped!) attribute values to be safely eval'd +- $code = preg_replace('/\{\w+\}/', '', strtr($templ, array_map('addslashes', $attrvals))); +- $res = false; +- +- try { +- $res = eval("return ($code);"); +- } +- catch (ParseError $e) { +- // ignore +- } +- +- if ($res === false) { +- rcube::raise_error([ +- 'code' => 505, 'file' => __FILE__, 'line' => __LINE__, +- 'message' => "Expression parse error on: ($code)" +- ], true, false); +- continue; +- } +- +- $attrs[$lf] = $res; +- } +- else { +- // replace {attr} placeholders with concrete attribute values +- $attrs[$lf] = preg_replace('/\{\w+\}/', '', strtr($templ, $attrvals)); +- } ++ // replace {attr} placeholders with concrete attribute values ++ $attrs[$lf] = preg_replace('/\{\w+\}/', '', strtr($templ, $attrvals)); + } + } ++ ++ $rcube = rcube::get_instance(); ++ $plugin = $rcube->plugins->exec_hook('ldap_autovalues', ['attrs' => $attrs]); ++ $attrs = $plugin['attrs']; + } + + /** diff -Nru roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48845.patch roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48845.patch --- roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48845.patch 1970-01-01 01:00:00.000000000 +0100 +++ roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48845.patch 2026-05-26 01:08:43.000000000 +0200 @@ -0,0 +1,39 @@ +From: Aleksander Machniak <[email protected]> +Date: Sun, 24 May 2026 09:21:07 +0200 +Subject: Fix local/private URL fetch bypass when remote resources were not + allowed + +Origin: https://github.com/roundcube/roundcubemail/commit/7b52353653a67e6073b97d70eb94047132b78556 +Bug: https://roundcube.net/news/2026/05/24/security-updates-1.6.16-and-1.7.1 +Bug-Debian: https://bugs.debian.org/1137507 +Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2026-48845 +--- + program/lib/Roundcube/rcube_washtml.php | 2 +- + tests/Framework/Washtml.php | 1 + + 2 files changed, 2 insertions(+), 1 deletion(-) + +diff --git a/program/lib/Roundcube/rcube_washtml.php b/program/lib/Roundcube/rcube_washtml.php +index b32a4de..32270ce 100644 +--- a/program/lib/Roundcube/rcube_washtml.php ++++ b/program/lib/Roundcube/rcube_washtml.php +@@ -399,7 +399,7 @@ class rcube_washtml + } + + if (preg_match('/^(http|https|ftp):.+/i', $uri)) { +- if (!empty($this->config['allow_remote']) || rcube_utils::is_local_url($uri)) { ++ if (!empty($this->config['allow_remote'])) { + return $uri; + } + +diff --git a/tests/Framework/Washtml.php b/tests/Framework/Washtml.php +index f569313..7d524f4 100644 +--- a/tests/Framework/Washtml.php ++++ b/tests/Framework/Washtml.php +@@ -712,6 +712,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase + ['<link href="http://TRACKING_URL/">', true], + ['<link href="src:abc">', false], + ['<img src="http://TRACKING_URL/">', true], ++ ['<img src="http://127.0.0.1">', true], + ['<img src="data:image">', false], + ['<p style="backgr\ound-image: \ur\l(\'http://TRACKING_URL\')"></p>', true], + ['<p style="background-image: var(--x, url(http://evil.com/1.gif))"></p>', true], diff -Nru roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48846.patch roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48846.patch --- roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48846.patch 1970-01-01 01:00:00.000000000 +0100 +++ roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48846.patch 2026-05-26 01:08:43.000000000 +0200 @@ -0,0 +1,91 @@ +From: Aleksander Machniak <[email protected]> +Date: Sun, 24 May 2026 09:20:18 +0200 +Subject: Fix bypass of remote image blocking via CSS var() + +Origin: https://github.com/roundcube/roundcubemail/commit/852350486b88b35b8544e8a630fad89e99e2150a +Bug: https://roundcube.net/news/2026/05/24/security-updates-1.6.16-and-1.7.1 +Bug-Debian: https://bugs.debian.org/1137507 +Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2026-48846 +--- + program/lib/Roundcube/rcube_utils.php | 15 +++++++++++---- + tests/Framework/Utils.php | 5 +++++ + tests/Framework/Washtml.php | 18 ++++++++++++------ + 3 files changed, 28 insertions(+), 10 deletions(-) + +diff --git a/program/lib/Roundcube/rcube_utils.php b/program/lib/Roundcube/rcube_utils.php +index d1fbbb2..0ddd910 100644 +--- a/program/lib/Roundcube/rcube_utils.php ++++ b/program/lib/Roundcube/rcube_utils.php +@@ -627,10 +627,17 @@ class rcube_utils + } else { + $value = ''; + foreach (self::explode_css_property_block($rule[1]) as $val) { +- if ($url_callback && preg_match('/^url\s*\(/i', $val)) { +- if (preg_match('/^url\s*\(\s*[\'"]?([^\'"\)]*)[\'"]?\s*\)/iu', $val, $match)) { +- if ($url = $url_callback($match[1])) { +- $value .= ' url(' . $url . ')'; ++ if ($url_callback && preg_match('/\burl\s*\(/i', $val)) { ++ if (preg_match_all('/(\b)url\s*\(\s*[\'"]?([^\'"\)]*)[\'"]?\s*\)/iu', $val, $matches)) { ++ foreach ($matches[2] as $idx => $url) { ++ if ($url = $url_callback($url)) { ++ $val = str_replace($matches[0][$idx], $matches[1][$idx] . "url({$url})", $val); ++ } else { ++ $val = ''; ++ } ++ } ++ if (strlen($val)) { ++ $value .= ' ' . $val; + } + } + } elseif (preg_match('/;.+/', $val)) { +diff --git a/tests/Framework/Utils.php b/tests/Framework/Utils.php +index d5da478..d654802 100644 +--- a/tests/Framework/Utils.php ++++ b/tests/Framework/Utils.php +@@ -253,6 +253,11 @@ class Framework_Utils extends PHPUnit\Framework\TestCase + $mod = \rcube_utils::mod_css_styles("p { background: url('//data:image&leak'); }", 'rcmbody'); + $this->assertSame('#rcmbody p {}', $mod); + ++ $mod = \rcube_utils::mod_css_styles("p { background-image: var(--x, url(http://evil.com/1.gif)) }", 'rcmbody'); ++ $this->assertSame('#rcmbody p {}', $mod); ++ $mod = \rcube_utils::mod_css_styles("p { background-image: var(--x, url(http://evil.com/1.gif)) }", 'rcmbody', true); ++ $this->assertSame('#rcmbody p { background-image: var(--x, url(http://evil.com/1.gif)); }', $mod); ++ + // Note: This looks to me like a bug in browsers, for now we don't allow image-set at all + $mod = \rcube_utils::mod_css_styles("p { background: image-set('//evil.com/img.png' 1x); }", 'rcmbody'); + $this->assertSame('#rcmbody p {}', $mod); +diff --git a/tests/Framework/Washtml.php b/tests/Framework/Washtml.php +index 611bcc3..f569313 100644 +--- a/tests/Framework/Washtml.php ++++ b/tests/Framework/Washtml.php +@@ -709,18 +709,24 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase + function test_extlinks() + { + $html = [ +- ["<link href=\"http://TRACKING_URL/\">", true], +- ["<link href=\"src:abc\">", false], +- ["<img src=\"http://TRACKING_URL/\">", true], +- ["<img src=\"data:image\">", false], +- ['<p style="backgr\\ound-image: \\ur\\l(\'http://TRACKING_URL\')"></p>', true], ++ ['<link href="http://TRACKING_URL/">', true], ++ ['<link href="src:abc">', false], ++ ['<img src="http://TRACKING_URL/">', true], ++ ['<img src="data:image">', false], ++ ['<p style="backgr\ound-image: \ur\l(\'http://TRACKING_URL\')"></p>', true], ++ ['<p style="background-image: var(--x, url(http://evil.com/1.gif))"></p>', true], ++ ['<p style="cursor: var(--x, url(http://evil.com/5.gif), auto)"></p>', true], ++ ['<p style="background: var(--a, var(--b, url(http://evil.com/6.gif)))"></p>', true], ++ ['<p style="color: red; background-image: var(--x, url(http://evil.com/7.gif)); font-size: 12px"></p>', true], ++ ['<p style="background: image(url(http://evil.com/8.gif))"></p>', true], ++ ['<p style="background: cross-fade(url(http://evil.com/9a.gif), url(http://evil.com/9b.gif), 50%)"></p>', true], + ]; + + foreach ($html as $item) { + $washer = new rcube_washtml; + $washed = $washer->wash($item[0]); + +- $this->assertSame($item[1], $washer->extlinks); ++ $this->assertSame($item[1], $washer->extlinks, "Failed on: {$item[0]}"); + } + + foreach ($html as $item) { diff -Nru roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48847.patch roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48847.patch --- roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48847.patch 1970-01-01 01:00:00.000000000 +0100 +++ roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48847.patch 2026-05-26 01:08:43.000000000 +0200 @@ -0,0 +1,80 @@ +From: Aleksander Machniak <[email protected]> +Date: Sun, 24 May 2026 09:23:23 +0200 +Subject: Fix pre-auth arbitrary file delete via redis/memcache session + poisoning bypass + +Origin: https://github.com/roundcube/roundcubemail/commit/703318e6a59515b73b0d8aa2a91e346b02f56baa +Bug: https://roundcube.net/news/2026/05/24/security-updates-1.6.16-and-1.7.1 +Bug-Debian: https://bugs.debian.org/1137507 +Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2026-48847 +--- + program/include/rcmail_attachment_handler.php | 11 +---------- + program/include/rcmail_sendmail.php | 12 ++---------- + 2 files changed, 3 insertions(+), 20 deletions(-) + +diff --git a/program/include/rcmail_attachment_handler.php b/program/include/rcmail_attachment_handler.php +index 2ee02e0..0eca6c0 100644 +--- a/program/include/rcmail_attachment_handler.php ++++ b/program/include/rcmail_attachment_handler.php +@@ -117,16 +117,6 @@ class rcmail_attachment_handler + $this->mimetype = rcube_mime::fix_mimetype($this->mimetype); + } + +- /** +- * Remove temp files, etc. +- */ +- public function __destruct() +- { +- if ($this->body_file) { +- @unlink($this->body_file); +- } +- } +- + /** + * Check if the object is a message part not uploaded file + * +@@ -260,6 +250,7 @@ class rcmail_attachment_handler + $this->body_file = $filename; + fclose($fp); + @chmod($filename, 0600); ++ rcmail::get_instance()->add_shutdown_function(static function () use ($filename) { @unlink($filename); }); + + return true; + } +diff --git a/program/include/rcmail_sendmail.php b/program/include/rcmail_sendmail.php +index 6b3ecfb..d202895 100644 +--- a/program/include/rcmail_sendmail.php ++++ b/program/include/rcmail_sendmail.php +@@ -76,16 +76,6 @@ class rcmail_sendmail + } + } + +- /** +- * Object destructor to cleanup temporary files +- */ +- public function __destruct() +- { +- foreach ($this->temp_files as $file) { +- @unlink($file); +- } +- } +- + /** + * Collect input data for message headers + * +@@ -461,6 +451,7 @@ class rcmail_sendmail + + if ($mailbody_file) { + $this->temp_files[$message->headers()['Message-ID']] = $mailbody_file; ++ $this->rcmail->add_shutdown_function(static function () use ($mailbody_file) { @unlink($mailbody_file); }); + } + + // save message sent time +@@ -545,6 +536,7 @@ class rcmail_sendmail + + if (!is_a($msg, 'PEAR_Error')) { + $this->temp_files[$msg_id] = $msg_file; ++ $this->rcmail->add_shutdown_function(static function () use ($msg_file) { @unlink($msg_file); }); + } + } + diff -Nru roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48848.patch roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48848.patch --- roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48848.patch 1970-01-01 01:00:00.000000000 +0100 +++ roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48848.patch 2026-05-26 01:08:43.000000000 +0200 @@ -0,0 +1,63 @@ +From: Aleksander Machniak <[email protected]> +Date: Sun, 24 May 2026 09:15:35 +0200 +Subject: Fix CSS injection bypass in HTML sanitizer via SVG `<animate + attributeName="style">` + +Origin: https://github.com/roundcube/roundcubemail/commit/58e5263f341e6a418774fb6d2643669a3c4d8a27 +Bug: https://roundcube.net/news/2026/05/24/security-updates-1.6.16-and-1.7.1 +Bug-Debian: https://bugs.debian.org/1137507 +Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2026-48848 +--- + program/lib/Roundcube/rcube_washtml.php | 14 ++++++++++---- + tests/Framework/Washtml.php | 12 ++++++++++++ + 2 files changed, 22 insertions(+), 4 deletions(-) + +diff --git a/program/lib/Roundcube/rcube_washtml.php b/program/lib/Roundcube/rcube_washtml.php +index 922a5da..b32a4de 100644 +--- a/program/lib/Roundcube/rcube_washtml.php ++++ b/program/lib/Roundcube/rcube_washtml.php +@@ -293,11 +293,17 @@ class rcube_washtml + $key = strtolower($name); + $value = $attr->nodeValue; + +- if ($key == 'style' && ($style = $this->wash_style($value))) { +- // replace double quotes to prevent syntax error and XSS issues (#1490227) +- $result .= ' style="' . str_replace('"', '"', $style) . '"'; ++ if ($key == 'style' || ($key == 'values' && self::attribute_value($node, 'attributename', '/^style$/i'))) { ++ $style = ''; ++ if ($value === '' || ($style = $this->wash_style($value))) { ++ // replace double quotes to prevent syntax error and XSS issues (#1490227) ++ $result .= ' ' . $attr->nodeName . '="' . str_replace('"', '"', $style) . '"'; ++ } ++ else { ++ $washed[] = htmlspecialchars($attr->nodeName, \ENT_QUOTES, $this->config['charset']); ++ } + } +- else if (isset($this->_html_attribs[$key]) || in_array($key, $additional_attribs)) { ++ elseif (isset($this->_html_attribs[$key]) || in_array($key, $additional_attribs)) { + $value = trim($value); + $out = null; + +diff --git a/tests/Framework/Washtml.php b/tests/Framework/Washtml.php +index ec1dd5d..611bcc3 100644 +--- a/tests/Framework/Washtml.php ++++ b/tests/Framework/Washtml.php +@@ -525,6 +525,18 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase + '<svg><animate attributeName="fill" values="url(http://external.site)" dur="1s" begin="0s" fill="freeze" /></svg>', + '<svg><!-- animate blocked --></svg>', + ], ++ [ ++ '<svg><rect><animate attributeName="style" values="filter:url(http://attacker.com)" dur="0s" fill="freeze"/></rect></svg>', ++ '<svg><rect><animate attributeName="style" dur="0s" fill="freeze" x-washed="values" /></rect></svg>', ++ ], ++ [ ++ '<svg><rect><animate attributeName="style" values="width:expression(alert(1))" dur="0s" fill="freeze"/></rect></svg>', ++ '<svg><rect><animate attributeName="style" dur="0s" fill="freeze" x-washed="values" /></rect></svg>', ++ ], ++ [ ++ '<svg><rect><animate attributeName="style" values="position:fixed;top:0;left:0" dur="0s" fill="freeze"/></rect></svg>', ++ '<svg><rect><animate attributeName="style" values="position: absolute; top: 0; left: 0" dur="0s" fill="freeze" /></rect></svg>', ++ ], + ]; + } + diff -Nru roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48849.patch roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48849.patch --- roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48849.patch 1970-01-01 01:00:00.000000000 +0100 +++ roundcube-1.6.5+dfsg/debian/patches/CVE-2026-48849.patch 2026-05-26 01:08:43.000000000 +0200 @@ -0,0 +1,26 @@ +From: Aleksander Machniak <[email protected]> +Date: Sun, 24 May 2026 09:34:27 +0200 +Subject: Fix stored XSS/HTML/CSS injection in subject field of the draft + restore dialog + +Origin: https://github.com/roundcube/roundcubemail/commit/a21519187873ce962db029b6ff68e47bd7f3fd8a +Bug: https://roundcube.net/news/2026/05/24/security-updates-1.6.16-and-1.7.1 +Bug-Debian: https://bugs.debian.org/1137507 +Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2026-48849 +--- + program/js/app.js | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/program/js/app.js b/program/js/app.js +index 16dcbb4..b8a51ff 100644 +--- a/program/js/app.js ++++ b/program/js/app.js +@@ -4718,7 +4718,7 @@ function rcube_webmail() + this.show_popup_dialog( + this.get_label('restoresavedcomposedata') + .replace('$date', new Date(formdata.changed).toLocaleString()) +- .replace('$subject', formdata._subject) ++ .replace('$subject', $('<span>').text(formdata._subject).html()) + .replace(/\n/g, '<br/>'), + this.get_label('restoremessage'), + [{ diff -Nru roundcube-1.6.5+dfsg/debian/patches/series roundcube-1.6.5+dfsg/debian/patches/series --- roundcube-1.6.5+dfsg/debian/patches/series 2026-03-20 19:15:19.000000000 +0100 +++ roundcube-1.6.5+dfsg/debian/patches/series 2026-05-26 01:08:43.000000000 +0200 @@ -46,5 +46,13 @@ CVE-2026-35544.patch CVE-2026-35539.patch CVE-2026-35540.patch -Avoid-dependency-on-new-package-mlocati-ip-lib.patch CVE-2026-35545.patch +CVE-2026-48849.patch +CVE-2026-48848.patch +CVE-2026-48842.patch +CVE-2026-48843.patch +CVE-2026-48846.patch +CVE-2026-48845.patch +CVE-2026-48847.patch +CVE-2026-48844.patch +Avoid-dependency-on-new-package-mlocati-ip-lib.patch
signature.asc
Description: PGP signature

