Medium chrome Logic Error 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactPolicy bypass in Paint
DescriptionPolicy bypass in Paint
ComponentPaint
Bug ClassLogic Error
Tracker501668745
Fix commit64cb2a5247df (chromium/src) +246/-112
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
ComputedStyle
third_party/blink/renderer/core/layout/custom_scrollbar.h
modified
GraphicsContext
third_party/blink/renderer/core/layout/custom_scrollbar.h
modified
LayoutObject
third_party/blink/renderer/core/layout/custom_scrollbar.h
modified
LayoutCustomScrollbarPart
third_party/blink/renderer/core/layout/custom_scrollbar.h
modified

Files Changed

  • third_party/blink/renderer/core/layout/custom_scrollbar.cc
  • third_party/blink/renderer/core/layout/custom_scrollbar.h
  • third_party/blink/renderer/core/paint/custom_scrollbar_theme.cc
  • third_party/blink/renderer/core/paint/custom_scrollbar_theme.h
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.