Linux Capabilities: Fine-Grained Privileges Beyond root and setuid
Understand Linux capability sets, exec transitions, file attributes, systemd and container boundaries, with a least-privilege audit workflow.
Linux capabilities split many privilege checks formerly associated with effective UID 0 into named bits. They reduce authority only when the complete execution transition, user namespace, bounding set, filesystem, and surrounding controls agree. “Not root” is not proof of low privilege, and one broad capability can still be enough for system compromise.
Capabilities are a least-privilege mechanism, not a sandbox by themselves.
Privilege checks are per thread
The kernel capability model is attached to threads, although processes normally begin with matching thread credentials. A privileged operation checks the relevant capability in the user namespace governing the target resource. UID 0 retains special handling unless securebits and namespace rules alter it.
The authoritative list is the running kernel plus capabilities(7). New capabilities have been split from CAP_SYS_ADMIN over time, so a hard-coded count becomes stale. Inspect the highest supported number with:
cat /proc/sys/kernel/cap_last_cap
Five sets determine usable authority
The model is larger than permitted/effective/inheritable:
- Permitted is the ceiling from which a thread can make capabilities effective.
- Effective is the set used for ordinary capability checks now.
- Inheritable participates in capability calculation across
execve()with file inheritable bits. - Bounding limits capabilities gained through file permitted sets; it is inherited and can be irreversibly reduced for a thread lineage.
- Ambient carries selected capabilities across execution of non-privileged programs, but only while they remain permitted and inheritable; executing a privileged file clears ambient capabilities.
There are also file permitted/inheritable sets plus one file effective flag. The execve() transformation rules—not an informal “inherit capabilities” description—determine the result.
Read the actual sets
/proc/<pid>/status exposes hexadecimal masks:
grep -E '^Cap(Inh|Prm|Eff|Bnd|Amb):' /proc/"$pid"/status
capsh --decode=0000000000000400
Use a PID you verified, account for PID namespaces, and inspect each relevant thread under /proc/<pid>/task/ when software changes credentials per thread. getpcaps <pid> and capsh --print from libcap can provide readable views. Access may be restricted by ptrace policy, namespaces, or procfs mount options.
Capability names differ radically in power
CAP_NET_BIND_SERVICE permits binding Internet-domain privileged ports, but the threshold is affected by the network namespace’s net.ipv4.ip_unprivileged_port_start. The old “ports below 1024 always need this” rule is not universal.
CAP_DAC_OVERRIDE bypasses many discretionary file checks. CAP_SYS_PTRACE can expose process memory. CAP_SYS_MODULE can affect kernel code. CAP_SYS_ADMIN covers a famously broad set of operations and is treated by the man page as overloaded; granting it often defeats the intended least-privilege exercise.
Always map a capability to the exact syscalls/resources used by the workload and its namespace.
File capabilities are extended attributes
setcap writes security.capability metadata to an executable. Inspect first:
getcap -r /usr/local/bin 2>/dev/null
getcap /usr/local/bin/mydaemon
An administrator might grant a controlled copy:
setcap 'cap_net_bind_service=ep' /usr/local/libexec/mydaemon
Do not test on a vendor-managed binary: upgrades can replace it, and adding privilege to an executable writable by an untrusted user creates an escalation path. Verify owner, mode, mount options, hash, signature/package provenance, interpreter behavior, and whether the filesystem preserves capability xattrs. Keep a rollback command and package-safe deployment method.
no_new_privs changes execution
When a thread sets no_new_privs, execve() promises not to grant privilege through setuid/setgid bits or file capabilities. This is a powerful hardening primitive used by sandboxing systems. Inspect it in /proc/<pid>/status and use service-manager controls rather than assuming a file capability will take effect.
Setuid transitions, securebits, filesystem UID, and capability-aware program logic also affect the result. Test the real service launch path, not only executing the binary from an administrator shell.
systemd can bound privileges without modifying files
For a service, systemd properties are often more auditable than persistent file xattrs:
[Service]
User=mydaemon
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
NoNewPrivileges=yes
CapabilityBoundingSet= removes everything not listed from the service’s bounding set; AmbientCapabilities= can supply a needed capability to a non-privileged executable. Empty/negated syntax and repeated directives have specific semantics, so validate with the current systemd.exec manual and systemd-analyze security.
Capabilities should accompany filesystem, device, syscall, namespace, and address-family restrictions—not replace them.
dropping is stronger than merely disabling
Removing a bit from Effective temporarily is not the same as removing it from Permitted or Bounding. A program may re-enable a permitted capability. For a permanent worker process, drop unnecessary bounding/permitted/ambient capabilities before parsing untrusted input, and verify after the transition.
Privilege separation is stronger when a small, reviewed helper performs one operation and an unprivileged process handles complex input. Do not let the helper accept arbitrary paths, network namespaces, or command fragments.
user namespaces change the reference point
A capability is meaningful relative to a user namespace. A process may appear UID 0 and hold capabilities inside a container’s user namespace without possessing those capabilities in the initial host user namespace. Conversely, a container sharing host namespaces or receiving powerful devices/capabilities can affect host resources.
Audit namespace links and mappings:
readlink /proc/"$pid"/ns/user
cat /proc/"$pid"/uid_map
cat /proc/"$pid"/gid_map
Do not conclude “safe root” or “host root” from the displayed UID alone.
container capability policy is only one layer
OCI runtimes model bounding, effective, inheritable, permitted, and ambient sets. Runtime defaults differ by product and release. Inspect the created container rather than copying a dated default list.
Start with all capabilities dropped and add only measured requirements where feasible. Avoid --privileged: implementations generally expand capabilities and relax devices and other isolation controls. A narrowly added CAP_SYS_ADMIN may still be an unacceptable escape surface even without that flag.
Seccomp, LSM policy, user namespaces, read-only mounts, device rules, and cgroups remain independent controls.
capability failures need syscall evidence
An EPERM does not automatically identify a missing capability; DAC, mount flags, seccomp, SELinux/AppArmor, immutable attributes, lockdown, or namespace ownership may deny the operation. Capture the failing syscall with an approved tracing method, then map it to the documented check.
Do not respond by adding capabilities one by one until the error disappears. That accumulates unexplained authority and can mask a path or policy bug.
audit file and service drift
Inventory file capabilities across expected local paths and package-managed trees, compare with a known-good manifest, and investigate additions:
getcap -r /usr /opt /usr/local 2>/dev/null
systemctl show mydaemon -p User -p NoNewPrivileges \
-p CapabilityBoundingSet -p AmbientCapabilities
Recursive scans can be expensive and cross mounts; scope them deliberately. Record xattrs in backups because archive/copy tools may omit them. After an upgrade, verify that intended xattrs remain and no obsolete privileged copy survives.
build a least-privilege acceptance test
Test the required operation, one explicitly forbidden file operation, one forbidden network/admin operation, child execution, restart, upgrade replacement, and rollback. Capture all five sets, user namespace, UID/GID maps, LSM label, seccomp status, executable hash/owner/mode, and service properties.
The design succeeds when the workload performs its one documented privileged action, cannot perform representative adjacent actions, cannot regain dropped authority across execve(), and an administrator can explain every remaining bit.
Related:
- Linux Namespaces: The Kernel Primitive Behind Every Container
- eBPF Explained: Safe, Programmable Observability in the Linux Kernel
Sources: