diff --git a/nix.conf.example b/nix.conf.example index 47973ce4e7..a75045b148 100644 --- a/nix.conf.example +++ b/nix.conf.example @@ -78,37 +78,46 @@ #build-max-jobs = 1 -### Option `build-allow-root' -# -# This option controls Nix's behaviour when it is invoked under the -# `root' user (or setuid-root). If `true' (default), builds are -# performed under the `root' user. If `false', builds are performed -# under one of the users listed in the `build-users' option (see -# below). -#build-allow-root = true - - ### Option `build-users' # -# This option is only applicable if `build-allow-root' is `false' and -# Nix is invoked under the `root' user (or setuid-root). It contains -# a list of user names under which Nix can execute builds. Builds -# cannot be performed by root since that would allow users to take -# over the system by supplying specially crafted builders; and they -# cannot be performed by the calling user since that would allow -# him/her to influence the build result. +# This option contains a list of user names under which Nix can +# execute builds. In multi-user Nix installations, builds should not +# be performed by the Nix account since that would allow users to +# arbitrarily modify the Nix store and database by supplying specially +# crafted builders; and they cannot be performed by the calling user +# since that would allow him/her to influence the build result. # # Thus this list should contain a number of `special' user accounts # created specifically for Nix, e.g., `nix-builder-1', # `nix-builder-2', and so on. The more users the better, since at # most a number of builds equal to the number of build users can be -# started. +# running simultaneously. +# +# If this list is empty, builds will be performed under the Nix +# account (that is, the uid under which the Nix daemon runs, or that +# owns the setuid nix-worker program). # # Example: # build-users = nix-builder-1 nix-builder-2 nix-builder-3 #build-users = +### Option `build-users-group' +# +# If `build-users' is used, then this option specifies the group ID +# (gid) under which each build is to be performed. This group should +# have permission to create files in the Nix store, but not delete +# them. I.e., /nix/store should be owned by the Nix account, its +# group should be the group specified here, and its mode should be +# 1775. +# +# The default is `nix'. +# +# Example: +# build-users-group = nix +#build-users-group = + + ### Option `system' # # This option specifies the canonical Nix system name of the current diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index da5aeadeb6..8577d34a50 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -258,6 +258,8 @@ static void setuidInit() if (setuid(nixUid)) abort(); if (setgid(nixGid)) abort(); #endif + + setuidMode = true; } diff --git a/src/libmain/shared.hh b/src/libmain/shared.hh index 16162604a9..20262004bd 100644 --- a/src/libmain/shared.hh +++ b/src/libmain/shared.hh @@ -24,6 +24,9 @@ namespace nix { Path makeRootName(const Path & gcRoot, int & counter); void printGCWarning(); +/* Whether we're running setuid. */ +bool setuidMode = false; + } diff --git a/src/libstore/build.cc b/src/libstore/build.cc index d8b90252b4..8a109bcefd 100644 --- a/src/libstore/build.cc +++ b/src/libstore/build.cc @@ -1271,12 +1271,10 @@ void DerivationGoal::startBuilder() } - /* If we are running as root, and the `build-allow-root' setting - is `false', then we have to build as one of the users listed in - `build-users'. */ - if (!queryBoolSetting("build-allow-root", true) && - getuid() == rootUserId) - { + /* If `build-users' is not empty, then we have to build as one of + the users listed in `build-users'. */ + gid_t gidBuildGroup = -1; + if (querySetting("build-users", Strings()).size() > 0) { buildUser.acquire(); assert(buildUser.getUID() != 0); @@ -1288,6 +1286,14 @@ void DerivationGoal::startBuilder() if (chown(tmpDir.c_str(), buildUser.getUID(), (gid_t) -1) == -1) throw SysError(format("cannot change ownership of `%1%'") % tmpDir); + /* What group to execute the builder in? */ + string buildGroup = querySetting("build-users-group", "nix"); + struct group * gr = getgrnam(buildGroup.c_str()); + if (!gr) throw Error( + format("the group `%1%' specified in `build-users-group' does not exist") + % buildGroup); + gidBuildGroup = gr->gr_gid; + /* Check that the Nix store has the appropriate permissions, i.e., owned by root and mode 1777 (sticky bit on so that the builder can create its output but not mess with the @@ -1295,13 +1301,13 @@ void DerivationGoal::startBuilder() struct stat st; if (stat(nixStore.c_str(), &st) == -1) throw SysError(format("cannot stat `%1%'") % nixStore); - if (st.st_uid != rootUserId) - throw Error(format("`%1%' is not owned by root") % nixStore); if (!(st.st_mode & S_ISVTX) || - ((st.st_mode & S_IRWXO) != S_IRWXO)) + ((st.st_mode & S_IRWXG) != S_IRWXG) || + (st.st_gid != gidBuildGroup)) throw Error(format( "builder does not have write permission to `%1%'; " - "try `chmod 1777 %1%'") % nixStore); + "try `chgrp %1% %2%; chmod 1775 %2%'") + % buildGroup % nixStore); } @@ -1343,23 +1349,25 @@ void DerivationGoal::startBuilder() envStrs.push_back(i->first + "=" + i->second); const char * * envArr = strings2CharPtrs(envStrs); - /* If we are running as root and `build-allow-root' is - `false', then switch to the user we allocated above. - Make sure that we drop all root privileges. Note that - initChild() above has closed all file descriptors - except std*, so that's safe. Also note that setuid() - when run as root sets the real, effective and saved - UIDs. */ + /* If we are running in `build-users' mode, then switch to + the user we allocated above. Make sure that we drop + all root privileges. Note that initChild() above has + closed all file descriptors except std*, so that's + safe. Also note that setuid() when run as root sets + the real, effective and saved UIDs. */ if (buildUser.getUID() != 0) { printMsg(lvlError, format("switching to uid `%1%'") % buildUser.getUID()); - /* !!! setgid also */ if (setgroups(0, 0) == -1) throw SysError("cannot clear the set of supplementary groups"); + setuid(buildUser.getUID()); assert(getuid() == buildUser.getUID()); assert(geteuid() == buildUser.getUID()); + setgid(gidBuildGroup); + assert(getgid() == gidBuildGroup); + assert(getegid() == gidBuildGroup); } /* Execute the program. This should not return. */ diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index 0b8900f250..2f3f3a7da8 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -235,10 +235,10 @@ void canonicalisePathMetaData(const Path & path) throw SysError(format("changing mode of `%1%' to %2$o") % path % mode); } - if (st.st_uid != getuid() || st.st_gid != getgid()) { - if (chown(path.c_str(), getuid(), getgid()) == -1) + if (st.st_uid != geteuid() || st.st_gid != getegid()) { + if (chown(path.c_str(), geteuid(), getegid()) == -1) throw SysError(format("changing owner/group of `%1%' to %2%/%3%") - % path % getuid() % getgid()); + % path % geteuid() % getegid()); } if (st.st_mtime != 0) {