Run builds in a user namespace
This way, all builds appear to have a uid/gid of 0 inside the chroot. In the future, this may allow using programs like systemd-nspawn inside builds, but that will require assigning a larger UID/GID map to the build. Issue #625.
This commit is contained in:
parent
202683a4fc
commit
c68e5913c7
|
@ -746,6 +746,9 @@ private:
|
||||||
/* Pipe for the builder's standard output/error. */
|
/* Pipe for the builder's standard output/error. */
|
||||||
Pipe builderOut;
|
Pipe builderOut;
|
||||||
|
|
||||||
|
/* Pipe for synchronising updates to the builder user namespace. */
|
||||||
|
Pipe userNamespaceSync;
|
||||||
|
|
||||||
/* The build hook. */
|
/* The build hook. */
|
||||||
std::shared_ptr<HookInstance> hook;
|
std::shared_ptr<HookInstance> hook;
|
||||||
|
|
||||||
|
@ -1930,17 +1933,14 @@ void DerivationGoal::startBuilder()
|
||||||
createDirs(chrootRootDir + "/etc");
|
createDirs(chrootRootDir + "/etc");
|
||||||
|
|
||||||
writeFile(chrootRootDir + "/etc/passwd",
|
writeFile(chrootRootDir + "/etc/passwd",
|
||||||
(format(
|
"root:x:0:0:Nix build user:/:/noshell\n"
|
||||||
"nixbld:x:%1%:%2%:Nix build user:/:/noshell\n"
|
"nobody:x:65534:65534:Nobody:/:/noshell\n");
|
||||||
"nobody:x:65534:65534:Nobody:/:/noshell\n")
|
|
||||||
% (buildUser.enabled() ? buildUser.getUID() : getuid())
|
|
||||||
% (buildUser.enabled() ? buildUser.getGID() : getgid())).str());
|
|
||||||
|
|
||||||
/* Declare the build user's group so that programs get a consistent
|
/* Declare the build user's group so that programs get a consistent
|
||||||
view of the system (e.g., "id -gn"). */
|
view of the system (e.g., "id -gn"). */
|
||||||
writeFile(chrootRootDir + "/etc/group",
|
writeFile(chrootRootDir + "/etc/group",
|
||||||
(format("nixbld:!:%1%:\n")
|
"root:x:0:\n"
|
||||||
% (buildUser.enabled() ? buildUser.getGID() : getgid())).str());
|
"nobody:x:65534:\n");
|
||||||
|
|
||||||
/* Create /etc/hosts with localhost entry. */
|
/* Create /etc/hosts with localhost entry. */
|
||||||
if (!fixedOutput)
|
if (!fixedOutput)
|
||||||
|
@ -2114,32 +2114,66 @@ void DerivationGoal::startBuilder()
|
||||||
if (!fixedOutput)
|
if (!fixedOutput)
|
||||||
privateNetwork = true;
|
privateNetwork = true;
|
||||||
|
|
||||||
|
userNamespaceSync.create();
|
||||||
|
|
||||||
ProcessOptions options;
|
ProcessOptions options;
|
||||||
options.allowVfork = false;
|
options.allowVfork = false;
|
||||||
|
|
||||||
Pid helper = startProcess([&]() {
|
Pid helper = startProcess([&]() {
|
||||||
|
|
||||||
|
/* Drop additional groups here because we can't do it
|
||||||
|
after we've created the new user namespace. */
|
||||||
|
if (getuid() == 0 && setgroups(0, 0) == -1)
|
||||||
|
throw SysError("setgroups failed");
|
||||||
|
|
||||||
size_t stackSize = 1 * 1024 * 1024;
|
size_t stackSize = 1 * 1024 * 1024;
|
||||||
char * stack = (char *) mmap(0, stackSize,
|
char * stack = (char *) mmap(0, stackSize,
|
||||||
PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
|
PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
|
||||||
if (stack == MAP_FAILED) throw SysError("allocating stack");
|
if (stack == MAP_FAILED) throw SysError("allocating stack");
|
||||||
int flags = CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWIPC | CLONE_NEWUTS | CLONE_PARENT | SIGCHLD;
|
|
||||||
if (getuid() != 0)
|
int flags = CLONE_NEWUSER | CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWIPC | CLONE_NEWUTS | CLONE_PARENT | SIGCHLD;
|
||||||
flags |= CLONE_NEWUSER;
|
|
||||||
if (privateNetwork)
|
if (privateNetwork)
|
||||||
flags |= CLONE_NEWNET;
|
flags |= CLONE_NEWNET;
|
||||||
|
|
||||||
pid_t child = clone(childEntry, stack + stackSize, flags, this);
|
pid_t child = clone(childEntry, stack + stackSize, flags, this);
|
||||||
if (child == -1 && errno == EINVAL)
|
if (child == -1 && errno == EINVAL)
|
||||||
/* Fallback for Linux < 2.13 where CLONE_NEWPID and
|
/* Fallback for Linux < 2.13 where CLONE_NEWPID and
|
||||||
CLONE_PARENT are not allowed together. */
|
CLONE_PARENT are not allowed together. */
|
||||||
child = clone(childEntry, stack + stackSize, flags & ~CLONE_NEWPID, this);
|
child = clone(childEntry, stack + stackSize, flags & ~CLONE_NEWPID, this);
|
||||||
if (child == -1) throw SysError("cloning builder process");
|
if (child == -1) throw SysError("cloning builder process");
|
||||||
|
|
||||||
writeFull(builderOut.writeSide, std::to_string(child) + "\n");
|
writeFull(builderOut.writeSide, std::to_string(child) + "\n");
|
||||||
_exit(0);
|
_exit(0);
|
||||||
}, options);
|
}, options);
|
||||||
|
|
||||||
if (helper.wait(true) != 0)
|
if (helper.wait(true) != 0)
|
||||||
throw Error("unable to start build process");
|
throw Error("unable to start build process");
|
||||||
|
|
||||||
|
userNamespaceSync.readSide.close();
|
||||||
|
|
||||||
pid_t tmp;
|
pid_t tmp;
|
||||||
if (!string2Int<pid_t>(readLine(builderOut.readSide), tmp)) abort();
|
if (!string2Int<pid_t>(readLine(builderOut.readSide), tmp)) abort();
|
||||||
pid = tmp;
|
pid = tmp;
|
||||||
|
|
||||||
|
/* Set the UID/GID mapping of the builder's user
|
||||||
|
namespace such that root maps to the build user, or to the
|
||||||
|
calling user (if build users are disabled). */
|
||||||
|
uid_t targetUid = buildUser.enabled() ? buildUser.getUID() : getuid();
|
||||||
|
uid_t targetGid = buildUser.enabled() ? buildUser.getGID() : getgid();
|
||||||
|
|
||||||
|
writeFile("/proc/" + std::to_string(pid) + "/uid_map",
|
||||||
|
(format("0 %d 1") % targetUid).str());
|
||||||
|
|
||||||
|
writeFile("/proc/" + std::to_string(pid) + "/setgroups", "deny");
|
||||||
|
|
||||||
|
writeFile("/proc/" + std::to_string(pid) + "/gid_map",
|
||||||
|
(format("0 %d 1") % targetGid).str());
|
||||||
|
|
||||||
|
/* Signal the builder that we've updated its user
|
||||||
|
namespace. */
|
||||||
|
writeFull(userNamespaceSync.writeSide, "1");
|
||||||
|
userNamespaceSync.writeSide.close();
|
||||||
|
|
||||||
} else
|
} else
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
|
@ -2176,9 +2210,18 @@ void DerivationGoal::runChild()
|
||||||
|
|
||||||
commonChildInit(builderOut);
|
commonChildInit(builderOut);
|
||||||
|
|
||||||
|
bool setUser = true;
|
||||||
|
|
||||||
#if __linux__
|
#if __linux__
|
||||||
if (useChroot) {
|
if (useChroot) {
|
||||||
|
|
||||||
|
userNamespaceSync.writeSide.close();
|
||||||
|
|
||||||
|
if (drainFD(userNamespaceSync.readSide) != "1")
|
||||||
|
throw Error("user namespace initialisation failed");
|
||||||
|
|
||||||
|
userNamespaceSync.readSide.close();
|
||||||
|
|
||||||
if (privateNetwork) {
|
if (privateNetwork) {
|
||||||
|
|
||||||
/* Initialise the loopback interface. */
|
/* Initialise the loopback interface. */
|
||||||
|
@ -2292,8 +2335,7 @@ void DerivationGoal::runChild()
|
||||||
requires the kernel to be compiled with
|
requires the kernel to be compiled with
|
||||||
CONFIG_DEVPTS_MULTIPLE_INSTANCES=y (which is the case
|
CONFIG_DEVPTS_MULTIPLE_INSTANCES=y (which is the case
|
||||||
if /dev/ptx/ptmx exists). */
|
if /dev/ptx/ptmx exists). */
|
||||||
if (getuid() == 0 &&
|
if (pathExists("/dev/pts/ptmx") &&
|
||||||
pathExists("/dev/pts/ptmx") &&
|
|
||||||
!pathExists(chrootRootDir + "/dev/ptmx")
|
!pathExists(chrootRootDir + "/dev/ptmx")
|
||||||
&& dirsInChroot.find("/dev/pts") == dirsInChroot.end())
|
&& dirsInChroot.find("/dev/pts") == dirsInChroot.end())
|
||||||
{
|
{
|
||||||
|
@ -2324,6 +2366,15 @@ void DerivationGoal::runChild()
|
||||||
|
|
||||||
if (rmdir("real-root") == -1)
|
if (rmdir("real-root") == -1)
|
||||||
throw SysError("cannot remove real-root directory");
|
throw SysError("cannot remove real-root directory");
|
||||||
|
|
||||||
|
/* Become root in the user namespace, which corresponds to
|
||||||
|
the build user or calling user in the parent namespace. */
|
||||||
|
if (setgid(0) == -1)
|
||||||
|
throw SysError("setgid failed");
|
||||||
|
if (setuid(0) == -1)
|
||||||
|
throw SysError("setuid failed");
|
||||||
|
|
||||||
|
setUser = false;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -2375,7 +2426,7 @@ void DerivationGoal::runChild()
|
||||||
descriptors except std*, so that's safe. Also note that
|
descriptors except std*, so that's safe. Also note that
|
||||||
setuid() when run as root sets the real, effective and
|
setuid() when run as root sets the real, effective and
|
||||||
saved UIDs. */
|
saved UIDs. */
|
||||||
if (buildUser.enabled()) {
|
if (setUser && buildUser.enabled()) {
|
||||||
/* Preserve supplementary groups of the build user, to allow
|
/* Preserve supplementary groups of the build user, to allow
|
||||||
admins to specify groups such as "kvm". */
|
admins to specify groups such as "kvm". */
|
||||||
if (!buildUser.getSupplementaryGIDs().empty() &&
|
if (!buildUser.getSupplementaryGIDs().empty() &&
|
||||||
|
@ -2819,7 +2870,7 @@ void DerivationGoal::registerOutputs()
|
||||||
format("output ‘%1%’ of ‘%2%’ differs from previous round")
|
format("output ‘%1%’ of ‘%2%’ differs from previous round")
|
||||||
% i->path % drvPath);
|
% i->path % drvPath);
|
||||||
}
|
}
|
||||||
assert(false); // shouldn't happen
|
abort(); // shouldn't happen
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.keepFailed) {
|
if (settings.keepFailed) {
|
||||||
|
|
Loading…
Reference in a new issue