# Container Security: Why Your Non-Root Container Drops Its Capabilities (SCC, CRI-O, Kernel)

> ANALYSIS — Why CAP_NET_RAW is missing in a non-root container: the SCC → CRI-O → kernel pipeline, the five capability sets (Bnd/Prm/Eff/Inh/Amb), the UID 0 → non-root transition and the real fixes (file capabilities, KEP 2763 ambient capabilities).

- **Date de publication** : 2026-10-06T08:30:00.000Z

---

A monitoring sidecar runs as non-root and needs ICMP health checks: it requires `CAP_NET_RAW`. The SecurityContextConstraint (SCC) allows the capability, the PodSpec requests it (`capabilities.add: [NET_RAW]`), CRI-O starts the container without error — and yet `ping` fails:

```
$ ping -c 1 10.0.0.1
ping: Operation not permitted
$ cat /proc/1/status | grep Cap
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 0000000000002000
```

This is not a bug. The scenario, documented by the **Red Hat Developer Blog** on 5 October 2026, is the collision of three systems that do not talk to each other: **SCC** admission on the API server, **CRI-O** translating the CRI request into an OCI spec, and **Linux capability semantics** in the kernel. Knowing where the SCC ends and the kernel begins is the whole art of debugging container privileges.

---

### 1. An SCC Is an Admission Gate, Not a Runtime Engine
An SCC only decides whether a security configuration is **allowed into** the cluster (permitted capabilities, UID ranges, volume types, privilege escalation, SELinux context). It is evaluated by the OpenShift API server admission controller when the Pod is created. Once the Pod is admitted, **the SCC's job is done**: it has no runtime component, does not talk to CRI-O, writes no iptables rules and configures no process capability.

* **At cluster scope:** SCCs are far more granular than Pod Security Admission (PSA) — PSA works on a coarse 3-tier model (privileged, baseline, restricted) whereas SCCs control field by field.

---

### 2. CRI-O: From SCC to OCI Spec, Where It Is Decided
CRI-O has no concept of SCCs: it receives a `CreateContainerRequest` and translates it into a runtime spec. Simple fields (hostNetwork, hostPID, hostIPC) map directly to namespace modes. Capabilities go through `SpecSetPrivileges()` and diverge by mode:

* **Privileged containers:** `SetupPrivileged(true)` populates all **five sets** with every known capability (41 at the time of writing). Note: the PodSpec's `dropCapabilities` field is then **ignored** — the add/drop logic is entirely bypassed.
* **Non-privileged containers:** CRI-O starts from a list and merges in **9 default capabilities** (`CHOWN`, `DAC_OVERRIDE`, `FSETID`, `FOWNER`, `SETGID`, `SETUID`, `SETPCAP`, `NET_BIND_SERVICE`, `KILL`), unless the PodSpec declares `drop: ALL`. This is exactly why the `drop: ALL` + `add: [one capability]` pattern is the production best practice: you restart from a clean, minimal set.

CRI-O then writes the final capability into **Bounding, Effective and Permitted** — **identically** whether the container runs as root or not. The runtime outcome, however, will be radically different.

---

### 3. Kernel: Five Capability Sets and a UID Transition That Wipes Them
Linux capabilities decompose the superuser model into distinct units spread across five sets, each evaluated differently by the kernel:

* **Bounding (CapBnd)** — the absolute ceiling of what a process may ever acquire.
* **Permitted (CapPrm)** — the pool from which effective capabilities can be drawn.
* **Effective (CapEff)** — what the kernel actually checks at runtime; without `CAP_NET_RAW` here, raw socket creation is denied regardless of what the other sets contain.
* **Inheritable (CapInh)** — capabilities carried across `execve()`, only when the binary also carries matching file capabilities.
* **Ambient (CapAmb)** — introduced in Linux 4.3 precisely to solve the non-root problem: they automatically repopulate Permitted and Effective after `execve()`.

The fatal mechanism: the OCI runtime starts the process as **root**, applies the sets, and **then switches to the target UID** (e.g. 1000). During that UID 0 → non-zero transition, **the kernel clears CapPrm and CapEff**. Only Bounding survives. And CRI-O **never** sets CapInh or CapAmb.

---

### 4. Why CRI-O Deliberately Skips Ambient Capabilities
This is not an oversight: it is a documented security decision in CRI-O's code. Kubernetes assumes that `runAsNonRoot` results in capabilities being dropped; enabling ambient capabilities would break that implicit contract. A **KEP (2763 – ambient capabilities)** does exist in SIG Security to make them selectively supportable — worth tracking, because it would change the game for every non-root sidecar needing fine-grained privilege.

---

### 5. The Real Fixes, and What They Cost
* **File capabilities (`setcap`)** — the most targeted approach: `setcap 'cap_net_raw=+ep' /usr/bin/ping` at image build time. The kernel promotes the capability into Permitted and Effective at exec, even for non-root, as long as it remains in the Bounding set. **Cost:** you must modify the image; file capabilities are stored as extended attributes, so the build pipeline and storage driver must preserve them — and trust shifts to **image provenance**.
* **Running as root** — avoids the UID transition. **Cost:** the most common production SCC (`restricted-v2`) forbids it.
* **Privileged container** — populates all five sets, but **disables SELinux, AppArmor and seccomp** and bypasses the entire add/drop logic: "a sledgehammer for a thumbtack". Never outside debugging.

**The engineering lesson:** the SCC, CRI-O and the kernel are three successive layers, each with its own mental model. Faced with `CapEff: 0000000000000000` on a non-root process, you are not hunting a bug — you are re-reading the UID transition and capability semantics.

---

### 6. Technical Sources & References
* **Red Hat Developer Blog — Why your non-root container dropped its capabilities (and how to fix it):** [developers.redhat.com](https://developers.redhat.com/articles/2026/10/05/why-your-non-root-container-dropped-its-capabilities-and-how-to-fix-it)
* **Linux man-pages — capabilities(7):** [man7.org](https://man7.org/linux/man-pages/man7/capabilities.7.html)
* **Kubernetes Enhancement Proposal 2763 — Ambient Capabilities:** [github.com/kubernetes/enhancements](https://github.com/kubernetes/enhancements/blob/master/keps/sig-security/2763-ambient-capabilities/README.md)
* **Red Hat Blog — Managing SCCs in OpenShift:** [redhat.com](https://www.redhat.com/en/blog/managing-sccs-in-openshift)

---

*Source : https://aperta.io/radar/2026-10-06-container-security-non-root-capabilities-scc-crio-kernel/ — Aperta Scientia Tech Radar*
