* Get rid of `build-users'. We'll just take all the members of

`build-users-group'.  This makes configuration easier: you can just
  add users in /etc/group.
This commit is contained in:
Eelco Dolstra 2006-12-06 20:00:15 +00:00
parent 751f6d2157
commit 6e5ec1029a
3 changed files with 88 additions and 66 deletions

View file

@ -78,44 +78,44 @@
#build-max-jobs = 1 #build-max-jobs = 1
### Option `build-users' ### Option `build-users-group'
# #
# This option contains a list of user names under which Nix can # This options specifies the Unix group containing the Nix build user
# execute builds. In multi-user Nix installations, builds should not # accounts. In multi-user Nix installations, builds should not
# be performed by the Nix account since that would allow users to # be performed by the Nix account since that would allow users to
# arbitrarily modify the Nix store and database by supplying specially # arbitrarily modify the Nix store and database by supplying specially
# crafted builders; and they cannot be performed by the calling user # crafted builders; and they cannot be performed by the calling user
# since that would allow him/her to influence the build result. # since that would allow him/her to influence the build result.
# #
# Thus this list should contain a number of `special' user accounts # Therefore, if this option is non-empty and specifies a valid group,
# created specifically for Nix, e.g., `nix-builder-1', # builds will be performed under the user accounts that are a member
# `nix-builder-2', and so on. The more users the better, since at # of the group specified here (as listed in /etc/group). Those user
# most a number of builds equal to the number of build users can be # accounts should not be used for any other purpose!
# running simultaneously.
# #
# If this list is empty, builds will be performed under the Nix # Nix will never run two builds under the same user account at the
# account (that is, the uid under which the Nix daemon runs, or that # same time. This is to prevent an obvious security hole: a malicious
# owns the setuid nix-worker program). # user writing a Nix expression that modifies the build result of a
# legitimate Nix expression being built by another user. Therefore it
# is good to have as many Nix build user accounts as you can spare.
# (Remember: uids are cheap.)
#
# The build users should have permission to create files in the Nix
# store, but not delete them. Therefore, /nix/store should be owned
# by the Nix account, its group should be the group specified here,
# and its mode should be 1775.
#
# If the build users group is empty, builds will be performed under
# the uid of the Nix process (that is, the uid of the caller if
# $NIX_REMOTE is empty, the uid under which the Nix daemon runs if
# $NIX_REMOTE is `daemon', or the uid that owns the setuid nix-worker
# program if $NIX_REMOTE is `slave'). Obviously, this should not be
# used in multi-user settings with untrusted users.
#
# The default is empty.
# #
# Example: # Example:
# build-users = nix-builder-1 nix-builder-2 nix-builder-3 # build-users-group = nix-builders
#build-users = build-users-group = nix-builders
### 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' ### Option `system'

View file

@ -341,6 +341,7 @@ private:
AutoCloseFD fdUserLock; AutoCloseFD fdUserLock;
uid_t uid; uid_t uid;
gid_t gid;
public: public:
UserLock(); UserLock();
@ -350,6 +351,7 @@ public:
void release(); void release();
uid_t getUID(); uid_t getUID();
uid_t getGID();
}; };
@ -358,7 +360,7 @@ PathSet UserLock::lockedPaths;
UserLock::UserLock() UserLock::UserLock()
{ {
uid = 0; uid = gid = 0;
} }
@ -372,20 +374,36 @@ void UserLock::acquire()
{ {
assert(uid == 0); assert(uid == 0);
Strings buildUsers = querySetting("build-users", Strings()); string buildUsersGroup = querySetting("build-users-group", "");
assert(buildUsersGroup != "");
if (buildUsers.empty()) /* Get the members of the build-users-group. */
throw Error( struct group * gr = getgrnam(buildUsersGroup.c_str());
"cannot build as `root'; you must define " if (!gr)
"one or more users for building in `build-users' " throw Error(format("the group `%1%' specified in `build-users-group' does not exist")
"in the Nix configuration file"); % buildUsersGroup);
gid = gr->gr_gid;
for (Strings::iterator i = buildUsers.begin(); i != buildUsers.end(); ++i) { /* Copy the result of getgrnam. */
Strings users;
for (char * * p = gr->gr_mem; *p; ++p) {
debug(format("found build user `%1%'") % *p);
users.push_back(*p);
}
if (users.empty())
throw Error(format("the build users group `%1%' has no members")
% buildUsersGroup);
/* Find a user account that isn't currently in use for another
build. */
for (Strings::iterator i = users.begin(); i != users.end(); ++i) {
debug(format("trying user `%1%'") % *i); debug(format("trying user `%1%'") % *i);
struct passwd * pw = getpwnam(i->c_str()); struct passwd * pw = getpwnam(i->c_str());
if (!pw) if (!pw)
throw Error(format("the user `%1%' listed in `build-users' does not exist") % *i); throw Error(format("the user `%1%' in the group `%2%' does not exist")
% *i % buildUsersGroup);
fnUserLock = (format("%1%/userpool/%2%") % nixStateDir % pw->pw_uid).str(); fnUserLock = (format("%1%/userpool/%2%") % nixStateDir % pw->pw_uid).str();
@ -405,9 +423,9 @@ void UserLock::acquire()
} }
} }
throw Error("all build users are currently in use; " throw Error(format("all build users are currently in use; "
"consider expanding the `build-users' field " "consider creating additional users and adding them to the `%1%' group")
"in the Nix configuration file"); % buildUsersGroup);
} }
@ -428,6 +446,12 @@ uid_t UserLock::getUID()
} }
uid_t UserLock::getGID()
{
return gid;
}
static void killUser(uid_t uid) static void killUser(uid_t uid)
{ {
debug(format("killing all processes running under uid `%1%'") % uid); debug(format("killing all processes running under uid `%1%'") % uid);
@ -1275,12 +1299,12 @@ void DerivationGoal::startBuilder()
} }
/* If `build-users' is not empty, then we have to build as one of /* If `build-users-group' is not empty, then we have to build as
the users listed in `build-users'. */ one of the members of that group. */
gid_t gidBuildGroup = -1; if (querySetting("build-users-group", "") != "") {
if (querySetting("build-users", Strings()).size() > 0) {
buildUser.acquire(); buildUser.acquire();
assert(buildUser.getUID() != 0); assert(buildUser.getUID() != 0);
assert(buildUser.getGID() != 0);
/* Make sure that no other processes are executing under this /* Make sure that no other processes are executing under this
uid. */ uid. */
@ -1290,16 +1314,8 @@ void DerivationGoal::startBuilder()
if (chown(tmpDir.c_str(), buildUser.getUID(), (gid_t) -1) == -1) if (chown(tmpDir.c_str(), buildUser.getUID(), (gid_t) -1) == -1)
throw SysError(format("cannot change ownership of `%1%'") % tmpDir); 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, /* Check that the Nix store has the appropriate permissions,
i.e., owned by root and mode 1777 (sticky bit on so that i.e., owned by root and mode 1775 (sticky bit on so that
the builder can create its output but not mess with the the builder can create its output but not mess with the
outputs of other processes). */ outputs of other processes). */
struct stat st; struct stat st;
@ -1307,11 +1323,11 @@ void DerivationGoal::startBuilder()
throw SysError(format("cannot stat `%1%'") % nixStore); throw SysError(format("cannot stat `%1%'") % nixStore);
if (!(st.st_mode & S_ISVTX) || if (!(st.st_mode & S_ISVTX) ||
((st.st_mode & S_IRWXG) != S_IRWXG) || ((st.st_mode & S_IRWXG) != S_IRWXG) ||
(st.st_gid != gidBuildGroup)) (st.st_gid != buildUser.getGID()))
throw Error(format( throw Error(format(
"builder does not have write permission to `%1%'; " "builder does not have write permission to `%1%'; "
"try `chgrp %1% %2%; chmod 1775 %2%'") "try `chgrp %1% %2%; chmod 1775 %2%'")
% buildGroup % nixStore); % buildUser.getGID() % nixStore);
} }
@ -1365,13 +1381,15 @@ void DerivationGoal::startBuilder()
if (setgroups(0, 0) == -1) if (setgroups(0, 0) == -1)
throw SysError("cannot clear the set of supplementary groups"); throw SysError("cannot clear the set of supplementary groups");
setgid(gidBuildGroup); if (setgid(buildUser.getGID()) == -1 ||
assert(getgid() == gidBuildGroup); getgid() != buildUser.getGID() ||
assert(getegid() == gidBuildGroup); getegid() != buildUser.getGID())
throw SysError("setgid failed");
setuid(buildUser.getUID()); if (setuid(buildUser.getUID()) == -1 ||
assert(getuid() == buildUser.getUID()); getuid() != buildUser.getUID() ||
assert(geteuid() == buildUser.getUID()); geteuid() != buildUser.getUID())
throw SysError("setuid failed");
} }
/* Execute the program. This should not return. */ /* Execute the program. This should not return. */

View file

@ -40,14 +40,18 @@ static void runBuilder(string userName,
don't want to create that directory here. */ don't want to create that directory here. */
secureChown(pw->pw_uid, gidBuilders, "."); secureChown(pw->pw_uid, gidBuilders, ".");
/* Set the real, effective and saved gid. Must be done before /* Set the real, effective and saved gid. Must be done before
setuid(), otherwise it won't set the real and saved gids. */ setuid(), otherwise it won't set the real and saved gids. */
if (setgroups(0, 0) == -1)
throw SysError("cannot clear the set of supplementary groups");
//setgid(gidBuilders); //setgid(gidBuilders);
/* Set the real, effective and saved uid. */ /* Set the real, effective and saved uid. */
setuid(pw->pw_uid); if (setuid(pw->pw_uid) == -1 ||
if (getuid() != pw->pw_uid || geteuid() != pw->pw_uid) getuid() != pw->pw_uid ||
throw Error("cannot setuid"); geteuid() != pw->pw_uid)
throw SysError("setuid failed");
/* Execute the program. */ /* Execute the program. */
std::vector<const char *> args; std::vector<const char *> args;