modules/programs: extend wrapperType="wrappedDerivation" to handle common share/ items
This commit is contained in:
parent
6f86e61a00
commit
7af970f38c
|
@ -256,7 +256,8 @@ let
|
|||
binaries wrap the binaries in the original derivation with a sandbox.
|
||||
|
||||
"inplace" is more reliable, but "wrappedDerivation" is more lightweight (doesn't force any rebuilds).
|
||||
the biggest gap in "wrappedDerivation" is that it doesn't handle .desktop files; just the binaries.
|
||||
the biggest gap in "wrappedDerivation" is that it doesn't link anything outside `bin/`, except for
|
||||
some limited (verified safe) support for `share/applications/*.desktop`
|
||||
"wrappedDerivation" is mostly good for prototyping.
|
||||
'';
|
||||
};
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
, runCommand
|
||||
, runtimeShell
|
||||
, sane-sandboxed
|
||||
, symlinkJoin
|
||||
, writeShellScriptBin
|
||||
, writeTextFile
|
||||
}:
|
||||
|
@ -28,15 +29,146 @@ let
|
|||
# assume that every argument after the binary name is an argument for the binary and not for the sandboxer.
|
||||
exec "$@"
|
||||
'';
|
||||
|
||||
makeHookable = pkg:
|
||||
if ((pkg.override or {}).__functionArgs or {}) ? runCommand then
|
||||
pkg.override {
|
||||
runCommand = name: env: cmd: runCommand name env (cmd + lib.optionalString (name == pkg.name) ''
|
||||
# if the package is a runCommand (common for wrappers), then patch it to call our `postFixup` hook, first
|
||||
runHook postFixup
|
||||
'');
|
||||
}
|
||||
else
|
||||
# assume the package already calls postFixup (if not, we error during system-level build)
|
||||
pkg;
|
||||
|
||||
# take an existing package, which may have a `bin/` folder as well as `share/` etc,
|
||||
# and patch the `bin/` items in-place
|
||||
sandboxBinariesInPlace = binMap: sane-sandboxed': extraSandboxArgsStr: pkgName: pkg: pkg.overrideAttrs (unwrapped: {
|
||||
# disable the sandbox and inject a minimal fake sandboxer which understands that flag,
|
||||
# in order to support packages which invoke sandboxed apps in their check phase.
|
||||
# note that it's not just for packages which invoke their *own* binaries in check phase,
|
||||
# but also packages which invoke OTHER PACKAGES' sandboxed binaries.
|
||||
# hence, put the fake sandbox in nativeBuildInputs instead of nativeCheckInputs.
|
||||
env = (unwrapped.env or {}) // {
|
||||
SANE_SANDBOX_DISABLE = 1;
|
||||
};
|
||||
nativeBuildInputs = (unwrapped.nativeBuildInputs or []) ++ [
|
||||
fakeSaneSandboxed
|
||||
];
|
||||
disallowedReferences = (unwrapped.disallowedReferences or []) ++ [
|
||||
# the fake sandbox gates itself behind SANE_SANDBOX_DISABLE, so if it did end up deployed
|
||||
# then it wouldn't permit anything not already permitted. but it would still be annoying.
|
||||
fakeSaneSandboxed
|
||||
];
|
||||
|
||||
postFixup = (unwrapped.postFixup or "") + ''
|
||||
getProfileFromBinMap() {
|
||||
case "$1" in
|
||||
${builtins.concatStringsSep "\n" (lib.mapAttrsToList
|
||||
(bin: profile: ''
|
||||
(${bin})
|
||||
echo "${profile}"
|
||||
;;
|
||||
'')
|
||||
binMap
|
||||
)}
|
||||
(*)
|
||||
;;
|
||||
esac
|
||||
}
|
||||
sandboxWrap() {
|
||||
_name="$1"
|
||||
_profileFromBinMap="$(getProfileFromBinMap $_name)"
|
||||
|
||||
_profiles=("$_profileFromBinMap" "$_name" "${pkgName}" "${unwrapped.pname or ""}" "${unwrapped.name or ""}")
|
||||
# filter to just the unique profiles
|
||||
_profileArgs=(${extraSandboxArgsStr})
|
||||
for _profile in "''${_profiles[@]}"; do
|
||||
if [ -n "$_profile" ] && ! [[ " ''${_profileArgs[@]} " =~ " $_profile " ]]; then
|
||||
_profileArgs+=("--sane-sandbox-profile" "$_profile")
|
||||
fi
|
||||
done
|
||||
|
||||
# N.B.: unlike `makeWrapper`, we place the unwrapped binary in a subdirectory and *preserve its name*.
|
||||
# the upside of this is that for applications which read "$0" to decide what to do (e.g. busybox, git)
|
||||
# they work as expected without any special hacks.
|
||||
# if desired, makeWrapper-style naming could be achieved by leveraging `exec -a <original_name>`.
|
||||
mkdir -p "$out/bin/.sandboxed"
|
||||
mv "$out/bin/$_name" "$out/bin/.sandboxed/"
|
||||
cat <<EOF >> "$out/bin/$_name"
|
||||
#!${runtimeShell}
|
||||
exec ${sane-sandboxed'} \
|
||||
''${_profileArgs[@]} \
|
||||
"$out/bin/.sandboxed/$_name" "\$@"
|
||||
EOF
|
||||
chmod +x "$out/bin/$_name"
|
||||
}
|
||||
|
||||
for _p in $(ls "$out/bin/"); do
|
||||
sandboxWrap "$_p"
|
||||
done
|
||||
'';
|
||||
});
|
||||
|
||||
# helper used for `wrapperType == "wrappedDerivation"` which simply symlinks all a package's binaries into a new derivation
|
||||
symlinkBinaries = pkgName: package: runCommand "${pkgName}-sandboxed" {} ''
|
||||
symlinkBinaries = pkgName: package: runCommand "${pkgName}-bin-only" {} ''
|
||||
mkdir -p "$out/bin"
|
||||
for d in $(ls "${package}/bin"); do
|
||||
ln -s "${package}/bin/$d" "$out/bin/$d"
|
||||
done
|
||||
# postFixup can do the actual wrapping
|
||||
# allow downstream wrapping to hook this (and thereby actually wrap the binaries)
|
||||
runHook postFixup
|
||||
'';
|
||||
|
||||
# helper used for `wrapperType == "wrappedDerivation"` which copies over the .desktop files
|
||||
# and ensures that they don't point to the unwrapped versions.
|
||||
# other important files it preserves:
|
||||
# - share/applications
|
||||
# - share/dbus-1 (frequently a source of leaked references!)
|
||||
# - share/icons
|
||||
# - share/man
|
||||
# - share/mime
|
||||
# TODO: it'd be nice to just symlink these instead, but then we couldn't leverage `disallowedReferences` like this.
|
||||
copyNonBinaries = pkgName: package: runCommand "${pkgName}-sandboxed-non-binary" {
|
||||
disallowedReferences = [ package ];
|
||||
} ''
|
||||
mkdir "$out"
|
||||
if [ -e "${package}/share" ]; then
|
||||
cp -R "${package}/share" "$out/"
|
||||
fi
|
||||
'';
|
||||
|
||||
# take the nearly-final sandboxed package, with binaries and and else, and
|
||||
# populate passthru attributes the caller expects, like `sandboxProfiles` and `checkSandboxed`.
|
||||
fixupMetaAndPassthru = pkgName: pkg: sandboxProfiles: pkg.overrideAttrs (orig: let
|
||||
final = fixupMetaAndPassthru pkgName pkg sandboxProfiles;
|
||||
in {
|
||||
meta = (orig.meta or {}) // {
|
||||
# take precedence over non-sandboxed versions of the same binary.
|
||||
priority = ((orig.meta or {}).priority or 0) - 1;
|
||||
};
|
||||
passthru = (pkg.passthru or {}) // {
|
||||
inherit sandboxProfiles;
|
||||
checkSandboxed = runCommand "${pkgName}-check-sandboxed" {} ''
|
||||
# invoke each binary in a way only the sandbox wrapper will recognize,
|
||||
# ensuring that every binary has in fact been wrapped.
|
||||
_numExec=0
|
||||
for b in ${final}/bin/*; do
|
||||
echo "checking if $b is sandboxed"
|
||||
PATH="${final}/bin:${sane-sandboxed}/bin:$PATH" \
|
||||
SANE_SANDBOX_DISABLE=1 \
|
||||
"$b" --sane-sandbox-replace-cli echo "printing for test" \
|
||||
| grep "printing for test"
|
||||
_numExec=$(( $_numExec + 1 ))
|
||||
done
|
||||
|
||||
echo "successfully tested $_numExec binaries"
|
||||
test "$_numExec" -ne 0 && touch "$out"
|
||||
'';
|
||||
};
|
||||
});
|
||||
|
||||
in
|
||||
{ pkgName, package, method, wrapperType, vpn ? null, allowedHomePaths ? [], allowedRootPaths ? [], autodetectCliPaths ? false, binMap ? {}, capabilities ? [], embedProfile ? false, embedSandboxer ? false, extraConfig ? [], whitelistPwd ? false }:
|
||||
let
|
||||
|
@ -95,112 +227,31 @@ let
|
|||
# here we switch between the options.
|
||||
# note that no.2 ("wrappedDerivation") *doesn't support .desktop files yet*.
|
||||
# the final package simply doesn't include .desktop files, only bin/.
|
||||
package' = if wrapperType == "inplace" then
|
||||
if ((package.override or {}).__functionArgs or {}) ? 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 = if wrapperType == "inplace" then
|
||||
sandboxBinariesInPlace
|
||||
binMap
|
||||
sane-sandboxed'
|
||||
maybeEmbedProfilesDir
|
||||
pkgName
|
||||
(makeHookable package)
|
||||
else if wrapperType == "wrappedDerivation" then
|
||||
symlinkBinaries pkgName package
|
||||
let
|
||||
binariesOnly = symlinkBinaries pkgName package;
|
||||
binariesWrapped = sandboxBinariesInPlace
|
||||
binMap
|
||||
sane-sandboxed'
|
||||
maybeEmbedProfilesDir
|
||||
pkgName
|
||||
binariesOnly;
|
||||
in
|
||||
symlinkJoin {
|
||||
name = "${pkgName}-sandboxed-all";
|
||||
paths = [
|
||||
binariesWrapped
|
||||
(copyNonBinaries pkgName package)
|
||||
];
|
||||
}
|
||||
else
|
||||
builtins.throw "unknown wrapperType: ${wrapperType}";
|
||||
|
||||
packageWrapped = package'.overrideAttrs (unwrapped: {
|
||||
# disable the sandbox and inject a minimal fake sandboxer which understands that flag,
|
||||
# in order to support packages which invoke sandboxed apps in their check phase.
|
||||
# note that it's not just for packages which invoke their *own* binaries in check phase,
|
||||
# but also packages which invoke OTHER PACKAGES' sandboxed binaries.
|
||||
# hence, put the fake sandbox in nativeBuildInputs instead of nativeCheckInputs.
|
||||
env = (unwrapped.env or {}) // {
|
||||
SANE_SANDBOX_DISABLE = 1;
|
||||
};
|
||||
nativeBuildInputs = (unwrapped.nativeBuildInputs or []) ++ [
|
||||
fakeSaneSandboxed
|
||||
];
|
||||
disallowedReferences = (unwrapped.disallowedReferences or []) ++ [
|
||||
# the fake sandbox gates itself behind SANE_SANDBOX_DISABLE, so if it did end up deployed
|
||||
# then it wouldn't permit anything not already permitted. but it would still be annoying.
|
||||
fakeSaneSandboxed
|
||||
];
|
||||
|
||||
postFixup = (unwrapped.postFixup or "") + ''
|
||||
getProfileFromBinMap() {
|
||||
case "$1" in
|
||||
${builtins.concatStringsSep "\n" (lib.mapAttrsToList
|
||||
(bin: profile: ''
|
||||
(${bin})
|
||||
echo "${profile}"
|
||||
;;
|
||||
'')
|
||||
binMap
|
||||
)}
|
||||
(*)
|
||||
;;
|
||||
esac
|
||||
}
|
||||
sandboxWrap() {
|
||||
_name="$1"
|
||||
_profileFromBinMap="$(getProfileFromBinMap $_name)"
|
||||
|
||||
_profiles=("$_profileFromBinMap" "$_name" "${pkgName}" "${unwrapped.pname or ""}" "${unwrapped.name or ""}")
|
||||
# filter to just the unique profiles
|
||||
_profileArgs=(${maybeEmbedProfilesDir})
|
||||
for _profile in "''${_profiles[@]}"; do
|
||||
if [ -n "$_profile" ] && ! [[ " ''${_profileArgs[@]} " =~ " $_profile " ]]; then
|
||||
_profileArgs+=("--sane-sandbox-profile" "$_profile")
|
||||
fi
|
||||
done
|
||||
|
||||
# N.B.: unlike `makeWrapper`, we place the unwrapped binary in a subdirectory and *preserve its name*.
|
||||
# the upside of this is that for applications which read "$0" to decide what to do (e.g. busybox, git)
|
||||
# they work as expected without any special hacks.
|
||||
# if desired, makeWrapper-style naming could be achieved by leveraging `exec -a <original_name>`.
|
||||
mkdir -p "$out/bin/.sandboxed"
|
||||
mv "$out/bin/$_name" "$out/bin/.sandboxed/"
|
||||
cat <<EOF >> "$out/bin/$_name"
|
||||
#!${runtimeShell}
|
||||
exec ${sane-sandboxed'} \
|
||||
''${_profileArgs[@]} \
|
||||
"$out/bin/.sandboxed/$_name" "\$@"
|
||||
EOF
|
||||
chmod +x "$out/bin/$_name"
|
||||
}
|
||||
|
||||
for _p in $(ls "$out/bin/"); do
|
||||
sandboxWrap "$_p"
|
||||
done
|
||||
'';
|
||||
|
||||
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 "${pkgName}-check-sandboxed" {} ''
|
||||
# invoke each binary in a way only the sandbox wrapper will recognize,
|
||||
# ensuring that every binary has in fact been wrapped.
|
||||
_numExec=0
|
||||
for b in ${packageWrapped}/bin/*; do
|
||||
echo "checking if $b is sandboxed"
|
||||
PATH="${packageWrapped}/bin:${sane-sandboxed}/bin:$PATH" \
|
||||
SANE_SANDBOX_DISABLE=1 \
|
||||
"$b" --sane-sandbox-replace-cli echo "printing for test" \
|
||||
| grep "printing for test"
|
||||
_numExec=$(( $_numExec + 1 ))
|
||||
done
|
||||
|
||||
echo "successfully tested $_numExec binaries"
|
||||
test "$_numExec" -ne 0 && touch "$out"
|
||||
'';
|
||||
|
||||
sandboxProfiles = sandboxProfilesPkg;
|
||||
};
|
||||
});
|
||||
in
|
||||
packageWrapped
|
||||
fixupMetaAndPassthru pkgName packageWrapped sandboxProfilesPkg
|
||||
|
|
Loading…
Reference in New Issue
Block a user