Chrome · Paint
CVE-2026-11132
Logic Error in Paint
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifthird_party/blink/renderer/core/layout/svg/layout_svg_resource_pattern.cc |
modified | |
ifthird_party/blink/renderer/core/paint/svg_object_painter.cc |
modified |
Files Changed
third_party/blink/renderer/core/layout/svg/layout_svg_resource_gradient.ccthird_party/blink/renderer/core/layout/svg/layout_svg_resource_gradient.hthird_party/blink/renderer/core/layout/svg/layout_svg_resource_paint_server.hthird_party/blink/renderer/core/layout/svg/layout_svg_resource_pattern.ccthird_party/blink/renderer/core/layout/svg/layout_svg_resource_pattern.hthird_party/blink/renderer/core/paint/svg_object_painter.cc
Patch
From 38e22bf93eb317b9aec320701f0f401ee760c2fb Mon Sep 17 00:00:00 2001 From: Philip Rogers <[email protected]> Date: Mon, 27 Apr 2026 07:16:58 -0700 Subject: [PATCH] [html-in-canvas] Do not paint cross-origin SVG patterns This patch plumbs PaintFlags through SVG resource painting to ensure cross-origin images within SVG patterns are blocked when drawing with privacy-preserving painting. Fixed: 501597365 Change-Id: I8bd52f1f64a074ca6bcb8031a1e9948c42d7517c Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7794467 Reviewed-by: Stephen Chenney <[email protected]> Auto-Submit: Philip Rogers <[email protected]> Commit-Queue: Stephen Chenney <[email protected]> Cr-Commit-Position: refs/heads/main@{#1621069} --- diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_resource_gradient.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_resource_gradient.cc index 18ca8c15..028d430 100644 --- a/third_party/blink/renderer/core/layout/svg/layout_svg_resource_gradient.cc +++ b/third_party/blink/renderer/core/layout/svg/layout_svg_resource_gradient.cc @@ -147,7 +147,8 @@ const gfx::RectF& reference_box, const AffineTransform* additional_transform, const AutoDarkMode& auto_dark_mode, - cc::PaintFlags& flags) { + cc::PaintFlags& flags, + PaintFlags /* paint_flags */) { NOT_DESTROYED(); ClearInvalidationMask(); diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_resource_gradient.h b/third_party/blink/renderer/core/layout/svg/layout_svg_resource_gradient.h index dfc7cd5..d154d1a 100644 --- a/third_party/blink/renderer/core/layout/svg/layout_svg_resource_gradient.h +++ b/third_party/blink/renderer/core/layout/svg/layout_svg_resource_gradient.h @@ -47,7 +47,8 @@ const gfx::RectF& reference_box, const AffineTransform* additional_transform, const AutoDarkMode& auto_dark_mode, - cc::PaintFlags&) final; + cc::PaintFlags& flags, + PaintFlags paint_flags) final; bool IsChildAllowed(LayoutObject* child, const ComputedStyle&) const final; diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_resource_paint_server.h b/third_party/blink/renderer/core/layout/svg/layout_svg_resource_paint_server.h index 069de534..ba84b0b 100644 --- a/third_party/blink/renderer/core/layout/svg/layout_svg_resource_paint_server.h +++ b/third_party/blink/renderer/core/layout/svg/layout_svg_resource_paint_server.h @@ -22,6 +22,7 @@ #include "cc/paint/paint_flags.h" #include "third_party/blink/renderer/core/layout/svg/layout_svg_resource_container.h" +#include "third_party/blink/renderer/core/paint/paint_flags.h" namespace blink { @@ -36,7 +37,8 @@ const gfx::RectF& reference_box, const AffineTransform* additional_transform, const AutoDarkMode& auto_dark_mode, - cc::PaintFlags&) = 0; + cc::PaintFlags&, + PaintFlags paint_flags) = 0; protected: void StyleDidChange(StyleDifference, diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_resource_pattern.cc b/third_party/blink/renderer/core/layout/svg/layout_svg_resource_pattern.cc index 90c2de36..85e5ccb1 100644 --- a/third_party/blink/renderer/core/layout/svg/layout_svg_resource_pattern.cc +++ b/third_party/blink/renderer/core/layout/svg/layout_svg_resource_pattern.cc @@ -47,6 +47,7 @@ public: std::unique_ptr<Pattern> pattern; AffineTransform transform; + PaintFlags paint_flags = PaintFlag::kNoFlag; }; LayoutSVGResourcePattern::LayoutSVGResourcePattern(SVGPatternElement* node) @@ -123,7 +124,8 @@ } std::unique_ptr<PatternData> LayoutSVGResourcePattern::BuildPatternData( - const gfx::RectF& object_bounding_box) { + const gfx::RectF& object_bounding_box, + PaintFlags paint_flags) { NOT_DESTROYED(); auto pattern_data = std::make_unique<PatternData>(); @@ -170,7 +172,8 @@ } pattern_data->pattern = Pattern::CreatePaintRecordPattern( - AsPaintRecord(tile_transform), gfx::RectF(tile_bounds.size())); + AsPaintRecord(tile_transform, paint_flags), + gfx::RectF(tile_bounds.size())); // Compute pattern space transformation. pattern_data->transform.Translate(tile_bounds.x(), tile_bounds.y()); @@ -184,14 +187,21 @@ const gfx::RectF& reference_box, const AffineTransform* additional_transform, const AutoDarkMode&, - cc::PaintFlags& flags) { + cc::PaintFlags& flags, + PaintFlags paint_flags) { NOT_DESTROYED(); ClearInvalidationMask(); std::unique_ptr<PatternData>& pattern_data = pattern_map_.insert(&client, nullptr).stored_value->value; - if (!pattern_data) - pattern_data = BuildPatternData(reference_box); + if (pattern_data && pattern_data->paint_flags != paint_flags) { + pattern_data.reset(); + } + + if (!pattern_data) { + pattern_data = BuildPatternData(reference_box, paint_flags); + pattern_data->paint_flags = paint_flags; + } if (!pattern_data->pattern) return false; @@ -205,7 +215,8 @@ } PaintRecord LayoutSVGResourcePattern::AsPaintRecord( - const AffineTransform& tile_transform) const { + const AffineTransform& tile_transform, + PaintFlags paint_flags) const { NOT_DESTROYED(); DCHECK(!should_collect_pattern_attributes_); @@ -231,7 +242,8 @@ PaintRecordBuilder builder; for (LayoutObject* child = pattern_layout_object->FirstChild(); child; child = child->NextSibling()) { - SVGObjectPainter(*child, nullptr).PaintResourceSubtree(builder.Context()); + SVGObjectPainter(*child, nullptr) + .PaintResourceSubtree(builder.Context(), paint_flags); } canvas->save(); canvas->concat(tile_transform.ToSkM44()); diff --git a/third_party/blink/renderer/core/layout/svg/layout_svg_resource_pattern.h b/third_party/blink/renderer/core/layout/svg/layout_svg_resource_pattern.h index 058a7e1..fc73f1c 100644 --- a/third_party/blink/renderer/core/layout/svg/layout_svg_resource_pattern.h +++ b/third_party/blink/renderer/core/layout/svg/layout_svg_resource_pattern.h @@ -50,8 +50,9 @@ bool ApplyShader(const SVGResourceClient&, const gfx::RectF& reference_box, const AffineTransform* additional_transform, - const AutoDarkMode&, - cc::PaintFlags&) override; + const AutoDarkMode& auto_dark_mode, + cc::PaintFlags& flags, + PaintFlags paint_flags) override; static const LayoutSVGResourceType kResourceType = kPatternResourceType; LayoutSVGResourceType ResourceType() const override { @@ -67,8 +68,10 @@ bool FindCycleFromSelf() const override; std::unique_ptr<PatternData> BuildPatternData( - const gfx::RectF& object_bounding_box); - PaintRecord AsPaintRecord(const AffineTransform&) const; + const gfx::RectF& object_bounding_box, + PaintFlags paint_flags); + PaintRecord AsPaintRecord(const AffineTransform&, + PaintFlags paint_flags) const; mutable bool should_collect_pattern_attributes_ : 1; mutable PatternAttributes attributes_; diff --git a/third_party/blink/renderer/core/paint/svg_object_painter.cc b/third_party/blink/renderer/core/paint/svg_object_painter.cc index 972d79a..35863f4 100644 --- a/third_party/blink/renderer/core/paint/svg_object_painter.cc +++ b/third_party/blink/renderer/core/paint/svg_object_painter.cc @@ -18,7 +18,8 @@ bool ApplyPaintResource( const SvgContextPaints::ContextPaint& context_paint, const AffineTransform* additional_paint_server_transform, - cc::PaintFlags& flags) { + cc::PaintFlags& flags, + PaintFlags paint_flags) { SVGElementResourceClient* client = SVGResources::GetClient(context_paint.object); if (!client) { @@ -34,7 +35,8 @@ context_paint.object.StyleRef(), DarkModeFilter::ElementRole::kSVG)); if (!uri_resource->ApplyShader( *client, SVGResources::ReferenceBoxForEffects(context_paint.object), - additional_paint_server_transform, auto_dark_mode, flags)) { + additional_paint_server_transform, auto_dark_mode, flags, + paint_flags)) { return false; } return true; @@ -151,7 +153,8 @@ std::optional<AffineTransform> resolved_transform = ResolveContextTransform(
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/svg-pattern-cross-origin-image.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/svg-pattern-cross-origin-image.https.sub.html
new file mode 100644
index 0000000..074cce88
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/svg-pattern-cross-origin-image.https.sub.html
@@ -0,0 +1,71 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>drawElementImage does not use cross-origin images in SVG patterns</title>
+ <link rel="help" href="https://github.com/WICG/html-in-canvas">
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+</head>
+<body>
+ <canvas id="canvas" width="100" height="100" layoutsubtree>
+ <svg width="100" height="100">
+ <defs>
+ <pattern id="pattern-same" patternUnits="userSpaceOnUse" x="0" y="0" width="100" height="50">
+ <image href="https://{{location[host]}}/wpt_internal/html/canvas/drawElementImage/resources/green-100x100.png" x="0" y="0" width="100" height="50" preserveAspectRatio="none"/>
+ </pattern>
+ <pattern id="pattern-cross" patternUnits="userSpaceOnUse" x="0" y="0" width="100" height="50">
+ <image href="https://{{hosts[alt][www]}}:{{ports[h2][0]}}/wpt_internal/html/canvas/drawElementImage/resources/red-100x100.png" x="0" y="0" width="100" height="50" preserveAspectRatio="none"/>
+ </pattern>
+ </defs>
+ <rect x="0" y="0" width="100" height="50" fill="url(#pattern-same)"/>
+ <rect x="0" y="50" width="100" height="50" fill="url(#pattern-cross)"/>
+ </svg>
+ </canvas>
+
+ <script>
+ window.onload = () => {
+ promise_test(async function(t) {
+ const preloadImage = (url) => new Promise((resolve, reject) => {
+ const img = new Image();
+ img.onload = () => resolve(img);
+ img.onerror = () => reject(new Error(`Failed to load image: ${url}`));
+ img.src = url;
+ });
+
+ const sameOriginImageEl = document.querySelector('#pattern-same image');
+ const crossOriginImageEl = document.querySelector('#pattern-cross image');
+ await Promise.all([
+ preloadImage(sameOriginImageEl.getAttribute('href')),
+ preloadImage(crossOriginImageEl.getAttribute('href'))
+ ]);
+
+ await new Promise(requestAnimationFrame);
+ await new Promise(setTimeout);
+ var ctx = canvas.getContext('2d');
+ ctx.fillStyle = 'blue';
+ ctx.fillRect(0, 0, 100, 100);
+ ctx.drawElementImage(document.querySelector('svg'), 0, 0);
+
+ // Fetch all pixel data once to avoid multiple slow readbacks.
+ const imgData = ctx.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, 25);
+ assert_array_equals(pixel, [0, 255, 0, 255], "Same origin pattern should draw");
+ pixel = getPixel(50, 75);
+ assert_array_equals(pixel, [0, 0, 255, 255], "Cross origin pattern should not draw");
+ });
+ }
+ </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