CVE-2025-54867: Path Traversal in Youki Container Runtime

Welcome back to the security lab! Today, we're dissecting CVE-2025-54867, a crafty vulnerability in the youki container runtime. It’s a classic tale of a trusted component being misled by a simple file system trick—a symbolic link—with potentially disastrous consequences. Grab your favorite beverage; we're going on a deep dive into how this container escape was possible and what we can learn from it.

TL;DR / Executive Summary

Vulnerability: CVE-2025-54867 is a path traversal vulnerability in the youki container runtime caused by improper handling of symbolic links for /proc and /sys mount points.

Affected Software: youki versions prior to 0.5.5.

Impact: A malicious container image could use a symbolic link to trick the youki runtime into mounting sensitive host resources or misconfiguring the container's environment. This could lead to a container escape, allowing an attacker to gain access to the underlying host system.

Severity: High. Container escape vulnerabilities fundamentally break the isolation model that makes containerization secure.

Mitigation: The fix is straightforward: upgrade youki to version 0.5.5 or later. This version correctly validates that /proc and /sys are actual directories, not symbolic links, before mounting.

Introduction: The Trusting Gatekeeper

Imagine a container as a secure apartment in a large building (the host server). The container runtime is the building manager, responsible for giving each tenant (container) the right keys and ensuring they can't access their neighbors' apartments or the building's utility rooms. The /proc and /sys directories are like the apartment's own private fuse box and plumbing controls—they look and feel real but are virtualized and isolated from the main building's systems.

CVE-2025-54867 is what happens when a tenant leaves a deceptive note on their door: "For all deliveries, please go to the building's main electrical room." The building manager (youki), without double-checking, follows the instruction and inadvertently gives the tenant control over something far more critical than their own apartment.

This vulnerability in youki, a fast and efficient container runtime written in Rust, highlights a critical lesson: when building security boundaries, you can't trust the environment you're given. Let's explore how this simple symlink trick could unravel the isolation we depend on.

Technical Deep Dive: Following the Breadcrumbs

At its core, this vulnerability is a failure to validate the nature of a file path before performing a privileged operation on it. When youki starts a container, one of its jobs is to mount special filesystems like procfs and sysfs inside the container at /proc and /sys, respectively.

Root Cause Analysis

The Open Container Initiative (OCI) specification dictates how runtimes should set up a container's environment, including its mounts. An attacker can craft a container image where the /proc path isn't a directory as expected, but a symbolic link pointing somewhere else.

For example, an image could have /proc as a symlink to /.

# Inside a malicious Dockerfile or image build process
rm -rf /proc
ln -s / /proc

A vulnerable version of youki would read the OCI spec, see a request to mount procfs on /proc, and proceed. However, because /proc is a symlink to /, the runtime would follow the link and attempt the mount operation on the target. This could lead to unpredictable behavior, but the most dangerous scenario is one that allows an attacker to influence paths outside the container's intended root filesystem, leading to a container escape.

The reference container runtime, runc, had already addressed this and similar issues. It explicitly checks if /proc and /sys are directories before mounting. youki, in its vulnerable state, skipped this crucial validation step.

Attack Vector and Business Impact

The primary attack vector is tricking a system into running a malicious container image. This is a significant threat in multi-tenant cloud environments, CI/CD pipelines, or any system that pulls and runs images from public or untrusted registries.

The impact is severe:

  • Container Escape: The ultimate goal. An attacker could break out of the container's isolation and gain code execution on the host.
  • Privilege Escalation: Once on the host, the attacker could potentially escalate privileges to root.
  • Full System Compromise: With host access, an attacker can access data from other containers, attack the internal network, and persist their presence.

Proof of Concept (PoC)

We can demonstrate this vulnerability by simulating the conditions. This PoC is based on the test cases added in the patch that fixed CVE-2025-54867.

1. Prepare the Malicious Root Filesystem

First, we create a directory that will act as our container's root. Inside, we'll create the malicious symlink.

# Create a temporary directory for our container's rootfs
mkdir /tmp/malicious-rootfs
cd /tmp/malicious-rootfs

# Create a target directory for the symlink
mkdir some-other-dir

# Instead of a 'proc' directory, create a symlink named 'proc'
# pointing to our target directory.
ln -s ./some-other-dir ./proc

# Now, our rootfs has a /proc that is not a directory.
ls -l
# total 4
# lrwxrwxrwx 1 user user   15 Aug 15 10:00 proc -> ./some-other-dir
# drwxr-xr-x 2 user user 4096 Aug 15 10:00 some-other-dir

2. Define the Container Spec

Create a minimal config.json that tells the runtime to mount /proc.

{
    "ociVersion": "1.0.2-dev",
    "process": {
        "args": ["/bin/sh"]
    },
    "root": {
        "path": "/tmp/malicious-rootfs",
        "readonly": true
    },
    "mounts": [
        {
            "destination": "/proc",
            "type": "proc",
            "source": "proc"
        }
    ]
}

3. Execution

  • With Vulnerable youki (< 0.5.5): The container creation would succeed. The procfs would be mounted inside /tmp/malicious-rootfs/some-other-dir instead of failing, demonstrating the symlink was followed.
  • With Patched youki (>= 0.5.5): The container creation would fail with an error, as the runtime now checks the destination path.
# youki --root /run/youki create my-container -b .
# Expected error on patched version:
# Error: failed to setup mount: filesystem proc must be mounted on ordinary directory

This simple check prevents the entire attack chain.

Mitigation and Remediation

Patching is your best friend here.

Immediate Fix:
Upgrade youki to version 0.5.5 or later. You can typically do this through your package manager or by downloading the latest release from the official repository.

Patch Analysis (The Good Stuff):
The fix, committed in 0d9b4f2aa5ceaf988f3eb568711d2acf0a4ace37, is located in crates/libcontainer/src/rootfs/mount.rs. Let's look at the key logic.

// Simplified snippet from the patch
Some(typ @ ("proc" | "sysfs")) => {
    // ... constructs the full destination path inside the container rootfs ...
    let dest_path = options
        .root
        .join_safely(Path::new(mount.destination()).normalize())?;

    // THE FIX: Check the metadata of the destination path
    match fs::symlink_metadata(&dest_path) {
        // If it exists and is NOT a directory, it's an error.
        // This catches symlinks, files, etc.
        Ok(m) if !m.is_dir() => {
            return Err(MountError::Other(
                format!("filesystem {} must be mounted on ordinary directory", typ)
                    .into(),
            ));
        }
        // If we get an error other than "Not Found", something is wrong.
        Err(e) if e.kind() != ErrorKind::NotFound => {
            return Err(MountError::Other(
                format!("symlink_metadata failed for {}: {}", dest_path.display(), e)
                    .into(),
            ));
        }
        // Otherwise, it's either a directory or doesn't exist yet, which is fine.
        _ => {}
    }

    // ... proceed with mounting ...
}

The code now uses fs::symlink_metadata, which crucially does not follow symbolic links. It inspects the link itself. If the path exists and isn't a directory, the process halts. This elegantly shuts down the vulnerability by enforcing the simple assumption that /proc and /sys must be directories before they are used as mount points.

Timeline

  • Discovery: Early August 2025 (Estimated)
  • Vendor Notification: Early August 2025 (Estimated)
  • Patch Committed: August 13, 2025 (Based on commit history)
  • Public Disclosure (CVE Published): August 14, 2025

Lessons Learned

This CVE is a fantastic reminder of some fundamental security principles.

  1. Never Trust Your Inputs: A container image is an input. The runtime should treat its filesystem layout with suspicion. Critical paths like /proc, /dev, and /sys demand rigorous validation before any privileged operations are performed on them.

  2. The Principle of Least Astonishment: Security-critical operations should behave predictably. Following a symlink when a directory is expected is an "astonishing" behavior that creates a security gap. The fix makes the behavior predictable and safe.

  3. Learn from History: This exact type of bug was previously discovered and fixed in runc. When implementing security-critical software, it's vital to study the pitfalls and patches of existing, mature projects in the same domain.

Key Takeaway: Assumptions are the enemy of security. The assumption that a path in a filesystem is of a certain type (e.g., a directory) is a dangerous one. Always check, then act.

So, the next time you're working with file paths at a security boundary, ask yourself: "What if this isn't a directory? What if it's a symlink to /? What if it's a FIFO pipe?" Thinking like an attacker is half the battle.

References and Further Reading

Read more