High chrome UAF 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Paint
DescriptionUse after free in Paint
ComponentPaint
Sub-componentHiC
Bug ClassUAF
Tracker572695599
Fix commit73c78a52d2b7 (chromium/src) +67/-9
CISA KEVNot listed
CreditedArni Hardarson (Neonix Security)
Disclosed2026-10-20

Changed Functions

FunctionChangeNotes
if
third_party/blink/renderer/core/paint/svg_mask_painter.cc
modified
promise_test
third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg/svg-mask-on-svg-content-ignored.tentative.https.sub.html
modified

Files Changed

  • third_party/blink/renderer/core/paint/svg_mask_painter.cc
  • third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg/svg-mask-on-svg-content-ignored.tentative.https.sub.html
From 73c78a52d2b79c5b9d1b48f1c09f97c036ec384e Mon Sep 17 00:00:00 2001
From: Stephen Chenney <schenney@chromium.org>
Date: Wed, 14 Oct 2026 10:21:23 -0700
Subject: [PATCH] [HiC] Disallow SVG masks on SVG content in readback-allowed mode

We were missing a check for readback-allowed in SVGMaskPainter,
used to paint masks on SVG content.

Fixed: 572695599


CHECK on privacy

Change-Id: I1a2d07b756f5c6a5d9fe1fb5f2997fa7bf512e6d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8580323
Commit-Queue: Stephen Chenney <schenney@chromium.org>
Commit-Queue: Stefan Zager <szager@chromium.org>
Reviewed-by: Stefan Zager <szager@chromium.org>
Auto-Submit: Stephen Chenney <schenney@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1718722}
---
diff --git a/third_party/blink/renderer/core/paint/svg_mask_painter.cc b/third_party/blink/renderer/core/paint/svg_mask_painter.cc
index 6d9bb5d..22387f6 100644
--- a/third_party/blink/renderer/core/paint/svg_mask_painter.cc
+++ b/third_party/blink/renderer/core/paint/svg_mask_painter.cc
@@ -102,6 +102,11 @@
     return;
   }
 
+  if ((paint_flags & PaintFlag::kReadBackAllowedRendering) &&
+      !style_image->IsCorsSameOrigin()) {
+    return;
+  }
+
   std::optional<ScopedMaskLuminanceLayer> mask_luminance_scope;
   SkBlendMode composite_op = SkBlendMode::kSrcOver;
   // Don't use the operator if this is the bottom layer.
@@ -227,11 +232,11 @@
 }
 
 template <typename Callback>
-void IterateFillLayersReveresed(const FillLayer* layer, Callback callback) {
+void IterateFillLayersReversed(const FillLayer* layer, Callback callback) {
   if (!layer) {
     return;
   }
-  IterateFillLayersReveresed(layer->NextForUsedValue(), callback);
+  IterateFillLayersReversed(layer->NextForUsedValue(), callback);
   callback(*layer);
 }
 }  // namespace
@@ -261,13 +266,13 @@
                            gfx::ToEnclosingRect(visual_rect));
 
   const SVGBackgroundPaintContext bg_paint_context(layout_object);
-  IterateFillLayersReveresed(&layout_object.Style().MaskLayers(),
-                             [&layout_object, &bg_paint_context, &context,
-                              paint_flags](const FillLayer& layer) {
-                               PaintMaskLayer(layer, layout_object,
-                                              bg_paint_context, context,
-                                              paint_flags);
-                             });
+  IterateFillLayersReversed(&layout_object.Style().MaskLayers(),
+                            [&layout_object, &bg_paint_context, &context,
+                             paint_flags](const FillLayer& layer) {
+                              PaintMaskLayer(layer, layout_object,
+                                             bg_paint_context, context,
+                                             paint_flags);
+                            });
 }
 
 void SVGMaskPainter::PaintSVGMaskLayer(GraphicsContext& context,
diff --git a/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg/svg-mask-on-svg-content-ignored.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg/svg-mask-on-svg-content-ignored.tentative.https.sub.html
new file mode 100644
index 0000000..bf210f6
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg/svg-mask-on-svg-content-ignored.tentative.https.sub.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>drawElementImage does not use cross-origin mask images on SVG content</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>
+  <style>
+  #child {
+    width: 100px;
+    height: 200px;
+    background: green;
+  }
+  </style>
+</head>
+<body>
+  <canvas id=canvas width=100 height=200 content=drawable>
+    <svg drawable id=child width=100px height=200px>
+      <rect id=sameOrigin x=0 y=0 width=100 height=100 fill="rgb(0, 0, 255)" style="mask-image: url('https://{{location[host]}}/images/blue-100x50-transparent-100x50.svg');"/>
+      <rect id=crossOrigin x=0 y=100 width=100 height=100 fill="rgb(0, 0, 255)" style="mask-image: url('https://{{hosts[alt][www]}}:{{ports[https][0]}}/images/blue-100x50-transparent-100x50.svg')"/>
+    </svg>
+  </canvas>
+
+  <script>
+    window.onload = () => {
+      promise_test(async function(t) {
+        await waitForCanvasPaint(canvas);
+        const context = canvas.getContext("2d");
+        context.fillStyle = 'red';
+        context.fillRect(0, 100, 100, 100);
+        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);
+
+        let pixel = _getPixelFromImageData(imgData, 50, 25);
+        assert_array_equals(pixel, [0, 128, 0, 255], "Same origin should mask");
+
+        pixel = _getPixelFromImageData(imgData, 50, 75);
+        assert_array_equals(pixel, [0, 0, 255, 255], "Same origin should mask");
+
+        // Missing mask image renders as black, blocking the red background
+        pixel = _getPixelFromImageData(imgData, 50, 125);
+        assert_array_equals(pixel, [0, 0, 255, 255], "Cross origin should not mask");
+
+        pixel = _getPixelFromImageData(imgData, 50, 175);
+        assert_array_equals(pixel, [0, 0, 255, 255], "Cross origin should not mask");
+      });
+    }
+  </script>
+</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/svg/svg-mask-on-svg-content-ignored.tentative.https.sub.html b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg/svg-mask-on-svg-content-ignored.tentative.https.sub.html
new file mode 100644
index 0000000..bf210f6
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/canvas/element/manual/draw-element-image/privacy/svg/svg-mask-on-svg-content-ignored.tentative.https.sub.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>drawElementImage does not use cross-origin mask images on SVG content</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>
+  <style>
+  #child {
+    width: 100px;
+    height: 200px;
+    background: green;
+  }
+  </style>
+</head>
+<body>
+  <canvas id=canvas width=100 height=200 content=drawable>
+    <svg drawable id=child width=100px height=200px>
+      <rect id=sameOrigin x=0 y=0 width=100 height=100 fill="rgb(0, 0, 255)" style="mask-image: url('https://{{location[host]}}/images/blue-100x50-transparent-100x50.svg');"/>
+      <rect id=crossOrigin x=0 y=100 width=100 height=100 fill="rgb(0, 0, 255)" style="mask-image: url('https://{{hosts[alt][www]}}:{{ports[https][0]}}/images/blue-100x50-transparent-100x50.svg')"/>
+    </svg>
+  </canvas>
+
+  <script>
+    window.onload = () => {
+      promise_test(async function(t) {
+        await waitForCanvasPaint(canvas);
+        const context = canvas.getContext("2d");
+        context.fillStyle = 'red';
+        context.fillRect(0, 100, 100, 100);
+        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);
+
+        let pixel = _getPixelFromImageData(imgData, 50, 25);
+        assert_array_equals(pixel, [0, 128, 0, 255], "Same origin should mask");
+
+        pixel = _getPixelFromImageData(imgData, 50, 75);
+        assert_array_equals(pixel, [0, 0, 255, 255], "Same origin should mask");
+
+        // Missing mask image renders as black, blocking the red background
+        pixel = _getPixelFromImageData(imgData, 50, 125);
+        assert_array_equals(pixel, [0, 0, 255, 255], "Cross origin should not mask");
+
+        pixel = _getPixelFromImageData(imgData, 50, 175);
+        assert_array_equals(pixel, [0, 0, 255, 255], "Cross origin should not mask");
+      });
+    }
+  </script>
+</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.