High chrome UAF 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Input
DescriptionUse after free in Input
ComponentInput
Bug ClassUAF
Tracker572893186
Fix commitc0e830bdda6e (chromium/src) +91/-6
CISA KEVNot listed
CreditedArni Hardarson (Neonix Security)
Disclosed2026-10-20

Files Changed

  • third_party/blink/renderer/platform/runtime_enabled_features.json5
  • third_party/blink/renderer/core/input/gesture_manager.cc
  • third_party/blink/renderer/core/input/event_handler_test.cc
  • third_party/blink/renderer/core/exported/web_view_test.cc
  • third_party/blink/renderer/core/editing/selection_template.cc
  • third_party/blink/renderer/core/editing/selection_controller.cc
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.