Chrome · Paint
CVE-2026-17885
Logic Error in Paint
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifthird_party/blink/renderer/core/paint/scoped_svg_paint_state.cc |
modified | |
promise_testthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-filter.tentative.https.sub.html |
modified |
Files Changed
third_party/blink/renderer/core/paint/scoped_svg_paint_state.ccthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-filter.tentative.https.sub.html
Patch
From bbe4ea0264d886f0c3bcbd40636550ca63880bd2 Mon Sep 17 00:00:00 2001 From: Stefan Zager <[email protected]> Date: Sat, 27 Jun 2026 11:16:52 -0700 Subject: [PATCH] [HiC] Correctly invalidate SVG filters for privacy-preserving paint Bug: 523698038 Change-Id: I5b2288867d1cfa0e5eb1a39678b7af76577a248f Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8012950 Reviewed-by: Stephen Chenney <[email protected]> Commit-Queue: Stefan Zager <[email protected]> Cr-Commit-Position: refs/heads/main@{#1653688} --- diff --git a/third_party/blink/renderer/core/paint/scoped_svg_paint_state.cc b/third_party/blink/renderer/core/paint/scoped_svg_paint_state.cc index d6f87251..abbc0cc5 100644 --- a/third_party/blink/renderer/core/paint/scoped_svg_paint_state.cc +++ b/third_party/blink/renderer/core/paint/scoped_svg_paint_state.cc @@ -146,10 +146,9 @@ const auto* filter = properties.Filter(); if (filter && filter->Filter() && filter->Filter()->OriginTainted() && (paint_info_.GetPaintFlags() & PaintFlag::kPrivacyPreserving)) { + state.SetEffect(*filter->Parent()); filter = nullptr; - } - - if (filter) { + } else if (filter) { state.SetEffect(*filter); } else if (const auto* effect = properties.Effect()) { state.SetEffect(*effect); diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-filter.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-filter.tentative.https.sub.html new file mode 100644 index 0000000..5ae47f1 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-filter.tentative.https.sub.html @@ -0,0 +1,72 @@ +<!DOCTYPE html> +<html> +<head> + <title>drawElementImage does not use cross-origin feImages on filtered SVG text inside masks</title> + <link rel="help" href="https://github.com/WICG/html-in-canvas"> + <script src='/resources/testharness.js'></script> + <script src='/resources/testharnessreport.js'></script> + <script src='/html/canvas/resources/canvas-tests.js'></script> + <script src="/html/canvas/resources/wait-for-canvas-paint.js"></script> +</head> + +<body> + <svg width="0" height="0"> + <defs> + <filter id="filter-same" filterUnits="userSpaceOnUse" x="0" y="0" width="100" height="100"> + <feImage href="https://{{location[host]}}/images/green-100x100.png"/> + <feComponentTransfer> + <feFuncR type="linear" slope="0" intercept="1"/> + <feFuncG type="linear" slope="0" intercept="1"/> + <feFuncB type="linear" slope="0" intercept="1"/> + </feComponentTransfer> + </filter> + <filter id="filter-cross" filterUnits="userSpaceOnUse" x="0" y="0" width="100" height="100"> + <feImage href="https://{{hosts[alt][www]}}:{{ports[https][0]}}/images/red-100x100.png"/> + <feComponentTransfer> + <feFuncR type="linear" slope="0" intercept="1"/> + <feFuncG type="linear" slope="0" intercept="1"/> + <feFuncB type="linear" slope="0" intercept="1"/> + </feComponentTransfer> + </filter> + <mask id="mask-same" maskUnits="userSpaceOnUse" x="0" y="0" width="100" height="100"> + <text x="0" y="50" filter="url(#filter-same)">x</text> + </mask> + <mask id="mask-cross" maskUnits="userSpaceOnUse" x="0" y="0" width="100" height="100"> + <text x="0" y="50" filter="url(#filter-cross)">x</text> + </mask> + </defs> + </svg> + + <canvas id="canvas" width="100" height="200" layoutsubtree> + <svg id="child" width="100" height="200"> + <svg x="0" y="0" width="100" height="100"> + <rect width="100" height="100" fill="rgb(30, 130, 230)" mask="url(#mask-same)"/> + </svg> + <svg x="0" y="100" width="100" height="100"> + <rect width="100" height="100" fill="rgb(40, 140, 240)" mask="url(#mask-cross)"/> + </svg> + </svg> + </canvas> + + <script> + window.onload = () => { + promise_test(async function(t) { + await waitForCanvasPaint(canvas); + var ctx = canvas.getContext('2d'); + ctx.fillStyle = 'blue'; + ctx.fillRect(0, 0, 100, 200); + ctx.drawElementImage(document.getElementById('child'), 0, 0); + + // Fetch all pixel data once to avoid multiple slow readbacks. + const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); + + let pixel = _getPixelFromImageData(imgData, 50, 50); + assert_array_equals(pixel, [30, 130, 230, 255], "Same origin filter image in external mask should be drawn"); + + pixel = _getPixelFromImageData(imgData, 50, 150); + assert_array_equals(pixel, [0, 0, 255, 255], "Cross origin filter image in external mask should not be drawn"); + }); + } + </script> +</body> +</html>
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-filter.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-filter.tentative.https.sub.html
new file mode 100644
index 0000000..5ae47f1
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-mask-filter.tentative.https.sub.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>drawElementImage does not use cross-origin feImages on filtered SVG text inside masks</title>
+ <link rel="help" href="https://github.com/WICG/html-in-canvas">
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src='/html/canvas/resources/canvas-tests.js'></script>
+ <script src="/html/canvas/resources/wait-for-canvas-paint.js"></script>
+</head>
+
+<body>
+ <svg width="0" height="0">
+ <defs>
+ <filter id="filter-same" filterUnits="userSpaceOnUse" x="0" y="0" width="100" height="100">
+ <feImage href="https://{{location[host]}}/images/green-100x100.png"/>
+ <feComponentTransfer>
+ <feFuncR type="linear" slope="0" intercept="1"/>
+ <feFuncG type="linear" slope="0" intercept="1"/>
+ <feFuncB type="linear" slope="0" intercept="1"/>
+ </feComponentTransfer>
+ </filter>
+ <filter id="filter-cross" filterUnits="userSpaceOnUse" x="0" y="0" width="100" height="100">
+ <feImage href="https://{{hosts[alt][www]}}:{{ports[https][0]}}/images/red-100x100.png"/>
+ <feComponentTransfer>
+ <feFuncR type="linear" slope="0" intercept="1"/>
+ <feFuncG type="linear" slope="0" intercept="1"/>
+ <feFuncB type="linear" slope="0" intercept="1"/>
+ </feComponentTransfer>
+ </filter>
+ <mask id="mask-same" maskUnits="userSpaceOnUse" x="0" y="0" width="100" height="100">
+ <text x="0" y="50" filter="url(#filter-same)">x</text>
+ </mask>
+ <mask id="mask-cross" maskUnits="userSpaceOnUse" x="0" y="0" width="100" height="100">
+ <text x="0" y="50" filter="url(#filter-cross)">x</text>
+ </mask>
+ </defs>
+ </svg>
+
+ <canvas id="canvas" width="100" height="200" layoutsubtree>
+ <svg id="child" width="100" height="200">
+ <svg x="0" y="0" width="100" height="100">
+ <rect width="100" height="100" fill="rgb(30, 130, 230)" mask="url(#mask-same)"/>
+ </svg>
+ <svg x="0" y="100" width="100" height="100">
+ <rect width="100" height="100" fill="rgb(40, 140, 240)" mask="url(#mask-cross)"/>
+ </svg>
+ </svg>
+ </canvas>
+
+ <script>
+ window.onload = () => {
+ promise_test(async function(t) {
+ await waitForCanvasPaint(canvas);
+ var ctx = canvas.getContext('2d');
+ ctx.fillStyle = 'blue';
+ ctx.fillRect(0, 0, 100, 200);
+ ctx.drawElementImage(document.getElementById('child'), 0, 0);
+
+ // Fetch all pixel data once to avoid multiple slow readbacks.
+ const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
+
+ let pixel = _getPixelFromImageData(imgData, 50, 50);
+ assert_array_equals(pixel, [30, 130, 230, 255], "Same origin filter image in external mask should be drawn");
+
+ pixel = _getPixelFromImageData(imgData, 50, 150);
+ assert_array_equals(pixel, [0, 0, 255, 255], "Cross origin filter image in external mask should not be drawn");
+ });
+ }
+ </script>
+</body>
+</html>
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page