aboutsummaryrefslogtreecommitdiff
path: root/src/libstore/build/local-derivation-goal.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/libstore/build/local-derivation-goal.cc')
-rw-r--r--src/libstore/build/local-derivation-goal.cc103
1 files changed, 76 insertions, 27 deletions
diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc
index 5cea3b590..64540d262 100644
--- a/src/libstore/build/local-derivation-goal.cc
+++ b/src/libstore/build/local-derivation-goal.cc
@@ -158,28 +158,21 @@ void LocalDerivationGoal::tryLocalBuild() {
return;
}
- /* If `build-users-group' is not empty, then we have to build as
- one of the members of that group. */
- if (settings.buildUsersGroup != "" && getuid() == 0) {
-#if defined(__linux__) || defined(__APPLE__)
- if (!buildUser) buildUser = std::make_unique<UserLock>();
-
- if (buildUser->findFreeUser()) {
- /* Make sure that no other processes are executing under this
- uid. */
- buildUser->kill();
- } else {
+ if (useBuildUsers()) {
+ if (!buildUser)
+ buildUser = acquireUserLock();
+
+ if (!buildUser) {
if (!actLock)
actLock = std::make_unique<Activity>(*logger, lvlWarn, actBuildWaiting,
fmt("waiting for UID to build '%s'", yellowtxt(worker.store.printStorePath(drvPath))));
worker.waitForAWhile(shared_from_this());
return;
}
-#else
- /* Don't know how to block the creation of setuid/setgid
- binaries on this platform. */
- throw Error("build users are not supported on this platform for security reasons");
-#endif
+
+ /* Make sure that no other processes are executing under this
+ uid. */
+ buildUser->kill();
}
actLock.reset();
@@ -502,6 +495,9 @@ void LocalDerivationGoal::startBuilder()
}
}
+ useUidRange = parsedDrv->getRequiredSystemFeatures().count("uid-range");
+ useSystemdCgroup = parsedDrv->getRequiredSystemFeatures().count("Systemd-cgroup");
+
if (useChroot) {
/* Allow a user-configurable set of directories from the
@@ -580,10 +576,11 @@ void LocalDerivationGoal::startBuilder()
printMsg(lvlChatty, format("setting up chroot environment in '%1%'") % chrootRootDir);
- if (mkdir(chrootRootDir.c_str(), 0750) == -1)
+ if (mkdir(chrootRootDir.c_str(), useUidRange ? 0755 : 0750) == -1)
throw SysError("cannot create '%1%'", chrootRootDir);
- if (buildUser && chown(chrootRootDir.c_str(), 0, buildUser->getGID()) == -1)
+ // FIXME: only make root writable for user namespace builds.
+ if (buildUser && chown(chrootRootDir.c_str(), buildUser->getUID(), buildUser->getGID()) == -1)
throw SysError("cannot change ownership of '%1%'", chrootRootDir);
/* Create a writable /tmp in the chroot. Many builders need
@@ -597,6 +594,10 @@ void LocalDerivationGoal::startBuilder()
nobody account. The latter is kind of a hack to support
Samba-in-QEMU. */
createDirs(chrootRootDir + "/etc");
+ chownToBuilder(chrootRootDir + "/etc");
+
+ if (useUidRange && (!buildUser || buildUser->getUIDCount() < 65536))
+ throw Error("feature 'uid-range' requires '%s' to be enabled", settings.autoAllocateUids.name);
/* Declare the build user's group so that programs get a consistent
view of the system (e.g., "id -gn"). */
@@ -647,12 +648,32 @@ void LocalDerivationGoal::startBuilder()
dirsInChroot.erase(worker.store.printStorePath(*i.second.second));
}
-#elif __APPLE__
- /* We don't really have any parent prep work to do (yet?)
- All work happens in the child, instead. */
+ if (useSystemdCgroup) {
+ settings.requireExperimentalFeature(Xp::SystemdCgroup);
+ std::optional<Path> cgroup;
+ if (!buildUser || !(cgroup = buildUser->getCgroup()))
+ throw Error("feature 'systemd-cgroup' requires 'auto-allocate-uids = true' in nix.conf");
+ chownToBuilder(*cgroup);
+ chownToBuilder(*cgroup + "/cgroup.procs");
+ }
+
#else
- throw Error("sandboxing builds is not supported on this platform");
+ if (useUidRange)
+ throw Error("feature 'uid-range' is not supported on this platform");
+ if (useSystemdCgroup)
+ throw Error("feature 'systemd-cgroup' is not supported on this platform");
+ #if __APPLE__
+ /* We don't really have any parent prep work to do (yet?)
+ All work happens in the child, instead. */
+ #else
+ throw Error("sandboxing builds is not supported on this platform");
+ #endif
#endif
+ } else {
+ if (useUidRange)
+ throw Error("feature 'uid-range' is only supported in sandboxed builds");
+ if (useSystemdCgroup)
+ throw Error("feature 'systemd-cgroup' is only supported in sandboxed builds");
}
if (needsHashRewrite() && pathExists(homeDir))
@@ -913,14 +934,16 @@ void LocalDerivationGoal::startBuilder()
the calling user (if build users are disabled). */
uid_t hostUid = buildUser ? buildUser->getUID() : getuid();
uid_t hostGid = buildUser ? buildUser->getGID() : getgid();
+ uint32_t nrIds = buildUser && useUidRange ? buildUser->getUIDCount() : 1;
writeFile("/proc/" + std::to_string(pid) + "/uid_map",
- fmt("%d %d 1", sandboxUid(), hostUid));
+ fmt("%d %d %d", sandboxUid(), hostUid, nrIds));
- writeFile("/proc/" + std::to_string(pid) + "/setgroups", "deny");
+ if (!useUidRange)
+ writeFile("/proc/" + std::to_string(pid) + "/setgroups", "deny");
writeFile("/proc/" + std::to_string(pid) + "/gid_map",
- fmt("%d %d 1", sandboxGid(), hostGid));
+ fmt("%d %d %d", sandboxGid(), hostGid, nrIds));
} else {
debug("note: not using a user namespace");
if (!buildUser)
@@ -947,6 +970,12 @@ void LocalDerivationGoal::startBuilder()
throw SysError("getting sandbox user namespace");
}
+ /* Move the child into its own cgroup. */
+ if (buildUser) {
+ if (auto cgroup = buildUser->getCgroup())
+ writeFile(*cgroup + "/cgroup.procs", fmt("%d", (pid_t) pid));
+ }
+
/* Signal the builder that we've updated its user namespace. */
writeFull(userNamespaceSync.writeSide.get(), "1");
@@ -1763,6 +1792,13 @@ void LocalDerivationGoal::runChild()
if (mount("none", (chrootRootDir + "/proc").c_str(), "proc", 0, 0) == -1)
throw SysError("mounting /proc");
+ /* Mount sysfs on /sys. */
+ if (useUidRange) {
+ createDirs(chrootRootDir + "/sys");
+ if (mount("none", (chrootRootDir + "/sys").c_str(), "sysfs", 0, 0) == -1)
+ throw SysError("mounting /sys");
+ }
+
/* Mount a new tmpfs on /dev/shm to ensure that whatever
the builder puts in /dev/shm is cleaned up automatically. */
if (pathExists("/dev/shm") && mount("none", (chrootRootDir + "/dev/shm").c_str(), "tmpfs", 0,
@@ -1805,6 +1841,12 @@ void LocalDerivationGoal::runChild()
if (unshare(CLONE_NEWNS) == -1)
throw SysError("unsharing mount namespace");
+ /* Unshare the cgroup namespace. This means
+ /proc/self/cgroup will show the child's cgroup as '/'
+ rather than whatever it is in the parent. */
+ if (useSystemdCgroup && unshare(CLONE_NEWCGROUP) == -1)
+ throw SysError("unsharing cgroup namespace");
+
/* Do the chroot(). */
if (chdir(chrootRootDir.c_str()) == -1)
throw SysError("cannot change directory to '%1%'", chrootRootDir);
@@ -2221,7 +2263,10 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
/* Canonicalise first. This ensures that the path we're
rewriting doesn't contain a hard link to /etc/shadow or
something like that. */
- canonicalisePathMetaData(actualPath, buildUser ? buildUser->getUID() : -1, inodesSeen);
+ canonicalisePathMetaData(
+ actualPath,
+ buildUser ? std::optional(buildUser->getUIDRange()) : std::nullopt,
+ inodesSeen);
debug("scanning for references for output '%s' in temp location '%s'", outputName, actualPath);
@@ -2314,6 +2359,10 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
sink.s = rewriteStrings(sink.s, outputRewrites);
StringSource source(sink.s);
restorePath(actualPath, source);
+
+ /* FIXME: set proper permissions in restorePath() so
+ we don't have to do another traversal. */
+ canonicalisePathMetaData(actualPath, {}, inodesSeen);
}
};
@@ -2476,7 +2525,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
/* FIXME: set proper permissions in restorePath() so
we don't have to do another traversal. */
- canonicalisePathMetaData(actualPath, -1, inodesSeen);
+ canonicalisePathMetaData(actualPath, {}, inodesSeen);
/* Calculate where we'll move the output files. In the checking case we
will leave leave them where they are, for now, rather than move to