// Minimal reproducer for Qt 6 Wayland screen use-after-free. // // Bug: libwayland resolves wl_output proxy pointers at demarshal time (when // events are read from the socket). If wl_registry.global_remove and // wl_surface.enter/leave for the same output arrive in the same dispatch // batch, the surface event handler receives a dangling proxy pointer. // QWaylandScreen::fromWlOutput() reads stale userdata from the freed proxy // and returns a garbage QWaylandScreen*, which crashes on dereference. // // This reproducer simulates the pattern that triggers the crash in practice: // multiple surfaces bound to an output that toggle visibility during screen // changes (like layer-shell panel windows being hidden/reshown on output // reconfiguration). Hiding a QWindow destroys its wl_surface; showing it // creates a new one. This surface churn during output removal maximizes the // chance that enter/leave events land in the same batch as global_remove. // // Build: // cmake -B build && cmake --build build // // Run (on any wlroots-based compositor or Niri): // ./build/screen-uaf-reproducer & // # then toggle an output off/on, e.g.: // # wlr-randr --output eDP-1 --off && wlr-randr --output eDP-1 --on // # niri msg action power-off-monitors (then move mouse to wake) // # or just unplug/replug an external monitor #include #include #include #include #include #include static std::vector> windows; // Hide and immediately re-show all windows, destroying and recreating their // wl_surfaces. This is what layer-shell panels do during output changes. static void toggleAllWindows() { std::fprintf(stderr, " toggling %zu windows...\n", windows.size()); for (auto &w : windows) w->hide(); for (auto &w : windows) w->show(); } int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); constexpr int kWindows = 20; windows.reserve(kWindows); for (int i = 0; i < kWindows; ++i) { auto w = std::make_unique(); w->setTitle(QStringLiteral("uaf-%1").arg(i)); w->resize(100, 30); w->setPosition(0, i * 35); w->show(); windows.push_back(std::move(w)); } std::fprintf(stderr, "screen-uaf-reproducer: %d windows on %d screen(s).\n" "Toggle an output off/on to trigger the crash.\n", kWindows, static_cast(QGuiApplication::screens().size())); // When screens change, toggle all windows - this creates surface // destruction/creation right when the compositor is sending // enter/leave/global_remove events, maximizing batch collision odds. QObject::connect(&app, &QGuiApplication::screenAdded, [](QScreen *s) { std::fprintf(stderr, " screenAdded: %s\n", qPrintable(s->name())); toggleAllWindows(); }); QObject::connect(&app, &QGuiApplication::screenRemoved, [](QScreen *s) { std::fprintf(stderr, " screenRemoved: %s\n", qPrintable(s->name())); toggleAllWindows(); }); return app.exec(); }