Chrome · Paint
CVE-2026-11142
Logic Error in Paint
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ComputedStylethird_party/blink/renderer/core/layout/custom_scrollbar.h |
modified | |
GraphicsContextthird_party/blink/renderer/core/layout/custom_scrollbar.h |
modified | |
LayoutObjectthird_party/blink/renderer/core/layout/custom_scrollbar.h |
modified | |
LayoutCustomScrollbarPartthird_party/blink/renderer/core/layout/custom_scrollbar.h |
modified |
Files Changed
third_party/blink/renderer/core/layout/custom_scrollbar.ccthird_party/blink/renderer/core/layout/custom_scrollbar.hthird_party/blink/renderer/core/paint/custom_scrollbar_theme.ccthird_party/blink/renderer/core/paint/custom_scrollbar_theme.h
Patch
From 64cb2a5247df3cd2be4000ac24e46ff22f8bec4a Mon Sep 17 00:00:00 2001 From: Philip Rogers <[email protected]> Date: Mon, 13 Apr 2026 12:24:14 -0700 Subject: [PATCH] [html-in-canvas] Preserve privacy when drawing scrollbars When drawing custom scrollbars, `CustomScrollbarTheme::PaintIntoRect` would create a new PaintInfo that did not respect the current privacy preserving flag, and this would allow for the painting of cross-origin images in scrollbars. This patch plumbs the PaintInfo with the privacy preserving flag from ScrollableAreaPainter. Fixed: 501668745 Change-Id: I7349a0bbee3e34147a8fb5e95063eef07abb1f52 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7751662 Reviewed-by: Stephen Chenney <[email protected]> Commit-Queue: Philip Rogers <[email protected]> Cr-Commit-Position: refs/heads/main@{#1613898} --- diff --git a/third_party/blink/renderer/core/layout/custom_scrollbar.cc b/third_party/blink/renderer/core/layout/custom_scrollbar.cc index be663ed..494bdd3 100644 --- a/third_party/blink/renderer/core/layout/custom_scrollbar.cc +++ b/third_party/blink/renderer/core/layout/custom_scrollbar.cc @@ -34,6 +34,7 @@ #include "third_party/blink/renderer/core/layout/layout_view.h" #include "third_party/blink/renderer/core/paint/custom_scrollbar_theme.h" #include "third_party/blink/renderer/core/paint/object_paint_invalidator.h" +#include "third_party/blink/renderer/core/paint/paint_info.h" #include "third_party/blink/renderer/core/scroll/scroll_types.h" #include "third_party/blink/renderer/platform/graphics/graphics_context.h" @@ -460,15 +461,15 @@ part.value->ClearPaintFlags(); } -void CustomScrollbar::Paint(GraphicsContext& context, +void CustomScrollbar::Paint(const PaintInfo& paint_info, const PhysicalOffset& paint_offset) const { auto& theme = GetTheme(); // TODO(crbug.com/40105990): We should not round paint_offset but should // consider subpixel accumulation when painting scrollbars. gfx::Vector2d offset = ToRoundedVector2d(paint_offset); - theme.PaintTrackAndButtons(context, *this, FrameRect() + offset); + theme.PaintTrackAndButtons(paint_info, *this, FrameRect() + offset); if (theme.HasThumb(*this)) { - theme.PaintThumb(context, *this, theme.ThumbRect(*this) + offset); + theme.PaintThumb(paint_info, *this, theme.ThumbRect(*this) + offset); } } diff --git a/third_party/blink/renderer/core/layout/custom_scrollbar.h b/third_party/blink/renderer/core/layout/custom_scrollbar.h index d456481..7d550f4a 100644 --- a/third_party/blink/renderer/core/layout/custom_scrollbar.h +++ b/third_party/blink/renderer/core/layout/custom_scrollbar.h @@ -35,9 +35,9 @@ #include "third_party/blink/renderer/platform/wtf/casting.h" namespace blink { +struct PaintInfo; class ComputedStyle; -class GraphicsContext; class LayoutObject; class LayoutCustomScrollbarPart; @@ -98,7 +98,7 @@ void InvalidateDisplayItemClientsOfScrollbarParts(); void ClearPaintFlags(); - void Paint(GraphicsContext&, const PhysicalOffset& paint_offset) const; + void Paint(const PaintInfo&, const PhysicalOffset& paint_offset) const; void Trace(Visitor*) const override; diff --git a/third_party/blink/renderer/core/paint/custom_scrollbar_theme.cc b/third_party/blink/renderer/core/paint/custom_scrollbar_theme.cc index e9e9ab7..a5522aa 100644 --- a/third_party/blink/renderer/core/paint/custom_scrollbar_theme.cc +++ b/third_party/blink/renderer/core/paint/custom_scrollbar_theme.cc @@ -136,10 +136,11 @@ } void CustomScrollbarTheme::PaintScrollCorner( - GraphicsContext& context, + const PaintInfo& paint_info, const ScrollableArea&, const DisplayItemClient& display_item_client, const gfx::Rect& corner_rect) { + GraphicsContext& context = paint_info.context; if (DrawingRecorder::UseCachedDrawingIfPossible(context, display_item_client, DisplayItem::kScrollCorner)) return; @@ -151,26 +152,27 @@ } void CustomScrollbarTheme::PaintTrackBackgroundAndButtons( - GraphicsContext& context, + const PaintInfo& paint_info, const Scrollbar& scrollbar, const gfx::Rect& rect) { - PaintPart(context, scrollbar, rect, kScrollbarBGPart); + PaintPart(paint_info, scrollbar, rect, kScrollbarBGPart); if (HasButtons(scrollbar)) { - PaintButton(context, scrollbar, ButtonRect(scrollbar, kBackButtonStartPart), + PaintButton(paint_info, scrollbar, + ButtonRect(scrollbar, kBackButtonStartPart), kBackButtonStartPart); - PaintButton(context, scrollbar, ButtonRect(scrollbar, kBackButtonEndPart), - kBackButtonEndPart); - PaintButton(context, scrollbar, + PaintButton(paint_info, scrollbar, + ButtonRect(scrollbar, kBackButtonEndPart), kBackButtonEndPart); + PaintButton(paint_info, scrollbar, ButtonRect(scrollbar, kForwardButtonStartPart), kForwardButtonStartPart); - PaintButton(context, scrollbar, + PaintButton(paint_info, scrollbar, ButtonRect(scrollbar, kForwardButtonEndPart), kForwardButtonEndPart); } gfx::Rect track_rect = TrackRect(scrollbar); - PaintPart(context, scrollbar, track_rect, kTrackBGPart); + PaintPart(paint_info, scrollbar, track_rect, kTrackBGPart); if (HasThumb(scrollbar)) { gfx::Rect start_track_rect; @@ -178,38 +180,40 @@ gfx::Rect end_track_rect; SplitTrack(scrollbar, track_rect, start_track_rect, thumb_rect, end_track_rect); - PaintPart(context, scrollbar, start_track_rect, kBackTrackPart); - PaintPart(context, scrollbar, end_track_rect, kForwardTrackPart); + PaintPart(paint_info, scrollbar, start_track_rect, kBackTrackPart); + PaintPart(paint_info, scrollbar, end_track_rect, kForwardTrackPart); } } -void CustomScrollbarTheme::PaintButton(GraphicsContext& context, +void CustomScrollbarTheme::PaintButton(const PaintInfo& paint_info, const Scrollbar& scrollbar, const gfx::Rect& rect, ScrollbarPart part) { - PaintPart(context, scrollbar, rect, part); + PaintPart(paint_info, scrollbar, rect, part); } -void CustomScrollbarTheme::PaintThumb(GraphicsContext& context, +void CustomScrollbarTheme::PaintThumb(const PaintInfo& paint_info, const Scrollbar& scrollbar, const gfx::Rect& rect) { - PaintPart(context, scrollbar, rect, kThumbPart); + PaintPart(paint_info, scrollbar, rect, kThumbPart); } -void CustomScrollbarTheme::PaintTickmarks(GraphicsContext& context, +void CustomScrollbarTheme::PaintTickmarks(const PaintInfo& paint_info, const Scrollbar& scrollbar, const gfx::Rect& rect) { - GetTheme().PaintTickmarks(context, scrollbar, rect); + GetTheme().PaintTickmarks(paint_info, scrollbar, rect); } void CustomScrollbarTheme::PaintIntoRect( const LayoutCustomScrollbarPart& layout_custom_scrollbar_part, - GraphicsContext& graphics_context, + const PaintInfo& parent_paint_info, const PhysicalRect& rect) { PaintInfo paint_info( - graphics_context, CullRect(ToPixelSnappedRect(rect)), + parent_paint_info.context, CullRect(ToPixelSnappedRect(rect)), PaintPhase::kForeground, - layout_custom_scrollbar_part.ChildPaintBlockedByDisplayLock()); + layout_custom_scrollbar_part.ChildPaintBlockedByDisplayLock(), + parent_paint_info.GetPaintFlags(), + parent_paint_info.GetSvgContextPaints()); // LayoutBox-derived objects normally paint via BoxFragmentPainter, which // determines which FragmentData to use, but that won't work for @@ -222,7 +226,7 @@ .PaintAllPhasesAtomically(paint_info); } -void CustomScrollbarTheme::PaintPart(GraphicsContext& context, +void CustomScrollbarTheme::PaintPart(const PaintInfo& paint_info, const Scrollbar& scrollbar, const gfx::Rect& rect, ScrollbarPart part) { @@ -230,7 +234,7 @@ const auto* part_layout_object = custom_scrollbar.GetPart(part); if (!part_layout_object) return; - PaintIntoRect(*part_layout_object, context, PhysicalRect(rect)); + PaintIntoRect(*part_layout_object, paint_info, PhysicalRect(rect)); } } // namespace blink diff --git a/third_party/blink/renderer/core/paint/custom_scrollbar_theme.h b/third_party/blink/renderer/core/paint/custom_scrollbar_theme.h index 28005638..7d9ffc3 100644 --- a/third_party/blink/renderer/core/paint/custom_scrollbar_theme.h
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/third_party/blink/renderer/core/scroll/scrollbar_theme_aura_test.cc b/third_party/blink/renderer/core/scroll/scrollbar_theme_aura_test.cc
index bbe95a2..0b4431ff 100644
--- a/third_party/blink/renderer/core/scroll/scrollbar_theme_aura_test.cc
+++ b/third_party/blink/renderer/core/scroll/scrollbar_theme_aura_test.cc
@@ -6,6 +6,7 @@
#include "base/notimplemented.h"
#include "third_party/blink/public/common/input/web_mouse_event.h"
+#include "third_party/blink/renderer/core/paint/paint_info.h"
#include "third_party/blink/renderer/core/scroll/scrollbar_test_suite.h"
#include "third_party/blink/renderer/platform/graphics/graphics_context.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_controller.h"
@@ -34,12 +35,12 @@
scrollbar.CSSScrollbarWidth());
}
- void PaintTrackBackground(GraphicsContext&,
+ void PaintTrackBackground(const PaintInfo&,
const Scrollbar&,
const gfx::Rect& rect) override {
last_painted_track_rect = rect;
}
- void PaintButton(GraphicsContext&,
+ void PaintButton(const PaintInfo&,
const Scrollbar&,
const gfx::Rect& rect,
ScrollbarPart part) override {
@@ -297,7 +298,10 @@
PaintController paint_controller;
paint_controller.UpdateCurrentPaintChunkProperties(PropertyTreeState::Root());
GraphicsContext context(paint_controller);
- theme.PaintTrackBackgroundAndButtons(context, *scrollbar, gfx::Rect(canvas));
+ PaintInfo paint_info(context, CullRect(gfx::Rect(canvas)),
+ PaintPhase::kForeground, false);
+ theme.PaintTrackBackgroundAndButtons(paint_info, *scrollbar,
+ gfx::Rect(canvas));
EXPECT_EQ(gfx::Rect(0, width, width, 1), theme.last_painted_track_rect);
EXPECT_EQ(gfx::Rect(0, 0, width, width), theme.last_painted_back_button_rect);
EXPECT_EQ(gfx::Rect(0, width + 1, width, width),
@@ -325,7 +329,10 @@
PaintController paint_controller;
paint_controller.UpdateCurrentPaintChunkProperties(PropertyTreeState::Root());
GraphicsContext context(paint_controller);
- theme.PaintTrackBackgroundAndButtons(context, *scrollbar, gfx::Rect(canvas));
+ PaintInfo paint_info(context, CullRect(gfx::Rect(canvas)),
+ PaintPhase::kForeground, false);
+ theme.PaintTrackBackgroundAndButtons(paint_info, *scrollbar,
+ gfx::Rect(canvas));
if (int track_height = height - button_size.height() * 2) {
EXPECT_EQ(track_height, 1);
EXPECT_EQ(gfx::Rect(0, button_size.height(), width, track_height),
@@ -354,7 +361,9 @@
PaintController paint_controller;
paint_controller.UpdateCurrentPaintChunkProperties(PropertyTreeState::Root());
GraphicsContext context(paint_controller);
- theme.PaintTrackBackgroundAndButtons(context, *scrollbar, gfx::Rect(1, 1));
+ PaintInfo paint_info(context, CullRect(gfx::Rect(1, 1)),
+ PaintPhase::kForeground, false);
+ theme.PaintTrackBackgroundAndButtons(paint_info, *scrollbar, gfx::Rect(1, 1));
EXPECT_EQ(gfx::Rect(1, 1), theme.last_painted_track_rect);
EXPECT_EQ(gfx::Rect(), theme.last_painted_back_button_rect);
EXPECT_EQ(gfx::Rect(), theme.last_painted_forward_button_rect);
diff --git a/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/scrollbar-thumb-images-ignored.https.sub.html b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/scrollbar-thumb-images-ignored.https.sub.html
new file mode 100644
index 0000000..b61c937
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/html/canvas/drawElementImage/privacy/scrollbar-thumb-images-ignored.https.sub.html
@@ -0,0 +1,85 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>drawElementImage does not draw cross-origin scrollbar thumb images</title>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <style>
+ #child {
+ width: 120px;
+ height: 200px;
+ background: blue;
+ position: relative;
+ }
+ .scroller::-webkit-scrollbar {
+ width: 100px;
+ }
+ .scroller::-webkit-scrollbar-track {
+ background: transparent;
+ }
+ #sameOrigin {
+ position: absolute;
+ left: 0px;
+ top: 0px;
+ width: 120px;
+ height: 100px;
+ overflow-y: scroll;
+ }
+ #sameOrigin::-webkit-scrollbar-thumb {
+ background-image: url("https://{{location[host]}}/wpt_internal/html/canvas/drawElementImage/resources/green-100x100.png");
+ min-height: 100px;
+ }
+ @supports not selector(::-webkit-scrollbar-thumb) {
+ #sameOrigin {
+ background-color: #0f0;
+ }
+ }
+ #crossOrigin {
+ position: absolute;
+ left: 0px;
+ top: 100px;
+ width: 120px;
+ height: 100px;
+ overflow-y: scroll;
+ }
+ #crossOrigin::-webkit-scrollbar-thumb {
+ background-image: url("https://{{hosts[alt][www]}}:{{ports[h2][0]}}/wpt_internal/html/canvas/drawElementImage/resources/red-100x100.png");
+ min-height: 100px;
+ }
+ .scroll-content {
+ width: 20px;
+ height: 200px;
+ background: lightblue;
+ }
+ </style>
+</head>
+<body>
+ <canvas id="canvas" width="100" height="200" layoutsubtree>
+ <div id="child">
+ <div id="sameOrigin" class="scroller">
+ <div class="scroll-content"></div>
+ </div>
+ <div id="crossOrigin" class="scroller">
+ <div class="scroll-content"></div>
+ </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);
+
+ let pixel = context.getImageData(60, 50, 1, 1).data;
+ assert_array_equals(pixel, [0, 255, 0, 255], "Same origin should draw");
+
+ pixel = context.getImageData(60, 150, 1, 1).data;
+ assert_array_equals(pixel, [0, 0, 255, 255], "Cross origin 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