CVE-2026-53495 Overview
CVE-2026-53495 is a resource exhaustion vulnerability [CWE-400] in containerd, an open-source container runtime widely deployed as the underlying runtime for Kubernetes. The flaw affects containerd installations on Linux with the Container Runtime Interface (CRI) plugin enabled. When CRI ExecSync is invoked by exec probes or lifecycle hooks that spawn long-lived background child processes retaining standard input and output pipes, the drainExecSyncIO goroutine in internal/cri/server/container_execsync.go can block indefinitely. Repeated invocations accumulate blocked goroutines and consume host memory until the Linux OOM killer terminates containerd, rendering the container runtime unavailable until restart.
Critical Impact
Repeated CRI ExecSync invocations can exhaust host memory, trigger the OOM killer against containerd, and leave the container runtime unavailable until a manual restart.
Affected Products
- containerd on Linux with the CRI plugin enabled, versions prior to 1.7.35
- containerd 2.0.x prior to 2.0.12 and 2.2.x prior to 2.2.8
- containerd 2.3.x prior to 2.3.5
Discovery Timeline
- 2026-09-14 - CVE-2026-53495 published to NVD
- 2026-09-14 - Last updated in NVD database
Technical Details for CVE-2026-53495
Vulnerability Analysis
The vulnerability resides in the CRI plugin's ExecSync handler, which executes a command inside a container and waits for both process exit and IO drain. The drainExecSyncIO function uses a select block that waits either on a drain timer (timerCh) or on the attach IO stream completing (attachDone). It does not wait on the request context being canceled. When an exec probe or lifecycle hook spawns a background child process that inherits stdout/stderr pipes, the attach IO never completes because the child holds the pipe file descriptors open.
Because the drain phase has no default timeout that respects request cancellation, each stuck ExecSync call parks a goroutine that holds process handles, buffers, and associated allocations. Kubernetes and other orchestrators frequently repeat exec probes on short intervals, so the number of parked goroutines grows linearly with time. Host memory pressure builds until the kernel OOM killer selects the containerd process, terminating the runtime and every workload managing container that depends on it.
Root Cause
The root cause is missing context cancellation handling in the IO drain path. The select statement did not include a case <-ctx.Done() branch, so client disconnects, request timeouts, or gRPC cancellations could not unblock the drain. Additionally, execProcess.Delete was invoked with the already-canceled parent context, which could fail before releasing the held IO resources.
Attack Vector
Exploitation requires local access at the CRI socket level and a container configured with an exec probe or lifecycle hook that starts a background process retaining stdout or stderr. A malicious or misconfigured workload can deliberately trigger the condition by executing commands that fork daemons which do not close standard streams. Deployments not using the CRI implementation and non-Linux hosts are not affected.
// Patch: cancel ExecSync IO drain on context cancellation
// internal/cri/server/container_execsync.go
select {
case <-timerCh:
case <-ctx.Done():
case <-attachDone:
log.G(ctx).Tracef("Stream pipe for exec process %q done", execProcess.ID())
return nil
}
log.G(ctx).Debugf("Exec process %q exits but the io is still held by other processes. Trying to delete exec process to release io", execProcess.ID())
deleteCtx := ctx
if ctx.Err() != nil {
var cancel context.CancelFunc
deleteCtx, cancel = util.DeferContext()
defer cancel()
}
_, err := execProcess.Delete(deleteCtx, containerd.WithProcessKill)
if err != nil {
if !errdefs.IsNotFound(err) {
return fmt.Errorf("failed to release exec io by deleting exec process %q: %w",
execProcess.ID(), err)
}
}
if err := ctx.Err(); err != nil {
return fmt.Errorf("failed to drain exec process %q io before context cancellation: %w",
execProcess.ID(), err)
}
Source: containerd commit 22ccf43
Detection Methods for CVE-2026-53495
Indicators of Compromise
- Sustained growth in containerd resident memory (RSS) not correlated with new container launches.
- OOM killer log entries in dmesg or journalctl -k naming the containerd process.
- Elevated goroutine counts exposed by containerd's debug endpoint or pprof profiles.
- Kubelet or CRI client errors reporting ExecSync timeouts or context cancellations without process cleanup.
Detection Strategies
- Monitor containerd process metrics for abnormal memory and goroutine growth over rolling time windows.
- Correlate exec probe failure rates with container runtime memory usage to surface probe-induced drains.
- Audit container images and pod specifications for exec probes or lifecycle hooks that launch daemonized child processes.
Monitoring Recommendations
- Ship containerd logs and kernel OOM events to a centralized analytics pipeline for alerting.
- Track the containerd_exec_sync_duration and Go runtime goroutines metrics via Prometheus scraping.
- Alert on kubelet events reporting repeated ExecSync context deadline exceeded errors on the same pod.
How to Mitigate CVE-2026-53495
Immediate Actions Required
- Upgrade containerd to a fixed release: 1.7.35, 2.0.12, 2.2.8, or 2.3.5.
- Identify workloads using exec probes or lifecycle hooks that fork background processes and refactor them to close stdout and stderr.
- Restart any containerd instances currently showing abnormal memory growth to release accumulated goroutines.
Patch Information
The fix adds a case <-ctx.Done() branch to the IO drain select in the CRI ExecSync handler and uses a deferred context for execProcess.Delete when the parent context is already canceled. Patched releases are available at containerd v1.7.35 and later, v2.2.8, and v2.3.5. Full technical details are documented in GHSA-7jxh-36q5-gcqv.
Workarounds
- Replace exec probes with httpGet or tcpSocket probes where feasible to avoid the CRI ExecSync path entirely.
- Ensure any process spawned by an exec probe or lifecycle hook redirects standard streams to /dev/null or closes them explicitly.
- Constrain containerd memory via a systemd MemoryHigh limit only as a last resort; this does not prevent runtime unavailability.
# Verify installed containerd version and upgrade path
containerd --version
# Example: pin containerd to a patched release on Debian/Ubuntu
sudo apt-get update && sudo apt-get install --only-upgrade containerd.io
# Confirm the fix is present by checking the release tag
containerd --version | grep -E "1\.7\.(3[5-9]|[4-9][0-9])|2\.0\.(1[2-9]|[2-9][0-9])|2\.2\.([8-9]|[1-9][0-9])|2\.3\.([5-9]|[1-9][0-9])"
# Restart containerd to clear any accumulated blocked goroutines
sudo systemctl restart containerd
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.