From 3a4833d46d3687764323401725540f0d488522c3 Mon Sep 17 00:00:00 2001 From: Gabriel Arazas Date: Mon, 5 Aug 2024 18:42:12 +0800 Subject: [PATCH] wrapper-manager/sandboxing/bubblewrap: init launcher submodule At the end of the day, I decided to make it in nixpkgs' runtime shell (GNU Bash) instead of Rust because it'll be a pain in the ass. --- .../sandboxing/bubblewrap/default.nix | 23 ++--- .../sandboxing/bubblewrap/launcher.nix | 81 +++++++++++++++++ .../sandboxing/bubblewrap/launcher/LICENSE | 19 ++++ .../sandboxing/bubblewrap/launcher/app.sh | 87 +++++++++++++++++++ .../bubblewrap/launcher/meson.build | 13 +++ .../bubblewrap/launcher/package.nix | 21 +++++ 6 files changed, 228 insertions(+), 16 deletions(-) create mode 100644 modules/wrapper-manager/sandboxing/bubblewrap/launcher.nix create mode 100644 modules/wrapper-manager/sandboxing/bubblewrap/launcher/LICENSE create mode 100644 modules/wrapper-manager/sandboxing/bubblewrap/launcher/app.sh create mode 100644 modules/wrapper-manager/sandboxing/bubblewrap/launcher/meson.build create mode 100644 modules/wrapper-manager/sandboxing/bubblewrap/launcher/package.nix diff --git a/modules/wrapper-manager/sandboxing/bubblewrap/default.nix b/modules/wrapper-manager/sandboxing/bubblewrap/default.nix index 3dabc049..ae36b319 100644 --- a/modules/wrapper-manager/sandboxing/bubblewrap/default.nix +++ b/modules/wrapper-manager/sandboxing/bubblewrap/default.nix @@ -55,7 +55,7 @@ let in { imports = [ - #./launcher.nix + ./launcher.nix ./dbus-filter.nix ./filesystem.nix ]; @@ -77,27 +77,18 @@ in config = lib.mkIf (config.sandboxing.variant == "bubblewrap") (lib.mkMerge [ { - # TODO: All of the Linux-exclusive flags could be handled by the - # launcher instead. ALSO MODULARIZE THIS CRAP! # Ordering of the arguments here matter(?). sandboxing.bubblewrap.extraArgs = cfg.extraArgs - ++ lib.optionals stdenv.isLinux [ - "--proc" "/proc" - "--dev" "/dev" - ] ++ lib.mapAttrsToList (var: metadata: - if metadata.action == "unset" - then "--unsetenv ${var}" - else "--setenv ${var} ${metadata.value}") + if metadata.action == "unset" then + "--unsetenv ${var}" + else if lib.elem metadata.action [ "prefix" "suffix" ] then + "--setenv ${var} ${lib.escapeShellArg (lib.concatStringsSep metadata.separator metadata.value)}" + else + "--setenv ${var} ${metadata.value}") config.env; - - arg0 = lib.getExe' submoduleCfg.package "bwrap"; - prependArgs = lib.mkBefore - (submoduleCfg.extraArgs - ++ [ "--" config.sandboxing.wraparound.arg0 ] - ++ config.sandboxing.wraparound.extraArgs); } (lib.mkIf submoduleCfg.enableNetwork { diff --git a/modules/wrapper-manager/sandboxing/bubblewrap/launcher.nix b/modules/wrapper-manager/sandboxing/bubblewrap/launcher.nix new file mode 100644 index 00000000..108dadaf --- /dev/null +++ b/modules/wrapper-manager/sandboxing/bubblewrap/launcher.nix @@ -0,0 +1,81 @@ +{ config, lib, options, pkgs, ... }: + +let + cfg = config.sandboxing.bubblewrap.launcher; + + bubblewrapModuleFactory = { isGlobal ? false }: { + package = lib.mkOption { + type = lib.types.package; + description = '' + Package containing the specialized Bubblewrap launcher used for this + module. + ''; + default = if isGlobal then pkgs.callPackage ./launcher/package.nix { } else cfg.package; + }; + + integrations = let + mkLauncherEnableOption = service: serviceName: lib.mkEnableOption "launcher integration for ${serviceName}" // { + default = if isGlobal then true else cfg.integrations.${service}.enable; + }; + in { + pipewire.enable = mkLauncherEnableOption "pipewire" "Pipewire"; + pulseaudio.enable = mkLauncherEnableOption "pulseaudio" "PulseAudio"; + wayland.enable = mkLauncherEnableOption "wayland" "Wayland desktop sessions"; + x11.enable = mkLauncherEnableOption "x11" "X11-based desktop sessions"; + }; + }; +in +{ + options.sandboxing.bubblewrap.launcher = bubblewrapModuleFactory { isGlobal = true; }; + + options.wrappers = + let + bubblewrapLauncherSubmodule = { config, lib, name, ... }: let + submoduleCfg = config.sandboxing.bubblewrap.launcher; + envSuffix = word: "WRAPPER_MANAGER_BWRAP_LAUNCHER_${word}"; + in { + options.sandboxing.bubblewrap.launcher = bubblewrapModuleFactory { isGlobal = false; }; + + config = lib.mkIf (config.sandboxing.variant == "bubblewrap") (lib.mkMerge [ + { + arg0 = lib.getExe' submoduleCfg.package "wrapper-manager-bubblewrap-launcher"; + prependArgs = lib.mkBefore + (config.sandboxing.bubblewrap.extraArgs + ++ [ "--" config.sandboxing.wraparound.arg0 ] + ++ config.sandboxing.wraparound.extraArgs); + env = { + "${envSuffix "BWRAP"}".value = lib.getExe' config.sandboxing.bubblewrap.package "bwrap"; + # We're just unsetting autoconfigure since we're configuring this + # through the module system anyways and would allow the user to + # have some more control over what can be enabled. + "${envSuffix "AUTOCONFIGURE"}".value = ""; + }; + } + + (lib.mkIf config.sandboxing.bubblewrap.dbus.enable { + env.${envSuffix "DBUS_PROXY"}.value = lib.getExe' config.sandboxing.bubblewrap.dbus.filter.package "xdg-dbus-proxy"; + env.${envSuffix "DBUS_PROXY_ARGS"}.value = lib.concatStringsSep " " config.sandboxing.bubblewrap.dbus.filter.extraArgs; + }) + + (lib.mkIf submoduleCfg.integrations.pulseaudio.enable { + env.${envSuffix "PULSEAUDIO"}.value = "1"; + }) + + (lib.mkIf submoduleCfg.integrations.pipewire.enable { + env.${envSuffix "PIPEWIRE"}.value = "1"; + }) + + (lib.mkIf submoduleCfg.integrations.x11.enable { + env.${envSuffix "X11"}.value = "1"; + }) + + (lib.mkIf submoduleCfg.integrations.wayland.enable { + env.${envSuffix "WAYLAND"}.value = "1"; + }) + ]); + }; + in + lib.mkOption { + type = with lib.types; attrsOf (submodule bubblewrapLauncherSubmodule); + }; +} diff --git a/modules/wrapper-manager/sandboxing/bubblewrap/launcher/LICENSE b/modules/wrapper-manager/sandboxing/bubblewrap/launcher/LICENSE new file mode 100644 index 00000000..ce20b53f --- /dev/null +++ b/modules/wrapper-manager/sandboxing/bubblewrap/launcher/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2024 Gabriel Arazas + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/modules/wrapper-manager/sandboxing/bubblewrap/launcher/app.sh b/modules/wrapper-manager/sandboxing/bubblewrap/launcher/app.sh new file mode 100644 index 00000000..927c3176 --- /dev/null +++ b/modules/wrapper-manager/sandboxing/bubblewrap/launcher/app.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash + +# A specialized launcher intended to handle a bunch of things in runtime such +# as adding flags when in certain systems and running xdg-dbus-proxy if +# required. Take note, we don't enforce any security model whatsoever, it's +# just a launcher that adds `bwrap` arguments in runtime for certain +# situations. +# +# Take note, we have the following design constraints for this launcher: +# +# * Using only the nixpkgs runtime shell and a few common dependencies found on +# Unix-adjacent systems. +# * No additional command-line options which means no flags and command-line +# parsing. This is essentially just a Bubblewrap wrapper. +# * If we ever let the user configure things, it should be done with +# environment variables with `WRAPPER_MANAGER_BWRAP_LAUNCHER` prefix. It's very +# long but who cares. +# * Ideally, there should be no options to clear the environment in this +# launcher. Let the user do it themselves if they want. + +declare -a additional_flags +: "${XDG_RUNTIME_DIR:="/run/user/$(id -u)"}" +: "${WRAPPER_MANAGER_BWRAP_LAUNCHER_BWRAP:="bwrap"}" +: "${WRAPPER_MANAGER_BWRAP_LAUNCHER_DBUS_PROXY:="xdg-dbus-proxy"}" +: "${WRAPPER_MANAGER_BWRAP_LAUNCHER_AUTOCONFIGURE:="1"}" + +is_autoconfigured_or() { + local service="$1" + [ "${WRAPPER_MANAGER_BWRAP_LAUNCHER_AUTOCONFIGURE}" = "1" ] || [ "${service}" = "1" ] +} + +# Bubblewrap is aggressively Linux-exclusive so we can add some things in here +# that are surely common within most Linux distros but just in case... +case "$(uname)" in + Linux*) + additional_flags+=(--proc /proc) + additional_flags+=(--dev /dev) + additional_flags+=(--dev-bind /dev/dri /dev/dri) + additional_flags+=(--tmpfs /tmp) + additional_flags+=(--ro-bind /sys/dev/char) + additional_flags+=(--ro-bind /sys/devices/pci0000:00) + + # Check if we're in a NixOS system. + if [[ -f /etc/NIXOS ]]; then + additional_flags+=(--ro-bind /run/opengl-driver/ /run/opengl-driver/) + + if [[ -d /run/opengl-driver-32 ]]; then + additional_flags+=(--ro-bind /run/opengl-driver-32 /run/opengl-driver-32/) + fi + fi + ;; +esac + +# TODO: Much of the flags added here are so far just cargo-culted lmao. +# Investigate it pls for the love of God. + +# Bind Wayland if it's detected to be running on one. +if is_autoconfigured_or "${WRAPPER_MANAGER_BWRAP_LAUNCHER_WAYLAND}" && [ -S "${XDG_RUNTIME_DIR}/${WAYLAND_DISPLAY}" ]; then + additional_flags+=(--ro-bind "${XDG_RUNTIME_DIR}/${WAYLAND_DISPLAY}") +fi + +# Bind Pipewire if it's detected. +if is_autoconfigured_or "${WRAPPER_MANAGER_BWRAP_LAUNCHER_PIPEWIRE}" && [ -S "${XDG_RUNTIME_DIR}/pipewire-0" ]; then + additional_flags+=(--ro-bind "${XDG_RUNTIME_DIR}/pipewire-0") +fi + +# Bind PulseAudio if it's detected and configured. +if is_autoconfigured_or "${WRAPPER_MANAGER_BWRAP_LAUNCHER_PULSEAUDIO}" && [ -e "${XDG_RUNTIME_DIR}/pulse/pid" ]; then + additional_flags+=(--ro-bind "${XDG_RUNTIME_DIR}/pulse") +fi + +# Bind X11 thingies if it's configured and detected. +if is_autoconfigured_or "${WRAPPER_MANAGER_BWRAP_LAUNCHER_X11}" && [ "${XAUTHORITY}" ]; then + additional_flags+=(--ro-bind "${XAUTHORITY}") + additional_flags+=(--ro-bind "/tmp/.X11-unix") +fi + +# Fork the D-Bus proxy in case it is needed. We only need to know if its needed +# if the *DBUS_PROXY_ARGS envvar is set. +if [ -n "${WRAPPER_MANAGER_BWRAP_LAUNCHER_DBUS_PROXY_ARGS}" ]; then + ( + ${WRAPPER_MANAGER_BWRAP_LAUNCHER_BWRAP} "${additional_flags[@]}" \ + -- "${WRAPPER_MANAGER_BWRAP_LAUNCHER_DBUS_PROXY}" "${WRAPPER_MANAGER_BWRAP_LAUNCHER_DBUS_PROXY_ARGS[@]}" + ) & +fi + +exec ${WRAPPER_MANAGER_BWRAP_LAUNCHER_BWRAP} "${additional_flags[@]}" "$@" diff --git a/modules/wrapper-manager/sandboxing/bubblewrap/launcher/meson.build b/modules/wrapper-manager/sandboxing/bubblewrap/launcher/meson.build new file mode 100644 index 00000000..8e599f51 --- /dev/null +++ b/modules/wrapper-manager/sandboxing/bubblewrap/launcher/meson.build @@ -0,0 +1,13 @@ +project('wrapper-manager-bubblewrap-launcher', + version: '0.1.0', + license: 'MIT', + meson_version: '>=0.54.0', +) + +configure_file( + input: 'app.sh', + output: meson.project_name(), + install_dir: get_option('bindir'), + install_mode: 'rwxr-xr-x', + install: true +) diff --git a/modules/wrapper-manager/sandboxing/bubblewrap/launcher/package.nix b/modules/wrapper-manager/sandboxing/bubblewrap/launcher/package.nix new file mode 100644 index 00000000..b180e658 --- /dev/null +++ b/modules/wrapper-manager/sandboxing/bubblewrap/launcher/package.nix @@ -0,0 +1,21 @@ +{ + stdenv, + lib, + meson, + ninja +}: + +stdenv.mkDerivation (finalAttrs: { + pname = "wrapper-manager-bubblewrap-launcher"; + version = "0.1.0"; + + src = lib.cleanSource ./.; + + nativeBuildInputs = [ meson ninja ]; + + meta = { + description = "wrapper-manager specialized launcher for Bubblewrap environments"; + license = lib.licenses.mit; + mainProgram = finalAttrs.pname; + }; +})