patch qtbase wayland client to fix screen use-after-free crash

This commit is contained in:
Damocles 2026-04-18 22:42:39 +02:00
parent d821500db7
commit 8ad32c631a
2 changed files with 141 additions and 2 deletions

View file

@ -36,12 +36,26 @@
programs.taplo.enable = true; programs.taplo.enable = true;
programs.keep-sorted.enable = true; programs.keep-sorted.enable = true;
}; };
# Patch qtbase wayland client to fix use-after-free in QWaylandSurface::oldestEnteredScreen.
# Waiting screens deleted via registry_global_remove leave dangling pointers in surface
# m_screens lists, crashing on the next screensChanged signal. Track surfaces in the
# display and scrub references before any screen deletion.
# TODO: remove once fixed upstream in Qt
qtbaseOverlay = final: prev: {
qt6 = prev.qt6.overrideScope (
qtFinal: qtPrev: {
qtbase = qtPrev.qtbase.overrideAttrs (old: {
patches = (old.patches or [ ]) ++ [ ./nix/patches/qtbase-wayland-screen-uaf.patch ];
});
}
);
};
forAllSystems = forAllSystems =
fn: fn:
nixpkgs.lib.genAttrs systems ( nixpkgs.lib.genAttrs systems (
system: system:
fn rec { fn rec {
pkgs = nixpkgs.legacyPackages.${system}; pkgs = (nixpkgs.legacyPackages.${system}).extend qtbaseOverlay;
treefmt-eval = treefmt-nix.lib.evalModule pkgs treefmt-config; treefmt-eval = treefmt-nix.lib.evalModule pkgs treefmt-config;
} }
); );
@ -52,7 +66,8 @@
packages = forAllSystems ( packages = forAllSystems (
{ pkgs, ... }: { pkgs, ... }:
let let
qs = quickshell.packages.${pkgs.stdenv.hostPlatform.system}.default.override { # Rebuild quickshell against patched Qt via its overlay
qs = (pkgs.extend quickshell.overlays.default).quickshell.override {
withX11 = false; withX11 = false;
withI3 = false; withI3 = false;
}; };

View file

@ -0,0 +1,124 @@
diff --git a/src/plugins/platforms/wayland/qwaylanddisplay.cpp b/src/plugins/platforms/wayland/qwaylanddisplay.cpp
index 02169e77..a1f3befe 100644
--- a/src/plugins/platforms/wayland/qwaylanddisplay.cpp
+++ b/src/plugins/platforms/wayland/qwaylanddisplay.cpp
@@ -423,8 +423,10 @@ void QWaylandDisplay::reconnect()
m_eventThread->wait();
m_frameEventQueueThread->wait();
- qDeleteAll(mWaitingScreens);
- mWaitingScreens.clear();
+ for (auto *screen : std::exchange(mWaitingScreens, {})) {
+ forgetScreenForSurfaces(screen);
+ delete screen;
+ }
while (!mScreens.isEmpty()) {
auto screen = mScreens.takeLast();
@@ -603,6 +605,22 @@ QWaylandScreen *QWaylandDisplay::screenForOutput(struct wl_output *output) const
return nullptr;
}
+void QWaylandDisplay::registerSurface(QWaylandSurface *surface)
+{
+ mSurfaces.append(surface);
+}
+
+void QWaylandDisplay::unregisterSurface(QWaylandSurface *surface)
+{
+ mSurfaces.removeOne(surface);
+}
+
+void QWaylandDisplay::forgetScreenForSurfaces(QWaylandScreen *screen)
+{
+ for (auto *surface : std::as_const(mSurfaces))
+ surface->m_screens.removeAll(screen);
+}
+
void QWaylandDisplay::handleScreenInitialized(QWaylandScreen *screen)
{
if (!mWaitingScreens.removeOne(screen))
@@ -823,6 +841,7 @@ void QWaylandDisplay::registry_global_remove(uint32_t id)
for (auto *screen : mWaitingScreens) {
if (screen->outputId() == id) {
mWaitingScreens.removeOne(screen);
+ forgetScreenForSurfaces(screen);
delete screen;
break;
}
@@ -831,6 +850,7 @@ void QWaylandDisplay::registry_global_remove(uint32_t id)
for (QWaylandScreen *screen : std::as_const(mScreens)) {
if (screen->outputId() == id) {
mScreens.removeOne(screen);
+ forgetScreenForSurfaces(screen);
// If this is the last screen, we have to add a fake screen, or Qt will break.
ensureScreen();
QWindowSystemInterface::handleScreenRemoved(screen);
diff --git a/src/plugins/platforms/wayland/qwaylanddisplay_p.h b/src/plugins/platforms/wayland/qwaylanddisplay_p.h
index 29952886..0baa378f 100644
--- a/src/plugins/platforms/wayland/qwaylanddisplay_p.h
+++ b/src/plugins/platforms/wayland/qwaylanddisplay_p.h
@@ -116,6 +116,9 @@ public:
QPlatformPlaceholderScreen *placeholderScreen() const { return mPlaceholderScreen; }
void ensureScreen();
+ void registerSurface(QWaylandSurface *surface);
+ void unregisterSurface(QWaylandSurface *surface);
+
QWaylandScreen *screenForOutput(struct wl_output *output) const;
void handleScreenInitialized(QWaylandScreen *screen);
@@ -289,6 +292,7 @@ private:
void checkWaylandError();
void reconnect();
void setupConnection();
+ void forgetScreenForSurfaces(QWaylandScreen *screen);
void handleWaylandSync();
void requestWaylandSync();
@@ -311,6 +315,7 @@ private:
QList<QWaylandScreen *> mScreens;
QPlatformPlaceholderScreen *mPlaceholderScreen = nullptr;
QList<QWaylandInputDevice *> mInputDevices;
+ QList<QWaylandSurface *> mSurfaces;
QList<Listener> mRegistryListeners;
QWaylandIntegration *mWaylandIntegration = nullptr;
#if QT_CONFIG(cursor)
diff --git a/src/plugins/platforms/wayland/qwaylandsurface.cpp b/src/plugins/platforms/wayland/qwaylandsurface.cpp
index 274fdda8..b37e9265 100644
--- a/src/plugins/platforms/wayland/qwaylandsurface.cpp
+++ b/src/plugins/platforms/wayland/qwaylandsurface.cpp
@@ -13,13 +13,16 @@ namespace QtWaylandClient {
QWaylandSurface::QWaylandSurface(QWaylandDisplay *display)
: wl_surface(display->createSurface(this))
+ , m_display(display)
{
+ display->registerSurface(this);
connect(qApp, &QGuiApplication::screenRemoved, this, &QWaylandSurface::handleScreenRemoved);
connect(qApp, &QGuiApplication::screenAdded, this, &QWaylandSurface::screensChanged);
}
QWaylandSurface::~QWaylandSurface()
{
+ m_display->unregisterSurface(this);
destroy();
}
diff --git a/src/plugins/platforms/wayland/qwaylandsurface_p.h b/src/plugins/platforms/wayland/qwaylandsurface_p.h
index 41860297..ddb63b04 100644
--- a/src/plugins/platforms/wayland/qwaylandsurface_p.h
+++ b/src/plugins/platforms/wayland/qwaylandsurface_p.h
@@ -57,10 +57,12 @@ protected:
QList<QWaylandScreen *> m_screens; //As seen by wl_surface.enter/leave events. Chronological order.
QWaylandWindow *m_window = nullptr;
+ QWaylandDisplay *m_display = nullptr;
std::optional<int32_t> m_preferredBufferScale;
std::optional<wl_output_transform> m_preferredBufferTransform;
friend class QWaylandWindow; // TODO: shouldn't need to be friends
+ friend class QWaylandDisplay;
};
} // namespace QtWaylandClient