Skip to content

CL-0017: Shared bind-mount propagation

Severity: LOW

Derivation (see severity model):

  • Baseline: A — the attacker already has code execution in this container, as the workload uid
  • Precondition: Second flaw — the leg that works unaided needs an event this file does not control: the host operator mounting something under the shared path after the container starts
  • Impact: Single container — what propagates in becomes visible to this container
  • Qualifier/modifier: read-only — the working leg conveys visibility of a host mount, not the ability to write to the host
  • Derived: Second flaw × Single container + read-only = LOW
  • Shipped: LOW
  • Scoping assumptions: Scored on the host → container leg. The container → host leg needs CAP_SYS_ADMIN, scored by CL-0024; on an AppArmor host it needs an unconfined profile as well, scored by CL-0009. Either way that leg depends on a key another rule owns, which is why this rule is scored on the leg that needs nothing
  • Evidence: _cl0017 proves only that a shared bind propagation is flagged shared: in the container's mountinfo — it does not test either propagation leg. The two legs are captured observations, no premise check yet. Propagation (kernel 6.18.29, LSM set capability,landlock,lockdown,yama,bpf — no AppArmor, deliberately, so kernel semantics are isolated from Docker's LSM gate): with the mount in a shared peer group, a mount made on the host side appears in the second namespace and one made in that namespace appears on the host side; under --make-private neither does, which is the negative control. Docker's gates (Docker 29.1.3 on Debian 13, AppArmor enforcing): the container → host leg additionally needs CAP_SYS_ADMIN and an unconfined AppArmor profile — mount is refused at defaults, refused with --cap-add SYS_ADMIN alone, refused with apparmor=unconfined alone, and succeeds only with both. The AppArmor half is posture-specific: re-measured on a host with no AppArmor policy loaded, --cap-add SYS_ADMIN alone mounts (ADR-020 grounds the docker-default profile as present, so this is captured evidence with its posture named rather than a fact that holds anywhere). Those two gates are flagged separately by CL-0024 and CL-0009, which is why this rule is scored on the host → container leg: it is the one that needs nothing

References: - CIS Docker Benchmark 5.20 — Ensure mount propagation mode is not set to shared

What it detects

Services using shared mount propagation in either syntax: - Short syntax: :shared suffix (e.g., /data:/app/data:shared) - Long syntax: bind.propagation: shared

Other propagation modes (rprivate, rslave, etc.) and mounts without explicit propagation are not flagged.

Why it matters

Shared mount propagation means mounts created inside the container are visible on the host, and mounts created on the host are visible inside the container. This breaks mount namespace isolation and can be exploited to: - Mount arbitrary filesystems that become visible on the host - Access newly mounted host filesystems that were not present when the container started - Escape container confinement by creating strategic mount points

Fix

Remove :shared or change the propagation to rprivate (the Docker default):

# Before — short syntax
services:
  app:
    image: myapp:1.0
    volumes:
      - /data:/app/data:shared

# After
services:
  app:
    image: myapp:1.0
    volumes:
      - /data:/app/data

# Before — long syntax
services:
  app:
    image: myapp:1.0
    volumes:
      - type: bind
        source: /data
        target: /app/data
        bind:
          propagation: shared

# After
services:
  app:
    image: myapp:1.0
    volumes:
      - type: bind
        source: /data
        target: /app/data
        bind:
          propagation: rprivate

If your workload requires mount visibility between host and container (e.g., CSI drivers), use rslave instead of shared — it allows host-to-container propagation without the reverse.

rslave is a partial fix, not a clean one: a malicious or compromised host process can still inject mounts into the container at sensitive in-container paths, which the workload may then read or execute from. If the only reason you reach for shared/rslave is symmetric debugging, consider whether the bind mount needs propagation at all (rprivate is almost always correct).

When to suppress

  • CSI / Kubernetes node plugins that genuinely require bidirectional propagation to operate.
  • Specific Docker-in-Docker setups where the inner daemon's bind mounts need to be visible on the host. Suppress with a reason: naming the workload; never suppress globally.

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-0013 — sensitive host paths (the surface a shared propagation can newly expose)