Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06fb97bc23 | ||
|
|
e85cd40266 | ||
|
|
b05861de78 |
5 changed files with 165 additions and 10 deletions
48
flake.nix
48
flake.nix
|
|
@ -55,8 +55,9 @@
|
||||||
nixpkgs.lib.genAttrs systems (
|
nixpkgs.lib.genAttrs systems (
|
||||||
system:
|
system:
|
||||||
fn rec {
|
fn rec {
|
||||||
pkgs = (nixpkgs.legacyPackages.${system}).extend qtbaseOverlay;
|
rawPkgs = nixpkgs.legacyPackages.${system};
|
||||||
treefmt-eval = treefmt-nix.lib.evalModule pkgs treefmt-config;
|
pkgs = rawPkgs.extend qtbaseOverlay;
|
||||||
|
treefmt-eval = treefmt-nix.lib.evalModule rawPkgs treefmt-config;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
in
|
in
|
||||||
|
|
@ -64,7 +65,7 @@
|
||||||
formatter = forAllSystems ({ treefmt-eval, ... }: treefmt-eval.config.build.wrapper);
|
formatter = forAllSystems ({ treefmt-eval, ... }: treefmt-eval.config.build.wrapper);
|
||||||
|
|
||||||
packages = forAllSystems (
|
packages = forAllSystems (
|
||||||
{ pkgs, ... }:
|
{ pkgs, rawPkgs, ... }:
|
||||||
let
|
let
|
||||||
# Rebuild quickshell against patched Qt via its overlay
|
# Rebuild quickshell against patched Qt via its overlay
|
||||||
qs = (pkgs.extend quickshell.overlays.default).quickshell.override {
|
qs = (pkgs.extend quickshell.overlays.default).quickshell.override {
|
||||||
|
|
@ -87,14 +88,37 @@
|
||||||
'';
|
'';
|
||||||
docs = pkgs.callPackage ./nix/docs.nix { inherit self; };
|
docs = pkgs.callPackage ./nix/docs.nix { inherit self; };
|
||||||
default = nova-shell;
|
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 (
|
devShells = forAllSystems (
|
||||||
{ pkgs, ... }:
|
{ rawPkgs, ... }:
|
||||||
{
|
{
|
||||||
default = pkgs.mkShell {
|
default = rawPkgs.mkShell {
|
||||||
packages = with pkgs; [
|
packages = with rawPkgs; [
|
||||||
cargo
|
cargo
|
||||||
rustc
|
rustc
|
||||||
rust-analyzer
|
rust-analyzer
|
||||||
|
|
@ -108,21 +132,25 @@
|
||||||
);
|
);
|
||||||
|
|
||||||
checks = forAllSystems (
|
checks = forAllSystems (
|
||||||
{ pkgs, treefmt-eval }:
|
{
|
||||||
|
pkgs,
|
||||||
|
rawPkgs,
|
||||||
|
treefmt-eval,
|
||||||
|
}:
|
||||||
{
|
{
|
||||||
formatting = treefmt-eval.config.build.check self;
|
formatting = treefmt-eval.config.build.check self;
|
||||||
build = self.packages.${pkgs.stdenv.hostPlatform.system}.default;
|
build = self.packages.${pkgs.stdenv.hostPlatform.system}.default;
|
||||||
nova-stats = self.packages.${pkgs.stdenv.hostPlatform.system}.nova-stats;
|
nova-stats = self.packages.${pkgs.stdenv.hostPlatform.system}.nova-stats;
|
||||||
docs = self.packages.${pkgs.stdenv.hostPlatform.system}.docs;
|
docs = self.packages.${pkgs.stdenv.hostPlatform.system}.docs;
|
||||||
qmllint =
|
qmllint =
|
||||||
pkgs.runCommand "nova-shell-qmllint"
|
rawPkgs.runCommand "nova-shell-qmllint"
|
||||||
{
|
{
|
||||||
nativeBuildInputs = [ pkgs.qt6.qtdeclarative ];
|
nativeBuildInputs = [ rawPkgs.qt6.qtdeclarative ];
|
||||||
src = self;
|
src = self;
|
||||||
}
|
}
|
||||||
''
|
''
|
||||||
cd $src
|
cd $src
|
||||||
export QML_IMPORT_PATH="${pkgs.qt6.qtdeclarative}/lib/qt-6/qml:${
|
export QML_IMPORT_PATH="${rawPkgs.qt6.qtdeclarative}/lib/qt-6/qml:${
|
||||||
quickshell.packages.${pkgs.stdenv.hostPlatform.system}.default
|
quickshell.packages.${pkgs.stdenv.hostPlatform.system}.default
|
||||||
}/lib/qt-6/qml"
|
}/lib/qt-6/qml"
|
||||||
qmllint -E \
|
qmllint -E \
|
||||||
|
|
|
||||||
9
test/screen-uaf-reproducer/CMakeLists.txt
Normal file
9
test/screen-uaf-reproducer/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
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)
|
||||||
63
test/screen-uaf-reproducer/main.cpp
Normal file
63
test/screen-uaf-reproducer/main.cpp
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
// 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 <QGuiApplication>
|
||||||
|
#include <QScreen>
|
||||||
|
#include <QWindow>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
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<std::unique_ptr<QWindow>> windows;
|
||||||
|
windows.reserve(kWindows);
|
||||||
|
|
||||||
|
for (int i = 0; i < kWindows; ++i) {
|
||||||
|
auto w = std::make_unique<QWindow>();
|
||||||
|
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();
|
||||||
|
}
|
||||||
11
test/screen-uaf-reproducer/shell.nix
Normal file
11
test/screen-uaf-reproducer/shell.nix
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
pkgs ? import <nixpkgs> { },
|
||||||
|
}:
|
||||||
|
pkgs.mkShell {
|
||||||
|
nativeBuildInputs = with pkgs; [
|
||||||
|
cmake
|
||||||
|
qt6.qtbase
|
||||||
|
qt6.wrapQtAppsHook
|
||||||
|
wlr-randr
|
||||||
|
];
|
||||||
|
}
|
||||||
44
test/screen-uaf-reproducer/trigger.sh
Executable file
44
test/screen-uaf-reproducer/trigger.sh
Executable file
|
|
@ -0,0 +1,44 @@
|
||||||
|
#!/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."
|
||||||
Reference in a new issue