Low watch CVSS 4.3 chrome Spoofing 🔧 Commit mapped

Overview

Low
Severity
4.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUI misrepresentation in Core
DescriptionUI misrepresentation in Core
ComponentCore
Sub-componentContent
Bug ClassSpoofing
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker519210950
Fix commit0eb9d43c1318 (chromium/src) +86/-3
CWECWE-451
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-06-02 (per vendor advisory)
Fix landed2026-07-15 (public git)
Disclosed2026-08-25
Patch gap41 days — fix public in source before the advisory

Background

IME candidate window
The OS-drawn input-method editor popup on macOS, positioned using a screen rect the browser reports for the current text range.
`SyncGetFirstRectForRange`
The RenderWidgetHostViewMac method that returns the first bounding rect for a text range so the OS can place the IME candidate window; the rect originates from the renderer.
`GetVisibleViewportSize`
The browser-side accessor for the current visible viewport dimensions, i.e. the trusted bounds the IME window should stay within.
`gfx::Rect::AdjustToFit`
A geometry helper that moves/clamps a rect so it lies within a bounding rectangle.

Root Cause Analysis

RenderWidgetHostViewMac::SyncGetFirstRectForRange (the enclosing function is named in the commit/test, not the hunk header) computed *rect from a renderer-supplied blink_rect, scaling it by 1.f / device_scale_factor, and returned it to the OS without bounding it to the page’s viewport. Because the renderer is untrusted and can be compromised, it could supply arbitrary coordinates (the test forges x=-100, y=-200), causing the browser to place the IME candidate window anywhere on screen, including outside the webpage viewport. The violated invariant is that browser-reported UI geometry driving OS chrome must be constrained to the page’s own visible area.

The fix appends rect->AdjustToFit(gfx::Rect(GetVisibleViewportSize())) just before return true;, clamping the final rect to the viewport so out-of-viewport coordinates are pulled back in (the test expects the forged negative origin clamped to (0,0)). This is a tightening: an unbounded value is now constrained.

Key insight
The browser trusted a renderer-supplied rect verbatim to position OS-level IME UI; the fix clamps that rect to GetVisibleViewportSize() via AdjustToFit before returning, so a compromised renderer can no longer draw the IME window outside the page viewport.

Attack Path

  1. Compromise the renderer An attacker achieves a compromised/controlled renderer process capable of responding to first-rect requests with arbitrary geometry.
  2. Forge an out-of-bounds rect When the OS requests the first rect for an IME range, the renderer returns coordinates outside the viewport (e.g. negative origin), which SyncGetFirstRectForRange scales and passes through unclamped.
  3. Mis-place the IME window macOS positions the IME candidate window at the attacker-chosen location outside the page viewport.
  4. Spoof UI The mispositioned candidate window can misrepresent UI relative to the page or surrounding browser chrome, the spoofing/UI-misrepresentation impact the advisory describes.

Impact Assessment

A compromised renderer can position the macOS IME candidate window outside the page viewport, enabling UI misrepresentation/spoofing as the advisory states; the impact is confined to on-screen placement of OS IME chrome. Preconditions are macOS and a renderer able to return a forged first rect (an active IME/text-input context). The advisory does not report in-the-wild exploitation, and any use of this to spoof specific security-relevant UI is (inferred).

Changed Functions

FunctionChangeNotes
FakeTextInputClientMacDelegate
content/browser/renderer_host/render_widget_host_view_mac_unittest.mm
modified
TEST_F
content/browser/renderer_host/render_widget_host_view_mac_unittest.mm
modified

Files Changed

  • content/browser/renderer_host/render_widget_host_view_mac.mm
  • content/browser/renderer_host/render_widget_host_view_mac_unittest.mm

Audit Directions

  • Renderer-supplied geometry to OS UI
    Audit other browser paths that forward renderer-provided rects/points to place OS-level surfaces (IME, tooltips, popups) and verify each clamps to trusted viewport/window bounds.
  • Post-scale bounds checks
    Wherever a coordinate is scaled by device_scale_factor from renderer data, confirm a bounds check follows the scaling rather than trusting the raw value.
  • Cross-platform parity
    Check the non-macOS GetFirstRectForRange equivalents for the same missing clamp against GetVisibleViewportSize.
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.