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,22 +2708,31 @@ 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;
/* Set the UID/GID mapping of the builder's user namespace if (usingUserNamespace) {
such that the sandbox user maps to the build user, or to /* Set the UID/GID mapping of the builder's user namespace
the calling user (if build users are disabled). */ such that the sandbox user maps to the build user, or to
uid_t hostUid = buildUser ? buildUser->getUID() : getuid(); the calling user (if build users are disabled). */
uid_t hostGid = buildUser ? buildUser->getGID() : getgid(); uid_t hostUid = buildUser ? buildUser->getUID() : getuid();
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;