February 11, 2019·6 min read·engineeringkubernetessecuritycontainers

The runc escape puts the node runtime inside the security boundary

CVE-2019-5736 lets a malicious container overwrite the host runc binary. The disclosure explains why Kubernetes operators need patched node runtimes and why image trust alone leaves an important attack path open.

A container runtime must perform privileged work before it can start an isolated process. That transition is the subject of today's Kubernetes security advisory about CVE-2019-5736. A malicious container can exploit runc to overwrite the host runtime binary and obtain root execution on the host. Kubernetes itself does not contain the vulnerable code, but the advisory says most installations use runc through their container runtime. The node's security therefore depends on a component below the Kubernetes release being operated.

The original disclosure by runc maintainer Aleksa Sarai describes two ways to reach the vulnerable transition. Creating a container from an attacker-controlled image is one. Executing a command as root inside an existing container to which the attacker previously had write access is another. The second case changes how this problem should be understood. Trust in an image when it entered the system cannot establish that a running container remains safe to enter later.

How a host executable becomes exposed

The disclosure credits Adam Iwaniuk and Borys Poplawski with finding the vulnerability. It describes host root execution through modification of the runc executable, with the necessary interaction being a root command inside the container. That command need not itself be attacker-controlled. This is a failure at the transition between the host runtime and a container whose contents may already be hostile.

The upstream fix provides a useful explanation without requiring exploit code. It changes namespace-entry code so that the executing runtime first runs a copy of its own binary. The patch comments identify /proc/self/exe as the route through which a container could otherwise access the host binary. Replacing that executable reference with a private copy prevents the transition from exposing the original installed runtime in the same way.

The preferred implementation uses memfd_create to create the copy and applies seals that prevent writes, growth and shrinking. The patch also contains an O_TMPFILE alternative for builds without the memfd path. After preparing the copy, the code executes it with the original arguments and environment. The namespace-entry path checks whether it is already running the cloned executable, so the protection can happen before the rest of the operation proceeds.

This choice has a documented cost. The commit message says the copied executable loses page-cache sharing for the runc binary. It discusses an overlay filesystem alternative but selects the less complicated copying approach. That is a concrete security tradeoff in runtime implementation. The fix changes which executable is exposed during a sensitive operation, accepting the loss of sharing described by its author.

I regard the runtime as part of the isolation mechanism, so its patch status belongs in any assessment of a Kubernetes node. Reconciliation can restore declared workloads, but it does not establish that the host executable performing those operations is trustworthy. The affected component must receive its own fix through the packaging or node-image path that supplies it.

Root inside the container is a relevant precondition

The Kubernetes advisory says exploitation requires a process running as UID 0 inside the container. It recommends running workloads as a nonzero user as a direct mitigation. That qualification explains why the pod's process identity matters even when the application does not ask to be a privileged container. The advisory's precondition is root execution inside the container, so a security review limited to the privileged-pod setting would omit the identity involved in this flaw.

The following fragment reproduces the pod-level mitigation from the advisory. It is a fragment to include in an otherwise complete pod specification, not a deployable application manifest.

spec:
  securityContext:
    runAsUser: 1000

The number expresses a nonzero user for the workload. This mitigation addresses the root execution condition identified in the disclosure. It does not replace the runtime patch, which removes the vulnerable behavior itself. Applications that need root inside their container still require a response, and changing their user identity is an application compatibility decision as well as a security setting.

The advisory also shows PodSecurityPolicy using MustRunAsNonRoot to enforce a non-root requirement. Its example includes privileged: false and allowPrivilegeEscalation: false. Those settings make the intended restriction more explicit than relying on every image author to select an appropriate user. The policy example is relevant to prevention of the disclosed root execution condition. It should not be interpreted as evidence that an already compromised host has recovered because an admission rule was changed.

Image provenance has a narrower role. Kubernetes recommends building trusted images or vetting external images and pinning their content hashes. That addresses the creation path involving an attacker-controlled image. Sarai's existing-container case still requires separate attention because the attacker may have obtained write access after the image was selected. Image review and runtime patching address different parts of the disclosed conditions.

Security labels need their actual policy

The original disclosure says default AppArmor policy does not block the vulnerability. It also distinguishes SELinux configurations that would be easy to group together under a single label. In the Fedora configuration described for the moby-engine package, container processes appear as container_runtime_t, and that policy does not block the exploit. The disclosure says the Fedora docker package and Podman use container_t and are protected against this exploit.

The Kubernetes advisory separately says Red Hat Enterprise Linux and CentOS packages include appropriate SELinux permissions and are believed unaffected when SELinux is enabled. These statements concern particular policies and packaging. They do not support a general claim that enabling a technology with the same name creates an identical boundary on every host. The process label and applicable policy determine whether the extra protection described by the maintainers exists.

Sarai also identifies correct use of user namespaces as a protection when host root is not mapped into the container's user namespace. The mapping condition is part of that statement. A claim that a runtime supports user namespaces would be insufficient without knowing how the affected workload actually runs. This distinction between available machinery and applied configuration is central to the advisory's mitigations.

I prefer explicit workload restrictions because they make such assumptions inspectable. Even so, the disclosed policy differences are a reason to read the distribution's advisory alongside the Kubernetes guidance. A cluster's declarative workload configuration and the node's installed security policy both contribute to the resulting protection.

The scope also extends beyond the Kubernetes deployment model. Sarai reports a related LXC vulnerability with a jointly developed fix, while describing a different attachment method as the reason systemd-nspawn appears unaffected. That comparison supports the mechanism-level reading of the issue. The relevant question is how a runtime enters the container and handles its executable, rather than whether the system uses the same orchestration software.

The remediation follows the installed runtime

Kubernetes describes runc as the low-level component beneath Docker, containerd and CRI-O. Its guidance is to update the distribution's runc package or replace an immutable operating-system image with one containing the fix. It also names Docker 18.09.2 as containing the fix. The appropriate action depends on how the node obtained runc, so updating an unrelated Kubernetes component does not implement this remediation.

Managed Kubernetes does not make all node images equivalent. The advisory says the default GKE node image is safe but Ubuntu node-image users must mitigate or upgrade to a fixed image. It directs EKS users to mitigate or upgrade their node image and reports Microsoft's recommendation that AKS users upgrade their cluster. Those instructions differ because the runtime arrives through different platform packaging. The common requirement is a fixed runtime on the nodes that execute containers.

The public disclosure also separates publication of the patch from publication of exploit code. It sets February 11 for the patch disclosure and schedules public exploit code for February 18. As of this announcement, the maintainer has supplied a patch and described the necessary interaction while delaying that code. There is no need to assume a later outcome to explain the urgency of replacing a runtime that can give a hostile container host root execution.

For operators, the unit of remediation is the affected node installation and its runtime package or image. Non-root execution and suitable mandatory access controls can block disclosed preconditions while that work proceeds. The advisory's existing-container path also means routine root exec operations belong in the exposure assessment, including containers whose original images passed review. The patch addresses the executable exposure at namespace entry, which is the behavior those operational restrictions cannot repair themselves.