Linux Capabilities Explained
Learn how Linux capability bits work, how to audit and replace setuid binaries with setcap/getcap, and how to use ambient capabilities in systemd services.
Before you start
- ▸Root or sudo access on the target system
- ▸Kernel 4.3 or newer for ambient capability support (any current LTS satisfies this)
- ▸Basic understanding of Linux process model and execve()
- ▸libcap2-bin / libcap package installed
Traditional Unix privilege is binary: a process either runs as root and can do anything, or runs as a normal user and is constrained. Linux capabilities break that model by splitting root's authority into around 40 discrete units—CAP_NET_BIND_SERVICE, CAP_SYS_TIME, CAP_KILL, and so on—that can be granted independently. This lets you give a binary only the specific privilege it needs and nothing more, dramatically reducing attack surface. Understanding capabilities is essential for auditing setuid binaries, hardening containers, and writing secure systemd services.
How Capability Sets Work
Every Linux process carries five capability sets. You need to understand all five to reason correctly about inheritance.
- Permitted (P): The ceiling. A process can never have an effective capability it lacks in permitted.
- Effective (E): What the kernel actually checks for privileged operations right now.
- Inheritable (I): Capabilities that survive an
execve()call—but only if the new binary also has them in its inheritable file set. - Bounding (B): A mask applied at
execve(). Capabilities not in the bounding set can never enter permitted, regardless of file capabilities. Root starts with a full bounding set; it can only be reduced, never expanded. - Ambient (A): Added in Linux 4.3. Capabilities in this set are inherited across
execve()without needing corresponding file capabilities—the practical solution for unprivileged services.
These sets interact through a transformation formula the kernel applies on every execve(). File capabilities stored in the binary's extended attributes add another three sets: permitted, inheritable, and a single-bit "effective" flag.
Viewing Capabilities
Process capabilities
Read any process's capability sets directly from /proc:
cat /proc/$$/status | grep Cap
Output (hex bitmasks, will vary):
# CapInh: 0000000000000000
# CapPrm: 0000000000000000
# CapEff: 0000000000000000
# CapBnd: 000001ffffffffff
# CapAmb: 0000000000000000
Decode a hex mask with capsh (part of libcap2-bin on Debian/Ubuntu, libcap on Fedora/Arch):
capsh --decode=000001ffffffffff
To inspect a running process by PID:
cat /proc/1234/status | grep Cap
capsh --decode=$(grep CapEff /proc/1234/status | awk '{print $2}')
File capabilities
File capabilities are stored in the security.capability extended attribute. Use getcap to read them:
# Single file
getcap /usr/bin/ping
# Recursive search from root (useful for auditing)
getcap -r / 2>/dev/null
A modern system might show:
# /usr/bin/ping cap_net_raw=ep
# /usr/bin/python3.11 cap_net_bind_service=eip
The letters after = encode which file sets the capability enters: effective, inheritable, permitted.
Setting File Capabilities with setcap
Install the tools if needed:
# Debian / Ubuntu
sudo apt install libcap2-bin
# Fedora / RHEL / Rocky
sudo dnf install libcap
# Arch
sudo pacman -S libcap
Replacing a setuid binary
The classic example is a custom web server that must bind to port 80. Without capabilities it runs as root or carries the setuid bit. With setcap:
sudo setcap cap_net_bind_service=ep /usr/local/bin/myserver
=ep places the capability in both the file's effective and permitted sets. When a normal user executes the binary, the kernel elevates just that one capability into the process's effective set. The process never gains full root.
Remove a capability cleanly:
sudo setcap -r /usr/local/bin/myserver
Important: setcap silently removes the setuid bit. If you add capabilities to a binary that was previously setuid root, verify afterwards with ls -l that you haven't left both in place. File capabilities and setuid-root interact unexpectedly: a setuid-root binary ignores file capabilities entirely—it just gets all capabilities from root's bounding set.
Capability flag syntax reference
| Operator | Meaning |
|---|---|
=ep | Set effective + permitted, clear inheritable |
=eip | Set effective + inheritable + permitted |
+p | Add to permitted only (no automatic raise to effective) |
-e | Remove from effective set only |
= | Clear all capability sets on the file |
Ambient Capabilities: Inheriting Across exec Without File Caps
Ambient capabilities (Linux ≥ 4.3) solve a real problem: you want a shell script or interpreter to run with a capability, but you cannot set file capabilities on every script. Ambient capabilities propagate across execve() automatically as long as the capability is also in the process's permitted and inheritable sets.
Grant ambient capabilities from a shell using capsh:
sudo capsh \
--caps="cap_net_bind_service+eip" \
--keep=1 \
--user=www-data \
--addamb=cap_net_bind_service \
-- -c "/usr/local/bin/myserver"
This drops to the www-data user but preserves cap_net_bind_service in the ambient set, so myserver inherits it even though it has no file capabilities of its own.
Capabilities in systemd Services
systemd exposes capability management natively. This is the preferred approach for daemons—no need for setuid or external wrappers.
# /etc/systemd/system/myserver.service
[Service]
User=www-data
Group=www-data
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
ExecStart=/usr/local/bin/myserver
NoNewPrivileges=yes
Key directives:
- AmbientCapabilities: Grants listed capabilities into the ambient set so they survive
execve()to the service binary without file capabilities. - CapabilityBoundingSet: Restricts the bounding set. The service can never escalate beyond what is listed here, even via setuid children.
- NoNewPrivileges: Equivalent to
PR_SET_NO_NEW_PRIVS. Prevents setuid, file capabilities, and seccomp bypasses from granting new privileges to any child process. Use this whenever you can.
sudo systemctl daemon-reload
sudo systemctl restart myserver
Auditing setuid Binaries on a Running System
Find all setuid and setgid binaries in one shot:
sudo find / -xdev \( -perm -4000 -o -perm -2000 \) -type f -ls 2>/dev/null
For each binary you find, ask: does it actually need all of root's privileges, or could a single capability replace the setuid bit? Common candidates include ping (CAP_NET_RAW), traceroute (CAP_NET_RAW), and custom daemons that bind low ports (CAP_NET_BIND_SERVICE).
Verification
After applying file capabilities, verify the full chain:
# Confirm file capabilities are set correctly
getcap /usr/local/bin/myserver
# Run as the target user and inspect the process
sudo -u www-data /usr/local/bin/myserver &
PID=$!
grep Cap /proc/$PID/status
capsh --decode=$(grep CapEff /proc/$PID/status | awk '{print $2}')
The effective set should contain only cap_net_bind_service and nothing else. If you see a full bitmask, the binary is still setuid root—check with ls -l.
Troubleshooting
- setcap returns "Operation not supported": The filesystem is mounted with
nosuidornouser_xattr. tmpfs and some network filesystems don't support xattrs. Move the binary to a local ext4/xfs/btrfs filesystem. - Capability not taking effect after setcap: Check whether the binary is a shell script—file capabilities only work on ELF executables. Use a small C wrapper or ambient capabilities via systemd instead.
- Python/Node scripts lose capabilities: Interpreter binaries (python3, node) don't automatically pass capabilities to scripts. Use the systemd
AmbientCapabilitiesapproach. - Service fails with "permission denied" despite AmbientCapabilities: Verify
CapabilityBoundingSetincludes the capability. Ambient can never exceed the bounding set. Also confirm the unit hasUser=set—ambient capabilities are irrelevant for services still running as root. - getcap -r hangs or errors on /proc /sys: Use
-r /with2>/dev/nulland consider limiting to/usr /bin /sbin /optfor faster audits.
Frequently asked questions
- Can file capabilities replace setuid root for all programs?
- No. Programs that need many different root privileges—like sudo itself or login—still require setuid root. File capabilities are most effective for narrow, single-purpose binaries that need only one or two capabilities.
- What is the difference between =ep and =eip on a file capability?
- =ep sets the capability in the file's effective and permitted sets so it activates immediately on exec. Adding i (=eip) also places it in the inheritable set, meaning processes that have it in their inheritable set can pass it to further child execs—necessary for capability-aware privilege delegation chains.
- Does NoNewPrivileges block file capabilities?
- Yes. Once a process or thread sets PR_SET_NO_NEW_PRIVS (or systemd sets it via NoNewPrivileges=yes), execve() will not grant new capabilities from file capability xattrs or from setuid bits on any child binary.
- Why don't capabilities work inside Docker containers by default?
- Docker drops most capabilities from the default bounding set for security. You can add specific ones with --cap-add, e.g., docker run --cap-add NET_BIND_SERVICE. Avoid --privileged, which restores the full set.
- How do I permanently remove a setuid bit and replace it with a capability across distribution upgrades?
- Package upgrades can restore the setuid bit. Use a post-install hook, a configuration management tool (Ansible, Puppet), or a systemd tmpfiles.d rule to reapply setcap after every package update for that binary.
Related guides
Manage Secrets with Ansible Vault
Encrypt Ansible secrets with AES-256 using ansible-vault: encrypt files and inline vars, automate with password files, and isolate group-level secrets with vault IDs.
AppArmor Explained
Learn how AppArmor profiles work, how to switch between enforce and complain mode, create new profiles, and diagnose access denials on Ubuntu, Debian, and Arch.
Apply CIS Benchmarks with OpenSCAP
Use OpenSCAP and scap-security-guide to evaluate, report on, and remediate Linux systems against CIS Benchmarks — covering install, eval, and automation.
How to Audit a Linux System with auditd
Set up auditd on Linux to track file access, syscalls, and privilege use. Covers persistent rules, file watches, ausearch, and aureport across major distros.