Chrome · HTML-in-Canvas
CVE-2026-15768
Logic Error in HTML-in-Canvas
Overview
High
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/background-image-in-iframe-ignored.tentative.https.sub.html |
modified |
Files Changed
third_party/blink/renderer/core/dom/element.ccthird_party/blink/renderer/core/dom/element.hthird_party/blink/renderer/core/html/forms/html_input_element.ccthird_party/blink/renderer/core/html/forms/html_select_element.ccthird_party/blink/renderer/core/html/forms/html_text_area_element.ccthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/background-image-in-iframe-ignored.tentative.https.sub.htmlthird_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/support/subframe-cross-origin-background-image.https.sub.html
Patch
From 4ca9747a45ccaf8beda9ed8803333b4b6bfc1bfe Mon Sep 17 00:00:00 2001 From: Stefan Zager <[email protected]> Date: Fri, 26 Jun 2026 10:08:22 -0700 Subject: [PATCH] [HiC] Invalidate cached paint when content is moved into canvas Bug: 517931625 Change-Id: I4b024abe0d670a30daab5089ac2a9c4942bb27c8 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7997860 Reviewed-by: Philip Rogers <[email protected]> Commit-Queue: Stefan Zager <[email protected]> Cr-Commit-Position: refs/heads/main@{#1653242} --- diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc index b57f6122..7a26053 100644 --- a/third_party/blink/renderer/core/dom/element.cc +++ b/third_party/blink/renderer/core/dom/element.cc @@ -255,6 +255,7 @@ #include "third_party/blink/renderer/core/page/scrolling/root_scroller_controller.h" #include "third_party/blink/renderer/core/page/scrolling/sync_scroll_attempt_heuristic.h" #include "third_party/blink/renderer/core/page/spatial_navigation.h" +#include "third_party/blink/renderer/core/paint/object_paint_invalidator.h" #include "third_party/blink/renderer/core/paint/paint_layer.h" #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h" #include "third_party/blink/renderer/core/probe/core_probes.h" @@ -4404,6 +4405,18 @@ DidChangeIsCanvasOrInCanvasSubtree(); } +void Element::DidChangeIsCanvasOrInCanvasSubtree() { + if (auto* layout_object = GetLayoutObject()) { + layout_object->SetNeedsPaintPropertyUpdate(); + if (layout_object->HasLayer()) { + To<LayoutBoxModelObject>(layout_object)->Layer()->SetNeedsRepaint(); + } + ObjectPaintInvalidator(*layout_object) + .InvalidateDisplayItemClient(*layout_object, + PaintInvalidationReason::kUncacheable); + } +} + void Element::RemovedFrom(ContainerNode& insertion_point) { bool was_in_document = insertion_point.isConnected(); if (Element* parent = DynamicTo<Element>(insertion_point)) { diff --git a/third_party/blink/renderer/core/dom/element.h b/third_party/blink/renderer/core/dom/element.h index 11d44cb..abeaff9 100644 --- a/third_party/blink/renderer/core/dom/element.h +++ b/third_party/blink/renderer/core/dom/element.h @@ -1138,7 +1138,7 @@ return HasElementFlag(ElementFlags::kIsCanvasOrInCanvasSubtree); } // Called when `IsCanvasOrInCanvasSubtree()` has changed. - virtual void DidChangeIsCanvasOrInCanvasSubtree() {} + virtual void DidChangeIsCanvasOrInCanvasSubtree(); // Like `IsCanvasOrInCanvasSubtree()`, but excludes the outermost <canvas>. bool IsInCanvasSubtree() const; diff --git a/third_party/blink/renderer/core/html/forms/html_input_element.cc b/third_party/blink/renderer/core/html/forms/html_input_element.cc index a1ea51ca..e3e6e74 100644 --- a/third_party/blink/renderer/core/html/forms/html_input_element.cc +++ b/third_party/blink/renderer/core/html/forms/html_input_element.cc @@ -1340,6 +1340,7 @@ } void HTMLInputElement::DidChangeIsCanvasOrInCanvasSubtree() { + TextControlElement::DidChangeIsCanvasOrInCanvasSubtree(); if (RuntimeEnabledFeatures::CanvasDrawElementEnabled(GetExecutionContext()) && IsInCanvasSubtree()) { // Hide suggested values when under canvas, to prevent leaking this diff --git a/third_party/blink/renderer/core/html/forms/html_select_element.cc b/third_party/blink/renderer/core/html/forms/html_select_element.cc index d6c70b5..c0e114e3 100644 --- a/third_party/blink/renderer/core/html/forms/html_select_element.cc +++ b/third_party/blink/renderer/core/html/forms/html_select_element.cc @@ -888,6 +888,7 @@ } void HTMLSelectElement::DidChangeIsCanvasOrInCanvasSubtree() { + HTMLFormControlElementWithState::DidChangeIsCanvasOrInCanvasSubtree(); if (RuntimeEnabledFeatures::CanvasDrawElementEnabled(GetExecutionContext()) && IsInCanvasSubtree()) { // Hide suggested values when under canvas, to prevent leaking this diff --git a/third_party/blink/renderer/core/html/forms/html_text_area_element.cc b/third_party/blink/renderer/core/html/forms/html_text_area_element.cc index 834ab13b..caabe91 100644 --- a/third_party/blink/renderer/core/html/forms/html_text_area_element.cc +++ b/third_party/blink/renderer/core/html/forms/html_text_area_element.cc @@ -703,6 +703,7 @@ } void HTMLTextAreaElement::DidChangeIsCanvasOrInCanvasSubtree() { + TextControlElement::DidChangeIsCanvasOrInCanvasSubtree(); if (RuntimeEnabledFeatures::CanvasDrawElementEnabled(GetExecutionContext()) && IsInCanvasSubtree()) { // Hide suggested values when under canvas, to prevent leaking this diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/background-image-in-iframe-ignored.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/background-image-in-iframe-ignored.tentative.https.sub.html new file mode 100644 index 0000000..5dc64d5 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/background-image-in-iframe-ignored.tentative.https.sub.html @@ -0,0 +1,36 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>drawElementImage does not draw cross-origin background images</title> + <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> + <iframe id="iframe" src="support/subframe-cross-origin-background-image.https.sub.html"></iframe> + <canvas id="canvas" width="300" height="150" layoutsubtree> + <div id="child"></div> + </canvas> + + <script> + window.onload = () => { + promise_test(async function(t) { + await waitForCanvasPaint(canvas); + + // Atomically move the iframe into the canvas. + child.moveBefore(iframe, null); + + await waitForCanvasPaint(canvas); + const context = canvas.getContext("2d"); + context.drawElementImage(child, 0, 0); + const imgData = context.getImageData(0, 0, canvas.width, canvas.height); + let pixel = _getPixelFromImageData(imgData, 50, 50); + assert_array_equals(pixel, [0, 128, 0, 255], + "Cross-origin background image in iframe should not draw"); + }); + }; + </script> +</body> +</html> diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/support/subframe-cross-origin-background-image.https.sub.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/support/subframe-cross-origin-background-image.https.sub.html new file mode 100644 index 0000000..48c81b7 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/support/subframe-cross-origin-background-image.https.sub.html @@ -0,0 +1,22 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>subframe with cross-origin background image</title> + <style> + body { + background-color: rgb(0 128 0); + } + div { + position: absolute; + left: 0px; + top: 0px; + width: 100px; + height: 100px; + background-image: url("https://{{hosts[alt][www]}}:{{ports[https][0]}}/images/red-100x100.png"); + } + </style> +</head> + +<body><div></div></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/background-image-in-iframe-ignored.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/background-image-in-iframe-ignored.tentative.https.sub.html
new file mode 100644
index 0000000..5dc64d5
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/background-image-in-iframe-ignored.tentative.https.sub.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>drawElementImage does not draw cross-origin background images</title>
+ <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>
+ <iframe id="iframe" src="support/subframe-cross-origin-background-image.https.sub.html"></iframe>
+ <canvas id="canvas" width="300" height="150" layoutsubtree>
+ <div id="child"></div>
+ </canvas>
+
+ <script>
+ window.onload = () => {
+ promise_test(async function(t) {
+ await waitForCanvasPaint(canvas);
+
+ // Atomically move the iframe into the canvas.
+ child.moveBefore(iframe, null);
+
+ await waitForCanvasPaint(canvas);
+ const context = canvas.getContext("2d");
+ context.drawElementImage(child, 0, 0);
+ const imgData = context.getImageData(0, 0, canvas.width, canvas.height);
+ let pixel = _getPixelFromImageData(imgData, 50, 50);
+ assert_array_equals(pixel, [0, 128, 0, 255],
+ "Cross-origin background image in iframe should not draw");
+ });
+ };
+ </script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/support/subframe-cross-origin-background-image.https.sub.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/support/subframe-cross-origin-background-image.https.sub.html
new file mode 100644
index 0000000..48c81b7
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/support/subframe-cross-origin-background-image.https.sub.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>subframe with cross-origin background image</title>
+ <style>
+ body {
+ background-color: rgb(0 128 0);
+ }
+ div {
+ position: absolute;
+ left: 0px;
+ top: 0px;
+ width: 100px;
+ height: 100px;
+ background-image: url("https://{{hosts[alt][www]}}:{{ports[https][0]}}/images/red-100x100.png");
+ }
+ </style>
+</head>
+
+<body><div></div></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