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 a7087ef0b3a10..cb81a30abec88 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 @@ -6899,12 +6899,24 @@ class DestroyingMockInputMethod : public ui::MockInputMethod { } } + void OnTextInputTypeChanged(ui::TextInputClient* client) override { + ui::MockInputMethod::OnTextInputTypeChanged(client); + if (on_text_input_type_changed_) { + std::move(on_text_input_type_changed_).Run(); + } + } + void set_on_caret_bounds_changed(base::OnceClosure closure) { on_caret_bounds_changed_ = std::move(closure); } + void set_on_text_input_type_changed(base::OnceClosure closure) { + on_text_input_type_changed_ = std::move(closure); + } + private: base::OnceClosure on_caret_bounds_changed_; + base::OnceClosure on_text_input_type_changed_; }; class RenderWidgetHostViewAuraOnBoundsChangedUAFTest @@ -6961,6 +6973,45 @@ TEST_F(RenderWidgetHostViewAuraOnBoundsChangedUAFTest, raw_view->OnBoundsChanged(gfx::Rect(), gfx::Rect(0, 0, 100, 100)); } +// RWHVA::OnUpdateTextInputStateCalled() calls +// GetInputMethod()->OnTextInputTypeChanged(this) without a WeakPtr guard. On +// Windows that reaches TSFBridgeImpl::OnTextInputTypeChanged() which performs +// synchronous COM calls into the active TSF/TIP (e.g. ITfThreadMgr::SetFocus). +// A TIP that pumps messages from inside that call can run a queued tab-close +// task and delete the RenderWidgetHostViewAura while it is still on the stack. +// On unwind, line 3434 reads the freed |this->text_input_manager_| and line +// 3475 dereferences the freed |updated_view| to fetch the host pointer. +TEST_F(RenderWidgetHostViewAuraOnBoundsChangedUAFTest, + DestroyDuringOnTextInputTypeChanged) { + InitViewForFrame(nullptr); + ParentHostView(view_, parent_view_); + // `view_` shares the root window (and thus the InputMethod) with + // `parent_view_`. + ASSERT_EQ(static_cast(input_method_.get()), + GetInputMethod()); + + // Arm the mock IME to synchronously destroy `view_` inside + // OnTextInputTypeChanged, simulating re-entrant destruction triggered by a + // TSF COM callout that pumps a queued window.close() / renderer-gone task. + input_method_->set_on_text_input_type_changed( + base::BindLambdaForTesting([&]() { + widget_host_ = nullptr; + view_.ExtractAsDangling()->Destroy(); + })); + + // Renderer-controlled mojom::WidgetHost::TextInputStateChanged payload that + // flips the input type from NONE -> TEXT so did_update_state is true and the + // synchronous IME call is made. + ui::mojom::TextInputState state; + state.type = ui::TEXT_INPUT_TYPE_TEXT; + state.mode = ui::TEXT_INPUT_MODE_TEXT; + + // Under ASAN this triggers heap-use-after-free inside + // RenderWidgetHostViewAura::OnUpdateTextInputStateCalled() when it + // dereferences |this| / |updated_view| after the IME call returns. + view_->TextInputStateChanged(state); +} + #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS) class MockVirtualKeyboardController final : public ui::VirtualKeyboardController {