The Linux capabilities system does not work with a single capability set. Instead, both processes and files each have three separate capability sets. Understanding the purpose and relationship between these six sets (3 per process, 3 per file) is the most important conceptual foundation for working with Linux capabilities.
Think of these sets as a series of gates or filters that control which capabilities a process can actually use at any given moment. They give very fine-grained control: a capability can be allowed but currently inactive, allowed to pass across exec, or permanently restricted.
The kernel maintains three capability sets for every process (technically, per-thread). Each set is stored as a bitmask โ one bit per capability. The three sets are:
| Set Name | Symbol | Purpose & Role |
|---|---|---|
| Permitted | P(permitted) |
The maximum ceiling of capabilities a process may ever use. A process can only raise a capability in its effective set if that capability is already in the permitted set. If a capability is dropped from the permitted set, it is permanently gone โ it cannot be reacquired (unless the process execs a file that grants it back). |
| Effective | P(effective) |
The currently active capabilities โ the ones the kernel actually checks during privilege verification. This is the set that matters when a system call requires a capability. A process can temporarily disable a capability by removing it from the effective set (while keeping it in the permitted set), and later re-enable it. |
| Inheritable | P(inheritable) |
Capabilities that may be passed across an exec() call. When a process calls exec(), the inheritable set is used (along with the file’s inheritable set) to determine which capabilities the new program gets in its permitted set. This allows a process to selectively preserve specific capabilities across exec(). |
Think of the permitted set as all the keys stored in the hotel safe in your room. The effective set is the subset of keys you actually have in your pocket right now (ready to use). You can take a key from the safe and put it in your pocket (raise to effective), or put it back (drop from effective). But if you throw a key in the bin (drop from permitted), you can never get it back.
The relationship between permitted and effective is exactly analogous to the relationship between the saved set-user-ID and the effective user ID in a set-user-ID-root program:
- Dropping a capability from effective only = temporarily dropping effective UID 0 (but keeping it in saved set-user-ID)
- Dropping a capability from both effective and permitted = permanently dropping superuser privileges (setting both effective UID and saved set-user-ID to nonzero)
Important implementation details:
- Capability sets are per-thread attributes. In a multithreaded program, each thread can have different capabilities.
- A child process created by
fork()inherits copies of all three capability sets from its parent. - The
/proc/PID/statusfile showsCapInh,CapPrm,CapEff, andCapBndas 64-bit hex values. - Before Linux 2.6.25, capability sets were 32-bit. They were expanded to 64-bit in 2.6.25 to accommodate additional capabilities.
Just like processes, executable files can have capability sets attached to them. These are stored as a security extended attribute named security.capability on the file. File capabilities were introduced in Linux 2.6.24.
When a process calls exec(), the kernel combines the process’s current capability sets with the file’s capability sets to compute the new capability sets for the process running the program.
| File Set Name | Symbol | What It Does During exec() |
|---|---|---|
| Permitted | F(permitted) |
Formerly called “forced”. These capabilities are forced into the process’s new permitted set regardless of what the process already had. This is how a file grants capabilities to a process โ it says “any process that execs me will get at least these capabilities in its permitted set.” |
| Effective | F(effective) |
This is NOT a full set โ it is a single bit (on or off). If the bit is 1, then all capabilities that end up in the process’s new permitted set are also automatically placed into the effective set. If the bit is 0, the new effective set starts empty (the program must manually raise capabilities). |
| Inheritable | F(inheritable) |
Formerly called “allowed”. This is ANDed against the process’s inheritable set to determine which capabilities from the process’s inheritable set are also added to the new permitted set. This is the file’s way of saying: “I allow these inheritable capabilities from the process to come through.” |
The design choice of making the file effective set a single bit reflects two use cases:
- Capability-dumb programs (old programs unaware of capabilities): They don’t know to raise capabilities in their effective set. For these programs, the file effective bit should be 1, so that permitted capabilities automatically become effective โ the program just works.
- Capability-aware programs (written to use capabilities properly): These programs will call libcap functions to raise and drop capabilities as needed. For them, the file effective bit should be 0, so the effective set starts empty and the program explicitly raises only what it needs โ implementing the principle of least privilege at runtime.
The setcap(8) and getcap(8) commands from the libcap package are the tools for managing file capabilities. Understanding the notation they use is important.
$ getcap /bin/ping
ping = cap_net_raw+ep
# Assign CAP_SYS_TIME to permitted(p) and effective(e) sets of a file
$ sudo setcap “cap_sys_time=pe” ./mydate
# Assign CAP_DAC_READ_SEARCH to permitted only (program is capability-aware)
$ sudo setcap “cap_dac_read_search=p” ./myprogram
# Remove all file capabilities from a file
$ sudo setcap -r ./myprogram
# Assign multiple capabilities at once
$ sudo setcap “cap_net_bind_service,cap_net_raw=ep” ./myserver
The notation format is: capability_name(s) = flags
| Flag Letter | Meaning |
|---|---|
p |
Add to file Permitted set |
e |
Set the file Effective bit to 1 |
i |
Add to file Inheritable set |
The
date(1) program normally cannot change the system time as a non-root user. By giving it CAP_SYS_TIME in both permitted and effective sets:sudo setcap "cap_sys_time=pe" ./dateNow any unprivileged user can run this copy of
date and change the system time. The capability is in the file’s permitted set (so it enters the process’s permitted set on exec) and the effective bit is set (so it’s immediately in the effective set too).Here is a side-by-side comparison of all six sets (3 process + 3 file):
| Set | Belongs To | Size | Primary Role |
|---|---|---|---|
| Permitted | Process | 64-bit mask | Maximum capabilities the process can ever use |
| Effective | Process | 64-bit mask | Currently active capabilities checked by the kernel |
| Inheritable | Process | 64-bit mask | Capabilities to possibly carry across exec() |
| Permitted | File | 64-bit mask | Capabilities forced into process permitted set on exec() |
| Effective | File | 1 bit only | If set, all new permitted caps also become effective |
| Inheritable | File | 64-bit mask | Mask applied to process inheritable set to grant caps on exec() |
This program uses libcap to display each of the three capability sets for the calling process, both as a human-readable string and by iterating over all known capabilities to show their individual status. It demonstrates how capability-aware programs inspect their own capability state at startup.
/*
* show_all_cap_sets.c
*
* Displays all three capability sets of the calling process:
* - Permitted
* - Effective
* - Inheritable
*
* Shows both the compact text form and a per-capability breakdown.
*
* Compile:
* gcc -o show_all_cap_sets show_all_cap_sets.c -lcap
*
* Test as root:
* sudo ./show_all_cap_sets
*
* Test with specific caps assigned to file:
* sudo setcap "cap_net_bind_service,cap_sys_time=p" ./show_all_cap_sets
* ./show_all_cap_sets
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/capability.h>
#include <unistd.h>
/*
* print_set_status() - For a given cap_t, print the status of every
* known capability in the specified set (Permitted, Effective, or Inheritable).
*
* We iterate capability values from 0 up to CAP_LAST_CAP.
* CAP_LAST_CAP is a macro defined by libcap for the highest-numbered capability.
* cap_is_supported() lets us skip capabilities not supported by this kernel.
*/
static void print_set_status(cap_t caps, cap_flag_t which_set, const char *set_name)
{
int cap_num;
cap_flag_value_t value;
const char *cap_name_str;
printf("\n --- %s Set ---\n", set_name);
printf(" %-35s %s\n", "Capability", "Status");
printf(" %-35s %s\n", "----------", "------");
/*
* CAP_LAST_CAP is the integer value of the highest-numbered capability.
* We iterate from 0 to CAP_LAST_CAP inclusive.
*
* cap_to_name() converts a capability number to its symbolic string,
* e.g., cap_to_name(13) might return "cap_net_bind_service".
* It allocates memory โ we must cap_free() it.
*/
for (cap_num = 0; cap_num <= CAP_LAST_CAP; cap_num++) {
/* Get the string name for this capability number */
cap_name_str = cap_to_name(cap_num);
if (cap_name_str == NULL)
continue; /* Unknown capability โ skip */
/* Get the flag value for this capability in this set */
if (cap_get_flag(caps, (cap_value_t)cap_num, which_set, &value) == -1) {
cap_free((void *)cap_name_str);
continue;
}
/* Only print capabilities that are SET (to keep output concise) */
if (value == CAP_SET) {
printf(" %-35s SET\n", cap_name_str);
}
cap_free((void *)cap_name_str);
}
}
int main(void)
{
cap_t caps;
char *text_form;
printf("=== Process Capability Sets Viewer ===\n");
printf("PID=%d UID=%d EUID=%d\n",
(int)getpid(), (int)getuid(), (int)geteuid());
/*
* cap_get_proc() retrieves the calling process's capability sets
* from the kernel and stores them in a newly allocated cap_t structure.
*
* The cap_t type is an opaque pointer โ never access it directly.
* Always use the libcap API functions.
*/
caps = cap_get_proc();
if (caps == NULL) {
perror("cap_get_proc");
exit(EXIT_FAILURE);
}
/*
* cap_to_text() converts the entire cap_t structure to a compact
* human-readable string using the POSIX 1003.1e notation.
*
* Examples:
* "" โ no capabilities at all
* "=ep" โ all capabilities in effective and permitted
* "cap_net_raw+ep" โ only CAP_NET_RAW in effective and permitted
* "cap_sys_time+p" โ CAP_SYS_TIME in permitted only
*/
text_form = cap_to_text(caps, NULL);
if (text_form != NULL) {
printf("\nCompact representation: [%s]\n", text_form);
cap_free(text_form);
}
/*
* Now print which capabilities are set in each of the three sets.
* We call print_set_status() three times, once per set.
*
* CAP_PERMITTED โ the maximum ceiling (what the process may have)
* CAP_EFFECTIVE โ currently active (what the kernel checks)
* CAP_INHERITABLE โ may be passed across exec()
*/
print_set_status(caps, CAP_PERMITTED, "Permitted");
print_set_status(caps, CAP_EFFECTIVE, "Effective");
print_set_status(caps, CAP_INHERITABLE, "Inheritable");
if (getuid() != 0) {
printf("\n (All sets empty for an unprivileged process with no file capabilities)\n");
printf(" Try: sudo ./show_all_cap_sets\n");
printf(" Or: sudo setcap \"cap_net_bind_service+ep\" ./show_all_cap_sets\n");
}
/*
* ALWAYS free the cap_t structure. cap_free() handles the internal
* memory correctly. Do NOT use free() on a cap_t.
*/
cap_free(caps);
printf("\n=== Done ===\n");
return 0;
}
Compact representation: [=ep] --- Permitted Set --- cap_chown SET cap_dac_override SET cap_dac_read_search SET cap_fowner SET cap_net_bind_service SET cap_sys_time SET ... (all ~40 capabilities listed as SET) --- Effective Set --- (same as Permitted โ all SET) --- Inheritable Set --- (empty by default even for root)
Permitted: The maximum ceiling โ a superset of what the process can ever have in effective or inheritable sets. Once a capability is dropped from permitted, it is permanently gone.
Effective: The currently active set โ what the kernel actually checks when the process attempts a privileged operation. A process can add/remove capabilities here as long as they remain in permitted.
Inheritable: Capabilities that may be carried across an exec() call, subject to masking by the file’s inheritable set.
The file effective bit handles two different types of programs. For capability-dumb programs (old programs that don’t know about capabilities), the bit is set to 1 so that all newly acquired permitted capabilities automatically become effective โ the program works without modification. For capability-aware programs, the bit is 0 so the effective set starts empty after exec, and the program explicitly raises only the capabilities it needs at the right time, following the principle of least privilege.
A child process created by fork() inherits exact copies of all three capability sets (permitted, effective, and inheritable) from its parent. The child starts with exactly the same capabilities as the parent at the moment fork() was called.
File capabilities are stored in a security extended attribute named security.capability on the executable file. Extended attributes (xattrs) are stored in the file’s i-node metadata. The CAP_SETFCAP capability is required to modify this attribute. You can view and set file capabilities using getcap(8) and setcap(8).
The file permitted set was formerly called “forced” โ these capabilities are forced into the process’s permitted set during exec, regardless of what the process already had.
The file inheritable set was formerly called “allowed” โ these are the capabilities the file allows to pass through from the process’s inheritable set into the new permitted set during exec. The old names directly describe the mechanism and are still useful for understanding the behavior.
The relationship is a direct analogy. In a set-user-ID-root program, you can temporarily drop root by calling seteuid() to a non-zero UID while keeping 0 in the saved set-user-ID (so you can restore it later). With capabilities, temporarily dropping a capability from the effective set (while keeping it in the permitted set) achieves the same goal at the granularity of a single capability. Permanently dropping from both effective AND permitted is analogous to permanently dropping root by setting both effective and saved set-user-ID to non-zero.
โ Part 2: Capabilities Table Next: Transformation During exec() โ
