Skip to content

CL-0024: Host-code-execution capabilities (ALL, SYS_ADMIN, SYS_MODULE, SYS_RAWIO)

Severity: CRITICAL

Derivation (see severity model):

  • Baseline: A — the attacker already has code execution in this container, as the workload uid
  • Precondition: Direct for the three mechanical members — SYS_MODULE loads a kernel module, SYS_RAWIO opens the raw I/O ports, ALL grants the full set — each reaching host code execution through supported calls, no technique and no second defect. SYS_ADMIN sits in this cell as a documented judgment call (its escape does need a technique — see below), not as a no-technique claim; both readings land at CRITICAL, so the cell is unaffected
  • Impact: Host
  • Qualifier/modifier: none
  • Derived: Direct × Host = CRITICAL
  • Shipped: CRITICAL
  • Scoping assumptions: SYS_ADMIN is placed here on consistency grounds rather than on a verified no-technique escape — see the note below. That affects which rule reports it, not what it derives: Technique × Host is also CRITICAL. The other three are mechanical
  • Evidence: _cl0011 proves only that cap_add deposits the capability into the effective set — not that any member reaches host code execution. Those reaches (module loading via SYS_MODULE, raw I/O port access via SYS_RAWIO, the full set via ALL) are kernel properties reasoned from the capability model and Docker's default seccomp profile, not exercised by a check

What it detects

cap_add entries naming a capability whose grant is host code execution:

Capability Grants
ALL every Linux capability — the capability half of privileged: true
SYS_ADMIN mount filesystems, configure namespaces; the historical home of most container-escape techniques
SYS_MODULE load and unload kernel modules — arbitrary code in kernel space
SYS_RAWIO raw I/O port access via iopl(2)/ioperm(2), granted by the capability alone. It also unlocks /dev/mem, though that path needs the device to be exposed as well and is bounded by CONFIG_STRICT_DEVMEM

CAP_-prefixed and lowercase spellings are equivalent and are matched the same way: Docker treats CAP_SYS_ADMIN and SYS_ADMIN as the same capability.

Why it matters

A container is not a security boundary against code running in the kernel. SYS_MODULE loads a module; SYS_RAWIO opens the raw I/O ports; ALL grants both and everything else. None of these needs a vulnerability — they are the capability working as designed, granted to a workload that should not have it.

SYS_ADMIN is a judgment call about membership, not about severity. Escaping with SYS_ADMIN does need a technique — mounting a cgroup hierarchy, abusing a filesystem, or similar — so on a strict reading of this rule's membership test (a capability reaching host code execution with no technique) it does not qualify. Its severity does not turn on that question: the matrix gives Technique × Host = CRITICAL exactly as it gives Direct × Host = CRITICAL, so SYS_ADMIN derives CRITICAL on either reading of the precondition axis.

It sits in this rule because it grants a superset of what the other three do in practice, and because a user triaging findings is not served by SYS_MODULE and SYS_ADMIN landing in different buckets. Grouping it here is a scale choice, documented as one — but it is not the reason it is CRITICAL. Relegating it to CL-0011 would score it HIGH only by also moving its impact axis to Cross-container, and a SYS_ADMIN escape reaches the host.

Fix

Remove the capability. Unlike the tiers below it, this one has no least-privilege reading — there is no subset of SYS_MODULE that is safe to hold:

# Before
services:
  app:
    image: myapp:1.0
    cap_add:
      - SYS_ADMIN

# After — drop everything, add back only what the workload proves it needs
services:
  app:
    image: myapp:1.0
    cap_drop:
      - ALL
    cap_add:
      - CHOWN

If the workload genuinely requires one of these — a kernel-module loader, a storage tool that manages block devices — it needs a virtual machine or a host process, not a container. CL-0006 documents the drop-and-observe method for finding the capability set a service actually needs.

When to suppress

Effectively never. cap_add: [ALL] in particular has no legitimate use: it is strictly worse than enumerating the capabilities a workload needs, and equal in blast radius to privileged: true (CL-0002) on the capability axis. If you must, suppress per service with a reason: naming the workload and the isolation that contains it.

ATT&CK coverage

Remediating this finding contributes to mitigating the following MITRE ATT&CK techniques (pinned to ATT&CK v18). compose-lint is a static analyser, so this is mitigation coverage — it detects nothing at runtime.

Technique Tactic
T1611 Escape to Host Privilege Escalation

See also

  • CL-0002privileged: true (a functional superset: these capabilities plus devices plus the profiles)
  • CL-0011 — strong host-adjacent capabilities (HIGH)
  • CL-0027 — bounded-grant capabilities (MEDIUM)
  • CL-0006 — the cap_drop: [ALL] baseline these entries re-open