reproducer: visible windows, toggle visibility on screen changes

This commit is contained in:
Damocles 2026-04-20 21:48:19 +02:00
parent 1d7a4d1895
commit 6fedbd72aa

View file

@ -7,19 +7,22 @@
// QWaylandScreen::fromWlOutput() reads stale userdata from the freed proxy // QWaylandScreen::fromWlOutput() reads stale userdata from the freed proxy
// and returns a garbage QWaylandScreen*, which crashes on dereference. // 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: // Build:
// cmake -B build && cmake --build build // cmake -B build && cmake --build build
// //
// Run (on any wlroots-based compositor or Niri): // Run (on any wlroots-based compositor or Niri):
// ./build/screen-uaf-reproducer & // ./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 // # wlr-randr --output eDP-1 --off && wlr-randr --output eDP-1 --on
// # niri msg action power-off-monitors (then move mouse to wake) // # niri msg action power-off-monitors (then move mouse to wake)
// # or just unplug/replug an external monitor // # 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 <QGuiApplication> #include <QGuiApplication>
#include <QScreen> #include <QScreen>
@ -28,34 +31,50 @@
#include <vector> #include <vector>
#include <cstdio> #include <cstdio>
static std::vector<std::unique_ptr<QWindow>> 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[]) int main(int argc, char *argv[])
{ {
QGuiApplication app(argc, argv); QGuiApplication app(argc, argv);
// Many surfaces = higher chance of batched enter/leave events
constexpr int kWindows = 20; constexpr int kWindows = 20;
std::vector<std::unique_ptr<QWindow>> windows;
windows.reserve(kWindows); windows.reserve(kWindows);
for (int i = 0; i < kWindows; ++i) { for (int i = 0; i < kWindows; ++i) {
auto w = std::make_unique<QWindow>(); auto w = std::make_unique<QWindow>();
w->setTitle(QStringLiteral("uaf-%1").arg(i)); w->setTitle(QStringLiteral("uaf-%1").arg(i));
w->resize(1, 1); w->resize(100, 30);
w->setPosition(0, i * 35);
w->show(); w->show();
windows.push_back(std::move(w)); windows.push_back(std::move(w));
} }
std::fprintf(stderr, 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", "Toggle an output off/on to trigger the crash.\n",
kWindows, QGuiApplication::screens().size()); kWindows, static_cast<int>(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) { QObject::connect(&app, &QGuiApplication::screenAdded, [](QScreen *s) {
std::fprintf(stderr, " screenAdded: %s\n", qPrintable(s->name())); std::fprintf(stderr, " screenAdded: %s\n", qPrintable(s->name()));
toggleAllWindows();
}); });
QObject::connect(&app, &QGuiApplication::screenRemoved, [](QScreen *s) { QObject::connect(&app, &QGuiApplication::screenRemoved, [](QScreen *s) {
std::fprintf(stderr, " screenRemoved: %s\n", qPrintable(s->name())); std::fprintf(stderr, " screenRemoved: %s\n", qPrintable(s->name()));
toggleAllWindows();
}); });
return app.exec(); return app.exec();