Don't run builds as root in the user namespace

This largely reverts c68e5913c7. Running
builds as root breaks "cp -p", since when running as root, "cp -p"
assumes that it can succesfully chown() files. But that's not actually
the case since the user namespace doesn't provide a complete uid
mapping. So it barfs with a fatal error message ("cp: failed to
preserve ownership for 'foo': Invalid argument").
This commit is contained in:
Eelco Dolstra 2016-09-08 18:16:23 +02:00
parent e4bdd49022
commit ff0c0b645c

View file

@ -804,6 +804,9 @@ private:
result. */ result. */
ValidPathInfos prevInfos; ValidPathInfos prevInfos;
const uid_t sandboxUid = 1000;
const gid_t sandboxGid = 100;
public: public:
DerivationGoal(const Path & drvPath, const StringSet & wantedOutputs, DerivationGoal(const Path & drvPath, const StringSet & wantedOutputs,
Worker & worker, BuildMode buildMode = bmNormal); Worker & worker, BuildMode buildMode = bmNormal);
@ -1941,14 +1944,18 @@ 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" "root:x:0:0:Nix build user:/:/noshell\n"
"nobody:x:65534:65534:Nobody:/:/noshell\n"); "nixbld:x:%1%:%2%:Nix build user:/:/noshell\n"
"nobody:x:65534:65534:Nobody:/:/noshell\n") % sandboxUid % sandboxGid).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(
"root:x:0:\n" "root:x:0:\n"
"nobody:x:65534:\n"); "nixbld:!:%1%:\n"
"nogroup:x:65534:\n") % sandboxGid).str());
/* Create /etc/hosts with localhost entry. */ /* Create /etc/hosts with localhost entry. */
if (!fixedOutput) if (!fixedOutput)
@ -2130,7 +2137,12 @@ void DerivationGoal::startBuilder()
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
after we've created the new user namespace. */ after we've created the new user namespace. FIXME:
this means that if we're not root in the parent
namespace, we can't drop additional groups; they will
be mapped to nogroup in the child namespace. There does
not seem to be a workaround for this. (But who can tell
from reading user_namespaces(7)?)*/
if (getuid() == 0 && setgroups(0, 0) == -1) if (getuid() == 0 && setgroups(0, 0) == -1)
throw SysError("setgroups failed"); throw SysError("setgroups failed");
@ -2163,19 +2175,19 @@ void DerivationGoal::startBuilder()
if (!string2Int<pid_t>(readLine(builderOut.readSide.get()), tmp)) abort(); if (!string2Int<pid_t>(readLine(builderOut.readSide.get()), tmp)) abort();
pid = tmp; pid = tmp;
/* Set the UID/GID mapping of the builder's user /* Set the UID/GID mapping of the builder's user namespace
namespace such that root maps to the build user, or to the such that the sandbox user maps to the build user, or to
calling user (if build users are disabled). */ the calling user (if build users are disabled). */
uid_t targetUid = buildUser.enabled() ? buildUser.getUID() : getuid(); uid_t hostUid = buildUser.enabled() ? buildUser.getUID() : getuid();
uid_t targetGid = buildUser.enabled() ? buildUser.getGID() : getgid(); uid_t hostGid = buildUser.enabled() ? buildUser.getGID() : getgid();
writeFile("/proc/" + std::to_string(pid) + "/uid_map", writeFile("/proc/" + std::to_string(pid) + "/uid_map",
(format("0 %d 1") % targetUid).str()); (format("%d %d 1") % sandboxUid % hostUid).str());
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("0 %d 1") % targetGid).str()); (format("%d %d 1") % sandboxGid % hostGid).str());
/* Signal the builder that we've updated its user /* Signal the builder that we've updated its user
namespace. */ namespace. */
@ -2341,6 +2353,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 0
if (pathExists("/dev/pts/ptmx") && if (pathExists("/dev/pts/ptmx") &&
!pathExists(chrootRootDir + "/dev/ptmx") !pathExists(chrootRootDir + "/dev/ptmx")
&& dirsInChroot.find("/dev/pts") == dirsInChroot.end()) && dirsInChroot.find("/dev/pts") == dirsInChroot.end())
@ -2353,6 +2366,7 @@ void DerivationGoal::runChild()
Linux versions, it is created with permissions 0. */ Linux versions, it is created with permissions 0. */
chmod_(chrootRootDir + "/dev/pts/ptmx", 0666); chmod_(chrootRootDir + "/dev/pts/ptmx", 0666);
} }
#endif
/* Do the chroot(). */ /* Do the chroot(). */
if (chdir(chrootRootDir.c_str()) == -1) if (chdir(chrootRootDir.c_str()) == -1)
@ -2373,11 +2387,12 @@ 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 /* Switch to the sandbox uid/gid in the user namespace,
the build user or calling user in the parent namespace. */ which corresponds to the build user or calling user in
if (setgid(0) == -1) the parent namespace. */
if (setgid(sandboxGid) == -1)
throw SysError("setgid failed"); throw SysError("setgid failed");
if (setuid(0) == -1) if (setuid(sandboxUid) == -1)
throw SysError("setuid failed"); throw SysError("setuid failed");
setUser = false; setUser = false;