Exploit the runtime or the kernel underneath

Leave the container by breaking something shared with the host rather than something configured:

  1. Identify the kernel and the runtime, both of which are the host’s rather than the container’s

  2. Match them against known escapes

uname -a
cat /proc/version
cat /etc/os-release
ls -la /proc/self/exe

Example: the shared kernel

A container image can be years newer than the kernel it runs on, because the kernel is not part of the image. A local privilege escalation that works inside the container works against the host, since there is only one kernel and namespaces do not contain a bug in it.

# the container reports the host's kernel, not the image's
uname -r
grep -i seccomp /proc/self/status

Runtime escapes work on the boundary itself. The 2019 flaw in runc allowed a container process to overwrite the runtime binary on the host through /proc/self/exe, and the 2024 leaked file descriptor flaw let a working directory reach the host filesystem. Both are patched, and both illustrate that the code enforcing the boundary runs on the other side of it.

Notes

The container boundary is a set of kernel features, not a hypervisor. Namespaces, cgroups, capabilities and seccomp are all enforced by the kernel the container is trying to escape.

Seccomp narrows the syscall surface substantially and is the control most often disabled for convenience. Checking whether it is active is usually more informative than enumerating kernel versions.

Unprivileged user namespaces are a double edge. They exist to reduce the need for privileged containers, and they also widen the kernel surface reachable without privilege, which is why some distributions restrict them.

Where the workload runs on a virtualised runtime, the escape lands in a guest kernel rather than the host’s, which changes the value of the whole route considerably.