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 <jonross@chromium.org>
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 <amanvr@google.com>
Commit-Queue: Jonathan Ross <jonross@chromium.org>
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