diff --git a/test/screen-uaf-reproducer/main.cpp b/test/screen-uaf-reproducer/main.cpp index d03b3ac..73e5bc2 100644 --- a/test/screen-uaf-reproducer/main.cpp +++ b/test/screen-uaf-reproducer/main.cpp @@ -7,19 +7,22 @@ // 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 rapidly, e.g.: +// # 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 -// -// The crash typically occurs within seconds of the output toggle. -// Creating many surfaces increases the odds that a surface.leave event -// lands in the same batch as the global_remove. #include #include @@ -28,34 +31,50 @@ #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); - // Many surfaces = higher chance of batched enter/leave events constexpr int kWindows = 20; - std::vector> windows; windows.reserve(kWindows); for (int i = 0; i < kWindows; ++i) { auto w = std::make_unique(); w->setTitle(QStringLiteral("uaf-%1").arg(i)); - w->resize(1, 1); + w->resize(100, 30); + w->setPosition(0, i * 35); w->show(); windows.push_back(std::move(w)); } std::fprintf(stderr, - "screen-uaf-reproducer: %d surfaces created on %lld screen(s).\n" + "screen-uaf-reproducer: %d windows on %d screen(s).\n" "Toggle an output off/on to trigger the crash.\n", - kWindows, QGuiApplication::screens().size()); + kWindows, static_cast(QGuiApplication::screens().size())); - // Log screen changes so we know when the toggle happens + // 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();