Chrome · Input
CVE-2026-17688
UAF in Input
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcomponents/input/android_input_helper.cc |
modified | |
ifcomponents/input/render_widget_host_input_event_router.cc |
modified | |
GetWeakPtrcomponents/input/render_widget_host_input_event_router.cc |
modified |
Files Changed
components/input/android_input_helper.cccomponents/input/android_input_helper.hcomponents/input/render_widget_host_input_event_router.cccomponents/input/render_widget_host_input_event_router.hcomponents/input/render_widget_host_view_input.cc
Patch
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.
References
On This Page