High chrome Logic Error 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient policy enforcement in SVG
DescriptionInsufficient policy enforcement in SVG
ComponentSVG
Bug ClassLogic Error
Tracker510829679
Fix commit4855238b10f2 (chromium/src) +90/-19
CISA KEVNot listed
Creditedpakhunov.anton.n@gmail.com
Disclosed2026-06-30

Changed Functions

FunctionChangeNotes
if
third_party/blink/renderer/core/layout/svg/layout_svg_resource_masker.cc
modified
if
third_party/blink/renderer/core/paint/paint_layer_painter.cc
modified
if
third_party/blink/renderer/core/paint/scoped_svg_paint_state.cc
modified
if
third_party/blink/renderer/core/paint/svg_mask_painter.cc
modified

Files Changed

  • third_party/blink/renderer/core/layout/svg/layout_svg_resource_masker.cc
  • third_party/blink/renderer/core/layout/svg/layout_svg_resource_masker.h
  • third_party/blink/renderer/core/paint/box_painter_base.cc
  • third_party/blink/renderer/core/paint/paint_layer_painter.cc
  • third_party/blink/renderer/core/paint/scoped_svg_paint_state.cc
  • third_party/blink/renderer/core/paint/svg_mask_painter.cc
  • third_party/blink/renderer/core/paint/svg_mask_painter.h
From 4855238b10f2054c2d258cd8144237f2d93bd634 Mon Sep 17 00:00:00 2001
From: Philip Rogers <pdr@chromium.org>
Date: Wed, 13 May 2026 14:48:37 -0700
Subject: [PATCH] [html-in-canvas] Fix cross-origin image leak in SVG masks

This CL fixes a privacy leak where cross-origin images used inside
<mask> elements were incorrectly rendered into <canvas>.

Fixed: 510829679
Bug: 512516331
Change-Id: Ibaed202157c13fcf42cb01b71fa92140d872a1ed
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7841426
Reviewed-by: Stefan Zager <szager@chromium.org>
Commit-Queue: Philip Rogers <pdr@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1630234}
---

diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_resource_masker.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_resource_masker.cc
index d3a8014..4394fcb 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_resource_masker.cc
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_resource_masker.cc
@@ -44,8 +44,12 @@
                                 kPaintInvalidation);
 }
 
-PaintRecord LayoutSVGResourceMasker::CreatePaintRecord() {
+PaintRecord LayoutSVGResourceMasker::CreatePaintRecord(PaintFlags paint_flags) {
   NOT_DESTROYED();
+  if (cached_paint_record_ && cached_paint_flags_ != paint_flags) {
+    cached_paint_record_ = std::nullopt;
+  }
+
   if (cached_paint_record_)
     return *cached_paint_record_;
 
@@ -59,10 +63,12 @@
         layout_object->StyleRef().Display() == EDisplay::kNone)
       continue;
     SVGObjectPainter(*layout_object, nullptr)
-        .PaintResourceSubtree(builder.Context(), PaintFlag::kPaintingSVGMask);
+        .PaintResourceSubtree(builder.Context(),
+                              PaintFlag::kPaintingSVGMask | paint_flags);
   }
 
   cached_paint_record_ = builder.EndRecording();
+  cached_paint_flags_ = paint_flags;
   return *cached_paint_record_;
 }
 
diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_resource_masker.h b/third_party/blink/renderer/core/layout/svg/layout_svg_resource_masker.h
index e100362..bc2a07bb 100644
--- a/third_party/blink/renderer/core/layout/svg/layout_svg_resource_masker.h
+++ b/third_party/blink/renderer/core/layout/svg/layout_svg_resource_masker.h
@@ -23,6 +23,7 @@
 #include <optional>
 
 #include "third_party/blink/renderer/core/layout/svg/layout_svg_resource_container.h"
+#include "third_party/blink/renderer/core/paint/paint_flags.h"
 #include "third_party/blink/renderer/core/svg/svg_unit_types.h"
 #include "ui/gfx/geometry/rect_f.h"
 
@@ -54,10 +55,11 @@
     return kResourceType;
   }
 
-  PaintRecord CreatePaintRecord();
+  PaintRecord CreatePaintRecord(PaintFlags paint_flags);
 
  private:
   std::optional<PaintRecord> cached_paint_record_;
+  PaintFlags cached_paint_flags_ = PaintFlag::kNoFlag;
 };
 
 template <>
diff --git a/third_party/blink/renderer/core/paint/box_painter_base.cc b/third_party/blink/renderer/core/paint/box_painter_base.cc
index 1bd91a17..2802e009 100644
--- a/third_party/blink/renderer/core/paint/box_painter_base.cc
+++ b/third_party/blink/renderer/core/paint/box_painter_base.cc
@@ -1421,7 +1421,8 @@
         SVGMaskPainter::PaintSVGMaskLayer(
             context, *mask_source, bg_paint_context.ImageClient(),
             reference_box, zoom, composite_op,
-            bg_layer.MaskMode() == EFillMaskMode::kMatchSource);
+            bg_layer.MaskMode() == EFillMaskMode::kMatchSource,
+            paint_info.GetPaintFlags());
         return;
       }
     }
diff --git a/third_party/blink/renderer/core/paint/paint_layer_painter.cc b/third_party/blink/renderer/core/paint/paint_layer_painter.cc
index eb1c45b..afd6f1e 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_painter.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_painter.cc
@@ -503,7 +503,7 @@
     if (should_paint_content && !selection_drag_image_only) {
       if (properties->Mask()) {
         if (object.IsSVGForeignObject()) {
-          SVGMaskPainter::Paint(context, object, object);
+          SVGMaskPainter::Paint(context, object, object, paint_flags);
         } else {
           PaintWithPhase(PaintPhase::kMask, context, paint_flags);
         }
diff --git a/third_party/blink/renderer/core/paint/scoped_svg_paint_state.cc b/third_party/blink/renderer/core/paint/scoped_svg_paint_state.cc
index 1a25ea57..7d92df1 100644
--- a/third_party/blink/renderer/core/paint/scoped_svg_paint_state.cc
+++ b/third_party/blink/renderer/core/paint/scoped_svg_paint_state.cc
@@ -87,8 +87,10 @@
   // the node hierarchy), to ensure the clip-path mask will be applied to the
   // mask to create an intersection of the masks, then the intersection will be
   // applied to the masked content.
-  if (should_paint_mask_)
-    SVGMaskPainter::Paint(paint_info_.context, object_, display_item_client_);
+  if (should_paint_mask_) {
+    SVGMaskPainter::Paint(paint_info_.context, object_, display_item_client_,
+                          paint_info_.GetPaintFlags());
+  }
 
   if (should_paint_clip_path_as_mask_image_) {
     ClipPathClipper::PaintClipPathAsMaskImage(paint_info_.context, object_,
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 23c6c88..0f136f8 100644
--- a/third_party/blink/renderer/core/paint/svg_mask_painter.cc
+++ b/third_party/blink/renderer/core/paint/svg_mask_painter.cc
@@ -95,7 +95,8 @@
 void PaintMaskLayer(const FillLayer& layer,
                     const LayoutObject& object,
                     const SVGBackgroundPaintContext& bg_paint_context,
-                    GraphicsContext& context) {
+                    GraphicsContext& context,
+                    PaintFlags paint_flags) {
   const StyleImage* style_image = layer.GetImage();
   if (!style_image) {
     return;
@@ -131,7 +132,7 @@
     saver.Save();
     SVGMaskPainter::PaintSVGMaskLayer(
         context, *mask_source, observer, reference_box, zoom, composite_op,
-        layer.MaskMode() == EFillMaskMode::kMatchSource);
+        layer.MaskMode() == EFillMaskMode::kMatchSource, paint_flags);
     return;
   }
 
@@ -237,7 +238,8 @@
 
 void SVGMaskPainter::Paint(GraphicsContext& context,
                            const LayoutObject& layout_object,
-                           const DisplayItemClient& display_item_client) {
+                           const DisplayItemClient& display_item_client,
+                           PaintFlags paint_flags) {
   const auto* properties = layout_object.FirstFragment().PaintProperties();
   DCHECK(properties);
   DCHECK(properties->Mask());
@@ -259,11 +261,13 @@
                            gfx::ToEnclosingRect(visual_rect));
 
   const SVGBackgroundPaintContext bg_paint_context(layout_object);
-  IterateFillLayersReveresed(
-      &layout_object.StyleRef().MaskLayers(),
-      [&layout_object, &bg_paint_context, &context](const FillLayer& layer) {
-        PaintMaskLayer(layer, layout_object, bg_paint_context, context);
-      });
+  IterateFillLayersReveresed(&layout_object.StyleRef().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,
@@ -272,7 +276,8 @@
                                        const gfx::RectF& reference_box,
                                        const float zoom,
                                        const SkBlendMode composite_op,
-                                       const bool apply_mask_type) {
+                                       const bool apply_mask_type,
+                                       PaintFlags paint_flags) {
   LayoutSVGResourceMasker* masker =
       ResolveElementReference(mask_source, observer);
   if (!masker) {
@@ -281,7 +286,7 @@
   const AffineTransform content_transformation =
       MaskToContentTransform(*masker, reference_box, zoom);
   SubtreeContentTransformScope content_transform_scope(content_transformation);
-  PaintRecord record = masker->CreatePaintRecord();
+  PaintRecord record = masker->CreatePaintRecord(paint_flags);
 
   context.Clip(masker->ResourceBoundingBox(reference_box, zoom));
 
diff --git a/third_party/blink/renderer/core/paint/svg_mask_painter.h b/third_party/blink/renderer/core/paint/svg_mask_painter.h
index 9512656..b0647d3 100644
--- a/third_party/blink/renderer/core/paint/svg_mask_painter.h
+++ b/third_party/blink/renderer/core/paint/svg_mask_painter.h
@@ -5,6 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_SVG_MASK_PAINTER_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_PAINT_SVG_MASK_PAINTER_H_
 
+#include "third_party/blink/renderer/core/paint/paint_flags.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/skia/include/core/SkBlendMode.h"
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/svg-mask-image-ignored.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/svg-mask-image-ignored.https.sub.html
new file mode 100644
index 0000000..eaafd01
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/svg-mask-image-ignored.https.sub.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>drawElementImage does not use cross-origin images in SVG masks</title>
+  <link rel="help" href="https://github.com/WICG/html-in-canvas">
+  <script src='/resources/testharness.js'></script>
+  <script src='/resources/testharnessreport.js'></script>
+  <script src='../support/getPixel.js'></script>
+  <script src="/wpt_internal/resources/canvas-draw-element/waitForCanvasPaint.js"></script>
+</head>
+<body>
+  <canvas id="canvas" width="100" height="200" layoutsubtree>
+    <svg width="100" height="200">
+      <defs>
+        <mask id="mask-same" maskContentUnits="objectBoundingBox">
+          <image href="https://{{location[host]}}/wpt_internal/html/canvas/drawElementImage/resources/blue-100x50-transparent-100x50.png" x="0" y="0" width="1" height="1" preserveAspectRatio="none" />
+        </mask>
+        <mask id="mask-cross" maskContentUnits="objectBoundingBox">
+          <image href="https://{{hosts[alt][www]}}:{{ports[https][0]}}/wpt_internal/html/canvas/drawElementImage/resources/blue-100x50-transparent-100x50.png" x="0" y="0" width="1" height="1" preserveAspectRatio="none" />
+        </mask>
+      </defs>
+      <rect x="0" y="0" width="100" height="100" fill="blue" mask="url(#mask-same)"/>
+      <rect x="0" y="100" width="100" height="100" fill="blue" mask="url(#mask-cross)"/>
+    </svg>
+  </canvas>
+
+  <script>
+    window.onload = () => {
+      promise_test(async function(t) {
+        await waitForCanvasPaint(canvas);
+        var ctx = canvas.getContext('2d');
+        ctx.drawElementImage(document.querySelector('canvas > svg'), 0, 0);
+
+        // Fetch all pixel data once to avoid multiple slow readbacks.
+        const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
+
+        let pixel = getPixel(imgData, 50, 25);
+        assert_array_equals(pixel, [0, 0, 0, 0], "Same origin mask should fully mask top-half of rect");
+
+        pixel = getPixel(imgData, 50, 75);
+        assert_array_equals(pixel, [0, 0, 255, 18], "Same origin mask should not mask bottom-half of rect");
+
+        pixel = getPixel(imgData, 50, 125);
+        assert_array_equals(pixel, [0, 0, 0, 0], "Cross origin mask data should not affect top-half of rect");
+
+        pixel = getPixel(imgData, 50, 175);
+        assert_array_equals(pixel, [0, 0, 0, 0], "Cross origin mask data should not affect bottom-half of rect");
+      });
+    }
+  </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.