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
Tracker517016413
Fix commit2a0a428cf2ca (chromium/src) +279/-107
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Changed Functions

FunctionChangeNotes
if
components/input/android_input_helper.cc
modified
if
components/input/render_widget_host_input_event_router.cc
modified
GetWeakPtr
components/input/render_widget_host_input_event_router.cc
modified

Files Changed

  • components/input/android_input_helper.cc
  • components/input/android_input_helper.h
  • components/input/render_widget_host_input_event_router.cc
  • components/input/render_widget_host_input_event_router.h
  • components/input/render_widget_host_view_input.cc
From 2a0a428cf2ca95f89344affcb4e3d20d4e659a86 Mon Sep 17 00:00:00 2001
From: Jonathan Ross <[email protected]>
Date: Fri, 12 Jun 2026 10:52:54 -0700
Subject: [PATCH] Harden Touch handling: Refcount FilteredGestureProvider

This CL converts FilteredGestureProvider to be held by scoped_refptr to
prevent any Use-After-Free if its parent class is destroyed during
gesture dispatch.

This CL also adds WeakPtr checks in the input stack (RenderWidgetHostInputEventRouter,
TouchEmulator) to detect self-destruction during dispatch.

Bug: 517016413
Change-Id: I6099fef0805bcdc7c120fc90eb4aaabfb49158e6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7919026
Reviewed-by: Aman Verma <[email protected]>
Commit-Queue: Jonathan Ross <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1646088}
---

diff --git a/components/input/android_input_helper.cc b/components/input/android_input_helper.cc
index ba8884b5..47e5f718 100644
--- a/components/input/android_input_helper.cc
+++ b/components/input/android_input_helper.cc
@@ -12,6 +12,7 @@
 #include "ui/events/blink/blink_event_util.h"
 #include "ui/events/blink/web_input_event_traits.h"
 #include "ui/events/event_utils.h"
+#include "ui/events/gesture_detection/filtered_gesture_provider.h"
 
 namespace input {
 
@@ -57,20 +58,20 @@
 }
 
 void AndroidInputHelper::ResetGestureDetection() {
-  ui::FilteredGestureProvider& gesture_provider =
-      delegate_->GetGestureProvider();
+  scoped_refptr<ui::FilteredGestureProvider> gesture_provider =
+      view_->GetGestureProvider();
 
   const ui::MotionEvent* current_down_event =
-      gesture_provider.GetCurrentDownEvent();
+      gesture_provider->GetCurrentDownEvent();
   if (!current_down_event) {
     // A hard reset ensures prevention of any timer-based events that might fire
     // after a touch sequence has ended.
-    gesture_provider.ResetDetection();
+    gesture_provider->ResetDetection();
     return;
   }
 
   const ui::MotionEvent* last_event =
-      gesture_provider.GetLastEventWithoutHistory();
+      gesture_provider->GetLastEventWithoutHistory();
   CHECK(last_event);
 
   std::unique_ptr<ui::MotionEvent> cancel_event;
@@ -82,7 +83,7 @@
   } else {
     cancel_event = last_event->Cancel();
   }
-  if (gesture_provider.OnTouchEvent(*cancel_event).succeeded) {
+  if (gesture_provider->OnTouchEvent(*cancel_event).succeeded) {
     blink::WebTouchEvent web_event = ui::CreateWebTouchEventFromMotionEvent(
         *cancel_event, false /* may_cause_scrolling */, false /* hovering */);
     RouteOrForwardTouchEvent(web_event);
@@ -127,12 +128,18 @@
   // blocking to the Renderer.
   const bool was_touch_blocked =
       ui::WebInputEventTraits::ShouldBlockEventStream(touch.event);
-  delegate_->GetGestureProvider().OnTouchEventAck(
+  auto weak_this = weak_factory_.GetWeakPtr();
+  scoped_refptr<ui::FilteredGestureProvider> gesture_provider =
+      view_->GetGestureProvider();
+  gesture_provider->OnTouchEventAck(
       touch.event.unique_touch_event_id, event_consumed,
       is_source_touch_event_set_non_blocking,
       was_touch_blocked
           ? std::make_optional(touch.event.GetEventLatencyMetadata())
           : std::nullopt);
+  if (!weak_this) {
+    return;
+  }
   if (touch.event.touch_start_or_first_touch_move && event_consumed &&
       view_->GetViewRenderInputRouter()->delegate() &&
       view_->GetViewRenderInputRouter()->delegate()->GetInputEventRouter()) {
diff --git a/components/input/android_input_helper.h b/components/input/android_input_helper.h
index aca4671..ad063ff 100644
--- a/components/input/android_input_helper.h
+++ b/components/input/android_input_helper.h
@@ -5,9 +5,9 @@
 #ifndef COMPONENTS_INPUT_ANDROID_INPUT_HELPER_H_
 #define COMPONENTS_INPUT_ANDROID_INPUT_HELPER_H_
 
+#include "base/memory/weak_ptr.h"
 #include "components/input/render_widget_host_view_input.h"
 #include "ui/events/android/motion_event_android.h"
-#include "ui/events/gesture_detection/filtered_gesture_provider.h"
 
 namespace input {
 
@@ -19,7 +19,6 @@
    public:
     virtual ~Delegate() = default;
     virtual void SendGestureEvent(const blink::WebGestureEvent& event) = 0;
-    virtual ui::FilteredGestureProvider& GetGestureProvider() = 0;
   };
 
   explicit AndroidInputHelper(RenderWidgetHostViewInput* view,
@@ -57,6 +56,8 @@
   raw_ref<RenderWidgetHostViewInput> view_;
   // |delegate_| is supposed to outlive |this|.
   raw_ref<Delegate> delegate_;
+
+  base::WeakPtrFactory<AndroidInputHelper> weak_factory_{this};
 };
 
 }  // namespace input
diff --git a/components/input/render_widget_host_input_event_router.cc b/components/input/render_widget_host_input_event_router.cc
index 6008f2c..3490fab 100644
--- a/components/input/render_widget_host_input_event_router.cc
+++ b/components/input/render_widget_host_input_event_router.cc
@@ -185,10 +185,18 @@
   if (ack_queue_.empty())
     return;
 
+  base::WeakPtr<RenderWidgetHostInputEventRouter> weak_client =
+      client_->GetWeakPtr();
   TouchEmulator* touch_emulator =
       client_->GetTouchEmulator(/*create_if_necessary=*/false);
+  base::WeakPtr<TouchEmulator> weak_touch_emulator =
+      touch_emulator ? touch_emulator->GetWeakPtr() : nullptr;
+
   while (!ack_queue_.empty() && ack_queue_.front().touch_event_ack_status ==
                                     TouchEventAckStatus::TouchEventAcked) {
+    if (!weak_client) {
+      return;
+    }
     // Extract values and bare pointers to avoid holding raw_ptrs on the stack
     // across synchronous view destruction boundaries.
     TouchEventWithLatencyInfo touch_event = ack_queue_.front().touch_event;
@@ -197,12 +205,18 @@
     RenderWidgetHostViewInput* root_view = ack_queue_.front().root_view;
     ack_queue_.pop_front();
 
-    if ((!touch_emulator ||
-         !touch_emulator->HandleTouchEventAck(touch_event.event, ack_result)) &&
-        (client_->IsViewInMap(root_view) || client_->ViewMapIsEmpty())) {
-      // Forward acked event and result to the root view associated with the
-      // event. The view map is only empty for AndroidWebView.
-      root_view->ProcessAckedTouchEvent(touch_event, ack_result);
+    bool handled_by_emulator = false;
+    if (weak_touch_emulator) {
+      handled_by_emulator = weak_touch_emulator->HandleTouchEventAck(
+          touch_event.event, ack_result);
+    }
+
+    if (!handled_by_emulator && weak_client) {
+      if (client_->IsViewInMap(root_view) || client_->ViewMapIsEmpty()) {
+        // Forward acked event and result to the root view associated with the
+        // event. The view map is only empty for AndroidWebView.
+        root_view->ProcessAckedTouchEvent(touch_event, ack_result);
+      }
     }
   }
 }
@@ -2061,6 +2075,11 @@
   return delegate_->GetTouchEmulator(create_if_necessary);
 }
 
+base::WeakPtr<RenderWidgetHostInputEventRouter>
+RenderWidgetHostInputEventRouter::GetWeakPtr() {
+  return weak_ptr_factory_.GetWeakPtr();
+}
+
 void RenderWidgetHostInputEventRouter::ForwardEmulatedGestureEvent(
     const blink::WebGestureEvent& event) {
   TRACE_EVENT0("input",
diff --git a/components/input/render_widget_host_input_event_router.h b/components/input/render_widget_host_input_event_router.h
index 0515faa5..69510dc5 100644
--- a/components/input/render_widget_host_input_event_router.h
+++ b/components/input/render_widget_host_input_event_router.h
@@ -165,6 +165,8 @@
   // creates a touch emulator.
   TouchEmulator* GetTouchEmulator(bool create_if_necessary);
 
+  base::WeakPtr<RenderWidgetHostInputEventRouter> GetWeakPtr();
+
   float last_device_scale_factor() { return last_device_scale_factor_; }
 
   // Returns the RenderWidgetHostViewInput inside the |root_view| at |point|
diff --git a/components/input/render_widget_host_view_input.cc b/components/input/render_widget_host_view_input.cc
index 4fd0b80..ee04e453 100644
--- a/components/input/render_widget_host_view_input.cc
+++ b/components/input/render_widget_host_view_input.cc
@@ -371,4 +371,9 @@
   return true;
 }
 
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/browser/renderer_host/render_widget_host_view_android_unittest.cc b/content/browser/renderer_host/render_widget_host_view_android_unittest.cc
index 74dba14..e851873 100644
--- a/content/browser/renderer_host/render_widget_host_view_android_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_view_android_unittest.cc
@@ -503,7 +503,7 @@
   MockInputTransferHandler* handler = new MockInputTransferHandler();
   rwhva->SetInputTransferHandlerForTesting(handler);
 
-  auto& gesture_provider = rwhva->GetGestureProvider();
+  auto gesture_provider = rwhva->GetGestureProvider();
 
   gfx::Point point(/*x=*/100, /*y=*/100);
   ui::MotionEventAndroid::Pointer p(0, point.x(), point.y(), 10, 0, 0, 0, 0, 0);
@@ -536,13 +536,13 @@
       /*pointer1=*/nullptr);
 
   EXPECT_CALL(*handler, OnTouchEventImpl(_, _)).WillOnce(Return(true));
-  EXPECT_EQ(gesture_provider.GetCurrentDownEvent(), nullptr);
+  EXPECT_EQ(gesture_provider->GetCurrentDownEvent(), nullptr);
   rwhva->OnTouchEvent(*touch_down);
-  EXPECT_EQ(gesture_provider.GetCurrentDownEvent(), nullptr);
+  EXPECT_EQ(gesture_provider->GetCurrentDownEvent(), nullptr);
 
   EXPECT_CALL(*handler, OnTouchEventImpl(_, _)).WillOnce(Return(false));
   rwhva->OnTouchEvent(*touch_down);
-  EXPECT_NE(gesture_provider.GetCurrentDownEvent(), nullptr);
+  EXPECT_NE(gesture_provider->GetCurrentDownEvent(), nullptr);
 }
 
 TEST_F(RenderWidgetHostViewAndroidTest, ResetGestureDetectionGeneratesCancel) {
@@ -589,14 +589,14 @@
       /*is_latest_event_time_resampled=*/false);
   rwhva->OnTouchEvent(*touch_down);
 
-  auto& gesture_provider = rwhva->GetGestureProvider();
-  EXPECT_NE(gesture_provider.GetCurrentDownEvent(), nullptr);
+  auto gesture_provider = rwhva->GetGestureProvider();
+  EXPECT_NE(gesture_provider->GetCurrentDownEvent(), nullptr);
 
   rwhva->ResetGestureDetection();
 
   // The current down should have been reset as a result of processing cancel
   // generated from `ResetGestureDetection` call.
-  EXPECT_EQ(gesture_provider.GetCurrentDownEvent(), nullptr);
+  EXPECT_EQ(gesture_provider->GetCurrentDownEvent(), nullptr);
 
   MockRenderWidgetHost* mock_widget =
       static_cast<MockRenderWidgetHost*>(rwhva->host());
diff --git a/ui/events/gesture_detection/filtered_gesture_provider_unittest.cc b/ui/events/gesture_detection/filtered_gesture_provider_unittest.cc
index efeac12..b9e6751 100644
--- a/ui/events/gesture_detection/filtered_gesture_provider_unittest.cc
+++ b/ui/events/gesture_detection/filtered_gesture_provider_unittest.cc
@@ -32,7 +32,9 @@
 // set until (incl) touch-end.
 TEST_F(FilteredGestureProviderTest, TouchMovedBeyondSlopRegion_SingleTouch) {
   GestureProvider::Config config;
-  FilteredGestureProvider provider(config, this);
+  auto provider_ptr =
+      base::MakeRefCounted<FilteredGestureProvider>(config, this);
+  FilteredGestureProvider& provider = *provider_ptr;
 
   const float kSlopRegion = config.gesture_detector_config.touch_slop;
 
@@ -96,7 +98,9 @@
 // first movement in any touch-point.
 TEST_F(FilteredGestureProviderTest, TouchMovedBeyondSlopRegion_MultiTouch) {
   GestureProvider::Config config;
-  FilteredGestureProvider provider(config, this);
+  auto provider_ptr =
+      base::MakeRefCounted<FilteredGestureProvider>(config, this);
+  FilteredGestureProvider& provider = *provider_ptr;
 
   const float kSlopRegion = config.gesture_detector_config.touch_slop;
 
@@ -158,7 +162,9 @@
 // Extra cancel events should be handled gracefully: https://crbug.com/1407442
 TEST_F(FilteredGestureProviderTest, ExtraCancel) {
   GestureProvider::Config config;
-  FilteredGestureProvider provider(config, this);
+  auto provider_ptr =
+      base::MakeRefCounted<FilteredGestureProvider>(config, this);
+  FilteredGestureProvider& provider = *provider_ptr;
 
   test::MockMotionEvent event(MotionEvent::Action::CANCEL, base::TimeTicks(), 0,
                               0);
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.