diff --git a/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc b/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc index c0699afba8b8f..0131cf3f5231b 100644 --- a/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc +++ b/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc @@ -30,6 +30,9 @@ #include "ui/base/dragdrop/os_exchange_data.h" #include "ui/base/dragdrop/os_exchange_data_provider_factory.h" #include "ui/events/base_event_utils.h" +#include "ui/events/event.h" +#include "ui/events/event_constants.h" +#include "ui/events/types/event_type.h" #include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/vector2d.h" #include "ui/gfx/native_ui_types.h" @@ -1556,6 +1559,69 @@ TEST_P(WaylandDataDragControllerTest, ScaleEnterAndMotionEventsLocation) { SendDndLeave(); } +// POC: heap-use-after-free in WaylandWindow::OnDragSessionClose. +// +// OnDragSessionClose() runs `drag_finished_callback_` (callout [A]) and then +// `ReleasePressedPointerButtons(this, ...)` (callout [B]) which synchronously +// dispatches a synthesized kMouseReleased event up to the platform window +// delegate. At higher application layers that dispatch can spin a nested +// kNestableTasksAllowed RunLoop (modal dialog, MenuController, etc.) inside +// which a queued CloseNow task for the origin window may run, freeing `this`. +// When dispatch returns, line `std::move(drag_loop_quit_closure_).Run()` reads +// a base::OnceClosure stored *inside the freed WaylandWindow allocation* and +// performs an indirect call through the (now attacker-controllable) +// bind_state_->polymorphic_invoke_ pointer. +// +// This test simulates the nested-loop destruction by destroying the origin +// window directly from the delegate's DispatchEvent handler for the +// synthesized release. ASAN reports heap-use-after-free at the +// drag_loop_quit_closure_ read in OnDragSessionClose. +TEST_P(WaylandDataDragControllerTest, OnDragSessionCloseUseAfterFree) { + // 1. Focus the origin window and press the left button so that + // WaylandEventSource::pointer_flags_ records EF_LEFT_MOUSE_BUTTON. This + // ensures ReleasePressedPointerButtons() will dispatch a synthesized + // release inside OnDragSessionClose(). + FocusAndPressLeftPointerButton(window_.get(), &delegate_); + + // 2. Schedule the in-loop test task. ScheduleTestTask posts it so that it + // runs inside StartDrag()'s kNestableTasksAllowed RunLoop. + ScheduleTestTask(base::BindLambdaForTesting([&]() { + // Clear pointer focus so that `prev_focused_window` captured in + // OnPointerButtonEvent() is null, avoiding an earlier dangling-dereference + // in OnPointerButtonEventInternal() and letting control reach the + // OnDragSessionClose() sink at drag_loop_quit_closure_.Run(). + SetPointerFocusedWindow(nullptr); + + // Arm the delegate so that the synthesized kMouseReleased dispatch + // destroys the origin window. In production this models a nested message + // loop spun by the mouse-up handler in which a queued CloseNow task for + // the origin window runs (~WaylandWindow). + ON_CALL(delegate_, DispatchEvent(_)) + .WillByDefault([this](Event* event) { + if (window_ && event->type() == EventType::kMouseReleased && + (event->flags() & EF_IS_SYNTHESIZED)) { + SetWmDropHandler(window_.get(), nullptr); + window_.reset(); // ~WaylandWindow runs here, frees `this`. + } + }); + + // 3. Drive the compositor side: wl_data_source.dnd_finished -> + // OnDataSourceFinish -> HandleDragEnd -> + // origin_window_->OnDragSessionClose(op). + // + // Inside OnDragSessionClose, ReleasePressedPointerButtons(this, ...) + // dispatches the synthesized release; the lambda above destroys + // `this`; control returns and reaches + // `std::move(drag_loop_quit_closure_).Run()` -> heap-use-after-free. + SendDndFinished(); + })); + + // 4. Start the drag session. StartDrag() spins the nested RunLoop in which + // the scheduled task above runs. + RunMouseDragWithSampleData( + window_.get(), DragDropTypes::DRAG_COPY | DragDropTypes::DRAG_MOVE); +} + INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest, WaylandDataDragControllerTest, Values(wl::ServerConfig{}));