Medium chrome Logic Error 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactPolicy bypass in Paint
DescriptionPolicy bypass in Paint
ComponentPaint
Bug ClassLogic Error
Tracker501650594
Fix commit941de2f7b139 (chromium/src) +108/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
promise_test
third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/box-reflect-images-ignored.https.sub.html
modified

Files Changed

  • third_party/blink/renderer/core/paint/paint_layer.cc
  • third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/box-reflect-images-ignored.https.sub.html
  • third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/resources/left-half-mask-50.svg
From 941de2f7b1394f3395b0b1a834b6c4c3bf1aefbf Mon Sep 17 00:00:00 2001
From: Philip Rogers <[email protected]>
Date: Fri, 17 Apr 2026 12:37:28 -0700
Subject: [PATCH] [html-in-canvas] Fix webkit-box-reflect privacy-preserving painting

-webkit-box-reflect accepts an image, and we were not honoring
privacy-preserving painting of that image. This patch strips
cross-origin box reflect images when under canvas.

Fixed: 501650594
Change-Id: Ia50cc4cf12a24426edd82cf2d32296f862b6b621
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7751504
Auto-Submit: Philip Rogers <[email protected]>
Reviewed-by: Stefan Zager <[email protected]>
Commit-Queue: Philip Rogers <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1616780}
---

diff --git a/third_party/blink/renderer/core/paint/paint_layer.cc b/third_party/blink/renderer/core/paint/paint_layer.cc
index 4566099..921e5e0 100644
--- a/third_party/blink/renderer/core/paint/paint_layer.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer.cc
@@ -2453,6 +2453,24 @@
   FilterOperations filter_operations = style.Filter();
   if (GetLayoutObject().HasReflection() && GetLayoutObject().IsBox()) {
     BoxReflection reflection = BoxReflectionForPaintLayer(*this, style);
+
+    if (RuntimeEnabledFeatures::CanvasDrawElementEnabled(
+            GetLayoutObject().GetDocument().GetExecutionContext())) {
+      auto* element = DynamicTo<Element>(GetLayoutObject().GetNode());
+      if (element && element->IsInCanvasSubtree()) {
+        if (const auto* reflect_style = style.BoxReflect()) {
+          if (auto* style_image = reflect_style->Mask().GetImage()) {
+            // Strip the mask image if it is being rendered into a canvas and it
+            // is cross-origin.
+            if (!style_image->IsCorsSameOrigin()) {
+              reflection =
+                  BoxReflection(reflection.Direction(), reflection.Offset());
+            }
+          }
+        }
+      }
+    }
+
     filter_operations.Operations().push_back(
         MakeGarbageCollected<BoxReflectFilterOperation>(reflection));
   }
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/box-reflect-images-ignored.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/box-reflect-images-ignored.https.sub.html
new file mode 100644
index 0000000..be75e3c
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/box-reflect-images-ignored.https.sub.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>drawElementImage does not draw cross-origin box-reflect images</title>
+  <link rel="help" href="https://github.com/WICG/html-in-canvas">
+  <script src='/resources/testharness.js'></script>
+  <script src='/resources/testharnessreport.js'></script>
+  <style>
+  #child {
+    width: 200px;
+    height: 200px;
+    background: blue;
+    position: relative;
+  }
+  #sameOrigin {
+    position: absolute;
+    left: 0px;
+    top: 0px;
+    width: 100px;
+    height: 100px;
+    background:green;
+    -webkit-box-reflect: below 0 url("https://{{location[host]}}/wpt_internal/html/canvas/drawElementImage/resources/left-half-mask-50.svg");
+  }
+  #crossOrigin {
+    position: absolute;
+    left: 100px;
+    top: 0px;
+    width: 100px;
+    height: 100px;
+    background: green;
+    -webkit-box-reflect: below 0 url("https://{{hosts[alt][www]}}:{{ports[h2][0]}}/wpt_internal/html/canvas/drawElementImage/resources/left-half-mask-50.svg");
+  }
+  </style>
+</head>
+<body>
+  <canvas id="canvas" width="200" height="200" layoutsubtree>
+    <!--
+      #child renders a 200x200 blue rect. The left side #sameOrigin and its
+      reflection. The right side is #crossOrigin and its reflection.
+    -->
+    <div id="child">
+      <div id="sameOrigin"></div>
+      <div id="crossOrigin"></div>
+    </div>
+  </canvas>
+
+  <script>
+    window.onload = () => {
+      promise_test(async function(t) {
+        await new Promise(requestAnimationFrame);
+        await new Promise(setTimeout);
+        var context = canvas.getContext("2d");
+        context.drawElementImage(child, 0, 0);
+
+        // Fetch all pixel data once to avoid multiple slow readbacks.
+        const imgData = context.getImageData(0, 0, canvas.width, canvas.height).data;
+
+        // Helper function to extract a pixel's RGBA array at (x, y).
+        const getPixel = (x, y) => {
+          const index = (y * canvas.width + x) * 4;
+          return [
+            imgData[index],
+            imgData[index + 1],
+            imgData[index + 2],
+            imgData[index + 3]
+          ];
+        };
+
+        let pixel = getPixel(50, 50);
+        assert_array_equals(pixel, [0, 128, 0, 255], "Same origin div should draw");
+        pixel = getPixel(25, 150);
+        assert_array_equals(pixel, [0, 128, 0, 255], "Same origin box-reflect mask should not mask out left side of reflection");
+        pixel = getPixel(75, 150);
+        assert_array_equals(pixel, [0, 0, 255, 255], "Same origin box-reflect mask should mask out right side of reflection");
+
+        pixel = getPixel(150, 50);
+        assert_array_equals(pixel, [0, 128, 0, 255], "Cross origin div should draw");
+        pixel = getPixel(125, 150);
+        assert_array_equals(pixel, [0, 128, 0, 255], "Cross origin box-reflect mask should not mask out left side of reflection");
+        pixel = getPixel(175, 150);
+        assert_array_equals(pixel, [0, 128, 0, 255], "Cross origin box-reflect mask should not mask out right side of reflection");
+      });
+    };
+  </script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/resources/left-half-mask-50.svg b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/resources/left-half-mask-50.svg
new file mode 100644
index 0000000..4268264
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/resources/left-half-mask-50.svg
@@ -0,0 +1,4 @@
+<svg xmlns='http://www.w3.org/2000/svg' viewBox="0 0 100 100">
+  <rect width='50%' height='100%' fill='black'/>
+  <!-- The right half is left transparent. -->
+</svg>
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/box-reflect-images-ignored.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/box-reflect-images-ignored.https.sub.html
new file mode 100644
index 0000000..be75e3c
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/box-reflect-images-ignored.https.sub.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>drawElementImage does not draw cross-origin box-reflect images</title>
+  <link rel="help" href="https://github.com/WICG/html-in-canvas">
+  <script src='/resources/testharness.js'></script>
+  <script src='/resources/testharnessreport.js'></script>
+  <style>
+  #child {
+    width: 200px;
+    height: 200px;
+    background: blue;
+    position: relative;
+  }
+  #sameOrigin {
+    position: absolute;
+    left: 0px;
+    top: 0px;
+    width: 100px;
+    height: 100px;
+    background:green;
+    -webkit-box-reflect: below 0 url("https://{{location[host]}}/wpt_internal/html/canvas/drawElementImage/resources/left-half-mask-50.svg");
+  }
+  #crossOrigin {
+    position: absolute;
+    left: 100px;
+    top: 0px;
+    width: 100px;
+    height: 100px;
+    background: green;
+    -webkit-box-reflect: below 0 url("https://{{hosts[alt][www]}}:{{ports[h2][0]}}/wpt_internal/html/canvas/drawElementImage/resources/left-half-mask-50.svg");
+  }
+  </style>
+</head>
+<body>
+  <canvas id="canvas" width="200" height="200" layoutsubtree>
+    <!--
+      #child renders a 200x200 blue rect. The left side #sameOrigin and its
+      reflection. The right side is #crossOrigin and its reflection.
+    -->
+    <div id="child">
+      <div id="sameOrigin"></div>
+      <div id="crossOrigin"></div>
+    </div>
+  </canvas>
+
+  <script>
+    window.onload = () => {
+      promise_test(async function(t) {
+        await new Promise(requestAnimationFrame);
+        await new Promise(setTimeout);
+        var context = canvas.getContext("2d");
+        context.drawElementImage(child, 0, 0);
+
+        // Fetch all pixel data once to avoid multiple slow readbacks.
+        const imgData = context.getImageData(0, 0, canvas.width, canvas.height).data;
+
+        // Helper function to extract a pixel's RGBA array at (x, y).
+        const getPixel = (x, y) => {
+          const index = (y * canvas.width + x) * 4;
+          return [
+            imgData[index],
+            imgData[index + 1],
+            imgData[index + 2],
+            imgData[index + 3]
+          ];
+        };
+
+        let pixel = getPixel(50, 50);
+        assert_array_equals(pixel, [0, 128, 0, 255], "Same origin div should draw");
+        pixel = getPixel(25, 150);
+        assert_array_equals(pixel, [0, 128, 0, 255], "Same origin box-reflect mask should not mask out left side of reflection");
+        pixel = getPixel(75, 150);
+        assert_array_equals(pixel, [0, 0, 255, 255], "Same origin box-reflect mask should mask out right side of reflection");
+
+        pixel = getPixel(150, 50);
+        assert_array_equals(pixel, [0, 128, 0, 255], "Cross origin div should draw");
+        pixel = getPixel(125, 150);
+        assert_array_equals(pixel, [0, 128, 0, 255], "Cross origin box-reflect mask should not mask out left side of reflection");
+        pixel = getPixel(175, 150);
+        assert_array_equals(pixel, [0, 128, 0, 255], "Cross origin box-reflect mask should not mask out right side of reflection");
+      });
+    };
+  </script>
+</body>
+</html>
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/resources/left-half-mask-50.svg b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/resources/left-half-mask-50.svg
new file mode 100644
index 0000000..4268264
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/resources/left-half-mask-50.svg
@@ -0,0 +1,4 @@
+<svg xmlns='http://www.w3.org/2000/svg' viewBox="0 0 100 100">
+  <rect width='50%' height='100%' fill='black'/>
+  <!-- The right half is left transparent. -->
+</svg>
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.