forked from lix-project/lix
Merge pull request #8965 from Artturin/bindfilesinchroot
Bindmount files instead of hardlinking or copying to chroot
(cherry picked from commit 622191c2b53882a1675fed5066ff8090b4f01827)
Change-Id: I278ec1baacdfa9044992b58fdec8f14d6d7d09ce
This commit is contained in:
parent
8abb20390e
commit
622b0e9428
|
@ -386,27 +386,27 @@ void LocalDerivationGoal::cleanupPostOutputsRegisteredModeNonCheck()
|
||||||
cleanupPostOutputsRegisteredModeCheck();
|
cleanupPostOutputsRegisteredModeCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#if __linux__
|
#if __linux__
|
||||||
static void linkOrCopy(const Path & from, const Path & to)
|
static void doBind(const Path & source, const Path & target, bool optional = false) {
|
||||||
{
|
debug("bind mounting '%1%' to '%2%'", source, target);
|
||||||
if (link(from.c_str(), to.c_str()) == -1) {
|
struct stat st;
|
||||||
/* Hard-linking fails if we exceed the maximum link count on a
|
if (stat(source.c_str(), &st) == -1) {
|
||||||
file (e.g. 32000 of ext3), which is quite possible after a
|
if (optional && errno == ENOENT)
|
||||||
'nix-store --optimise'. FIXME: actually, why don't we just
|
return;
|
||||||
bind-mount in this case?
|
else
|
||||||
|
throw SysError("getting attributes of path '%1%'", source);
|
||||||
It can also fail with EPERM in BeegFS v7 and earlier versions
|
|
||||||
or fail with EXDEV in OpenAFS
|
|
||||||
which don't allow hard-links to other directories */
|
|
||||||
if (errno != EMLINK && errno != EPERM && errno != EXDEV)
|
|
||||||
throw SysError("linking '%s' to '%s'", to, from);
|
|
||||||
copyPath(from, to);
|
|
||||||
}
|
}
|
||||||
}
|
if (S_ISDIR(st.st_mode))
|
||||||
|
createDirs(target);
|
||||||
|
else {
|
||||||
|
createDirs(dirOf(target));
|
||||||
|
writeFile(target, "");
|
||||||
|
}
|
||||||
|
if (mount(source.c_str(), target.c_str(), "", MS_BIND | MS_REC, 0) == -1)
|
||||||
|
throw SysError("bind mount from '%1%' to '%2%' failed", source, target);
|
||||||
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
void LocalDerivationGoal::startBuilder()
|
void LocalDerivationGoal::startBuilder()
|
||||||
{
|
{
|
||||||
if ((buildUser && buildUser->getUIDCount() != 1)
|
if ((buildUser && buildUser->getUIDCount() != 1)
|
||||||
|
@ -581,7 +581,7 @@ void LocalDerivationGoal::startBuilder()
|
||||||
|
|
||||||
/* Allow a user-configurable set of directories from the
|
/* Allow a user-configurable set of directories from the
|
||||||
host file system. */
|
host file system. */
|
||||||
dirsInChroot.clear();
|
pathsInChroot.clear();
|
||||||
|
|
||||||
for (auto i : settings.sandboxPaths.get()) {
|
for (auto i : settings.sandboxPaths.get()) {
|
||||||
if (i.empty()) continue;
|
if (i.empty()) continue;
|
||||||
|
@ -592,19 +592,19 @@ void LocalDerivationGoal::startBuilder()
|
||||||
}
|
}
|
||||||
size_t p = i.find('=');
|
size_t p = i.find('=');
|
||||||
if (p == std::string::npos)
|
if (p == std::string::npos)
|
||||||
dirsInChroot[i] = {i, optional};
|
pathsInChroot[i] = {i, optional};
|
||||||
else
|
else
|
||||||
dirsInChroot[i.substr(0, p)] = {i.substr(p + 1), optional};
|
pathsInChroot[i.substr(0, p)] = {i.substr(p + 1), optional};
|
||||||
}
|
}
|
||||||
if (hasPrefix(worker.store.storeDir, tmpDirInSandbox))
|
if (hasPrefix(worker.store.storeDir, tmpDirInSandbox))
|
||||||
{
|
{
|
||||||
throw Error("`sandbox-build-dir` must not contain the storeDir");
|
throw Error("`sandbox-build-dir` must not contain the storeDir");
|
||||||
}
|
}
|
||||||
dirsInChroot[tmpDirInSandbox] = tmpDir;
|
pathsInChroot[tmpDirInSandbox] = tmpDir;
|
||||||
|
|
||||||
/* Add the closure of store paths to the chroot. */
|
/* Add the closure of store paths to the chroot. */
|
||||||
StorePathSet closure;
|
StorePathSet closure;
|
||||||
for (auto & i : dirsInChroot)
|
for (auto & i : pathsInChroot)
|
||||||
try {
|
try {
|
||||||
if (worker.store.isInStore(i.second.source))
|
if (worker.store.isInStore(i.second.source))
|
||||||
worker.store.computeFSClosure(worker.store.toStorePath(i.second.source).first, closure);
|
worker.store.computeFSClosure(worker.store.toStorePath(i.second.source).first, closure);
|
||||||
|
@ -615,7 +615,7 @@ void LocalDerivationGoal::startBuilder()
|
||||||
}
|
}
|
||||||
for (auto & i : closure) {
|
for (auto & i : closure) {
|
||||||
auto p = worker.store.printStorePath(i);
|
auto p = worker.store.printStorePath(i);
|
||||||
dirsInChroot.insert_or_assign(p, p);
|
pathsInChroot.insert_or_assign(p, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
PathSet allowedPaths = settings.allowedImpureHostPrefixes;
|
PathSet allowedPaths = settings.allowedImpureHostPrefixes;
|
||||||
|
@ -643,7 +643,7 @@ void LocalDerivationGoal::startBuilder()
|
||||||
|
|
||||||
/* Allow files in __impureHostDeps to be missing; e.g.
|
/* Allow files in __impureHostDeps to be missing; e.g.
|
||||||
macOS 11+ has no /usr/lib/libSystem*.dylib */
|
macOS 11+ has no /usr/lib/libSystem*.dylib */
|
||||||
dirsInChroot[i] = {i, true};
|
pathsInChroot[i] = {i, true};
|
||||||
}
|
}
|
||||||
|
|
||||||
#if __linux__
|
#if __linux__
|
||||||
|
@ -711,15 +711,12 @@ void LocalDerivationGoal::startBuilder()
|
||||||
for (auto & i : inputPaths) {
|
for (auto & i : inputPaths) {
|
||||||
auto p = worker.store.printStorePath(i);
|
auto p = worker.store.printStorePath(i);
|
||||||
Path r = worker.store.toRealPath(p);
|
Path r = worker.store.toRealPath(p);
|
||||||
if (S_ISDIR(lstat(r).st_mode))
|
pathsInChroot.insert_or_assign(p, r);
|
||||||
dirsInChroot.insert_or_assign(p, r);
|
|
||||||
else
|
|
||||||
linkOrCopy(r, chrootRootDir + p);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If we're repairing, checking or rebuilding part of a
|
/* If we're repairing, checking or rebuilding part of a
|
||||||
multiple-outputs derivation, it's possible that we're
|
multiple-outputs derivation, it's possible that we're
|
||||||
rebuilding a path that is in settings.dirsInChroot
|
rebuilding a path that is in settings.sandbox-paths
|
||||||
(typically the dependencies of /bin/sh). Throw them
|
(typically the dependencies of /bin/sh). Throw them
|
||||||
out. */
|
out. */
|
||||||
for (auto & i : drv->outputsAndOptPaths(worker.store)) {
|
for (auto & i : drv->outputsAndOptPaths(worker.store)) {
|
||||||
|
@ -729,7 +726,7 @@ void LocalDerivationGoal::startBuilder()
|
||||||
is already in the sandbox, so we don't need to worry about
|
is already in the sandbox, so we don't need to worry about
|
||||||
removing it. */
|
removing it. */
|
||||||
if (i.second.second)
|
if (i.second.second)
|
||||||
dirsInChroot.erase(worker.store.printStorePath(*i.second.second));
|
pathsInChroot.erase(worker.store.printStorePath(*i.second.second));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cgroup) {
|
if (cgroup) {
|
||||||
|
@ -787,9 +784,9 @@ void LocalDerivationGoal::startBuilder()
|
||||||
} else {
|
} else {
|
||||||
auto p = line.find('=');
|
auto p = line.find('=');
|
||||||
if (p == std::string::npos)
|
if (p == std::string::npos)
|
||||||
dirsInChroot[line] = line;
|
pathsInChroot[line] = line;
|
||||||
else
|
else
|
||||||
dirsInChroot[line.substr(0, p)] = line.substr(p + 1);
|
pathsInChroot[line.substr(0, p)] = line.substr(p + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1555,41 +1552,32 @@ void LocalDerivationGoal::addDependency(const StorePath & path)
|
||||||
|
|
||||||
Path source = worker.store.Store::toRealPath(path);
|
Path source = worker.store.Store::toRealPath(path);
|
||||||
Path target = chrootRootDir + worker.store.printStorePath(path);
|
Path target = chrootRootDir + worker.store.printStorePath(path);
|
||||||
debug("bind-mounting %s -> %s", target, source);
|
|
||||||
|
|
||||||
if (pathExists(target))
|
if (pathExists(target))
|
||||||
|
// There is a similar debug message in doBind, so only run it in this block to not have double messages.
|
||||||
|
debug("bind-mounting %s -> %s", target, source);
|
||||||
throw Error("store path '%s' already exists in the sandbox", worker.store.printStorePath(path));
|
throw Error("store path '%s' already exists in the sandbox", worker.store.printStorePath(path));
|
||||||
|
|
||||||
auto st = lstat(source);
|
/* Bind-mount the path into the sandbox. This requires
|
||||||
|
entering its mount namespace, which is not possible
|
||||||
|
in multithreaded programs. So we do this in a
|
||||||
|
child process.*/
|
||||||
|
Pid child(startProcess([&]() {
|
||||||
|
|
||||||
if (S_ISDIR(st.st_mode)) {
|
if (usingUserNamespace && (setns(sandboxUserNamespace.get(), 0) == -1))
|
||||||
|
throw SysError("entering sandbox user namespace");
|
||||||
|
|
||||||
/* Bind-mount the path into the sandbox. This requires
|
if (setns(sandboxMountNamespace.get(), 0) == -1)
|
||||||
entering its mount namespace, which is not possible
|
throw SysError("entering sandbox mount namespace");
|
||||||
in multithreaded programs. So we do this in a
|
|
||||||
child process.*/
|
|
||||||
Pid child(startProcess([&]() {
|
|
||||||
|
|
||||||
if (usingUserNamespace && (setns(sandboxUserNamespace.get(), 0) == -1))
|
doBind(source, target);
|
||||||
throw SysError("entering sandbox user namespace");
|
|
||||||
|
|
||||||
if (setns(sandboxMountNamespace.get(), 0) == -1)
|
_exit(0);
|
||||||
throw SysError("entering sandbox mount namespace");
|
}));
|
||||||
|
|
||||||
createDirs(target);
|
int status = child.wait();
|
||||||
|
if (status != 0)
|
||||||
if (mount(source.c_str(), target.c_str(), "", MS_BIND, 0) == -1)
|
throw Error("could not add path '%s' to sandbox", worker.store.printStorePath(path));
|
||||||
throw SysError("bind mount from '%s' to '%s' failed", source, target);
|
|
||||||
|
|
||||||
_exit(0);
|
|
||||||
}));
|
|
||||||
|
|
||||||
int status = child.wait();
|
|
||||||
if (status != 0)
|
|
||||||
throw Error("could not add path '%s' to sandbox", worker.store.printStorePath(path));
|
|
||||||
|
|
||||||
} else
|
|
||||||
linkOrCopy(source, target);
|
|
||||||
|
|
||||||
#else
|
#else
|
||||||
throw Error("don't know how to make path '%s' (produced by a recursive Nix call) appear in the sandbox",
|
throw Error("don't know how to make path '%s' (produced by a recursive Nix call) appear in the sandbox",
|
||||||
|
@ -1779,7 +1767,7 @@ void LocalDerivationGoal::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;
|
Strings ss;
|
||||||
if (dirsInChroot.find("/dev") == dirsInChroot.end()) {
|
if (pathsInChroot.find("/dev") == pathsInChroot.end()) {
|
||||||
createDirs(chrootRootDir + "/dev/shm");
|
createDirs(chrootRootDir + "/dev/shm");
|
||||||
createDirs(chrootRootDir + "/dev/pts");
|
createDirs(chrootRootDir + "/dev/pts");
|
||||||
ss.push_back("/dev/full");
|
ss.push_back("/dev/full");
|
||||||
|
@ -1814,34 +1802,15 @@ void LocalDerivationGoal::runChild()
|
||||||
ss.push_back(path);
|
ss.push_back(path);
|
||||||
|
|
||||||
if (settings.caFile != "")
|
if (settings.caFile != "")
|
||||||
dirsInChroot.try_emplace("/etc/ssl/certs/ca-certificates.crt", settings.caFile, true);
|
pathsInChroot.try_emplace("/etc/ssl/certs/ca-certificates.crt", settings.caFile, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto & i : ss) dirsInChroot.emplace(i, i);
|
for (auto & i : ss) pathsInChroot.emplace(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. */
|
||||||
auto doBind = [&](const Path & source, const Path & target, bool optional = false) {
|
for (auto & i : pathsInChroot) {
|
||||||
debug("bind mounting '%1%' to '%2%'", source, target);
|
|
||||||
struct stat st;
|
|
||||||
if (stat(source.c_str(), &st) == -1) {
|
|
||||||
if (optional && errno == ENOENT)
|
|
||||||
return;
|
|
||||||
else
|
|
||||||
throw SysError("getting attributes of path '%1%'", source);
|
|
||||||
}
|
|
||||||
if (S_ISDIR(st.st_mode))
|
|
||||||
createDirs(target);
|
|
||||||
else {
|
|
||||||
createDirs(dirOf(target));
|
|
||||||
writeFile(target, "");
|
|
||||||
}
|
|
||||||
if (mount(source.c_str(), target.c_str(), "", MS_BIND | MS_REC, 0) == -1)
|
|
||||||
throw SysError("bind mount from '%1%' to '%2%' failed", source, target);
|
|
||||||
};
|
|
||||||
|
|
||||||
for (auto & i : dirsInChroot) {
|
|
||||||
if (i.second.source == "/proc") continue; // backwards compatibility
|
if (i.second.source == "/proc") continue; // backwards compatibility
|
||||||
|
|
||||||
#if HAVE_EMBEDDED_SANDBOX_SHELL
|
#if HAVE_EMBEDDED_SANDBOX_SHELL
|
||||||
|
@ -1882,7 +1851,7 @@ void LocalDerivationGoal::runChild()
|
||||||
if /dev/ptx/ptmx exists). */
|
if /dev/ptx/ptmx exists). */
|
||||||
if (pathExists("/dev/pts/ptmx") &&
|
if (pathExists("/dev/pts/ptmx") &&
|
||||||
!pathExists(chrootRootDir + "/dev/ptmx")
|
!pathExists(chrootRootDir + "/dev/ptmx")
|
||||||
&& !dirsInChroot.count("/dev/pts"))
|
&& !pathsInChroot.count("/dev/pts"))
|
||||||
{
|
{
|
||||||
if (mount("none", (chrootRootDir + "/dev/pts").c_str(), "devpts", 0, "newinstance,mode=0620") == 0)
|
if (mount("none", (chrootRootDir + "/dev/pts").c_str(), "devpts", 0, "newinstance,mode=0620") == 0)
|
||||||
{
|
{
|
||||||
|
@ -2017,7 +1986,7 @@ void LocalDerivationGoal::runChild()
|
||||||
/* We build the ancestry before adding all inputPaths to the store because we know they'll
|
/* We build the ancestry before adding all inputPaths to the store because we know they'll
|
||||||
all have the same parents (the store), and there might be lots of inputs. This isn't
|
all have the same parents (the store), and there might be lots of inputs. This isn't
|
||||||
particularly efficient... I doubt it'll be a bottleneck in practice */
|
particularly efficient... I doubt it'll be a bottleneck in practice */
|
||||||
for (auto & i : dirsInChroot) {
|
for (auto & i : pathsInChroot) {
|
||||||
Path cur = i.first;
|
Path cur = i.first;
|
||||||
while (cur.compare("/") != 0) {
|
while (cur.compare("/") != 0) {
|
||||||
cur = dirOf(cur);
|
cur = dirOf(cur);
|
||||||
|
@ -2025,7 +1994,7 @@ void LocalDerivationGoal::runChild()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* And we want the store in there regardless of how empty dirsInChroot. We include the innermost
|
/* And we want the store in there regardless of how empty pathsInChroot. We include the innermost
|
||||||
path component this time, since it's typically /nix/store and we care about that. */
|
path component this time, since it's typically /nix/store and we care about that. */
|
||||||
Path cur = worker.store.storeDir;
|
Path cur = worker.store.storeDir;
|
||||||
while (cur.compare("/") != 0) {
|
while (cur.compare("/") != 0) {
|
||||||
|
@ -2036,7 +2005,7 @@ void LocalDerivationGoal::runChild()
|
||||||
/* Add all our input paths to the chroot */
|
/* Add all our input paths to the chroot */
|
||||||
for (auto & i : inputPaths) {
|
for (auto & i : inputPaths) {
|
||||||
auto p = worker.store.printStorePath(i);
|
auto p = worker.store.printStorePath(i);
|
||||||
dirsInChroot[p] = p;
|
pathsInChroot[p] = p;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Violations will go to the syslog if you set this. Unfortunately the destination does not appear to be configurable */
|
/* Violations will go to the syslog if you set this. Unfortunately the destination does not appear to be configurable */
|
||||||
|
@ -2067,7 +2036,7 @@ void LocalDerivationGoal::runChild()
|
||||||
without file-write* allowed, access() incorrectly returns EPERM
|
without file-write* allowed, access() incorrectly returns EPERM
|
||||||
*/
|
*/
|
||||||
sandboxProfile += "(allow file-read* file-write* process-exec\n";
|
sandboxProfile += "(allow file-read* file-write* process-exec\n";
|
||||||
for (auto & i : dirsInChroot) {
|
for (auto & i : pathsInChroot) {
|
||||||
if (i.first != i.second.source)
|
if (i.first != i.second.source)
|
||||||
throw Error(
|
throw Error(
|
||||||
"can't map '%1%' to '%2%': mismatched impure paths not supported on Darwin",
|
"can't map '%1%' to '%2%': mismatched impure paths not supported on Darwin",
|
||||||
|
|
|
@ -86,8 +86,8 @@ struct LocalDerivationGoal : public DerivationGoal
|
||||||
: source(source), optional(optional)
|
: source(source), optional(optional)
|
||||||
{ }
|
{ }
|
||||||
};
|
};
|
||||||
typedef map<Path, ChrootPath> DirsInChroot; // maps target path to source path
|
typedef map<Path, ChrootPath> PathsInChroot; // maps target path to source path
|
||||||
DirsInChroot dirsInChroot;
|
PathsInChroot pathsInChroot;
|
||||||
|
|
||||||
typedef map<std::string, std::string> Environment;
|
typedef map<std::string, std::string> Environment;
|
||||||
Environment env;
|
Environment env;
|
||||||
|
|
Loading…
Reference in a new issue