mirror of
https://github.com/NixOS/nix.git
synced 2025-11-23 02:39:37 +01:00
The manpage for `getgrouplist` says:
> If the number of groups of which user is a member is less than or
> equal to *ngroups, then the value *ngroups is returned.
>
> If the user is a member of more than *ngroups groups, then
> getgrouplist() returns -1. In this case, the value returned in
> *ngroups can be used to resize the buffer passed to a further
> call getgrouplist().
In our original code, however, we allocated a list of size `10` and, if
`getgrouplist` returned `-1` threw an exception. In practice, this
caused the code to fail for any user belonging to more than 10 groups.
While unusual for single-user systems, large companies commonly have a
huge number of POSIX groups users belong to, causing this issue to crop
up and make multi-user Nix unusable in such settings.
The fix is relatively simple, when `getgrouplist` fails, it stores the
real number of GIDs in `ngroups`, so we must resize our list and retry.
Only then, if it errors once more, we can raise an exception.
This should be backported to, at least, 2.9.x.
(cherry picked from commit 931930feb1)
106 lines
3.1 KiB
C++
106 lines
3.1 KiB
C++
#include "lock.hh"
|
|
#include "globals.hh"
|
|
#include "pathlocks.hh"
|
|
|
|
#include <grp.h>
|
|
#include <pwd.h>
|
|
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
|
|
namespace nix {
|
|
|
|
UserLock::UserLock()
|
|
{
|
|
assert(settings.buildUsersGroup != "");
|
|
createDirs(settings.nixStateDir + "/userpool");
|
|
}
|
|
|
|
bool UserLock::findFreeUser() {
|
|
if (enabled()) return true;
|
|
|
|
/* Get the members of the build-users-group. */
|
|
struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str());
|
|
if (!gr)
|
|
throw Error("the group '%1%' specified in 'build-users-group' does not exist",
|
|
settings.buildUsersGroup);
|
|
gid = gr->gr_gid;
|
|
|
|
/* Copy the result of getgrnam. */
|
|
Strings users;
|
|
for (char * * p = gr->gr_mem; *p; ++p) {
|
|
debug("found build user '%1%'", *p);
|
|
users.push_back(*p);
|
|
}
|
|
|
|
if (users.empty())
|
|
throw Error("the build users group '%1%' has no members",
|
|
settings.buildUsersGroup);
|
|
|
|
/* Find a user account that isn't currently in use for another
|
|
build. */
|
|
for (auto & i : users) {
|
|
debug("trying user '%1%'", i);
|
|
|
|
struct passwd * pw = getpwnam(i.c_str());
|
|
if (!pw)
|
|
throw Error("the user '%1%' in the group '%2%' does not exist",
|
|
i, settings.buildUsersGroup);
|
|
|
|
|
|
fnUserLock = (format("%1%/userpool/%2%") % settings.nixStateDir % pw->pw_uid).str();
|
|
|
|
AutoCloseFD fd = open(fnUserLock.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0600);
|
|
if (!fd)
|
|
throw SysError("opening user lock '%1%'", fnUserLock);
|
|
|
|
if (lockFile(fd.get(), ltWrite, false)) {
|
|
fdUserLock = std::move(fd);
|
|
user = i;
|
|
uid = pw->pw_uid;
|
|
|
|
/* Sanity check... */
|
|
if (uid == getuid() || uid == geteuid())
|
|
throw Error("the Nix user should not be a member of '%1%'",
|
|
settings.buildUsersGroup);
|
|
|
|
#if __linux__
|
|
/* Get the list of supplementary groups of this build user. This
|
|
is usually either empty or contains a group such as "kvm". */
|
|
int ngroups = 32; // arbitrary initial guess
|
|
supplementaryGIDs.resize(ngroups);
|
|
|
|
int err = getgrouplist(pw->pw_name, pw->pw_gid, supplementaryGIDs.data(),
|
|
&ngroups);
|
|
|
|
// Our initial size of 32 wasn't sufficient, the correct size has
|
|
// been stored in ngroups, so we try again.
|
|
if (err == -1) {
|
|
supplementaryGIDs.resize(ngroups);
|
|
err = getgrouplist(pw->pw_name, pw->pw_gid, supplementaryGIDs.data(),
|
|
&ngroups);
|
|
}
|
|
|
|
// If it failed once more, then something must be broken.
|
|
if (err == -1)
|
|
throw Error("failed to get list of supplementary groups for '%1%'",
|
|
pw->pw_name);
|
|
|
|
// Finally, trim back the GID list to its real size
|
|
supplementaryGIDs.resize(ngroups);
|
|
#endif
|
|
|
|
isEnabled = true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void UserLock::kill()
|
|
{
|
|
killUser(uid);
|
|
}
|
|
|
|
}
|