2024-01-23 01:23:14 +00:00
{ lib
, firejail
, runCommand
, runtimeShell
2024-01-23 03:48:09 +00:00
, sane-sandboxed
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 : [
" n o b l a c k l i s t ${ p } "
" w h i t e l i s t ${ p } "
] ;
allowHomePath = p : allowPath '' ''$ { H O M E } / ${ p } '' ;
allowPaths = paths : lib . flatten ( builtins . map allowPath paths ) ;
allowHomePaths = paths : lib . flatten ( builtins . map allowHomePath paths ) ;
vpnItems = [
" n e t ${ vpn . bridgeDevice } "
] ++ ( builtins . map ( addr : " d n s ${ addr } " ) vpn . dns ) ;
firejailItems = [
# "--quiet" #< TODO: enable
# "--tracelog" # logs blacklist violations to syslog (but default firejail disallows this)
# "--keep-dev-shm" #< required for spotify
] ++ allowPaths allowedRootPaths
++ 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 " " ) + ''
tryFirejailProfile ( ) {
_maybeProfile = " ${ firejail } / e t c / f i r e j a i l / $ 1 . p r o f i l e "
echo " c h e c k i n g f o r f i r e j a i l p r o f i l e a t : $ _ m a y b e P r o f i l e "
if [ - e " $ _ m a y b e P r o f i l e " ] ; then
firejailProfilePath = " $ _ m a y b e P r o f i l e "
firejailProfileName = " $ 1 "
true
else
false
fi
}
tryFirejailProfileFromBinMap ( ) {
case " $ 1 " in
$ { builtins . concatStringsSep " \n " ( lib . mapAttrsToList
( bin : profile : ''
( $ { bin } )
tryFirejailProfile " ${ profile } "
; ;
'' )
binMap
) }
( * )
echo " n o s p e c i a l - c a s e p r o f i l e f o r $ 1 "
false
; ;
esac
}
getFirejailProfile ( ) {
tryFirejailProfileFromBinMap " $ 1 " \
|| tryFirejailProfile " $ 1 " \
|| tryFirejailProfile " ${ unwrapped . pname or " " } " \
|| tryFirejailProfile " ${ unwrapped . name or " " } " \
|| tryFirejailProfile " ${ pkgName } " \
|| ( echo " f a i l e d t o l o c a t e f i r e j a i l p r o f i l e f o r $ 1 : a b o r t i n g ! " && false )
}
firejailWrap ( ) {
name = " $ 1 "
getFirejailProfile " $ n a m e "
mv " $ o u t / b i n / $ n a m e " " $ o u t / b i n / . $ n a m e - f i r e j a i l e d "
cat < < EOF > > " $ o u t / b i n / $ n a m e "
#!${runtimeShell}
2024-01-23 03:48:09 +00:00
exec $ { sane-sandboxed' } \
- - sane-sandbox-firejail-arg - - include = " ${ pkgName } . l o c a l " \
- - sane-sandbox-firejail-arg - - profile = " : $ f i r e j a i l P r o f i l e N a m e " \
- - sane-sandbox-firejail-arg - - join-or-start = " $ f i r e j a i l P r o f i l e N a m e " \
" $ o u t / b i n / . $ n a m e - f i r e j a i l e d " " \$ @ "
2024-01-23 01:23:14 +00:00
EOF
chmod + x " $ o u t / b i n / $ n a m e "
}
for _p in $ ( ls " $ o u t / b i n / " ) ; do
firejailWrap " $ _ p "
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 { } ) // {
checkSandboxed = runCommand " ${ unwrapped . name or unwrapped . pname or " u n k n o w n " } - c h e c k - s a n d b o x e d " { } ''
# 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 "
'' ;
firejailLocalConfig = builtins . concatStringsSep " \n " firejailItems ;
} ;
} ) ;
in
packageWrapped