sandboxing: fix checkSandboxed to handle packages with multiple outputs

This commit is contained in:
Colin 2024-05-31 23:53:42 +00:00
parent e3e86a43a9
commit f875db916d
4 changed files with 70 additions and 32 deletions

View File

@ -39,7 +39,8 @@ let
else
let
makeSandboxArgs = pkgs.callPackage ./make-sandbox-args.nix { };
makeSandboxed = pkgs.callPackage ./make-sandboxed.nix { sanebox = config.sane.programs.sanebox.package; };
# makeSandboxed = pkgs.callPackage ./make-sandboxed.nix { sanebox = config.sane.programs.sanebox.package; };
makeSandboxed = pkgs.callPackage ./make-sandboxed.nix { };
vpn = lib.findSingle (v: v.default) null null (builtins.attrValues config.sane.vpn);

View File

@ -229,43 +229,54 @@ let
priority = ((prevAttrs.meta or {}).priority or 0) - 1;
};
passthru = (prevAttrs.passthru or {}) // extraPassthru // {
checkSandboxed = runCommandLocal "${pkgName}-check-sandboxed" {} ''
checkSandboxed = runCommandLocal "${pkgName}-check-sandboxed" {
nativeBuildInputs = [ sanebox ];
buildInputs = builtins.map (out: finalAttrs.finalPackage."${out}") (finalAttrs.outputs or [ "out" ]);
} ''
set -e
# invoke each binary in a way only the sandbox wrapper will recognize,
# ensuring that every binary has in fact been wrapped.
_numExec=0
_checkExecutable() {
echo "checking if $1 is sandboxed"
PATH="${finalAttrs.finalPackage}/bin:${sanebox}/bin:$PATH" \
SANEBOX_DISABLE=1 \
"$1" --sanebox-replace-cli echo "printing for test" \
local dir="$1"
local binname="$2"
echo "checking if $dir/$binname is sandboxed"
# XXX: call by full path because some binaries (e.g. util-linux) would otherwise
# be shadowed by things the nix builder implicitly puts on PATH.
"$dir/$binname" --sanebox-replace-cli echo "printing for test" \
| grep "printing for test"
_numExec=$(( $_numExec + 1 ))
}
_checkDir() {
for b in $(ls "$1"); do
if [ -d "$1/$b" ]; then
local dir="$1"
for b in $(ls "$dir"); do
if [ -d "$dir/$b" ]; then
if [ "$b" != .sandboxed ]; then
_checkDir "$1/$b"
_checkDir "$dir/$b"
fi
elif [ -x "$1/$b" ]; then
_checkExecutable "$1/$b"
elif [ -x "$dir/$b" ]; then
_checkExecutable "$dir" "$b"
else
test -n "$CHECK_DIR_NON_BIN"
fi
done
}
# *everything* in the bin dir should be a wrapped executable
if [ -e "${finalAttrs.finalPackage}/bin" ]; then
_checkDir "${finalAttrs.finalPackage}/bin"
fi
for outDir in $buildInputs; do
echo "starting crawl from package output: $outDir"
# *everything* in the bin dir should be a wrapped executable
if [ -e "$outDir/bin" ]; then
echo "checking toplevel dir at $outDir/bin"
_checkDir "$outDir/bin"
fi
# the libexec dir is 90% wrapped executables, but sometimes also .so/.la objects.
# note that this directory isn't flat
if [ -e "${finalAttrs.finalPackage}/libexec" ]; then
CHECK_DIR_NON_BIN=1 _checkDir "${finalAttrs.finalPackage}/libexec"
fi
# the libexec dir is 90% wrapped executables, but sometimes also .so/.la objects.
# note that this directory isn't flat
if [ -e "$outDir/libexec" ]; then
echo "checking toplevel dir at $outDir/libexec"
CHECK_DIR_NON_BIN=1 _checkDir "$outDir/libexec"
fi
done
echo "successfully tested $_numExec binaries"
test "$_numExec" -ne 0

View File

@ -1,6 +1,7 @@
{ lib, stdenv
, bash
, bubblewrap
, coreutils
, passt
, landlock-sandboxer
, libcap
@ -24,7 +25,8 @@ stdenv.mkDerivation {
--replace-fail '@bwrap@' '${lib.getExe bubblewrap}' \
--replace-fail '@landlockSandboxer@' '${lib.getExe landlock-sandboxer}' \
--replace-fail '@capsh@' '${lib.getExe' libcap "capsh"}' \
--replace-fail '@pasta@' '${lib.getExe' passt "pasta"}'
--replace-fail '@pasta@' '${lib.getExe' passt "pasta"}' \
--replace-fail '@env@' '${lib.getExe' coreutils "env"}'
runHook postBuild
'';
@ -36,6 +38,16 @@ stdenv.mkDerivation {
runHook postInstall
'';
passthru = {
runtimeDeps = [
bubblewrap
coreutils
landlock-sandboxer
libcap
passt
];
};
meta = {
description = ''
helper program to run some other program in a sandbox.

View File

@ -6,6 +6,7 @@ BWRAP_FALLBACK='@bwrap@'
LANDLOCK_SANDBOXER_FALLBACK='@landlockSandboxer@'
CAPSH_FALLBACK='@capsh@'
PASTA_FALLBACK='@pasta@'
ENV_FALLBACK='@env@'
## EARLY DEBUG HOOKS
@ -254,13 +255,17 @@ parent() {
# `locate outVar <bin-name> </path/to/default>` => if `<bin-name>` is on PATH, then return that, else </path/to/default>
locate() {
# N.B.: explicitly avoid returning the output of `command -v`, for optimization.
# unlike other bash builtins, `x="$(command -v y)"` forks, whereas just `command -v y` does not.
if command -v "$2" > /dev/null; then
declare -g "$1"="$2"
else
declare -g "$1"="$3"
fi
local outVar="$1"
local bin="$2"
local loc="$3"
# N.B.: `bin=$(command -v $bin)` would make more sense, but it forks.
for dir in ${PATH//:/ }; do
if [ -e "$dir/$bin" ]; then
loc="$dir/$bin"
break
fi
done
declare -g "$outVar"="$loc"
}
# `urldecode outVar <uri>`
@ -680,6 +685,7 @@ bwrapGetCli() {
# --unshare-uts
# --unshare-user (implicit to every non-suid call to bwrap)
locate _bwrap "bwrap" "$BWRAP_FALLBACK"
locate _env "env" "$ENV_FALLBACK"
if [ -n "$bwrapUsePasta" ]; then
# pasta drops us into an environment where we're root, but some apps complain if run as root.
# TODO: this really belongs on the `pastaonlyGetCli` side.
@ -697,7 +703,7 @@ bwrapGetCli() {
"${bwrapUnshareUts[@]}"
"${bwrapVirtualizeDev[@]}" "${bwrapVirtualizeProc[@]}" "${bwrapVirtualizeTmp[@]}"
"${bwrapFlags[@]}" --
env "${portalEnv[@]}" "${cliArgs[@]}"
"$_env" "${portalEnv[@]}" "${cliArgs[@]}"
)
if [ -n "$bwrapUsePasta" ]; then
pastaonlyGetCli
@ -769,9 +775,10 @@ landlockGetCli() {
# invoke the actual user command.
locate _sandboxer "sandboxer" "$LANDLOCK_SANDBOXER_FALLBACK"
locate _capsh "capsh" "$CAPSH_FALLBACK"
cliArgs=(env LL_FS_RO= LL_FS_RW="$landlockPaths"
locate _env "env" "$ENV_FALLBACK"
cliArgs=("$_env" LL_FS_RO= LL_FS_RW="$landlockPaths"
"$_sandboxer"
"$_capsh" "--caps=$capshCapsArg" --no-new-privs --shell="/usr/bin/env" -- "${portalEnv[@]}" "${cliArgs[@]}"
"$_capsh" "--caps=$capshCapsArg" --no-new-privs --shell="$_env" -- "${portalEnv[@]}" "${cliArgs[@]}"
)
}
@ -823,8 +830,9 @@ capshonlyIngestCapability() {
capshonlyGetCli() {
locate _capsh "capsh" "$CAPSH_FALLBACK"
locate _env "env" "$ENV_FALLBACK"
cliArgs=(
"$_capsh" "--caps=$capshCapsArg" --no-new-privs --shell="/usr/bin/env" -- "${portalEnv[@]}" "${cliArgs[@]}"
"$_capsh" "--caps=$capshCapsArg" --no-new-privs --shell="$_env" -- "${portalEnv[@]}" "${cliArgs[@]}"
)
}
@ -911,6 +919,12 @@ noneGetCli() {
## ARGUMENT POST-PROCESSING
loadLinkCache() {
if ! [ -e /etc/sanebox/symlink-cache ]; then
# don't error if the link cache is inaccessible.
# this can happen during nix builds e.g.
return
fi
# readarray -t: reads some file into an array; each line becomes one element
readarray -t _linkCacheArray < /etc/sanebox/symlink-cache
for link in "${_linkCacheArray[@]}"; do