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 <pdr@chromium.org>
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 <schenney@chromium.org>
Commit-Queue: Philip Rogers <pdr@chromium.org>
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