Merge pull request #4121 from NixOS/no-user-namespace

Support user namespaces being disabled
This commit is contained in:
Eelco Dolstra 2020-10-08 09:01:57 +02:00 committed by GitHub
commit 54f4500457
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -831,6 +831,10 @@ private:
paths to the sandbox as a result of recursive Nix calls. */ paths to the sandbox as a result of recursive Nix calls. */
AutoCloseFD sandboxMountNamespace; AutoCloseFD sandboxMountNamespace;
/* On Linux, whether we're doing the build in its own user
namespace. */
bool usingUserNamespace = true;
/* The build hook. */ /* The build hook. */
std::unique_ptr<HookInstance> hook; std::unique_ptr<HookInstance> hook;
@ -920,8 +924,8 @@ private:
result. */ result. */
std::map<Path, ValidPathInfo> prevInfos; std::map<Path, ValidPathInfo> prevInfos;
const uid_t sandboxUid = 1000; uid_t sandboxUid() { return usingUserNamespace ? 1000 : buildUser->getUID(); }
const gid_t sandboxGid = 100; gid_t sandboxGid() { return usingUserNamespace ? 100 : buildUser->getGID(); }
const static Path homeDir; const static Path homeDir;
@ -2424,15 +2428,14 @@ void DerivationGoal::startBuilder()
"root:x:0:0:Nix build user:%3%:/noshell\n" "root:x:0:0:Nix build user:%3%:/noshell\n"
"nixbld:x:%1%:%2%:Nix build user:%3%:/noshell\n" "nixbld:x:%1%:%2%:Nix build user:%3%:/noshell\n"
"nobody:x:65534:65534:Nobody:/:/noshell\n", "nobody:x:65534:65534:Nobody:/:/noshell\n",
sandboxUid, sandboxGid, settings.sandboxBuildDir)); sandboxUid(), sandboxGid(), settings.sandboxBuildDir));
/* 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( fmt("root:x:0:\n"
"root:x:0:\n"
"nixbld:!:%1%:\n" "nixbld:!:%1%:\n"
"nogroup:x:65534:\n") % sandboxGid).str()); "nogroup:x:65534:\n", sandboxGid()));
/* Create /etc/hosts with localhost entry. */ /* Create /etc/hosts with localhost entry. */
if (!(derivationIsImpure(derivationType))) if (!(derivationIsImpure(derivationType)))
@ -2629,6 +2632,13 @@ void DerivationGoal::startBuilder()
options.allowVfork = false; options.allowVfork = false;
Path maxUserNamespaces = "/proc/sys/user/max_user_namespaces";
static bool userNamespacesEnabled =
pathExists(maxUserNamespaces)
&& trim(readFile(maxUserNamespaces)) != "0";
usingUserNamespace = userNamespacesEnabled;
Pid helper = startProcess([&]() { Pid helper = startProcess([&]() {
/* Drop additional groups here because we can't do it /* Drop additional groups here because we can't do it
@ -2647,9 +2657,11 @@ void DerivationGoal::startBuilder()
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_NEWUSER | CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWIPC | CLONE_NEWUTS | CLONE_PARENT | SIGCHLD; int flags = CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWIPC | CLONE_NEWUTS | CLONE_PARENT | SIGCHLD;
if (privateNetwork) if (privateNetwork)
flags |= CLONE_NEWNET; flags |= CLONE_NEWNET;
if (usingUserNamespace)
flags |= CLONE_NEWUSER;
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) {
@ -2658,11 +2670,12 @@ void DerivationGoal::startBuilder()
flags &= ~CLONE_NEWPID; flags &= ~CLONE_NEWPID;
child = clone(childEntry, stack + stackSize, flags, this); child = clone(childEntry, stack + stackSize, flags, this);
} }
if (child == -1 && (errno == EPERM || errno == EINVAL)) { if (usingUserNamespace && child == -1 && (errno == EPERM || errno == EINVAL)) {
/* Some distros patch Linux to not allow unprivileged /* Some distros patch Linux to not allow unprivileged
* user namespaces. If we get EPERM or EINVAL, try * user namespaces. If we get EPERM or EINVAL, try
* without CLONE_NEWUSER and see if that works. * without CLONE_NEWUSER and see if that works.
*/ */
usingUserNamespace = false;
flags &= ~CLONE_NEWUSER; flags &= ~CLONE_NEWUSER;
child = clone(childEntry, stack + stackSize, flags, this); child = clone(childEntry, stack + stackSize, flags, this);
} }
@ -2673,7 +2686,8 @@ void DerivationGoal::startBuilder()
_exit(1); _exit(1);
if (child == -1) throw SysError("cloning builder process"); if (child == -1) throw SysError("cloning builder process");
writeFull(builderOut.writeSide.get(), std::to_string(child) + "\n"); writeFull(builderOut.writeSide.get(),
fmt("%d %d\n", usingUserNamespace, child));
_exit(0); _exit(0);
}, options); }, options);
@ -2694,9 +2708,13 @@ void DerivationGoal::startBuilder()
}); });
pid_t tmp; pid_t tmp;
if (!string2Int<pid_t>(readLine(builderOut.readSide.get()), tmp)) abort(); auto ss = tokenizeString<std::vector<std::string>>(readLine(builderOut.readSide.get()));
assert(ss.size() == 2);
usingUserNamespace = ss[0] == "1";
if (!string2Int<pid_t>(ss[1], tmp)) abort();
pid = tmp; pid = tmp;
if (usingUserNamespace) {
/* Set the UID/GID mapping of the builder's user namespace /* Set the UID/GID mapping of the builder's user namespace
such that the sandbox user maps to the build user, or to such that the sandbox user maps to the build user, or to
the calling user (if build users are disabled). */ the calling user (if build users are disabled). */
@ -2704,12 +2722,17 @@ void DerivationGoal::startBuilder()
uid_t hostGid = buildUser ? buildUser->getGID() : getgid(); uid_t hostGid = buildUser ? buildUser->getGID() : getgid();
writeFile("/proc/" + std::to_string(pid) + "/uid_map", writeFile("/proc/" + std::to_string(pid) + "/uid_map",
(format("%d %d 1") % sandboxUid % hostUid).str()); fmt("%d %d 1", sandboxUid(), hostUid));
writeFile("/proc/" + std::to_string(pid) + "/setgroups", "deny"); writeFile("/proc/" + std::to_string(pid) + "/setgroups", "deny");
writeFile("/proc/" + std::to_string(pid) + "/gid_map", writeFile("/proc/" + std::to_string(pid) + "/gid_map",
(format("%d %d 1") % sandboxGid % hostGid).str()); fmt("%d %d 1", sandboxGid(), hostGid));
} else {
debug("note: not using a user namespace");
if (!buildUser)
throw Error("cannot perform a sandboxed build because user namespaces are not enabled; check /proc/sys/user/max_user_namespaces");
}
/* Save the mount namespace of the child. We have to do this /* Save the mount namespace of the child. We have to do this
*before* the child does a chroot. */ *before* the child does a chroot. */
@ -2745,7 +2768,7 @@ void DerivationGoal::startBuilder()
ex.addTrace({}, "while setting up the build environment"); ex.addTrace({}, "while setting up the build environment");
throw ex; throw ex;
} }
debug(msg); debug("sandbox setup: " + msg);
} }
} }
@ -3569,9 +3592,9 @@ void DerivationGoal::runChild()
/* Switch to the sandbox uid/gid in the user namespace, /* Switch to the sandbox uid/gid in the user namespace,
which corresponds to the build user or calling user in which corresponds to the build user or calling user in
the parent namespace. */ the parent namespace. */
if (setgid(sandboxGid) == -1) if (setgid(sandboxGid()) == -1)
throw SysError("setgid failed"); throw SysError("setgid failed");
if (setuid(sandboxUid) == -1) if (setuid(sandboxUid()) == -1)
throw SysError("setuid failed"); throw SysError("setuid failed");
setUser = false; setUser = false;