[Pkg-roundcube-maintainers] Bug#1137507: roundcube: Multiple security vulnerabilities
Guilhem Moulin
guilhem at debian.org
Tue May 26 12:57:38 BST 2026
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
-------------- next part --------------
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 <guilhem at debian.org> 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 at 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) {
-------------- next part --------------
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 <guilhem at debian.org> 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 <alec at alec.pl>
+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 <alec at alec.pl>
+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 <alec at alec.pl>
+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 <alec at alec.pl>
+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 <alec at alec.pl>
+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 <alec at alec.pl>
+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 <alec at alec.pl>
+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 <alec at alec.pl>
+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
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: not available
URL: <http://alioth-lists.debian.net/pipermail/pkg-roundcube-maintainers/attachments/20260526/eff5dc7b/attachment-0001.sig>
More information about the Pkg-roundcube-maintainers
mailing list