CVE-2026-87455
Overview
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
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