While Barmar already answered the stated question, I think it might be useful to go into the details a bit.
Then how can initgroups call setgroups to initialize the supplementary group ID list for an arbitrary user?
initgroups()
scans the group database (using getgrent()
or a similar internal facility) to construct the list of supplementary groups to set using setgroups()
.
In other words, setgroups()
is the interface to manipulate the supplementary group IDs of the current process. initgroups()
is a helper function, that scans the group database to construct a list of all the group IDs the specified user is a member of, and calls setgroups()
to install that set as supplementary group IDs.
Here is an example implementation of initgroups()
:
int initgroups(const char *name, gid_t group)
{
gidarray gids = GIDARRAY_INIT;
struct group *gr;
size_t i;
/* Initialize the gids list to the specified group. */
if (gidarray_add(&gids, group)) {
errno = ENOMEM;
return -1;
}
/* Loop through the group database. */
setgrent();
while (1) {
errno = 0;
gr = getgrent();
if (!gr) {
/* End of groups, or an error? */
if (errno) {
const int saved_errno = errno;
gidarray_free(&gids);
endgrent();
errno = saved_errno;
return -1;
}
/* No error, just end of groups. */
break;
}
/* Is there is no member list, this group is not interesting. */
if (!gr->gr_mem)
continue;
/* Check if the user is listed in this group member list. */
for (i = 0; gr->gr_mem[i] != NULL; i++) {
if (!strcmp(gr->gr_mem[i], name)) {
/* Yes; add to list, break out of this for loop. */
if (gidarray_add(&gids, gr->gr_gid)) {
gidarray_free(&gids);
endgrent();
errno = ENOMEM;
return -1;
}
break;
}
}
}
endgrent();
/* Set the supplementary group list. */
if (setgroups(gidarray_size(&gids), gidarray_ptr(&gids)) == -1) {
const int saved_errno = errno;
gidarray_free(&gids);
errno = saved_errno;
return -1;
}
gidarray_free(&gids);
return 0;
}
typedef struct {
size_t max;
size_t num;
gid_t *gid;
} gidarray;
#define GIDARRAY_INIT { 0, 0, NULL }
static void gidarray_free(gidarray *garr)
{
if (garr) {
free(garr->gid);
garr->max = 0;
garr->num = 0;
garr->gid = NULL;
}
}
static size_t gidarray_size(gidarray *garr)
{
return (garr) ? garr->num : 0;
}
static gid_t *gidarray_ptr(gidarray *garr)
{
return (garr) ? garr->gid : NULL;
}
static int gidarray_add(gidarray *garr, const gid_t gid)
{
/* Check if already included. */
size_t i = garr->num;
while (i-->0)
if (garr->gid[i] == gid)
return 0;
if (garr->num >= garr->max) {
size_t max = (garr->num | 15) + 17;
void *tmp;
tmp = realloc(garr->gid, max * sizeof garr->gid[0]);
if (!tmp)
return -1;
garr->gid = tmp;
garr->max = max;
}
garr->gid[garr->num++] = gid;
return 0;
}
The gidarray_free()
, gidarray_add()
, gidarray_size()
, and gidarray_ptr()
are helper functions listed below the function above, that manage the group ID array.
In practice, when a privileged (root) process drops the privileges and switches to the identity of a specific user, it sets the user and group ID as specified in the password database, and supplementary group IDs as specified in the group database. In practice, such a function is something similar to
int drop_privileges(const char *username)
{
struct passwd *pw;
/* Find out the user and group ID. */
pw = getpwnam(username);
if (!pw) {
errno = ENOENT; /* For "no such user" */
return -1;
}
/* Initialize supplementary groups. */
if (initgroups(username, pw->pw_gid) == -1)
return -1;
/* Set real, effective, and saved group ID. */
if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1)
return -1;
/* Omitted: Dropping Linux capabilities. */
/* Drop privileges by setting real, effective, and saved user ID. */
if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1)
return -1;
/* Now this process has the identity and thus privileges
of user 'username', and no more. */
return 0;
}
There are other relevant details to be considered when dropping privileges (in particular, access checks for files and devices are typically done only at open time, so leaking privileged open files is a problem), but the above is a rough sketch of how the user and group identity part of it works.
Note that you can use the id
utility (part of Coreutils, so should be installed on all systems) to examine the identity of the current process. For example, id -un
shows the user name matching the current user ID, id -gn
shows the group name matching the current group ID, and id -Gn
lists the group names matching the supplementary group IDs.
Similarly, you can use the getent
utility (installed as part of the C library) to examine the user and password databases: getent passwd
shows the public fields of the user database, getent passwd username
shows the public fields for user 'username' in the user database, getent group
shows the public fields of the group database, and getent group groupname
shows the public fields of the group 'groupname' in the group database.