Chrome · Input
CVE-2026-113025
UAF in Input
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcontent/browser/web_contents/web_contents_impl.cc |
modified | |
TEST_Fcontent/browser/web_contents/web_contents_impl_unittest.cc |
modified | |
WebContentsImplTestKeyboardEventscontent/browser/web_contents/web_contents_impl_unittest.cc |
modified |
Files Changed
content/browser/web_contents/web_contents_impl_unittest.cccontent/browser/web_contents/web_contents_impl.cc
Patch
From 1732e833369ccac6e659d8ede8c60b1bd1143875 Mon Sep 17 00:00:00 2001 From: Jonathan Ross <[email protected]> Date: Wed, 16 Sep 2026 12:01:32 -0700 Subject: [PATCH] Clamp selection bounding box to the viewport TextInputManager::SelectionBoundsChanged clamps `anchor_rect` and `focus_rect` to the root viewport via TransformAndClampBounds, but the sibling `bounding_box` field was transformed into root coordinates and then stored without being clamped. Consumers such as WebContentsImpl::GetTextSelectionBounds and RenderWidgetHostViewAura::GetSelectionBoundingBox translate this rect into screen coordinates to position UI, so a bounding box that falls outside the viewport can place that UI outside the web contents area. Clamp the bounding box in ` WebContentsImpl::GetTextSelectionBounds`. We do not clamp it in `TextInputManager` as the clipboard API needs to know the full range, even if it is outside the viewport. TAG=agy CONV=bfb8e921-0c0e-40cb-a8ce-192a5ff95a3a Bug: 533113044 Change-Id: If756f435fb8d2ec8c4ad7de08054c2e7c3b3dcc8 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8408493 Reviewed-by: Dave Tapuska <[email protected]> Commit-Queue: Jonathan Ross <[email protected]> Cr-Commit-Position: refs/heads/main@{#1699142} --- diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc index c01bcd9..683d1490 100644 --- a/content/browser/web_contents/web_contents_impl.cc +++ b/content/browser/web_contents/web_contents_impl.cc @@ -6727,6 +6727,12 @@ const auto* region = text_input_manager_->GetSelectionRegion(view); if (region) { gfx::Rect bounds = region->bounding_box; + // `bounding_box` is renderer-supplied and is deliberately stored + // unclamped, because consumers such as ClipboardHistory rely on it to + // describe the full extent of the selection even when that extent is + // larger than the viewport. Callers of this method instead use the + // result to position UI, so clamp it to the outermost view here. + bounds.AdjustToFit(gfx::Rect(root_view->GetVisibleViewportSize())); if (!bounds.IsEmpty()) { gfx::Point origin = bounds.origin(); origin += root_view->GetViewBounds().OffsetFromOrigin(); diff --git a/content/browser/web_contents/web_contents_impl_unittest.cc b/content/browser/web_contents/web_contents_impl_unittest.cc index ee64777..4c7a6042 100644 --- a/content/browser/web_contents/web_contents_impl_unittest.cc +++ b/content/browser/web_contents/web_contents_impl_unittest.cc @@ -4086,6 +4086,47 @@ EXPECT_FALSE(text_input_manager->HasObserver(contents())); } +// The renderer-supplied selection bounding box is stored unclamped, but +// GetTextSelectionBounds() is used to position UI, so the rect it returns must +// lie inside the view even when the reported selection does not. +TEST_F(WebContentsImplTest, GetTextSelectionBoundsIsClampedToView) { + TestRenderFrameHost* rfh = main_test_rfh(); + auto* view = static_cast<RenderWidgetHostViewBase*>(rfh->GetView()); + ASSERT_TRUE(view); + view->SetBounds(gfx::Rect(0, 0, 800, 600)); + + // Fetching the manager through the view is what registers the view with it. + TextInputManager* text_input_manager = view->GetTextInputManager(); + ASSERT_TRUE(text_input_manager); + ASSERT_EQ(text_input_manager, contents()->GetTextInputManager()); + + ui::mojom::TextInputState state; + state.type = ui::TEXT_INPUT_TYPE_TEXT; + text_input_manager->UpdateTextInputState(view, state); + + // A bounding box whose large negative y places it above the top of the view. + const gfx::Rect anchor_rect(8, 16, 0, 19); + const gfx::Rect out_of_view_bounding_box(200, -120, 40, 20); + text_input_manager->SelectionBoundsChanged( + view, anchor_rect, base::i18n::LEFT_TO_RIGHT, anchor_rect, + base::i18n::LEFT_TO_RIGHT, out_of_view_bounding_box, + /*is_anchor_first=*/true); + + // The stored region keeps the unclamped extent. + const TextInputManager::SelectionRegion* region = + text_input_manager->GetSelectionRegion(view); + ASSERT_TRUE(region); + EXPECT_EQ(region->bounding_box, out_of_view_bounding_box); + + // The bounds handed to callers are clamped into the view, then offset into + // screen coordinates. + const std::optional<gfx::Rect> bounds = + contents()->GetTextSelectionBounds(rfh); + ASSERT_TRUE(bounds.has_value()); + const gfx::Vector2d offset = view->GetViewBounds().OffsetFromOrigin(); + EXPECT_EQ(*bounds, gfx::Rect(gfx::Point(200, 0) + offset, gfx::Size(40, 20))); +} + class WebContentsImplTestKeyboardEvents : public WebContentsImplTest, public testing::WithParamInterface<blink::WebInputEvent::Type> {};
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/content/browser/web_contents/web_contents_impl_unittest.cc b/content/browser/web_contents/web_contents_impl_unittest.cc
index ee64777..4c7a6042 100644
--- a/content/browser/web_contents/web_contents_impl_unittest.cc
+++ b/content/browser/web_contents/web_contents_impl_unittest.cc
@@ -4086,6 +4086,47 @@
EXPECT_FALSE(text_input_manager->HasObserver(contents()));
}
+// The renderer-supplied selection bounding box is stored unclamped, but
+// GetTextSelectionBounds() is used to position UI, so the rect it returns must
+// lie inside the view even when the reported selection does not.
+TEST_F(WebContentsImplTest, GetTextSelectionBoundsIsClampedToView) {
+ TestRenderFrameHost* rfh = main_test_rfh();
+ auto* view = static_cast<RenderWidgetHostViewBase*>(rfh->GetView());
+ ASSERT_TRUE(view);
+ view->SetBounds(gfx::Rect(0, 0, 800, 600));
+
+ // Fetching the manager through the view is what registers the view with it.
+ TextInputManager* text_input_manager = view->GetTextInputManager();
+ ASSERT_TRUE(text_input_manager);
+ ASSERT_EQ(text_input_manager, contents()->GetTextInputManager());
+
+ ui::mojom::TextInputState state;
+ state.type = ui::TEXT_INPUT_TYPE_TEXT;
+ text_input_manager->UpdateTextInputState(view, state);
+
+ // A bounding box whose large negative y places it above the top of the view.
+ const gfx::Rect anchor_rect(8, 16, 0, 19);
+ const gfx::Rect out_of_view_bounding_box(200, -120, 40, 20);
+ text_input_manager->SelectionBoundsChanged(
+ view, anchor_rect, base::i18n::LEFT_TO_RIGHT, anchor_rect,
+ base::i18n::LEFT_TO_RIGHT, out_of_view_bounding_box,
+ /*is_anchor_first=*/true);
+
+ // The stored region keeps the unclamped extent.
+ const TextInputManager::SelectionRegion* region =
+ text_input_manager->GetSelectionRegion(view);
+ ASSERT_TRUE(region);
+ EXPECT_EQ(region->bounding_box, out_of_view_bounding_box);
+
+ // The bounds handed to callers are clamped into the view, then offset into
+ // screen coordinates.
+ const std::optional<gfx::Rect> bounds =
+ contents()->GetTextSelectionBounds(rfh);
+ ASSERT_TRUE(bounds.has_value());
+ const gfx::Vector2d offset = view->GetViewBounds().OffsetFromOrigin();
+ EXPECT_EQ(*bounds, gfx::Rect(gfx::Point(200, 0) + offset, gfx::Size(40, 20)));
+}
+
class WebContentsImplTestKeyboardEvents
: public WebContentsImplTest,
public testing::WithParamInterface<blink::WebInputEvent::Type> {};
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