reproducer: visible windows, toggle visibility on screen changes
This commit is contained in:
parent
1d7a4d1895
commit
6fedbd72aa
1 changed files with 30 additions and 11 deletions
|
|
@ -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();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue