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 <szager@chromium.org>
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 <pdr@chromium.org>
Commit-Queue: Stefan Zager <szager@chromium.org>
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