research /

Root on the Node From a Containerd Image Label

Two labels baked into a container image turn containerd's restart monitor into root command execution on the node. Run the image as an ordinary pod with no privileged container, hostPath, or checkpoint.

I started in checkpoint restore because that was the obvious weird surface but the bug that mattered most was instead a chain of custody adjacent to it. Tricking the logic of how labels were interpreted during runtime rather than restore would give you RCE in a direct way.

From this you can get root on a node from any ordinary image run via the containerd shim breaking isolation. With no privileged pod, host path, or checkpoint there is nothing suspicious in admission review. Just two labels baked into an OCI config and a daemon feature that had been trusting those labels for years.

I found it with the same agent rig behind the four containerd CVEs but it was the one that required stepping outside the convergent checkpoint trail.

CVE-2026-53488 · ghsa-xhf5-7wjv-pqxp

The reader was somewhere else

containerd ships an always-on restart monitor so if a container is labeled to stay running, it keeps it running. You opt in with containerd.io/restart.status plus a few containerd.io/restart.* keys. It’s a default builtin and it reconciles every namespace on the node, including the k8s.io namespace where Kubernetes workloads live. One of those keys, containerd.io/restart.loguri, names a URI for the restarted task’s logs and containerd’s log code supports a binary:// scheme that spawns a program and pipes logs to it. The path is the binary and the query string is its arguments leading to a nice RCE injection potential.

The important part is not that a restart monitor exists but that it is a root reader. The CRI plugin accepts labels from an image, stores them in container metadata, and the monitor comes along afterward with a different trust level and a different job. That split is exactly where single-flow thinking goes soft and vuln chains exist.

The image author writes into the daemon namespace

When the CRI plugin creates a container, it merges labels from the pod spec and from the image’s own OCI config, checking only the total label size. There’s no reserved-key filter, so the containerd.io/ control-plane namespace is open to whatever an image author wrote. Those labels are parsed server-side from the image blob, after Kubernetes admission has had its say so PodSecurity and securityContext never see them.

The bad pair is small:

containerd.io/restart.status = running
containerd.io/restart.loguri = binary:///bin/sh?-c=<url-encoded command>

The CRI plugin copies both into metadata then the monitor sees restart.status, restarts the task, and hands the shim restart.loguri as the log URI. The shim sees binary:// and runs it:

OCI image labels pass through containerd metadata, bypass Kubernetes admission, and let the root restart monitor execute restart.loguri as a host command.

// the shim's binary-IO handler, paraphrased
cmd := exec.Command(binaryURI.Path, args...) // "/bin/sh", "-c", "<command>"
cmd.Start()

No Setns, no Chroot, no dropped credentials. The logger is a child of the root shim, in the host’s namespaces. This gives us arbitrary command execution as root on the node.

There is no clean call stack from “attacker controls image label” to exec.Command. The write happens at create time then the read happens later. The handoff is persistent metadata which is why I care about state and time when using agents for vuln research. The interesting bug was not a sink in a traditional sense.

The managed-node twist

A major managed Kubernetes provider hardened its node image years ago by disabling the monitor via disabled_plugins = ["io.containerd.internal.v1.restart"]. But containerd 2.x renamed the plugin to io.containerd.monitor.container.v1.restart, so the old string matches nothing and the monitor is silently back on every 2.x node, fleet-wide - meaning nobody had to misconfigure anything. On a managed-node offering the payload runs as root in the host network namespace, past the metadata concealment workload identity depends on and within reach of the node’s cloud service-account credentials. I confirmed the primitive against a default cluster and stopped at proof.

Disclosure & credit

Reported through containerd’s private advisory flow, published Jun 18 2026 with patches available.

It was one of four containerd findings from the same agent rig — Thinking Outside the Containerd.


← all research