diff --git a/flake.nix b/flake.nix index 5365927..c62df34 100644 --- a/flake.nix +++ b/flake.nix @@ -55,9 +55,8 @@ nixpkgs.lib.genAttrs systems ( system: fn rec { - rawPkgs = nixpkgs.legacyPackages.${system}; - pkgs = rawPkgs.extend qtbaseOverlay; - treefmt-eval = treefmt-nix.lib.evalModule rawPkgs treefmt-config; + pkgs = (nixpkgs.legacyPackages.${system}).extend qtbaseOverlay; + treefmt-eval = treefmt-nix.lib.evalModule pkgs treefmt-config; } ); in @@ -65,7 +64,7 @@ formatter = forAllSystems ({ treefmt-eval, ... }: treefmt-eval.config.build.wrapper); packages = forAllSystems ( - { pkgs, rawPkgs, ... }: + { pkgs, ... }: let # Rebuild quickshell against patched Qt via its overlay qs = (pkgs.extend quickshell.overlays.default).quickshell.override { @@ -88,37 +87,14 @@ ''; docs = pkgs.callPackage ./nix/docs.nix { inherit self; }; default = nova-shell; - - # Reproducer for the qtbase wayland screen UAF, built twice: - # patched (should survive) and unpatched (should crash). - screen-uaf-reproducer-patched = pkgs.stdenv.mkDerivation { - pname = "screen-uaf-reproducer-patched"; - version = "0"; - src = ./test/screen-uaf-reproducer; - nativeBuildInputs = [ - pkgs.cmake - pkgs.qt6.wrapQtAppsHook - ]; - buildInputs = [ pkgs.qt6.qtbase ]; - }; - screen-uaf-reproducer-unpatched = rawPkgs.stdenv.mkDerivation { - pname = "screen-uaf-reproducer-unpatched"; - version = "0"; - src = ./test/screen-uaf-reproducer; - nativeBuildInputs = [ - rawPkgs.cmake - rawPkgs.qt6.wrapQtAppsHook - ]; - buildInputs = [ rawPkgs.qt6.qtbase ]; - }; } ); devShells = forAllSystems ( - { rawPkgs, ... }: + { pkgs, ... }: { - default = rawPkgs.mkShell { - packages = with rawPkgs; [ + default = pkgs.mkShell { + packages = with pkgs; [ cargo rustc rust-analyzer @@ -132,25 +108,21 @@ ); checks = forAllSystems ( - { - pkgs, - rawPkgs, - treefmt-eval, - }: + { pkgs, treefmt-eval }: { formatting = treefmt-eval.config.build.check self; build = self.packages.${pkgs.stdenv.hostPlatform.system}.default; nova-stats = self.packages.${pkgs.stdenv.hostPlatform.system}.nova-stats; docs = self.packages.${pkgs.stdenv.hostPlatform.system}.docs; qmllint = - rawPkgs.runCommand "nova-shell-qmllint" + pkgs.runCommand "nova-shell-qmllint" { - nativeBuildInputs = [ rawPkgs.qt6.qtdeclarative ]; + nativeBuildInputs = [ pkgs.qt6.qtdeclarative ]; src = self; } '' cd $src - export QML_IMPORT_PATH="${rawPkgs.qt6.qtdeclarative}/lib/qt-6/qml:${ + export QML_IMPORT_PATH="${pkgs.qt6.qtdeclarative}/lib/qt-6/qml:${ quickshell.packages.${pkgs.stdenv.hostPlatform.system}.default }/lib/qt-6/qml" qmllint -E \ diff --git a/test/screen-uaf-reproducer/CMakeLists.txt b/test/screen-uaf-reproducer/CMakeLists.txt deleted file mode 100644 index 122c8b4..0000000 --- a/test/screen-uaf-reproducer/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -cmake_minimum_required(VERSION 3.19) -project(screen-uaf-reproducer LANGUAGES CXX) - -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_AUTOMOC ON) - -find_package(Qt6 REQUIRED COMPONENTS Gui) -add_executable(screen-uaf-reproducer main.cpp) -target_link_libraries(screen-uaf-reproducer PRIVATE Qt6::Gui) diff --git a/test/screen-uaf-reproducer/main.cpp b/test/screen-uaf-reproducer/main.cpp deleted file mode 100644 index 7a108b2..0000000 --- a/test/screen-uaf-reproducer/main.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// Minimal reproducer for Qt 6 Wayland screen use-after-free. -// -// Bug: libwayland resolves wl_output proxy pointers at demarshal time (when -// events are read from the socket). If wl_registry.global_remove and -// wl_surface.enter/leave for the same output arrive in the same dispatch -// batch, the surface event handler receives a dangling proxy pointer. -// QWaylandScreen::fromWlOutput() reads stale userdata from the freed proxy -// and returns a garbage QWaylandScreen*, which crashes on dereference. -// -// 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.: -// # 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 -#include -#include -#include -#include - -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->setFlag(Qt::ToolTip); // small, no decoration, no focus - w->show(); - windows.push_back(std::move(w)); - } - - std::fprintf(stderr, - "screen-uaf-reproducer: %d surfaces created on %d screen(s).\n" - "Toggle an output off/on to trigger the crash.\n", - kWindows, QGuiApplication::screens().size()); - - // Log screen changes so we know when the toggle happens - QObject::connect(&app, &QGuiApplication::screenAdded, [](QScreen *s) { - std::fprintf(stderr, " screenAdded: %s\n", qPrintable(s->name())); - }); - QObject::connect(&app, &QGuiApplication::screenRemoved, [](QScreen *s) { - std::fprintf(stderr, " screenRemoved: %s\n", qPrintable(s->name())); - }); - - return app.exec(); -} diff --git a/test/screen-uaf-reproducer/shell.nix b/test/screen-uaf-reproducer/shell.nix deleted file mode 100644 index 0c6fd61..0000000 --- a/test/screen-uaf-reproducer/shell.nix +++ /dev/null @@ -1,11 +0,0 @@ -{ - pkgs ? import { }, -}: -pkgs.mkShell { - nativeBuildInputs = with pkgs; [ - cmake - qt6.qtbase - qt6.wrapQtAppsHook - wlr-randr - ]; -} diff --git a/test/screen-uaf-reproducer/trigger.sh b/test/screen-uaf-reproducer/trigger.sh deleted file mode 100755 index 31609ff..0000000 --- a/test/screen-uaf-reproducer/trigger.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env bash -# Trigger rapid output power-cycling to provoke the Qt Wayland screen UAF. -# Run the reproducer binary first, then this script in another terminal. -# -# Usage: ./trigger.sh [iterations] -# iterations: number of off/on cycles (default 20) - -set -euo pipefail - -cycles="${1:-20}" - -if command -v niri &>/dev/null && niri msg version &>/dev/null; then - echo "Detected Niri - using niri msg" - for i in $(seq 1 "$cycles"); do - echo "cycle $i/$cycles" - niri msg action power-off-monitors - sleep 0.3 - niri msg action power-on-monitors - sleep 0.5 - done -elif command -v wlr-randr &>/dev/null; then - output=$(wlr-randr --json 2>/dev/null | python3 -c \ - "import sys,json; print(next(o['name'] for o in json.load(sys.stdin) if o['enabled']))" 2>/dev/null \ - || wlr-randr | grep -oP '^\S+' | head -1) - if [ -z "$output" ]; then - echo "error: could not detect an output via wlr-randr" >&2 - exit 1 - fi - echo "Detected wlroots compositor - toggling output $output" - for i in $(seq 1 "$cycles"); do - echo "cycle $i/$cycles" - wlr-randr --output "$output" --off - sleep 0.3 - wlr-randr --output "$output" --on - sleep 0.5 - done -else - echo "error: neither niri nor wlr-randr found" >&2 - echo "Manually unplug/replug a monitor while the reproducer is running." >&2 - exit 1 -fi - -echo "Done. If the reproducer is still alive, the bug did not trigger." -echo "Try increasing iterations or adding more surfaces."