Chrome · Paint › HiC
CVE-2026-113179
UAF in Paint (HiC)
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifthird_party/blink/renderer/core/paint/svg_mask_painter.cc |
modified | |
promise_testthird_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.ccthird_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
Patch
From 73c78a52d2b79c5b9d1b48f1c09f97c036ec384e Mon Sep 17 00:00:00 2001 From: Stephen Chenney <[email protected]> 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 <[email protected]> Commit-Queue: Stefan Zager <[email protected]> Reviewed-by: Stefan Zager <[email protected]> Auto-Submit: Stephen Chenney <[email protected]> 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.
References
On This Page