#3851·cadvisor

Path traversal in ReadFile() via container label escapes proc namespace boundary

Author: RyujiyasuCreated Mar 9, 2026Updated Aug 19, 2026
Labelslifecycle/stale

Summary

In manager/container.go, the ReadFile() function uses path.Join with a user-controlled filepath parameter derived from Docker container labels, without any prefix validation. This enables path traversal from within a container's namespace to arbitrary files on the host.

Vulnerable Code

File: cadvisor/manager/container.go:250-272

go
func (cd *containerData) ReadFile(filepath string, inHostNamespace bool) ([]byte, error) {
    pids, err := cd.getContainerPids(inHostNamespace)
    rootfs := "/"
    if !inHostNamespace {
        rootfs = "/rootfs"
    }
    for _, pid := range pids {
        filePath := path.Join(rootfs, "/proc", pid, "/root", filepath)
        // ↑ NO prefix check: path.Join resolves ".." → escapes proc/PID/root/ boundary
        data, err := os.ReadFile(filePath)

The filepath comes from the io.cadvisor.metric.* container label. With a label value of ../../../../etc/shadow, path.Join resolves to /etc/shadow on the host.

Proof:

go
path.Join("/", "/proc", "1234", "/root", "../../../../etc/shadow")
// → "/etc/shadow"   ← host shadow file, not container's

path.Join("/rootfs", "/proc", "1234", "/root", "../../../../proc/1/environ")
// → "/proc/1/environ"   ← host init process environment (may contain secrets)

Attack Scenario

  1. Attacker deploys container with label: io.cadvisor.metric.steal=../../../../etc/shadow
  2. cAdvisor calls ReadFile("../../../../etc/shadow", inHostNamespace) for that container
  3. path.Join resolves to host /etc/shadow — file is read by cAdvisor (running as root in a DaemonSet)
  4. Contents leak into cAdvisor logs or error messages

Suggested Fix

After path.Join, validate that filePath still starts with the expected prefix (rootfs + "/proc/" + pid + "/root/"). Reject any path that escapes the container's root namespace.