modules/programs: make-sandboxed: avoid deep-copying all of /share when sandboxing

saves like 1 GiB of closure. but i haven't thoroughly tested this
This commit is contained in:
Colin 2024-02-06 05:01:30 +00:00
parent 5ff7bf0c69
commit d7612d5034

View File

@ -1,4 +1,5 @@
{ lib
, buildPackages
, runCommand
, runtimeShell
, sane-sandboxed
@ -128,65 +129,80 @@ let
runHook postFixup
'';
# helper used for `wrapperType == "wrappedDerivation"` which copies over the .desktop files
# and ensures that they don't point to the unwrapped versions.
# helper used for `wrapperType == "wrappedDerivation"` which ensures that and copied/symlinked share/ files (like .desktop) files
# don't point to the unwrapped binaries.
# 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: sandboxedBins: runCommand "${pkgName}-sandboxed-non-binary" {
disallowedReferences = [ package ];
# users can build this one when they get a disallowed references failure
passthru.unchecked = (copyNonBinaries pkgName package sandboxedBins).overrideAttrs (_: {
disallowedReferences = [];
});
} ''
trySubstitute() {
_outPath="$1"
_pattern="$2"
_from=$(printf "$_pattern" "${package}")
_to=$(printf "$_pattern" "${sandboxedBins}")
printf "applying known substitutions to %s" "$_outPath"
# substituteInPlace can fail on symlinks, but frequently that's fine because
# the referenced file is already safe, so don't error on failure here.
substituteInPlace "$_outPath" \
--replace "$_from" "$_to" \
|| true
}
mkdir "$out"
if [ -e "${package}/share" ]; then
cp -R "${package}/share" "$out/"
fi
# fixup a few files i understand well enough
for d in $out/share/applications/*.desktop; do
trySubstitute "$d" "Exec=%s/bin/"
done
for d in $out/share/dbus-1/services/*.service; do
trySubstitute "$d" "Exec=%s/bin/"
done
'';
fixHardcodedRefs = unsandboxed: sandboxedNonBin: sandboxedBin: sandboxedNonBin.overrideAttrs (prevAttrs: {
postInstall = (prevAttrs.postInstall or "") + ''
trySubstitute() {
_outPath="$1"
_pattern="$2"
_from=$(printf "$_pattern" "${unsandboxed}")
_to=$(printf "$_pattern" "${sandboxedBin}")
printf "applying known substitutions to %s" "$_outPath"
# substituteInPlace can fail on symlinks, but frequently that's fine because
# the referenced file is already safe, so don't error on failure here.
substituteInPlace "$_outPath" \
--replace "$_from" "$_to" \
|| true
}
# fixup a few files i understand well enough
for d in $out/share/applications/*.desktop; do
trySubstitute "$d" "Exec=%s/bin/"
done
for d in $out/share/dbus-1/services/*.service; do
trySubstitute "$d" "Exec=%s/bin/"
done
'';
});
# copy or symlink the non-binary files from the unsandboxed package,
# patch them to use the sandboxed binaries,
# and add some passthru metadata to enforce no lingering references to the unsandboxed binaries.
sandboxNonBinaries = method: pkgName: unsandboxed: sandboxedBin: let
unsandboxedNonBinaries = runCommand "${pkgName}-sandboxed-non-binary" {
passthru.checkSandboxed = (copyNonBinaries pkgName unsandboxed sandboxedBin).overrideAttrs (_: {
# copy everything, patch just as we would for any method, and then enforce no refs to the unsandboxed pkg.
# we can do that only because we're copying instead of symlinking.
# TODO: instead of `passthru.checkSandboxed`, just make this a build prereq of the out derivation,
# such that it fails the main derivation if it would contain bad refs
disallowedReferences = [ unsandboxed ];
});
} ''
mkdir "$out"
if [ -e "${unsandboxed}/share" ]; then
${method} "${unsandboxed}/share" "$out/"
fi
runHook postInstall
'';
in fixHardcodedRefs unsandboxed unsandboxedNonBinaries sandboxedBin;
copyNonBinaries = sandboxNonBinaries "cp -R";
symlinkNonBinaries = sandboxNonBinaries "${buildPackages.xorg.lndir}/bin/lndir";
# 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: extraPassthru: pkg.overrideAttrs (orig: let
fixupMetaAndPassthru = pkgName: pkg: sandboxProfiles: extraPassthru: pkg.overrideAttrs (finalAttrs: prevAttrs: let
final = fixupMetaAndPassthru pkgName pkg sandboxProfiles extraPassthru;
in {
meta = (orig.meta or {}) // {
meta = (prevAttrs.meta or {}) // {
# take precedence over non-sandboxed versions of the same binary.
priority = ((orig.meta or {}).priority or 0) - 1;
priority = ((prevAttrs.meta or {}).priority or 0) - 1;
};
passthru = (pkg.passthru or {}) // extraPassthru // {
passthru = (prevAttrs.passthru or {}) // extraPassthru // {
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
for b in ${finalAttrs.finalPackage}/bin/*; do
echo "checking if $b is sandboxed"
PATH="${final}/bin:${sane-sandboxed}/bin:$PATH" \
PATH="${finalAttrs.finalPackage}/bin:${sane-sandboxed}/bin:$PATH" \
SANE_SANDBOX_DISABLE=1 \
"$b" --sane-sandbox-replace-cli echo "printing for test" \
| grep "printing for test"
@ -194,6 +210,10 @@ let
done
echo "successfully tested $_numExec binaries"
# forward prevAttrs checkSandboxed, to guarantee correctness for the /share directory (`sandboxNonBinaries`).
${lib.optionalString (prevAttrs ? passthru && prevAttrs.passthru ? checkSandboxed)
''echo "also checked: ${prevAttrs.passthru.checkSandboxed}"''
}
test "$_numExec" -ne 0 && touch "$out"
'';
};
@ -270,7 +290,7 @@ let
maybeEmbedProfilesDir
pkgName
(symlinkBinaries pkgName package);
nonBinaries = copyNonBinaries pkgName package binaries;
nonBinaries = symlinkNonBinaries pkgName package binaries;
in symlinkJoin {
name = "${pkgName}-sandboxed-all";
paths = [ binaries nonBinaries ];