# REPRO-2026-00206: runc symlink deletion via malicious /dev symlink in container image ## Summary Status: published Severity: low Type: security Confidence: high ## Identifiers REPRO ID: REPRO-2026-00206 CVE: CVE-2026-41579 ## Package Name: Unknown Ecosystem: Unknown Affected: Unknown Fixed: Unknown ## Root Cause # CVE-2026-41579 — Root Cause Analysis ## Summary CVE-2026-41579 is a low-severity host filesystem integrity issue in opencontainers/runc. When runc prepares a container rootfs, the functions `setupPtmx` and `setupDevSymlinks` operate on path strings under the bundle rootfs before `pivot_root(2)` occurs. If the container image has `/dev` as a symlink that points outside the rootfs (for example to a host directory controlled by the attacker), `filepath.Join(rootfs, "/dev/ptmx")` resolves through the symlink and runc deletes or re-creates files on the host. A malicious image can therefore trick runc into removing an existing file named `ptmx` and creating a small fixed set of device symlinks in an attacker-chosen host directory. ## Impact - **Package / component:** opencontainers/runc - **Affected versions:** prior to 1.3.6, 1.4.0-rc.1 through 1.4.3, and 1.5.0-rc.1 through 1.5.0-rc.3 - **Risk level:** low (per upstream advisory) - **Consequences:** Arbitrary deletion of a host file named `ptmx` and creation of a limited set of hardcoded symlinks in a host directory reachable via a malicious `/dev` symlink. Not exploitable under Docker, but exploitable via other runc-based runtimes that do not mask `/dev` with a top-level read-only layer. ## Impact Parity - **Disclosed / claimed maximum impact:** Arbitrary file deletion and symlink creation on the host filesystem through a malicious container image (`/dev` symlink). - **Reproduced impact from this run:** Vulnerable runc deleted a decoy file named `ptmx` and replaced it with a symlink in an attacker-controlled directory; fixed runc left the decoy untouched. - **Parity:** `full` for the documented filesystem-integrity impact. The reproduction does not demonstrate privilege escalation or code execution, which is consistent with the advisory's low-severity rating. ## Root Cause The bug is in runc's rootfs preparation code. Before the container pivots into its rootfs, `setupPtmx` and `setupDevSymlinks` use `filepath.Join(rootfs, "/dev/...")` and then call `os.Remove` / `os.Symlink`. Because the operations happen before `pivot_root`, a `/dev` entry in the image that is a symlink to an attacker-controlled host directory is followed, causing the operations to affect the host path instead of the container rootfs. Upstream fix commit: - `opencontainers/runc@864db8042dbb` — "rootfs: make /dev initialisation code fd-based" The fix rewrites the `/dev` setup code to operate on file descriptors relative to the opened rootfs directory, so symlinks in the image cannot redirect the operations to host paths. ## Reproduction Steps The reproduction is implemented in `bundle/repro/reproduction_steps.sh`. At a high level it: 1. Verifies Docker is available. 2. Downloads the vulnerable runc release binary (`v1.3.5`) and the fixed release binary (`v1.3.6`). 3. Builds a minimal OCI rootfs from the official `busybox` image. 4. Builds two privileged Docker images (`repro-runc-vuln` and `repro-runc-fixed`) that each contain one runc binary and the rootfs. 5. Inside a privileged container, replaces `/bundle/rootfs/dev` with a symlink to `/controlled_dev` and creates a decoy `/controlled_dev/ptmx`. 6. Generates an OCI bundle with `runc spec`, disables the terminal, and sets the command to `/bin/true`. 7. Runs `runc run cve-ptmx-test -b /bundle`. 8. Checks whether the decoy file was deleted. Expected evidence: - **Vulnerable (1.3.5):** the `ptmx` decoy is removed and `/controlled_dev` contains symlinks such as `ptmx -> pts/ptmx`, `core -> /proc/kcore`, `fd -> /proc/self/fd`, etc. - **Fixed (1.3.6):** the `ptmx` decoy remains untouched and runc does not create host symlinks. ## Evidence - `bundle/logs/repro_vuln.log` — vulnerable runc 1.3.5 deletes the decoy and creates host symlinks. - `bundle/logs/repro_fixed.log` — fixed runc 1.3.6 preserves the decoy. - `bundle/logs/build_repro-runc-vuln.log` — Docker build log for the vulnerable image. - `bundle/logs/build_repro-runc-fixed.log` — Docker build log for the fixed image. - `bundle/repro/runtime_manifest.json` — runtime evidence manifest produced by the script. Key excerpts: Vulnerable run: ```text RUN_VERSION: runc version 1.3.5 BEFORE: /controlled_dev/ptmx present? -rw-r---- 1 root root 10 ... ptmx ... AFTER: /controlled_dev contents: -rw-r--r-- ... ptmx RESULT: decoy deleted ``` Fixed run: ```text RUN_VERSION: runc version 1.3.6 BEFORE: /controlled_dev/ptmx present? -rw-r--r-- ... ptmx ... AFTER: /controlled_dev contents: -rw-r--r-- ... ptmx RESULT: decoy preserved ``` ## Recommendations / Next Steps - Upgrade runc to a patched version: **1.3.6**, **1.4.3**, or **1.5.0** (or later). - Higher-level runtimes that consume runc should ensure container images cannot ship a `/dev` symlink that resolves to a host path, or rely on the patched runc version. - Regression tests should include a rootfs where `/dev` is a symlink to a controlled host directory and verify that `setupPtmx`/`setupDevSymlinks` do not operate on the host path. ## Additional Notes - The script is idempotent: it re-downloads only missing binaries, rebuilds the Docker images each run, and uses unique container names. - The reproduction uses the real `runc` CLI binary and the real OCI bundle execution path (`runc run`), not a reimplemented parser or mocked environment. - The Docker-in-Docker privileged container is required in this sandbox because the host environment lacks `CAP_SYS_ADMIN` and a writable cgroup hierarchy; inside the privileged container runc has the capabilities needed to create a genuine container. - No sanitizer or crash is involved; the proof relies on the filesystem state difference between the vulnerable and fixed versions. ## Reproduction Details Reproduced: 2026-07-02T19:38:13.955Z Duration: 1521 seconds Tool calls: 181 Turns: Unknown Handoffs: 2 ## Quick Verification Run one of these commands to verify locally: pruva-verify REPRO-2026-00206 pruva-verify CVE-2026-41579 Or open in GitHub Codespaces (zero-friction, auto-runs): https://github.com/codespaces/new?ref=repro/REPRO-2026-00206&repo=N3mes1s/pruva-sandbox Or download and run the script manually: curl -O https://api.pruva.dev/v1/reproductions/REPRO-2026-00206/artifacts/bundle/repro/reproduction_steps.sh chmod +x reproduction_steps.sh ./reproduction_steps.sh WARNING: Run in a sandboxed environment. This exploits a real vulnerability. ## References - NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-41579 - Source: opencontainers/runc ## Artifacts - bundle/repro/reproduction_steps.sh (reproduction_script, 7731 bytes) - bundle/repro/rca_report.md (analysis, 5749 bytes) - bundle/vuln_variant/reproduction_steps.sh (reproduction_script, 12086 bytes) - bundle/vuln_variant/rca_report.md (analysis, 11485 bytes) - bundle/ticket.md (ticket, 892 bytes) - bundle/ticket.json (other, 1305 bytes) - bundle/repro/validation_verdict.json (other, 733 bytes) - bundle/repro/runtime_manifest.json (other, 587 bytes) - bundle/logs/build_repro-runc-vuln.log (log, 1448 bytes) - bundle/logs/build_repro-runc-fixed.log (log, 1449 bytes) - bundle/logs/repro_vuln.log (log, 985 bytes) - bundle/logs/repro_fixed.log (log, 572 bytes) - bundle/logs/vuln_variant/inner_scripts/relative_dev_symlink_vuln.sh (other, 1848 bytes) - bundle/logs/vuln_variant/inner_scripts/relative_dev_symlink_fixed.sh (other, 1848 bytes) - bundle/logs/vuln_variant/inner_scripts/pts_symlink_vuln.sh (other, 1873 bytes) - bundle/logs/vuln_variant/inner_scripts/pts_symlink_fixed.sh (other, 1873 bytes) - bundle/logs/vuln_variant/inner_scripts/create_start_entrypoint_vuln.sh (other, 1856 bytes) - bundle/logs/vuln_variant/inner_scripts/create_start_entrypoint_fixed.sh (other, 1856 bytes) - bundle/logs/vuln_variant/relative_dev_symlink_vuln.log (log, 1248 bytes) - bundle/logs/vuln_variant/relative_dev_symlink_fixed.log (log, 823 bytes) - bundle/logs/vuln_variant/pts_symlink_vuln.log (log, 1038 bytes) - bundle/logs/vuln_variant/pts_symlink_fixed.log (log, 1038 bytes) - bundle/logs/vuln_variant/create_start_entrypoint_vuln.log (log, 1270 bytes) - bundle/logs/vuln_variant/create_start_entrypoint_fixed.log (log, 845 bytes) - bundle/logs/vuln_variant/eval_relative_dev_symlink.txt (other, 51 bytes) - bundle/logs/vuln_variant/eval_pts_symlink.txt (other, 43 bytes) - bundle/logs/vuln_variant/eval_create_start_entrypoint.txt (other, 54 bytes) - bundle/logs/vuln_variant/vulnerable_version.txt (other, 418 bytes) - bundle/logs/vuln_variant/fixed_version.txt (other, 794 bytes) - bundle/vuln_variant/runtime_manifest.json (other, 790 bytes) - bundle/vuln_variant/patch_analysis.md (documentation, 9628 bytes) - bundle/vuln_variant/source_identity.json (other, 875 bytes) - bundle/vuln_variant/variant_manifest.json (other, 3060 bytes) - bundle/vuln_variant/validation_verdict.json (other, 1049 bytes) - bundle/vuln_variant/root_cause_equivalence.json (other, 1851 bytes) ## API Access - JSON: https://api.pruva.dev/v1/reproductions/REPRO-2026-00206 - Script: https://api.pruva.dev/v1/reproductions/REPRO-2026-00206/artifacts/bundle/repro/reproduction_steps.sh - Web: https://pruva.dev/r/REPRO-2026-00206 ## For AI Assistants To help users with this reproduction: 1. The `pruva-verify` command is the simplest way to run the reproduction 2. The script downloads and runs automatically in an isolated temp directory 3. Always recommend running in a VM, container, or disposable environment 4. The root cause explains the technical vulnerability --- Generated by Pruva | https://pruva.dev