From b649071d98bb8f52d9f504d06b7b0f7823434979 Mon Sep 17 00:00:00 2001 From: Colin Date: Wed, 15 May 2024 08:33:29 +0000 Subject: [PATCH] programs: sandboxing: make the profiles be generic across users this is a step toward making the profile not even be dynamically loaded, since its content is no longer dynamic :) --- modules/programs/default.nix | 140 ++++++++++++++++++----------------- 1 file changed, 71 insertions(+), 69 deletions(-) diff --git a/modules/programs/default.nix b/modules/programs/default.nix index d2594970..c2aa51a3 100644 --- a/modules/programs/default.nix +++ b/modules/programs/default.nix @@ -63,39 +63,74 @@ let vpn = lib.findSingle (v: v.default) null null (builtins.attrValues config.sane.vpn); - sandboxProfilesFor = userName: let - allowedHomePaths = builtins.attrNames fs ++ builtins.attrNames persist.byPath ++ sandbox.extraHomePaths; - allowedRunPaths = sandbox.extraRuntimePaths; - homeDir = config.sane.users."${userName}".home; - uid = config.users.users."${userName}".uid; - xdgRuntimeDir = "/run/user/${builtins.toString uid}"; - fullHomePaths = lib.optionals (userName != null) ( - builtins.map - (p: path-lib.concat [ homeDir p ]) - allowedHomePaths - ); - fullRunPaths = lib.optionals (userName != null) ( - builtins.map - (p: path-lib.concat [ xdgRuntimeDir p ]) - allowedRunPaths - ); - allowedPaths = [ - "/nix/store" - "/bin/sh" + allowedHomePaths = builtins.attrNames fs ++ builtins.attrNames persist.byPath ++ sandbox.extraHomePaths; + allowedRunPaths = sandbox.extraRuntimePaths; + allowedPaths = [ + "/nix/store" + "/bin/sh" - "/etc" #< especially for /etc/profiles/per-user/$USER/bin - "/run/current-system" #< for basics like `ls`, and all this program's `suggestedPrograms` (/run/current-system/sw/bin) - "/run/wrappers" #< SUID wrappers, in this case so that firejail can be re-entrant. TODO: remove! - # /run/opengl-driver is a symlink into /nix/store; needed by e.g. mpv - "/run/opengl-driver" - "/run/opengl-driver-32" #< XXX: doesn't exist on aarch64? - "/usr/bin/env" - ] ++ lib.optionals (config.services.resolved.enable) [ - "/run/systemd/resolve" #< to allow reading /etc/resolv.conf, which ultimately symlinks here (if using systemd-resolved) - ] ++ lib.optionals (builtins.elem "system" sandbox.whitelistDbus) [ "/run/dbus/system_bus_socket" ] - ++ sandbox.extraPaths + "/etc" #< especially for /etc/profiles/per-user/$USER/bin + "/run/current-system" #< for basics like `ls`, and all this program's `suggestedPrograms` (/run/current-system/sw/bin) + "/run/wrappers" #< SUID wrappers, in this case so that firejail can be re-entrant. TODO: remove! + # /run/opengl-driver is a symlink into /nix/store; needed by e.g. mpv + "/run/opengl-driver" + "/run/opengl-driver-32" #< XXX: doesn't exist on aarch64? + "/usr/bin/env" + ] ++ lib.optionals (config.services.resolved.enable) [ + "/run/systemd/resolve" #< to allow reading /etc/resolv.conf, which ultimately symlinks here (if using systemd-resolved) + ] ++ lib.optionals (builtins.elem "system" sandbox.whitelistDbus) [ "/run/dbus/system_bus_socket" ] + ++ sandbox.extraPaths + ; + + additionalPathsForUser = user: let + fullHomePaths = builtins.map + (p: path-lib.concat [ user.home p ]) + allowedHomePaths ; - in makeProfile { + fullRunPaths = builtins.map + (p: path-lib.concat [ "/run/user/${builtins.toString user.uid}" p ]) + allowedRunPaths + ; + in + fullHomePaths ++ fullRunPaths; + # expand user-specific paths for all users. + # this feeds into the symlink cache, so that cached link data can be available no matter which user invokes the program. + userPathsClosure = lib.flatten ( + builtins.map additionalPathsForUser (builtins.attrValues config.users.users) + ); + symlinkCache = { + "/bin/sh" = config.environment.binsh; + "${builtins.unsafeDiscardStringContext config.environment.binsh}" = "bash"; + "/usr/bin/env" = config.environment.usrbinenv; + "${builtins.unsafeDiscardStringContext config.environment.usrbinenv}" = "coreutils"; + + # "/run/current-system" = "${config.system.build.toplevel}"; + # XXX: /run/current-system symlink can't be cached without forcing regular mass rebuilds: + # mount it as if it were a directory instead. + "/run/current-system" = ""; + } // lib.optionalAttrs config.hardware.opengl.enable { + "/run/opengl-driver" = let + gl = config.hardware.opengl; + # from: + package = pkgs.buildEnv { + name = "opengl-drivers"; + paths = [ gl.package ] ++ gl.extraPackages; + }; + in "${package}"; + } // lib.optionalAttrs (config.hardware.opengl.enable && config.hardware.opengl.driSupport32Bit) { + "/run/opengl-driver-32" = let + gl = config.hardware.opengl; + # from: + package = pkgs.buildEnv { + name = "opengl-drivers-32bit"; + paths = [ gl.package32 ] ++ gl.extraPackages32; + }; + in "${package}"; + } // ( + symlinksToAttrs (symlinksClosure (allowedPaths ++ userPathsClosure)) + ); + + sandboxProfiles = makeProfile { inherit pkgName; inherit (sandbox) autodetectCliPaths @@ -112,41 +147,8 @@ let vpn.dns else null; - inherit allowedPaths allowedHomePaths allowedRunPaths; - - symlinkCache = { - "/bin/sh" = config.environment.binsh; - "${builtins.unsafeDiscardStringContext config.environment.binsh}" = "bash"; - "/usr/bin/env" = config.environment.usrbinenv; - "${builtins.unsafeDiscardStringContext config.environment.usrbinenv}" = "coreutils"; - - # "/run/current-system" = "${config.system.build.toplevel}"; - # XXX: /run/current-system symlink can't be cached without forcing regular mass rebuilds: - # mount it as if it were a directory instead. - "/run/current-system" = ""; - } // lib.optionalAttrs config.hardware.opengl.enable { - "/run/opengl-driver" = let - gl = config.hardware.opengl; - # from: - package = pkgs.buildEnv { - name = "opengl-drivers"; - paths = [ gl.package ] ++ gl.extraPackages; - }; - in "${package}"; - } // lib.optionalAttrs (config.hardware.opengl.enable && config.hardware.opengl.driSupport32Bit) { - "/run/opengl-driver-32" = let - gl = config.hardware.opengl; - # from: - package = pkgs.buildEnv { - name = "opengl-drivers-32bit"; - paths = [ gl.package32 ] ++ gl.extraPackages32; - }; - in "${package}"; - } // ( - symlinksToAttrs (symlinksClosure (allowedPaths ++ fullHomePaths ++ fullRunPaths)) - ); + inherit allowedPaths allowedHomePaths allowedRunPaths symlinkCache; }; - defaultProfile = sandboxProfilesFor config.sane.defaultUser; makeSandboxedArgs = { inherit pkgName package; inherit (sandbox) @@ -157,13 +159,13 @@ let in makeSandboxed (makeSandboxedArgs // { passthru = { - inherit sandboxProfilesFor; + inherit sandboxProfiles; withEmbeddedSandboxer = makeSandboxed (makeSandboxedArgs // { # embed the sandboxer AND a profile, whichever profile the package would have if installed by the default user. # useful to iterate a package's sandbox config without redeploying. embedSandboxer = true; extraSandboxerArgs = [ - "--sanebox-profile-dir" "${defaultProfile}/share/sanebox/profiles" + "--sanebox-profile-dir" "${sandboxProfiles}/share/sanebox/profiles" ]; }); withEmbeddedSandboxerOnly = makeSandboxed (makeSandboxedArgs // { @@ -566,7 +568,7 @@ let # conditionally add to system PATH and env environment = lib.optionalAttrs (p.enabled && p.enableFor.system) { systemPackages = lib.optionals (p.package != null) ( - [ p.package ] ++ lib.optional (p.sandbox.enable && p.sandbox.method != null) (p.package.passthru.sandboxProfilesFor null) + [ p.package ] ++ lib.optional (p.sandbox.enable && p.sandbox.method != null) (p.package.passthru.sandboxProfiles) ); # sessionVariables are set by PAM, as opposed to environment.variables which goes in /etc/profile sessionVariables = p.env; @@ -575,7 +577,7 @@ let # conditionally add to user(s) PATH users.users = lib.mapAttrs (userName: en: { packages = lib.optionals (p.package != null && en && p.enabled) ( - [ p.package ] ++ lib.optional (p.sandbox.enable && p.sandbox.method != null) (p.package.passthru.sandboxProfilesFor userName) + [ p.package ] ++ lib.optional (p.sandbox.enable && p.sandbox.method != null) (p.package.passthru.sandboxProfiles) ); }) p.enableFor.user;