diff --git a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc index 7aea341d9dd14..72593a919b871 100644 --- a/ui/ozone/platform/wayland/host/wayland_window_unittest.cc +++ b/ui/ozone/platform/wayland/host/wayland_window_unittest.cc @@ -483,6 +483,50 @@ TEST_P(WaylandWindowTest, DeleteWindowFromOnStateUpdate) { window_->SetBoundsInDIP(gfx::Rect(1024, 768)); } +// POC: WaylandPopup::SetBoundsInDIP continues to dereference |this| after +// WaylandWindow::SetBoundsInDIP -> OnStateUpdate has synchronously freed the +// popup. The codebase documents (and unit-tests) at every adjacent layer that +// PlatformWindowDelegate::OnStateUpdate / OnBoundsChanged may delete the +// platform window; WaylandPopup::SetBoundsInDIP is the only frame on the +// unwind path that does not weak-guard. ASAN reports heap-use-after-free. +TEST_P(WaylandWindowTest, DeletePopupFromOnStateUpdateDuringSetBounds) { + testing::NiceMock popup_delegate( + connection_.get()); + + // Create a kMenu popup parented to the toplevel; Show() populates xdg_popup_. + std::unique_ptr popup = + CreateWaylandWindowWithParams(PlatformWindowType::kMenu, + gfx::Rect(6, 20, 8, 20), + &popup_delegate, window_->GetWidget()); + ASSERT_TRUE(popup); + ASSERT_TRUE(popup->IsVisible()); + + // Mirror the production contract exercised by + // WindowTreeHostPlatformTest.DeleteHostFromOnStateUpdate: a + // WindowTreeHostObserver synchronously deletes the host during the + // bounds-change notification, which destroys the owned PlatformWindow + // (the WaylandPopup) while SetBoundsInDIP is still on the stack. + popup_delegate.set_on_state_update_callback( + base::BindLambdaForTesting([&]() { + popup.reset(); + return false; + })); + + // Save a raw pointer; the unique_ptr is reset inside the callback above. + WaylandWindow* popup_ptr = popup.get(); + + // Change origin so old_bounds_dip != bounds_dip and the post-callout branch + // at wayland_popup.cc:187 is taken. The base-class call at :183 frees |this| + // via the callback above; line 187 then UAF-reads this->xdg_popup_, line 191 + // wild-vcalls delegate()->CalculateInsetsInDIP(), line 198/206 UAF-write + // schedule_redraw_, line 204 derefs the freed xdg_popup_ unique_ptr. + popup_ptr->SetBoundsInDIP(gfx::Rect(10, 10, 8, 20)); + + // Prevent TearDown from talking to a server that has already torn down the + // popup's xdg objects out-of-order. + DisableSyncOnTearDown(); +} + TEST_P(WaylandWindowTest, SetTitle) { window_->SetTitle(u"hello"); PostToServerAndWait([id = surface_id_](wl::TestWaylandServerThread* server) {