LinuxXXX · Linux Capabilities and PrivilegeCapabilities overview
Linux capabilities overview - the full list and categories
What you'll learn
- List capabilities by category
- Use getcap and setcap
- Understand capability bounding set
- Recognise capability trade-offs
Prerequisites
Verified against Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL 9.x · Rocky Linux 9.x · AlmaLinux 9.x · Linux kernel 6.1 LTS / 6.6 LTS · systemd 255+ · OpenSSH 8.7p1 (RHEL 9) / 9.6p1 (Ubuntu 24.04) · nftables 1.0.x · chrony 4.x · Pacemaker 2.1.x · Corosync 3.1.x · 2026-08-09
Linux has around 40 capabilities. This lesson covers the categories, the tools to manage them, and the trade-offs of using capabilities vs full root.
Capabilities by category
File and permissions
CAP_CHOWN: make arbitrary changes to file UIDs and GIDs (chown(2)). This is the capability that lets a process give a file away or take it over.CAP_DAC_OVERRIDE: bypass file read, write, and execute permission checks.CAP_DAC_READ_SEARCH: bypass file read permission checks and directory read/execute checks.CAP_FOWNER: bypass the checks on operations that normally require the process’s filesystem UID to match the file’s UID (chmod,utime, setting inode flags), excluding whatCAP_DAC_OVERRIDEandCAP_DAC_READ_SEARCHalready cover.CAP_FSETID: do not clear the set-UID and set-GID mode bits when a file is modified, and set the set-GID bit on a file whose GID does not match the caller’s. It has nothing to do with changing ownership - that isCAP_CHOWN.CAP_SETFCAP: set file capabilities on any file. Treat this as root-equivalent: a process holding it can write a capability onto a binary it controls and re-acquire anything the bounding set still permits.CAP_LINUX_IMMUTABLE: set the immutable and append-only inode flags.CAP_LEASE: establish leases on files.CAP_MKNOD: create device files withmknod(2).
Network
CAP_NET_ADMIN: network configuration - interfaces, routes, firewall rules, socket options. Broad; covers most network administration.CAP_NET_BIND_SERVICE: bind to ports < 1024.CAP_NET_BROADCAST: make socket broadcasts and listen to multicasts. Marked(Unused)in the man page - the kernel does not currently check it, so granting it buys nothing.CAP_NET_RAW: use RAW and PACKET sockets, and bind to any address for transparent proxying.
Process
CAP_KILL: send signals to processes of other users.CAP_SETPCAP: drop capabilities from the calling thread’s bounding set, and add capabilities to the inheritable set.CAP_SYS_PTRACE: trace processes (gdb, strace).CAP_SYS_CHROOT: use chroot.CAP_SYS_NICE: set nice values, scheduling policy and CPU affinity arbitrarily.CAP_SYS_RESOURCE: override resource limits.CAP_SYS_TIME: set system clock.CAP_SETUID: change UID.CAP_SETGID: change GID.
System
CAP_SYS_ADMIN: extensive; mount, swapon, key management, namespace operations, and many more. Effectively the “new root” - avoid granting it.CAP_SYS_BOOT: reboot.CAP_SYS_MODULE: load/unload kernel modules.CAP_SYS_RAWIO: raw I/O operations (/dev/mem, port I/O).CAP_IPC_LOCK: lock memory (mlock,mlockall, shared memory) - what a database needs to keep pages out of swap.CAP_MAC_ADMIN: change the MAC (Smack/SELinux) policy.CAP_MAC_OVERRIDE: bypass MAC policy enforcement.CAP_BPF: perform privileged BPF operations.CAP_PERFMON: use perf and other performance-monitoring interfaces.CAP_CHECKPOINT_RESTORE: checkpoint and restore processes (CRIU).CAP_SYSLOG: read the kernel log; configure syslog.CAP_AUDIT_WRITE: write to the audit log.CAP_AUDIT_CONTROL: enable/disable auditing and change the audit rules.CAP_AUDIT_READ: read the audit log.
The lists above are the ones you meet in production, not the
whole set. Generate the authoritative list for the kernel you
are actually running, and check each entry against
man 7 capabilities before you grant it:
capsh --print | grep '^Bounding set' | tr ',' '\n'
Capabilities used by services
| Service | Capability |
|---|---|
| Web server (bind 80) | CAP_NET_BIND_SERVICE |
| ping | CAP_NET_RAW |
| tcpdump | CAP_NET_RAW |
| cron | CAP_DAC_OVERRIDE (for /etc/cron.allow) |
| sshd | CAP_AUDIT_WRITE, CAP_SYS_CHROOT |
| dhclient | CAP_NET_ADMIN |
| mount | CAP_SYS_ADMIN |
| ping6 | CAP_NET_RAW |
Tools
# Read capabilities of a file
getcap /usr/local/bin/myservice
# /usr/local/bin/myservice cap_net_bind_service=ep
# Set capabilities on a file - a single-purpose binary only
sudo setcap cap_net_bind_service=+ep /usr/local/bin/myservice
# Remove
sudo setcap -r /usr/local/bin/myservice
# Decode a capability mask
capsh --decode=0000003fffffffff
# Drop capabilities for the current shell
sudo capsh --drop=cap_net_bind_service --print
Capability sets per process
For each process:
cat /proc/$$/status | grep Cap
# CapInh: 0000000000000000 # Inheritable
# CapPrm: 0000000000000000 # Permitted
# CapEff: 0000000000000000 # Effective
# CapBnd: 0000003fffffffff # Bounding
# CapAmb: 0000000000000000 # Ambient
Decode the bounding set to see what the process can ever acquire.
Bounding set
The bounding set is a per-thread attribute, and has been
since Linux 2.6.25. The old system-wide bounding set no longer
exists. It is inherited at fork(), preserved across
execve(), and it caps which capabilities a thread can ever
gain from a file’s permitted set.
A thread holding CAP_SETPCAP drops capabilities from its own
bounding set with prctl(PR_CAPBSET_DROP). The drop is
one-way: a capability removed from a bounding set cannot be put
back, and every descendant inherits the reduced set. Dropping
from one process’s bounding set has no effect on unrelated
processes on the host.
In a unit file the two are set together, because they defend different paths:
[Service]
User=www-data
CapabilityBoundingSet=CAP_NET_BIND_SERVICE # nothing else is ever obtainable
AmbientCapabilities=CAP_NET_BIND_SERVICE # grant it without setuid or file caps
NoNewPrivileges=true # separate control: no gain on execve
Verify what is actually in effect rather than trusting the file:
systemctl show -p CapabilityBoundingSet,AmbientCapabilities,NoNewPrivileges myservice
grep Cap /proc/$(systemctl show -p MainPID --value myservice)/status
capsh --decode=0000000000000400 # decode the CapBnd value you just read
systemd-analyze security myservice scores the whole sandbox and
names the directives you have not set yet.
For containers, drop capabilities the container does not
need before starting (docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE). That reduces the bounding set of
the container’s init thread, and every process in the container
inherits the reduced set.
File capabilities
A binary can have capabilities stored in its extended attributes (xattrs). When the binary is exec’d, the capabilities are added to the process.
sudo setcap cap_net_bind_service=+ep /usr/local/bin/myservice
getcap /usr/local/bin/myservice
# /usr/local/bin/myservice cap_net_bind_service=ep
The +ep means: in the Effective and Permitted sets after
exec.
The target is a single-purpose binary. Do not put file capabilities on an interpreter, a shell, or a packaged binary that a package upgrade will silently replace - an upgrade rewrites the inode and drops the xattr, so the service starts failing at the next patch window with no change record to explain it.
Grant capabilities to the service, not the file
For anything managed by systemd, AmbientCapabilities= is the
better tool. It grants the capability to that service instance
only, so it survives package upgrades and gives nothing to the
users who can execute the same binary:
# /etc/systemd/system/myservice.service.d/override.conf
[Service]
User=myservice
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
AmbientCapabilities= requires a non-empty
CapabilityBoundingSet= containing the same capability;
systemd raises the ambient set for the service, and the
bounding set caps what it can ever hold.
If the only requirement is a low port, skip capabilities altogether and lower the privileged-port threshold:
sudo sysctl -w net.ipv4.ip_unprivileged_port_start=80
# Persist in /etc/sysctl.d/99-unprivileged-ports.conf
setuid vs file capabilities
| setuid | File capability | |
|---|---|---|
| Privilege granted | Full root | Specific capability |
| Use case | Legacy compat | Modern services |
| Blast radius | All of root | Just the capability |
| Audit | Easy (setuid bit) | Requires getcap |
Replace setuid with file capabilities where possible. A
service that previously required setuid root can run as a
non-root user with just the capabilities it needs.
Knowledge check
Knowledge check · 4 questions
Q1. Which capability allows binding to port 80?
Q2. setuid binaries should be preferred over file capabilities.
Q3. Which of the following are valid capabilities? Select all that apply.
Q4. A change ticket says "hardened: CAP_SYS_ADMIN and CAP_SYS_MODULE can no longer be acquired by the service". The diff adds only NoNewPrivileges=true to the unit. What is the state of the service after the deploy?
Passing score: 75%. Answers are checked in this browser.