A process can raise or drop capabilities from its capability sets using either the capset() system call or, preferably, the libcap API, which we describe below. Changes to process capabilities are subject to the following rules:
If the process doesn’t have the CAP_SETPCAP
capability in its effective set, then the new inheritable set must be a subset of the combination of the existing inheritable and permitted sets.
The new inheritable set must be a subset of the combination of the existing inheritable set and the capability bounding set.
The new permitted set must be a subset of the existing permitted set. In other words, a process can’t grant itself permitted capabilities that it doesn’t have. Put another way, a capability dropped from the permitted set can’t be reacquired.
The new effective set is allowed to contain only capabilities that are also in the new permitted set.
Up to this point, we have deliberately not shown the prototype of the capset() system call, or its counterpart capget(), which retrieves a process’s capabilities. This is because the use of these system calls should be avoided. Instead, the functions in the libcap library should be employed. These functions provide an interface that conforms with the withdrawn draft POSIX 1003.1e standard, along with some Linux extensions.
For reasons of space, we don’t describe the libcap API in detail. As an overview, we note that programs employing these functions typically carry out the following steps:
Use the cap_get_proc() function to retrieve a copy of the process’s current capability sets from the kernel and place it in a structure that the function allocates in user space. (Alternatively, we may use the cap_init() function to create a new, empty capability set structure.) In the libcap API, the cap_t data type is a pointer used to refer to such structures.
Use the cap_set_flag() function to update the user-space structure to raise (CAP_SET
) and drop (CAP_CLEAR
) capabilities from the permitted, effective, and inheritable sets stored in the user-space structure retrieved in the previous step.
Use the cap_set_proc() function to pass the user-space structure back to the kernel in order to change the process’s capabilities.
Use the cap_free() function to free the structure that was allocated by the libcap API in the first step.
At the time of writing, work is in progress on libcap-ng, a new, improved capabilities library API. Details can be found at http://freshmeat.net/projects/libcap-ng.
In Example 8-2, in Summary, we presented a program that authenticates a username plus password against the standard password database. We noted that the program requires privilege in order to read the shadow password file, which is protected to prevent reading by users other than root or members of the shadow group. The traditional way of providing this program with the privileges that it requires would be to run it under a root login or to make it a set-user-ID-root program. We now present a modified version of this program that employs capabilities and the libcap API.
In order to read the shadow password file as a normal user, we need to bypass the standard file permission checks. Scanning the capabilities listed in Table 39-1, we see that the appropriate capability is CAP_DAC_READ_SEARCH
. Our modified version of the password authentication program is shown in Example 39-1. This program uses the libcap API to raise CAP_DAC_READ_SEARCH
in its effective capability set just before accessing the shadow password file, and then drops the capability again immediately after this access. In order for an unprivileged user to employ the program, we must set this capability in the file permitted capability set, as shown in the following shell session:
$sudo setcap "cap_dac_read_search=p" check_password_caps
root's password: $getcap check_password_caps
check_password_caps = cap_dac_read_search+p $./check_password_caps
Username:mtk
Password: Successfully authenticated: UID=1000
Example 39-1. A capability-aware program that authenticates a user
cap/check_password_caps.c
#define _BSD_SOURCE /* Get getpass() declaration from <unistd.h> */ #define _XOPEN_SOURCE /* Get crypt() declaration from <unistd.h> */ #include <sys/capability.h> #include <unistd.h> #include <limits.h> #include <pwd.h> #include <shadow.h> #include "tlpi_hdr.h" /* Change setting of capability in caller's effective capabilities */ static int modifyCap(int capability, int setting) { cap_t caps; cap_value_t capList[1]; /* Retrieve caller's current capabilities */ caps = cap_get_proc(); if (caps == NULL) return -1; /* Change setting of 'capability' in the effective set of 'caps'. The third argument, 1, is the number of items in the array 'capList'. */ capList[0] = capability; if (cap_set_flag(caps, CAP_EFFECTIVE, 1, capList, setting) == -1) { cap_free(caps); return -1; } /* Push modified capability sets back to kernel, to change caller's capabilities */ if (cap_set_proc(caps) == -1) { cap_free(caps); return -1; } /* Free the structure that was allocated by libcap */ if (cap_free(caps) == -1) return -1; return 0; } static int /* Raise capability in caller's effective set */ raiseCap(int capability) { return modifyCap(capability, CAP_SET); } /* An analogous dropCap() (unneeded in this program), could be defined as: modifyCap(capability, CAP_CLEAR); */ static int /* Drop all capabilities from all sets */ dropAllCaps(void) { cap_t empty; int s; empty = cap_init(); if (empty == NULL) return -1; s = cap_set_proc(empty); if (cap_free(empty) == -1) return -1; return s; } int main(int argc, char *argv[]) { char *username, *password, *encrypted, *p; struct passwd *pwd; struct spwd *spwd; Boolean authOk; size_t len; long lnmax; lnmax = sysconf(_SC_LOGIN_NAME_MAX); if (lnmax == -1) /* If limit is indeterminate */ lnmax = 256; /* make a guess */ username = malloc(lnmax); if (username == NULL) errExit("malloc"); printf("Username: "); fflush(stdout); if (fgets(username, lnmax, stdin) == NULL) exit(EXIT_FAILURE); /* Exit on EOF */ len = strlen(username); if (username[len - 1] == '\n') username[len - 1] = '\0'; /* Remove trailing '\n' */ pwd = getpwnam(username); if (pwd == NULL) fatal("couldn't get password record"); /* Only raise CAP_DAC_READ_SEARCH for as long as we need it */ if (raiseCap(CAP_DAC_READ_SEARCH) == -1) fatal("raiseCap() failed"); spwd = getspnam(username); if (spwd == NULL && errno == EACCES) fatal("no permission to read shadow password file"); /* At this point, we won't need any more capabilities, so drop all capabilities from all sets */ if (dropAllCaps() == -1) fatal("dropAllCaps() failed"); if (spwd != NULL) /* If there is a shadow password record */ pwd->pw_passwd = spwd->sp_pwdp; /* Use the shadow password */ password = getpass("Password: "); /* Encrypt password and erase cleartext version immediately */ encrypted = crypt(password, pwd->pw_passwd); for (p = password; *p != '\0'; ) *p++ = '\0'; if (encrypted == NULL) errExit("crypt"); authOk = strcmp(encrypted, pwd->pw_passwd) == 0; if (!authOk) { printf("Incorrect password\n"); exit(EXIT_FAILURE); } printf("Successfully authenticated: UID=%ld\n", (long) pwd->pw_uid); /* Now do authenticated work... */ exit(EXIT_SUCCESS); }cap/check_password_caps.c