CVE-2026-79248
Overview
Files Changed
components/input/render_widget_host_input_event_router.cccomponents/input/render_widget_host_input_event_router.hcontent/browser/fenced_frame/fenced_frame_browsertest.cccontent/browser/renderer_host/render_widget_host_impl.cc
Patch
From 8de7cce11c3f5b4d9bc52ea323b795288369db99 Mon Sep 17 00:00:00 2001
From: Tzarial <zork@google.com>
Date: Fri, 17 Jul 2026 16:13:16 -0700
Subject: [PATCH] [agy][content] Restrict autoscroll to outermost
Restrict SetAutoscrollSelectionActiveInMainFrame to only be
honored when called from the outermost main frame.
Inner main frames (like fenced frames or guest views) share the
outer WebContents' input event router but should not be allowed
to trigger mouse-up routing to the outermost root view.
Fixed: 502918844
Test: FencedFrameMPArchBrowserTest.AutoscrollSelectionFromFencedFrameIgnored
Change-Id: I199e18eb9e0da24da1e27cf6a0c436c6b9faba0a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8086442
Commit-Queue: Tzarial <zork@chromium.org>
Reviewed-by: Bo Liu <boliu@chromium.org>
Reviewed-by: Kartar Singh <kartarsingh@google.com>
Cr-Commit-Position: refs/heads/main@{#1664233}
---
diff --git a/components/input/render_widget_host_input_event_router.cc b/components/input/render_widget_host_input_event_router.cc
index 3490fab..3595a0b 100644
--- a/components/input/render_widget_host_input_event_router.cc
+++ b/components/input/render_widget_host_input_event_router.cc
@@ -730,7 +730,7 @@
if (root_view_receive_additional_mouse_up_ && target != root_view &&
mouse_event.GetType() == blink::WebInputEvent::Type::kMouseUp) {
- root_view->ProcessMouseEvent(event, latency);
+ root_view->ProcessMouseEvent(mouse_event, latency);
}
}
diff --git a/components/input/render_widget_host_input_event_router.h b/components/input/render_widget_host_input_event_router.h
index 69510dc5..64f38be 100644
--- a/components/input/render_widget_host_input_event_router.h
+++ b/components/input/render_widget_host_input_event_router.h
@@ -55,6 +55,8 @@
FORWARD_DECLARE_TEST(
WebContentsImplBrowserTest,
MouseUpInOOPIframeShouldCancelMainFrameAutoscrollSelection);
+FORWARD_DECLARE_TEST(FencedFrameMPArchBrowserTest,
+ AutoscrollSelectionFromFencedFrameIgnored);
FORWARD_DECLARE_TEST(SitePerProcessHitTestBrowserTest,
CacheCoordinateTransformUponMouseDown);
FORWARD_DECLARE_TEST(SitePerProcessHitTestBrowserTest,
@@ -266,6 +268,8 @@
FRIEND_TEST_ALL_PREFIXES(
content::WebContentsImplBrowserTest,
MouseUpInOOPIframeShouldCancelMainFrameAutoscrollSelection);
+ FRIEND_TEST_ALL_PREFIXES(content::FencedFrameMPArchBrowserTest,
+ AutoscrollSelectionFromFencedFrameIgnored);
using FrameSinkIdOwnerMap =
std::unordered_map<viz::FrameSinkId,
diff --git a/content/browser/fenced_frame/fenced_frame_browsertest.cc b/content/browser/fenced_frame/fenced_frame_browsertest.cc
index c6163d6..5d450e59 100644
--- a/content/browser/fenced_frame/fenced_frame_browsertest.cc
+++ b/content/browser/fenced_frame/fenced_frame_browsertest.cc
@@ -21,6 +21,7 @@
#include "base/time/time.h"
#include "build/build_config.h"
#include "build/buildflag.h"
+#include "components/input/render_widget_host_input_event_router.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/browser/back_forward_cache_browsertest.h"
@@ -31,6 +32,8 @@
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_frame_proxy_host.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/browser/renderer_host/render_widget_host_view_base.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/features.h"
#include "content/public/browser/browser_context.h"
@@ -1118,6 +1121,79 @@
fenced_frame_rfh->GetRenderWidgetHost()->ForwardMouseEvent(mouse_event);
}
+// Tests that a SetAutoscrollSelectionActiveInMainFrame request from a fenced
+// frame's main-frame widget does not affect mouse-up routing to the outer
+// root view. This should only be honored for the outermost main frame.
+IN_PROC_BROWSER_TEST_F(FencedFrameMPArchBrowserTest,
+ AutoscrollSelectionFromFencedFrameIgnored) {
+ ASSERT_TRUE(https_server()->Start());
+ const GURL main_url = https_server()->GetURL("c.test", "/title1.html");
+ EXPECT_TRUE(NavigateToURL(shell(), main_url));
+ RenderFrameHostImplWrapper primary_rfh(primary_main_frame_host());
+
+ const GURL fenced_frame_url =
+ https_server()->GetURL("c.test", "/fenced_frames/title1.html");
+ RenderFrameHostImplWrapper fenced_frame_rfh(
+ fenced_frame_test_helper().CreateFencedFrame(primary_rfh.get(),
+ fenced_frame_url));
+
+ RenderWidgetHostImpl* fenced_frame_rwh =
+ fenced_frame_rfh->GetRenderWidgetHost();
+ RenderWidgetHostImpl* primary_rwh = primary_rfh->GetRenderWidgetHost();
+
+ ASSERT_TRUE(fenced_frame_rwh->owner_delegate());
+ ASSERT_TRUE(fenced_frame_rwh->GetView()->IsRenderWidgetHostViewChildFrame());
+
+ input::RenderWidgetHostInputEventRouter* router =
+ web_contents()->GetInputEventRouter();
+
+ RenderWidgetHostMouseEventMonitor primary_monitor(primary_rwh);
+ RenderWidgetHostMouseEventMonitor fenced_frame_monitor(fenced_frame_rwh);
+
+ // 1. Simulate the request arriving from the fenced frame's renderer.
+ // It should be ignored.
+ fenced_frame_rwh->SetAutoscrollSelectionActiveInMainFrame(true);
+
+ // Dispatch MouseUp to fenced frame.
+ blink::WebMouseEvent mouse_up(
+ blink::WebInputEvent::Type::kMouseUp, blink::WebInputEvent::kNoModifiers,
+ blink::WebInputEvent::GetStaticTimeStampForTests());
+ mouse_up.button = blink::WebPointerProperties::Button::kLeft;
+ mouse_up.SetPositionInWidget(10, 20);
+ gfx::PointF target_location(5, 8);
+
+ router->DispatchMouseEvent(primary_rwh->GetView(),
+ fenced_frame_rwh->GetView(), mouse_up,
+ ui::LatencyInfo(), target_location);
+
+ // Fenced frame should receive it at target_location.
+ EXPECT_TRUE(fenced_frame_monitor.EventWasReceived());
+ EXPECT_EQ(fenced_frame_monitor.event().PositionInWidget().x(), 5);
+ EXPECT_EQ(fenced_frame_monitor.event().PositionInWidget().y(), 8);
+ // Primary main frame should NOT receive it.
+ EXPECT_FALSE(primary_monitor.EventWasReceived());
+
+ // Reset monitors.
+ fenced_frame_monitor.ResetEventReceived();
+ primary_monitor.ResetEventReceived();
+
+ // 2. The outermost main frame's request should still be honored.
+ primary_rwh->SetAutoscrollSelectionActiveInMainFrame(true);
+
+ router->DispatchMouseEvent(primary_rwh->GetView(),
+ fenced_frame_rwh->GetView(), mouse_up,
+ ui::LatencyInfo(), target_location);
+
+ // Fenced frame should receive it at target_location.
+ EXPECT_TRUE(fenced_frame_monitor.EventWasReceived());
+ EXPECT_EQ(fenced_frame_monitor.event().PositionInWidget().x(), 5);
+ EXPECT_EQ(fenced_frame_monitor.event().PositionInWidget().y(), 8);
+ // Primary main frame should ALSO receive it, but at original coordinates.
+ EXPECT_TRUE(primary_monitor.EventWasReceived());
+ EXPECT_EQ(primary_monitor.event().PositionInWidget().x(), 10);
+ EXPECT_EQ(primary_monitor.event().PositionInWidget().y(), 20);
+}
+
// Test that WebContents::GetFocusedFrame includes results from a fenced
// frame's frame tree.
IN_PROC_BROWSER_TEST_F(FencedFrameMPArchBrowserTest,
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index 189a571..9f7e49d 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -3517,6 +3517,14 @@
return;
}
+ // Only the outermost main frame should request this. Main frames of inner
+ // frame trees (e.g. fenced frames, guest views) share the outer WebContents'
+ // input event router, but should not be allowed to trigger mouse-up routing
+ // to the outermost root view.
+ if (!frame_tree_ || !frame_tree_->is_primary()) {
+ return;
+ }
+
if (!delegate_ || !delegate_->GetInputEventRouter()) {
return;
}
Regression Test / PoC
diff --git a/content/browser/fenced_frame/fenced_frame_browsertest.cc b/content/browser/fenced_frame/fenced_frame_browsertest.cc
index c6163d6..5d450e59 100644
--- a/content/browser/fenced_frame/fenced_frame_browsertest.cc
+++ b/content/browser/fenced_frame/fenced_frame_browsertest.cc
@@ -21,6 +21,7 @@
#include "base/time/time.h"
#include "build/build_config.h"
#include "build/buildflag.h"
+#include "components/input/render_widget_host_input_event_router.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/browser/back_forward_cache_browsertest.h"
@@ -31,6 +32,8 @@
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_frame_proxy_host.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/browser/renderer_host/render_widget_host_view_base.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/features.h"
#include "content/public/browser/browser_context.h"
@@ -1118,6 +1121,79 @@
fenced_frame_rfh->GetRenderWidgetHost()->ForwardMouseEvent(mouse_event);
}
+// Tests that a SetAutoscrollSelectionActiveInMainFrame request from a fenced
+// frame's main-frame widget does not affect mouse-up routing to the outer
+// root view. This should only be honored for the outermost main frame.
+IN_PROC_BROWSER_TEST_F(FencedFrameMPArchBrowserTest,
+ AutoscrollSelectionFromFencedFrameIgnored) {
+ ASSERT_TRUE(https_server()->Start());
+ const GURL main_url = https_server()->GetURL("c.test", "/title1.html");
+ EXPECT_TRUE(NavigateToURL(shell(), main_url));
+ RenderFrameHostImplWrapper primary_rfh(primary_main_frame_host());
+
+ const GURL fenced_frame_url =
+ https_server()->GetURL("c.test", "/fenced_frames/title1.html");
+ RenderFrameHostImplWrapper fenced_frame_rfh(
+ fenced_frame_test_helper().CreateFencedFrame(primary_rfh.get(),
+ fenced_frame_url));
+
+ RenderWidgetHostImpl* fenced_frame_rwh =
+ fenced_frame_rfh->GetRenderWidgetHost();
+ RenderWidgetHostImpl* primary_rwh = primary_rfh->GetRenderWidgetHost();
+
+ ASSERT_TRUE(fenced_frame_rwh->owner_delegate());
+ ASSERT_TRUE(fenced_frame_rwh->GetView()->IsRenderWidgetHostViewChildFrame());
+
+ input::RenderWidgetHostInputEventRouter* router =
+ web_contents()->GetInputEventRouter();
+
+ RenderWidgetHostMouseEventMonitor primary_monitor(primary_rwh);
+ RenderWidgetHostMouseEventMonitor fenced_frame_monitor(fenced_frame_rwh);
+
+ // 1. Simulate the request arriving from the fenced frame's renderer.
+ // It should be ignored.
+ fenced_frame_rwh->SetAutoscrollSelectionActiveInMainFrame(true);
+
+ // Dispatch MouseUp to fenced frame.
+ blink::WebMouseEvent mouse_up(
+ blink::WebInputEvent::Type::kMouseUp, blink::WebInputEvent::kNoModifiers,
+ blink::WebInputEvent::GetStaticTimeStampForTests());
+ mouse_up.button = blink::WebPointerProperties::Button::kLeft;
+ mouse_up.SetPositionInWidget(10, 20);
+ gfx::PointF target_location(5, 8);
+
+ router->DispatchMouseEvent(primary_rwh->GetView(),
+ fenced_frame_rwh->GetView(), mouse_up,
+ ui::LatencyInfo(), target_location);
+
+ // Fenced frame should receive it at target_location.
+ EXPECT_TRUE(fenced_frame_monitor.EventWasReceived());
+ EXPECT_EQ(fenced_frame_monitor.event().PositionInWidget().x(), 5);
+ EXPECT_EQ(fenced_frame_monitor.event().PositionInWidget().y(), 8);
+ // Primary main frame should NOT receive it.
+ EXPECT_FALSE(primary_monitor.EventWasReceived());
+
+ // Reset monitors.
+ fenced_frame_monitor.ResetEventReceived();
+ primary_monitor.ResetEventReceived();
+
+ // 2. The outermost main frame's request should still be honored.
+ primary_rwh->SetAutoscrollSelectionActiveInMainFrame(true);
+
+ router->DispatchMouseEvent(primary_rwh->GetView(),
+ fenced_frame_rwh->GetView(), mouse_up,
+ ui::LatencyInfo(), target_location);
+
+ // Fenced frame should receive it at target_location.
+ EXPECT_TRUE(fenced_frame_monitor.EventWasReceived());
+ EXPECT_EQ(fenced_frame_monitor.event().PositionInWidget().x(), 5);
+ EXPECT_EQ(fenced_frame_monitor.event().PositionInWidget().y(), 8);
+ // Primary main frame should ALSO receive it, but at original coordinates.
+ EXPECT_TRUE(primary_monitor.EventWasReceived());
+ EXPECT_EQ(primary_monitor.event().PositionInWidget().x(), 10);
+ EXPECT_EQ(primary_monitor.event().PositionInWidget().y(), 20);
+}
+
// Test that WebContents::GetFocusedFrame includes results from a fenced
// frame's frame tree.
IN_PROC_BROWSER_TEST_F(FencedFrameMPArchBrowserTest,
Original Bug Report
Event Spoofing: Subframes can inject cross-origin MouseUp events into embedder via IPC
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the Chrome Security team. Please see go/chrome-ai-generated-security-bugs-faq for more information.
Overview: A logic flaw in input event routing allows Fenced Frames and Guest Views to bypass main-frame restrictions and enable autoscroll event duplication. When enabled, a MouseUp event targeting the subframe is duplicated to the embedder without reverse coordinate transformation. This allows a compromised subframe to trigger onmouseup handlers at attacker-controlled coordinates in the cross-origin embedder.
Affected files:
components/input/render_widget_host_input_event_router.cccontent/browser/renderer_host/render_widget_host_impl.cc
Estimated timestamp from git blame: 2023-12-14
Summary
A potential logic vulnerability exists in the browser’s input event routing mechanism that allows a compromised renderer (specifically Guest main frames or Fenced Frame main frames) to inject cross-origin MouseUp events into its embedder. This occurs due to insufficient caller validation in RenderWidgetHostImpl::SetAutoscrollSelectionActiveInMainFrame and a coordinate transformation bug in RenderWidgetHostInputEventRouter::DispatchMouseEvent.
Technical Details
When a renderer performs an autoscroll-for-selection operation, it notifies the browser to duplicate subsequent MouseUp events to the root view. This ensures the top-level page knows the mouse was released even if it happens outside the scrolling iframe. The notification is sent via the WidgetInputHandlerHost::SetAutoscrollSelectionActiveInMainFrame Mojo message, handled by RenderWidgetHostImpl::SetAutoscrollSelectionActiveInMainFrame.
// content/browser/renderer_host/render_widget_host_impl.cc
void RenderWidgetHostImpl::SetAutoscrollSelectionActiveInMainFrame(
bool autoscroll_selection) {
// If there is no |owner_delegate|, this is not a main frame.
if (!owner_delegate()) {
mojo::ReportBadMessage(...);
return;
}
// ...
delegate_->GetInputEventRouter()->RootViewReceivesMouseUpIfNecessary(
autoscroll_selection);
}
The if (!owner_delegate()) check is intended to restrict this call to the outermost main frame. However, Guest main frames (e.g., <webview>) and Fenced Frame main frames possess their own FrameTree and RenderViewHostImpl, meaning they have a non-null owner_delegate_. This allows a compromised subframe renderer to bypass the check and set the root_view_receive_additional_mouse_up_ flag on the shared RenderWidgetHostInputEventRouter.
When a MouseUp event subsequently occurs within the compromised frame, the router duplicates the event to the root view:
// components/input/render_widget_host_input_event_router.cc
void RenderWidgetHostInputEventRouter::DispatchMouseEvent(...) {
// ...
blink::WebMouseEvent event = mouse_event;
event.SetPositionInWidget(target_location->x(), target_location->y());
target->ProcessMouseEvent(event, latency);
if (root_view_receive_additional_mouse_up_ && target != root_view &&
mouse_event.GetType() == blink::WebInputEvent::Type::kMouseUp) {
root_view->ProcessMouseEvent(event, latency);
}
}
Crucially, the event passed to root_view->ProcessMouseEvent at line 715 has already been transformed into the target subframe’s local coordinate space at line 697. Because the root_view (embedder) interprets these coordinates relative to its own top-left origin, the compromised renderer can inject MouseUp events at attacker-controlled positions within the embedder. The injectible coordinates are bounded by (0 <= x <= subframe_width, 0 <= y <= subframe_height).
Potential Attack Steps
- An attacker compromises a Fenced Frame or Guest View renderer process embedded on a target site.
- The compromised renderer sends the
SetAutoscrollSelectionActiveInMainFrame(true)Mojo IPC. - The attacker positions UI elements in their subframe to entice a user click at specific local coordinates
(x, y). - When the user clicks and releases the mouse over the subframe, the browser routes the
MouseUpto the subframe normally. - Due to the manipulated flag, the browser also routes a
MouseUpevent to the embedder. - The embedder’s renderer receives a
MouseUpat coordinates(x, y)relative to its own origin, potentially triggering sensitiveonmouseupevent handlers. (Note: A full DOMclickevent is typically not synthesized by Blink because the embedder never received the correspondingMouseDown, limiting the impact to raw pointer/mouse release handlers.)
Suggested Fix
Validation should be tightened to ensure only the primary main frame can enable this state. A robust check would be to verify that the requesting widget is not for a child frame:
if (!owner_delegate() || GetView()->IsRenderWidgetHostViewChildFrame()) {
mojo::ReportBadMessage(...);
return;
}
Alternatively, delegate_->IsWidgetForPrimaryMainFrame(this) could be used if appropriate.
Additionally, DispatchMouseEvent should be fixed so that if an event must be duplicated to the root view, it uses coordinates transformed into the root view’s coordinate space (or simply uses the original mouse_event which is already in root coordinates), rather than the target’s local coordinates.
Evaluated with Chrome root at commit: 661452647ddb2827305122ff3273bd5dea403f09
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.