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:
_cl0017proves only that a shared bind propagation is flaggedshared: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 setcapability,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-privateneither does, which is the negative control. Docker's gates (Docker 29.1.3 on Debian 13, AppArmor enforcing): the container → host leg additionally needsCAP_SYS_ADMINand an unconfined AppArmor profile —mountis refused at defaults, refused with--cap-add SYS_ADMINalone, refused withapparmor=unconfinedalone, and succeeds only with both. The AppArmor half is posture-specific: re-measured on a host with no AppArmor policy loaded,--cap-add SYS_ADMINalone mounts (ADR-020 grounds thedocker-defaultprofile 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)