Run a checkpoint image as an ordinary pod and containerd copies any file it can open into the pod log path. The bytes come back through kubectl logs on your own pod with no escape, no privilege, no host mount, no CRIU. Point it at a co-resident workload’s projected ServiceAccount token and you walk away with a live credential.
This is CVE-2026-53489, the third bug I reported in containerd’s checkpoint-restore path while hunting with the same agent rig. All three checkpoint issues live in the same function, which is exactly why that function became the center of the run.
CVE-2026-53489 · ghsa-rgh6-rfwx-v388
The archive becomes filesystem authority
containerd supports CRIU checkpoint/restore over CRI. A checkpoint ships as an OCI image bundling the container’s rootfs, OCI spec, CRIU memory dumps, and a container.log of captured stdout/stderr. When a pod is created from one, the CRI server sees the checkpoint annotation, runs CRImportCheckpoint, unpacks the content into the container’s root directory, and copies container.log to the pod log path so kubectl logs shows the pre-checkpoint output. The flow assumes a trusted operator produced the checkpoint. But a tenant who can run an image can hand containerd any checkpoint they like.
The problem centers around a restore artifact is treated like trusted filesystem content, but the entry point is just an image a tenant can run.
container.log can point somewhere else
The unpack recreates the symlink verbatim. Both restore paths recreate a shipped link with os.Symlink(target, path) and no confinement, and container.log is not in the exclude set — only config.dump, spec.dump, and status.dump are skipped. So an attacker’s container.log lands in the container directory as whatever link they shipped.
Then the copy follows it:
// CRImportCheckpoint — after the checkpoint is unpacked into containerRootDir
containerLog := filepath.Join(containerRootDir, "container.log")
if _, err := c.os.Stat(containerLog); err == nil { // os.Stat resolves the symlink
c.os.CopyFile(containerLog, meta.LogPath, 0600) // opens the target, copies its bytes
}
RealOS.Stat is plain os.Stat, which resolves the link as RealOS.CopyFile and opens the source with a pure os.Open
Point container.log at /etc/os-release and the tenant reads a host file. Point it at a neighbor’s projected token and the tenant reads a live credential, because of where containerd sits in the Kubernetes trust hierarchy.
It fires without CRIU as well. The copy runs inside CreateContainer and CRIU only runs later at StartContainer. On a node with no criu binary the pod fails to start but the read already completed during import. The pod never even has to run.
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 — and two siblings reach the same CRImportCheckpoint path: the CDI annotation smuggling issue and the RootfsImageName tag poisoning.