CVE-2026-10967
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
MouseCursorOverlayControllercontent/browser/media/capture/mouse_cursor_overlay_controller_android.cc |
modified | |
ifcontent/browser/media/capture/mouse_cursor_overlay_controller_android.cc |
modified |
Files Changed
content/browser/media/capture/mouse_cursor_overlay_controller_android.cccontent/browser/media/capture/mouse_cursor_overlay_controller_browsertest.cc
Patch
From 42c11a4e10b1d9c8f854c23ea37910fbf7142278 Mon Sep 17 00:00:00 2001
From: Sangwhan Moon <sxm@chromium.org>
Date: Thu, 14 May 2026 18:12:17 -0700
Subject: [PATCH] Use raw_ptr for Android mouse cursor view
MouseCursorOverlayController's Android Observer can outlive ViewAndroid
after a renderer crash. Store the view as raw_ptr and observe
ViewAndroid destruction directly so the cached pointer is cleared.
Also add a regression test for destroying ViewAndroid.
Bug: 511714900
Change-Id: Icffc94e2b04dda61a1851026ac127581806f2fbd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7842558
Reviewed-by: Dale Curtis <dalecurtis@chromium.org>
Reviewed-by: Jordan Bayles <jophba@chromium.org>
Auto-Submit: Sangwhan Moon <sxm@chromium.org>
Commit-Queue: Sangwhan Moon <sxm@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1630969}
---
diff --git a/content/browser/media/capture/mouse_cursor_overlay_controller_android.cc b/content/browser/media/capture/mouse_cursor_overlay_controller_android.cc
index a8b0a29a..524efc5 100644
--- a/content/browser/media/capture/mouse_cursor_overlay_controller_android.cc
+++ b/content/browser/media/capture/mouse_cursor_overlay_controller_android.cc
@@ -18,6 +18,7 @@
#include "third_party/skia/include/core/SkPathBuilder.h"
#include "ui/android/event_forwarder.h"
#include "ui/android/view_android.h"
+#include "ui/android/view_android_observer.h"
#include "ui/events/android/motion_event_android.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/rect_f.h"
@@ -27,7 +28,8 @@
// Observes mouse events and updates the cursor overlay.
// This is used to draw the mouse cursor for things such as tab sharing.
class MouseCursorOverlayController::Observer
- : public ui::EventForwarder::Observer {
+ : public ui::EventForwarder::Observer,
+ public ui::ViewAndroidObserver {
public:
Observer(MouseCursorOverlayController* controller, gfx::NativeView view)
: controller_(controller), view_(view) {
@@ -35,6 +37,7 @@
CHECK(view_);
controller_->OnMouseHasGoneIdle();
+ view_->AddObserver(this);
// Get contentview's event forwarder.
gfx::NativeView parent = view_->parent();
@@ -55,6 +58,7 @@
}
if (view_) {
+ view_->RemoveObserver(this);
view_ = nullptr;
controller_->OnMouseHasGoneIdle();
}
@@ -99,11 +103,14 @@
// Ignore touch events. The cursor overlay is strictly for mouse input.
}
+ // ui::ViewAndroidObserver overrides:
+ void OnViewAndroidDestroyed() override { StopTracking(); }
+
gfx::NativeView view() const { return view_; }
private:
raw_ptr<MouseCursorOverlayController> controller_;
- gfx::NativeView view_;
+ raw_ptr<ui::ViewAndroid> view_;
// The specific forwarder we are observing (the parent's).
raw_ptr<ui::EventForwarder> observed_forwarder_ = nullptr;
};
diff --git a/content/browser/media/capture/mouse_cursor_overlay_controller_browsertest.cc b/content/browser/media/capture/mouse_cursor_overlay_controller_browsertest.cc
index 5dae77a8..03a0726 100644
--- a/content/browser/media/capture/mouse_cursor_overlay_controller_browsertest.cc
+++ b/content/browser/media/capture/mouse_cursor_overlay_controller_browsertest.cc
@@ -24,6 +24,7 @@
#if BUILDFLAG(IS_ANDROID)
#include "content/browser/media/capture/android_cursor_renderer.h"
+#include "ui/android/view_android.h"
#endif
#if BUILDFLAG(IS_CHROMEOS)
@@ -234,6 +235,7 @@
protected:
base::test::ScopedFeatureList scoped_feature_list_;
+ MouseCursorOverlayController controller_;
private:
gfx::PointF ToAbsoluteLocationInView(float relative_x, float relative_y) {
@@ -267,8 +269,6 @@
base::RunLoop().RunUntilIdle();
}
- MouseCursorOverlayController controller_;
-
#if BUILDFLAG(IS_CHROMEOS)
std::unique_ptr<wm::CursorLoader> cursor_loader_;
#endif
@@ -359,6 +359,19 @@
EXPECT_TRUE(IsUserInteractingWithView());
}
+#if BUILDFLAG(IS_ANDROID)
+IN_PROC_BROWSER_TEST_F(MouseCursorOverlayControllerBrowserTest,
+ DoesNotCrashWhenViewAndroidDestroyed) {
+ auto dummy_view = std::make_unique<ui::ViewAndroid>();
+ controller_.SetTargetView(dummy_view.get());
+
+ dummy_view.reset();
+
+ // This accesses the observer's target view when updating the overlay.
+ controller_.OnMouseMoved(gfx::PointF());
+}
+#endif // BUILDFLAG(IS_ANDROID)
+
// This test verifies that MouseCoordinatesUpdated calls are forwarded to the
// overlay.
IN_PROC_BROWSER_TEST_F(MouseCursorOverlayControllerBrowserTest,
Regression Test / PoC
diff --git a/content/browser/media/capture/mouse_cursor_overlay_controller_browsertest.cc b/content/browser/media/capture/mouse_cursor_overlay_controller_browsertest.cc
index 5dae77a8..03a0726 100644
--- a/content/browser/media/capture/mouse_cursor_overlay_controller_browsertest.cc
+++ b/content/browser/media/capture/mouse_cursor_overlay_controller_browsertest.cc
@@ -24,6 +24,7 @@
#if BUILDFLAG(IS_ANDROID)
#include "content/browser/media/capture/android_cursor_renderer.h"
+#include "ui/android/view_android.h"
#endif
#if BUILDFLAG(IS_CHROMEOS)
@@ -234,6 +235,7 @@
protected:
base::test::ScopedFeatureList scoped_feature_list_;
+ MouseCursorOverlayController controller_;
private:
gfx::PointF ToAbsoluteLocationInView(float relative_x, float relative_y) {
@@ -267,8 +269,6 @@
base::RunLoop().RunUntilIdle();
}
- MouseCursorOverlayController controller_;
-
#if BUILDFLAG(IS_CHROMEOS)
std::unique_ptr<wm::CursorLoader> cursor_loader_;
#endif
@@ -359,6 +359,19 @@
EXPECT_TRUE(IsUserInteractingWithView());
}
+#if BUILDFLAG(IS_ANDROID)
+IN_PROC_BROWSER_TEST_F(MouseCursorOverlayControllerBrowserTest,
+ DoesNotCrashWhenViewAndroidDestroyed) {
+ auto dummy_view = std::make_unique<ui::ViewAndroid>();
+ controller_.SetTargetView(dummy_view.get());
+
+ dummy_view.reset();
+
+ // This accesses the observer's target view when updating the overlay.
+ controller_.OnMouseMoved(gfx::PointF());
+}
+#endif // BUILDFLAG(IS_ANDROID)
+
// This test verifies that MouseCoordinatesUpdated calls are forwarded to the
// overlay.
IN_PROC_BROWSER_TEST_F(MouseCursorOverlayControllerBrowserTest,
Original Bug Report
Potential UAF in MouseCursorOverlayController on Android during Renderer Crash
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 Use-After-Free (UAF) vulnerability exists in the browser process on Android when a renderer crashes during a tab capture session. MouseCursorOverlayController::Observer retains a dangling raw pointer to a freed ui::ViewAndroid due to an edge case in process death notification ordering, which can be exploited via subsequent mouse events.
Affected files:
content/browser/media/capture/web_contents_frame_tracker.cccontent/browser/media/capture/mouse_cursor_overlay_controller_android.cccontent/browser/media/capture/web_contents_frame_tracker.h
Estimated timestamp from git blame: 2026-03-30
Summary
A Use-After-Free (UAF) vulnerability exists in the Android implementation of the MouseCursorOverlayController, used to render mouse cursors during media capture (e.g., tab sharing). The issue occurs because WebContentsFrameTracker fails to clear its associated MouseCursorOverlayController when a renderer process crashes. This failure is rooted in the specific ordering of RenderProcessHost observer notifications. As a result, the controller’s observer maintains a dangling raw pointer to a ui::ViewAndroid object, leading to a UAF and potential arbitrary code execution in the browser process (Sandbox Escape) when subsequent mouse events occur.
Vulnerability Details
During a tab capture session, WebContentsFrameTracker instantiates a MouseCursorOverlayController. On Android, this controller creates an Observer that stores a gfx::NativeView (a raw ui::ViewAndroid*) to track mouse movements. Because it is an alias, this pointer is not protected by MiraclePtr (base::raw_ptr).
When a renderer crashes, RenderProcessHostImpl::ProcessDied iterates over its observers. SiteInstanceGroup is always registered as an observer before RenderViewHostImpl.
- Frame Deletion:
SiteInstanceGroup::RenderProcessExitedfires first, cascading toRenderFrameHostImpl::RenderProcessGoneand triggering aRenderFrameDeletednotification. - Early Return Bypass:
WebContentsFrameTracker::RenderFrameDeletedcatches this notification and callsOnPossibleTargetChange(), which evaluatesGetCaptureTarget(). BecauseRenderViewHostImplhas not yet received the crash notification,RenderWidgetHostViewAndroid(and itsui::ViewAndroid) is still alive.SetTargetViewis called, but because theui::ViewAndroidpointer matches the existingtarget_native_view_, it returns early, bypassing the logic that would normally reset theMouseCursorOverlayController. - View Destruction: The observer loop continues, notifying
RenderViewHostImpl::RenderProcessExited, which destroys theRenderWidgetHostViewAndroidanddeletes theui::ViewAndroid. TheMouseCursorOverlayController::Observernow holds a dangling pointer. - UAF Trigger: If the user moves a physical mouse (or the attacker simulates mouse events), the surviving parent
EventForwardertriggersObserver::OnMouseEvent. This callscontroller_->OnMouseMoved, leading toUpdateOverlay.UpdateOverlayinvokesGetCurrentCursorOrDefault()andComputeRelativeBoundsForOverlay(), both of which execute the virtual methodobserver_->view()->GetDipScale()on the freed object.
If an attacker can successfully groom the browser process heap, this virtual method call provides a direct path to hijack the vtable and achieve arbitrary code execution.
Potential Reproduction Steps
Note: These steps are theoretical as our tooling does not execute code.
- An attacker tricks a user into visiting a malicious site on an Android device (e.g., a Chromebook running Android apps) and granting tab capture permissions (
getDisplayMedia). - The attacker’s JavaScript initiates the capture, causing the browser to set up the
MouseCursorOverlayController. - The attacker intentionally triggers a renderer process crash (e.g., by exhausting memory).
- The attacker simultaneously simulates or induces mouse movement over the captured area.
- The browser process reads the dangling
ui::ViewAndroidpointer, resulting in a UAF whenGetDipScale()is called.
Suggested Fix
There are two primary ways to fix this:
- Adopt MiraclePtr: Change the
gfx::NativeView view_member inMouseCursorOverlayController::Observertoraw_ptr<ui::ViewAndroid>. This mirrors the Aura implementation (raw_ptr<aura::Window> window_) and would turn this exploitable UAF into a safe, non-exploitable crash. - Explicit Cleanup: Ensure
WebContentsFrameTrackerorMouseCursorOverlayControlleractively listens for view destruction (e.g., by implementingui::ViewAndroidObserver) so it can reset its internal pointers immediately upon view deletion, rather than relying onWebContentsObserversignals which are subject to subtle ordering bugs.
Evaluated with Chrome root at commit: eca8648a4e1cdfdda68c495a6003059fed641955
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.