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 <magchen@chromium.org>
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 <avi@chromium.org>
Commit-Queue: Maggie Chen <magchen@chromium.org>
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