High chrome Logic Error 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient policy enforcement in HTML-in-Canvas
DescriptionInsufficient policy enforcement in HTML-in-Canvas
ComponentHTML-in-Canvas
Bug ClassLogic Error
Tracker517931625
Fix commit4ca9747a45cc (chromium/src) +75/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-14

Changed Functions

FunctionChangeNotes
promise_test
third_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.cc
  • third_party/blink/renderer/core/dom/element.h
  • third_party/blink/renderer/core/html/forms/html_input_element.cc
  • third_party/blink/renderer/core/html/forms/html_select_element.cc
  • third_party/blink/renderer/core/html/forms/html_text_area_element.cc
  • third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/background-image-in-iframe-ignored.tentative.https.sub.html
  • third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/support/subframe-cross-origin-background-image.https.sub.html
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.