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)
# If 'to' is nil, path will be the same
mounts = { '/nix/store' => nil,
'/dev' => nil,
mounts = { '/' => 'host',
'/proc' => nil,
'/sys' => nil,
'/etc' => 'host-etc',
'/tmp' => 'host-tmp',
'/home' => nil,
'/nix' => nil,
'/tmp' => nil,
'/var' => nil,
'/run' => nil,
'/root' => nil,
'/dev' => nil,
'/home' => nil,
}
# Propagate environment variables
@ -62,9 +61,8 @@ $mount = make_fcall 'mount', [Fiddle::TYPE_VOIDP,
Fiddle::TYPE_INT
# Read command line args
abort "Usage: chrootenv swdir program args..." unless ARGV.length >= 2
swdir = Pathname.new ARGV[0]
execp = ARGV.drop 1
abort "Usage: chrootenv program args..." unless ARGV.length >= 1
execp = ARGV
# Populate extra mounts
if not ENV["CHROOTENV_EXTRA_BINDS"].nil?
@ -132,24 +130,6 @@ if $cpid == 0
Dir.chroot root
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_env = Hash[ envvars.map { |x| [x, ENV[x]] } ]

View file

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

View file

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

View file

@ -118,29 +118,7 @@ in
buildEnv = callPackage ../build-support/buildenv { }; # not actually a package
buildFHSEnv = callPackage ../build-support/build-fhs-chrootenv/env.nix {
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 {};
};
buildFHSUserEnv = callPackage ../build-support/build-fhs-userenv { };
buildMaven = callPackage ../build-support/build-maven.nix {};