Linux capabilities: the pieces of root

The handful that actually decide an escape.

Advanced14 min · lesson 3 of 25

Classic Unix had a binary: root (UID 0) could do everything, everyone else almost nothing. Linux capabilities shattered that all-or-nothing power into ~40 discrete privileges — bind a low port, load a kernel module, change file ownership, trace another process — each grantable independently. A container does not run with full root power even when it runs as UID 0; it gets a default subset, and the whole game of runtime hardening is dropping what it does not need.

terminal
# what a default container actually holds (a subset of full root)
$ docker run --rm alpine sh -c 'apk add -q libcap; capsh --print' | grep Current
Current: cap_chown,cap_dac_override,cap_fowner,cap_net_bind_service,cap_setuid,cap_setgid,cap_kill,...
# ~14 by default — not the full ~40 root would have on the host

The dangerous handful

A few capabilities are escape-grade and should almost never be added. SYS_ADMIN is so broad — it gates mount and dozens of kernel operations — that it is nicknamed “the new root,” and mount alone is often enough to break out. SYS_PTRACE lets a process inspect and hijack others. SYS_MODULE loads kernel code, game over. NET_ADMIN reconfigures networking. DAC_READ_SEARCH bypasses file permission checks. If a manifest adds any of these “to be safe,” that is the thing to challenge.

Capabilities, triaged
escape-grade — almost never add
SYS_ADMIN
mount + dozens of ops — "the new root"
SYS_MODULE
load kernel code — total compromise
SYS_PTRACE
inspect/hijack other processes
DAC_READ_SEARCH
bypass file permission checks
the ONE you often legitimately add
NET_BIND_SERVICE
bind a port < 1024 without root
The pattern is always the same: --cap-drop ALL, then --cap-add only the specific one a proven need requires.
terminal
# least privilege: drop everything, add back only what is needed
$ docker run --rm --cap-drop ALL --cap-add NET_BIND_SERVICE nginx:1.27 \
sh -c 'apk add -q libcap; capsh --print' | grep Current
Current: cap_net_bind_service # exactly one capability remains
Non-root does not mean no capabilities
Capabilities and the UID are separate axes: a container can run as UID 0 with all caps dropped (mostly harmless) or as a non-root UID that was granted SYS_ADMIN (dangerous). Hardening needs both — drop capabilities AND run non-root — because either one alone leaves a path an attacker can take.