CVE-2026-10953
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcontent/browser/renderer_host/render_widget_host_view_android.cc |
modified | |
TEST_Fui/touch_selection/touch_selection_controller_unittest.cc |
modified | |
TouchSelectionControllerUAFTestui/touch_selection/touch_selection_controller_unittest.cc |
modified | |
ifui/touch_selection/touch_selection_controller_unittest.cc |
modified |
Files Changed
content/browser/renderer_host/render_widget_host_view_android.ccui/touch_selection/touch_selection_controller.ccui/touch_selection/touch_selection_controller.hui/touch_selection/touch_selection_controller_unittest.cc
Patch
From 4df1b9d5d3d74aef8819c9bf805db871b88bc8bc Mon Sep 17 00:00:00 2001
From: Aman Verma <amanvr@google.com>
Date: Mon, 11 May 2026 15:11:00 -0700
Subject: [PATCH] Fix Use-After-Free in TouchSelectionController and RWHVA
A potential use-after-free vulnerability exists in the browser process
during RenderFrameMetadata processing. A compromised renderer can
trigger a synchronous JNI selection callback that destroys the
WebContents, leading to the destruction of RenderWidgetHostViewAndroid
and TouchSelectionController while they are still on the stack.
This CL fixes the issue by:
1. Using `base::WeakAutoReset` in `TouchSelectionController::
OnSelectionBoundsChanged`.
2. Adding a `base::WeakPtr` guard in `RenderWidgetHostViewAndroid::
OnRenderFrameMetadataChangedBeforeActivation`.
3. Adding a regression test in
`ui/touch_selection/touch_selection_controller_unittest.cc`.
Bug: 506147564
Test: ui_touch_selection_unittests
Change-Id: Ida6bc5d1098ddf6a98b80448c5429314513fdc24
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7837638
Commit-Queue: Avi Drissman <avi@chromium.org>
Reviewed-by: Avi Drissman <avi@chromium.org>
Reviewed-by: Jonathan Ross <jonross@chromium.org>
Auto-Submit: Aman Verma <amanvr@google.com>
Cr-Commit-Position: refs/heads/main@{#1628849}
---
diff --git a/content/browser/renderer_host/render_widget_host_view_android.cc b/content/browser/renderer_host/render_widget_host_view_android.cc
index 3142da2..c67c99e 100644
--- a/content/browser/renderer_host/render_widget_host_view_android.cc
+++ b/content/browser/renderer_host/render_widget_host_view_android.cc
@@ -967,11 +967,20 @@
if (!gesture_listener_manager_)
return;
+ base::WeakPtr<RenderWidgetHostViewAndroid> weak_this =
+ weak_ptr_factory_.GetWeakPtr();
+
UpdateTouchSelectionController(metadata.selection, metadata.page_scale_factor,
metadata.top_controls_height,
metadata.top_controls_shown_ratio,
scrollable_viewport_size_dip);
+ // Abort if the view was destroyed during UpdateTouchSelectionController
+ // (e.g. via synchronous JNI callout to Java).
+ if (!weak_this) {
+ return;
+ }
+
// ViewAndroid::content_offset() must be in dip.
float top_content_offset_dip = top_content_offset / dip_scale;
view_.UpdateFrameInfo({scrollable_viewport_size_dip, top_content_offset_dip});
diff --git a/ui/touch_selection/touch_selection_controller.cc b/ui/touch_selection/touch_selection_controller.cc
index 84427cb..075db3c 100644
--- a/ui/touch_selection/touch_selection_controller.cc
+++ b/ui/touch_selection/touch_selection_controller.cc
@@ -8,6 +8,7 @@
#include "base/auto_reset.h"
#include "base/check_op.h"
+#include "base/memory/weak_auto_reset.h"
#include "base/metrics/user_metrics.h"
#include "base/notreached.h"
#include "build/build_config.h"
@@ -126,8 +127,12 @@
// of the call.
InputEventType causal_input_event = response_pending_input_event_;
response_pending_input_event_ = InputEventType::kNone;
- base::AutoReset<InputEventType> auto_reset_response_pending_input_event(
- &response_pending_input_event_, causal_input_event);
+ // Use WeakAutoReset to avoid writing to freed memory if this is destroyed
+ // during the call (e.g. via synchronous JNI callout).
+ base::WeakAutoReset reset_response_pending_input_event(
+ weak_factory_.GetWeakPtr(),
+ &TouchSelectionController::response_pending_input_event_,
+ causal_input_event);
if ((start_orientation_ == TouchHandleOrientation::LEFT ||
start_orientation_ == TouchHandleOrientation::RIGHT) &&
diff --git a/ui/touch_selection/touch_selection_controller.h b/ui/touch_selection/touch_selection_controller.h
index f2af9cd1..ba65fab 100644
--- a/ui/touch_selection/touch_selection_controller.h
+++ b/ui/touch_selection/touch_selection_controller.h
@@ -6,6 +6,7 @@
#define UI_TOUCH_SELECTION_TOUCH_SELECTION_CONTROLLER_H_
#include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/point.h"
@@ -297,6 +298,8 @@
// Whether a swipe-to-move-cursor gesture is activated.
bool swipe_to_move_cursor_activated_ = false;
+
+ base::WeakPtrFactory<TouchSelectionController> weak_factory_{this};
};
} // namespace ui
diff --git a/ui/touch_selection/touch_selection_controller_unittest.cc b/ui/touch_selection/touch_selection_controller_unittest.cc
index 698a121..8553caf5 100644
--- a/ui/touch_selection/touch_selection_controller_unittest.cc
+++ b/ui/touch_selection/touch_selection_controller_unittest.cc
@@ -242,6 +242,9 @@
TouchSelectionController& controller() { return *controller_; }
+ protected:
+ std::unique_ptr<TouchSelectionController> controller_;
+
private:
gfx::PointF last_event_start_;
gfx::PointF last_event_end_;
@@ -257,7 +260,6 @@
bool needs_animate_ = false;
bool animation_enabled_ = true;
bool dragging_enabled_ = false;
- std::unique_ptr<TouchSelectionController> controller_;
};
TEST_F(TouchSelectionControllerTest, InsertionBasic) {
@@ -1907,5 +1909,37 @@
EXPECT_TRUE(test_controller.GetStartVisible());
}
+// Test class that destroys the controller when handles are shown,
+// to simulate embedder behavior in the UAF bug.
+class TouchSelectionControllerUAFTest : public TouchSelectionControllerTest {
+ public:
+ void OnSelectionEvent(SelectionEventType event) override {
+ TouchSelectionControllerTest::OnSelectionEvent(event);
+ if (event == SELECTION_HANDLES_SHOWN) {
+ // Destroy the controller when handles are shown.
+ controller_.reset();
+ }
+ }
+};
+
+// Tests that destroying the controller during a selection event callback
+// does not result in a write-after-free when the scoped reset goes out of
+// scope.
+TEST_F(TouchSelectionControllerUAFTest, AutoResetWriteAfterFree) {
+ // Setup handles to be shown.
+ OnLongPressEvent();
+
+ gfx::RectF start_rect(0, 0, 0, 10);
+ gfx::RectF end_rect(50, 0, 0, 10);
+ bool visible = true;
+
+ // This should trigger OnSelectionEvent(SELECTION_HANDLES_SHOWN)
+ // which will destroy the controller.
+ ChangeSelection(start_rect, visible, end_rect, visible);
+
+ // If fixed, this should not crash.
+ EXPECT_EQ(nullptr, controller_.get());
+}
+
} // namespace
} // namespace ui
Regression Test / PoC
diff --git a/ui/touch_selection/touch_selection_controller_unittest.cc b/ui/touch_selection/touch_selection_controller_unittest.cc
index 698a121..8553caf5 100644
--- a/ui/touch_selection/touch_selection_controller_unittest.cc
+++ b/ui/touch_selection/touch_selection_controller_unittest.cc
@@ -242,6 +242,9 @@
TouchSelectionController& controller() { return *controller_; }
+ protected:
+ std::unique_ptr<TouchSelectionController> controller_;
+
private:
gfx::PointF last_event_start_;
gfx::PointF last_event_end_;
@@ -257,7 +260,6 @@
bool needs_animate_ = false;
bool animation_enabled_ = true;
bool dragging_enabled_ = false;
- std::unique_ptr<TouchSelectionController> controller_;
};
TEST_F(TouchSelectionControllerTest, InsertionBasic) {
@@ -1907,5 +1909,37 @@
EXPECT_TRUE(test_controller.GetStartVisible());
}
+// Test class that destroys the controller when handles are shown,
+// to simulate embedder behavior in the UAF bug.
+class TouchSelectionControllerUAFTest : public TouchSelectionControllerTest {
+ public:
+ void OnSelectionEvent(SelectionEventType event) override {
+ TouchSelectionControllerTest::OnSelectionEvent(event);
+ if (event == SELECTION_HANDLES_SHOWN) {
+ // Destroy the controller when handles are shown.
+ controller_.reset();
+ }
+ }
+};
+
+// Tests that destroying the controller during a selection event callback
+// does not result in a write-after-free when the scoped reset goes out of
+// scope.
+TEST_F(TouchSelectionControllerUAFTest, AutoResetWriteAfterFree) {
+ // Setup handles to be shown.
+ OnLongPressEvent();
+
+ gfx::RectF start_rect(0, 0, 0, 10);
+ gfx::RectF end_rect(50, 0, 0, 10);
+ bool visible = true;
+
+ // This should trigger OnSelectionEvent(SELECTION_HANDLES_SHOWN)
+ // which will destroy the controller.
+ ChangeSelection(start_rect, visible, end_rect, visible);
+
+ // If fixed, this should not crash.
+ EXPECT_EQ(nullptr, controller_.get());
+}
+
} // namespace
} // namespace ui
Original Bug Report
Potential UAF in RenderWidgetHostViewAndroid via synchronous JNI selection event callback
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the Chrome Security team. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: A potential use-after-free vulnerability exists in the browser process during RenderFrameMetadata processing. A compromised renderer can trigger a synchronous JNI selection callback that, if it causes the embedder to synchronously destroy the WebContents, leads to the destruction of RenderWidgetHostViewAndroid while it is still on the stack. MiraclePtr does not protect the object because all tracking raw_ptr references are explicitly cleared during destruction.
Affected files:
content/browser/renderer_host/render_widget_host_view_android.ccui/touch_selection/touch_selection_controller.cccontent/browser/renderer_host/render_frame_metadata_provider_impl.cc
Estimated timestamp from git blame: 2022-06-17
Root Cause Analysis
A potential Use-After-Free (UAF) vulnerability exists in RenderWidgetHostViewAndroid (RWHVA) due to the handling of synchronous JNI callouts during the processing of RenderFrameMetadata IPCs.
When a renderer sends new metadata via mojom::RenderFrameMetadataObserverClient, it is processed by RenderFrameMetadataProviderImpl::OnRenderFrameMetadataChanged. This iterates through its observers and dispatches to RenderWidgetHostViewAndroid::OnRenderFrameMetadataChangedBeforeActivation. This function then calls TouchSelectionController::OnSelectionBoundsChanged to update selection handles.
OnSelectionBoundsChanged can trigger selection events (e.g., SELECTION_HANDLES_CLEARED) that are dispatched to Java via JNI in SelectionPopupControllerImpl.onSelectionEvent. These Java callbacks (such as onDestroyActionMode or onSelectionEvent) allow the Android embedder to execute arbitrary code. If the embedder synchronously destroys the WebContents during this callback, the RWHVA and its members are destroyed.
Crucially, because the destruction process explicitly clears all tracking raw_ptr references (including removing the observer from RenderFrameMetadataProviderImpl’s list mid-iteration, and clearing RenderWidgetHostConnector pointers), BackupRefPtr (MiraclePtr) does not quarantine the RenderWidgetHostViewAndroid memory block. It is freed immediately.
When the JNI call returns and the C++ stack unwinds, RenderWidgetHostViewAndroid::OnRenderFrameMetadataChangedBeforeActivation continues to execute, performing multiple member reads, writes, and virtual method calls on the fully freed, unquarantined memory block.
Potential Exploitation Steps
Note: These are suggested potential steps; no working proof-of-concept has been executed by this agent.
- An attacker gains arbitrary code execution in a sandboxed Renderer process (e.g., via a V8 bug).
- The attacker constructs a malicious
cc::RenderFrameMetadataIPC payload, specifically modifying theselectionfield to provide empty bounds (sostart.HasHandle()evaluates to false). - The renderer sends this IPC to the browser process.
RenderWidgetHostViewAndroid::OnRenderFrameMetadataChangedBeforeActivationreceives the metadata and triggers a selection update.- Because the bounds are empty,
TouchSelectionControllerhides the handles and fires aSELECTION_HANDLES_CLEAREDevent. - This event is forwarded synchronously over JNI to the Android Java layer.
- In Java, this triggers the destruction of the Action Mode, firing the
onDestroyActionModecallback to the Android embedder. - The embedder (e.g., a custom WebView host) reacts to this callback by synchronously destroying the
WebContents. - The
RenderWidgetHostViewAndroidis destroyed. All BRPraw_ptrtracking references are explicitly cleared viaRemoveObserverandRenderWidgetHostConnectortear-down. - The memory is returned to PartitionAlloc. The attacker uses asynchronous IPCs to spray the heap and overwrite the freed RWHVA memory block with controlled data.
- The C++ stack unwinds back to
RenderWidgetHostViewAndroid::OnRenderFrameMetadataChangedBeforeActivation, which accesses the freed and attacker-controlled memory block, resulting in memory corruption and a potential sandbox escape.
Suggested Fix
RenderWidgetHostViewAndroid::OnRenderFrameMetadataChangedBeforeActivation and the functions it calls (UpdateTouchSelectionController) should not make synchronous callouts to Java that could alter the lifecycle of the WebContents or RenderWidgetHostViewAndroid.
Consider using base::WeakPtr at the beginning of OnRenderFrameMetadataChangedBeforeActivation to check if this has been destroyed after UpdateTouchSelectionController returns, or posting the selection event updates as asynchronous tasks to the UI thread’s message loop rather than executing them synchronously.
Evaluated with Chrome root at commit: 3acbde3302da0cb19488c22c0eb007c791207b4b
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.