diff --git a/ui/ozone/platform/x11/x11_window_ozone_unittest.cc b/ui/ozone/platform/x11/x11_window_ozone_unittest.cc index a45afbfb87a12..9d13f52f0ed00 100644 --- a/ui/ozone/platform/x11/x11_window_ozone_unittest.cc +++ b/ui/ozone/platform/x11/x11_window_ozone_unittest.cc @@ -17,7 +17,9 @@ #include "ui/events/event.h" #include "ui/events/platform/x11/x11_event_source.h" #include "ui/events/test/events_test_utils_x11.h" +#include "ui/gfx/x/connection.h" #include "ui/gfx/x/event.h" +#include "ui/gfx/x/xproto.h" #include "ui/ozone/platform/x11/x11_window_manager.h" #include "ui/ozone/test/mock_platform_window_delegate.h" #include "ui/platform_window/platform_window_delegate.h" @@ -301,4 +303,46 @@ TEST_F(X11WindowOzoneTest, SetFullscreen) { window->SetFullscreen(true, display::kInvalidDisplayId); } +// A core EnterNotify/LeaveNotify is dispatched via PlatformEventSource into +// X11Window::DispatchUiEvent, which calls HandleEvent → OnCrossingEvent → +// AfterActivationStateChanged → delegate->OnActivationChanged. In production +// (DesktopWindowTreeHostPlatform::OnActivationChanged → +// DesktopNativeWidgetAura::HandleActivationChanged → Widget::CloseNow → +// SetPlatformWindow(nullptr)) this can synchronously destroy the X11Window. +// DispatchUiEvent then continues to access |this| after HandleEvent returns: +// without a fix, ASAN reports heap-use-after-free. +TEST_F(X11WindowOzoneTest, + DispatchUiEventSurvivesSynchronousCloseOnActivation) { + testing::NiceMock delegate; + gfx::AcceleratedWidget widget; + constexpr gfx::Rect bounds(30, 80, 800, 600); + auto window = CreatePlatformWindow(&delegate, bounds, &widget, nullptr); + + // Mirror the production destruction chain: when activation flips, the + // delegate synchronously frees the platform window (~X11Window runs while + // we are still inside DispatchUiEvent's HandleEvent call). + EXPECT_CALL(delegate, OnActivationChanged(true)) + .WillOnce([&](bool) { window.reset(); }); + + // Craft a synthetic core EnterNotify with the focus flag set so that + // OnCrossingEvent sets has_pointer_focus_=true (since has_window_focus_ is + // false on a fresh window) and IsActive() flips false→true. A compromised + // GPU process can deliver exactly this event to a browser window via its + // pre-sandbox X11 connection using XSendEvent. + x11::CrossingEvent crossing{}; + crossing.opcode = x11::CrossingEvent::EnterNotify; + crossing.event = static_cast(widget); + crossing.detail = x11::NotifyDetail::Ancestor; // != Inferior + crossing.mode = x11::NotifyMode::Normal; + crossing.same_screen_focus = 0x01; // CROSSING_FLAG_FOCUS + + x11::Event xev(/*send_event=*/true, std::move(crossing)); + + // Dispatch through the connection so dispatching_event() is set and the + // CrossingEvent flows X11EventSource::OnEvent → DispatchEvent → + // X11Window::DispatchEvent → DispatchUiEvent → HandleEvent (frees |this|) + // → UpdateWMUserTime/platform_window_delegate() on freed memory. + x11::Connection::Get()->DispatchEvent(xev); +} + } // namespace ui