Chrome · Core
CVE-2026-79284
Logic Error in Core
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
FakeTextInputClientMacDelegatecontent/browser/renderer_host/render_widget_host_view_mac_unittest.mm |
modified | |
TEST_Fcontent/browser/renderer_host/render_widget_host_view_mac_unittest.mm |
modified |
Files Changed
content/browser/renderer_host/render_widget_host_view_mac.mmcontent/browser/renderer_host/render_widget_host_view_mac_unittest.mm
Patch
From 0eb9d43c13183f55606f1d763ffda3603660e844 Mon Sep 17 00:00:00 2001 From: Maggie Chen <[email protected]> Date: Wed, 15 Jul 2026 10:08:37 -0700 Subject: [PATCH] [agy][content] Clamp macOS IME candidate rect Clamp the first rect returned by SyncGetFirstRectForRange on macOS to the visible viewport size. This prevents a compromised renderer from placing the IME candidate window outside the webpage viewport. Fixed: 519210950 Test: content_unittests --gtest_filter=RenderWidgetHostViewMacTest.SyncGetFirstRectForRange_Clamped Change-Id: I419e7014ede241d393047378d2ba12349f71c0c5 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8092181 Reviewed-by: Avi Drissman <[email protected]> Commit-Queue: Maggie Chen <[email protected]> Cr-Commit-Position: refs/heads/main@{#1662699} --- diff --git a/content/browser/renderer_host/render_widget_host_view_mac.mm b/content/browser/renderer_host/render_widget_host_view_mac.mm index 1b82d56c..90880472 100644 --- a/content/browser/renderer_host/render_widget_host_view_mac.mm +++ b/content/browser/renderer_host/render_widget_host_view_mac.mm @@ -2369,6 +2369,11 @@ // which means we have to scale the rect by the device scale factor. *rect = gfx::ScaleToEnclosingRect(blink_rect, 1.f / device_scale_factor); } + + // Ensure the returned rect is clamped to the viewport to prevent a + // compromised renderer from placing IME windows outside the page. + // See https://crbug.com/519210950. + rect->AdjustToFit(gfx::Rect(GetVisibleViewportSize())); return true; } diff --git a/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm b/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm index dcc0c0a7..d8f3530d 100644 --- a/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm +++ b/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm @@ -33,7 +33,9 @@ #include "content/browser/compositor/image_transport_factory.h" #include "content/browser/gpu/compositor_util.h" #include "content/browser/renderer_host/frame_token_message_queue.h" +#include "content/browser/renderer_host/frame_tree.h" #include "content/browser/renderer_host/render_widget_host_delegate.h" +#include "content/browser/renderer_host/text_input_client_mac.h" #include "content/browser/renderer_host/text_input_manager.h" #include "content/browser/site_instance_group.h" #include "content/common/features.h" @@ -51,6 +53,7 @@ #include "content/test/stub_render_widget_host_owner_delegate.h" #include "content/test/test_render_view_host.h" #include "content/test/test_render_widget_host.h" +#include "content/test/test_web_contents.h" #include "gpu/ipc/service/image_transport_surface.h" #include "mojo/public/cpp/bindings/pending_remote.h" #include "testing/gmock/include/gmock/gmock.h" @@ -424,9 +427,10 @@ MockRenderWidgetHostImpl(RenderWidgetHostDelegate* delegate, base::SafeRef<SiteInstanceGroup> site_instance_group, int32_t routing_id, - bool for_frame_widget) + bool for_frame_widget, + FrameTree* frame_tree = nullptr) : RenderWidgetHostImpl( - /*frame_tree=*/nullptr, + frame_tree, /*self_owned=*/false, DefaultFrameSinkId(*site_instance_group, routing_id), delegate, @@ -530,7 +534,7 @@ host_ = std::make_unique<MockRenderWidgetHostImpl>( &delegate_, site_instance_group_->GetSafeRef(), process_host_->GetNextRoutingID(), - /*for_frame_widget=*/true); + /*for_frame_widget=*/true, &contents()->GetPrimaryFrameTree()); host_->set_owner_delegate(&mock_owner_delegate_); delegate_.set_focused_widget(host_.get()); rwhv_mac_ = new RenderWidgetHostViewMac(host_.get()); @@ -2518,4 +2522,78 @@ EXPECT_NSEQ([view accessibilityParent], parent_view); } +class FakeTextInputClientMacDelegate + : public TextInputClientMac::AsyncRequestDelegate { + public: + FakeTextInputClientMacDelegate() = default; + ~FakeTextInputClientMacDelegate() override = default; + + void SetResponseRect(const gfx::Rect& rect) { response_rect_ = rect; } + + void GetCharacterIndexAtPoint( + RenderFrameHost* rfh, + const TextInputClientMac::RequestToken& request_token, + const gfx::Point& point) override { + FAIL() << "Unexpected call to GetCharacterIndexAtPoint"; + } + + void GetFirstRectForRange( + RenderFrameHost* rfh, + const TextInputClientMac::RequestToken& request_token, + const gfx::Range& range) override { + TextInputClientMac::GetInstance()->SetFirstRectWhileLockedForTesting( + request_token, response_rect_); + } + + private: + gfx::Rect response_rect_; +}; + +TEST_F(RenderWidgetHostViewMacTest, SyncGetFirstRectForRange_Clamped) { + base::test::ScopedFeatureList feature_list; + feature_list.InitWithFeatures( + /*enabled_features=*/{}, + /*disabled_features=*/{ + features::kCachedFirstRectAllowRangeOutsideSelection, + features::kCachedFirstRectAllowInvalidSelection}); + + // Focus the root frame tree node so GetFocusedRenderFrameHostImpl succeeds. + contents()->GetPrimaryFrameTree().SetFocusedFrame( + contents()->GetPrimaryFrameTree().root(), nullptr); + + // Set the view bounds to a known size. + rwhv_mac_->SetBounds(gfx::Rect(0, 0, 800, 600)); + + // Create a fake delegate that returns an out-of-bounds rect. + // Forged rect: x=-100, y=-200, w=10, h=20 (in physical pixels). + float dsf = rwhv_mac_->GetDeviceScaleFactor(); + gfx::Rect forged_rect_in_pixels(-100, -200, 10, 20); + + auto fake_delegate = std::make_unique<FakeTextInputClientMacDelegate>(); + fake_delegate->SetResponseRect(forged_rect_in_pixels); + + TextInputClientMac::GetInstance()->SetAsyncRequestDelegateForTesting( + std::move(fake_delegate)); + + gfx::Rect rect; + gfx::Range actual_range; + bool success = false; + + // Call the method under test. + rwhv_mac_->SyncGetFirstRectForRange(gfx::Range(1, 2), &rect, &actual_range, + &success); + + EXPECT_TRUE(success); + + // Expected clamped rect (in DIPs). + // Clamped to viewport (0, 0, 800, 600): + // X should be clamped to 0. + // Y should be clamped to 0. + gfx::Rect expected_rect(0, 0, 10 / dsf, 20 / dsf); + EXPECT_EQ(rect, expected_rect); + + // Restore default delegate. + TextInputClientMac::GetInstance()->SetAsyncRequestDelegateForTesting(nullptr); +} + } // namespace content
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm b/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm
index dcc0c0a7..d8f3530d 100644
--- a/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm
+++ b/content/browser/renderer_host/render_widget_host_view_mac_unittest.mm
@@ -33,7 +33,9 @@
#include "content/browser/compositor/image_transport_factory.h"
#include "content/browser/gpu/compositor_util.h"
#include "content/browser/renderer_host/frame_token_message_queue.h"
+#include "content/browser/renderer_host/frame_tree.h"
#include "content/browser/renderer_host/render_widget_host_delegate.h"
+#include "content/browser/renderer_host/text_input_client_mac.h"
#include "content/browser/renderer_host/text_input_manager.h"
#include "content/browser/site_instance_group.h"
#include "content/common/features.h"
@@ -51,6 +53,7 @@
#include "content/test/stub_render_widget_host_owner_delegate.h"
#include "content/test/test_render_view_host.h"
#include "content/test/test_render_widget_host.h"
+#include "content/test/test_web_contents.h"
#include "gpu/ipc/service/image_transport_surface.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "testing/gmock/include/gmock/gmock.h"
@@ -424,9 +427,10 @@
MockRenderWidgetHostImpl(RenderWidgetHostDelegate* delegate,
base::SafeRef<SiteInstanceGroup> site_instance_group,
int32_t routing_id,
- bool for_frame_widget)
+ bool for_frame_widget,
+ FrameTree* frame_tree = nullptr)
: RenderWidgetHostImpl(
- /*frame_tree=*/nullptr,
+ frame_tree,
/*self_owned=*/false,
DefaultFrameSinkId(*site_instance_group, routing_id),
delegate,
@@ -530,7 +534,7 @@
host_ = std::make_unique<MockRenderWidgetHostImpl>(
&delegate_, site_instance_group_->GetSafeRef(),
process_host_->GetNextRoutingID(),
- /*for_frame_widget=*/true);
+ /*for_frame_widget=*/true, &contents()->GetPrimaryFrameTree());
host_->set_owner_delegate(&mock_owner_delegate_);
delegate_.set_focused_widget(host_.get());
rwhv_mac_ = new RenderWidgetHostViewMac(host_.get());
@@ -2518,4 +2522,78 @@
EXPECT_NSEQ([view accessibilityParent], parent_view);
}
+class FakeTextInputClientMacDelegate
+ : public TextInputClientMac::AsyncRequestDelegate {
+ public:
+ FakeTextInputClientMacDelegate() = default;
+ ~FakeTextInputClientMacDelegate() override = default;
+
+ void SetResponseRect(const gfx::Rect& rect) { response_rect_ = rect; }
+
+ void GetCharacterIndexAtPoint(
+ RenderFrameHost* rfh,
+ const TextInputClientMac::RequestToken& request_token,
+ const gfx::Point& point) override {
+ FAIL() << "Unexpected call to GetCharacterIndexAtPoint";
+ }
+
+ void GetFirstRectForRange(
+ RenderFrameHost* rfh,
+ const TextInputClientMac::RequestToken& request_token,
+ const gfx::Range& range) override {
+ TextInputClientMac::GetInstance()->SetFirstRectWhileLockedForTesting(
+ request_token, response_rect_);
+ }
+
+ private:
+ gfx::Rect response_rect_;
+};
+
+TEST_F(RenderWidgetHostViewMacTest, SyncGetFirstRectForRange_Clamped) {
+ base::test::ScopedFeatureList feature_list;
+ feature_list.InitWithFeatures(
+ /*enabled_features=*/{},
+ /*disabled_features=*/{
+ features::kCachedFirstRectAllowRangeOutsideSelection,
+ features::kCachedFirstRectAllowInvalidSelection});
+
+ // Focus the root frame tree node so GetFocusedRenderFrameHostImpl succeeds.
+ contents()->GetPrimaryFrameTree().SetFocusedFrame(
+ contents()->GetPrimaryFrameTree().root(), nullptr);
+
+ // Set the view bounds to a known size.
+ rwhv_mac_->SetBounds(gfx::Rect(0, 0, 800, 600));
+
+ // Create a fake delegate that returns an out-of-bounds rect.
+ // Forged rect: x=-100, y=-200, w=10, h=20 (in physical pixels).
+ float dsf = rwhv_mac_->GetDeviceScaleFactor();
+ gfx::Rect forged_rect_in_pixels(-100, -200, 10, 20);
+
+ auto fake_delegate = std::make_unique<FakeTextInputClientMacDelegate>();
+ fake_delegate->SetResponseRect(forged_rect_in_pixels);
+
+ TextInputClientMac::GetInstance()->SetAsyncRequestDelegateForTesting(
+ std::move(fake_delegate));
+
+ gfx::Rect rect;
+ gfx::Range actual_range;
+ bool success = false;
+
+ // Call the method under test.
+ rwhv_mac_->SyncGetFirstRectForRange(gfx::Range(1, 2), &rect, &actual_range,
+ &success);
+
+ EXPECT_TRUE(success);
+
+ // Expected clamped rect (in DIPs).
+ // Clamped to viewport (0, 0, 800, 600):
+ // X should be clamped to 0.
+ // Y should be clamped to 0.
+ gfx::Rect expected_rect(0, 0, 10 / dsf, 20 / dsf);
+ EXPECT_EQ(rect, expected_rect);
+
+ // Restore default delegate.
+ TextInputClientMac::GetInstance()->SetAsyncRequestDelegateForTesting(nullptr);
+}
+
} // namespace content
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