Chrome · SVG
CVE-2026-87620
Logic Error in SVG
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
promise_testthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-linear-gradient-outside-subtree-ignored.tentative.html |
modified | |
promise_testthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-pattern-outside-subtree-ignored.tentative.html |
modified |
Files Changed
third_party/blink/renderer/core/paint/svg_object_painter.ccthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-linear-gradient-outside-subtree-ignored.tentative.htmlthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-pattern-outside-subtree-ignored.tentative.htmlthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-radial-gradient-outside-subtree-ignored.tentative.html
Patch
From 07625009a5eefe24a3511b87fee9142e4d7a71a3 Mon Sep 17 00:00:00 2001 From: Stephen Chenney <[email protected]> Date: Wed, 12 Aug 2026 13:22:26 -0700 Subject: [PATCH] [HiC] Disallow non-subtree SVG paint resources SVG Paint Resources may use visited link colors, or the <pattern> element may contain arbitrary DOM content, including forms with autofill. When defined outside the canvas subtree these elements are not marked as being inside the subtree, which defeats various privacy preserving measures. So disallow the use of resources from outside the canvas subtree when inside the canvas subtree. Add tests and start to re-organize the privacy tests to make it easier to see what areas we are testing. A bug has been filed to verify the behavior of SVG <use> elements. Fixed: 514489101 Change-Id: Ife76a6c02aaa2c3eefdb604343b849a213657900 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8058744 Commit-Queue: Stephen Chenney <[email protected]> Reviewed-by: Stefan Zager <[email protected]> Cr-Commit-Position: refs/heads/main@{#1678269} --- diff --git a/third_party/blink/renderer/core/paint/svg_object_painter.cc b/third_party/blink/renderer/core/paint/svg_object_painter.cc index 35863f4..7b078882 100644 --- a/third_party/blink/renderer/core/paint/svg_object_painter.cc +++ b/third_party/blink/renderer/core/paint/svg_object_painter.cc @@ -31,6 +31,11 @@ return false; } + if (paint_flags & PaintFlag::kPrivacyPreserving && + !uri_resource->GetElement()->IsInCanvasSubtree()) { + return false; + } + AutoDarkMode auto_dark_mode(PaintAutoDarkMode( context_paint.object.StyleRef(), DarkModeFilter::ElementRole::kSVG)); if (!uri_resource->ApplyShader( diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-linear-gradient-outside-subtree-ignored.tentative.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-linear-gradient-outside-subtree-ignored.tentative.html new file mode 100644 index 0000000..da2ee27 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-linear-gradient-outside-subtree-ignored.tentative.html @@ -0,0 +1,62 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>drawElementImage does not use SVG resources from outside the subtree</title> + <script src='/resources/testharness.js'></script> + <script src='/resources/testharnessreport.js'></script> + <script src="/html/canvas/resources/wait-for-canvas-paint.js"></script> + <style> + a { + color: blue; + } + a:visited { + color: red; + } + canvas { + background-color: green; + } + </style> +</head> +<body> + <a href=""> + <svg width="0" height="0"> + <defs> + <linearGradient id="grad-outside"> + <stop offset="0" stop-color="currentColor"/> + <stop offset="1" stop-color="currentColor"/> + </linearGradient> + </defs> + </svg> + </a> + + <canvas id="canvas" width="100" height="100" layoutsubtree> + <div id="child"> + <svg width="100" height="100"> + <rect x="0" y="0" width="100" height="100" fill="url(#grad-outside)"/> + </svg> + </div> + </canvas> + + <script> + window.onload = () => { + promise_test(async function(t) { + await waitForCanvasPaint(canvas); + + var context = canvas.getContext("2d"); + + context.fillStyle = 'green'; + context.fillRect(0, 0, 100, 100); + + // Draw the element image + context.drawElementImage(child, 0, 0); + + // The canvas should ignore the gradient and use transparent black. + let pixel = context.getImageData(50, 50, 1, 1).data; + assert_false(pixel[0] > 0, "The canvas should not use the gradient."); + assert_true(pixel[1] > 0, "The canvas should not use the gradient."); + assert_false(pixel[2] > 0, "The canvas should not use the gradient."); + }); + }; + </script> +</body> +</html> diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-pattern-outside-subtree-ignored.tentative.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-pattern-outside-subtree-ignored.tentative.html new file mode 100644 index 0000000..71ab5f6d --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-pattern-outside-subtree-ignored.tentative.html @@ -0,0 +1,55 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>drawElementImage does not use SVG patterns outside the subtree</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/wait-for-canvas-paint.js"></script> +</head> +<body> + <svg width="0" height="0"> + <defs> + <pattern id="pattern" patternUnits="userSpaceOnUse" viewBox="0 0 100 100" width="100" height="100"> + <image href="/images/red-100x100.png" x="0" y="0" width="100" height="100" preserveAspectRatio="none"/> + </pattern> + </defs> + </svg> + + <canvas id="canvas" width="100" height="100" layoutsubtree> + <svg id=child width="100" height="100"> + <rect x="0" y="0" width="100" height="100" fill="url(#pattern)"/> + </svg> + </canvas> + + <script> + window.onload = () => { + promise_test(async function(t) { + const preloadImage = (url) => new Promise((resolve, reject) => { + const img = new Image(); + img.onload = () => resolve(img); + img.onerror = () => reject(new Error(`Failed to load image: ${url}`)); + img.src = url; + }); + + const imageEl = document.querySelector('#pattern image'); + await Promise.all([ + preloadImage(imageEl.getAttribute('href')), + ]); + + await waitForCanvasPaint(canvas); + var ctx = canvas.getContext('2d'); + ctx.fillStyle = 'green'; + ctx.fillRect(0, 0, 100, 100); + ctx.drawElementImage(child, 0, 0); + + // The canvas should ignore the gradient and use transparent black. + let pixel = ctx.getImageData(50, 50, 1, 1).data; + assert_false(pixel[0] > 0, "The canvas should not use the pattern."); + assert_true(pixel[1] > 0, "The canvas should not use the pattern."); + assert_false(pixel[2] > 0, "The canvas should not use the pattern."); + }); + } + </script> +</body> +</html> diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-radial-gradient-outside-subtree-ignored.tentative.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-radial-gradient-outside-subtree-ignored.tentative.html new file mode 100644 index 0000000..6d540f6 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-radial-gradient-outside-subtree-ignored.tentative.html @@ -0,0 +1,62 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>drawElementImage does not use SVG resources from outside the subtree</title> + <script src='/resources/testharness.js'></script> + <script src='/resources/testharnessreport.js'></script> + <script src="/html/canvas/resources/wait-for-canvas-paint.js"></script> + <style> + a { + color: blue; + } + a:visited { + color: red; + } + canvas { + background-color: green; + } + </style> +</head> +<body> + <a href=""> + <svg width="0" height="0"> + <defs>
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-linear-gradient-outside-subtree-ignored.tentative.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-linear-gradient-outside-subtree-ignored.tentative.html
new file mode 100644
index 0000000..da2ee27
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-linear-gradient-outside-subtree-ignored.tentative.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>drawElementImage does not use SVG resources from outside the subtree</title>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src="/html/canvas/resources/wait-for-canvas-paint.js"></script>
+ <style>
+ a {
+ color: blue;
+ }
+ a:visited {
+ color: red;
+ }
+ canvas {
+ background-color: green;
+ }
+ </style>
+</head>
+<body>
+ <a href="">
+ <svg width="0" height="0">
+ <defs>
+ <linearGradient id="grad-outside">
+ <stop offset="0" stop-color="currentColor"/>
+ <stop offset="1" stop-color="currentColor"/>
+ </linearGradient>
+ </defs>
+ </svg>
+ </a>
+
+ <canvas id="canvas" width="100" height="100" layoutsubtree>
+ <div id="child">
+ <svg width="100" height="100">
+ <rect x="0" y="0" width="100" height="100" fill="url(#grad-outside)"/>
+ </svg>
+ </div>
+ </canvas>
+
+ <script>
+ window.onload = () => {
+ promise_test(async function(t) {
+ await waitForCanvasPaint(canvas);
+
+ var context = canvas.getContext("2d");
+
+ context.fillStyle = 'green';
+ context.fillRect(0, 0, 100, 100);
+
+ // Draw the element image
+ context.drawElementImage(child, 0, 0);
+
+ // The canvas should ignore the gradient and use transparent black.
+ let pixel = context.getImageData(50, 50, 1, 1).data;
+ assert_false(pixel[0] > 0, "The canvas should not use the gradient.");
+ assert_true(pixel[1] > 0, "The canvas should not use the gradient.");
+ assert_false(pixel[2] > 0, "The canvas should not use the gradient.");
+ });
+ };
+ </script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-pattern-outside-subtree-ignored.tentative.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-pattern-outside-subtree-ignored.tentative.html
new file mode 100644
index 0000000..71ab5f6d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-pattern-outside-subtree-ignored.tentative.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>drawElementImage does not use SVG patterns outside the subtree</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/wait-for-canvas-paint.js"></script>
+</head>
+<body>
+ <svg width="0" height="0">
+ <defs>
+ <pattern id="pattern" patternUnits="userSpaceOnUse" viewBox="0 0 100 100" width="100" height="100">
+ <image href="/images/red-100x100.png" x="0" y="0" width="100" height="100" preserveAspectRatio="none"/>
+ </pattern>
+ </defs>
+ </svg>
+
+ <canvas id="canvas" width="100" height="100" layoutsubtree>
+ <svg id=child width="100" height="100">
+ <rect x="0" y="0" width="100" height="100" fill="url(#pattern)"/>
+ </svg>
+ </canvas>
+
+ <script>
+ window.onload = () => {
+ promise_test(async function(t) {
+ const preloadImage = (url) => new Promise((resolve, reject) => {
+ const img = new Image();
+ img.onload = () => resolve(img);
+ img.onerror = () => reject(new Error(`Failed to load image: ${url}`));
+ img.src = url;
+ });
+
+ const imageEl = document.querySelector('#pattern image');
+ await Promise.all([
+ preloadImage(imageEl.getAttribute('href')),
+ ]);
+
+ await waitForCanvasPaint(canvas);
+ var ctx = canvas.getContext('2d');
+ ctx.fillStyle = 'green';
+ ctx.fillRect(0, 0, 100, 100);
+ ctx.drawElementImage(child, 0, 0);
+
+ // The canvas should ignore the gradient and use transparent black.
+ let pixel = ctx.getImageData(50, 50, 1, 1).data;
+ assert_false(pixel[0] > 0, "The canvas should not use the pattern.");
+ assert_true(pixel[1] > 0, "The canvas should not use the pattern.");
+ assert_false(pixel[2] > 0, "The canvas should not use the pattern.");
+ });
+ }
+ </script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-radial-gradient-outside-subtree-ignored.tentative.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-radial-gradient-outside-subtree-ignored.tentative.html
new file mode 100644
index 0000000..6d540f6
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg-radial-gradient-outside-subtree-ignored.tentative.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>drawElementImage does not use SVG resources from outside the subtree</title>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src="/html/canvas/resources/wait-for-canvas-paint.js"></script>
+ <style>
+ a {
+ color: blue;
+ }
+ a:visited {
+ color: red;
+ }
+ canvas {
+ background-color: green;
+ }
+ </style>
+</head>
+<body>
+ <a href="">
+ <svg width="0" height="0">
+ <defs>
+ <radialGradient id="grad-outside">
+ <stop offset="0" stop-color="currentColor"/>
+ <stop offset="1" stop-color="currentColor"/>
+ </radialGradient>
+ </defs>
+ </svg>
+ </a>
+
+ <canvas id="canvas" width="100" height="100" layoutsubtree>
+ <div id="child">
+ <svg width="100" height="100">
+ <rect x="0" y="0" width="100" height="100" fill="url(#grad-outside)"/>
+ </svg>
+ </div>
+ </canvas>
+
+ <script>
+ window.onload = () => {
+ promise_test(async function(t) {
+ await waitForCanvasPaint(canvas);
+
+ var context = canvas.getContext("2d");
+
+ context.fillStyle = 'green';
+ context.fillRect(0, 0, 100, 100);
+
+ // Draw the element image
+ context.drawElementImage(child, 0, 0);
+
+ // The canvas should ignore the gradient and use transparent black.
+ let pixel = context.getImageData(50, 50, 1, 1).data;
+ assert_false(pixel[0] > 0, "The canvas should not use the gradient.");
+ assert_true(pixel[1] > 0, "The canvas should not use the gradient.");
+ assert_false(pixel[2] > 0, "The canvas should not use the gradient.");
+ });
+ };
+ </script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/visited-link-color-ignored.tentative.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/visited-link-color-ignored.tentative.html
deleted file mode 100644
index 5a149b2..0000000
--- a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/visited-link-color-ignored.tentative.html
+++ /dev/null
@@ -1,45 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
- <title>drawElementImage does not reveal visited link colors</title>
- <script src='/resources/testharness.js'></script>
- <script src='/resources/testharnessreport.js'></script>
- <script src="/html/canvas/resources/wait-for-canvas-paint.js"></script>
- <style>
- #link {
- width: 100px;
- height: 100px;
- background-color: green;
- color: green;
- display: block;
- }
- #link:visited {
- background-color: red; /* Visited color */
- color: red; /* Visited color */
- }
- </style>
-</head>
-<body>
- <canvas id="canvas" width="100" height="100" layoutsubtree>
- <a id="link" href="">link</a>
- </canvas>
-
- <script>
- window.onload = () => {
- promise_test(async function(t) {
- await waitForCanvasPaint(canvas);
-
- var context = canvas.getContext("2d");
-
- // Draw the element image
- context.drawElementImage(link, 0, 0);
-
- // The canvas should NOT reflect the visited state (red), it should paint the unvisited state (green).
- let pixel = context.getImageData(10, 10, 1, 1).data;
- assert_false(pixel[0] > 0, "The canvas should not reflect the visited state.");
- assert_true(pixel[1] > 0, "The canvas should not reflect the visited state.");
- });
- };
- </script>
-</body>
-</html>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/visited-link-currentcolor-ignored.tentative.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/visited-link-currentcolor-ignored.tentative.html
deleted file mode 100644
index d4d94d1..0000000
--- a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/visited-link-currentcolor-ignored.tentative.html
+++ /dev/null
@@ -1,47 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
- <title>drawElementImage does not reveal visited link colors through currentColor</title>
- <script src='/resources/testharness.js'></script>
- <script src='/resources/testharnessreport.js'></script>
- <script src="/html/canvas/resources/wait-for-canvas-paint.js"></script>
- <style>
- #link {
- color: green;
- }
- #link:visited {
- color: red;
- }
- #target {
- width: 100px;
- height: 100px;
- background-color: currentColor;
- }
- </style>
-</head>
-<body>
- <a id="link" href="">
- <canvas id="canvas" width="100" height="100" layoutsubtree>
- <div id="target"></div>
- </canvas>
- </a>
-
- <script>
- window.onload = () => {
- promise_test(async function(t) {
- await waitForCanvasPaint(canvas);
-
- var context = canvas.getContext("2d");
-
- // Draw the element image
- context.drawElementImage(target, 0, 0);
-
- // The canvas should NOT reflect the visited state (red), it should paint the unvisited state (green).
- let pixel = context.getImageData(10, 10, 1, 1).data;
- assert_false(pixel[0] > 0, "The canvas should not reflect the visited state.");
- assert_true(pixel[1] > 0, "The canvas should not reflect the visited state.");
- });
- };
- </script>
-</body>
... (truncated)
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