From 8ad32c631aaffd9b9174db256a0c81a858e51b03 Mon Sep 17 00:00:00 2001 From: Damocles Date: Sat, 18 Apr 2026 22:42:39 +0200 Subject: [PATCH] patch qtbase wayland client to fix screen use-after-free crash --- flake.nix | 19 ++- nix/patches/qtbase-wayland-screen-uaf.patch | 124 ++++++++++++++++++++ 2 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 nix/patches/qtbase-wayland-screen-uaf.patch diff --git a/flake.nix b/flake.nix index 6dfbd44..c62df34 100644 --- a/flake.nix +++ b/flake.nix @@ -36,12 +36,26 @@ programs.taplo.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 = fn: nixpkgs.lib.genAttrs systems ( system: fn rec { - pkgs = nixpkgs.legacyPackages.${system}; + pkgs = (nixpkgs.legacyPackages.${system}).extend qtbaseOverlay; treefmt-eval = treefmt-nix.lib.evalModule pkgs treefmt-config; } ); @@ -52,7 +66,8 @@ packages = forAllSystems ( { pkgs, ... }: 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; withI3 = false; }; diff --git a/nix/patches/qtbase-wayland-screen-uaf.patch b/nix/patches/qtbase-wayland-screen-uaf.patch new file mode 100644 index 0000000..1b1a326 --- /dev/null +++ b/nix/patches/qtbase-wayland-screen-uaf.patch @@ -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 mScreens; + QPlatformPlaceholderScreen *mPlaceholderScreen = nullptr; + QList mInputDevices; ++ QList mSurfaces; + QList 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 m_screens; //As seen by wl_surface.enter/leave events. Chronological order. + QWaylandWindow *m_window = nullptr; ++ QWaylandDisplay *m_display = nullptr; + std::optional m_preferredBufferScale; + std::optional m_preferredBufferTransform; + + friend class QWaylandWindow; // TODO: shouldn't need to be friends ++ friend class QWaylandDisplay; + }; + + } // namespace QtWaylandClient