2023-04-09 19:50:02 +02:00
|
|
|
{ lib
|
2023-04-22 14:31:27 +02:00
|
|
|
, stdenv
|
2023-04-09 19:50:02 +02:00
|
|
|
, callPackage
|
|
|
|
, runCommandLocal
|
|
|
|
, writeShellScript
|
|
|
|
, glibc
|
|
|
|
, pkgsi686Linux
|
|
|
|
, coreutils
|
|
|
|
, bubblewrap
|
|
|
|
}:
|
2020-10-30 19:22:04 +01:00
|
|
|
|
2023-04-09 19:50:02 +02:00
|
|
|
{ name ? null
|
|
|
|
, pname ? null
|
2023-03-02 02:07:06 +01:00
|
|
|
, version ? null
|
2020-12-09 03:20:44 +01:00
|
|
|
, runScript ? "bash"
|
|
|
|
, extraInstallCommands ? ""
|
|
|
|
, meta ? {}
|
|
|
|
, passthru ? {}
|
2021-12-29 21:30:44 +01:00
|
|
|
, extraBwrapArgs ? []
|
buildFHSEnv: disable security features by default
The implicit contract of buildFHSUserEnv was that it allows to run
software built for a typical GNU/Linux distribution (not NixOS) without
patching it (patchelf, autoPatchelfHook, etc.). Note that this does not
inherently imply running untrusted programs.
buildFHSUserEnv was implemented by using chroot and assembling a
standard-compliant FHS environment in the new root. As expected, this
did not provide any kind of isolation between the system and the
programs.
However, when it was later reimplemented using bubblewrap
(PR #225748), which *is* a security tool, several isolation features
involving detaches Linux namespaces were turned on by default.
This decision has introduced a number of breakages that are very
difficult to debug and trace back to this change.
For example: `unshareIPC` breaks software audio mixing in programs using
ALSA (dmix) and `unsharePID` breaks gdb,
Since:
1. the security features were enable without any clear threat model;
2. `buildFHSEnvBubblewrap` is supposed to be a drop-in replacement of
`buildFHSEnvChrootenv` (see the release notes for NixOS 23.05);
3. the change is breaking in several common cases (security does not
come for free);
4. the contract was not changed, or at least communicated in a clear
way to the users;
all security features should be turned off by default.
P.S. It would be useful to create a variant of buildFHSEnv that does
provide some isolation. This could unshare some namespaces and mount
only limited parts of the filesystem.
Note that buildFHSEnv mounts every directory in / under the new root, so
again, very little is gained by unsharing alone.
2023-09-08 08:58:31 +02:00
|
|
|
, unshareUser ? false
|
|
|
|
, unshareIpc ? false
|
|
|
|
, unsharePid ? false
|
2020-12-09 03:20:44 +01:00
|
|
|
, unshareNet ? false
|
buildFHSEnv: disable security features by default
The implicit contract of buildFHSUserEnv was that it allows to run
software built for a typical GNU/Linux distribution (not NixOS) without
patching it (patchelf, autoPatchelfHook, etc.). Note that this does not
inherently imply running untrusted programs.
buildFHSUserEnv was implemented by using chroot and assembling a
standard-compliant FHS environment in the new root. As expected, this
did not provide any kind of isolation between the system and the
programs.
However, when it was later reimplemented using bubblewrap
(PR #225748), which *is* a security tool, several isolation features
involving detaches Linux namespaces were turned on by default.
This decision has introduced a number of breakages that are very
difficult to debug and trace back to this change.
For example: `unshareIPC` breaks software audio mixing in programs using
ALSA (dmix) and `unsharePID` breaks gdb,
Since:
1. the security features were enable without any clear threat model;
2. `buildFHSEnvBubblewrap` is supposed to be a drop-in replacement of
`buildFHSEnvChrootenv` (see the release notes for NixOS 23.05);
3. the change is breaking in several common cases (security does not
come for free);
4. the contract was not changed, or at least communicated in a clear
way to the users;
all security features should be turned off by default.
P.S. It would be useful to create a variant of buildFHSEnv that does
provide some isolation. This could unshare some namespaces and mount
only limited parts of the filesystem.
Note that buildFHSEnv mounts every directory in / under the new root, so
again, very little is gained by unsharing alone.
2023-09-08 08:58:31 +02:00
|
|
|
, unshareUts ? false
|
|
|
|
, unshareCgroup ? false
|
2021-04-08 07:16:30 +02:00
|
|
|
, dieWithParent ? true
|
2020-12-09 03:20:44 +01:00
|
|
|
, ...
|
2023-04-09 19:50:02 +02:00
|
|
|
} @ args:
|
|
|
|
|
|
|
|
assert (pname != null || version != null) -> (name == null && pname != null); # You must declare either a name or pname + version (preferred).
|
2020-07-31 14:58:03 +02:00
|
|
|
|
2019-02-20 16:22:17 +01:00
|
|
|
with builtins;
|
2020-07-31 14:58:03 +02:00
|
|
|
let
|
2023-09-24 19:37:44 +02:00
|
|
|
pname = if args ? name && args.name != null then args.name else args.pname;
|
2023-04-09 19:50:02 +02:00
|
|
|
versionStr = lib.optionalString (version != null) ("-" + version);
|
|
|
|
name = pname + versionStr;
|
|
|
|
|
2023-03-07 20:35:30 +01:00
|
|
|
buildFHSEnv = callPackage ./buildFHSEnv.nix { };
|
2020-12-09 03:20:44 +01:00
|
|
|
|
2023-04-09 19:50:02 +02:00
|
|
|
fhsenv = buildFHSEnv (removeAttrs (args // { inherit name; }) [
|
2021-12-29 21:30:44 +01:00
|
|
|
"runScript" "extraInstallCommands" "meta" "passthru" "extraBwrapArgs" "dieWithParent"
|
2020-12-09 03:20:44 +01:00
|
|
|
"unshareUser" "unshareCgroup" "unshareUts" "unshareNet" "unsharePid" "unshareIpc"
|
2023-04-09 19:50:02 +02:00
|
|
|
"pname" "version"
|
2019-02-20 16:22:17 +01:00
|
|
|
]);
|
2020-07-31 14:58:03 +02:00
|
|
|
|
2023-03-17 18:00:03 +01:00
|
|
|
etcBindEntries = let
|
2019-09-15 15:29:53 +02:00
|
|
|
files = [
|
|
|
|
# NixOS Compatibility
|
|
|
|
"static"
|
2021-04-08 07:16:30 +02:00
|
|
|
"nix" # mainly for nixUnstable users, but also for access to nix/netrc
|
|
|
|
# Shells
|
2022-10-06 13:53:05 +02:00
|
|
|
"shells"
|
2021-04-08 07:16:30 +02:00
|
|
|
"bashrc"
|
|
|
|
"zshenv"
|
|
|
|
"zshrc"
|
|
|
|
"zinputrc"
|
|
|
|
"zprofile"
|
2019-09-15 15:29:53 +02:00
|
|
|
# Users, Groups, NSS
|
|
|
|
"passwd"
|
|
|
|
"group"
|
|
|
|
"shadow"
|
|
|
|
"hosts"
|
|
|
|
"resolv.conf"
|
|
|
|
"nsswitch.conf"
|
2021-01-26 01:41:50 +01:00
|
|
|
# User profiles
|
|
|
|
"profiles"
|
2019-09-15 15:29:53 +02:00
|
|
|
# Sudo & Su
|
|
|
|
"login.defs"
|
|
|
|
"sudoers"
|
|
|
|
"sudoers.d"
|
|
|
|
# Time
|
|
|
|
"localtime"
|
|
|
|
"zoneinfo"
|
|
|
|
# Other Core Stuff
|
|
|
|
"machine-id"
|
|
|
|
"os-release"
|
|
|
|
# PAM
|
|
|
|
"pam.d"
|
|
|
|
# Fonts
|
|
|
|
"fonts"
|
|
|
|
# ALSA
|
2021-07-10 12:50:52 +02:00
|
|
|
"alsa"
|
2019-09-15 15:29:53 +02:00
|
|
|
"asound.conf"
|
|
|
|
# SSL
|
|
|
|
"ssl/certs"
|
2021-10-27 18:55:29 +02:00
|
|
|
"ca-certificates"
|
2019-09-15 15:29:53 +02:00
|
|
|
"pki"
|
|
|
|
];
|
2023-03-17 18:00:03 +01:00
|
|
|
in map (path: "/etc/${path}") files;
|
2019-09-15 15:29:53 +02:00
|
|
|
|
2020-10-30 19:22:04 +01:00
|
|
|
# Create this on the fly instead of linking from /nix
|
|
|
|
# The container might have to modify it and re-run ldconfig if there are
|
|
|
|
# issues running some binary with LD_LIBRARY_PATH
|
|
|
|
createLdConfCache = ''
|
|
|
|
cat > /etc/ld.so.conf <<EOF
|
|
|
|
/lib
|
|
|
|
/lib/x86_64-linux-gnu
|
|
|
|
/lib64
|
|
|
|
/usr/lib
|
|
|
|
/usr/lib/x86_64-linux-gnu
|
|
|
|
/usr/lib64
|
|
|
|
/lib/i386-linux-gnu
|
|
|
|
/lib32
|
|
|
|
/usr/lib/i386-linux-gnu
|
|
|
|
/usr/lib32
|
2022-08-02 17:05:33 +02:00
|
|
|
/run/opengl-driver/lib
|
|
|
|
/run/opengl-driver-32/lib
|
2020-10-30 19:22:04 +01:00
|
|
|
EOF
|
|
|
|
ldconfig &> /dev/null
|
|
|
|
'';
|
2023-04-09 19:50:02 +02:00
|
|
|
init = run: writeShellScript "${name}-init" ''
|
2019-02-20 16:22:17 +01:00
|
|
|
source /etc/profile
|
2020-10-30 19:22:04 +01:00
|
|
|
${createLdConfCache}
|
2023-05-21 12:43:19 +02:00
|
|
|
exec ${run} "$@"
|
2019-02-20 16:22:17 +01:00
|
|
|
'';
|
|
|
|
|
2023-04-22 14:31:27 +02:00
|
|
|
indentLines = str: lib.concatLines (map (s: " " + s) (filter (s: s != "") (lib.splitString "\n" str)));
|
2020-08-17 09:49:34 +02:00
|
|
|
bwrapCmd = { initArgs ? "" }: ''
|
2023-03-17 18:00:14 +01:00
|
|
|
ignored=(/nix /dev /proc /etc)
|
2020-09-20 17:02:08 +02:00
|
|
|
ro_mounts=()
|
2020-10-24 17:23:37 +02:00
|
|
|
symlinks=()
|
2023-03-17 18:00:03 +01:00
|
|
|
etc_ignored=()
|
2023-03-07 20:35:30 +01:00
|
|
|
for i in ${fhsenv}/*; do
|
2020-07-31 14:58:03 +02:00
|
|
|
path="/''${i##*/}"
|
2019-09-15 15:29:53 +02:00
|
|
|
if [[ $path == '/etc' ]]; then
|
2020-10-24 17:23:37 +02:00
|
|
|
:
|
|
|
|
elif [[ -L $i ]]; then
|
2021-03-11 14:27:14 +01:00
|
|
|
symlinks+=(--symlink "$(${coreutils}/bin/readlink "$i")" "$path")
|
2023-03-17 18:00:14 +01:00
|
|
|
ignored+=("$path")
|
2020-10-24 17:23:37 +02:00
|
|
|
else
|
|
|
|
ro_mounts+=(--ro-bind "$i" "$path")
|
2023-03-17 18:00:14 +01:00
|
|
|
ignored+=("$path")
|
2019-09-15 15:29:53 +02:00
|
|
|
fi
|
2020-07-31 14:58:03 +02:00
|
|
|
done
|
|
|
|
|
2023-03-07 20:35:30 +01:00
|
|
|
if [[ -d ${fhsenv}/etc ]]; then
|
|
|
|
for i in ${fhsenv}/etc/*; do
|
2019-09-15 15:29:53 +02:00
|
|
|
path="/''${i##*/}"
|
2021-02-22 20:54:04 +01:00
|
|
|
# NOTE: we're binding /etc/fonts and /etc/ssl/certs from the host so we
|
|
|
|
# don't want to override it with a path from the FHS environment.
|
|
|
|
if [[ $path == '/fonts' || $path == '/ssl' ]]; then
|
2021-01-26 01:42:32 +01:00
|
|
|
continue
|
|
|
|
fi
|
2020-09-20 17:02:08 +02:00
|
|
|
ro_mounts+=(--ro-bind "$i" "/etc$path")
|
2023-03-17 18:00:03 +01:00
|
|
|
etc_ignored+=("/etc$path")
|
2019-09-15 15:29:53 +02:00
|
|
|
done
|
|
|
|
fi
|
|
|
|
|
2023-03-17 18:00:03 +01:00
|
|
|
for i in ${lib.escapeShellArgs etcBindEntries}; do
|
|
|
|
if [[ "''${etc_ignored[@]}" =~ "$i" ]]; then
|
|
|
|
continue
|
|
|
|
fi
|
|
|
|
if [[ -L $i ]]; then
|
|
|
|
symlinks+=(--symlink "$(${coreutils}/bin/readlink "$i")" "$i")
|
|
|
|
else
|
|
|
|
ro_mounts+=(--ro-bind-try "$i" "$i")
|
|
|
|
fi
|
|
|
|
done
|
|
|
|
|
2020-10-28 21:00:17 +01:00
|
|
|
declare -a auto_mounts
|
2019-02-20 16:22:17 +01:00
|
|
|
# loop through all directories in the root
|
|
|
|
for dir in /*; do
|
2023-03-17 18:00:14 +01:00
|
|
|
# if it is a directory and it is not ignored
|
|
|
|
if [[ -d "$dir" ]] && [[ ! "''${ignored[@]}" =~ "$dir" ]]; then
|
2019-02-20 16:22:17 +01:00
|
|
|
# add it to the mount list
|
2020-10-28 21:00:17 +01:00
|
|
|
auto_mounts+=(--bind "$dir" "$dir")
|
2019-02-20 16:22:17 +01:00
|
|
|
fi
|
|
|
|
done
|
2020-07-31 14:58:03 +02:00
|
|
|
|
2023-02-11 15:30:18 +01:00
|
|
|
declare -a x11_args
|
|
|
|
# Always mount a tmpfs on /tmp/.X11-unix
|
|
|
|
# Rationale: https://github.com/flatpak/flatpak/blob/be2de97e862e5ca223da40a895e54e7bf24dbfb9/common/flatpak-run.c#L277
|
|
|
|
x11_args+=(--tmpfs /tmp/.X11-unix)
|
|
|
|
|
|
|
|
# Try to guess X socket path. This doesn't cover _everything_, but it covers some things.
|
|
|
|
if [[ "$DISPLAY" == :* ]]; then
|
|
|
|
display_nr=''${DISPLAY#?}
|
|
|
|
local_socket=/tmp/.X11-unix/X$display_nr
|
|
|
|
x11_args+=(--ro-bind-try "$local_socket" "$local_socket")
|
|
|
|
fi
|
|
|
|
|
2020-09-20 17:02:08 +02:00
|
|
|
cmd=(
|
|
|
|
${bubblewrap}/bin/bwrap
|
|
|
|
--dev-bind /dev /dev
|
|
|
|
--proc /proc
|
|
|
|
--chdir "$(pwd)"
|
2020-12-09 03:20:44 +01:00
|
|
|
${lib.optionalString unshareUser "--unshare-user"}
|
|
|
|
${lib.optionalString unshareIpc "--unshare-ipc"}
|
|
|
|
${lib.optionalString unsharePid "--unshare-pid"}
|
|
|
|
${lib.optionalString unshareNet "--unshare-net"}
|
|
|
|
${lib.optionalString unshareUts "--unshare-uts"}
|
|
|
|
${lib.optionalString unshareCgroup "--unshare-cgroup"}
|
2021-04-08 07:16:30 +02:00
|
|
|
${lib.optionalString dieWithParent "--die-with-parent"}
|
2020-09-20 17:02:08 +02:00
|
|
|
--ro-bind /nix /nix
|
2021-02-23 15:44:16 +01:00
|
|
|
# Our glibc will look for the cache in its own path in `/nix/store`.
|
|
|
|
# As such, we need a cache to exist there, because pressure-vessel
|
|
|
|
# depends on the existence of an ld cache. However, adding one
|
|
|
|
# globally proved to be a bad idea (see #100655), the solution we
|
|
|
|
# settled on being mounting one via bwrap.
|
|
|
|
# Also, the cache needs to go to both 32 and 64 bit glibcs, for games
|
|
|
|
# of both architectures to work.
|
2020-10-30 19:22:04 +01:00
|
|
|
--tmpfs ${glibc}/etc \
|
|
|
|
--symlink /etc/ld.so.conf ${glibc}/etc/ld.so.conf \
|
|
|
|
--symlink /etc/ld.so.cache ${glibc}/etc/ld.so.cache \
|
|
|
|
--ro-bind ${glibc}/etc/rpc ${glibc}/etc/rpc \
|
|
|
|
--remount-ro ${glibc}/etc \
|
2023-04-22 14:31:27 +02:00
|
|
|
'' + lib.optionalString (stdenv.isx86_64 && stdenv.isLinux) (indentLines ''
|
2021-02-04 02:16:20 +01:00
|
|
|
--tmpfs ${pkgsi686Linux.glibc}/etc \
|
|
|
|
--symlink /etc/ld.so.conf ${pkgsi686Linux.glibc}/etc/ld.so.conf \
|
|
|
|
--symlink /etc/ld.so.cache ${pkgsi686Linux.glibc}/etc/ld.so.cache \
|
|
|
|
--ro-bind ${pkgsi686Linux.glibc}/etc/rpc ${pkgsi686Linux.glibc}/etc/rpc \
|
|
|
|
--remount-ro ${pkgsi686Linux.glibc}/etc \
|
2023-04-22 14:31:27 +02:00
|
|
|
'') + ''
|
2020-09-20 17:02:08 +02:00
|
|
|
"''${ro_mounts[@]}"
|
2020-10-24 17:23:37 +02:00
|
|
|
"''${symlinks[@]}"
|
2020-09-20 17:02:08 +02:00
|
|
|
"''${auto_mounts[@]}"
|
2023-02-11 15:30:18 +01:00
|
|
|
"''${x11_args[@]}"
|
2021-12-29 21:30:44 +01:00
|
|
|
${concatStringsSep "\n " extraBwrapArgs}
|
2023-04-09 19:50:02 +02:00
|
|
|
${init runScript} ${initArgs}
|
2020-09-20 17:02:08 +02:00
|
|
|
)
|
|
|
|
exec "''${cmd[@]}"
|
2020-07-31 14:58:03 +02:00
|
|
|
'';
|
|
|
|
|
2023-04-09 19:50:02 +02:00
|
|
|
bin = writeShellScript "${name}-bwrap" (bwrapCmd { initArgs = ''"$@"''; });
|
|
|
|
in runCommandLocal name {
|
2020-07-31 14:58:03 +02:00
|
|
|
inherit meta;
|
|
|
|
|
|
|
|
passthru = passthru // {
|
|
|
|
env = runCommandLocal "${name}-shell-env" {
|
2020-08-17 09:49:34 +02:00
|
|
|
shellHook = bwrapCmd {};
|
2020-07-31 14:58:03 +02:00
|
|
|
} ''
|
|
|
|
echo >&2 ""
|
|
|
|
echo >&2 "*** User chroot 'env' attributes are intended for interactive nix-shell sessions, not for building! ***"
|
|
|
|
echo >&2 ""
|
|
|
|
exit 1
|
|
|
|
'';
|
2023-03-07 20:35:30 +01:00
|
|
|
inherit args fhsenv;
|
2020-07-31 14:58:03 +02:00
|
|
|
};
|
|
|
|
} ''
|
|
|
|
mkdir -p $out/bin
|
2023-04-09 19:50:02 +02:00
|
|
|
ln -s ${bin} $out/bin/${pname}
|
|
|
|
|
2020-07-31 14:58:03 +02:00
|
|
|
${extraInstallCommands}
|
|
|
|
''
|