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 <jonross@chromium.org>
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 <dtapuska@chromium.org>
Commit-Queue: Jonathan Ross <jonross@chromium.org>
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