Chrome · Input
CVE-2026-112993
UAF in Input
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Files Changed
third_party/blink/renderer/platform/runtime_enabled_features.json5third_party/blink/renderer/core/input/gesture_manager.ccthird_party/blink/renderer/core/input/event_handler_test.ccthird_party/blink/renderer/core/exported/web_view_test.ccthird_party/blink/renderer/core/editing/selection_template.ccthird_party/blink/renderer/core/editing/selection_controller.cc
Patch
From c0e830bdda6e389c8c8c1b5c5174793f853a55fa Mon Sep 17 00:00:00 2001 From: Kent Tamura <[email protected]> Date: Tue, 13 Oct 2026 04:44:56 -0700 Subject: [PATCH] Prevent cross-frame selection on GestureLongPress When an event handler for `gesturelongpress` mutates the DOM (e.g. removing an overlay element above an `<iframe>`), the subsequent hit test in GestureManager::HandleGestureLongPress() could descend into a child frame and set a cross-document selection on the parent frame's FrameSelection. * gesture_manager.cc: Use event_handling_util::HitTestResultInFrame() instead of EventHandler::HitTestResultAtLocation() in GestureManager::HandleGestureLongPress() so the re-hit-test stays within the handling frame and does not descend into child frames. This is the main fix of the issue. * web_view_test.cc: Update WebViewTest.LongPressLink to long-press at (499, 299) on the link inside the 500x300 viewport instead of out-of-bounds at (500, 300). This test worked by accident because HitTestResultAtLocation() doesn't have viewport bounds check. * selection_controller.cc: Verify in `SelectionController:: UpdateSelectionForMouseDownDispatchingSelectStart()` that `target_node` and `selection` belong to GetDocument() before and after dispatching `selectstart`, matching UpdateSelectionForMouseDrag(). This is an optional change for defence-in-depth. * selection_template.cc: Upgrade DCHECK_EQ() to CHECK_EQ() in SelectionTemplate::AssertValidFor() to prevent setting a selection whose document does not match the FrameSelection's document. This is another optional change for defence-in-depth. Bug: 572893186 Change-Id: I2e51c75838351009987dc7820a7fe3d2cef944ac Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8561139 Auto-Submit: Kent Tamura <[email protected]> Commit-Queue: Koji Ishii <[email protected]> Reviewed-by: Koji Ishii <[email protected]> Cr-Commit-Position: refs/heads/main@{#1717499} --- diff --git a/third_party/blink/renderer/core/editing/selection_controller.cc b/third_party/blink/renderer/core/editing/selection_controller.cc index d54fb10..0515079 100644 --- a/third_party/blink/renderer/core/editing/selection_controller.cc +++ b/third_party/blink/renderer/core/editing/selection_controller.cc @@ -723,6 +723,13 @@ Node* target_node, const SelectionInFlatTree& selection, const SetSelectionOptions& set_selection_options) { + if (RuntimeEnabledFeatures::LongPressHitTestInFrameEnabled()) { + if ((target_node && target_node->GetDocument() != GetDocument()) || + !selection.Anchor().IsValidFor(GetDocument()) || + !selection.Focus().IsValidFor(GetDocument())) { + return false; + } + } if (target_node && target_node->GetLayoutObject() && !target_node->GetLayoutObject()->IsSelectable()) return false; @@ -736,6 +743,11 @@ // |DispatchSelectStart()| can change document hosted by |frame_|. if (!Selection().IsAvailable()) return false; + if (RuntimeEnabledFeatures::LongPressHitTestInFrameEnabled() && + (!selection.Anchor().IsValidFor(GetDocument()) || + !selection.Focus().IsValidFor(GetDocument()))) { + return false; + } // TODO(editing-dev): Use of UpdateStyleAndLayout // needs to be audited. See http://crbug.com/590369 for more details. diff --git a/third_party/blink/renderer/core/editing/selection_template.cc b/third_party/blink/renderer/core/editing/selection_template.cc index 988fad25..27916aa 100644 --- a/third_party/blink/renderer/core/editing/selection_template.cc +++ b/third_party/blink/renderer/core/editing/selection_template.cc @@ -10,6 +10,7 @@ #include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/editing/ephemeral_range.h" #include "third_party/blink/renderer/core/editing/position_with_affinity.h" +#include "third_party/blink/renderer/platform/runtime_enabled_features.h" namespace blink { @@ -96,7 +97,11 @@ if (anchor_.IsNull()) { return true; } - DCHECK_EQ(anchor_.GetDocument(), document) << *this; + if (RuntimeEnabledFeatures::LongPressHitTestInFrameEnabled()) { + CHECK_EQ(anchor_.GetDocument(), document) << *this; + } else { + DCHECK_EQ(anchor_.GetDocument(), document) << *this; + } return true; } diff --git a/third_party/blink/renderer/core/input/gesture_manager.cc b/third_party/blink/renderer/core/input/gesture_manager.cc index 976ab7e..1b44847 100644 --- a/third_party/blink/renderer/core/input/gesture_manager.cc +++ b/third_party/blink/renderer/core/input/gesture_manager.cc @@ -493,10 +493,24 @@ // scenario that it's unlikely to matter much in practice. long_press_position_in_root_frame_ = gesture_event.PositionInRootFrame(); - HitTestLocation location(frame_->View()->ConvertFromRootFrame( - gfx::ToFlooredPoint(long_press_position_in_root_frame_))); - HitTestResult hit_test_result = - frame_->GetEventHandler().HitTestResultAtLocation(location); + HitTestResult hit_test_result; + if (RuntimeEnabledFeatures::LongPressHitTestInFrameEnabled()) { + LocalFrame& main_frame = frame_->LocalFrameRoot(); + if (!frame_->View() || !main_frame.View() || + !main_frame.View()->UpdateAllLifecyclePhasesExceptPaint( + DocumentUpdateReason::kHitTest)) { + return WebInputEventResult::kNotHandled; + } + HitTestLocation location(frame_->View()->ConvertFromRootFrame( + gfx::ToFlooredPoint(long_press_position_in_root_frame_))); + hit_test_result = event_handling_util::HitTestResultInFrame( + frame_, location, GetHitTypeForGestureType(gesture_event.GetType())); + } else { + HitTestLocation location(frame_->View()->ConvertFromRootFrame( + gfx::ToFlooredPoint(long_press_position_in_root_frame_))); + hit_test_result = + frame_->GetEventHandler().HitTestResultAtLocation(location); + } gesture_context_menu_deferred_ = false; diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5 index 18cf673..6f9164d44 100644 --- a/third_party/blink/renderer/platform/runtime_enabled_features.json5 +++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5 @@ -4202,6 +4202,11 @@ status: "test", }, { + // https://crbug.com/572893186 + name: "LongPressHitTestInFrame", + status: "stable", + }, + { // Long press on a link selects the link text instead of bringing up // context menu. Enabled only on Android AuthView. name: "LongPressLinkSelectText", diff --git a/third_party/blink/renderer/core/exported/web_view_test.cc b/third_party/blink/renderer/core/exported/web_view_test.cc index d4a2932..2ccdddc 100644 --- a/third_party/blink/renderer/core/exported/web_view_test.cc +++ b/third_party/blink/renderer/core/exported/web_view_test.cc @@ -4223,7 +4223,7 @@ WebInputEvent::kNoModifiers, WebInputEvent::GetStaticTimeStampForTests(), WebGestureDevice::kTouchscreen); - event.SetPositionInWidget(gfx::PointF(500, 300)); + event.SetPositionInWidget(gfx::PointF(499, 299)); EXPECT_EQ(WebInputEventResult::kHandledSystem, web_view->MainFrameWidget()->HandleInputEvent( diff --git a/third_party/blink/renderer/core/input/event_handler_test.cc b/third_party/blink/renderer/core/input/event_handler_test.cc index 302d3e6..a4cf128 100644 --- a/third_party/blink/renderer/core/input/event_handler_test.cc +++ b/third_party/blink/renderer/core/input/event_handler_test.cc @@ -4412,4 +4412,53 @@ EXPECT_TRUE(popover->popoverOpen()); } +// https://crbug.com/572893186 +TEST_F(EventHandlerSimTest, + LongPressRemovingOverlayDoesNotSelectCrossFrameContent) { + WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600)); + + SimRequest main_resource("https://example.com/test.html", "text/html"); + SimRequest frame_resource("https://cross-origin.com/iframe.html", + "text/html"); + LoadURL("https://example.com/test.html"); + main_resource.Complete(R"HTML( + <!DOCTYPE html> + <style> + body { margin: 0; } + #iframe, #overlay { + position: absolute; + top: 0; + left: 0; + width: 200px; + height: 200px; + border: none; + } + </style> + <iframe id="iframe" src="https://cross-origin.com/iframe.html"></iframe> + <div id="overlay">overlay</div> + <script> + const overlay = document.getElementById('overlay'); + overlay.addEventListener('gesturelongpress', () => { + overlay.remove(); + }); + </script> + )HTML"); + frame_resource.Complete(R"HTML( + <!DOCTYPE html> + <style>body { margin: 0; font-size: 20px; }</style> + <div id="victim">SELECTABLE</div> + )HTML"); + Compositor().BeginFrame(); + + LongPressEventBuilder long_press(gfx::PointF(20, 10)); + GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(long_press); + + EXPECT_FALSE(GetDocument().getElementById(AtomicString("overlay"))); + const SelectionInDomTree& main_selection = + GetDocument().GetFrame()->Selection().GetSelectionInDomTree(); + EXPECT_TRUE(main_selection.Anchor().IsValidFor(GetDocument())); + EXPECT_TRUE(main_selection.Focus().IsValidFor(GetDocument())); + EXPECT_EQ("", GetDocument().GetFrame()->Selection().SelectedText()); +} + } // namespace blink
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/third_party/blink/renderer/core/exported/web_view_test.cc b/third_party/blink/renderer/core/exported/web_view_test.cc
index d4a2932..2ccdddc 100644
--- a/third_party/blink/renderer/core/exported/web_view_test.cc
+++ b/third_party/blink/renderer/core/exported/web_view_test.cc
@@ -4223,7 +4223,7 @@
WebInputEvent::kNoModifiers,
WebInputEvent::GetStaticTimeStampForTests(),
WebGestureDevice::kTouchscreen);
- event.SetPositionInWidget(gfx::PointF(500, 300));
+ event.SetPositionInWidget(gfx::PointF(499, 299));
EXPECT_EQ(WebInputEventResult::kHandledSystem,
web_view->MainFrameWidget()->HandleInputEvent(
diff --git a/third_party/blink/renderer/core/input/event_handler_test.cc b/third_party/blink/renderer/core/input/event_handler_test.cc
index 302d3e6..a4cf128 100644
--- a/third_party/blink/renderer/core/input/event_handler_test.cc
+++ b/third_party/blink/renderer/core/input/event_handler_test.cc
@@ -4412,4 +4412,53 @@
EXPECT_TRUE(popover->popoverOpen());
}
+// https://crbug.com/572893186
+TEST_F(EventHandlerSimTest,
+ LongPressRemovingOverlayDoesNotSelectCrossFrameContent) {
+ WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 600));
+
+ SimRequest main_resource("https://example.com/test.html", "text/html");
+ SimRequest frame_resource("https://cross-origin.com/iframe.html",
+ "text/html");
+ LoadURL("https://example.com/test.html");
+ main_resource.Complete(R"HTML(
+ <!DOCTYPE html>
+ <style>
+ body { margin: 0; }
+ #iframe, #overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 200px;
+ height: 200px;
+ border: none;
+ }
+ </style>
+ <iframe id="iframe" src="https://cross-origin.com/iframe.html"></iframe>
+ <div id="overlay">overlay</div>
+ <script>
+ const overlay = document.getElementById('overlay');
+ overlay.addEventListener('gesturelongpress', () => {
+ overlay.remove();
+ });
+ </script>
+ )HTML");
+ frame_resource.Complete(R"HTML(
+ <!DOCTYPE html>
+ <style>body { margin: 0; font-size: 20px; }</style>
+ <div id="victim">SELECTABLE</div>
+ )HTML");
+ Compositor().BeginFrame();
+
+ LongPressEventBuilder long_press(gfx::PointF(20, 10));
+ GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(long_press);
+
+ EXPECT_FALSE(GetDocument().getElementById(AtomicString("overlay")));
+ const SelectionInDomTree& main_selection =
+ GetDocument().GetFrame()->Selection().GetSelectionInDomTree();
+ EXPECT_TRUE(main_selection.Anchor().IsValidFor(GetDocument()));
+ EXPECT_TRUE(main_selection.Focus().IsValidFor(GetDocument()));
+ EXPECT_EQ("", GetDocument().GetFrame()->Selection().SelectedText());
+}
+
} // namespace blink
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