Skip to main content
11 min read

CVE-2026-80521: The Ubuntu Container Escape Is Not Just an Ubuntu Problem

CVE-2026-80521: The Ubuntu Container Escape Is Not Just an Ubuntu Problem

On September 22, DepthFirst published a working exploit for CVE-2026-80521: an unprivileged process inside a default Docker or Kubernetes container gets a root shell on the host. Their exploit targets one specific Ubuntu 26.04 kernel, but the bug behind it is much wider. Ten days later, Ubuntu's tracker still lists the main kernels for 24.04 LTS and 26.04 LTS as vulnerable, with no fixed package to install.

Most coverage calls it "the Ubuntu container escape". That framing is too narrow, and it can make you look in the wrong place. The bug is in upstream Linux code that arrived in 6.10 and was later backported to the 6.1 and 6.6 long-term kernels. Upstream has fixed 6.12, 6.18 and 7.1, but as of today there is no fix in the 6.1 or 6.6 branches at all. Debian 12, for example, is listed as vulnerable.

This post shows how to tell whether a machine has the affected code (in one command that usually works inside a pod too), where fixes exist, and what to do on nodes that cannot be patched yet.

TLDR

Detail Info
CVE CVE-2026-80521, CVSS 7.8
Class Use-after-free in the AF_UNIX socket garbage collector, container escape
Reported by Kyle Zeng
Public exploit September 22, 2026 (DepthFirst), built for an Ubuntu 26.04 LTS kernel
Vulnerable code Linux 6.10 and later, plus backports in 6.1.141+ and 6.6.93+
Fixed upstream 6.12.111, 6.18.53, 7.1.10, 7.2
Not fixed upstream 6.1 and 6.6 long-term branches (as of October 2)
Default seccomp Docker's default profile allows the calls the exploit needs
Quick check grep -cE ' unix_(add|del)_edges$' /proc/kallsyms (above 0 means the new garbage collector is present)

Prerequisites

  • Shell access to the hosts you want to check, or kubectl access to a cluster
  • For the cluster check: permission to run kubectl debug node/...
  • Your distribution's security tracker for the final answer on a distro kernel

What happened

Unix domain sockets can carry open file descriptors from one process to another (SCM_RIGHTS). A socket can even be sent over itself, so the kernel needs a garbage collector to find groups of sockets that only reference each other and free them.

Linux 6.10 replaced that garbage collector with a new design that tracks sockets as vertices and edges in a graph and groups them into strongly connected components (SCCs). The fix commit describes a race in that code: while one thread sends a socket and another closes the sockets involved, the collector can free a vertex but leave it linked in a cached SCC list. The next garbage collection run walks that list and touches freed memory. That use-after-free is what the exploit turns into a host root shell.

Two details make this bad for container platforms:

  • Ordinary containers can reach it. Creating Unix sockets and passing file descriptors are everyday operations. Docker's default seccomp profile allows them, and so does the RuntimeDefault profile of the common container runtimes, because almost all software needs them.
  • The kernel is shared. A container is a process on the host kernel. Namespaces, cgroups and dropped capabilities limit what a process can do through normal interfaces, and a kernel memory bug like this one goes around them.

Upstream fixed it in August (af_unix: Unlink scc_entry in unix_del_edge()), and the kernel CVE team published the CVE on August 26. The fix is one line. Getting it into every distribution kernel is the slow part.

Who is affected

According to the kernel CVE record, the vulnerable code is in:

  • every kernel from 6.10 onward, until the fixed releases below
  • 6.1.141 and later in the 6.1 series, and 6.6.93 and later in the 6.6 series, because the new garbage collector was backported to those long-term branches

The fix is in 6.12.111, 6.18.53, 7.1.10 and 7.2. The CVE record lists no fixed 6.1 or 6.6 release, and a search of the 6.1.y and 6.6.y stable branch history on October 2 found no commit with the fix's title (the same search finds it in 6.12.y and 6.18.y). The newest releases there (6.1.188 and 6.6.157, both from September 14) still have the bug.

What the main distributions say, checked on October 2:

Distribution Kernel Status
Ubuntu 26.04 LTS linux and the cloud kernels (aws, azure, gcp, gke, oracle) Vulnerable, work in progress
Ubuntu 24.04 LTS linux and the cloud kernels Vulnerable
Ubuntu 22.04 LTS linux (5.15) Not affected
Debian 13 (trixie) 6.12 Fixed in 6.12.111-1 (DSA-6528-1)
Debian 12 (bookworm) 6.1 Vulnerable (6.1.176-1), no fix yet
Debian 11 (bullseye) 5.10 Not affected
Amazon Linux 2023 6.18.35 Livepatch for kernel-livepatch-6.18.35-68.129, September 30 (ALAS2023LIVEPATCH-2026-397)

Sources: the Ubuntu CVE page, the Debian security tracker and the Amazon Linux advisory. We did not check every vendor. If you run RHEL and its rebuilds, Azure Linux, Container-Optimized OS, Bottlerocket or Talos, check that vendor's tracker.

Note

Some early reports listed Ubuntu 22.04 as vulnerable. Ubuntu's own tracker marks the 22.04 linux kernel (5.15) as not affected, which matches the upstream record: 5.15 never received the new garbage collector. The 22.04 HWE kernels are newer and are tracked separately, so check them on the tracker.

Why the version number is not enough

The natural first step is to compare uname -r against the list above. That works for upstream kernels and fails for distribution kernels, in both directions.

Ubuntu 24.04 ships a 6.8 kernel. Upstream 6.8 never had the new garbage collector, so a version check says "not affected". Ubuntu's tracker says vulnerable: distribution kernels pull in fixes from the stable branches, and the backport that brought the new code into 6.6 can bring it into a distro's 6.8 too. The reverse also happens. A distro can backport the one-line fix and keep the old version number.

So check for the code itself. The new garbage collector adds functions named unix_add_edges and unix_del_edges, and the kernel lists its function names in /proc/kallsyms. That file is world-readable by default, and unprivileged users usually see the names with the addresses zeroed. An ordinary container sees the host kernel's list, because there is only one kernel (a gVisor or Kata sandbox does not).

grep -cE ' unix_(add|del)_edges$' /proc/kallsyms
  • 0: the new garbage collector was not found. On a standard distribution kernel, that means this CVE does not apply. On a custom or heavily modified kernel, confirm with whoever builds it.
  • 1 or more: the new garbage collector is present. Now the version and your vendor decide whether the fix is in.

The presence check cannot see the fix itself, because the fix adds one line to a different function, unix_del_edge(). That is why the script below combines it with the version, and sends you to your vendor when the kernel is a distro build. Treat it as a fast first filter, not a certificate.

A check script

#!/usr/bin/env bash
# Heuristic check for CVE-2026-80521 (AF_UNIX garbage collector use-after-free).
# Usually works inside a container too: /proc/kallsyms lists the host kernel's symbols.
# Exit codes: 0 not detected or fixed, 1 affected, 2 check your vendor, 3 cannot tell.
set -u

release=${KERNEL_RELEASE:-$(uname -r)}   # KERNEL_RELEASE only for testing the logic
if [[ ! $release =~ ^([0-9]+)\.([0-9]+)(\.([0-9]+))?(.*)$ ]]; then
  echo "verdict:     CANNOT TELL (unrecognised kernel release '$release')"; exit 3
fi
major=${BASH_REMATCH[1]} minor=${BASH_REMATCH[2]} patch=${BASH_REMATCH[4]:-0}
suffix=${BASH_REMATCH[5]}   # anything after x.y.z: a distro, custom or -rc build

# The bug is in the garbage collector that Linux 6.10 rewrote (backported to
# 6.1.141+ and 6.6.93+). unix_add_edges and unix_del_edges exist only in that code.
if ! head -n 1 /proc/kallsyms 2>/dev/null | grep -q .; then
  new_gc=unknown
else
  grep -qE ' unix_(add|del)_edges$' /proc/kallsyms
  case $? in 0) new_gc=yes ;; 1) new_gc=no ;; *) new_gc=unknown ;; esac
fi

# Upstream releases that contain the fix (kernel CVE record, 2 October 2026).
fixed_upstream=no
case "$major.$minor" in
  6.12) (( patch >= 111 )) && fixed_upstream=yes ;;
  6.18) (( patch >= 53 )) && fixed_upstream=yes ;;
  7.1)  (( patch >= 10 )) && fixed_upstream=yes ;;
esac
if (( major > 7 || (major == 7 && minor >= 2) )); then fixed_upstream=yes; fi

echo "kernel:      $release"
echo "new AF_UNIX GC found: $new_gc"

case $new_gc in
  unknown) echo "verdict:     CANNOT TELL (could not read /proc/kallsyms); check $major.$minor with your vendor"; exit 3 ;;
  no)      echo "verdict:     NOT DETECTED (no new garbage collector; on a standard distro kernel this means not affected)"; exit 0 ;;
esac
if [[ -n $suffix ]]; then
  if [[ $fixed_upstream == yes && $suffix != -rc* ]]; then
    echo "verdict:     PROBABLY FIXED (base $major.$minor.$patch has the fix upstream); confirm with your vendor"; exit 2
  fi
  echo "verdict:     CHECK YOUR VENDOR (new garbage collector present; distro kernels backport fixes without changing the version)"; exit 2
fi
if [[ $fixed_upstream == yes ]]; then
  echo "verdict:     FIXED (if this is an unmodified upstream $release build)"; exit 0
fi
echo "verdict:     AFFECTED (upstream $release has the bug and no fix)"; exit 1

We ran it as an unprivileged user on two machines: a Raspberry Pi test box on Raspberry Pi OS, and an Ubuntu 22.04 server. The KERNEL_RELEASE variable is only there to test the version logic with made-up strings.

We also read /proc/kallsyms from inside a running Docker container on the 22.04 server. The symbol names were there (addresses zeroed), so the one-line check worked from inside the container. Hardened setups can mask that file; then the script says it cannot tell.

Check a whole cluster

Start with the kernel and OS image of every node:

kubectl get nodes -o custom-columns=NAME:.metadata.name,OS:.status.nodeInfo.osImage,KERNEL:.status.nodeInfo.kernelVersion

Then run the presence check on every distinct kernel build you see, or simply on every node during a rollout, when old and new images run side by side:

# Starts a debugging pod on the node; /proc/kallsyms is the node's kernel symbol list
kubectl debug node/<node-name> -it --image=busybox:1.36 -- \
  grep -cE ' unix_(add|del)_edges$' /proc/kallsyms

kubectl debug node creates a pod that shares the node's PID, network and IPC namespaces, so it needs the matching RBAC and must be allowed by your admission policy. Delete the debug pod when you are done (kubectl get pods shows it as node-debugger-...).

What to do now

1. Patch where a fix exists

If your kernel line has a fix (Debian 13, upstream 6.12.111+, 6.18.53+, 7.1.10+ or 7.2+), install it. A new kernel package does nothing until the node boots into it, so plan the reboots: cordon and drain one node at a time, or replace node pools with a new image. On Amazon Linux 2023 with the matching kernel, the livepatch applies without a reboot once kernel livepatching is enabled; verify that it is applied.

2. Find the workloads that run code you did not write

Where there is no fix yet (Ubuntu 24.04 and 26.04, Debian 12, anything on a 6.1 or 6.6 kernel at or above the backport), the question is who can run arbitrary code on those nodes. A container escape needs code execution inside a container first. The usual places where strangers get exactly that:

  • CI runners and build agents that run pull or merge request code from forks
  • multi-tenant clusters where teams or customers deploy their own images
  • notebooks, online code runners, plugin systems and "run your script" features
  • any pod with a known remote code execution bug that you have not patched yet

Single-tenant nodes that only run your own images are still worth patching, but they are not where you start.

3. Move untrusted workloads into a sandbox or onto unaffected nodes

For the workloads in step 2, the most reliable option you can use today is a different isolation boundary:

  • gVisor runs the container on a user-space kernel (the Sentry) that implements Unix sockets itself, so the sandbox's own Unix sockets do not use the host kernel's AF_UNIX garbage collector. (Access to host Unix sockets is a separate option and is off by default.) On GKE this is GKE Sandbox. Elsewhere, install runsc on the nodes, register it as a containerd runtime handler, and then add a RuntimeClass.
  • Kata Containers runs each pod in a lightweight VM, and can use Firecracker as its VM monitor. The guest kernel may have the same bug, but an escape then lands in a VM, not on the host.
  • A separate node pool on an unaffected kernel, such as Ubuntu 22.04 with its 5.15 kernel, for the untrusted workloads until your main image is fixed. It is a short-term measure with its own tradeoffs (an older kernel and different hardware support), not a destination.

A RuntimeClass for gVisor, and a pod that uses it:

apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: gvisor
handler: runsc # the containerd runtime handler configured on the node
---
apiVersion: v1
kind: Pod
metadata:
  name: untrusted-build
spec:
  runtimeClassName: gvisor
  containers:
    - name: build
      image: registry.example.com/ci/build-runner:2026.10

Test your workloads under gVisor before you switch: it does not implement every system call, and I/O heavy jobs can run slower.

4. Know what does not help

  • Default seccomp. The default profiles allow Unix sockets and file descriptor passing.
  • Blocking AF_UNIX with a custom seccomp profile, in general. For the Copy Fail AF_ALG bug earlier this year, blocking one rarely used socket family was a clean mitigation. AF_UNIX is different: DNS resolution through NSS, database clients on local sockets, process managers, logging and many language runtimes use it. Blocking it breaks most real applications. A custom profile can still make sense for a specific workload that you have tested without Unix sockets.
  • Rootless containers, user namespaces and dropped capabilities. They are good hardening, but they are not a fix here: the vulnerable code is reachable without any special privilege.

5. Watch for the fix and re-check

Subscribe to your distribution's security notices, and keep the check above in your node image pipeline. Once the fixed kernel is out, the version check plus the vendor's fixed package version tells you which nodes still need a reboot. Then move the sandboxed workloads back, or keep them sandboxed. Copy Fail and Fragnesia were container escapes too, earlier this year, so the sandbox may be worth keeping.

Summary

  • CVE-2026-80521 is a use-after-free in the Linux AF_UNIX garbage collector. A public exploit escapes default Docker and Kubernetes containers to host root.
  • It is not limited to Ubuntu. The vulnerable code is in 6.10 and later and in the 6.1.141+ and 6.6.93+ long-term kernels. Upstream fixed 6.12, 6.18 and 7.1, but not 6.1 or 6.6.
  • Ubuntu 24.04 and 26.04 and Debian 12 had no fixed kernel on October 2. Ubuntu 22.04 (5.15), Debian 11 and other kernels without the new garbage collector are not affected.
  • Version numbers mislead on distro kernels. grep -cE ' unix_(add|del)_edges$' /proc/kallsyms tells you whether the new garbage collector is there, usually even from inside a container.
  • Until you can patch, put workloads that run untrusted code behind gVisor, Kata or a VM, or on nodes with an unaffected kernel. Default seccomp and user namespaces do not stop this one.

Run the commands from this article in the browser. Nothing to install.

Published: 2026-10-02|Last updated: 2026-10-02T09:00:00Z

Found an issue?

Also worth your time on this topic