From a6b824d3c4a5f1b51c1fa25b10948ff1c295048a Mon Sep 17 00:00:00 2001 From: Colin Date: Sat, 27 Jan 2024 12:23:25 +0000 Subject: [PATCH] modules/programs/sandbox: add an "embedProfile" option to source sandbox settings from the package instead of the system --- modules/programs/default.nix | 13 ++++++++++- modules/programs/make-sandboxed.nix | 21 ++++++++++------- modules/programs/sane-sandboxed | 35 ++++++++++++++++++++--------- 3 files changed, 50 insertions(+), 19 deletions(-) diff --git a/modules/programs/default.nix b/modules/programs/default.nix index 2f110e24..92070871 100644 --- a/modules/programs/default.nix +++ b/modules/programs/default.nix @@ -43,7 +43,7 @@ let in makeSandboxed { inherit pkgName package; - inherit (sandbox) binMap method extraConfig; + inherit (sandbox) binMap embedProfile extraConfig method; vpn = if net == "vpn" then vpn else null; allowedHomePaths = builtins.attrNames fs ++ builtins.attrNames persist.byPath ++ sandbox.extraHomePaths; allowedRootPaths = [ @@ -222,6 +222,17 @@ let how/whether to sandbox all binaries in the package. ''; }; + sandbox.embedProfile = mkOption { + type = types.bool; + default = false; + description = '' + whether to embed the sandbox settings (path access, etc) into the wrapped binary that lives in /nix/store (true), + or to encode only a profile name in the wrapper, and use it to query the settings at runtime (false). + + embedded profile means you have to rebuild the wrapper any time you adjust the sandboxing flags, + but it also means you can run the program without installing it: helpful for iteration. + ''; + }; sandbox.binMap = mkOption { type = types.attrsOf types.str; default = {}; diff --git a/modules/programs/make-sandboxed.nix b/modules/programs/make-sandboxed.nix index 664864c3..9aae9a43 100644 --- a/modules/programs/make-sandboxed.nix +++ b/modules/programs/make-sandboxed.nix @@ -4,7 +4,7 @@ , sane-sandboxed , writeTextFile }: -{ pkgName, package, method, vpn ? null, allowedHomePaths ? [], allowedRootPaths ? [], binMap ? {}, extraConfig ? [] }: +{ pkgName, package, method, vpn ? null, allowedHomePaths ? [], allowedRootPaths ? [], binMap ? {}, extraConfig ? [], embedProfile ? false }: let sane-sandboxed' = sane-sandboxed.meta.mainProgram; #< load by bin name to reduce rebuilds @@ -34,6 +34,15 @@ let ++ lib.optionals (vpn != null) vpnItems ++ extraConfig; + sandboxProfilesPkg = writeTextFile { + name = "${pkgName}-sandbox-profiles"; + destination = "/share/sane-sandboxed/profiles/${pkgName}.profile"; + text = builtins.concatStringsSep "\n" sandboxFlags; + }; + sandboxProfileDir = "${sandboxProfilesPkg}/share/sane-sandboxed/profiles"; + + maybeEmbedProfilesDir = lib.optionalString embedProfile ''"--sane-sandbox-profile-dir" "${sandboxProfileDir}"''; + # two ways i could wrap a package in a sandbox: # 1. package.overrideAttrs, with `postFixup`. # 2. pkgs.symlinkJoin, or pkgs.runCommand, creating an entirely new package which calls into the inner binaries. @@ -43,7 +52,7 @@ let # but even no.2 has to consider such edge-cases, just less frequently. # no.1 may bloat rebuild times. # - # ultimately, no.1 is probably more reliable, but i expect i'll factor out a switch to allow either approach -- particularly when debugging package buld failures. + # ultimately, no.1 is probably more reliable, but i expect i'll factor out a switch to allow either approach -- particularly when debugging package build failures. package' = if package.override.__functionArgs ? runCommand then package.override { runCommand = name: env: cmd: runCommand name env (cmd + lib.optionalString (name == package.name) '' @@ -77,7 +86,7 @@ let _profiles=("$_profileFromBinMap" "$_name" "${pkgName}" "${unwrapped.pname or ""}" "${unwrapped.name or ""}") # filter to just the unique profiles - _profileArgs=() + _profileArgs=(${maybeEmbedProfilesDir}) for _profile in "''${_profiles[@]}"; do if [ -n "$_profile" ] && ! [[ " ''${_profileArgs[@]} " =~ " $_profile " ]]; then _profileArgs+=("--sane-sandbox-profile" "$_profile") @@ -121,11 +130,7 @@ let test "$_numExec" -ne 0 && touch "$out" ''; - sandboxProfiles = writeTextFile { - name = "${pkgName}-sandbox-profiles"; - destination = "/share/sane-sandboxed/profiles/${pkgName}.profile"; - text = builtins.concatStringsSep "\n" sandboxFlags; - }; + sandboxProfiles = sandboxProfilesPkg; }; }); in diff --git a/modules/programs/sane-sandboxed b/modules/programs/sane-sandboxed index 8ce3587d..75f25f03 100644 --- a/modules/programs/sane-sandboxed +++ b/modules/programs/sane-sandboxed @@ -4,6 +4,8 @@ isDebug="$SANE_SANDBOX_DEBUG" test -n "$isDebug" && set -x isDisable="$SANE_SANDBOX_DISABLE" +profileDirs=(@profileDirs@) + cliArgs=() cliPathArgs=() autodetect= @@ -31,17 +33,25 @@ loadProfileByPath() { } tryLoadProfileByName() { - profilesNamed+=("$1") + _profile="$1" + if [ "${_profile:0:1}" = "/" ]; then + # absolute path to profile. + # consider it an error if it doesn't exist. + # in general, prefer to use `--sane-sandbox-profile-dir` and specify the profile by name. + # doing so maximizes compatibility with anything else that uses the name, like firejail. + loadProfileByPath "$_profile" + else + profilesNamed+=("$_profile") - _profileDirs=(@profileDirs@) - for _profileDir in "${_profileDirs[@]}"; do - _profile="$_profileDir/$1.profile" - debug "try profile at path: '$_profile'" - if [ -f "$_profile" ]; then - loadProfileByPath "$_profile" - break - fi - done + for _profileDir in "${profileDirs[@]}"; do + _profilePath="$_profileDir/$_profile.profile" + debug "try profile at path: '$_profilePath'" + if [ -f "$_profilePath" ]; then + loadProfileByPath "$_profilePath" + break + fi + done + fi } # convert e.g. `file:///Local%20Users/foo.mp3` to `file:///Local Users/foo.mp3` @@ -160,6 +170,11 @@ parseArgs() { tryLoadProfileByName "$1" shift ;; + (--sane-sandbox-profile-dir) + _dir="$1" + shift + profileDirs+=("$_dir") + ;; (*) parseArgsExtra+=("$_arg") ;;