From efb5c08696b0893a0c8ae1670347836abaffea64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ryan4yin=20=7C=20=E4=BA=8C=E8=8A=B1?= Date: Sun, 20 Sep 2026 09:43:21 +0800 Subject: [PATCH] Merge pull request #274 from ryan4yin/fix/nixpak-pulse-audio fix(nixpak): repair sandboxed audio and deduplicate mounts --- hardening/nixpaks/firefox.nix | 8 +--- .../modules/{common.nix => custom-policy.nix} | 42 ++++++++----------- hardening/nixpaks/modules/gui-base.nix | 42 ++++++++++++++----- hardening/nixpaks/qq.nix | 7 +--- hardening/nixpaks/telegram-desktop.nix | 7 +--- 5 files changed, 52 insertions(+), 54 deletions(-) rename hardening/nixpaks/modules/{common.nix => custom-policy.nix} (86%) diff --git a/hardening/nixpaks/firefox.nix b/hardening/nixpaks/firefox.nix index ee018186..f84e4a03 100644 --- a/hardening/nixpaks/firefox.nix +++ b/hardening/nixpaks/firefox.nix @@ -31,7 +31,7 @@ let imports = [ ./modules/gui-base.nix ./modules/network.nix - ./modules/common.nix + ./modules/custom-policy.nix ]; bubblewrap = { @@ -81,12 +81,6 @@ let # Unsure (sloth.concat' sloth.xdgConfigHome "/dconf") ]; - - sockets = { - x11 = false; - wayland = true; - pipewire = true; - }; }; }; }; diff --git a/hardening/nixpaks/modules/common.nix b/hardening/nixpaks/modules/custom-policy.nix similarity index 86% rename from hardening/nixpaks/modules/common.nix rename to hardening/nixpaks/modules/custom-policy.nix index 9016fa17..046878ae 100644 --- a/hardening/nixpaks/modules/common.nix +++ b/hardening/nixpaks/modules/custom-policy.nix @@ -1,6 +1,14 @@ -# https://github.com/mnixry/nixos-config/blob/74913c2b90d06e31170bbbaa0074f915721da224/desktop/packages/nixpaks-common.nix -# https://github.com/Kraftland/portable/blob/09c4a4227538a3f42de208a6ecbdc938ac9c00dd/portable.sh -# https://flatpak.github.io/xdg-desktop-portal/docs/api-reference.html +# Custom, repo-local policy for the sandboxed desktop apps (Firefox, QQ, +# Telegram). Unlike ./gui-base.nix, which forks nixpak/pkgs's gui-base and is +# meant to track upstream, this file is hand-written for this repository: it +# grants the D-Bus surface those apps need (portals, MPRIS, tray, input +# methods, a11y), redirects the XDG dirs Flatpak-style, and adds the remaining +# document/device mounts. Keep only app-specific mounts in the app files. +# +# Adapted from: +# - https://github.com/mnixry/nixos-config/blob/74913c2b90d06e31170bbbaa0074f915721da224/desktop/packages/nixpaks-common.nix +# - https://github.com/Kraftland/portable/blob/09c4a4227538a3f42de208a6ecbdc938ac9c00dd/portable.sh +# - https://flatpak.github.io/xdg-desktop-portal/docs/api-reference.html { lib, sloth, @@ -185,11 +193,12 @@ in etc.sslCertificates.enable = true; bubblewrap = { network = lib.mkDefault true; - sockets = { - wayland = true; - pulse = true; - }; + # Desktop-wide mounts (Wayland/at-spi/gvfsd, the app cache, fontconfig and + # Mesa caches, GTK config, /dev/shm) are provided by ./gui-base.nix, so + # each path is bound exactly once. This is what keeps a read-only bind + # from shadowing a writable one (see the note on the PulseAudio runtime + # dir there). Only app-specific mounts belong in this file. bind.rw = with sloth; [ [ (mkdir appDataDir) @@ -199,38 +208,21 @@ in (mkdir appConfigDir) xdgConfigHome ] - [ - (mkdir appCacheDir) - xdgCacheHome - ] # FileChooser may return a Document Portal path even for a directly mapped XDG directory; # keep the FUSE mount writable so SaveFile can create the selected file. (sloth.concat' sloth.runtimeDir "/doc") - (sloth.concat [ - sloth.runtimeDir - "/" - (sloth.envOr "WAYLAND_DISPLAY" "no") - ]) - (sloth.concat' sloth.runtimeDir "/at-spi/bus") - (sloth.concat' sloth.runtimeDir "/gvfsd") (sloth.concat' sloth.runtimeDir "/dconf") - (sloth.concat' sloth.xdgCacheHome "/fontconfig") - (sloth.concat' sloth.xdgCacheHome "/mesa_shader_cache") (sloth.concat' sloth.xdgCacheHome "/mesa_shader_cache_db") (sloth.concat' sloth.xdgCacheHome "/radv_builtin_shaders") ]; bind.ro = [ (sloth.concat' sloth.xdgConfigHome "/kdeglobals") - (sloth.concat' sloth.xdgConfigHome "/gtk-2.0") - (sloth.concat' sloth.xdgConfigHome "/gtk-3.0") - (sloth.concat' sloth.xdgConfigHome "/gtk-4.0") - (sloth.concat' sloth.xdgConfigHome "/fontconfig") (sloth.concat' sloth.xdgConfigHome "/dconf") ]; - bind.dev = [ "/dev/shm" ] ++ (map (id: "/dev/video${toString id}") (lib.lists.range 0 9)); + bind.dev = map (id: "/dev/video${toString id}") (lib.lists.range 0 9); }; }; } diff --git a/hardening/nixpaks/modules/gui-base.nix b/hardening/nixpaks/modules/gui-base.nix index b02103b7..354fb7b3 100644 --- a/hardening/nixpaks/modules/gui-base.nix +++ b/hardening/nixpaks/modules/gui-base.nix @@ -1,3 +1,9 @@ +# Fork of nixpak/pkgs's gui-base.nix: the shared GUI runtime plumbing (GPU, +# fonts, desktop caches, /dev/shm, and the audio/display sockets) that lets a +# GUI app run under bubblewrap. Paths bound here must not be bound again in +# ./custom-policy.nix or the app configs: nixpak emits read-only binds last, so +# a duplicate read-only bind would silently shadow a writable one. +# # https://github.com/nixpak/pkgs/blob/master/pkgs/modules/gui-base.nix { config, @@ -16,7 +22,7 @@ in config = { dbus.policies = { "${config.flatpak.appId}" = "own"; - # we add other policies in ./common.nix + # we add other policies in ./custom-policy.nix }; # https://github.com/nixpak/nixpak/blob/master/modules/gpu.nix # The NixOS provider exposes host drivers at /run/opengl-driver (read-only) @@ -37,6 +43,18 @@ in # Nixpak uses --ro-bind-try / --dev-bind-try: absent host paths are skipped, # so shared mappings work on Intel/NVIDIA hosts and Apple Silicon alike. network = lib.mkDefault false; + + # nixpak binds sockets read-only, which is sufficient: connect() only needs + # the socket inode's write bit, and the kernel's read-only-filesystem check + # does not apply to sockets. PulseAudio is the exception (see bind.rw). + # Every app here is Wayland-only, so X11 stays disabled (the nixpak default, + # spelled out for intent) and PipeWire is shared for screensharing. + sockets = { + wayland = true; + pipewire = true; + x11 = false; + }; + bind.rw = [ [ (envSuffix "HOME" "/.var/app/${config.flatpak.appId}/cache") @@ -45,19 +63,23 @@ in (sloth.concat' sloth.xdgCacheHome "/fontconfig") (sloth.concat' sloth.xdgCacheHome "/mesa_shader_cache") - (sloth.concat [ - (sloth.env "XDG_RUNTIME_DIR") - "/" - (sloth.envOr "WAYLAND_DISPLAY" "no") - ]) - - (envSuffix "XDG_RUNTIME_DIR" "/at-spi/bus") + # gvfsd is a directory that holds FUSE mounts, not a socket, so it must + # stay writable. (envSuffix "XDG_RUNTIME_DIR" "/gvfsd") - (envSuffix "XDG_RUNTIME_DIR" "/pulse") - "/run/dbus" + # libpulse prepares its runtime dir before connecting (mkdir + chmod 0700), + # so this directory must be writable. nixpak's built-in `sockets.pulse` + # binds it read-only instead, and libpulse then aborts with EROFS when the + # directory is not already 0700 (e.g. pipewire-pulse has not started yet). + # Keep this the only bind of $XDG_RUNTIME_DIR/pulse. + (envSuffix "XDG_RUNTIME_DIR" "/pulse") ]; bind.ro = [ + # Sockets only need to be connectable, and connect() works on a read-only + # bind (see the note on `sockets` above). + (envSuffix "XDG_RUNTIME_DIR" "/at-spi/bus") + "/run/dbus" + (sloth.concat' sloth.xdgConfigHome "/gtk-2.0") (sloth.concat' sloth.xdgConfigHome "/gtk-3.0") (sloth.concat' sloth.xdgConfigHome "/gtk-4.0") diff --git a/hardening/nixpaks/qq.nix b/hardening/nixpaks/qq.nix index a02a1f4c..b2800ab1 100644 --- a/hardening/nixpaks/qq.nix +++ b/hardening/nixpaks/qq.nix @@ -28,7 +28,7 @@ let imports = [ ./modules/gui-base.nix ./modules/network.nix - ./modules/common.nix + ./modules/custom-policy.nix ]; bubblewrap = { @@ -42,11 +42,6 @@ let sloth.xdgVideosDir sloth.xdgPicturesDir ]; - sockets = { - x11 = false; - wayland = true; - pipewire = true; - }; }; }; }; diff --git a/hardening/nixpaks/telegram-desktop.nix b/hardening/nixpaks/telegram-desktop.nix index 6159a1c3..c4aadbb8 100644 --- a/hardening/nixpaks/telegram-desktop.nix +++ b/hardening/nixpaks/telegram-desktop.nix @@ -15,7 +15,7 @@ let imports = [ ./modules/gui-base.nix ./modules/network.nix - ./modules/common.nix + ./modules/custom-policy.nix ]; app.package = telegram-desktop; flatpak = { @@ -40,11 +40,6 @@ let sloth.xdgVideosDir sloth.xdgPicturesDir ]; - sockets = { - x11 = false; - wayland = true; - pipewire = true; - }; }; }; };