← Back to Feed CACHED · 2026-10-02 05:29:37 · CACHE_KEY CVE-2026-41579
CVE-2026-41579 · CWE-61 · Disclosed 2026-07-01

runc /dev symlink following allows limited host filesystem integrity violations

ASSESSED — NOISGATE
Vendor
—
—
—
Reassessed
—
—
—
Verdict: —
Do you agree?
01 · The Real Story

Like someone sneaking a prank sign onto your office door — annoying and visible, but no keys were stolen and the locks still work

CVE-2026-41579 is a symlink-following flaw (CWE-61) in runc's setupPtmx and setupDevSymlinks functions. When runc initializes a container's /dev directory, it calls os.Remove and os.Symlink using filepath.Join on unsanitized paths. A malicious container image can set /dev itself as a symlink pointing to an arbitrary *pre-existing* host directory. runc then blindly deletes a file called ptmx in that directory and creates a hardcoded set of symlinks — core, fd, ptmx, stdin, stdout, stderr — with fixed targets (/proc/kcore, /proc/self/fd, /dev/pts/ptmx, /proc/self/fd/0, /proc/self/fd/1, /proc/self/fd/2). Affected versions: all runc prior to 1.3.6, all 1.4.x before 1.4.3, and all 1.5.0-rc.x before 1.5.0-rc.3. Docker is completely immune because it adds a read-only top layer that masks the malicious symlink. Podman and containerd (including the 95% of Kubernetes clusters using containerd as their CRI) do *not* add this layer and are vulnerable.

The vendor's LOW / 3.3 rating is honest and well-calibrated for a change. The impact ceiling is genuinely low: no confidentiality leak, no availability impact, no container escape, no privilege escalation, no arbitrary file write. The attacker cannot choose the symlink targets — they are hardcoded to standard /dev device paths. The only tangible outcome is dropping a handful of fixed symlinks into a pre-existing host directory or deleting a ptmx file that devpts will typically recreate. Even on a Kubernetes production node running containerd, exploiting this gives you graffiti, not a foothold. The broader containerd exposure justifies a slight nudge to 3.6, but this stays firmly in LOW territory.

"Low-impact symlink trick in runc: hardcoded targets, no escape, Docker immune"
02 · The Attack Path

4 steps from start to impact.

STEP 01

Craft malicious container image

The attacker builds a container image where the /dev directory entry is replaced with a symbolic link pointing to a target directory on the host filesystem, e.g., /dev → /etc/some-dir. This image must be published to a registry accessible to the victim. No special tooling beyond standard image-building utilities (buildah, docker build with a modified layer) is needed.
Conditions required:
  • Attacker can publish or inject an image into a registry the victim pulls from
  • Target directory on the host must already exist (symlink will not create it)
Where this breaks in practice:
  • Most enterprises restrict image sources to trusted registries via admission controllers (OPA/Gatekeeper, Kyverno)
  • Image signing (cosign/Notary) blocks unsigned or tampered images
  • Registry vulnerability scanning (Trivy, Grype, Snyk Container) can flag anomalous /dev symlinks in layers
Detection/coverage: Container image scanners that inspect layer contents for anomalous symlinks in /dev can flag this. No Nuclei template or dedicated scanner signature identified as of October 2026.
STEP 02

Victim pulls and runs the image

A user or automated pipeline (CI/CD, Kubernetes Deployment) must pull and start a container from the malicious image. The CVSS UI:R (User Interaction Required) reflects this — the victim must take an affirmative action to launch the container. In Kubernetes, this happens when a Pod spec references the malicious image.
Conditions required:
  • Victim uses Podman or containerd (Docker is immune)
  • No admission controller blocks the image
  • User or automation initiates run / Pod creation
Where this breaks in practice:
  • Docker users (dominant in dev environments) are completely unaffected
  • Kubernetes admission controllers with image-allow-listing prevent unknown images
  • User namespaces (rootless Podman, Kubernetes user namespace support) restrict writes to world-writable directories only
Detection/coverage: Pod creation events in audit logs; image pull events in containerd/CRI logs.
STEP 03

runc follows the /dev symlink during rootfs setup

During container initialization, runc's setupPtmx calls os.Remove(filepath.Join(rootfs, "/dev/ptmx")) and then os.Symlink to create the ptmx device link. Because /dev is a symlink to a host directory, filepath.Join resolves to the host path. Similarly, setupDevSymlinks creates symlinks for core, fd, stdin, stdout, stderr in the resolved host directory. runc does not validate that /dev is a real directory rather than a symlink.
Conditions required:
  • runc version is < 1.3.6, < 1.4.3, or < 1.5.0-rc.3
  • Runtime is not Docker (no read-only top layer)
Where this breaks in practice:
  • SELinux in enforcing mode (default on RHEL/Fedora) constrains runc's write targets via container_runtime_t policy
  • AppArmor profiles on Ubuntu restrict filesystem operations outside expected paths
Detection/coverage: SELinux AVC denials or AppArmor audit events for unexpected symlink/remove operations by the runc process.
STEP 04

Host directory modified with hardcoded symlinks

The target host directory now contains up to six symlinks (core → /proc/kcore, fd → /proc/self/fd, ptmx → /dev/pts/ptmx, stdin → /proc/self/fd/0, stdout → /proc/self/fd/1, stderr → /proc/self/fd/2) and may have had a file named ptmx deleted. These are standard /dev device paths — the attacker cannot control the symlink targets. The practical impact is host filesystem integrity violation with no path to code execution, data exfiltration, or privilege escalation.
Conditions required:
  • Steps 1-3 succeeded
  • Target directory is writable by the runc process
Where this breaks in practice:
  • The symlinks point to standard device paths — weaponizing them requires a separate, unrelated vulnerability in a host process that reads from the target directory
  • Deleted ptmx files are typically recreated by devpts mount
  • Impact is cosmetic / integrity-only — no confidentiality or availability effect
Detection/coverage: File integrity monitoring (AIDE, OSSEC, osquery) on host directories will detect unexpected symlink creation.
03 · Compensating Control

1
LOW 3.6→IGNORE 0.0
SEVERITY REDUCED
Enable admission controllers with image allow-listing — Deploy OPA/Gatekeeper or Kyverno policies that restrict Pod image sources to trusted, signed registries. This breaks the attack at Step 2 by preventing the malicious image from being scheduled. As a LOW-severity finding, there is no noisgate mitigation SLA — go straight to the 365-day remediation window for patching runc itself.
2
LOW 3.6→IGNORE 0.0
SEVERITY REDUCED
Enforce image signing verification (cosign/Notary) — Require all container images to carry a valid signature before the runtime will execute them. A malicious image crafted by the attacker won't have a valid organizational signature, blocking Step 1→2. Deploy within your standard policy rollout cycle.
3
LOW 3.6→LOW 2.0
Enable user namespaces on containerd/Podman — User namespaces remap container root to an unprivileged host UID, restricting symlink/delete operations to world-writable directories only. This degrades Step 4's impact from 'arbitrary pre-existing directory' to 'only world-writable directories,' which are rarely sensitive. Podman rootless mode does this by default.
4
LOW 3.6→LOW 2.4
Enforce SELinux or AppArmor in enforcing mode — On RHEL/Fedora, SELinux's container_runtime_t policy constrains runc's filesystem operations. On Ubuntu/Debian, AppArmor profiles achieve the same. This may block the symlink creation or deletion at Step 3 depending on the target directory's label. Verify your policy covers the specific paths.
5
LOW 3.6→IGNORE 0.0
SEVERITY REDUCED
Patch runc to 1.3.6 / 1.4.3 / 1.5.0-rc.3 — The definitive fix. Patches commits 864db8042dbb and 66acd48f9d42 convert the rootfs initialization code to fd-based operations that cannot follow symlinks. Per noisgate remediation SLA for LOW severity, patch as backlog hygiene — no hard deadline, but track and close within your regular update cycle.
What doesn't work
  • Network segmentation / firewall rules — This is a local vulnerability triggered by running a container image, not a network-based attack. Firewalls have no effect.
  • Runtime security tools (Falco, Sysdig) in default configuration — Default rulesets focus on container escape indicators (namespace manipulation, mount abuse, process injection). Creating standard /dev symlinks in a host directory is unlikely to trigger default rules without custom policies.
  • Read-only container filesystem (--read-only flag) — The vulnerable code runs *before* the container filesystem is mounted read-only. The symlink operations happen during rootfs setup, not at container runtime.
04 · Intelligence Metadata

The supporting signals.

In-the-Wild ExploitationNone observed. Not listed in CISA KEV. No campaigns, threat actor activity, or honeypot hits reported by GreyNoise or other CTI sources as of October 2026.
Proof-of-Concept AvailabilityNo public PoC identified. Checked pocindex.io, GitHub (no repos named CVE-2026-41579), ExploitDB, and Nuclei templates. The advisory describes the mechanism in detail, making a PoC trivial to construct with buildah or manual layer manipulation, but none has been published.
EPSS Score0.00186 (bottom ~15th percentile) — reflects near-zero predicted exploitation probability in the next 30 days.
CISA KEV StatusNot listed. No federal mandate to patch on an accelerated timeline.
CVSS VectorCVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N — Local access, low complexity, no privileges required but user interaction needed. Scope unchanged. Integrity-only impact (Low). No confidentiality or availability impact.
Affected Versionsrunc < 1.3.6, runc 1.4.0 through 1.4.2, runc 1.5.0-rc.1 through 1.5.0-rc.2. Affects Podman and containerd runtimes; Docker is immune (read-only top layer).
Fixed Versionsrunc 1.3.6, 1.4.3, 1.5.0-rc.3. Debian unstable: 1.3.6+ds1-1. Debian bookworm/trixie: still vulnerable as of October 2026. Patches: commits 864db8042dbb and 66acd48f9d42.
Exposure / Installed Baserunc underpins ~95% of Kubernetes clusters (via containerd) and all Podman installations. However, Docker's immunity covers the majority of developer workstations. Kubernetes production nodes are the primary exposed population. Not remotely exploitable — no Shodan/Censys exposure relevant.
Disclosure Timelineoss-security disclosure: 2026-06-13. CVE published: 2026-07-01. Patches available at disclosure.
Credited ResearchersDavias (initial finder), Arthur Chan, Junyi Liu, Derek Manzella.

Sources.

  1. GitHub Security Advisory GHSA-xjvp-4fhw-gc47
  2. oss-security disclosure (2026-06-13)
  3. NVD — CVE-2026-41579
  4. Debian Security Tracker
  5. GitHub Advisory Database
  6. Container Runtime Market Share Statistics 2026
  7. OpenCVE — CVE-2026-41579
  8. AWS ALAS — CVE-2026-41579
05 · The Call

Final Verdict
= UNCHANGED to LOW (3.6/10)

Why this verdict

  • Impact ceiling is genuinely negligible. The attacker can only create six hardcoded symlinks with fixed targets (standard /dev device paths) or delete a file named ptmx. No confidentiality leak, no availability disruption, no code execution, no container escape. This is filesystem graffiti, not a compromise.
  • User interaction required narrows the attack surface. A victim must explicitly pull and run the malicious image (UI:R). In enterprises with admission controllers, image signing, and registry allow-listing, this is a significant gate.
  • Docker immunity eliminates the largest runtime population. Docker's read-only top layer completely blocks the attack. Only Podman and containerd users are exposed.
  • Role multiplier: Kubernetes production nodes (containerd). The chain *does* succeed on Kubernetes nodes running containerd — and containerd is the default CRI for ~95% of K8s clusters. However, even on a production Kubernetes node, the blast radius is *host-local integrity violation only*: six hardcoded symlinks in one directory. This does NOT produce domain takeover, fleet compromise, mass data egress, or supply-chain pivot. The outcome is the same LOW-impact filesystem modification regardless of the host's role. The high-value-role floor does not trigger because the impact category (trivial integrity violation with fixed targets) does not match the floor-triggering outcomes.
  • Role multiplier: CI/CD workers (containerd/Podman). Same analysis — the chain can land on a CI runner, but creating /proc/kcore symlinks in a host directory doesn't give supply-chain write access or artifact tampering capability. No floor trigger.
  • Slight upward adjustment from 3.3 → 3.6 reflects that containerd's 95% Kubernetes share makes the reachable population larger than a Docker-centric framing implies, even though the impact remains minimal.

Why not higher?

The impact ceiling locks this in LOW. Even if every friction point is removed and the chain succeeds on the highest-value Kubernetes production node, the attacker gains the ability to place six *hardcoded* symlinks with *fixed, non-weaponizable* targets in a single pre-existing directory. There is no path from this primitive to container escape, RCE, privilege escalation, or data access without a separate, unrelated vulnerability. No PoC exists, EPSS is bottom-percentile, and no threat actor has shown interest.

Why not lower?

Containerd's dominance in Kubernetes means the reachable population is large — nearly every K8s node in production runs an affected runtime. The vulnerability is real and exploitable (not theoretical), the attack is low-complexity, and user namespaces / LSMs are not universally deployed. Dropping to IGNORE would understate the breadth of exposure, even if the depth of impact is shallow. It still warrants tracking and patching within the standard remediation window.

06 · Verification

Crowdsourced verification payload.

Run this script on each container host (Kubernetes node, Podman host) as any user with read access to the runc binary. Example: bash check_cve_2026_41579.sh. No root required.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# CVE-2026-41579 Checker — runc /dev symlink integrity violation
# Run on container hosts. No root required.
# Exit codes: 0=PATCHED, 1=VULNERABLE, 2=UNKNOWN

set -euo pipefail

RUNC_BIN=""
for candidate in runc /usr/bin/runc /usr/sbin/runc /usr/local/bin/runc /usr/local/sbin/runc; do
  if command -v "$candidate" &>/dev/null 2>&1; then
    RUNC_BIN="$candidate"
    break
  fi
done

if [ -z "$RUNC_BIN" ]; then
  echo "UNKNOWN — runc binary not found on this host."
  exit 2
fi

VERSION_OUTPUT=$("$RUNC_BIN" --version 2>/dev/null || true)
VERSION=$(echo "$VERSION_OUTPUT" | head -1 | grep -oP '[0-9]+\.[0-9]+\.[0-9]+(-rc\.[0-9]+)?' || true)

if [ -z "$VERSION" ]; then
  echo "UNKNOWN — could not parse runc version from: $VERSION_OUTPUT"
  exit 2
fi

echo "Detected runc version: $VERSION"

# Check if Docker is the sole higher-level runtime (Docker is immune)
DOCKER_ONLY=false
if command -v docker &>/dev/null && ! command -v podman &>/dev/null; then
  # Check if containerd is used outside Docker context
  if ! systemctl is-active --quiet containerd 2>/dev/null || \
     (docker info 2>/dev/null | grep -qi 'containerd'); then
    DOCKER_ONLY=true
  fi
fi

if $DOCKER_ONLY; then
  echo "NOTE: Docker detected as sole runtime. Docker is IMMUNE to CVE-2026-41579"
  echo "      (read-only top layer masks malicious /dev symlinks)."
  echo "PATCHED — Docker's architecture mitigates this vulnerability."
  exit 0
fi

# Version comparison function
ver_to_num() {
  local IFS=.
  local parts=($1)
  printf '%d%03d%03d' "${parts[0]:-0}" "${parts[1]:-0}" "${parts[2]:-0}"
}

MAJMINPATCH=$(echo "$VERSION" | grep -oP '^[0-9]+\.[0-9]+\.[0-9]+')
RC_NUM=$(echo "$VERSION" | grep -oP 'rc\.\K[0-9]+' || echo "")
VER_NUM=$(ver_to_num "$MAJMINPATCH")

VULNERABLE=false

# Branch 1.3.x: fixed in 1.3.6
if [ "$VER_NUM" -lt $(ver_to_num "1.3.6") ]; then
  VULNERABLE=true
# Branch 1.4.x: fixed in 1.4.3
elif [ "$VER_NUM" -ge $(ver_to_num "1.4.0") ] && [ "$VER_NUM" -lt $(ver_to_num "1.4.3") ]; then
  VULNERABLE=true
# Branch 1.5.0-rc.x: fixed in 1.5.0-rc.3
elif [ "$MAJMINPATCH" = "1.5.0" ] && [ -n "$RC_NUM" ] && [ "$RC_NUM" -lt 3 ]; then
  VULNERABLE=true
fi

RUNTIMES=""
command -v podman &>/dev/null && RUNTIMES="${RUNTIMES}podman "
pgrep -x containerd &>/dev/null && RUNTIMES="${RUNTIMES}containerd "
[ -z "$RUNTIMES" ] && RUNTIMES="unknown "

if $VULNERABLE; then
  echo "VULNERABLE — runc $VERSION is affected by CVE-2026-41579."
  echo "  Exposed runtimes: $RUNTIMES"
  echo "  Fix: upgrade to runc 1.3.6, 1.4.3, or 1.5.0-rc.3+"
  exit 1
else
  echo "PATCHED — runc $VERSION is not affected by CVE-2026-41579."
  exit 0
fi