WIP: make-sandboxed: handle /opt-style packaging, with toplevels linked into /bin, a bit better
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
buildPackages,
|
buildPackages,
|
||||||
file,
|
file,
|
||||||
gnugrep,
|
gnugrep,
|
||||||
|
gnused,
|
||||||
makeBinaryWrapper,
|
makeBinaryWrapper,
|
||||||
runCommandLocal,
|
runCommandLocal,
|
||||||
runtimeShell,
|
runtimeShell,
|
||||||
@@ -11,6 +12,7 @@
|
|||||||
symlinkJoin,
|
symlinkJoin,
|
||||||
writeShellScriptBin,
|
writeShellScriptBin,
|
||||||
writeTextFile,
|
writeTextFile,
|
||||||
|
xorg,
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
fakeSaneSandboxed = writeShellScriptBin "sanebox" ''
|
fakeSaneSandboxed = writeShellScriptBin "sanebox" ''
|
||||||
@@ -60,6 +62,7 @@ let
|
|||||||
# the ordering here is specific: inject our deps BEFORE the unwrapped program's
|
# the ordering here is specific: inject our deps BEFORE the unwrapped program's
|
||||||
# so that the unwrapped's take precendence and we limit interference (e.g. makeWrapper impl)
|
# so that the unwrapped's take precendence and we limit interference (e.g. makeWrapper impl)
|
||||||
fakeSaneSandboxed
|
fakeSaneSandboxed
|
||||||
|
gnugrep
|
||||||
makeBinaryWrapper
|
makeBinaryWrapper
|
||||||
] ++ (unwrapped.nativeBuildInputs or []);
|
] ++ (unwrapped.nativeBuildInputs or []);
|
||||||
disallowedReferences = (unwrapped.disallowedReferences or []) ++ [
|
disallowedReferences = (unwrapped.disallowedReferences or []) ++ [
|
||||||
@@ -70,8 +73,7 @@ let
|
|||||||
|
|
||||||
postFixup = (unwrapped.postFixup or "") + ''
|
postFixup = (unwrapped.postFixup or "") + ''
|
||||||
assertExecutable() {
|
assertExecutable() {
|
||||||
# my programs refer to sanebox by name, not path, which triggers an over-eager assertion in nixpkgs (so, mask that)
|
: # my programs refer to sanebox by name, not path, which triggers an over-eager assertion in nixpkgs (so, mask that)
|
||||||
:
|
|
||||||
}
|
}
|
||||||
makeDocumentedCWrapper() {
|
makeDocumentedCWrapper() {
|
||||||
# this is identical to nixpkgs' implementation, only replace execv with execvp, the latter which looks for the executable on PATH.
|
# this is identical to nixpkgs' implementation, only replace execv with execvp, the latter which looks for the executable on PATH.
|
||||||
@@ -93,26 +95,68 @@ let
|
|||||||
# if desired, makeWrapper-style naming could be achieved by leveraging `exec -a <original_name>`
|
# if desired, makeWrapper-style naming could be achieved by leveraging `exec -a <original_name>`
|
||||||
# or `make-wrapper --inherit-argv0`
|
# or `make-wrapper --inherit-argv0`
|
||||||
mkdir -p "$_dir/.sandboxed"
|
mkdir -p "$_dir/.sandboxed"
|
||||||
if [[ "$(readlink $_dir/$_name)" =~ ^\.\./ ]]; then
|
mv "$_dir/$_name" "$_dir/.sandboxed/"
|
||||||
# relative links which ascend a directory (into a non-bin/ directory)
|
|
||||||
# won't point to the right place if we naively move them
|
|
||||||
ln -s "../$(readlink $_dir/$_name)" "$_dir/.sandboxed/$_name"
|
|
||||||
rm "$_dir/$_name"
|
|
||||||
else
|
|
||||||
mv "$_dir/$_name" "$_dir/.sandboxed/"
|
|
||||||
fi
|
|
||||||
makeBinaryWrapper ${sanebox'} "$_dir/$_name" --suffix PATH : /run/current-system/sw/libexec/sanebox ${lib.escapeShellArgs (lib.flatten (builtins.map (f: [ "--add-flags" f ]) extraSandboxArgs))} --add-flags "$_dir/.sandboxed/$_name"
|
makeBinaryWrapper ${sanebox'} "$_dir/$_name" --suffix PATH : /run/current-system/sw/libexec/sanebox ${lib.escapeShellArgs (lib.flatten (builtins.map (f: [ "--add-flags" f ]) extraSandboxArgs))} --add-flags "$_dir/.sandboxed/$_name"
|
||||||
}
|
}
|
||||||
|
|
||||||
crawlAndWrap() {
|
derefWhileInSameOutput() {
|
||||||
local _dir="$1"
|
local output="$1"
|
||||||
for _p in $(ls "$_dir/"); do
|
local item="$2"
|
||||||
if [ -x "$_dir/$_p" ] && ! [ -d "$_dir/$_p" ]; then
|
if [ -L "$item" ]; then
|
||||||
sandboxWrap "$_dir" "$_p"
|
local target=$(readlink "$item")
|
||||||
elif [ -d "$_dir/$_p" ]; then
|
if [[ "$target" =~ ^"$output"/ ]]; then
|
||||||
crawlAndWrap "$_dir/$_p"
|
# absolute link back into the same package
|
||||||
|
item=$(derefWhileInSameOutput "$output" "$target")
|
||||||
|
elif [[ "$target" =~ ^/nix/store/ ]]; then
|
||||||
|
: # absolute link to another package: we're done
|
||||||
|
else
|
||||||
|
# relative link
|
||||||
|
local parent=$(dirname "$item")
|
||||||
|
target="$parent/$target"
|
||||||
|
item=$(derefWhileInSameOutput "$output" "$target")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo "$item"
|
||||||
|
}
|
||||||
|
findUnwrapped() {
|
||||||
|
if [ -L "$1" ]; then
|
||||||
|
echo "$1"
|
||||||
|
else
|
||||||
|
local dir_=$(dirname "$1")
|
||||||
|
local file_=$(basename "$1")
|
||||||
|
local sandboxed="$dir_/.sandboxed/$file_"
|
||||||
|
local unwrapped="$dir_/.''${file_}-unwrapped"
|
||||||
|
if grep -q "$sandboxed" "$1"; then
|
||||||
|
echo "/dev/null" #< already sandboxed
|
||||||
|
elif grep -q "$unwrapped" "$1"; then
|
||||||
|
echo $(findUnwrapped "$unwrapped")
|
||||||
|
else
|
||||||
|
echo "$1"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
crawlAndWrap() {
|
||||||
|
local output="$1"
|
||||||
|
local _dir="$2"
|
||||||
|
local item
|
||||||
|
for item in $(ls -a "$_dir/"); do
|
||||||
|
if [ "$item" != . ] && [ "$item" != .. ]; then
|
||||||
|
local target="$_dir/$item"
|
||||||
|
if [ -x "$target" ] && ! [ -d "$target" ]; then
|
||||||
|
# in the case of symlinks, deref until we find the real file, or the symlink points outside the package
|
||||||
|
target=$(derefWhileInSameOutput "$output" "$target")
|
||||||
|
target=$(findUnwrapped "$target")
|
||||||
|
if [ "$target" != /dev/null ]; then
|
||||||
|
local parent=$(dirname "$target")
|
||||||
|
local bin=$(basename "$target")
|
||||||
|
sandboxWrap "$parent" "$bin"
|
||||||
|
fi
|
||||||
|
elif [ -d "$_dir/$item" ]; then
|
||||||
|
crawlAndWrap "$_dir/$item"
|
||||||
|
fi
|
||||||
|
# ignore all non-binaries
|
||||||
fi
|
fi
|
||||||
# ignore all non-binaries
|
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,10 +164,10 @@ let
|
|||||||
local outdir=''${!output}
|
local outdir=''${!output}
|
||||||
echo "scanning output '$output' at $outdir for binaries to sandbox"
|
echo "scanning output '$output' at $outdir for binaries to sandbox"
|
||||||
if [ -e "$outdir/bin" ]; then
|
if [ -e "$outdir/bin" ]; then
|
||||||
crawlAndWrap "$outdir/bin"
|
crawlAndWrap "$outdir" "$outdir/bin"
|
||||||
fi
|
fi
|
||||||
if [ -e "$outdir/libexec" ]; then
|
if [ -e "$outdir/libexec" ]; then
|
||||||
crawlAndWrap "$outdir/libexec"
|
crawlAndWrap "$outdir" "$outdir/libexec"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
'';
|
'';
|
||||||
@@ -139,20 +183,57 @@ let
|
|||||||
;
|
;
|
||||||
|
|
||||||
# helper used for `wrapperType == "wrappedDerivation"` which simply symlinks all a package's binaries into a new derivation
|
# helper used for `wrapperType == "wrappedDerivation"` which simply symlinks all a package's binaries into a new derivation
|
||||||
symlinkBinaries = pkgName: package: (runCommandLocal "${pkgName}-bin-only" {} ''
|
symlinkBinaries = pkgName: package: (runCommandLocal "${pkgName}-bin-only" {
|
||||||
|
nativeBuildInputs = [ gnused ];
|
||||||
|
} ''
|
||||||
set -e
|
set -e
|
||||||
if [ -e "${package}/bin" ]; then
|
symlinkPath() {
|
||||||
mkdir -p "$out/bin"
|
if [ -e "$out/$1" ]; then
|
||||||
${buildPackages.xorg.lndir}/bin/lndir "${package}/bin" "$out/bin"
|
: # already linked. may happen when e.g. the package has bin/foo, and sbin -> bin.
|
||||||
fi
|
elif ! [ -x "${package}/$1" ]; then
|
||||||
if [ "$(readlink ${package}/sbin)" == "bin" ]; then
|
: # not a binary, nor a directory (-x) which could contain binaries
|
||||||
# weird packages like wpa_supplicant depend on a sbin/ -> bin symlink in their service files
|
elif [ -L "${package}/$1" ]; then
|
||||||
ln -s bin "$out/sbin"
|
local target=$(readlink "${package}/$1")
|
||||||
fi
|
if [[ "$target" =~ ^${package}/ ]]; then
|
||||||
if [ -e "${package}/libexec" ]; then
|
# absolute link back into the same package
|
||||||
mkdir -p "$out/libexec"
|
echo "handling $1: descending into absolute symlink to same package: $target"
|
||||||
${buildPackages.xorg.lndir}/bin/lndir "${package}/libexec" "$out/libexec"
|
target=$(echo "$target" | sed 's:${package}/::')
|
||||||
fi
|
ln -s "$out/$target" "$out/$1"
|
||||||
|
# create/link the backing path
|
||||||
|
# N.B.: if some leading component of the backing path is also a symlink... this might not work as expected.
|
||||||
|
local parent=$(dirname "$out/$target")
|
||||||
|
mkdir -p "$parent"
|
||||||
|
symlinkPath "$target"
|
||||||
|
elif [[ "$target" =~ ^/nix/store/ ]]; then
|
||||||
|
# absolute link to another package
|
||||||
|
echo "handling $1: symlinking absolute store path: $target"
|
||||||
|
ln -s "$target" "$out/$1"
|
||||||
|
else
|
||||||
|
# relative link
|
||||||
|
echo "handling $1: descending into relative symlink: $target"
|
||||||
|
ln -s "$target" "$out/$1"
|
||||||
|
local parent=$(dirname "$1")
|
||||||
|
local derefParent=$(dirname "$out/$parent/$target")
|
||||||
|
$(set -x && mkdir -p "$derefParent")
|
||||||
|
symlinkPath "$parent/$target"
|
||||||
|
fi
|
||||||
|
elif [ -d "${package}/$1" ]; then
|
||||||
|
echo "handling $1: descending into directory"
|
||||||
|
mkdir -p "$out/$1"
|
||||||
|
items=($(ls -a "${package}/$1"))
|
||||||
|
for item in "''${items[@]}"; do
|
||||||
|
if [ "$item" != . ] && [ "$item" != .. ]; then
|
||||||
|
symlinkPath "$1/$item"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
elif [ -e "${package}/$1" ]; then
|
||||||
|
echo "handling $1: symlinking ordinary file"
|
||||||
|
ln -s "${package}/$1" "$out/$1"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
symlinkPath bin
|
||||||
|
symlinkPath sbin
|
||||||
|
symlinkPath libexec
|
||||||
# allow downstream wrapping to hook this (and thereby actually wrap the binaries)
|
# allow downstream wrapping to hook this (and thereby actually wrap the binaries)
|
||||||
runHook postFixup
|
runHook postFixup
|
||||||
'').overrideAttrs (_: {
|
'').overrideAttrs (_: {
|
||||||
@@ -185,6 +266,28 @@ let
|
|||||||
mv ./substituteResult "$_outPath"
|
mv ./substituteResult "$_outPath"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# remove any files which exist in sandoxedBin (makes it possible to sandbox /opt-style packages)
|
||||||
|
# also remove any files which would be "hidden". mostly useful for /opt-style packages which contain nix-wrapped binaries.
|
||||||
|
removeUnwanted() {
|
||||||
|
local file_=$(basename "$1")
|
||||||
|
if [[ "$file_" == .* ]]; then
|
||||||
|
rm -r "$out/$1"
|
||||||
|
elif [ -f "$out/$1" ] || [ -L "$out/$1" ]; then
|
||||||
|
if [ -e "${sandboxedBin}/$1" ]; then
|
||||||
|
rm "$out/$1"
|
||||||
|
fi
|
||||||
|
elif [ -d "$out/$1" ]; then
|
||||||
|
local files=($(ls -a "$out/$1"))
|
||||||
|
for item in "''${files[@]}"; do
|
||||||
|
if [ "$item" != . ] && [ "$item" != .. ]; then
|
||||||
|
removeUnwanted "$1/$item"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
removeUnwanted ""
|
||||||
|
|
||||||
# fixup a few files i understand well enough
|
# fixup a few files i understand well enough
|
||||||
for d in \
|
for d in \
|
||||||
$out/etc/xdg/autostart/*.desktop \
|
$out/etc/xdg/autostart/*.desktop \
|
||||||
@@ -207,13 +310,15 @@ let
|
|||||||
# further, since the sandboxed binaries intentionally reference the unsandboxed binaries,
|
# further, since the sandboxed binaries intentionally reference the unsandboxed binaries,
|
||||||
# we have to patch those out as a way to whitelist them.
|
# we have to patch those out as a way to whitelist them.
|
||||||
checkSandboxed = let
|
checkSandboxed = let
|
||||||
sandboxedNonBin = fixHardcodedRefs unsandboxed "/dev/null" unsandboxedNonBin;
|
sandboxedNonBin = fixHardcodedRefs unsandboxed sandboxedBin unsandboxedNonBin;
|
||||||
in runCommandLocal "${sandboxedNonBin.name}-check-sandboxed"
|
in runCommandLocal "${sandboxedNonBin.name}-check-sandboxed"
|
||||||
{ disallowedReferences = [ unsandboxed ]; }
|
{ disallowedReferences = [ unsandboxed ]; }
|
||||||
|
# dereference every symlink, ensuring that whatever data is behind it does not reference non-sandboxed binaries.
|
||||||
|
# the dereference *can* fail, in case it's a relative symlink that refers to a part of the non-binaries we don't patch.
|
||||||
|
# in such case, this could lead to weird brokenness (e.g. no icons/images), so failing is reasonable.
|
||||||
|
# N.B.: this `checkSandboxed` protects against accidentally referencing unsandboxed binaries from data files (.deskop, .service, etc).
|
||||||
|
# there's an *additional* `checkSandboxed` further below which invokes every executable in the final package to make sure the binaries are truly sandboxed.
|
||||||
''
|
''
|
||||||
# dereference every symlink, ensuring that whatever data is behind it does not reference non-sandboxed binaries.
|
|
||||||
# the dereference *can* fail, in case it's a relative symlink that refers to a part of the non-binaries we don't patch.
|
|
||||||
# in such case, this could lead to weird brokenness (e.g. no icons/images), so failing is reasonable.
|
|
||||||
cp -R --dereference "${sandboxedNonBin}" "$out" # IF YOUR BUILD FAILS HERE, TRY SANDBOXING WITH "inplace"
|
cp -R --dereference "${sandboxedNonBin}" "$out" # IF YOUR BUILD FAILS HERE, TRY SANDBOXING WITH "inplace"
|
||||||
''
|
''
|
||||||
;
|
;
|
||||||
@@ -224,7 +329,9 @@ let
|
|||||||
# patch them to use the sandboxed binaries,
|
# patch them to use the sandboxed binaries,
|
||||||
# and add some passthru metadata to enforce no lingering references to the unsandboxed binaries.
|
# and add some passthru metadata to enforce no lingering references to the unsandboxed binaries.
|
||||||
sandboxNonBinaries = pkgName: unsandboxed: sandboxedBin: let
|
sandboxNonBinaries = pkgName: unsandboxed: sandboxedBin: let
|
||||||
sandboxedWithoutFixedRefs = (runCommandLocal "${pkgName}-sandboxed-non-binary" {} ''
|
sandboxedWithoutFixedRefs = (runCommandLocal "${pkgName}-sandboxed-non-binary" {
|
||||||
|
nativeBuildInputs = [ xorg.lndir ];
|
||||||
|
} ''
|
||||||
set -e
|
set -e
|
||||||
mkdir "$out"
|
mkdir "$out"
|
||||||
# link in a limited subset of the directories.
|
# link in a limited subset of the directories.
|
||||||
@@ -233,7 +340,7 @@ let
|
|||||||
for dir in etc share; do
|
for dir in etc share; do
|
||||||
if [ -e "${unsandboxed}/$dir" ]; then
|
if [ -e "${unsandboxed}/$dir" ]; then
|
||||||
mkdir "$out/$dir"
|
mkdir "$out/$dir"
|
||||||
${buildPackages.xorg.lndir}/bin/lndir "${unsandboxed}/$dir" "$out/$dir"
|
lndir "${unsandboxed}/$dir" "$out/$dir"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
runHook postInstall
|
runHook postInstall
|
||||||
@@ -243,7 +350,7 @@ let
|
|||||||
});
|
});
|
||||||
in fixHardcodedRefs unsandboxed sandboxedBin sandboxedWithoutFixedRefs;
|
in fixHardcodedRefs unsandboxed sandboxedBin sandboxedWithoutFixedRefs;
|
||||||
|
|
||||||
# take the nearly-final sandboxed package, with binaries and and else, and
|
# take the nearly-final sandboxed package, with binaries and all else, and
|
||||||
# populate passthru attributes the caller expects, like `checkSandboxed`.
|
# populate passthru attributes the caller expects, like `checkSandboxed`.
|
||||||
fixupMetaAndPassthru = pkgName: pkg: extraPassthru: pkg.overrideAttrs (finalAttrs: prevAttrs: let
|
fixupMetaAndPassthru = pkgName: pkg: extraPassthru: pkg.overrideAttrs (finalAttrs: prevAttrs: let
|
||||||
nonBin = (prevAttrs.passthru or {}).sandboxedNonBin or {};
|
nonBin = (prevAttrs.passthru or {}).sandboxedNonBin or {};
|
||||||
|
Reference in New Issue
Block a user