buildFHSEnv: refactor and simplify, drop buildFHSChrootEnv

This takes another approach at binding FHS directory structure. We
now bind-mount all the root filesystem to directory "/host" in the target tree.
From that we symlink all the directories into the tree if they do not already
exist in FHS structure.

This probably makes `CHROOTENV_EXTRA_BINDS` unnecessary -- its main usecase was
to add bound directories from the host to the sandbox, and we not just symlink
all of them. I plan to get some feedback on its usage and maybe deprecate it.

This also drops old `buildFHSChrootEnv` infrastructure. The main problem with it
is it's very difficult to unmount a recursive-bound directory when mount is not
sandboxed. This problem is a bug even without these changes -- if
you have for example `/home/alice` mounted to somewhere, you wouldn't see
it in `buildFHSChrootEnv` now. With the new directory structure, it's
impossible to use regular bind at all. After some tackling with this I realized
that the fix would be brittle and dangerous (if you don't unmount everything
clearly and proceed to removing the temporary directory, bye-bye fs!). It also
probably doesn't worth it because I haven't heard that someone actually uses it
for a long time, and `buildFHSUserEnv` should cover most cases while being much
more maintainable and safe for the end-user.
This commit is contained in:
Nikolay Amiantov 2016-06-05 04:44:06 +03:00
parent 38ba568634
commit 74107a7867
10 changed files with 58 additions and 253 deletions

View File

@ -1,48 +0,0 @@
{ stdenv } : { env, extraInstallCommands ? "" } :
let
# References to shell scripts that set up or tear down the environment
initSh = ./init.sh.in;
mountSh = ./mount.sh.in;
loadSh = ./load.sh.in;
umountSh = ./umount.sh.in;
destroySh = ./destroy.sh.in;
name = env.pname;
in stdenv.mkDerivation {
name = "${name}-chrootenv";
preferLocalBuild = true;
buildCommand = ''
mkdir -p $out/bin
cd $out/bin
sed -e "s|@chrootEnv@|${env}|g" \
-e "s|@name@|${name}|g" \
-e "s|@shell@|${stdenv.shell}|g" \
${initSh} > init-${name}-chrootenv
chmod +x init-${name}-chrootenv
sed -e "s|@shell@|${stdenv.shell}|g" \
-e "s|@name@|${name}|g" \
${mountSh} > mount-${name}-chrootenv
chmod +x mount-${name}-chrootenv
sed -e "s|@shell@|${stdenv.shell}|g" \
-e "s|@name@|${name}|g" \
${loadSh} > load-${name}-chrootenv
chmod +x load-${name}-chrootenv
sed -e "s|@shell@|${stdenv.shell}|g" \
-e "s|@name@|${name}|g" \
${umountSh} > umount-${name}-chrootenv
chmod +x umount-${name}-chrootenv
sed -e "s|@chrootEnv@|${env}|g" \
-e "s|@shell@|${stdenv.shell}|g" \
-e "s|@name@|${name}|g" \
${destroySh} > destroy-${name}-chrootenv
chmod +x destroy-${name}-chrootenv
${extraInstallCommands}
'';
}

View File

@ -1,22 +0,0 @@
#! @shell@ -e
chrootenvDest=/run/chrootenv/@name@
# Remove bind mount points
rmdir $chrootenvDest/{dev,nix/store,nix,proc,sys,host-etc,home,var,run,tmp}
# Remove symlinks to the software that should be part of the chroot system profile
for i in @chrootEnv@/*
do
if [ "$i" != "@chrootEnv@/etc" ] && [ "$i" != "@chrootEnv@/var" ]
then
rm $chrootenvDest/$(basename $i)
fi
done
# Remove the remaining folders
rm -Rf $chrootenvDest/{etc,root}
rm -Rf /tmp/chrootenv-@name@
# Remove the chroot environment folder
rmdir $chrootenvDest

View File

@ -1,22 +0,0 @@
#! @shell@ -e
chrootenvDest=/run/chrootenv/@name@
# Create some mount points for stuff that must be bind mounted
mkdir -p $chrootenvDest/{nix/store,dev,proc,sys,host-etc,host-tmp,home,var,run}
# Symlink the software that should be part of the chroot system profile
for i in @chrootEnv@/*
do
if [ "$i" != "@chrootEnv@/var" ]
then
ln -s "$i" "$chrootenvDest"
fi
done
# Create root folder
mkdir $chrootenvDest/root
# Create tmp folder
mkdir -m1777 $chrootenvDest/tmp
mkdir -m1777 -p /tmp/chrootenv-@name@

View File

@ -1,13 +0,0 @@
#! @shell@ -e
chrootenvDest=/run/chrootenv/@name@
# Enter the LFS chroot environment
sudo chroot --userspec "$USER:${GROUPS[0]}" --groups "${GROUPS[0]}" $chrootenvDest /usr/bin/env -i \
TERM="$TERM" \
DISPLAY="$DISPLAY" \
HOME="$HOME" \
XDG_RUNTIME_DIR="$XDG_RUNTIME_DIR" \
LANG="$LANG" \
SSL_CERT_FILE="$SSL_CERT_FILE" \
/bin/bash --login

View File

@ -1,34 +0,0 @@
#! @shell@ -e
chrootenvDest=/run/chrootenv/@name@
# Bind mount the Nix store
mount --bind /nix/store $chrootenvDest/nix/store
# Bind mount some kernel related stuff
mount --bind /dev $chrootenvDest/dev
mount --bind /dev/pts $chrootenvDest/dev/pts
mount --bind /dev/shm $chrootenvDest/dev/shm
mount --bind /proc $chrootenvDest/proc
mount --bind /sys $chrootenvDest/sys
# Bind mount home directories
mount --bind /home $chrootenvDest/home
# Bind mount state directories
mount --bind /var $chrootenvDest/var
mount --rbind /run $chrootenvDest/run
# Bind mount the host system's /etc
mount --bind /etc $chrootenvDest/host-etc
# Bind mount the host system's /tmp
mount --bind /tmp $chrootenvDest/host-tmp
# Bind mount /tmp
mount --bind /tmp/chrootenv-@name@ $chrootenvDest/tmp
# Expose sockets in /tmp
for i in /tmp/.*-unix; do
ln -s "/host-tmp/$(basename "$i")" "$chrootenvDest/$i"
done

View File

@ -1,6 +0,0 @@
#! @shell@ -e
chrootenvDest=/run/chrootenv/@name@
# Unmount all (r)bind mounts
umount -l $chrootenvDest/{dev/pts,dev/shm,dev,nix/store,proc,sys,host-etc,host-tmp,home,var,tmp,run}

View File

@ -2,16 +2,15 @@
# Bind mounts hierarchy: from => to (relative) # Bind mounts hierarchy: from => to (relative)
# If 'to' is nil, path will be the same # If 'to' is nil, path will be the same
mounts = { '/nix/store' => nil, mounts = { '/' => 'host',
'/dev' => nil,
'/proc' => nil, '/proc' => nil,
'/sys' => nil, '/sys' => nil,
'/etc' => 'host-etc', '/nix' => nil,
'/tmp' => 'host-tmp', '/tmp' => nil,
'/home' => nil,
'/var' => nil, '/var' => nil,
'/run' => nil, '/run' => nil,
'/root' => nil, '/dev' => nil,
'/home' => nil,
} }
# Propagate environment variables # Propagate environment variables
@ -62,9 +61,8 @@ $mount = make_fcall 'mount', [Fiddle::TYPE_VOIDP,
Fiddle::TYPE_INT Fiddle::TYPE_INT
# Read command line args # Read command line args
abort "Usage: chrootenv swdir program args..." unless ARGV.length >= 2 abort "Usage: chrootenv program args..." unless ARGV.length >= 1
swdir = Pathname.new ARGV[0] execp = ARGV
execp = ARGV.drop 1
# Populate extra mounts # Populate extra mounts
if not ENV["CHROOTENV_EXTRA_BINDS"].nil? if not ENV["CHROOTENV_EXTRA_BINDS"].nil?
@ -132,24 +130,6 @@ if $cpid == 0
Dir.chroot root Dir.chroot root
Dir.chdir '/' Dir.chdir '/'
# Symlink swdir hierarchy
mount_dirs = Set.new mounts.map { |_, v| Pathname.new v }
link_swdir = lambda do |swdir, prefix|
swdir.find do |path|
rel = prefix.join path.relative_path_from(swdir)
# Don't symlink anything in binded or symlinked directories
Find.prune if mount_dirs.include? rel or rel.symlink?
if not rel.directory?
# File does not exist; make a symlink and bail out
rel.make_symlink path
Find.prune
end
# Recursively follow symlinks
link_swdir.call path.readlink, rel if path.symlink?
end
end
link_swdir.call swdir, Pathname.new('')
# New environment # New environment
new_env = Hash[ envvars.map { |x| [x, ENV[x]] } ] new_env = Hash[ envvars.map { |x| [x, ENV[x]] } ]

View File

@ -1,28 +1,29 @@
{ runCommand, lib, writeText, writeScriptBin, stdenv, ruby } : { callPackage, runCommand, lib, writeScript, stdenv, coreutils, ruby }:
{ env, runScript ? "bash", extraBindMounts ? [], extraInstallCommands ? "", meta ? {}, passthru ? {} } :
let buildFHSEnv = callPackage ./env.nix { }; in
args@{ name, runScript ? "bash", extraBindMounts ? [], extraInstallCommands ? "", meta ? {}, passthru ? {}, ... }:
let let
name = env.pname; env = buildFHSEnv (removeAttrs args [ "runScript" "extraBindMounts" "extraInstallCommands" "meta" "passthru" ]);
# Sandboxing script # Sandboxing script
chroot-user = writeScriptBin "chroot-user" '' chroot-user = writeScript "chroot-user" ''
#! ${ruby}/bin/ruby #! ${ruby}/bin/ruby
${builtins.readFile ./chroot-user.rb} ${builtins.readFile ./chroot-user.rb}
''; '';
init = run: writeText "${name}-init" '' init = run: writeScript "${name}-init" ''
source /etc/profile #! ${stdenv.shell}
for i in ${env}/* /host/*; do
# Make /tmp directory path="/''${i##*/}"
mkdir -m 1777 /tmp [ -e "$path" ] || ${coreutils}/bin/ln -s "$i" "$path"
# Expose sockets in /tmp
for i in /host-tmp/.*-unix; do
ln -s "$i" "/tmp/$(basename "$i")"
done done
[ -d "$1" ] && [ -r "$1" ] && cd "$1" [ -d "$1" ] && [ -r "$1" ] && cd "$1"
shift shift
source /etc/profile
exec ${run} "$@" exec ${run} "$@"
''; '';
@ -32,7 +33,7 @@ in runCommand name {
env = runCommand "${name}-shell-env" { env = runCommand "${name}-shell-env" {
shellHook = '' shellHook = ''
export CHROOTENV_EXTRA_BINDS="${lib.concatStringsSep ":" extraBindMounts}:$CHROOTENV_EXTRA_BINDS" export CHROOTENV_EXTRA_BINDS="${lib.concatStringsSep ":" extraBindMounts}:$CHROOTENV_EXTRA_BINDS"
exec ${chroot-user}/bin/chroot-user ${env} bash ${init "bash"} "$(pwd)" exec ${chroot-user} ${init "bash"} "$(pwd)"
''; '';
} '' } ''
echo >&2 "" echo >&2 ""
@ -46,7 +47,7 @@ in runCommand name {
cat <<EOF >$out/bin/${name} cat <<EOF >$out/bin/${name}
#! ${stdenv.shell} #! ${stdenv.shell}
export CHROOTENV_EXTRA_BINDS="${lib.concatStringsSep ":" extraBindMounts}:\$CHROOTENV_EXTRA_BINDS" export CHROOTENV_EXTRA_BINDS="${lib.concatStringsSep ":" extraBindMounts}:\$CHROOTENV_EXTRA_BINDS"
exec ${chroot-user}/bin/chroot-user ${env} bash ${init runScript} "\$(pwd)" "\$@" exec ${chroot-user} ${init runScript} "\$(pwd)" "\$@"
EOF EOF
chmod +x $out/bin/${name} chmod +x $out/bin/${name}
${extraInstallCommands} ${extraInstallCommands}

View File

@ -1,7 +1,7 @@
{ nixpkgs, nixpkgs_i686, system { stdenv, buildEnv, writeText, pkgs, pkgsi686Linux, system }:
} :
{ name, profile ? "" { name, profile ? ""
, pkgs ? null, targetPkgs ? pkgs: [], multiPkgs ? pkgs: [] , targetPkgs ? pkgs: [], multiPkgs ? pkgs: []
, extraBuildCommands ? "", extraBuildCommandsMulti ? "" , extraBuildCommands ? "", extraBuildCommandsMulti ? ""
, extraOutputsToInstall ? [] , extraOutputsToInstall ? []
}: }:
@ -22,37 +22,32 @@
# /lib will link to /lib32 # /lib will link to /lib32
let let
isMultiBuild = pkgs == null && multiPkgs != null && system == "x86_64-linux"; is64Bit = system == "x86_64-linux";
isMultiBuild = multiPkgs != null && is64Bit;
isTargetBuild = !isMultiBuild; isTargetBuild = !isMultiBuild;
# support deprecated "pkgs" option.
targetPkgs' =
if pkgs != null
then builtins.trace "buildFHSEnv: 'pkgs' option is deprecated, use 'targetPkgs'" (pkgs': pkgs)
else targetPkgs;
# list of packages (usually programs) which are only be installed for the # list of packages (usually programs) which are only be installed for the
# host's architecture # host's architecture
targetPaths = targetPkgs' nixpkgs ++ (if multiPkgs == null then [] else multiPkgs nixpkgs); targetPaths = targetPkgs pkgs ++ (if multiPkgs == null then [] else multiPkgs pkgs);
# list of packages which are installed for both x86 and x86_64 on x86_64 # list of packages which are installed for both x86 and x86_64 on x86_64
# systems # systems
multiPaths = multiPkgs nixpkgs_i686; multiPaths = multiPkgs pkgsi686Linux;
# base packages of the chroot # base packages of the chroot
# these match the host's architecture, glibc_multi is used for multilib # these match the host's architecture, glibc_multi is used for multilib
# builds. # builds.
basePkgs = with nixpkgs; basePkgs = with pkgs;
[ (if isMultiBuild then glibc_multi else glibc) [ (if isMultiBuild then glibc_multi else glibc)
gcc.cc.lib bashInteractive coreutils less shadow su gcc.cc.lib bashInteractive coreutils less shadow su
gawk diffutils findutils gnused gnugrep gawk diffutils findutils gnused gnugrep
gnutar gzip bzip2 xz glibcLocales gnutar gzip bzip2 xz glibcLocales
]; ];
baseMultiPkgs = with nixpkgs_i686; baseMultiPkgs = with pkgsi686Linux;
[ gcc.cc.lib [ gcc.cc.lib
]; ];
etcProfile = nixpkgs.writeText "profile" '' etcProfile = writeText "profile" ''
export PS1='${name}-chrootenv:\u@\h:\w\$ ' export PS1='${name}-chrootenv:\u@\h:\w\$ '
export LOCALE_ARCHIVE='/usr/lib/locale/locale-archive' export LOCALE_ARCHIVE='/usr/lib/locale/locale-archive'
export LD_LIBRARY_PATH='/run/opengl-driver/lib:/run/opengl-driver-32/lib:/usr/lib:/usr/lib32' export LD_LIBRARY_PATH='/run/opengl-driver/lib:/run/opengl-driver-32/lib:/usr/lib:/usr/lib32'
@ -67,7 +62,7 @@ let
''; '';
# Compose /etc for the chroot environment # Compose /etc for the chroot environment
etcPkg = nixpkgs.stdenv.mkDerivation { etcPkg = stdenv.mkDerivation {
name = "${name}-chrootenv-etc"; name = "${name}-chrootenv-etc";
buildCommand = '' buildCommand = ''
mkdir -p $out/etc mkdir -p $out/etc
@ -77,38 +72,38 @@ let
ln -s ${etcProfile} profile ln -s ${etcProfile} profile
# compatibility with NixOS # compatibility with NixOS
ln -s /host-etc/static static ln -s /host/etc/static static
# symlink some NSS stuff # symlink some NSS stuff
ln -s /host-etc/passwd passwd ln -s /host/etc/passwd passwd
ln -s /host-etc/group group ln -s /host/etc/group group
ln -s /host-etc/shadow shadow ln -s /host/etc/shadow shadow
ln -s /host-etc/hosts hosts ln -s /host/etc/hosts hosts
ln -s /host-etc/resolv.conf resolv.conf ln -s /host/etc/resolv.conf resolv.conf
ln -s /host-etc/nsswitch.conf nsswitch.conf ln -s /host/etc/nsswitch.conf nsswitch.conf
# symlink sudo and su stuff # symlink sudo and su stuff
ln -s /host-etc/login.defs login.defs ln -s /host/etc/login.defs login.defs
ln -s /host-etc/sudoers sudoers ln -s /host/etc/sudoers sudoers
ln -s /host-etc/sudoers.d sudoers.d ln -s /host/etc/sudoers.d sudoers.d
# symlink other core stuff # symlink other core stuff
ln -s /host-etc/localtime localtime ln -s /host/etc/localtime localtime
ln -s /host-etc/machine-id machine-id ln -s /host/etc/machine-id machine-id
ln -s /host-etc/os-release os-release ln -s /host/etc/os-release os-release
# symlink PAM stuff # symlink PAM stuff
ln -s /host-etc/pam.d pam.d ln -s /host/etc/pam.d pam.d
# symlink fonts stuff # symlink fonts stuff
ln -s /host-etc/fonts fonts ln -s /host/etc/fonts fonts
# symlink ALSA stuff # symlink ALSA stuff
ln -s /host-etc/asound.conf asound.conf ln -s /host/etc/asound.conf asound.conf
# symlink SSL certs # symlink SSL certs
mkdir -p ssl mkdir -p ssl
ln -s /host-etc/ssl/certs ssl/certs ln -s /host/etc/ssl/certs ssl/certs
# symlink /etc/mtab -> /proc/mounts (compat for old userspace progs) # symlink /etc/mtab -> /proc/mounts (compat for old userspace progs)
ln -s /proc/mounts mtab ln -s /proc/mounts mtab
@ -116,14 +111,14 @@ let
}; };
# Composes a /usr-like directory structure # Composes a /usr-like directory structure
staticUsrProfileTarget = nixpkgs.buildEnv { staticUsrProfileTarget = buildEnv {
name = "${name}-usr-target"; name = "${name}-usr-target";
paths = [ etcPkg ] ++ basePkgs ++ targetPaths; paths = [ etcPkg ] ++ basePkgs ++ targetPaths;
extraOutputsToInstall = [ "lib" "out" ] ++ extraOutputsToInstall; extraOutputsToInstall = [ "lib" "out" ] ++ extraOutputsToInstall;
ignoreCollisions = true; ignoreCollisions = true;
}; };
staticUsrProfileMulti = nixpkgs.buildEnv { staticUsrProfileMulti = buildEnv {
name = "${name}-usr-multi"; name = "${name}-usr-multi";
paths = baseMultiPkgs ++ multiPaths; paths = baseMultiPkgs ++ multiPaths;
extraOutputsToInstall = [ "lib" "out" ] ++ extraOutputsToInstall; extraOutputsToInstall = [ "lib" "out" ] ++ extraOutputsToInstall;
@ -132,10 +127,9 @@ let
# setup library paths only for the targeted architecture # setup library paths only for the targeted architecture
setupLibDirs_target = '' setupLibDirs_target = ''
mkdir -m0755 lib # link content of targetPaths
cp -rsHf ${staticUsrProfileTarget}/lib lib
# copy content of targetPaths ln -s lib lib${if is64Bit then "64" else "32"}
cp -rsHf ${staticUsrProfileTarget}/lib/* lib/
''; '';
# setup /lib, /lib32 and /lib64 # setup /lib, /lib32 and /lib64
@ -184,7 +178,7 @@ let
done done
''; '';
in nixpkgs.stdenv.mkDerivation { in stdenv.mkDerivation {
name = "${name}-fhs"; name = "${name}-fhs";
buildCommand = '' buildCommand = ''
mkdir -p $out mkdir -p $out
@ -196,7 +190,4 @@ in nixpkgs.stdenv.mkDerivation {
${if isMultiBuild then extraBuildCommandsMulti else ""} ${if isMultiBuild then extraBuildCommandsMulti else ""}
''; '';
preferLocalBuild = true; preferLocalBuild = true;
passthru = {
pname = name;
};
} }

View File

@ -118,29 +118,7 @@ in
buildEnv = callPackage ../build-support/buildenv { }; # not actually a package buildEnv = callPackage ../build-support/buildenv { }; # not actually a package
buildFHSEnv = callPackage ../build-support/build-fhs-chrootenv/env.nix { buildFHSUserEnv = callPackage ../build-support/build-fhs-userenv { };
nixpkgs = pkgs;
nixpkgs_i686 = pkgsi686Linux;
};
chrootFHSEnv = callPackage ../build-support/build-fhs-chrootenv { };
userFHSEnv = callPackage ../build-support/build-fhs-userenv {
ruby = ruby_2_1;
};
buildFHSChrootEnv = args: chrootFHSEnv {
env = buildFHSEnv (removeAttrs args [ "extraInstallCommands" ]);
extraInstallCommands = args.extraInstallCommands or "";
};
buildFHSUserEnv = args: userFHSEnv {
env = buildFHSEnv (removeAttrs args [ "runScript" "extraBindMounts" "extraInstallCommands" "meta" "passthru" ]);
runScript = args.runScript or "bash";
extraBindMounts = args.extraBindMounts or [];
extraInstallCommands = args.extraInstallCommands or "";
meta = args.meta or {};
passthru = args.passthru or {};
};
buildMaven = callPackage ../build-support/build-maven.nix {}; buildMaven = callPackage ../build-support/build-maven.nix {};