Chrome · Compositing
CVE-2025-0448
Logic Error in Compositing
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcc/layers/painted_scrollbar_layer_impl.cc |
modified |
Files Changed
cc/layers/painted_scrollbar_layer_impl.cccc/layers/painted_scrollbar_layer_impl_unittest.cc
Patch
From 4d86cee91c4307c8bb4fe2a8b6874b661482b179 Mon Sep 17 00:00:00 2001
From: Xianzhu Wang <wangxianzhu@chromium.org>
Date: Wed, 13 Nov 2024 22:17:29 +0000
Subject: [PATCH] Fix solid color thumb quad under large scale
If a layer has a large scale, visible_layer_rect() can't reliably
clip a quad before scaling because a "pixel" in the layer is very
large and scale-after-clip will create a quad exceeding the clip.
clip
large-scale
layer
Now share more code with the non-solid-color-thumb code path.
For a AppendQuads method, to ensure the clip rect is applied, it's
better to call PopulateScaledSharedQuadState() instead of creating a
shared quad state by itself.
Bug: 377948403
Change-Id: I8d7dd08fe9fc7907685bfbaa1a5fd5682688aed0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6014683
Reviewed-by: Philip Rogers <pdr@chromium.org>
Commit-Queue: Xianzhu Wang <wangxianzhu@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1382613}
---
diff --git a/cc/layers/painted_scrollbar_layer_impl.cc b/cc/layers/painted_scrollbar_layer_impl.cc
index ed9b32b..12fb191 100644
--- a/cc/layers/painted_scrollbar_layer_impl.cc
+++ b/cc/layers/painted_scrollbar_layer_impl.cc
@@ -109,19 +109,28 @@
AppendQuadsData* append_quads_data) const {
viz::SharedQuadState* shared_quad_state =
render_pass->CreateAndAppendSharedQuadState();
- if (thumb_color_.has_value()) {
- const gfx::Rect thumb_rect = ComputeThumbQuadRect();
- if (thumb_rect.IsEmpty()) {
- return;
- }
- gfx::Rect visible_thumb_rect =
- draw_properties().occlusion_in_content_space.GetUnoccludedContentRect(
- thumb_rect);
- visible_thumb_rect.Intersect(visible_layer_rect());
- if (visible_thumb_rect.IsEmpty()) {
- return;
- }
+ // The thumb sqs must be non-opaque so that the track and buttons will not be
+ // occluded in viz by the thumb's 'quad_layer_rect'.
+ constexpr bool kContentsOpaque = false;
+ PopulateScaledSharedQuadState(shared_quad_state, internal_contents_scale_,
+ kContentsOpaque);
+ AppendDebugBorderQuad(render_pass, gfx::Rect(internal_content_bounds_),
+ shared_quad_state, append_quads_data);
+
+ const gfx::Rect thumb_quad_rect = ComputeThumbQuadRect();
+ const gfx::Rect scaled_thumb_quad_rect =
+ gfx::ScaleToEnclosingRect(thumb_quad_rect, internal_contents_scale_);
+ const gfx::Rect visible_thumb_quad_rect =
+ draw_properties().occlusion_in_content_space.GetUnoccludedContentRect(
+ thumb_quad_rect);
+ if (visible_thumb_quad_rect.IsEmpty()) {
+ return;
+ }
+ const gfx::Rect scaled_visible_thumb_quad_rect = gfx::ScaleToEnclosingRect(
+ visible_thumb_quad_rect, internal_contents_scale_);
+
+ if (thumb_color_.has_value()) {
gfx::MaskFilterInfo rounded_corners_mask =
draw_properties().mask_filter_info;
// Web tests draw the thumb as a square to avoid issues that come with the
@@ -130,24 +139,18 @@
if (!is_web_test() && IsFluentScrollbarEnabled()) {
const int rounded_corner_radius =
orientation() == ScrollbarOrientation::kHorizontal
- ? thumb_rect.height()
- : thumb_rect.width();
- rounded_corners_mask = gfx::MaskFilterInfo(
- gfx::RRectF(gfx::RectF(thumb_rect), rounded_corner_radius));
- rounded_corners_mask.ApplyTransform(
+ ? thumb_quad_rect.height()
+ : thumb_quad_rect.width();
+ shared_quad_state->mask_filter_info = gfx::MaskFilterInfo(
+ gfx::RRectF(gfx::RectF(thumb_quad_rect), rounded_corner_radius));
+ shared_quad_state->mask_filter_info.ApplyTransform(
draw_properties().target_space_transform);
}
- shared_quad_state->SetAll(
- draw_properties().target_space_transform, thumb_rect,
- visible_thumb_rect, rounded_corners_mask, /*clip=*/std::nullopt,
- /*contents_opaque=*/false, draw_properties().opacity,
- /*blend=*/SkBlendMode::kSrcOver, GetSortingContextId(),
- static_cast<uint32_t>(id()),
- /*fast_rounded_corner=*/true);
auto* thumb_quad =
render_pass->CreateAndAppendDrawQuad<viz::SolidColorDrawQuad>();
- thumb_quad->SetNew(shared_quad_state, thumb_rect, visible_thumb_rect,
- thumb_color_.value(), /*anti_aliasing_off=*/false);
+ thumb_quad->SetNew(shared_quad_state, scaled_thumb_quad_rect,
+ scaled_visible_thumb_quad_rect, thumb_color_.value(),
+ /*anti_aliasing_off=*/false);
ValidateQuadResources(thumb_quad);
return;
}
@@ -159,26 +162,9 @@
return;
}
- // The thumb sqs must be non-opaque so that the track and buttons will not be
- // occluded in viz by the thumb's 'quad_layer_rect'.
- constexpr bool kContentsOpaque = false;
- PopulateScaledSharedQuadState(shared_quad_state, internal_contents_scale_,
- kContentsOpaque);
-
- AppendDebugBorderQuad(render_pass, gfx::Rect(internal_content_bounds_),
- shared_quad_state, append_quads_data);
- gfx::Rect thumb_quad_rect = ComputeThumbQuadRect();
- gfx::Rect scaled_thumb_quad_rect =
- gfx::ScaleToEnclosingRect(thumb_quad_rect, internal_contents_scale_);
- gfx::Rect visible_thumb_quad_rect =
- draw_properties().occlusion_in_content_space.GetUnoccludedContentRect(
- thumb_quad_rect);
- gfx::Rect scaled_visible_thumb_quad_rect = gfx::ScaleToEnclosingRect(
- visible_thumb_quad_rect, internal_contents_scale_);
viz::ResourceId thumb_resource_id =
layer_tree_impl()->ResourceIdForUIResource(thumb_ui_resource_id_);
-
- if (!thumb_resource_id || visible_thumb_quad_rect.IsEmpty()) {
+ if (!thumb_resource_id) {
return;
}
diff --git a/cc/layers/painted_scrollbar_layer_impl_unittest.cc b/cc/layers/painted_scrollbar_layer_impl_unittest.cc
index 26ff7c4..6ea79f9d 100644
--- a/cc/layers/painted_scrollbar_layer_impl_unittest.cc
+++ b/cc/layers/painted_scrollbar_layer_impl_unittest.cc
@@ -65,8 +65,7 @@
impl.CalcDrawProps(viewport_size);
gfx::Rect thumb_rect = scrollbar_layer_impl->ComputeThumbQuadRect();
- EXPECT_EQ(gfx::Rect(0, 500 / 4, 10, layer_size.height() / 2).ToString(),
- thumb_rect.ToString());
+ EXPECT_EQ(gfx::Rect(0, 500 / 4, 10, layer_size.height() / 2), thumb_rect);
{
SCOPED_TRACE("No occlusion");
@@ -97,16 +96,14 @@
viz::TextureDrawQuad::MaterialCast(track_and_buttons_draw_quad);
gfx::Rect scaled_thumb_rect = gfx::ScaleToEnclosingRect(thumb_rect, scale);
- EXPECT_EQ(track_and_buttons_quad->rect.ToString(),
- gfx::Rect(scaled_layer_size).ToString());
+ EXPECT_EQ(track_and_buttons_quad->rect, gfx::Rect(scaled_layer_size));
EXPECT_EQ(scrollbar_layer_impl->contents_opaque(),
track_and_buttons_quad->shared_quad_state->are_contents_opaque);
- EXPECT_EQ(track_and_buttons_quad->visible_rect.ToString(),
- gfx::Rect(scaled_layer_size).ToString());
+ EXPECT_EQ(track_and_buttons_quad->visible_rect,
+ gfx::Rect(scaled_layer_size));
EXPECT_FALSE(track_and_buttons_quad->needs_blending);
- EXPECT_EQ(thumb_quad->rect.ToString(), scaled_thumb_rect.ToString());
- EXPECT_EQ(thumb_quad->visible_rect.ToString(),
- scaled_thumb_rect.ToString());
+ EXPECT_EQ(thumb_quad->rect, scaled_thumb_rect);
+ EXPECT_EQ(thumb_quad->visible_rect, scaled_thumb_rect);
EXPECT_TRUE(thumb_quad->needs_blending);
EXPECT_FALSE(thumb_quad->shared_quad_state->are_contents_opaque);
@@ -184,8 +181,8 @@
impl.CalcDrawProps(viewport_size);
gfx::Rect thumb_rect = scrollbar_layer_impl->ComputeThumbQuadRect();
- EXPECT_EQ(gfx::Rect(2, 500 / 4, 11, layer_size.height() / 2).ToString(),
- thumb_rect.ToString());
+ EXPECT_EQ(gfx::Rect(2, 500 / 4, 11, layer_size.height() / 2), thumb_rect);
+ gfx::Rect scaled_thumb_rect = gfx::ScaleToEnclosingRect(thumb_rect, scale);
{
SCOPED_TRACE("No occlusion");
@@ -220,8 +217,8 @@
EXPECT_EQ(track_and_buttons_quad->visible_rect,
gfx::Rect(scaled_layer_size));
EXPECT_FALSE(track_and_buttons_quad->needs_blending);
- EXPECT_EQ(thumb_quad->rect, thumb_rect);
- EXPECT_EQ(thumb_quad->visible_rect, thumb_rect);
+ EXPECT_EQ(thumb_quad->rect, scaled_thumb_rect);
+ EXPECT_EQ(thumb_quad->visible_rect, scaled_thumb_rect);
EXPECT_FALSE(thumb_quad->needs_blending);
EXPECT_FALSE(thumb_quad->shared_quad_state->are_contents_opaque);
@@ -252,6 +249,84 @@
}
}
+TEST(PaintedScrollbarLayerImplTest,
+ SolidColorThumbNinePatchTrackUnderClipAndScale) {
+ gfx::Size layer_size(15, 1000);
+ gfx::Rect clip_rect(0, 0, 1000, 1000);
+ float scale = 33.f;
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/cc/layers/painted_scrollbar_layer_impl_unittest.cc b/cc/layers/painted_scrollbar_layer_impl_unittest.cc
index 26ff7c4..6ea79f9d 100644
--- a/cc/layers/painted_scrollbar_layer_impl_unittest.cc
+++ b/cc/layers/painted_scrollbar_layer_impl_unittest.cc
@@ -65,8 +65,7 @@
impl.CalcDrawProps(viewport_size);
gfx::Rect thumb_rect = scrollbar_layer_impl->ComputeThumbQuadRect();
- EXPECT_EQ(gfx::Rect(0, 500 / 4, 10, layer_size.height() / 2).ToString(),
- thumb_rect.ToString());
+ EXPECT_EQ(gfx::Rect(0, 500 / 4, 10, layer_size.height() / 2), thumb_rect);
{
SCOPED_TRACE("No occlusion");
@@ -97,16 +96,14 @@
viz::TextureDrawQuad::MaterialCast(track_and_buttons_draw_quad);
gfx::Rect scaled_thumb_rect = gfx::ScaleToEnclosingRect(thumb_rect, scale);
- EXPECT_EQ(track_and_buttons_quad->rect.ToString(),
- gfx::Rect(scaled_layer_size).ToString());
+ EXPECT_EQ(track_and_buttons_quad->rect, gfx::Rect(scaled_layer_size));
EXPECT_EQ(scrollbar_layer_impl->contents_opaque(),
track_and_buttons_quad->shared_quad_state->are_contents_opaque);
- EXPECT_EQ(track_and_buttons_quad->visible_rect.ToString(),
- gfx::Rect(scaled_layer_size).ToString());
+ EXPECT_EQ(track_and_buttons_quad->visible_rect,
+ gfx::Rect(scaled_layer_size));
EXPECT_FALSE(track_and_buttons_quad->needs_blending);
- EXPECT_EQ(thumb_quad->rect.ToString(), scaled_thumb_rect.ToString());
- EXPECT_EQ(thumb_quad->visible_rect.ToString(),
- scaled_thumb_rect.ToString());
+ EXPECT_EQ(thumb_quad->rect, scaled_thumb_rect);
+ EXPECT_EQ(thumb_quad->visible_rect, scaled_thumb_rect);
EXPECT_TRUE(thumb_quad->needs_blending);
EXPECT_FALSE(thumb_quad->shared_quad_state->are_contents_opaque);
@@ -184,8 +181,8 @@
impl.CalcDrawProps(viewport_size);
gfx::Rect thumb_rect = scrollbar_layer_impl->ComputeThumbQuadRect();
- EXPECT_EQ(gfx::Rect(2, 500 / 4, 11, layer_size.height() / 2).ToString(),
- thumb_rect.ToString());
+ EXPECT_EQ(gfx::Rect(2, 500 / 4, 11, layer_size.height() / 2), thumb_rect);
+ gfx::Rect scaled_thumb_rect = gfx::ScaleToEnclosingRect(thumb_rect, scale);
{
SCOPED_TRACE("No occlusion");
@@ -220,8 +217,8 @@
EXPECT_EQ(track_and_buttons_quad->visible_rect,
gfx::Rect(scaled_layer_size));
EXPECT_FALSE(track_and_buttons_quad->needs_blending);
- EXPECT_EQ(thumb_quad->rect, thumb_rect);
- EXPECT_EQ(thumb_quad->visible_rect, thumb_rect);
+ EXPECT_EQ(thumb_quad->rect, scaled_thumb_rect);
+ EXPECT_EQ(thumb_quad->visible_rect, scaled_thumb_rect);
EXPECT_FALSE(thumb_quad->needs_blending);
EXPECT_FALSE(thumb_quad->shared_quad_state->are_contents_opaque);
@@ -252,6 +249,84 @@
}
}
+TEST(PaintedScrollbarLayerImplTest,
+ SolidColorThumbNinePatchTrackUnderClipAndScale) {
+ gfx::Size layer_size(15, 1000);
+ gfx::Rect clip_rect(0, 0, 1000, 1000);
+ float scale = 33.f;
+ gfx::Size scaled_layer_size = gfx::ScaleToCeiledSize(layer_size, scale);
+ gfx::Size viewport_size(1000, 1000);
+
+ LayerTreeImplTestBase impl;
+
+ SkBitmap thumb_sk_bitmap;
+ thumb_sk_bitmap.allocN32Pixels(10, 10);
+ thumb_sk_bitmap.setImmutable();
+ UIResourceId thumb_uid = 5;
+ UIResourceBitmap thumb_bitmap(thumb_sk_bitmap);
+ impl.host_impl()->CreateUIResource(thumb_uid, thumb_bitmap);
+
+ SkBitmap track_and_buttons_sk_bitmap;
+ track_and_buttons_sk_bitmap.allocN32Pixels(10, 10);
+ track_and_buttons_sk_bitmap.setImmutable();
+ UIResourceId track_and_buttons_uid = 6;
+ UIResourceBitmap track_and_buttons_bitmap(track_and_buttons_sk_bitmap);
+ impl.host_impl()->CreateUIResource(track_and_buttons_uid,
+ track_and_buttons_bitmap);
+
+ ScrollbarOrientation orientation = ScrollbarOrientation::kVertical;
+
+ PaintedScrollbarLayerImpl* scrollbar_layer_impl =
+ impl.AddLayerInActiveTree<PaintedScrollbarLayerImpl>(orientation, false,
+ false);
+ scrollbar_layer_impl->SetBounds(layer_size);
+ scrollbar_layer_impl->SetContentsOpaque(true);
+ scrollbar_layer_impl->set_internal_contents_scale_and_bounds(
+ scale, scaled_layer_size);
+ scrollbar_layer_impl->SetDrawsContent(true);
+ scrollbar_layer_impl->SetThumbThickness(11);
+ scrollbar_layer_impl->SetThumbLength(500);
+ scrollbar_layer_impl->SetTrackRect(gfx::Rect(0, 0, 15, layer_size.height()));
+ scrollbar_layer_impl->SetCurrentPos(100.f / 4);
+ scrollbar_layer_impl->SetClipLayerLength(100.f);
+ scrollbar_layer_impl->SetScrollLayerLength(200.f);
+ scrollbar_layer_impl->set_track_and_buttons_ui_resource_id(
+ track_and_buttons_uid);
+ scrollbar_layer_impl->SetThumbColor(SkColors::kRed);
+
+ CopyProperties(impl.root_layer(), scrollbar_layer_impl);
+ CreateClipNode(scrollbar_layer_impl).clip = gfx::RectF(clip_rect);
+ CreateTransformNode(scrollbar_layer_impl).local.Scale(scale);
+
+ impl.CalcDrawProps(viewport_size);
+
+ gfx::Rect thumb_rect = scrollbar_layer_impl->ComputeThumbQuadRect();
+ EXPECT_EQ(gfx::Rect(2, 500 / 4, 11, layer_size.height() / 2), thumb_rect);
+ gfx::Rect scaled_thumb_rect = gfx::ScaleToEnclosingRect(thumb_rect, scale);
+
+ impl.AppendQuads(scrollbar_layer_impl);
+ EXPECT_EQ(2u, impl.quad_list().size());
+
+ const viz::SolidColorDrawQuad* thumb_quad =
+ viz::SolidColorDrawQuad::MaterialCast(impl.quad_list().ElementAt(0));
+ EXPECT_EQ(thumb_quad->rect, scaled_thumb_rect);
+ EXPECT_EQ(thumb_quad->visible_rect, scaled_thumb_rect);
+ EXPECT_FALSE(thumb_quad->needs_blending);
+ EXPECT_FALSE(thumb_quad->shared_quad_state->are_contents_opaque);
+ EXPECT_EQ(1.f, thumb_quad->shared_quad_state->opacity);
+
+ const viz::TextureDrawQuad* track_and_buttons_quad =
+ viz::TextureDrawQuad::MaterialCast(impl.quad_list().ElementAt(1));
+ EXPECT_EQ(track_and_buttons_quad->shared_quad_state->clip_rect, clip_rect);
+ EXPECT_EQ(track_and_buttons_quad->rect, gfx::Rect(scaled_layer_size));
+ EXPECT_EQ(scrollbar_layer_impl->contents_opaque(),
+ track_and_buttons_quad->shared_quad_state->are_contents_opaque);
+ EXPECT_EQ(track_and_buttons_quad->visible_rect, gfx::Rect(scaled_layer_size));
+ EXPECT_FALSE(track_and_buttons_quad->needs_blending);
+ EXPECT_EQ(track_and_buttons_quad->shared_quad_state->clip_rect, clip_rect);
+ EXPECT_EQ(1.f, track_and_buttons_quad->shared_quad_state->opacity);
+}
+
TEST(PaintedScrollbarLayerImplTest, PaintedOpacityChangesInvalidate) {
LayerTreeImplTestBase impl;
ScrollbarOrientation orientation = ScrollbarOrientation::kVertical;
@@ -339,7 +414,7 @@
: public PaintedScrollbarLayerImplSolidColorThumbTest {
protected:
void SetUp() override {
- LayerTreeSettings settings;
+ LayerTreeSettings settings = CommitToPendingTreeLayerListSettings();
settings.enable_fluent_overlay_scrollbar = true;
settings.enable_fluent_scrollbar = true;
impl_ = std::make_unique<LayerTreeImplTestBase>(settings);
@@ -394,6 +469,9 @@
scrollbar_layer_impl->SetThumbThickness(
scrollbar_layer_impl->GetIdleThicknessScale());
+ CopyProperties(impl_->root_layer(), scrollbar_layer_impl);
+ impl_->CalcDrawProps(gfx::Size(500, 500));
+
// AppendThumbQuads should return early after checking that there is no
// `thumb_color_` and Fluent scrollbars are enabled.
// AppendTrackQuads should return early when the Fluent overlay code decides
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-large-scale-in-iframe-ref.html b/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-large-scale-in-iframe-ref.html
new file mode 100644
index 0000000..38c8d0c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-large-scale-in-iframe-ref.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<div style="position: absolute; top: 100px; left: 100px;
+ width: 100px; height: 100px; background: green"></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-large-scale-in-iframe.html b/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-large-scale-in-iframe.html
new file mode 100644
index 0000000..1808d1d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-overflow/scrollbar-large-scale-in-iframe.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<link rel="help" href="https://crbug.com/377948403">
+<link rel="match" href="scrollbar-large-scale-in-iframe-ref.html">
+<iframe style="position: absolute; top: 100px; left: 100px;
+ border: none; width: 100px; height: 100px"
+ srcdoc="
+ <select multiple style='transform: scale(500, 10)'>
+ <optgroup style='height: 1000px'></optgroup>
+ </select>">
+</iframe>
+<div style="position: absolute; top: 100px; left: 100px;
+ width: 100px; height: 100px; background: green"></div>
diff --git a/third_party/blink/web_tests/platform/linux/fast/sub-pixel/transformed-iframe-copy-on-scroll-expected.png b/third_party/blink/web_tests/platform/linux/fast/sub-pixel/transformed-iframe-copy-on-scroll-expected.png
index 21e6518a..47ab936 100644
--- a/third_party/blink/web_tests/platform/linux/fast/sub-pixel/transformed-iframe-copy-on-scroll-expected.png
+++ b/third_party/blink/web_tests/platform/linux/fast/sub-pixel/transformed-iframe-copy-on-scroll-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/frames/iframe-scaling-with-scroll-expected.png b/third_party/blink/web_tests/platform/win/fast/frames/iframe-scaling-with-scroll-expected.png
index b3caa2d..fdfac45 100644
--- a/third_party/blink/web_tests/platform/win/fast/frames/iframe-scaling-with-scroll-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/frames/iframe-scaling-with-scroll-expected.png
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/sub-pixel/transformed-iframe-copy-on-scroll-expected.png b/third_party/blink/web_tests/platform/win/fast/sub-pixel/transformed-iframe-copy-on-scroll-expected.png
index e9ba7122..d19d0f5 100644
--- a/third_party/blink/web_tests/platform/win/fast/sub-pixel/transformed-iframe-copy-on-scroll-expected.png
+++ b/third_party/blink/web_tests/platform/win/fast/sub-pixel/transformed-iframe-copy-on-scroll-expected.png
Binary files differ
Loading diff…
Original Bug Report
reported by tr...@gmail.com
blank <select> and <optgroup> inside iframe can be drawn outside of iframe
Steps to reproduce the problem
- Locate frame_1920x1080.html and src.html to same directory
- Open frame_1920x1080.html, the grey box overflows outside of iframe
Problem Description
Of the iframes placed in 3x3, only the iframe in the middle has src. The content that should only be drawn in this iframe is being drawn outside the iframe. I haven’t figured out how to turn a grey box into another shape since I don’t know much about CSS, but I think it could be used for a variety of security attacks if it could be.
Not reproducible on chrome 129.0.6668.70
Summary
blank <select> and <optgroup> inside iframe can be drawn outside of iframe
Custom Questions
Reporter credit:
Dahyeon Park
Additional Data
Category: Security
Chrome Channel: Stable
Regression: Yes
References
On This Page