cap/cap_launcher.c

This is cap/cap_launcher.c, an example to accompany the book, The Linux Programming Interface.

This file is not printed in the book; it is a supplementary file for Chapter 39.

The source code file is copyright 2022, Michael Kerrisk, and is licensed under the GNU General Public License, version 3.

In the listing below, the names of Linux system calls and C library functions are hyperlinked to manual pages from the Linux man-pages project, and the names of functions implemented in the book are hyperlinked to the implementations of those functions.

 

Download cap/cap_launcher.c

  Cover of The Linux Programming Interface

Function list (Bold in this list means a function is not static)

/* cap_launcher.c

   Launch a program with the credentials (UIDs, GIDs, supplementary GIDs)
   of a specified user, and with the capabilities specified on the
   command line.  The program relies on the use of ambient capabilities,
   a feature that first appeared in Linux 4.3.
*/
#define _GNU_SOURCE         /* See feature_test_macros(7) */
#include <string.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <sys/capability.h>
#include <linux/securebits.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include "cap_functions.h"
#include "tlpi_hdr.h"
static void
usage(char *pname)
{
    fprintf(stderr, "Usage: %s user cap,... cmd arg...\n", pname);
    fprintf(stderr, "\t'user' is the user with whose credentials\n");
    fprintf(stderr, "\t\tthe program is to be launched\n");
    fprintf(stderr, "\t'cap,...' is the set of capabilities with which\n");
    fprintf(stderr, "\t\tthe program is to be launched\n");
    fprintf(stderr, "\t'cmd' and 'arg...' specify the command plus\n");
    fprintf(stderr, "\t\tfor the program that is to be launched\n");
    exit(EXIT_FAILURE);
}
/* Switch credentials (user ID, group ID, supplementary groups) to
   those for the user named in 'user' */

static void
setCredentials(char *user)
{
    /* Look up user in user database */

    struct passwd *pwd = getpwnam(user);
    if (pwd == NULL) {
        fprintf(stderr, "Unknown user: %s\n", user);
        exit(EXIT_FAILURE);
    }

    /* Find out how many supplementary groups the user is a member of */

    int ngroups = 0;
    getgrouplist(user, pwd->pw_gid, NULL, &ngroups);

    /* Allocate an array for supplementary group IDs */

    gid_t *groups = calloc(ngroups, sizeof(gid_t));
    if (groups == NULL)
        errExit("calloc");

    /* Get supplementary group list of 'user' from group database */

    if (getgrouplist(user, pwd->pw_gid, groups, &ngroups) == -1)
        errExit("getgrouplist");

    /* Set the supplementary group list */

    if (setgroups(ngroups, groups) == -1)
        errExit("setgroups");

    /* Set all group IDs to GID of this user */

    if (setresgid(pwd->pw_gid, pwd->pw_gid, pwd->pw_gid) == -1)
        errExit("setresgid");

    /* Set all user IDs to UID of this user */

    if (setresuid(pwd->pw_uid, pwd->pw_uid, pwd->pw_uid) == -1)
        errExit("setresuid");
}
/* Add a set of capabilities to the process's ambient list */

static void
setAmbientCapabilities(char *capList)
{
    /* Walk through the capabilities listed in the comma-delimited list
       of capability names in 'capList', adding each capability to the
       ambient set. This will cause the capability to pass into the
       process permitted and effective sets during exec(). */

    for (char *p = capList; (p = strtok(p, ",")); p = NULL) {

        /* Convert the capability name to a capability number */

        cap_value_t cap;
        if (cap_from_name(p, &cap) == -1) {
            fprintf(stderr, "Unrecognized capability name: %s\n", p);
            exit(EXIT_FAILURE);
        }

        /* In order to place a capability into the ambient set,
           that capability must also be in the inheritable set */

        if (modifyCapSetting(CAP_INHERITABLE, cap, CAP_SET) == -1) {
            fprintf(stderr, "Could not raise '%s' inheritable "
                    "capability (%s)\n", p, strerror(errno));
            exit(EXIT_FAILURE);
        }

        /* Raise the capability in the ambient set */

        if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap, 0, 0) == -1) {
            fprintf(stderr, "Could not raise '%s' ambient "
                    "capability (%s)\n", p, strerror(errno));
            exit(EXIT_FAILURE);
        }
    }
}
int
main(int argc, char *argv[])
{
    if (argc < 4 || strcmp(argv[1], "--help") == 0)
        usage(argv[0]);

    if (geteuid() != 0)
        fatal("Must be run as root");

    /* Set "no setuid fixup" securebit so that when we switch to
       a nonzero UID, we don't lose capabilities */

    if (prctl(PR_SET_SECUREBITS, SECBIT_NO_SETUID_FIXUP, 0, 0, 0) == -1)
        errExit("prctl");

    setCredentials(argv[1]);

    setAmbientCapabilities(argv[2]);

    /* Execute the program (with arguments) named in argv[3]... */

    execvp(argv[3], &argv[3]);
    errExit("execvp");
}

 

Download cap/cap_launcher.c

Note that, in most cases, the programs rendered in these web pages are not free standing: you'll typically also need a few other source files (mostly in the lib/ subdirectory) as well. Generally, it's easier to just download the entire source tarball and build the programs with make(1). By hovering your mouse over the various hyperlinked include files and function calls above, you can see which other source files this file depends on.

Valid XHTML 1.1