[Pkg-roundcube-maintainers] CVE-2024-42009/roundcube: bullseye-security upload
Guilhem Moulin
guilhem at debian.org
Mon Aug 12 12:05:09 BST 2024
Hi,
On Tue, 06 Aug 2024 at 18:31:37 +0200, Guilhem Moulin wrote:
> Unfortunately for bullseye the backport requires more work. I'll try to
> do that ASAP but I'm not yet sure that I will have time to finalize it
> before the suite is handed over to the LTS team.
Thanks for the bookworm DSA! Here comes a debdiff for bullseye-security.
The backports were quite intrusive but have now been thoroughly tested,
incl. on two production instances totaling ~200 users AFAIK none of whom
didn't report any misbehavior.
Note that the original patch for CVE-2024-42008 introduces a regression
(#1078456): it sets a too restrictive Content-Security-Policy on the
attachment preview page which breaks printing and other handling of
image attachments. I backported the fix for 1.4.15+dfsg.1-1+deb11u4,
but 1.6.5+dfsg-1+deb12u3 is affected. I assume this doesn't warrant a
follow-up DSA, right? Will go via s-pu in that case.
Cheers,
--
Guilhem.
-------------- next part --------------
diffstat for roundcube-1.4.15+dfsg.1 roundcube-1.4.15+dfsg.1
changelog | 13
patches/CVE-2024-42008.patch | 71 +
patches/CVE-2024-42009.patch | 616 ++++++++++
patches/CVE-2024-42010.patch | 611 +++++++++
patches/Fix-infinite-loop-when-parsing-malformed-Sieve-script.patch | 52
patches/Fix-regression-where-printing-scaling-rotating-image-atta.patch | 71 +
patches/series | 5
7 files changed, 1439 insertions(+)
diff -Nru roundcube-1.4.15+dfsg.1/debian/changelog roundcube-1.4.15+dfsg.1/debian/changelog
--- roundcube-1.4.15+dfsg.1/debian/changelog 2024-06-17 04:10:38.000000000 +0200
+++ roundcube-1.4.15+dfsg.1/debian/changelog 2024-08-08 23:48:56.000000000 +0200
@@ -1,3 +1,16 @@
+roundcube (1.4.15+dfsg.1-1+deb11u4) bullseye-security; urgency=high
+
+ * Fix CVE-2024-42008: Cross-site scripting (XSS) vulnerability in serving of
+ attachments other than HTML or SVG.
+ * Fix CVE-2024-42009: Cross-site scripting (XSS) vulnerability in
+ post-processing of sanitized HTML content. (Closes: #1077969)
+ * Fix CVE-2024-42010: Information leak (access to remote content) via
+ insufficient CSS filtering.
+ * Backport upstream fix for infinite loop when parsing malformed Sieve
+ script.
+
+ -- Guilhem Moulin <guilhem at debian.org> Thu, 08 Aug 2024 23:48:56 +0200
+
roundcube (1.4.15+dfsg.1-1+deb11u3) bullseye-security; urgency=high
* Fix CVE-2024-37384: Cross-site scripting (XSS) vulnerability in handling
diff -Nru roundcube-1.4.15+dfsg.1/debian/patches/CVE-2024-42008.patch roundcube-1.4.15+dfsg.1/debian/patches/CVE-2024-42008.patch
--- roundcube-1.4.15+dfsg.1/debian/patches/CVE-2024-42008.patch 1970-01-01 01:00:00.000000000 +0100
+++ roundcube-1.4.15+dfsg.1/debian/patches/CVE-2024-42008.patch 2024-08-08 23:48:56.000000000 +0200
@@ -0,0 +1,71 @@
+From: Aleksander Machniak <alec at alec.pl>
+Date: Sat, 3 Aug 2024 09:10:55 +0200
+Subject: Fix XSS vulnerability in serving of attachments other than HTML or
+ SVG
+
+Credits to Oskar Zeino-Mahmalat (Sonar) https://www.sonarsource.com
+
+Origin: https://github.com/roundcube/roundcubemail/commit/c222ea8b99448ead20ab3864fcc29c84ed17403a
+Bug-Debian: https://security-tracker.debian.org/tracker/CVE-2024-42008
+Bug-Debian: https://bugs.debian.org/1077969
+---
+ program/lib/Roundcube/rcube_output.php | 20 +++++++++++++++-----
+ program/steps/mail/get.inc | 5 +++++
+ 2 files changed, 20 insertions(+), 5 deletions(-)
+
+diff --git a/program/lib/Roundcube/rcube_output.php b/program/lib/Roundcube/rcube_output.php
+index f24c47f..a51b75b 100644
+--- a/program/lib/Roundcube/rcube_output.php
++++ b/program/lib/Roundcube/rcube_output.php
+@@ -242,13 +242,20 @@ abstract class rcube_output
+ $ctype = $params['type'];
+ }
+
+- if ($disposition == 'inline' && stripos($ctype, 'text') === 0) {
+- $charset = $this->charset;
+- if (!empty($params['type_charset']) && rcube_charset::is_valid($params['type_charset'])) {
+- $charset = $params['type_charset'];
++ // Send unsafe content as plain text
++ if ($disposition == 'inline') {
++ if (preg_match('~(javascript|jscript|ecmascript|xml|html|text/)~i', $ctype)) {
++ $ctype = 'text/plain';
+ }
+
+- $ctype .= "; charset={$charset}";
++ if (stripos($ctype, 'text') === 0) {
++ $charset = $this->charset;
++ if (!empty($params['type_charset']) && rcube_charset::is_valid($params['type_charset'])) {
++ $charset = $params['type_charset'];
++ }
++
++ $ctype .= "; charset={$charset}";
++ }
+ }
+
+ if (is_string($filename) && strlen($filename) > 0 && strlen($filename) <= 1024) {
+@@ -278,6 +285,9 @@ abstract class rcube_output
+ header("Content-Length: " . $params['length']);
+ }
+
++ // Use strict security policy to make sure no javascript content is executed
++ header("Content-Security-Policy: default-src 'none'");
++
+ // don't kill the connection if download takes more than 30 sec.
+ if (!array_key_exists('time_limit', $params)) {
+ $params['time_limit'] = 3600;
+diff --git a/program/steps/mail/get.inc b/program/steps/mail/get.inc
+index 8e3647f..db152cb 100644
+--- a/program/steps/mail/get.inc
++++ b/program/steps/mail/get.inc
+@@ -240,6 +240,11 @@ if (empty($_GET['_thumb']) && $attachment->is_valid()) {
+ $RCMAIL->gettext('allow'),
+ $RCMAIL->url(array_merge($_GET, array('_safe' => 1)))
+ );
++ } else {
++ // Use strict security policy to make sure no javascript is executed
++ // TODO: Make the above "blocked resources button" working with strict policy
++ // TODO: Move this to rcmail_html_page::write()?
++ header("Content-Security-Policy: script-src 'none'");
+ }
+ }
+
diff -Nru roundcube-1.4.15+dfsg.1/debian/patches/CVE-2024-42009.patch roundcube-1.4.15+dfsg.1/debian/patches/CVE-2024-42009.patch
--- roundcube-1.4.15+dfsg.1/debian/patches/CVE-2024-42009.patch 1970-01-01 01:00:00.000000000 +0100
+++ roundcube-1.4.15+dfsg.1/debian/patches/CVE-2024-42009.patch 2024-08-08 23:48:56.000000000 +0200
@@ -0,0 +1,616 @@
+From: Aleksander Machniak <alec at alec.pl>
+Date: Sat, 3 Aug 2024 09:09:09 +0200
+Subject: Fix XSS vulnerability in post-processing of sanitized HTML content
+
+Credits to Oskar Zeino-Mahmalat (https://www.sonarsource.com)
+
+Origin: https://github.com/roundcube/roundcubemail/commit/1b3bb11d4f3880ba2ac8d1f398d32bf99be47122
+Origin: https://github.com/roundcube/roundcubemail/commit/a25e48e2daec522432fea3c37f3917366e2948d1
+Bug-Debian: https://security-tracker.debian.org/tracker/CVE-2024-42009
+Bug-Debian: https://bugs.debian.org/1077969
+---
+ program/lib/Roundcube/rcube_washtml.php | 46 +++++---
+ program/steps/mail/compose.inc | 37 ++----
+ program/steps/mail/func.inc | 189 +++++++++++++------------------
+ program/steps/mail/show.inc | 4 -
+ program/steps/settings/save_identity.inc | 9 +-
+ tests/MailFunc.php | 69 +++++++----
+ 6 files changed, 170 insertions(+), 184 deletions(-)
+
+diff --git a/program/lib/Roundcube/rcube_washtml.php b/program/lib/Roundcube/rcube_washtml.php
+index 8cb0d24..8a3a5ab 100644
+--- a/program/lib/Roundcube/rcube_washtml.php
++++ b/program/lib/Roundcube/rcube_washtml.php
+@@ -164,7 +164,14 @@ class rcube_washtml
+ public $extlinks = false;
+
+ /** @var array Current settings */
+- private $config = array();
++ private $config = array(
++ 'add_comments' => true,
++ 'allow_remote' => false,
++ 'base_url' => '',
++ 'charset' => RCUBE_CHARSET,
++ 'cid_map' => array(),
++ 'show_washed' => true,
++ );
+
+ /** @var array Registered callback functions for tags */
+ private $handlers = array();
+@@ -217,7 +224,7 @@ class rcube_washtml
+
+ unset($p['html_elements'], $p['html_attribs'], $p['ignore_elements'], $p['void_elements'], $p['css_prefix']);
+
+- $this->config = $p + array('show_washed' => true, 'allow_remote' => false, 'cid_map' => array());
++ $this->config = array_merge($this->config, $p);
+ }
+
+ /**
+@@ -562,7 +569,7 @@ class rcube_washtml
+ true, false);
+ }
+
+- return '<!-- ignored -->';
++ return $this->config['add_comments'] ? '<!-- ignored -->' : '';
+ }
+
+ $node = $node->firstChild;
+@@ -576,7 +583,9 @@ class rcube_washtml
+ if ($tagName == 'link') {
+ $uri = $this->wash_uri($node->getAttribute('href'), false, false);
+ if (!$uri) {
+- $dump .= '<!-- link ignored -->';
++ if ($this->config['add_comments']) {
++ $dump .= '<!-- link ignored -->';
++ }
+ break;
+ }
+
+@@ -586,7 +595,9 @@ class rcube_washtml
+ && self::attribute_value($node, 'attributename', 'href')
+ ) {
+ // Insecure svg tags
+- $dump .= "<!-- $tagName blocked -->";
++ if ($this->config['add_comments']) {
++ $dump .= "<!-- {$tagName} blocked -->";
++ }
+ break;
+ }
+
+@@ -596,13 +607,13 @@ class rcube_washtml
+ }
+ else if (isset($this->_html_elements[$tagName])) {
+ $content = $this->dumpHtml($node, $level);
+- $dump .= '<' . $node->nodeName;
++ $tag = '<' . $node->nodeName;
+
+ if ($tagName == 'svg') {
+ $xpath = new DOMXPath($node->ownerDocument);
+ foreach ($xpath->query('namespace::*') as $ns) {
+ if ($ns->nodeName != 'xmlns:xml') {
+- $dump .= sprintf(' %s="%s"',
++ $tag .= sprintf(' %s="%s"',
+ $ns->nodeName,
+ htmlspecialchars($ns->nodeValue, ENT_QUOTES, $this->config['charset'])
+ );
+@@ -613,20 +624,25 @@ class rcube_washtml
+ $content = htmlspecialchars($content, ENT_QUOTES | ENT_SUBSTITUTE, $this->config['charset']);
+ }
+
+- $dump .= $this->wash_attribs($node);
++ $tag .= $this->wash_attribs($node);
+
+- if ($content === '' && ($this->is_xml || isset($this->_void_elements[$tagName]))) {
+- $dump .= ' />';
+- }
+- else {
+- $dump .= '>' . $content . '</' . $node->nodeName . '>';
++ if (isset($this->_ignore_elements[$tagName])) {
++ $dump .= $content;
++ } elseif ($content === '' && ($this->is_xml || isset($this->_void_elements[$tagName]))) {
++ $dump .= $tag . ' />';
++ } else {
++ $dump .= $tag . '>' . $content . '</' . $node->nodeName . '>';
+ }
+ }
+ else if (isset($this->_ignore_elements[$tagName])) {
+- $dump .= '<!-- ' . htmlspecialchars($node->nodeName, ENT_QUOTES, $this->config['charset']) . ' not allowed -->';
++ if ($this->config['add_comments']) {
++ $dump .= '<!-- ' . htmlspecialchars($node->nodeName, ENT_QUOTES, $this->config['charset']) . ' not allowed -->';
++ }
+ }
+ else {
+- $dump .= '<!-- ' . htmlspecialchars($node->nodeName, ENT_QUOTES, $this->config['charset']) . ' ignored -->';
++ if ($this->config['add_comments']) {
++ $dump .= '<!-- ' . htmlspecialchars($node->nodeName, ENT_QUOTES, $this->config['charset']) . ' ignored -->';
++ }
+ $dump .= $this->dumpHtml($node, $level); // ignore tags not its content
+ }
+ break;
+diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc
+index 73be67b..06db2b4 100644
+--- a/program/steps/mail/compose.inc
++++ b/program/steps/mail/compose.inc
+@@ -335,7 +335,7 @@ function rcmail_process_compose_params(&$COMPOSE)
+ // clean HTML message body which can be submitted by URL
+ if (!empty($COMPOSE['param']['body'])) {
+ if ($COMPOSE['param']['html'] = strpos($COMPOSE['param']['body'], '<') !== false) {
+- $wash_params = array('safe' => false, 'inline_html' => true);
++ $wash_params = array('safe' => false);
+ $COMPOSE['param']['body'] = rcmail_prepare_html_body($COMPOSE['param']['body'], $wash_params);
+ }
+ }
+@@ -880,40 +880,25 @@ function rcmail_prepare_html_body($body, $wash_params = array())
+ static $part_no;
+
+ // Set attributes of the part container
+- $container_id = $COMPOSE['mode'] . 'body' . (++$part_no);
+- $container_attrib = array('id' => $container_id);
+-
+- $body_args = array(
+- 'safe' => $MESSAGE->is_safe,
+- 'plain' => false,
+- 'css_prefix' => 'v' . $part_no,
++ $container_id = $COMPOSE['mode'] . 'body' . (++$part_no);
++ $wash_params += array(
++ 'safe' => $MESSAGE->is_safe,
++ 'css_prefix' => 'v' . $part_no,
++ 'add_comments' => false,
+ );
+
+- // remove comments (produced by washtml)
+- $replace = array('/<!--[^>]+-->/' => '');
+-
+ if ($COMPOSE['mode'] == rcmail_sendmail::MODE_DRAFT) {
+ // convert TinyMCE's empty-line sequence (#1490463)
+- $replace['/<p>\xC2\xA0<\/p>/'] = '<p><br /></p>';
+- // remove <body> tags
+- $replace['/<body([^>]*)>/i'] = '';
+- $replace['/<\/body>/i'] = '';
++ $body = preg_replace('/<p>\xC2\xA0<\/p>/', '<p><br /></p>', $body);
++ // remove <body> tags (not their content)
++ $wash_params['ignore_elements'] = array('body');
+ }
+ else {
+- $body_args['container_id'] = $container_id;
+- $body_args['container_attrib'] = $container_attrib;
++ $wash_params['container_id'] = $container_id;
+ }
+
+ // Make the HTML content safe and clean
+- $body = rcmail_wash_html($body, $wash_params + $body_args, $CID_MAP);
+- $body = preg_replace(array_keys($replace), array_values($replace), $body);
+- $body = rcmail_html4inline($body, $body_args);
+-
+- if ($COMPOSE['mode'] != rcmail_sendmail::MODE_DRAFT) {
+- $body = html::div($container_attrib, $body);
+- }
+-
+- return $body;
++ return rcmail_wash_html($body, $wash_params, $CID_MAP);
+ }
+
+ // Removes signature from the message body
+diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
+index 4b96270..f8aed12 100644
+--- a/program/steps/mail/func.inc
++++ b/program/steps/mail/func.inc
+@@ -836,6 +836,7 @@ function rcmail_check_safe($message)
+ function rcmail_wash_html($html, $p, $cid_replaces = array())
+ {
+ global $REMOTE_OBJECTS, $RCMAIL;
++ global $wash_html_body_attrs;
+
+ $p += array('safe' => false, 'inline_html' => true);
+
+@@ -862,13 +863,17 @@ function rcmail_wash_html($html, $p, $cid_replaces = array())
+ // clean HTML with washhtml by Frederic Motte
+ $wash_opts = array(
+ 'show_washed' => false,
++ 'add_comments' => isset($p['add_comments']) ? $p['add_comments'] : true,
+ 'allow_remote' => $p['safe'],
+ 'blocked_src' => $RCMAIL->output->asset_url('program/resources/blocked.gif'),
+ 'charset' => RCUBE_CHARSET,
+ 'cid_map' => $cid_replaces,
+ 'html_elements' => array('body'),
+ 'css_prefix' => $p['css_prefix'],
++ 'ignore_elements' => isset($p['ignore_elements']) ? $p['ignore_elements'] : array(),
++ // internal configuration
+ 'container_id' => $p['container_id'],
++ 'body_class' => isset($p['body_class']) ? $p['body_class'] : '',
+ );
+
+ if (!$p['inline_html']) {
+@@ -889,6 +894,20 @@ function rcmail_wash_html($html, $p, $cid_replaces = array())
+ // initialize HTML washer
+ $washer = new rcube_washtml($wash_opts);
+
++ $wash_html_body_attrs = array();
++
++ if ($p['inline_html']) {
++ $washer->add_callback('body', 'rcmail_washtml_callback');
++
++ if ($wash_opts['body_class']) {
++ $wash_html_body_attrs['class'] = $wash_opts['body_class'];
++ }
++
++ if ($wash_opts['container_id']) {
++ $wash_html_body_attrs['id'] = $wash_opts['container_id'];
++ }
++ }
++
+ if (!$p['skip_washer_form_callback']) {
+ $washer->add_callback('form', 'rcmail_washtml_callback');
+ }
+@@ -911,6 +930,11 @@ function rcmail_wash_html($html, $p, $cid_replaces = array())
+ $html = $washer->wash($html);
+ $REMOTE_OBJECTS = $washer->extlinks;
+
++ // There was no <body>, but a wrapper element is required
++ if ($p['inline_html'] && !empty($wash_html_body_attrs)) {
++ $html = html::tag('div', $wash_html_body_attrs, $html);
++ }
++
+ return $html;
+ }
+
+@@ -999,6 +1023,7 @@ function rcmail_plain_body($body, $flowed = false, $delsp = false)
+ */
+ function rcmail_washtml_callback($tagname, $attrib, $content, $washtml)
+ {
++ global $wash_html_body_attrs;
+ switch ($tagname) {
+ case 'form':
+ $out = html::div('form', $content);
+@@ -1022,11 +1047,65 @@ function rcmail_washtml_callback($tagname, $attrib, $content, $washtml)
+ $washtml->extlinks = true;
+ }
+ else {
+- $out = html::tag('style', array('type' => 'text/css'), $decoded);
++ $out = $decoded;
+ }
+- break;
+ }
+
++ if (strlen($out)) {
++ $css_prefix = $washtml->get_config('css_prefix');
++ $is_safe = $washtml->get_config('allow_remote');
++ $body_class = $washtml->get_config('body_class') ?: '';
++ $cont_id = $washtml->get_config('container_id') ?: '';
++ $cont_id = trim($cont_id . ($body_class ? " div.{$body_class}" : ''));
++
++ $out = rcube_utils::mod_css_styles($out, $cont_id, $is_safe, $css_prefix);
++
++ $out = html::tag('style', array('type' => 'text/css'), $out);
++ }
++
++ break;
++
++ case 'body':
++ $style = array();
++ $attrs = $wash_html_body_attrs;
++
++ foreach (html::parse_attrib_string($attrib) as $attr_name => $value) {
++ switch (strtolower($attr_name)) {
++ case 'bgcolor':
++ // Get bgcolor, we'll set it as background-color of the message container
++ if (preg_match('/^([a-z0-9#]+)$/i', $value, $m)) {
++ $style['background-color'] = $value;
++ }
++ break;
++ case 'text':
++ // Get text color, we'll set it as font color of the message container
++ if (preg_match('/^([a-z0-9#]+)$/i', $value, $m)) {
++ $style['color'] = $value;
++ }
++ break;
++ case 'background':
++ // Get background, we'll set it as background-image of the message container
++ if (preg_match('/^([^\s]+)$/', $value, $m)) {
++ $style['background-image'] = "url({$value})";
++ }
++ break;
++ default:
++ $attrs[$attr_name] = $value;
++ }
++ }
++
++ if (!empty($style)) {
++ foreach ($style as $idx => $val) {
++ $style[$idx] = $idx . ': ' . $val;
++ }
++
++ $attrs['style'] = ($attrs['style'] ? trim($attrs['style'], ';') . '; ' : '') . implode('; ', $style);
++ }
++
++ $out = html::tag('div', $attrs, $content);
++ $wash_html_body_attrs = array();
++ break;
++
+ default:
+ $out = '';
+ }
+@@ -1083,112 +1162,6 @@ function rcmail_part_image_type($part)
+ }
+ }
+
+-/**
+- * Modify a HTML message that it can be displayed inside a HTML page
+- */
+-function rcmail_html4inline($body, &$args)
+-{
+- $last_pos = 0;
+- $cont_id = $args['container_id'] . ($args['body_class'] ? ' div.' . $args['body_class'] : '');
+-
+- // find STYLE tags
+- while (($pos = stripos($body, '<style', $last_pos)) !== false && ($pos2 = stripos($body, '</style>', $pos+1))) {
+- $pos = strpos($body, '>', $pos) + 1;
+- $len = $pos2 - $pos;
+-
+- // replace all css definitions with #container [def]
+- $styles = substr($body, $pos, $len);
+- $styles = rcube_utils::mod_css_styles($styles, $cont_id, $args['safe'], $args['css_prefix']);
+-
+- $body = substr_replace($body, $styles, $pos, $len);
+- $last_pos = $pos2 + strlen($styles) - $len;
+- }
+-
+- $replace = array(
+- // add comments around html and other tags
+- '/(<!DOCTYPE[^>]*>)/i' => '<!--\\1-->',
+- '/(<\?xml[^>]*>)/i' => '<!--\\1-->',
+- '/(<\/?html[^>]*>)/i' => '<!--\\1-->',
+- '/(<\/?head[^>]*>)/i' => '<!--\\1-->',
+- '/(<title[^>]*>.*<\/title>)/Ui' => '<!--\\1-->',
+- '/(<\/?meta[^>]*>)/i' => '<!--\\1-->',
+- // quote <? of php and xml files that are specified as text/html
+- '/<\?/' => '<?',
+- '/\?>/' => '?>',
+- );
+-
+- $regexp = '/<body([^>]*)/';
+-
+- // Handle body attributes that doesn't play nicely with div elements
+- if (preg_match($regexp, $body, $m)) {
+- $style = array();
+- $attrs = $m[0];
+-
+- // Get bgcolor, we'll set it as background-color of the message container
+- if ($m[1] && preg_match('/bgcolor=["\']*([a-z0-9#]+)["\']*/i', $attrs, $mb)) {
+- $style['background-color'] = $mb[1];
+- $attrs = preg_replace('/\s?bgcolor=["\']*[a-z0-9#]+["\']*/i', '', $attrs);
+- }
+-
+- // Get text color, we'll set it as font color of the message container
+- if ($m[1] && preg_match('/text=["\']*([a-z0-9#]+)["\']*/i', $attrs, $mb)) {
+- $style['color'] = $mb[1];
+- $attrs = preg_replace('/\s?text=["\']*[a-z0-9#]+["\']*/i', '', $attrs);
+- }
+-
+- // Get background, we'll set it as background-image of the message container
+- if ($m[1] && preg_match('/background=["\']*([^"\'>\s]+)["\']*/', $attrs, $mb)) {
+- $style['background-image'] = 'url('.$mb[1].')';
+- $attrs = preg_replace('/\s?background=["\']*([^"\'>\s]+)["\']*/', '', $attrs);
+- }
+-
+- if (!empty($style)) {
+- $body = preg_replace($regexp, rtrim($attrs), $body, 1);
+- }
+-
+- // handle body styles related to background image
+- if ($style['background-image']) {
+- // get body style
+- if (preg_match('/#'.preg_quote($cont_id, '/').'\s+\{([^}]+)}/i', $body, $m)) {
+- // get background related style
+- $regexp = '/(background-position|background-repeat)\s*:\s*([^;]+);/i';
+- if (preg_match_all($regexp, $m[1], $matches, PREG_SET_ORDER)) {
+- foreach ($matches as $m) {
+- $style[$m[1]] = $m[2];
+- }
+- }
+- }
+- }
+-
+- if (!empty($style)) {
+- foreach ($style as $idx => $val) {
+- $style[$idx] = $idx . ': ' . $val;
+- }
+-
+- $args['container_attrib']['style'] = implode('; ', $style);
+- }
+-
+- // replace <body> with <div>
+- if (!empty($args['body_class'])) {
+- $replace['/<body([^>]*)>/i'] = '<div class="' . $args['body_class'] . '"\\1>';
+- }
+- else {
+- $replace['/<body/i'] = '<div';
+- }
+-
+- $replace['/<\/body>/i'] = '</div>';
+- }
+- // make sure there's 'rcmBody' div, we need it for proper css modification
+- // its name is hardcoded in rcmail_message_body() also
+- else if (!empty($args['body_class'])) {
+- $body = '<div class="' . $args['body_class'] . '">' . $body . '</div>';
+- }
+-
+- // Clean up, and replace <body> with <div>
+- $body = preg_replace(array_keys($replace), array_values($replace), $body);
+-
+- return $body;
+-}
+
+ /**
+ * Parse link (a, link, area) attributes and set correct target
+diff --git a/program/steps/mail/show.inc b/program/steps/mail/show.inc
+index f7c6819..6ddc4bd 100644
+--- a/program/steps/mail/show.inc
++++ b/program/steps/mail/show.inc
+@@ -687,10 +687,6 @@ function rcmail_message_body($attrib)
+ $OUTPUT->set_env('is_pgp_content', '#' . $container_id);
+ }
+
+- if ($part->ctype_secondary == 'html') {
+- $body = rcmail_html4inline($body, $body_args);
+- }
+-
+ $out .= html::div($body_args['container_attrib'], $plugin['prefix'] . $body);
+ }
+ }
+diff --git a/program/steps/settings/save_identity.inc b/program/steps/settings/save_identity.inc
+index 052941e..ab44b77 100644
+--- a/program/steps/settings/save_identity.inc
++++ b/program/steps/settings/save_identity.inc
+@@ -244,6 +244,8 @@ function rcmail_wash_html($html)
+ 'charset' => RCUBE_CHARSET,
+ 'html_elements' => array('body', 'link'),
+ 'html_attribs' => array('rel', 'type'),
++ 'ignore_elements' => array('body'),
++ 'add_comments' => false,
+ );
+
+ // initialize HTML washer
+@@ -255,10 +257,5 @@ function rcmail_wash_html($html)
+ // Remove non-UTF8 characters (#1487813)
+ $html = rcube_charset::clean($html);
+
+- $html = $washer->wash($html);
+-
+- // remove unwanted comments and tags (produced by washtml)
+- $html = preg_replace(array('/<!--[^>]+-->/', '/<\/?body>/'), '', $html);
+-
+- return $html;
++ return $washer->wash($html);
+ }
+diff --git a/tests/MailFunc.php b/tests/MailFunc.php
+index a72b5c9..9c0ae05 100644
+--- a/tests/MailFunc.php
++++ b/tests/MailFunc.php
+@@ -41,11 +41,11 @@ class MailFunc extends \PHPUnit\Framework\TestCase
+ $part = $this->get_html_part('src/htmlbody.txt');
+ $part->replaces = array('ex1.jpg' => 'part_1.2.jpg', 'ex2.jpg' => 'part_1.2.jpg');
+
+- $params = array('container_id' => 'foo');
++ $params = array('container_id' => 'foo', 'safe' => false);
+
+ // render HTML in normal mode
+- $html = rcmail_html4inline(rcmail_print_body($part->body, $part, array('safe' => false)), $params);
+-
++ $html = rcmail_print_body($part->body, $part, $params);
++
+ $this->assertMatchesRegularExpression('/src="'.$part->replaces['ex1.jpg'].'"/', $html, "Replace reference to inline image");
+ $this->assertMatchesRegularExpression('#background="program/resources/blocked.gif"#', $html, "Replace external background image");
+ $this->assertDoesNotMatchRegularExpression('/ex3.jpg/', $html, "No references to external images");
+@@ -57,7 +57,8 @@ class MailFunc extends \PHPUnit\Framework\TestCase
+ $this->assertTrue($GLOBALS['REMOTE_OBJECTS'], "Remote object detected");
+
+ // render HTML in safe mode
+- $html2 = rcmail_html4inline(rcmail_print_body($part->body, $part, array('safe' => true)), $params);
++ $params['safe'] = true;
++ $html2 = rcmail_print_body($part->body, $part, $params);
+
+ $this->assertMatchesRegularExpression('/<style [^>]+>/', $html2, "Allow styles in safe mode");
+ $this->assertMatchesRegularExpression('#src="http://evilsite.net/mailings/ex3.jpg"#', $html2, "Allow external images in HTML (safe mode)");
+@@ -72,15 +73,12 @@ class MailFunc extends \PHPUnit\Framework\TestCase
+ function test_html_xss()
+ {
+ $part = $this->get_html_part('src/htmlxss.txt');
+- $washed = rcmail_print_body($part->body, $part, array('safe' => true));
+-
+- $this->assertDoesNotMatchRegularExpression('/src="skins/', $washed, "Remove local references");
+- $this->assertDoesNotMatchRegularExpression('/\son[a-z]+/', $washed, "Remove on* attributes");
+- $this->assertStringNotContainsString('onload', $washed, "Handle invalid style");
+-
+- $params = array('container_id' => 'foo');
+- $html = rcmail_html4inline($washed, $params);
++ $params = array('container_id' => 'foo', 'safe' => true);
++ $html = rcmail_print_body($part->body, $part, $params);
+
++ $this->assertDoesNotMatchRegularExpression('/src="skins/', $html, 'Remove local references');
++ $this->assertDoesNotMatchRegularExpression('/\son[a-z]+/', $html, 'Remove on* attributes');
++ $this->assertStringNotContainsString('onload', $html, 'Handle invalid style');
+ $this->assertDoesNotMatchRegularExpression('/onclick="return rcmail.command(\'compose\',\'xss at somehost.net\',this)"/', $html, "Clean mailto links");
+ $this->assertDoesNotMatchRegularExpression('/alert/', $html, "Remove alerts");
+ }
+@@ -93,7 +91,7 @@ class MailFunc extends \PHPUnit\Framework\TestCase
+ {
+ $part = $this->get_html_part('src/BID-26800.txt');
+ $params = array('container_id' => 'dabody', 'safe' => true);
+- $washed = rcmail_html4inline(rcmail_print_body($part->body, $part, array('safe' => true)), $params);
++ $washed = rcmail_print_body($part->body, $part, $params);
+
+ $this->assertDoesNotMatchRegularExpression('/alert|expression|javascript|xss/', $washed, "Remove evil style blocks");
+ $this->assertDoesNotMatchRegularExpression('/font-style:italic/', $washed, "Allow valid styles");
+@@ -113,20 +111,40 @@ class MailFunc extends \PHPUnit\Framework\TestCase
+ $this->assertDoesNotMatchRegularExpression('/vbscript:/', $washed, "Remove vbscript: links");
+ }
+
++ /**
++ * Test that HTML sanitization does not change attribute (evil) values
++ */
++ public function test_html_body_attributes()
++ {
++ $part = $this->get_html_part();
++ $part->body = '<body title="bgcolor=foo" name="bar style=animation-name:progress-bar-stripes onanimationstart=alert(origin) foo=bar">Foo</body>';
++
++ $params = array('safe' => true, 'add_comments' => false);
++ $washed = rcmail_print_body($part->body, $part, $params);
++
++ $this->assertSame(str_replace('body', 'div', $part->body) . "\n", $washed);
++
++ $params['inline_html'] = false;
++ $washed = rcmail_print_body($part->body, $part, $params);
++
++ $this->assertSame('<html><head></head>' . $part->body . '</html>', $washed);
++ }
++
+ /**
+ * Test handling of body style attributes
+ */
+- function test_html4inline_body_style()
++ public function test_wash_html_body_style()
+ {
+- $html = '<body background="test" bgcolor="#fff" style="font-size:11px" text="#000"><p>test</p></body>';
+- $params = array('container_id' => 'foo');
+- $html = rcmail_html4inline($html, $params);
+-
+- $this->assertMatchesRegularExpression('/<div style="font-size:11px">/', $html, "Body attributes");
+- $this->assertArrayHasKey('container_attrib', $params, "'container_attrib' param set");
+- $this->assertMatchesRegularExpression('/background-color: #fff;/', $params['container_attrib']['style'], "Body style (bgcolor)");
+- $this->assertMatchesRegularExpression('/background-image: url\(test\)/', $params['container_attrib']['style'], "Body style (background)");
+- $this->assertMatchesRegularExpression('/color: #000/', $params['container_attrib']['style'], "Body style (text)");
++ $html = '<body background="http://test.com/image" bgcolor="#fff" style="font-size: 11px" text="#000"><p>test</p></body>';
++ $params = array('container_id' => 'foo', 'add_comments' => false, 'safe' => false);
++ $washed = rcmail_wash_html($html, $params, array());
++
++ $this->assertSame('<div id="foo" style="font-size: 11px; background-image: url(program/resources/blocked.gif); background-color: #fff; color: #000"><p>test</p></div>' . "\n", $washed);
++
++ $params['safe'] = true;
++ $washed = rcmail_wash_html($html, $params, array());
++
++ $this->assertSame('<div id="foo" style="font-size: 11px; background-image: url(http://test.com/image); background-color: #fff; color: #000"><p>test</p></div>' . "\n", $washed);
+ }
+
+ /**
+@@ -149,6 +167,7 @@ class MailFunc extends \PHPUnit\Framework\TestCase
+ {
+ $meta = '<meta charset="'.RCUBE_CHARSET.'" />';
+ $args = array(
++ 'inline_html' => false,
+ 'html_elements' => array('html', 'body', 'meta', 'head'),
+ 'html_attribs' => array('charset'),
+ );
+@@ -201,10 +220,10 @@ class MailFunc extends \PHPUnit\Framework\TestCase
+ function test_mailto()
+ {
+ $part = $this->get_html_part('src/mailto.txt');
+- $params = array('container_id' => 'foo');
++ $params = array('container_id' => 'foo', 'safe' => false);
+
+ // render HTML in normal mode
+- $html = rcmail_html4inline(rcmail_print_body($part->body, $part, array('safe' => false)), $params);
++ $html = rcmail_print_body($part->body, $part, $params);
+
+ $mailto = '<a href="mailto:me at me.com"'
+ .' onclick="return rcmail.command(\'compose\',\'me at me.com?subject=this is the subject&body=this is the body\',this)" rel="noreferrer">e-mail</a>';
diff -Nru roundcube-1.4.15+dfsg.1/debian/patches/CVE-2024-42010.patch roundcube-1.4.15+dfsg.1/debian/patches/CVE-2024-42010.patch
--- roundcube-1.4.15+dfsg.1/debian/patches/CVE-2024-42010.patch 1970-01-01 01:00:00.000000000 +0100
+++ roundcube-1.4.15+dfsg.1/debian/patches/CVE-2024-42010.patch 2024-08-08 23:48:56.000000000 +0200
@@ -0,0 +1,611 @@
+From: Aleksander Machniak <alec at alec.pl>
+Date: Sat, 3 Aug 2024 09:24:19 +0200
+Subject: Fix information leak (access to remote content) via insufficient CSS
+ filtering
+
+Credits to Oskar Zeino-Mahmalat (Sonar) https://www.sonarsource.com
+
+Origin: https://github.com/roundcube/roundcubemail/commit/53da61f7fc8a5d8414c36c45e44901d2a568319a
+Origin: https://github.com/roundcube/roundcubemail/commit/9f19b931e3b89c2fa577e2bf719f7db84492eb66
+Bug-Debian: https://security-tracker.debian.org/tracker/CVE-2024-42010
+Bug-Debian: https://bugs.debian.org/1077969
+---
+ program/lib/Roundcube/rcube_utils.php | 236 ++++++++++++++++++++++++++------
+ program/lib/Roundcube/rcube_washtml.php | 95 +------------
+ tests/Framework/Utils.php | 146 +++++++++++++++++---
+ tests/Framework/Washtml.php | 2 +-
+ 4 files changed, 327 insertions(+), 152 deletions(-)
+
+diff --git a/program/lib/Roundcube/rcube_utils.php b/program/lib/Roundcube/rcube_utils.php
+index d427352..68f8684 100644
+--- a/program/lib/Roundcube/rcube_utils.php
++++ b/program/lib/Roundcube/rcube_utils.php
+@@ -373,19 +373,40 @@ class rcube_utils
+ */
+ public static function mod_css_styles($source, $container_id, $allow_remote = false, $prefix = '')
+ {
+- $last_pos = 0;
+- $replacements = new rcube_string_replacer;
+-
+- // ignore the whole block if evil styles are detected
+ $source = self::xss_entity_decode($source);
+- $stripped = preg_replace('/[^a-z\(:;]/i', '', $source);
+- $evilexpr = 'expression|behavior|javascript:|import[^a]' . (!$allow_remote ? '|url\((?!data:image)' : '');
+
+- if (preg_match("/$evilexpr/i", $stripped)) {
++ // No @import allowed
++ // TODO: We should just remove it, not invalidate the whole content
++ if (stripos($source, '@import') !== false) {
++ return '/* evil! */';
++ }
++
++ // Incomplete style expression
++ if (strpos($source, '{') === false) {
++ return '/* invalid! */';
++ }
++
++ // To prevent from a double-escaping tricks we consider a script with
++ // any escape sequences (after de-escaping them above) an evil script.
++ // This probably catches many valid scripts, but we\'re on the safe side.
++ if (preg_match('/\\\[0-9a-fA-F]{2}/', $source)) {
+ return '/* evil! */';
+ }
+
+- $strict_url_regexp = '!url\s*\(\s*["\']?(https?:)//[a-z0-9/._+-]+["\']?\s*\)!Uims';
++ // remove html comments
++ $source = preg_replace('/(^\s*<\!--)|(-->\s*$)/m', '', $source);
++
++ $url_callback = static function ($url) use ($allow_remote) {
++ if (strpos($url, 'data:image') === 0) {
++ return $url;
++ }
++ if ($allow_remote && preg_match('|^https?://[a-z0-9/._+-]+$|i', $url)) {
++ return $url;
++ }
++ };
++
++ $last_pos = 0;
++ $replacements = new rcube_string_replacer();
+
+ // cut out all contents between { and }
+ while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos))) {
+@@ -394,45 +415,14 @@ class rcube_utils
+ $pos = $nested;
+ $length = $pos2 - $pos - 1;
+ $styles = substr($source, $pos+1, $length);
++ $styles = self::sanitize_css_block($styles, $url_callback);
+
+- // Convert position:fixed to position:absolute (#5264)
+- $styles = preg_replace('/position[^a-z]*:[\s\r\n]*fixed/i', 'position: absolute', $styles);
+-
+- // Remove 'page' attributes (#7604)
+- $styles = preg_replace('/((^|[\n\s;])page:)[^;]+;*/im', '\\1 unset;', $styles);
+-
+- // check every line of a style block...
+- if ($allow_remote) {
+- $a_styles = preg_split('/;[\r\n]*/', $styles, -1, PREG_SPLIT_NO_EMPTY);
+-
+- for ($i=0, $len=count($a_styles); $i < $len; $i++) {
+- $line = $a_styles[$i];
+- $stripped = preg_replace('/[^a-z\(:;]/i', '', $line);
+-
+- // allow data:image uri, join with continuation
+- if (stripos($stripped, 'url(data:image')) {
+- $a_styles[$i] .= ';' . $a_styles[$i+1];
+- unset($a_styles[$i+1]);
+- }
+- // allow strict url() values only
+- else if (stripos($stripped, 'url(') && !preg_match($strict_url_regexp, $line)) {
+- $a_styles = array('/* evil! */');
+- break;
+- }
+- }
+-
+- $styles = implode(";\n", $a_styles);
+- }
+-
+- $key = $replacements->add($styles);
++ $key = $replacements->add(strlen($styles) ? " {$styles} " : '');
+ $repl = $replacements->get_replacement($key);
+ $source = substr_replace($source, $repl, $pos+1, $length);
+ $last_pos = $pos2 - ($length - strlen($repl));
+ }
+
+- // remove html comments
+- $source = preg_replace('/(^\s*<\!--)|(-->\s*$)/m', '', $source);
+-
+ // add #container to each tag selector and prefix to id/class identifiers
+ if ($container_id || $prefix) {
+ // Exclude rcube_string_replacer pattern matches, this is needed
+@@ -475,6 +465,170 @@ class rcube_utils
+ return $source;
+ }
+
++ /**
++ * Parse and sanitize single CSS block
++ *
++ * @param string $styles CSS styles block
++ * @param ?callable $url_callback URL validator callback
++ *
++ * @return string
++ */
++ public static function sanitize_css_block($styles, $url_callback = null)
++ {
++ $output = array();
++
++ // check every css rule in the style block...
++ foreach (self::parse_css_block($styles) as $rule) {
++ $property = $rule[0];
++ $value = $rule[1];
++
++ if ($property == 'page') {
++ // Remove 'page' attributes (#7604)
++ continue;
++ } elseif ($property == 'position' && strcasecmp($value, 'fixed') === 0) {
++ // Convert position:fixed to position:absolute (#5264)
++ $value = 'absolute';
++ } elseif (preg_match('/expression|image-set/i', $value)) {
++ continue;
++ } 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 . ')';
++ }
++ }
++ } else {
++ // whitelist ?
++ $value .= ' ' . $val;
++
++ // #1488535: Fix size units, so width:800 would be changed to width:800px
++ if ($val
++ && preg_match('/^(left|right|top|bottom|width|height)/i', $property)
++ && preg_match('/^[0-9]+$/', $val)
++ ) {
++ $value .= 'px';
++ }
++ }
++ }
++ }
++
++ if (strlen($value)) {
++ $output[] = $property . ': ' . trim($value);
++ }
++ }
++
++ return count($output) > 0 ? implode('; ', $output) . ';' : '';
++ }
++
++ /**
++ * Explode css style. Property names will be lower-cased and trimmed.
++ * Values will be trimmed. Invalid entries will be skipped.
++ *
++ * @param string $style CSS style
++ *
++ * @return array List of CSS rule pairs, e.g. [['color', 'red'], ['top', '0']]
++ */
++ public static function parse_css_block($style)
++ {
++ $pos = 0;
++
++ // first remove comments
++ while (($pos = strpos($style, '/*', $pos)) !== false) {
++ $end = strpos($style, '*/', $pos+2);
++
++ if ($end === false) {
++ $style = substr($style, 0, $pos);
++ }
++ else {
++ $style = substr_replace($style, '', $pos, $end - $pos + 2);
++ }
++ }
++
++ // Replace new lines with spaces
++ $style = preg_replace('/[\r\n]+/', ' ', $style);
++
++ $style = trim($style);
++ $length = strlen($style);
++ $result = array();
++ $pos = 0;
++
++ while ($pos < $length && ($colon_pos = strpos($style, ':', $pos))) {
++ // Property name
++ $name = strtolower(trim(substr($style, $pos, $colon_pos - $pos)));
++
++ // get the property value
++ $q = $s = false;
++ for ($i = $colon_pos + 1; $i < $length; $i++) {
++ if (($style[$i] == "\"" || $style[$i] == "'") && ($i == 0 || $style[$i-1] != "\\")) {
++ if ($q == $style[$i]) {
++ $q = false;
++ }
++ else if ($q === false) {
++ $q = $style[$i];
++ }
++ }
++ else if ($style[$i] == "(" && !$q && ($i == 0 || $style[$i-1] != "\\")) {
++ $q = "(";
++ }
++ else if ($style[$i] == ")" && $q == "(" && $style[$i-1] != "\\") {
++ $q = false;
++ }
++
++ if ($q === false && (($s = $style[$i] == ';') || $i == $length - 1)) {
++ break;
++ }
++ }
++
++ $value_length = $i - $colon_pos - ($s ? 1 : 0);
++ $value = trim(substr($style, $colon_pos + 1, $value_length));
++
++ if (strlen($name) && !preg_match('/[^a-z-]/', $name) && strlen($value) && $value !== ';') {
++ $result[] = array($name, $value);
++ }
++
++ $pos = $i + 1;
++ }
++
++ return $result;
++ }
++
++ /**
++ * Explode css style value
++ *
++ * @param string $style CSS style
++ *
++ * @return array List of CSS values
++ */
++ public static function explode_css_property_block($style)
++ {
++ $style = preg_replace('/\s+/', ' ', $style);
++ $result = array();
++ $strlen = strlen($style);
++ $q = false;
++
++ // explode value
++ for ($p = $i = 0; $i < $strlen; $i++) {
++ if (($style[$i] == '"' || $style[$i] == "'") && ($i == 0 || $style[$i - 1] != '\\')) {
++ if ($q == $style[$i]) {
++ $q = false;
++ } elseif (!$q) {
++ $q = $style[$i];
++ }
++ }
++
++ if (!$q && $style[$i] == ' ' && ($i == 0 || !preg_match('/[,\(]/', $style[$i - 1]))) {
++ $result[] = substr($style, $p, $i - $p);
++ $p = $i + 1;
++ }
++ }
++
++ $result[] = (string) substr($style, $p);
++
++ return $result;
++ }
++
+ /**
+ * Generate CSS classes from mimetype and filename extension
+ *
+diff --git a/program/lib/Roundcube/rcube_washtml.php b/program/lib/Roundcube/rcube_washtml.php
+index 8a3a5ab..43c9274 100644
+--- a/program/lib/Roundcube/rcube_washtml.php
++++ b/program/lib/Roundcube/rcube_washtml.php
+@@ -249,51 +249,19 @@ class rcube_washtml
+ {
+ $result = array();
+
+- // Remove unwanted white-space characters so regular expressions below work better
+- $style = preg_replace('/[\n\r\s\t]+/', ' ', $style);
+-
+ // Decode insecure character sequences
+ $style = rcube_utils::xss_entity_decode($style);
+
+- foreach (explode(';', $style) as $declaration) {
+- if (preg_match('/^\s*([a-z\\\-]+)\s*:\s*(.*)\s*$/i', $declaration, $match)) {
+- $cssid = $match[1];
+- $str = $match[2];
+- $value = '';
+-
+- foreach ($this->explode_style($str) as $val) {
+- if (preg_match('/^url\(/i', $val)) {
+- if (preg_match('/^url\(\s*[\'"]?([^\'"\)]*)[\'"]?\s*\)/iu', $val, $match)) {
+- if ($url = $this->wash_uri($match[1])) {
+- $value .= ' url(' . htmlspecialchars($url, ENT_QUOTES, $this->config['charset']) . ')';
+- }
+- }
+- }
+- else if (!preg_match('/^(behavior|expression)/i', $val)) {
+- // Set position:fixed to position:absolute for security (#5264)
+- if (!strcasecmp($cssid, 'position') && !strcasecmp($val, 'fixed')) {
+- $val = 'absolute';
+- }
+-
+- // whitelist ?
+- $value .= ' ' . $val;
+-
+- // #1488535: Fix size units, so width:800 would be changed to width:800px
+- if (preg_match('/^(left|right|top|bottom|width|height)/i', $cssid)
+- && preg_match('/^[0-9]+$/', $val)
+- ) {
+- $value .= 'px';
+- }
+- }
+- }
++ // Remove unwanted white-space characters
++ $style = preg_replace('/[\n\r\t]+/', ' ', $style);
+
+- if (isset($value[0])) {
+- $result[] = $cssid . ':' . $value;
+- }
++ $uri_callback = function ($uri) {
++ if ($uri = $this->wash_uri($uri)) {
++ return htmlspecialchars($uri, \ENT_QUOTES, $this->config['charset']);
+ }
+- }
++ };
+
+- return implode('; ', $result);
++ return rtrim(rcube_utils::sanitize_css_block($style, $uri_callback), ';');
+ }
+
+ /**
+@@ -970,53 +938,4 @@ class rcube_washtml
+
+ return $html;
+ }
+-
+- /**
+- * Explode css style value
+- *
+- * @param string $style CSS style
+- *
+- * @return array List of CSS rules
+- */
+- protected function explode_style($style)
+- {
+- $pos = 0;
+-
+- // first remove comments
+- while (($pos = strpos($style, '/*', $pos)) !== false) {
+- $end = strpos($style, '*/', $pos+2);
+-
+- if ($end === false) {
+- $style = substr($style, 0, $pos);
+- }
+- else {
+- $style = substr_replace($style, '', $pos, $end - $pos + 2);
+- }
+- }
+-
+- $style = trim($style);
+- $strlen = strlen($style);
+- $result = array();
+-
+- // explode value
+- for ($p=$i=0; $i < $strlen; $i++) {
+- if (($style[$i] == "\"" || $style[$i] == "'") && $style[$i-1] != "\\") {
+- if ($q == $style[$i]) {
+- $q = false;
+- }
+- else if (!$q) {
+- $q = $style[$i];
+- }
+- }
+-
+- if (!$q && $style[$i] == ' ' && !preg_match('/[,\(]/', $style[$i-1])) {
+- $result[] = substr($style, $p, $i - $p);
+- $p = $i + 1;
+- }
+- }
+-
+- $result[] = (string) substr($style, $p);
+-
+- return $result;
+- }
+ }
+diff --git a/tests/Framework/Utils.php b/tests/Framework/Utils.php
+index b7922e3..4bd9e52 100644
+--- a/tests/Framework/Utils.php
++++ b/tests/Framework/Utils.php
+@@ -182,8 +182,8 @@ class Framework_Utils extends \PHPUnit\Framework\TestCase
+ $mod = rcube_utils::mod_css_styles($css, 'rcmbody');
+
+ $this->assertStringContainsString('#rcmbody table[class=w600]', $mod, 'Replace styles nested in @media block');
+- $this->assertStringContainsString('#rcmbody {width:600px', $mod, 'Replace body selector nested in @media block');
+- $this->assertStringContainsString('#rcmbody {min-width:474px', $mod, 'Replace body selector nested in @media block (#5811)');
++ $this->assertStringContainsString('#rcmbody { width: 600px', $mod, 'Replace body selector nested in @media block');
++ $this->assertStringContainsString('#rcmbody { min-width: 474px', $mod, 'Replace body selector nested in @media block (#5811)');
+ }
+
+ /**
+@@ -191,26 +191,48 @@ class Framework_Utils extends \PHPUnit\Framework\TestCase
+ */
+ function test_mod_css_styles_xss()
+ {
+- $mod = rcube_utils::mod_css_styles("body.main2cols { background-image: url('../images/leftcol.png'); }", 'rcmbody');
+- $this->assertEquals("/* evil! */", $mod, "No url() values allowed");
++ $mod = \rcube_utils::mod_css_styles('font-size: 1em;', 'rcmbody');
++ $this->assertSame('/* invalid! */', $mod);
+
+ $mod = rcube_utils::mod_css_styles("@import url('http://localhost/somestuff/css/master.css');", 'rcmbody');
+- $this->assertEquals("/* evil! */", $mod, "No import statements");
++ $this->assertSame('/* evil! */', $mod);
+
+- $mod = rcube_utils::mod_css_styles("left:expression(document.body.offsetWidth-20)", 'rcmbody');
+- $this->assertEquals("/* evil! */", $mod, "No expression properties");
++ $mod = \rcube_utils::mod_css_styles("@\\69mport url('http://localhost/somestuff/css/master.css');", 'rcmbody');
++ $this->assertSame('/* evil! */', $mod);
+
+- $mod = rcube_utils::mod_css_styles("left:exp/* */ression( alert('xss3') )", 'rcmbody');
+- $this->assertEquals("/* evil! */", $mod, "Don't allow encoding quirks");
++ $mod = \rcube_utils::mod_css_styles("@\\5C 69mport url('http://localhost/somestuff/css/master.css'); a { color: red; }", '');
++ $this->assertSame('/* evil! */', $mod);
+
+- $mod = rcube_utils::mod_css_styles("background:\\0075\\0072\\00006c( javascript:alert('xss') )", 'rcmbody');
+- $this->assertEquals("/* evil! */", $mod, "Don't allow encoding quirks (2)");
++ $mod = \rcube_utils::mod_css_styles("body.main2cols { background-image: url('../images/leftcol.png'); }", 'rcmbody');
++ $this->assertSame('#rcmbody.main2cols {}', $mod);
+
+- $mod = rcube_utils::mod_css_styles("background: \\75 \\72 \\6C ('/images/img.png')", 'rcmbody');
+- $this->assertEquals("/* evil! */", $mod, "Don't allow encoding quirks (3)");
++ $mod = \rcube_utils::mod_css_styles('p { left:expression(document.body.offsetWidth-20); }', 'rcmbody');
++ $this->assertSame('#rcmbody p {}', $mod);
+
+- $mod = rcube_utils::mod_css_styles("background: u\\r\\l('/images/img.png')", 'rcmbody');
+- $this->assertEquals("/* evil! */", $mod, "Don't allow encoding quirks (4)");
++ $mod = \rcube_utils::mod_css_styles('p { left:exp/* */ression( alert('xss3') ); }', 'rcmbody');
++ $this->assertSame('#rcmbody p {}', $mod);
++
++ $mod = \rcube_utils::mod_css_styles("p { background:\\0075\\0072\\00006c('//evil.com/test'); }", 'rcmbody');
++ $this->assertSame('#rcmbody p {}', $mod);
++
++ $mod = \rcube_utils::mod_css_styles("p { background: \\75 \\72 \\6C ('/images/img.png'); }", 'rcmbody');
++ $this->assertSame('#rcmbody p {}', $mod);
++
++ $mod = \rcube_utils::mod_css_styles("p { background: u\\r\\l('/images/img2.png'); }", 'rcmbody');
++ $this->assertSame('#rcmbody p {}', $mod);
++
++ $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: url('//data:image&leak'); }", 'rcmbody');
++ $this->assertSame('#rcmbody p {}', $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);
++
++ $mod = \rcube_utils::mod_css_styles('p { background: none !important; }', 'rcmbody');
++ $this->assertSame('#rcmbody p { background: none !important; }', $mod);
+
+ // position: fixed (#5264)
+ $mod = rcube_utils::mod_css_styles(".test { position: fixed; }", 'rcmbody');
+@@ -241,19 +263,20 @@ class Framework_Utils extends \PHPUnit\Framework\TestCase
+ // XSS issue, HTML in 'content' property
+ $style = "body { content: '</style><img src onerror=\"alert(\'hello\');\">'; color: red; }";
+ $mod = rcube_utils::mod_css_styles($style, 'rcmbody', true);
+- $this->assertSame("#rcmbody { content: '';\n color: red;\n }", $mod);
++ $this->assertSame("#rcmbody { content: ''; color: red; }", $mod);
+
+ $style = "body { content: '< page: ;/style>< page: ;img src onerror=\"alert(\'hello\');\">'; color: red; }";
+ $mod = rcube_utils::mod_css_styles($style, 'rcmbody', true);
+- $this->assertSame(
+- "#rcmbody { content: '< page: unset;/style>< page: unset;img src onerror=\"alert('hello');\">'; color: red; }",
+- str_replace("\n", '', $mod)
+- );
++ $this->assertSame("#rcmbody { content: '< page: ;/style>< page: ;img src onerror=\"alert('hello');\">'; color: red; }", $mod);
+
+ // Removing page: property
+- $style = "body { page: test; color: red; }";
++ $style = "body { page: test; color: red }";
+ $mod = rcube_utils::mod_css_styles($style, 'rcmbody', true);
+- $this->assertSame("#rcmbody { page: unset;\n color: red;\n }", $mod);
++ $this->assertSame("#rcmbody { color: red; }", $mod);
++
++ $style = "body { background:url(alert('URL!')); }";
++ $mod = rcube_utils::mod_css_styles($style, 'rcmbody', true);
++ $this->assertSame("#rcmbody {}", $mod);
+ }
+
+ /**
+@@ -308,6 +331,85 @@ class Framework_Utils extends \PHPUnit\Framework\TestCase
+ $this->assertStringContainsString('#foo', $mod, "Strip HTML comments from content, but not the content");
+ }
+
++ /**
++ * Test-Cases for parse_css_block() test
++ */
++ function data_parse_css_block()
++ {
++ return [
++ [
++ 'test:test2',
++ [['test', 'test2']],
++ ],
++ [
++ 'Test :teSt2 ;',
++ [['test', 'teSt2']],
++ ],
++ [
++ 'test : test2 test3;',
++ [['test', 'test2 test3']],
++ ],
++ [
++ '/* : */ test : val /* ; */ ;',
++ [['test', 'val']],
++ ],
++ [
++ '/* test : val */ ;',
++ [],
++ ],
++ [
++ 'test :"test1\\"test2" ;',
++ [['test', '"test1\\"test2"']],
++ ],
++ [
++ "test : 'test5 \\'test6';",
++ [['test', "'test5 \\'test6'"]],
++ ],
++ [
++ "test: test8\ntest6;",
++ [['test', 'test8 test6']],
++ ],
++ [
++ "PRop: val1; prop-two: 'val2: '",
++ [['prop', 'val1'], ['prop-two', "'val2: '"]],
++ ],
++ [
++ "prop: val1; prop-two :",
++ [['prop', 'val1']],
++ ],
++ [
++ "prop: val1; prop-two :;",
++ [['prop', 'val1']],
++ ],
++ [
++ "background: url(data:image/png;base64,test)",
++ [['background', 'url(data:image/png;base64,test)']],
++ ],
++ [
++ "background:url('data:image/png;base64,test')",
++ [['background', "url('data:image/png;base64,test')"]],
++ ],
++ [
++ "background: url(\"data:image/png;base64,test\")",
++ [['background', 'url("data:image/png;base64,test")']],
++ ],
++ [
++ 'font-family:"新細明體","serif";color:red',
++ [['font-family', '"新細明體","serif"'], ['color', 'red']]
++ ],
++ ];
++ }
++
++ /**
++ * Test parse_css_block()
++ *
++ * @dataProvider data_parse_css_block
++ */
++ function test_explode_style($input, $output)
++ {
++ $this->assertSame($output, rcube_utils::parse_css_block($input));
++ }
++
+ /**
+ * Check rcube_utils::explode_quoted_string()
+ */
+diff --git a/tests/Framework/Washtml.php b/tests/Framework/Washtml.php
+index 6848eb2..afddedf 100644
+--- a/tests/Framework/Washtml.php
++++ b/tests/Framework/Washtml.php
+@@ -252,7 +252,7 @@ class Framework_Washtml extends \PHPUnit\Framework\TestCase
+ $washer = new rcube_washtml;
+ $washed = $washer->wash($html);
+
+- $this->assertTrue(strpos($washed, $expected) !== false, "White-space and new-line characters handling");
++ $this->assertSame($this->cleanupResult($washed), $expected, 'White-space and new-line characters handling');
+ }
+
+ /**
diff -Nru roundcube-1.4.15+dfsg.1/debian/patches/Fix-infinite-loop-when-parsing-malformed-Sieve-script.patch roundcube-1.4.15+dfsg.1/debian/patches/Fix-infinite-loop-when-parsing-malformed-Sieve-script.patch
--- roundcube-1.4.15+dfsg.1/debian/patches/Fix-infinite-loop-when-parsing-malformed-Sieve-script.patch 1970-01-01 01:00:00.000000000 +0100
+++ roundcube-1.4.15+dfsg.1/debian/patches/Fix-infinite-loop-when-parsing-malformed-Sieve-script.patch 2024-08-08 23:48:56.000000000 +0200
@@ -0,0 +1,52 @@
+From: Aleksander Machniak <alec at alec.pl>
+Date: Wed, 31 Jul 2024 18:11:31 +0200
+Subject: Fix infinite loop when parsing malformed Sieve script
+
+Origin: https://github.com/roundcube/roundcubemail/commit/3567090a997e95aac6bb052bfb48bb301d0c03c3
+Bug: https://github.com/roundcube/roundcubemail/issues/9562
+---
+ plugins/managesieve/lib/Roundcube/rcube_sieve_script.php | 7 ++++---
+ plugins/managesieve/tests/Parser.php | 14 ++++++++++++++
+ 2 files changed, 18 insertions(+), 3 deletions(-)
+
+diff --git a/plugins/managesieve/lib/Roundcube/rcube_sieve_script.php b/plugins/managesieve/lib/Roundcube/rcube_sieve_script.php
+index 4b70b5e..46923d3 100644
+--- a/plugins/managesieve/lib/Roundcube/rcube_sieve_script.php
++++ b/plugins/managesieve/lib/Roundcube/rcube_sieve_script.php
+@@ -1305,9 +1305,10 @@ class rcube_sieve_script
+
+ // bracket-comment
+ case '/':
+- if ($str[$position + 1] == '*') {
+- if ($end_pos = strpos($str, '*/', $position + 2)) {
+- $position = $end_pos + 2;
++ $position++;
++ if ($str[$position] == '*') {
++ if ($end_pos = strpos($str, '*/', $position + 1)) {
++ $position = $end_pos + 1;
+ }
+ else {
+ // error
+diff --git a/plugins/managesieve/tests/Parser.php b/plugins/managesieve/tests/Parser.php
+index e7d511d..537bed0 100644
+--- a/plugins/managesieve/tests/Parser.php
++++ b/plugins/managesieve/tests/Parser.php
+@@ -59,4 +59,18 @@ class Parser extends \PHPUnit\Framework\TestCase
+
+ return $result;
+ }
++
++ /**
++ * Sieve script parsing
++ */
++ public function test_parser_bug9562()
++ {
++ // This is an obviously invalid script
++ $input = "vacation :subject \"a\" :from \"b\"\n<a href=\"https://test.org/\">test</a>";
++ $script = new rcube_sieve_script($input);
++ $result = $script->as_text();
++
++ // TODO: The output still is BS, but it at least does not cause an infinite loop
++ $this->assertSame("require [\"vacation\"];\r\nvacation :subject \"a\" :from \"b\" \"a\";\r\n", $result);
++ }
+ }
diff -Nru roundcube-1.4.15+dfsg.1/debian/patches/Fix-regression-where-printing-scaling-rotating-image-atta.patch roundcube-1.4.15+dfsg.1/debian/patches/Fix-regression-where-printing-scaling-rotating-image-atta.patch
--- roundcube-1.4.15+dfsg.1/debian/patches/Fix-regression-where-printing-scaling-rotating-image-atta.patch 1970-01-01 01:00:00.000000000 +0100
+++ roundcube-1.4.15+dfsg.1/debian/patches/Fix-regression-where-printing-scaling-rotating-image-atta.patch 2024-08-08 23:48:56.000000000 +0200
@@ -0,0 +1,71 @@
+From: Aleksander Machniak <alec at alec.pl>
+Date: Thu, 8 Aug 2024 13:54:32 +0200
+Subject: Fix regression where printing/scaling/rotating image attachments was
+ broken
+
+Origin: https://github.com/roundcube/roundcubemail/commit/44cec17e8f1b9a03af75f97a9cb6a77724586c47
+Bug: https://github.com/roundcube/roundcubemail/issues/9571
+Bug-Debian: https://bugs.debian.org/1078456
+---
+ program/js/app.js | 19 +++++++------------
+ program/lib/Roundcube/rcube_output.php | 3 ++-
+ 2 files changed, 9 insertions(+), 13 deletions(-)
+
+diff --git a/program/js/app.js b/program/js/app.js
+index d95bf3c..4aedf1d 100644
+--- a/program/js/app.js
++++ b/program/js/app.js
+@@ -422,13 +422,11 @@ function rcube_webmail()
+ var contents = $(this).contents();
+
+ // do not apply styles to an error page (with no image)
+- if (contents.find('img').length)
+- contents.find('head').append(
+- '<style type="text/css">'
+- + 'img { max-width:100%; max-height:100%; } ' // scale
+- + 'body { display:flex; align-items:center; justify-content:center; height:100%; margin:0; }' // align
+- + '</style>'
+- );
++ if (contents.find('img').length) {
++ contents.find('img').css({ maxWidth: '100%', maxHeight: '100%' });
++ contents.find('body').css({ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100%', margin: 0 });
++ contents.find('html').css({ height: '100%' });
++ }
+ });
+ }
+ // show printing dialog
+@@ -5725,9 +5723,7 @@ function rcube_webmail()
+ this.apply_image_style = function()
+ {
+ var style = [],
+- head = $(this.gui_objects.messagepartframe).contents().find('head');
+-
+- $('#image-style', head).remove();
++ img = $(this.gui_objects.messagepartframe).contents().find('img');
+
+ $.each({scale: '', rotate: 'deg'}, function(i, v) {
+ var val = ref.image_style[i];
+@@ -5735,8 +5731,7 @@ function rcube_webmail()
+ style.push(i + '(' + val + v + ')');
+ });
+
+- if (style)
+- head.append($('<style id="image-style">').text('img { transform: ' + style.join(' ') + '}'));
++ img.css('transform', style.join(' '));
+ };
+
+ // Update import dialog state
+diff --git a/program/lib/Roundcube/rcube_output.php b/program/lib/Roundcube/rcube_output.php
+index a51b75b..e070c5c 100644
+--- a/program/lib/Roundcube/rcube_output.php
++++ b/program/lib/Roundcube/rcube_output.php
+@@ -286,7 +286,8 @@ abstract class rcube_output
+ }
+
+ // Use strict security policy to make sure no javascript content is executed
+- header("Content-Security-Policy: default-src 'none'");
++ // img-src is needed to be able to print attachment preview page
++ header("Content-Security-Policy: default-src 'none'; img-src 'self'");
+
+ // don't kill the connection if download takes more than 30 sec.
+ if (!array_key_exists('time_limit', $params)) {
diff -Nru roundcube-1.4.15+dfsg.1/debian/patches/series roundcube-1.4.15+dfsg.1/debian/patches/series
--- roundcube-1.4.15+dfsg.1/debian/patches/series 2024-06-17 04:10:38.000000000 +0200
+++ roundcube-1.4.15+dfsg.1/debian/patches/series 2024-08-08 23:48:56.000000000 +0200
@@ -22,3 +22,8 @@
CVE-2023-47272.patch
CVE-2024-37384.patch
CVE-2024-37383.patch
+CVE-2024-42009.patch
+CVE-2024-42008.patch
+Fix-regression-where-printing-scaling-rotating-image-atta.patch
+CVE-2024-42010.patch
+Fix-infinite-loop-when-parsing-malformed-Sieve-script.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/20240812/a14c8ab6/attachment-0001.sig>
More information about the Pkg-roundcube-maintainers
mailing list