Demystifying Linux Capabilities
For decades, traditional Unix security relied on a binary model: you were either root (UID 0), with absolute power over the entire operating system, or you were an unprivileged user with restricted access.
To bridge this dangerous gap, the Linux kernel introduced Capabilities (back in 1999 with Linux 2.2). Instead of handing a process all-or-nothing root privileges, capabilities split root's monolithic power into granular, isolated units of privilege—such as CAP_NET_BIND_SERVICE (binding to ports below 1024), CAP_NET_RAW (crafting raw sockets), CAP_SYS_ADMIN (the catch-all system administration bit), and CAP_BPF (loading eBPF programs).
This post breaks down how Linux capabilities actually work, covering where they are set, who enforces them, whether they apply to the host or containers, and how they bridge the gap between files on disk and running processes in memory.
Are Capabilities Per File or Per Process?
The short answer is both, but at different stages of execution. Capabilities exist in two distinct contexts:
File Capabilities (Stored On Disk)
You can attach capabilities directly to executable binaries on your hard drive using the setcap utility. These are stored as extended attributes (security.capability) in the filesystem metadata.
- What they do: They act as a permission blueprint. When an unprivileged user executes that binary, the kernel reads the file capabilities and grants those specific privileges to the newly created process.
- Classic Example: The standard
/bin/pingutility needs to create raw network sockets. On modern Linux systems,pingis run by normal users, but the binary hasCAP_NET_RAWstamped onto it (sudo setcap cap_net_raw+ep /bin/ping). When executed, the kernel gives that single process raw socket privileges without making the user root.
Process Capabilities (Stored In Kernel Memory)
Once a program is running, capabilities live per thread in kernel memory inside the thread's struct cred structure.
- A running process does not check the file on disk during its lifetime; instead, it holds active 64-bit bitmasks (
Effective,Permitted,Bounding, etc.) in kernel memory. - When the process makes a privileged system call (like
bpf()ormount()), the kernel checks these in-memory process capability bitmasks.
When a process calls execve() to launch a binary, the kernel calculates the new process's capabilities by combining the file capabilities on disk with the process's bounding set and user ID.
2. Where Are Capabilities Set?
Capabilities are configured at different layers depending on whether you are running a host service, a container, or a manual command:
- On Disk via
setcap: System administrators stamp capabilities onto executable files. - On Host Services via
systemd: In system service files, administrators use directives likeCapabilityBoundingSet=orAmbientCapabilities=to strip dangerous privileges from background daemons (like a web server or database) while granting specific needs. - In Container Workloads via Kubernetes / Docker: When you define a
securityContext.capabilitiesblock in a Kubernetes Pod manifest, that configuration passes through a translation chain:
- The Kubernetes API Server validates the request against Pod Security policies.
- The node's Kubelet and container runtime (
containerd/CRI-O) translate it into an OCI container specification. - The low-level OCI runtime (
runc/crun) calls low-level Linux syscalls (prctl()andcapset()) to set up the capability bitmasks on the container process right before callingexecve().
Inside kernel memory (struct cred), these are stored as 64-bit bitmasks representing each capability bit (for example, CAP_NET_ADMIN = 12, CAP_SYS_ADMIN = 21, CAP_BPF = 39). You can inspect the raw bitmasks of any running process by viewing /proc/<pid>/status and decoding them with capsh --decode=....
3. Who Enforces Capabilities?
The Linux kernel itself enforces capabilities.
Whenever your process makes a privileged system call (such as loading an eBPF program via bpf() or mounting a filesystem via mount()), the kernel executes internal security checks:
- The Subsystem Check (
capable()): Inside the kernel subsystem code, before executing the request, it callscapable(CAP_BPF)orns_capable(&init_user_ns, CAP_BPF). - The Bitmask Verification (
cap_capable()): The kernel checks the calling task'sEffectivecapability bitmask (current_cred()->cap_effective). If the required capability bit is raised (1), the kernel returns0(allowed). If not, it returns-EPERM(permission denied). - Secondary LSM Layers (SELinux / AppArmor / BPF LSM): Even if the capability bitmask check passes, the kernel checks active Linux Security Modules. If SELinux or AppArmor restricts the operation, the LSM overrides the result and blocks it.
4. Are Capabilities Only for Containers?
No. Capabilities are a core feature of host Linux, created in 1999—long before modern containers, Docker, or Kubernetes existed. Containers merely inherited and reused the host kernel's capability framework to sandbox isolated processes.
On a standard, non-containerized Linux host, capabilities are used heavily:
- File Capabilities: Allowing unprivileged users to run utilities like
pingortraceroutewithout root access. - Systemd Hardening: Locking down system daemons so they cannot compromise the host even if an attacker exploits a remote code execution vulnerability in the application.
- Host Daemons & Tooling: Privileged host monitoring tools, network packet sniffers (
tcpdump), container runtimes, and eBPF telemetry agents rely on host capabilities (CAP_NET_ADMIN,CAP_PERFMON,CAP_BPF) to interact with kernel subsystems safely without running as unrestrictedroot.
