2024-01-23 01:23:14 +00:00
{ lib
2024-01-23 08:01:23 +00:00
, buildPackages
2024-01-23 01:23:14 +00:00
, firejail
, runCommand
, runtimeShell
2024-01-23 03:48:09 +00:00
, sane-sandboxed
2024-01-23 08:01:23 +00:00
, writeTextFile
2024-01-23 01:23:14 +00:00
} :
{ pkgName , package , vpn ? null , allowedHomePaths ? [ ] , allowedRootPaths ? [ ] , binMap ? { } }:
let
2024-01-23 03:48:09 +00:00
sane-sandboxed' = sane-sandboxed . meta . mainProgram ; #< load by bin name to reduce rebuilds
2024-01-23 01:23:14 +00:00
allowPath = p : [
2024-01-23 08:01:23 +00:00
" - - s a n e - s a n d b o x - p a t h "
p
] ;
allowHomePath = p : [
" - - s a n e - s a n d b o x - h o m e - p a t h "
p
2024-01-23 01:23:14 +00:00
] ;
allowPaths = paths : lib . flatten ( builtins . map allowPath paths ) ;
allowHomePaths = paths : lib . flatten ( builtins . map allowHomePath paths ) ;
vpnItems = [
2024-01-23 08:01:23 +00:00
" - - s a n e - s a n d b o x - n e t "
vpn . bridgeDevice
] ++ lib . flatten ( builtins . map ( addr : [
" - - s a n e - s a n d b o x - d n s "
addr
] ) vpn . dns ) ;
2024-01-23 01:23:14 +00:00
2024-01-23 08:01:23 +00:00
sandboxFlags = allowPaths allowedRootPaths
2024-01-23 01:23:14 +00:00
++ allowHomePaths allowedHomePaths
++ lib . optionals ( vpn != null ) vpnItems ;
# 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.
#
# no.2 would require special-casing for .desktop files, to ensure they refer to the jailed version.
# no.1 may require extra care for recursive binaries, or symlink-heavy binaries (like busybox)
# 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.
package' = if package . override . __functionArgs ? runCommand then
package . override {
runCommand = name : env : cmd : runCommand name env ( cmd + lib . optionalString ( name == package . name ) ''
# if the package is a runCommand (common for wrappers), then patch it to call our `postFixup` hook, first
runHook postFixup
'' ) ;
}
else
package
;
packageWrapped = package' . overrideAttrs ( unwrapped : {
postFixup = ( unwrapped . postFixup or " " ) + ''
2024-01-23 08:01:23 +00:00
getProfileFromBinMap ( ) {
2024-01-23 01:23:14 +00:00
case " $ 1 " in
$ { builtins . concatStringsSep " \n " ( lib . mapAttrsToList
( bin : profile : ''
( $ { bin } )
2024-01-23 08:01:23 +00:00
echo " ${ profile } "
2024-01-23 01:23:14 +00:00
; ;
'' )
binMap
) }
( * )
; ;
esac
}
2024-01-23 08:01:23 +00:00
sandboxWrap ( ) {
_name = " $ 1 "
_profileFromBinMap = " $ ( g e t P r o f i l e F r o m B i n M a p $ _ n a m e ) "
_profiles = ( " $ _ p r o f i l e F r o m B i n M a p " " $ _ n a m e " " ${ pkgName } " " ${ unwrapped . pname or " " } " " ${ unwrapped . name or " " } " )
# filter to just the unique profiles
_profileArgs = ( )
for _profile in " ' ' ${ _profiles [ @ ] } " ; do
if [ - n " $ _ p r o f i l e " ] && ! [ [ " ' ' ${ _profileArgs [ @ ] } " = ~ " $ _ p r o f i l e " ] ] ; then
_profileArgs + = ( " - - s a n e - s a n d b o x - p r o f i l e " " $ _ p r o f i l e " )
fi
done
mv " $ o u t / b i n / $ _ n a m e " " $ o u t / b i n / . $ _ n a m e - s a n d b o x e d "
cat < < EOF > > " $ o u t / b i n / $ _ n a m e "
2024-01-23 01:23:14 +00:00
#!${runtimeShell}
2024-01-23 03:48:09 +00:00
exec $ { sane-sandboxed' } \
2024-01-23 08:01:23 +00:00
'' ${ _profileArgs [ @ ] } \
" $ o u t / b i n / . $ _ n a m e - s a n d b o x e d " " \$ @ "
2024-01-23 01:23:14 +00:00
EOF
2024-01-23 08:01:23 +00:00
chmod + x " $ o u t / b i n / $ _ n a m e "
2024-01-23 01:23:14 +00:00
}
for _p in $ ( ls " $ o u t / b i n / " ) ; do
2024-01-23 08:01:23 +00:00
sandboxWrap " $ _ p "
2024-01-23 01:23:14 +00:00
done
# stamp file which can be consumed to ensure this wrapping code was actually called.
mkdir - p $ out/nix-support
touch $ out/nix-support/sandboxed
'' ;
meta = ( unwrapped . meta or { } ) // {
# take precedence over non-sandboxed versions of the same binary.
priority = ( ( unwrapped . meta or { } ) . priority or 0 ) - 1 ;
} ;
passthru = ( unwrapped . passthru or { } ) // {
2024-01-23 08:01:23 +00:00
checkSandboxed = runCommand " ${ pkgName } - c h e c k - s a n d b o x e d " { } ''
2024-01-23 01:23:14 +00:00
# this pseudo-package gets "built" as part of toplevel system build.
# if the build is failing here, that means the program isn't properly sandboxed:
# make sure that "postFixup" gets called as part of the package's build script
test - f " ${ packageWrapped } / n i x - s u p p o r t / s a n d b o x e d " \
&& touch " $ o u t "
'' ;
2024-01-23 08:01:23 +00:00
sandboxProfiles = writeTextFile {
name = " ${ pkgName } - s a n d b o x - p r o f i l e s " ;
destination = " / s h a r e / s a n e - s a n d b o x e d / p r o f i l e s / ${ pkgName } . p r o f i l e " ;
text = builtins . concatStringsSep " \n " sandboxFlags ;
} ;
2024-01-23 01:23:14 +00:00
} ;
} ) ;
in
packageWrapped