Use chroots for all derivations

If ‘build-use-chroot’ is set to ‘true’, fixed-output derivations are
now also chrooted. However, unlike normal derivations, they don't get
a private network namespace, so they can still access the
network. Also, the use of the ‘__noChroot’ derivation attribute is
no longer allowed.

Setting ‘build-use-chroot’ to ‘relaxed’ gives the old behaviour.
This commit is contained in:
Eelco Dolstra 2015-02-23 15:41:41 +01:00
parent 15d2d3c34e
commit 99897f6979
4 changed files with 56 additions and 30 deletions

View File

@ -227,24 +227,32 @@ flag, e.g. <literal>--option gc-keep-outputs false</literal>.</para>
<varlistentry><term><literal>build-use-chroot</literal></term> <varlistentry><term><literal>build-use-chroot</literal></term>
<listitem><para>If set to <literal>true</literal>, builds will be <listitem><para>If set to <literal>true</literal>, builds will be
performed in a <emphasis>chroot environment</emphasis>, i.e., the performed in a <emphasis>chroot environment</emphasis>, i.e.,
build will be isolated from the normal file system hierarchy and theyre isolated from the normal file system hierarchy and will
will only see its dependencies in the Nix store, the temporary only see their dependencies in the Nix store, the temporary build
build directory, private versions of <filename>/proc</filename>, directory, private versions of <filename>/proc</filename>,
<filename>/dev</filename>, <filename>/dev/shm</filename> and <filename>/dev</filename>, <filename>/dev/shm</filename> and
<filename>/dev/pts</filename>, and the paths configured with the <filename>/dev/pts</filename>, and the paths configured with the
<link linkend='conf-build-chroot-dirs'><literal>build-chroot-dirs</literal> <link linkend='conf-build-chroot-dirs'><literal>build-chroot-dirs</literal>
option</link>. This is useful to prevent undeclared dependencies option</link>. This is useful to prevent undeclared dependencies
on files in directories such as on files in directories such as <filename>/usr/bin</filename>. In
<filename>/usr/bin</filename>.</para> addition, on Linux, builds run in rivate PID, mount, network, IPC
and UTS namespaces to isolate them from other processes in the
system (except that fixed-output derivations do not run in private
network namespace to ensure they can access the network).</para>
<para>The use of a chroot requires that Nix is run as root (so you <para>Currently, chroots only work on Linux and Mac OS X. The use
should use the <link linkend='conf-build-users-group'>“build of a chroot requires that Nix is run as root (so you should use
users” feature</link> to perform the actual builds under different the <link linkend='conf-build-users-group'>“build users”
users than root). Currently, chroot builds only work on Linux feature</link> to perform the actual builds under different users
because Nix uses “bind mounts” to make the Nix store and other than root).</para>
directories available inside the chroot. Kernel version 3.13 or later
is needed.</para> <para>If this option is set to <literal>relaxed</literal>, then
fixed-output derivations and derivations that have the
<varname>__noChroot</varname> attribute set to
<literal>true</literal> do not run in chroots.</para>
<para>The default is <literal>false</literal>.</para>
</listitem> </listitem>

View File

@ -1768,12 +1768,20 @@ void DerivationGoal::startBuilder()
functions like fetchurl (which needs a proper /etc/resolv.conf) functions like fetchurl (which needs a proper /etc/resolv.conf)
work properly. Purity checking for fixed-output derivations work properly. Purity checking for fixed-output derivations
is somewhat pointless anyway. */ is somewhat pointless anyway. */
useChroot = settings.useChroot; {
string x = settings.get("build-use-chroot", string("false"));
if (fixedOutput) useChroot = false; if (x != "true" && x != "false" && x != "relaxed")
throw Error("option build-use-chroot must be set to one of true, false or relaxed");
/* Hack to allow derivations to disable chroot builds. */ if (x == "true") {
if (get(drv.env, "__noChroot") == "1") useChroot = false; if (get(drv.env, "__noChroot") == "1")
throw Error(format("derivation %1% has __noChroot set, but that's not allowed when build-use-chroot is true") % drvPath);
useChroot = true;
}
else if (x == "false")
useChroot = false;
else if (x == "relaxed")
useChroot = !fixedOutput && get(drv.env, "__noChroot") != "1";
}
if (useChroot) { if (useChroot) {
/* Allow a user-configurable set of directories from the /* Allow a user-configurable set of directories from the
@ -1856,7 +1864,8 @@ void DerivationGoal::startBuilder()
% (buildUser.enabled() ? buildUser.getGID() : getgid())).str()); % (buildUser.enabled() ? buildUser.getGID() : getgid())).str());
/* Create /etc/hosts with localhost entry. */ /* Create /etc/hosts with localhost entry. */
writeFile(chrootRootDir + "/etc/hosts", "127.0.0.1 localhost\n"); if (!fixedOutput)
writeFile(chrootRootDir + "/etc/hosts", "127.0.0.1 localhost\n");
/* Make the closure of the inputs available in the chroot, /* Make the closure of the inputs available in the chroot,
rather than the whole Nix store. This prevents any access rather than the whole Nix store. This prevents any access
@ -1964,7 +1973,9 @@ void DerivationGoal::startBuilder()
- The private network namespace ensures that the builder - The private network namespace ensures that the builder
cannot talk to the outside world (or vice versa). It cannot talk to the outside world (or vice versa). It
only has a private loopback interface. only has a private loopback interface. (Fixed-output
derivations are not run in a private network namespace
to allow functions like fetchurl to work.)
- The IPC namespace prevents the builder from communicating - The IPC namespace prevents the builder from communicating
with outside processes using SysV IPC mechanisms (shared with outside processes using SysV IPC mechanisms (shared
@ -1983,8 +1994,9 @@ void DerivationGoal::startBuilder()
*/ */
Pid helper = startProcess([&]() { Pid helper = startProcess([&]() {
char stack[32 * 1024]; char stack[32 * 1024];
pid_t child = clone(childEntry, stack + sizeof(stack) - 8, int flags = CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWIPC | CLONE_NEWUTS | CLONE_PARENT | SIGCHLD;
CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWNET | CLONE_NEWIPC | CLONE_NEWUTS | CLONE_PARENT | SIGCHLD, this); if (!fixedOutput) flags |= CLONE_NEWNET;
pid_t child = clone(childEntry, stack + sizeof(stack) - 8, flags, this);
if (child == -1) { if (child == -1) {
if (errno == EINVAL) if (errno == EINVAL)
throw SysError("cloning builder process (Linux chroot builds require 3.13 or later)"); throw SysError("cloning builder process (Linux chroot builds require 3.13 or later)");
@ -2081,10 +2093,10 @@ void DerivationGoal::runChild()
/* Set up a nearly empty /dev, unless the user asked to /* Set up a nearly empty /dev, unless the user asked to
bind-mount the host /dev. */ bind-mount the host /dev. */
Strings ss;
if (dirsInChroot.find("/dev") == dirsInChroot.end()) { if (dirsInChroot.find("/dev") == dirsInChroot.end()) {
createDirs(chrootRootDir + "/dev/shm"); createDirs(chrootRootDir + "/dev/shm");
createDirs(chrootRootDir + "/dev/pts"); createDirs(chrootRootDir + "/dev/pts");
Strings ss;
ss.push_back("/dev/full"); ss.push_back("/dev/full");
#ifdef __linux__ #ifdef __linux__
if (pathExists("/dev/kvm")) if (pathExists("/dev/kvm"))
@ -2095,13 +2107,24 @@ void DerivationGoal::runChild()
ss.push_back("/dev/tty"); ss.push_back("/dev/tty");
ss.push_back("/dev/urandom"); ss.push_back("/dev/urandom");
ss.push_back("/dev/zero"); ss.push_back("/dev/zero");
foreach (Strings::iterator, i, ss) dirsInChroot[*i] = *i;
createSymlink("/proc/self/fd", chrootRootDir + "/dev/fd"); createSymlink("/proc/self/fd", chrootRootDir + "/dev/fd");
createSymlink("/proc/self/fd/0", chrootRootDir + "/dev/stdin"); createSymlink("/proc/self/fd/0", chrootRootDir + "/dev/stdin");
createSymlink("/proc/self/fd/1", chrootRootDir + "/dev/stdout"); createSymlink("/proc/self/fd/1", chrootRootDir + "/dev/stdout");
createSymlink("/proc/self/fd/2", chrootRootDir + "/dev/stderr"); createSymlink("/proc/self/fd/2", chrootRootDir + "/dev/stderr");
} }
/* Fixed-output derivations typically need to access the
network, so give them access to /etc/resolv.conf and so
on. */
if (fixedOutput) {
ss.push_back("/etc/resolv.conf");
ss.push_back("/etc/nsswitch.conf");
ss.push_back("/etc/services");
ss.push_back("/etc/hosts");
}
for (auto & i : ss) dirsInChroot[i] = i;
/* Bind-mount all the directories from the "host" /* Bind-mount all the directories from the "host"
filesystem that we want in the chroot filesystem that we want in the chroot
environment. */ environment. */

View File

@ -47,7 +47,6 @@ Settings::Settings()
syncBeforeRegistering = false; syncBeforeRegistering = false;
useSubstitutes = true; useSubstitutes = true;
buildUsersGroup = getuid() == 0 ? "nixbld" : ""; buildUsersGroup = getuid() == 0 ? "nixbld" : "";
useChroot = false;
useSshSubstituter = true; useSshSubstituter = true;
impersonateLinux26 = false; impersonateLinux26 = false;
keepLog = true; keepLog = true;
@ -158,7 +157,6 @@ void Settings::update()
_get(syncBeforeRegistering, "sync-before-registering"); _get(syncBeforeRegistering, "sync-before-registering");
_get(useSubstitutes, "build-use-substitutes"); _get(useSubstitutes, "build-use-substitutes");
_get(buildUsersGroup, "build-users-group"); _get(buildUsersGroup, "build-users-group");
_get(useChroot, "build-use-chroot");
_get(impersonateLinux26, "build-impersonate-linux-26"); _get(impersonateLinux26, "build-impersonate-linux-26");
_get(keepLog, "build-keep-log"); _get(keepLog, "build-keep-log");
_get(compressLog, "build-compress-log"); _get(compressLog, "build-compress-log");

View File

@ -145,9 +145,6 @@ struct Settings {
/* The Unix group that contains the build users. */ /* The Unix group that contains the build users. */
string buildUsersGroup; string buildUsersGroup;
/* Whether to build in chroot. */
bool useChroot;
/* Set of ssh connection strings for the ssh substituter */ /* Set of ssh connection strings for the ssh substituter */
Strings sshSubstituterHosts; Strings sshSubstituterHosts;