forked from lix-project/lix
libstore: Add LocalDerivationGoal prepareSandbox hook
Add a new OS-specific hook called `prepareSandbox`, run before forking
On Darwin this is empty as nothing is required,
on Linux this creates the chroot directory and adds basic files,
and on platforms using a fallback this throws an exception
Change-Id: Ie30c38c387f2e0e5844b2afa32fd4d33b1180dae
This commit is contained in:
parent
5eec6418de
commit
af1dcc2d5e
|
@ -659,101 +659,11 @@ void LocalDerivationGoal::startBuilder()
|
||||||
pathsInChroot[i] = {i, true};
|
pathsInChroot[i] = {i, true};
|
||||||
}
|
}
|
||||||
|
|
||||||
#if __linux__
|
if (parsedDrv->useUidRange() && !supportsUidRange())
|
||||||
/* Create a temporary directory in which we set up the chroot
|
|
||||||
environment using bind-mounts. We put it in the Nix store
|
|
||||||
to ensure that we can create hard-links to non-directory
|
|
||||||
inputs in the fake Nix store in the chroot (see below). */
|
|
||||||
chrootRootDir = worker.store.Store::toRealPath(drvPath) + ".chroot";
|
|
||||||
deletePath(chrootRootDir);
|
|
||||||
|
|
||||||
/* Clean up the chroot directory automatically. */
|
|
||||||
autoDelChroot = std::make_shared<AutoDelete>(chrootRootDir);
|
|
||||||
|
|
||||||
printMsg(lvlChatty, "setting up chroot environment in '%1%'", chrootRootDir);
|
|
||||||
|
|
||||||
// FIXME: make this 0700
|
|
||||||
if (mkdir(chrootRootDir.c_str(), buildUser && buildUser->getUIDCount() != 1 ? 0755 : 0750) == -1)
|
|
||||||
throw SysError("cannot create '%1%'", chrootRootDir);
|
|
||||||
|
|
||||||
if (buildUser && chown(chrootRootDir.c_str(), buildUser->getUIDCount() != 1 ? buildUser->getUID() : 0, buildUser->getGID()) == -1)
|
|
||||||
throw SysError("cannot change ownership of '%1%'", chrootRootDir);
|
|
||||||
|
|
||||||
/* Create a writable /tmp in the chroot. Many builders need
|
|
||||||
this. (Of course they should really respect $TMPDIR
|
|
||||||
instead.) */
|
|
||||||
Path chrootTmpDir = chrootRootDir + "/tmp";
|
|
||||||
createDirs(chrootTmpDir);
|
|
||||||
chmodPath(chrootTmpDir, 01777);
|
|
||||||
|
|
||||||
/* Create a /etc/passwd with entries for the build user and the
|
|
||||||
nobody account. The latter is kind of a hack to support
|
|
||||||
Samba-in-QEMU. */
|
|
||||||
createDirs(chrootRootDir + "/etc");
|
|
||||||
if (parsedDrv->useUidRange())
|
|
||||||
chownToBuilder(chrootRootDir + "/etc");
|
|
||||||
|
|
||||||
if (parsedDrv->useUidRange() && (!buildUser || buildUser->getUIDCount() < 65536))
|
|
||||||
throw Error("feature 'uid-range' requires the setting '%s' to be enabled", settings.autoAllocateUids.name);
|
|
||||||
|
|
||||||
/* Create /etc/hosts with localhost entry. */
|
|
||||||
if (derivationType->isSandboxed())
|
|
||||||
writeFile(chrootRootDir + "/etc/hosts", "127.0.0.1 localhost\n::1 localhost\n");
|
|
||||||
|
|
||||||
/* Make the closure of the inputs available in the chroot,
|
|
||||||
rather than the whole Nix store. This prevents any access
|
|
||||||
to undeclared dependencies. Directories are bind-mounted,
|
|
||||||
while other inputs are hard-linked (since only directories
|
|
||||||
can be bind-mounted). !!! As an extra security
|
|
||||||
precaution, make the fake Nix store only writable by the
|
|
||||||
build user. */
|
|
||||||
Path chrootStoreDir = chrootRootDir + worker.store.storeDir;
|
|
||||||
createDirs(chrootStoreDir);
|
|
||||||
chmodPath(chrootStoreDir, 01775);
|
|
||||||
|
|
||||||
if (buildUser && chown(chrootStoreDir.c_str(), 0, buildUser->getGID()) == -1)
|
|
||||||
throw SysError("cannot change ownership of '%1%'", chrootStoreDir);
|
|
||||||
|
|
||||||
for (auto & i : inputPaths) {
|
|
||||||
auto p = worker.store.printStorePath(i);
|
|
||||||
Path r = worker.store.toRealPath(p);
|
|
||||||
pathsInChroot.insert_or_assign(p, r);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If we're repairing, checking or rebuilding part of a
|
|
||||||
multiple-outputs derivation, it's possible that we're
|
|
||||||
rebuilding a path that is in settings.sandbox-paths
|
|
||||||
(typically the dependencies of /bin/sh). Throw them
|
|
||||||
out. */
|
|
||||||
for (auto & i : drv->outputsAndOptPaths(worker.store)) {
|
|
||||||
/* If the name isn't known a priori (i.e. floating
|
|
||||||
content-addressed derivation), the temporary location we use
|
|
||||||
should be fresh. Freshness means it is impossible that the path
|
|
||||||
is already in the sandbox, so we don't need to worry about
|
|
||||||
removing it. */
|
|
||||||
if (i.second.second)
|
|
||||||
pathsInChroot.erase(worker.store.printStorePath(*i.second.second));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cgroup) {
|
|
||||||
if (mkdir(cgroup->c_str(), 0755) != 0)
|
|
||||||
throw SysError("creating cgroup '%s'", *cgroup);
|
|
||||||
chownToBuilder(*cgroup);
|
|
||||||
chownToBuilder(*cgroup + "/cgroup.procs");
|
|
||||||
chownToBuilder(*cgroup + "/cgroup.threads");
|
|
||||||
//chownToBuilder(*cgroup + "/cgroup.subtree_control");
|
|
||||||
}
|
|
||||||
|
|
||||||
#else
|
|
||||||
if (parsedDrv->useUidRange())
|
|
||||||
throw Error("feature 'uid-range' is not supported on this platform");
|
throw Error("feature 'uid-range' is not supported on this platform");
|
||||||
#if __APPLE__
|
|
||||||
/* We don't really have any parent prep work to do (yet?)
|
prepareSandbox();
|
||||||
All work happens in the child, instead. */
|
|
||||||
#else
|
|
||||||
throw Error("sandboxing builds is not supported on this platform");
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
} else {
|
} else {
|
||||||
if (parsedDrv->useUidRange())
|
if (parsedDrv->useUidRange())
|
||||||
throw Error("feature 'uid-range' is only supported in sandboxed builds");
|
throw Error("feature 'uid-range' is only supported in sandboxed builds");
|
||||||
|
|
|
@ -324,12 +324,29 @@ struct LocalDerivationGoal : public DerivationGoal
|
||||||
protected:
|
protected:
|
||||||
using DerivationGoal::DerivationGoal;
|
using DerivationGoal::DerivationGoal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup dependencies outside the sandbox.
|
||||||
|
* Called in the parent nix process.
|
||||||
|
*/
|
||||||
|
virtual void prepareSandbox()
|
||||||
|
{
|
||||||
|
throw Error("sandboxing builds is not supported on this platform");
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the builder, replacing the current process.
|
* Execute the builder, replacing the current process.
|
||||||
* Generally this means an `execve` call.
|
* Generally this means an `execve` call.
|
||||||
*/
|
*/
|
||||||
virtual void execBuilder(std::string builder, Strings args, Strings envStrs);
|
virtual void execBuilder(std::string builder, Strings args, Strings envStrs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether derivation can be built on current platform with `uid-range` feature
|
||||||
|
*/
|
||||||
|
virtual bool supportsUidRange()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,12 @@ public:
|
||||||
using LocalDerivationGoal::LocalDerivationGoal;
|
using LocalDerivationGoal::LocalDerivationGoal;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/**
|
||||||
|
* Prepare the sandbox: This is empty on Darwin since sandbox setup happens in
|
||||||
|
* enterSandbox
|
||||||
|
*/
|
||||||
|
void prepareSandbox() override{};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set process flags to enter or leave rosetta, then execute the builder
|
* Set process flags to enter or leave rosetta, then execute the builder
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#include "build/worker.hh"
|
||||||
#include "cgroup.hh"
|
#include "cgroup.hh"
|
||||||
#include "gc-store.hh"
|
#include "gc-store.hh"
|
||||||
#include "signals.hh"
|
#include "signals.hh"
|
||||||
|
@ -116,6 +117,93 @@ void LinuxLocalStore::findPlatformRoots(UncheckedRoots & unchecked)
|
||||||
readFileRoots("/proc/sys/kernel/poweroff_cmd", unchecked);
|
readFileRoots("/proc/sys/kernel/poweroff_cmd", unchecked);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LinuxLocalDerivationGoal::prepareSandbox()
|
||||||
|
{
|
||||||
|
/* Create a temporary directory in which we set up the chroot
|
||||||
|
environment using bind-mounts. We put it in the Nix store
|
||||||
|
to ensure that we can create hard-links to non-directory
|
||||||
|
inputs in the fake Nix store in the chroot (see below). */
|
||||||
|
chrootRootDir = worker.store.Store::toRealPath(drvPath) + ".chroot";
|
||||||
|
deletePath(chrootRootDir);
|
||||||
|
|
||||||
|
/* Clean up the chroot directory automatically. */
|
||||||
|
autoDelChroot = std::make_shared<AutoDelete>(chrootRootDir);
|
||||||
|
|
||||||
|
printMsg(lvlChatty, "setting up chroot environment in '%1%'", chrootRootDir);
|
||||||
|
|
||||||
|
// FIXME: make this 0700
|
||||||
|
if (mkdir(chrootRootDir.c_str(), buildUser && buildUser->getUIDCount() != 1 ? 0755 : 0750) == -1)
|
||||||
|
throw SysError("cannot create '%1%'", chrootRootDir);
|
||||||
|
|
||||||
|
if (buildUser && chown(chrootRootDir.c_str(), buildUser->getUIDCount() != 1 ? buildUser->getUID() : 0, buildUser->getGID()) == -1)
|
||||||
|
throw SysError("cannot change ownership of '%1%'", chrootRootDir);
|
||||||
|
|
||||||
|
/* Create a writable /tmp in the chroot. Many builders need
|
||||||
|
this. (Of course they should really respect $TMPDIR
|
||||||
|
instead.) */
|
||||||
|
Path chrootTmpDir = chrootRootDir + "/tmp";
|
||||||
|
createDirs(chrootTmpDir);
|
||||||
|
chmodPath(chrootTmpDir, 01777);
|
||||||
|
|
||||||
|
/* Create a /etc/passwd with entries for the build user and the
|
||||||
|
nobody account. The latter is kind of a hack to support
|
||||||
|
Samba-in-QEMU. */
|
||||||
|
createDirs(chrootRootDir + "/etc");
|
||||||
|
if (parsedDrv->useUidRange())
|
||||||
|
chownToBuilder(chrootRootDir + "/etc");
|
||||||
|
|
||||||
|
if (parsedDrv->useUidRange() && (!buildUser || buildUser->getUIDCount() < 65536))
|
||||||
|
throw Error("feature 'uid-range' requires the setting '%s' to be enabled", settings.autoAllocateUids.name);
|
||||||
|
|
||||||
|
/* Create /etc/hosts with localhost entry. */
|
||||||
|
if (derivationType->isSandboxed())
|
||||||
|
writeFile(chrootRootDir + "/etc/hosts", "127.0.0.1 localhost\n::1 localhost\n");
|
||||||
|
|
||||||
|
/* Make the closure of the inputs available in the chroot,
|
||||||
|
rather than the whole Nix store. This prevents any access
|
||||||
|
to undeclared dependencies. Directories are bind-mounted,
|
||||||
|
while other inputs are hard-linked (since only directories
|
||||||
|
can be bind-mounted). !!! As an extra security
|
||||||
|
precaution, make the fake Nix store only writable by the
|
||||||
|
build user. */
|
||||||
|
Path chrootStoreDir = chrootRootDir + worker.store.storeDir;
|
||||||
|
createDirs(chrootStoreDir);
|
||||||
|
chmodPath(chrootStoreDir, 01775);
|
||||||
|
|
||||||
|
if (buildUser && chown(chrootStoreDir.c_str(), 0, buildUser->getGID()) == -1)
|
||||||
|
throw SysError("cannot change ownership of '%1%'", chrootStoreDir);
|
||||||
|
|
||||||
|
for (auto & i : inputPaths) {
|
||||||
|
auto p = worker.store.printStorePath(i);
|
||||||
|
Path r = worker.store.toRealPath(p);
|
||||||
|
pathsInChroot.insert_or_assign(p, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we're repairing, checking or rebuilding part of a
|
||||||
|
multiple-outputs derivation, it's possible that we're
|
||||||
|
rebuilding a path that is in settings.sandbox-paths
|
||||||
|
(typically the dependencies of /bin/sh). Throw them
|
||||||
|
out. */
|
||||||
|
for (auto & i : drv->outputsAndOptPaths(worker.store)) {
|
||||||
|
/* If the name isn't known a priori (i.e. floating
|
||||||
|
content-addressed derivation), the temporary location we use
|
||||||
|
should be fresh. Freshness means it is impossible that the path
|
||||||
|
is already in the sandbox, so we don't need to worry about
|
||||||
|
removing it. */
|
||||||
|
if (i.second.second)
|
||||||
|
pathsInChroot.erase(worker.store.printStorePath(*i.second.second));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cgroup) {
|
||||||
|
if (mkdir(cgroup->c_str(), 0755) != 0)
|
||||||
|
throw SysError("creating cgroup '%s'", *cgroup);
|
||||||
|
chownToBuilder(*cgroup);
|
||||||
|
chownToBuilder(*cgroup + "/cgroup.procs");
|
||||||
|
chownToBuilder(*cgroup + "/cgroup.threads");
|
||||||
|
//chownToBuilder(*cgroup + "/cgroup.subtree_control");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void LinuxLocalDerivationGoal::killSandbox(bool getStats)
|
void LinuxLocalDerivationGoal::killSandbox(bool getStats)
|
||||||
{
|
{
|
||||||
if (cgroup) {
|
if (cgroup) {
|
||||||
|
|
|
@ -42,7 +42,23 @@ public:
|
||||||
using LocalDerivationGoal::LocalDerivationGoal;
|
using LocalDerivationGoal::LocalDerivationGoal;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/**
|
||||||
|
* Create and populate chroot
|
||||||
|
*/
|
||||||
|
void prepareSandbox() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kill all processes by build user, possibly using a reused
|
||||||
|
* cgroup if we have one
|
||||||
|
*/
|
||||||
void killSandbox(bool getStatus) override;
|
void killSandbox(bool getStatus) override;
|
||||||
|
|
||||||
|
|
||||||
|
bool supportsUidRange() override
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue