CVE-2026-87455
Overview
Background
- `DetachFromInputMethod()`
- an
RWHVAmethod that callsinput_method->DetachTextInputClient(), a call that can synchronously re-enter and destroythis, and which therefore already guards its own remaining work with aweak_ptrcheck. - `OnWindowFocused()`
- the
aura::client::FocusChangeObservercallback onRWHVA; its lost-focus branch callsDetachFromInputMethod(false)and then continues touchingthis. - Re-entrant destruction via TSF/IME
- on Windows,
DetachTextInputClient()reachesITfThreadMgr::AssociateFocus(), a synchronous COM call that can run third-party Text-Input-Processor code which pumps a queued task (e.g.window.close()) and frees the view.
Root Cause Analysis
RWHVA::OnWindowFocused()’s lost-focus branch called DetachFromInputMethod(false) and then unconditionally continued executing on this, including dereferencing selection_controller_. DetachFromInputMethod() can, through input_method->DetachTextInputClient(), synchronously invoke a platform text-services callout that runs arbitrary IME/TIP code, which can re-enter Chromium and destroy the RWHVA before the call returns. DetachFromInputMethod() itself anticipated this and re-checked a weak_ptr to this after the callout, but its caller OnWindowFocused() did not, so when the view was freed the subsequent statements operated on freed memory — a use-after-free.
The fix takes weak_ptr_factory_.GetWeakPtr() before calling DetachFromInputMethod(false) and returns immediately if the weak pointer is invalidated, so no post-detach code runs on a destroyed view. This mirrors the exact guard already present inside DetachFromInputMethod(), extending it to the focus-change caller.
DetachFromInputMethod() was guarded only inside that method, while its caller OnWindowFocused() assumed the view still existed afterward; the fix propagates the same weak_ptr liveness check into the caller so execution stops when the callout frees this.Attack Path
- Own the input-method callout
On Windows, a registered third-party Text Input Processor / IME (or code able to run during the TSF
ITfThreadMgr::AssociateFocus()COM callout) obtains a synchronous execution point during input-method detach. - Trigger a focus change
A focus change drives
RWHVA::OnWindowFocused()into its lost-focus branch, which callsDetachFromInputMethod(false). - Re-enter and free the view
Inside
DetachTextInputClient(), the callout pumps a queued task (e.g. a pendingwindow.close()) that synchronously destroys theRWHVA, as the regression test simulates by destroyingview_insideDetachTextInputClient. - Use-after-free on resume
Control returns to
OnWindowFocused(), which (pre-patch) resumes and dereferencesthis->selection_controller_on the freed view, causing the UAF (inferred: any further exploitation of the freed allocation beyond the crash).
Impact Assessment
RWHVA mid-focus-change and cause the browser process to use the freed object, a use-after-free in the browser process; preconditions include the Windows (or ChromeOS, per the test’s BUILDFLAG guard) input-method path and a way to trigger re-entrant view destruction during focus loss. The advisory classifies this as Use after free in Aura and does not state in-the-wild exploitation. Escalation of the UAF beyond the demonstrated freed-pointer dereference is not shown by the patch (inferred).Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcontent/browser/renderer_host/render_widget_host_view_aura.cc |
modified | |
ifcontent/browser/renderer_host/render_widget_host_view_aura_unittest.cc |
modified | |
RenderWidgetHostViewAuraReentrantDestructionIMEcontent/browser/renderer_host/render_widget_host_view_aura_unittest.cc |
modified | |
BindLambdaForTestingcontent/browser/renderer_host/render_widget_host_view_aura_unittest.cc |
modified | |
MockVirtualKeyboardControllercontent/browser/renderer_host/render_widget_host_view_aura_unittest.cc |
modified |
Files Changed
content/browser/renderer_host/render_widget_host_view_aura.cccontent/browser/renderer_host/render_widget_host_view_aura_unittest.cc
Audit Directions
- Guarded callee, unguarded callerWhen a method self-protects with a
weak_ptr/liveness re-check after a re-entrant callout, audit every caller that continues touching the same object afterward, since the caller inherits the same destruction hazard. - Synchronous platform/COM calloutsTreat synchronous OS callouts (TSF/IME COM, accessibility, drag-and-drop) that can run third-party code as potential re-entrant free points, and verify object liveness after every such call.
- Focus/observer callbacks that mutate view lifetimeReview
FocusChangeObserverand similar UI callbacks that detach input, hide selection, or notify hosts, checking that post-notification statements do not assume the view survived the notification.
Patch
From 00f687785bd706d9d19a238045059445d811bbff Mon Sep 17 00:00:00 2001 From: Gaston Rodriguez <[email protected]> Date: Fri, 04 Sep 2026 09:52:04 -0700 Subject: [PATCH] Prevent UAF if DetachFromInputMethod frees RWHVA RenderWidgetHostViewAura::DetachFromInputMethod() calls input_method->DetachTextInputClient(), which can result in RWHVA being freed. DetachFromInputMethod() guards against this by getting a weak pointer to `this` and checking it after the call. RWHVA::OnWindowFocused() calls DetachFromInputMethod(), but doesn't account for the possibility that RWHVA will be freed, and unconditionally continues execution. If DetachFromInputMethod() has freed RWHVA, this results in a UAF. This CL addresses this issue by adding the same weak pointer mechanism to the focus change function, and also adds a unit test that would crash if this check weren't introduced. Bug: 542565481 Change-Id: If73645258ebf19765f2eb625928e56f821229561 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8306510 Reviewed-by: Olga Gerchikov <[email protected]> Commit-Queue: Gaston Rodriguez <[email protected]> Reviewed-by: Dave Tapuska <[email protected]> Cr-Commit-Position: refs/heads/main@{#1692536} --- diff --git a/content/browser/renderer_host/render_widget_host_view_aura.cc b/content/browser/renderer_host/render_widget_host_view_aura.cc index 77c4593..277febb 100644 --- a/content/browser/renderer_host/render_widget_host_view_aura.cc +++ b/content/browser/renderer_host/render_widget_host_view_aura.cc @@ -2850,7 +2850,11 @@ UpdateActiveState(false); host()->LostFocus(); + auto weak_this = weak_ptr_factory_.GetWeakPtr(); DetachFromInputMethod(false); + if (!weak_this) { + return; + } // TODO(wjmaclean): Do we need to let TouchSelectionControllerClientAura // handle this, just in case it stomps on a new highlight in another view diff --git a/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc b/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc index 10341a0..33ed433 100644 --- a/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc +++ b/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc @@ -7045,6 +7045,13 @@ } } + void DetachTextInputClient(ui::TextInputClient* client) override { + ui::MockInputMethod::DetachTextInputClient(client); + if (on_detach_text_input_client_) { + std::move(on_detach_text_input_client_).Run(); + } + } + void set_on_caret_bounds_changed(base::OnceClosure closure) { on_caret_bounds_changed_ = std::move(closure); } @@ -7053,9 +7060,14 @@ on_text_input_type_changed_ = std::move(closure); } + void set_on_detach_text_input_client(base::OnceClosure closure) { + on_detach_text_input_client_ = std::move(closure); + } + private: base::OnceClosure on_caret_bounds_changed_; base::OnceClosure on_text_input_type_changed_; + base::OnceClosure on_detach_text_input_client_; }; class RenderWidgetHostViewAuraReentrantDestructionIME @@ -7143,6 +7155,46 @@ GetTextInputManager(view_)->UpdateTextInputState(view_, state); } +// RWHVA::OnWindowFocused()'s lost-focus branch calls +// DetachFromInputMethod(false). On Windows this reaches +// TSFBridge::RemoveFocusedClient() -> ITfThreadMgr::AssociateFocus(), a +// synchronous COM call into the platform text-services framework that can run +// arbitrary code registered by a third-party Text Input Processor (e.g. an +// IME). DetachFromInputMethod() already guards its own remaining statements +// with a weak_ptr check in case that callout destroys the view re-entrantly, +// but OnWindowFocused()'s lost-focus branch does not: it unconditionally +// continues on to +// `selection_controller_->HideAndDisallowShowingAutomatically()` afterward, +// touching the now-freed `this`. +TEST_F(RenderWidgetHostViewAuraReentrantDestructionIME, + DestroyDuringDetachTextInputClientOnLostFocus) { + InitViewForFrame(nullptr); + ParentHostView(view_, parent_view_); + // `view_` shares the root window (and thus the InputMethod) with + // `parent_view_`. + ASSERT_EQ(static_cast<ui::InputMethod*>(input_method_.get()), + GetInputMethod()); + + // Focus `view_` so that DetachTextInputClient(view_) below is not a no-op. + input_method_->SetFocusedTextInputClient(view_.get()); + + // Arrange for the view to be synchronously destroyed inside + // DetachTextInputClient, simulating re-entrant destruction triggered by a + // TSF/IME callout that pumps a queued window.close() task + // while the view is losing focus. + FakeRenderWidgetHostViewAura* raw_view = view_.get(); + input_method_->set_on_detach_text_input_client( + base::BindLambdaForTesting([&]() { + widget_host_ = nullptr; + view_.ExtractAsDangling()->Destroy(); + })); + + // If the weak_ptr check wasn't in place, OnWindowFocused()'s lost-focus + // branch resumes after DetachFromInputMethod() returns and dereferences + // `this->selection_controller_` on the freed view. + raw_view->OnWindowFocused(nullptr, raw_view->GetNativeView()); +} + #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS) class MockVirtualKeyboardController final : public ui::VirtualKeyboardController {
Regression Test / PoC
diff --git a/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc b/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
index 10341a0..33ed433 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
@@ -7045,6 +7045,13 @@
}
}
+ void DetachTextInputClient(ui::TextInputClient* client) override {
+ ui::MockInputMethod::DetachTextInputClient(client);
+ if (on_detach_text_input_client_) {
+ std::move(on_detach_text_input_client_).Run();
+ }
+ }
+
void set_on_caret_bounds_changed(base::OnceClosure closure) {
on_caret_bounds_changed_ = std::move(closure);
}
@@ -7053,9 +7060,14 @@
on_text_input_type_changed_ = std::move(closure);
}
+ void set_on_detach_text_input_client(base::OnceClosure closure) {
+ on_detach_text_input_client_ = std::move(closure);
+ }
+
private:
base::OnceClosure on_caret_bounds_changed_;
base::OnceClosure on_text_input_type_changed_;
+ base::OnceClosure on_detach_text_input_client_;
};
class RenderWidgetHostViewAuraReentrantDestructionIME
@@ -7143,6 +7155,46 @@
GetTextInputManager(view_)->UpdateTextInputState(view_, state);
}
+// RWHVA::OnWindowFocused()'s lost-focus branch calls
+// DetachFromInputMethod(false). On Windows this reaches
+// TSFBridge::RemoveFocusedClient() -> ITfThreadMgr::AssociateFocus(), a
+// synchronous COM call into the platform text-services framework that can run
+// arbitrary code registered by a third-party Text Input Processor (e.g. an
+// IME). DetachFromInputMethod() already guards its own remaining statements
+// with a weak_ptr check in case that callout destroys the view re-entrantly,
+// but OnWindowFocused()'s lost-focus branch does not: it unconditionally
+// continues on to
+// `selection_controller_->HideAndDisallowShowingAutomatically()` afterward,
+// touching the now-freed `this`.
+TEST_F(RenderWidgetHostViewAuraReentrantDestructionIME,
+ DestroyDuringDetachTextInputClientOnLostFocus) {
+ InitViewForFrame(nullptr);
+ ParentHostView(view_, parent_view_);
+ // `view_` shares the root window (and thus the InputMethod) with
+ // `parent_view_`.
+ ASSERT_EQ(static_cast<ui::InputMethod*>(input_method_.get()),
+ GetInputMethod());
+
+ // Focus `view_` so that DetachTextInputClient(view_) below is not a no-op.
+ input_method_->SetFocusedTextInputClient(view_.get());
+
+ // Arrange for the view to be synchronously destroyed inside
+ // DetachTextInputClient, simulating re-entrant destruction triggered by a
+ // TSF/IME callout that pumps a queued window.close() task
+ // while the view is losing focus.
+ FakeRenderWidgetHostViewAura* raw_view = view_.get();
+ input_method_->set_on_detach_text_input_client(
+ base::BindLambdaForTesting([&]() {
+ widget_host_ = nullptr;
+ view_.ExtractAsDangling()->Destroy();
+ }));
+
+ // If the weak_ptr check wasn't in place, OnWindowFocused()'s lost-focus
+ // branch resumes after DetachFromInputMethod() returns and dereferences
+ // `this->selection_controller_` on the freed view.
+ raw_view->OnWindowFocused(nullptr, raw_view->GetNativeView());
+}
+
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS)
class MockVirtualKeyboardController final
: public ui::VirtualKeyboardController {
Original Bug Report
RenderWidgetHostViewAura UAF during shutdown with focus change
VULNERABILITY DETAILS
Please provide a brief explanation of the security issue.
VERSION
Chrome Version: [150.0.0.0] + [stable] Operating System: [Windows11, Platforms that use aura]
REPRODUCTION CASE
I don’t have a repro scenario on the browser but can reliably repro with a unit test. I’ve put it on CL:8156380.
ROOT CAUSE
RenderWidgetHostViewAura registers itself as the FocusChangeObserver for its window_ via a raw pointer stored in window_’s property store (aura::client::SetFocusChangeObserver). This property is never explicitly cleared in RenderWidgetHostViewAura’s destructor. [1]
In the normal teardown path (Destroy() -> delete window_), this is safe. The window is destroyed first and the raw pointer never outlives the view. [2]
However, when a renderer process exits unexpectedly (RendererExited -> RenderWidgetHostImpl::RendererExited -> aura::Window::~Window -> OnWindowDestroyed -> delete this), ~RenderWidgetHostViewAura() runs while window_ may still be alive (e.g. owned by its parent). The destructor does not clear the FocusChangeObserver property. A subsequent, unrelated event that triggers a focus change during browser window close eventually reaches the FocusController. The FocusController checks for observers, and finds the dangling pointer to the already deleted RenderWidgetHostViewAura. [3]
FOR CRASHES, PLEASE INCLUDE THE FOLLOWING ADDITIONAL INFORMATION
Type of crash: Browser process UAF
Crash State: Crash stack:
ui::TouchSelectionController::HideAndDisallowShowingAutomatically+0xd
content::RenderWidgetHostViewAura::OnWindowFocused+0x69
wm::FocusController::SetFocusedWindow+0x267
aura::Window::NotifyWindowVisibilityChangedAtReceiver+0xcc
aura::Window::NotifyWindowVisibilityChangedDown+0x2e
aura::Window::NotifyWindowVisibilityChangedDown+0x12a
aura::Window::NotifyWindowVisibilityChanged+0x15
aura::Window::SetVisibleInternal+0x260
views::NativeViewHostAuraWithClipWindow::HideWidget+0x18
views::View::VisibilityChangedImpl+0x29
views::View::PropagateVisibilityNotifications+0xb4
views::View::PropagateVisibilityNotifications+0x105
views::View::PropagateVisibilityNotifications+0x105
views::View::PropagateVisibilityNotifications+0x105
views::View::PropagateVisibilityNotifications+0x105
views::View::PropagateVisibilityNotifications+0x105
views::View::PropagateVisibilityNotifications+0x105
views::View::PropagateVisibilityNotifications+0x105
views::View::PropagateVisibilityNotifications+0x105
views::View::PropagateVisibilityNotifications+0x105
views::View::PropagateVisibilityNotifications+0x105
views::View::PropagateVisibilityNotifications+0x105
views::Widget::OnNativeWidgetVisibilityChanged+0x38
views::DesktopWindowTreeHostWin::HandleVisibilityChanged+0x4f
views::HWNDMessageHandler::OnWindowPosChanged+0x11d
views::HWNDMessageHandler::_ProcessWindowMessage+0x3e1
views::HWNDMessageHandler::OnWndProc+0x188
gfx::WindowImpl::WndProc+0x7e
base::win::WrappedWindowProc<&gfx::WindowImpl::WndProc>+0xf
user32!UserCallWinProcCheckWow+0x356
user32!DispatchClientMessage+0x9c
user32!__fnINLPWINDOWPOS+0x33
ntdll!KiUserCallbackDispatcherContinue
win32u!ZwUserSetWindowPos+0x14
views::HWNDMessageHandler::Hide+0x3a
aura::WindowTreeHost::Hide+0x18
views::DesktopNativeWidgetAura::Hide+0x29
views::Widget::Hide+0x63
BrowserView::OnWindowCloseRequested+0x13c
views::Widget::CloseWithReason+0x4f
// ...
We also have an ASAN GWP repro, this is the deallocation stack:
Deallocation stack trace size: 25
Allocation address: <>
Allocation size: 57c
Error type: Use After Free
Allocator Type: PartitionAlloc
GWP-ASan Mode: Lightweight UAF Detector (BRP sampling)
Deallocation Thread ID: 13152
Deallocation Stack Trace:
[0-4] ...
4 content::RenderWidgetHostViewAura::~RenderWidgetHostViewAura
5 aura::Window::~Window
6 aura::Window::~Window
7 content::RenderWidgetHostImpl::RendererExited
8 content::RenderViewHostImpl::RenderProcessExited
9 content::RenderProcessHostImpl::ProcessDied
10 content::RenderProcessHostImpl::FastShutdown
11 content::RenderProcessHostImpl::FastShutdownIfPossible
12 TabStripModel::CloseWebContentses
13 TabStripModel::InternalCloseTabsImpl
14 TabStripModel::CloseTabs
15 TabStripModel::CloseAllTabs
16 Browser::OnWindowClosing
17 BrowserView::OnWindowCloseRequested
18 views::Widget::CloseWithReason
19 views::HWNDMessageHandler::_ProcessWindowMessage
20 views::HWNDMessageHandler::OnWndProc
21 gfx::WindowImpl::WndProc
22 base::win::WrappedWindowProc<&gfx::WindowImpl::WndProc>
Client ID (if relevant): N/A
CREDIT INFORMATION
Externally reported security bugs may appear in Chrome release notes. If this bug is included, how would you like to be credited?
Reporter credit: Microsoft
- https://chromium-review.googlesource.com/c/chromium/src/+/8156380
- https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/render_widget_host_view_aura.cc;l=1104
- https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/render_widget_host_view_aura.cc;l=2889
- https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/render_widget_host_view_aura.cc;l=2947