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
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:
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
- Attacker deploys container with label:
io.cadvisor.metric.steal=../../../../etc/shadow
- cAdvisor calls
ReadFile("../../../../etc/shadow", inHostNamespace) for that container
path.Join resolves to host /etc/shadow — file is read by cAdvisor (running as root in a DaemonSet)
- 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.
Summary
In
manager/container.go, theReadFile()function usespath.Joinwith a user-controlledfilepathparameter 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
The
filepathcomes from theio.cadvisor.metric.*container label. With a label value of../../../../etc/shadow,path.Joinresolves to/etc/shadowon the host.Proof:
Attack Scenario
io.cadvisor.metric.steal=../../../../etc/shadowReadFile("../../../../etc/shadow", inHostNamespace)for that containerpath.Joinresolves to host/etc/shadow— file is read by cAdvisor (running as root in a DaemonSet)Suggested Fix
After
path.Join, validate thatfilePathstill starts with the expected prefix (rootfs + "/proc/" + pid + "/root/"). Reject any path that escapes the container's root namespace.