0

From APUE

#include <grp.h> /* on Linux */
int setgroups(int ngroups, const gid_t grouplist[]);

The setgroups function can be called by the superuser to set the supplementary group ID list for the calling process

#include <grp.h> /* on Linux and Solaris */
int initgroups(const char *username, gid_t basegid);

The setgroups function is usually called from the initgroups function, which reads the entire group file—with the functions getgrent, setgrent, and endgrent, which we described earlier—and determines the group membership for username. It then calls setgroups to initialize the supplementary group ID list for the user.

initgroups can take a username as a parameter, while setgroups doesn't take a username as a parameter. Then how can initgroups call setgroups to initialize the supplementary group ID list for an arbitrary user?

Thanks.

Tim
  • 1
  • 141
  • 372
  • 590

2 Answers2

3

setgroups() operates on the current process, not a user. initgroups() gets the username as a parameter, looks up the user's groups, then passes that list of groups to setgroups() to modify the current process's supplementary group list.

This is normally done during login, the username is the name that you're logging in as. The login process sets its group list, then execs your login shell. The group list is inherited by all the other processes in your login session.

Barmar
  • 741,623
  • 53
  • 500
  • 612
1

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.

Nominal Animal
  • 38,216
  • 5
  • 59
  • 86