A use-after-free in the AF_UNIX socket garbage collector lets an unprivileged container process get root on the host kernel, and Ubuntu still hasn’t shipped a patch for any supported LTS release.
The short version
Security research firm DepthFirst published a working exploit on September 22, 2026, for a use-after-free in the Linux kernel’s AF_UNIX socket garbage collector. The bug, tracked as CVE-2026-80521 with a CVSS v3.1 score of 7.8 (High), lets an unprivileged process inside a default Docker or Kubernetes container break out of namespace isolation, cgroup boundaries, and seccomp filtering to get an interactive root shell on the host.
The part that matters for anyone running production containers: Ubuntu hasn’t patched any of its supported LTS releases. Ubuntu 26.04, 24.04, and 22.04 LTS, including their AWS, Azure, and GCP kernel variants, are still vulnerable as of this writing. Ubuntu’s security tracker lists the affected packages as “vulnerable, work in progress,” with no remediation date given. The upstream fix landed in mainline kernel 7.2 and stable branch 7.1.10 on August 6, 2026, but the distribution backports haven’t shown up yet.
There’s no confirmed active exploitation, and the CVE isn’t in CISA’s Known Exploited Vulnerabilities catalog. But a reliable, target-specific proof-of-concept being public shrinks the gap between disclosure and weaponization considerably. Anyone running containerized workloads on unpatched Ubuntu kernels should treat this as urgent.
How the vulnerability works: AF_UNIX sockets, SCC garbage collection, and a race window
Why containers can’t just block AF_UNIX sockets
AF_UNIX (Unix domain) sockets handle inter-process communication on the same host without touching the network stack. They’re the backbone of systemd notifications, Docker’s containerd shim, kubelet’s CRI interface, and a lot of other local IPC. Because nearly every containerized workload depends on them, Docker’s default seccomp profile and Kubernetes’ baseline Pod Security Standards both allow AF_UNIX socket creation and SCM_RIGHTS descriptor-passing by default. There’s no practical way to turn them off without breaking container orchestration itself.
When a process sends a file descriptor through an AF_UNIX socket via the SCM_RIGHTS ancillary message, the kernel has to track the relationship between the sending socket, the receiving socket, and the in-flight descriptor. If the receiving process never reads the message, or a cycle of sockets ends up holding references to each other with no external file table entry keeping them alive, those descriptors leak kernel memory. The garbage collector’s job is to find and reclaim these orphaned references.
The new SCC garbage collector
Before kernel 6.10, the AF_UNIX garbage collector used a fairly simple mark-and-sweep approach. The 6.10 rewrite, later backported to the 6.1 and 6.6 stable branches, replaced it with a graph-based model built around Strongly Connected Components (SCCs) — borrowed from graph theory, where a set of nodes is “strongly connected” if every node can reach every other node in the set.
The kernel now models in-flight Unix sockets as a directed graph:
unix_vertexrepresents an in-flight Unix socket (one with outstanding SCM_RIGHTS references).unix_edgerepresents a queued SCM_RIGHTS reference between two sockets.unix_add_edges()inserts edges into the graph and increments the receiver’s in-flight count when a descriptor-passing message is queued.unix_del_edges()removes edges when the receiving socket reads or destroys the skb.unix_walk_scc()does a full Tarjan-style SCC traversal to find garbage cycles.unix_walk_scc_fast()revisits SCCs found by a previous full walk using a cached list, skipping the O(V+E) cost of a full traversal on every pass.
An SCC counts as garbage when every file reference to its vertices is accounted for by an edge internal to that same SCC, meaning nothing outside the cycle keeps the sockets alive. unix_collect_skb() then splices the receive queues of garbage vertices into a hit list, drops unix_gc_lock, and purges the skbs.
The bug: a missing list_del_init that leaves a dangling pointer
The vulnerability sits in the interaction between unix_del_edge() and the cached SCC list used by unix_walk_scc_fast(). When removing a vertex’s last outgoing edge drops its out_degree to zero, the function moves the vertex’s entry list head back to a private free-pending list (FPL). When that list is later destroyed, unix_free_vertices() calls kfree(vertex), releasing the memory.
📬 Stay Ahead of Cyber Threats
Get the latest cybersecurity news, critical vulnerabilities, threat intelligence, tutorials, and exclusive giveaways delivered straight to your inbox. No spam. Unsubscribe anytime.
Subscribe to the Newsletter →Here’s the vulnerable code path in net/unix/garbage.c:
if (!vertex->out_degree) { edge->predecessor->vertex = NULL; list_move_tail(&vertex->entry, &fpl->vertices);}
What’s missing, and what the upstream fix adds, is:
list_del_init(&vertex->scc_entry);
Without that unlink, the freed vertex stays linked through its scc_entry pointer in the cached SCC list. A later call to unix_walk_scc_fast() follows that pointer into freed memory — a textbook use-after-free: the object’s been returned to the slab allocator, but a live kernel data structure still points at it.
The upstream fix, commit 594d905195024b228c962627ae5ae7c17bd582a4, explicitly unlinks scc_entry before the vertex is freed so no cached walk can reach a deallocated object.
Why the race matters
The bug doesn’t trigger from a simple sequence of operations. It needs a race between graph publication and skb queueing during a multi-descriptor sendmsg() call. The kernel’s internal ordering is:
unix_add_edges()publishes new graph edges underunix_gc_lock, then drops the lock.unix_prepare_fpl()allocates temporary vertices for the file-descriptor batch.skb_queue_tail()publishes the skb that owns those edges.
Between steps 1 and 3, there’s a window where the graph knows about new edges but the owning skb hasn’t been queued yet. If the garbage collector runs during that window, triggered by inflight pressure from a parallel sender, it can observe a half-built state: it might free vertex A (its references look fully internal to the SCC) while vertex B survives because its owning skb isn’t published yet. B is left holding a stale scc_entry link to the now-freed A.
That’s the exploitable outcome: a partially collected SCC where one vertex is freed but the graph still thinks it exists.
The exploit chain: from unprivileged container to host root shell
DepthFirst’s proof-of-concept targets the repository’s default Ubuntu 26.04 VM running kernel 7.0.0-31-generic on a two-vCPU q35 guest with 4 GiB of memory. It runs inside an unprivileged Docker container with no capabilities beyond Docker’s defaults — no io_uring, no kernel modules, no CAP_SYS_ADMIN, no seccomp tweaks. Here’s how each stage works.

Stage 1: building the graph topology
The exploit builds a specific socket graph based on the topology described in the advisory:
X ─┐ A ──► B▲ │ ▲ │└──┘ └─────┘
Socket pair X maintains at least one cyclic SCC in the graph so the garbage collector’s fast-walk path (unix_walk_scc_fast()) gets exercised on later collections, using the cached SCC list instead of doing a full traversal. That matters because the use-after-free is only reachable through the cached walk.
The PoC also queues an inflight-pressure batch under a dedicated UID, creating enough pending in-flight descriptors that an ordinary SCM_RIGHTS send from a second thread can synchronously trigger GC work right when the race window opens.
Stage 2: winning the race with calibrated jitter
The critical sendmsg() call passes 252 copies of socket B’s file descriptor plus one pipe file descriptor as a marker. Because the pipe isn’t a Unix socket, unix_prepare_fpl() allocates 252 temporary vertices — but B already has a graph vertex, so none of those temporaries actually gets used when the 252 B-to-B edges go in. That mismatch widens what’s normally a tiny gap between unix_add_edges() publishing the graph edges and skb_queue_tail() publishing the owning skb.
The PoC blocks the sender thread after FPL preparation using a missing user page fault combined with a long mmap write-lock operation. Releasing this gate starts the sender and a synchronous GC worker on different vCPUs at the same time. A short busy-loop delay, tuned through binary search across multiple trials, times the GC worker’s execution to land inside the race window.
A pipe descriptor embedded in the old A-to-B skb reports whether that skb got collected (meaning GC ran too late), while a separate marker in the new skb distinguishes a partial collection from a complete one. After the initial calibration, small jitter and feedback loops track drift in host scheduling across iterations.
The race lands in one of three places:
- GC runs too early — B still has unaccounted private file references, and the old SCC isn’t collected. No progress.
- GC runs too late — the new skb is already queued, and both A and B get collected normally. No stale pointer.
- GC lands after
unix_add_edges()but beforeskb_queue_tail()— the collector frees A, sees the B edge, but can’t collect B’s not-yet-queued owning skb. B survives while still linked to freed A through the cachedscc_entry. This is the outcome the exploit needs.
Stage 3: locating physical objects without a kernel read primitive
This is the part that shows real engineering effort. Ubuntu turns on several kernel hardening features that block straightforward heap exploitation: randomized kmalloc caches (CONFIG_SLAB_FREELIST_RANDOM and the kmalloc-rnd-* cache split), slab freelist randomization, and init_on_alloc, which zeroes every slab object before handing it to the allocator.
The exploit has no way to know which kmalloc cache instance holds unix_vertex objects, or where in a slab page a given object sits, and it can’t read kernel memory directly.
To get around this, the PoC first classifies the randomized kmalloc-rnd-*-96 cache that unix_vertex structures (96 bytes) live in. It then allocates AF_PACKET TPACKET_V1 RX rings as physical page anchors, which pin specific physical pages and make them visible from userspace. Where /proc/self/pagemap doesn’t expose page frame numbers to unprivileged processes on hardened kernels, the exploit falls back to a PFN oracle built on /proc/kpagecount: by cycling a mapping through mapped → unmapped → mapped again and comparing /proc/kpagecount snapshots, it identifies which physical page corresponds to its anchor.
One complication: on 4 GiB q35 guests, a PCI/MMIO hole relocates part of RAM to guest physical addresses above 4 GiB, and normal GFP_KERNEL allocation can reach into that high segment. So the PFN oracle scans both PFNs below 1 << 20 and the high range starting at 1 << 20, with the direct-map address calculation adjusted for high PFNs, where prefetch timing shows both a long low-RAM run and a short relocated-RAM run. The PoC picks the longest contiguous mapped run before normalization and requires the A, B, and support anchors to agree independently.
To land vertex A or B on a known physical frame, the PoC unmaps an anchor page and waits for a single-object unix_prepare_fpl() allocation to convert that PFN into the classified 96-byte slab. B’s exact slot within the slab gets recovered through a Prime+Probe cache side channel: dropping a probe message containing B updates B’s out_degree cache line in a way that’s observable via timing, while a control message doesn’t. A focused pass checks every possible slot, and multiple passes have to agree before the exploit attempts the destructive race.
Stage 4: reclaiming the freed vertex with controlled data
Once the race wins and vertex A is freed, the exploit needs to reclaim A’s slab slot with attacker-controlled data before the next cached SCC walk follows the dangling pointer.
A delayed-fput gate keeps the last socket reference from being released before the replacement is ready — it uses a full Unix socket and a splice() call blocked while holding the pipe mutex. Dropping the peer later lets the delayed-fput worker proceed to the replacement.
The first kfree() leaves the page in the 96-byte slab. The exploit starts batches of 253-vertex preparations behind another mmap lock, then releases them to drain per-CPU vertex sheaves and empty the target slab, polling KPF_SLAB via /proc/kpageflags rather than relying on a fixed delay, so it knows the slab has actually gone back to the buddy allocator before moving on.
Before draining, the exploit opens candidate AF_PACKET sockets without allocating their rings. Once the target PFN reaches the buddy allocator, it activates one-page RX rings alternately on both vCPUs. A mapping-count check picks out the first allocation that claimed the stale PFN — giving userspace a writable mapping of the exact physical page that used to hold vertex A.
Stage 5: forging a fake vertex and getting code execution
Allocation initialization wipes A’s old list pointers, and freelist randomization hides which slot it’s in. The PoC writes a candidate unix_vertex and edge structure into every possible 96-byte slot on the reclaimed page. Every candidate’s scc_entry points back to the already-localized B, so whichever slot ends up reached through B’s stale link passes list hardening checks.
The support page holds one shared fake unix_sock, socket, file, skb, and one edge-list node per A candidate. At first, the fake receiver’s vertex pointer is NULL. The first cached walk can move the real candidate A’s entry into the global visited list, but unix_vertex_dead() stops safely at the unresolved successor instead of collecting the forged queue — the PoC watches for that list mutation and waits for the walk to finish.
It then changes only the shared fake receiver’s vertex pointer to point at a sentinel vertex with the same SCC index. On the next cached walk: the fake A has one outgoing edge, the edge stays inside the synthetic SCC, the fake file’s biased reference counter represents one live reference, and total_ref == out_degree evaluates true. unix_collect_skb() splices the fake receive queue into its hit list.
The fake skb’s destructor is set to call_umh_work at the leaked KASLR base. The skb body is arranged as the expected usermode-helper work item, containing /bin/sh -c and a command stored in the shared support page. The helper finds the current payload process on the host, signals that the escape worked, and connects an interactive root shell to the payload’s stdin/stdout/stderr.
The end result: UID 0 in the host’s mount and PID namespaces, with PID 1 running systemd instead of the container’s entrypoint — full host compromise from an unprivileged Docker container.
Affected systems and patch status
Which kernels are vulnerable
The vulnerable SCC garbage collector code was introduced in kernel 6.10 and backported to stable branches 6.1 and 6.6. Any kernel built from these branches without the fix commit is affected. The fix landed upstream on August 6, 2026, in mainline kernel 7.2 and stable branch 7.1.10, in commit 594d905195024b228c962627ae5ae7c17bd582a4 in net/unix/garbage.c.
Ubuntu’s patch gap
As of September 23, 2026, Ubuntu’s security tracker shows:
Release Status Notes Ubuntu 26.04 LTS Vulnerable – Work in Progress No fix shipped. DepthFirst’s PoC targets this release. Ubuntu 24.04 LTS Vulnerable Affected through newer kernel packages. Ubuntu 22.04 LTS Vulnerable Affected through newer kernel packages. Cloud variants (AWS, Azure, GCP) Vulnerable Affected through cloud-optimized kernel packages on 24.04 and 22.04.
No distribution update has shipped for any affected release, and neither DepthFirst nor Canonical has published a temporary workaround.
A second PoC: CVE-2026-52910
The DepthFirst disclosure also includes a second proof-of-concept, CVE-2026-52910, targeting a reuseport cBPF container escape on Ubuntu 24.04 kernel 6.8.0-139-generic (x86-64). That exploit races reuseport cBPF program replacement, reclaims the stale program, and uses target-specific cBPF JIT data to expose a root shell in the guest’s host namespaces on success. It’s hard-coded for that specific kernel build and exits if the release string doesn’t match.
How the flaw was found: AI-assisted kernel auditing goes mainstream
The discovery timeline is worth noting on its own. DepthFirst says its proprietary model, dfs-large1 — trained specifically for kernel vulnerability detection — found the flaw alongside a human-operated testing harness. The company won a Google kernelCTF slot with the exploit on July 24, 2026, and reported the bug to the Linux kernel security team on August 5, 2026.
Notably, kernel maintainers told DepthFirst that a researcher at OpenAI had independently reported the same bug. The CVE commit credits kernel-exploitation researcher Kyle Zeng as the reporter. Two independent teams, one using a purpose-built AI model and one a human researcher, finding the same bug says something about both how reproducible it is and how fast AI-assisted auditing is moving.
It’s not an isolated case, either. CVE-2026-80521 is the latest in a run of 2026 Linux kernel vulnerabilities enabling container escapes:
- July 2026: A futex subsystem vulnerability let unprivileged container users escalate to host root, with AI-assisted research contributing to the discovery.
- April 2026: A flaw in the kernel’s cryptographic subsystem enabled a similar container-to-host escalation, also with AI-assisted research involved.
DepthFirst’s take: “The barrier to escaping containers by attacking the kernel has fallen so significantly that we must assume attackers can do so at will.” The company argues that AI-accelerated vulnerability discovery has structurally lowered the cost and expertise needed to find and exploit kernel bugs, and that container isolation shouldn’t be treated as a security boundary in the traditional sense anymore.
The numbers back that up somewhat: LinuxCVETracker counts nearly 5,700 Linux kernel CVEs published in 2026, the highest annual total on record. Not all of those are exploitable or relevant to containers, but the sheer volume raises the odds that container-reachable, privilege-escalation bugs keep turning up and getting weaponized.
Why this is worse than a typical container CVE
A few things push CVE-2026-80521 above the usual kernel CVE:
Default container configurations are enough. The exploit only uses AF_UNIX sockets, SCM_RIGHTS descriptor passing, and standard system calls — all permitted by Docker’s default seccomp profile, Kubernetes’ baseline Pod Security Standards, and most container runtime configs. No CAP_SYS_ADMIN, no --privileged flag, no io_uring, no kernel module loading, no runtime modification needed. The only non-default capability it uses is CAP_NET_RAW, which Docker grants by default and which the AF_PACKET-based physical page oracle needs.
Namespace isolation, cgroups, and seccomp all get bypassed at once. Because the bug is in the host kernel’s AF_UNIX garbage collector, exploiting it doesn’t require a separate namespace-escaping bug. The use-after-free corrupts kernel data structures shared across all namespaces, so the attacker moves from container PID namespace to host PID namespace, container mount namespace to host mount namespace, and an unprivileged UID to UID 0, all in one chain.
It’s reliable once calibrated. The binary-search delay calibration and multi-pass Prime+Probe voting make the race win deterministic in practice rather than a rare spray. The PoC tracks host-scheduling drift, and failed attempts produce clean misses that can be retried without crashing the container.
Cloud workloads are exposed too. Ubuntu’s AWS, Azure, and GCP kernel packages for 24.04 and 22.04 are all listed as vulnerable, so Kubernetes clusters on Ubuntu nodes in any major cloud are exposed unless their kernel packages were patched independently.
There’s no workaround. The vulnerability sits in core kernel networking code that can’t be disabled without breaking container orchestration, and neither DepthFirst nor Canonical has published a mitigation that closes the attack surface.
What you can do about it now
The real fix: patch the host kernel
The only complete fix is updating the host kernel to a build with the upstream patch, which unlinks scc_entry when a vertex’s last edge is removed so a partially freed vertex can’t stay reachable by a later cached SCC walk.
- Upstream fixed releases: Linux 7.1.10 (stable) and 7.2 (mainline).
- Check your vendor’s security advisory for the package version with the backport — stable backports don’t always keep the upstream version number.
- Running hosts need a reboot or a live kernel patch (kpatch, klp-convert). Updating container images does nothing here, since the vulnerable code is in the shared host kernel, not any container filesystem.
If you can’t wait for Ubuntu’s package update, applying the upstream patch directly to the kernel source and rebuilding is an option, but it carries its own operational risk and should be tested thoroughly in staging first.
Interim measures
These aren’t complete fixes — they break specific links in the demonstrated exploit chain and raise the cost of exploitation, but a determined attacker could substitute other primitives.
Drop CAP_NET_RAW from containers. The PoC uses AF_PACKET sockets with PACKET_RX_RING for both physical page anchoring and the writable exact-PFN replacement mapping. Removing this capability kills that primitive:
docker run --cap-drop=NET_RAW ...
In Kubernetes, enforce it through Pod Security Standards or a securityContext:
securityContext: capabilities: drop: - NET_RAW
If workloads don’t need packet sockets at all, a custom seccomp profile can also deny socket(AF_PACKET, ...) and setsockopt(SOL_PACKET, PACKET_RX_RING, ...).
Restrict PFN disclosure interfaces. Block container processes from reading /proc/kpagecount, /proc/kpageflags, and PFN data from /proc/self/pagemap to remove the fallback PFN oracle the PoC relies on. Options include OCI runtime maskedPaths for those files, rootless containers with a separate user-ID mapping, or host-level permission restrictions.
Set a realistic RLIMIT_NOFILE per container. The exploit chain needs thousands of simultaneously open sockets and queued descriptors for cache classification, vertex grooming, and repeated 253-descriptor FPL batches. A low file descriptor limit raises the cost meaningfully:
docker run --ulimit nofile=1024:1024 ...
Base the limit on what the workload actually needs rather than Docker’s default.
Apply memory and process limits. The implementation relies on large mmap-lock populations, many concurrent sender threads, and heavy page/slab pressure. Constraining container memory (--memory), PID count (--pids-limit), and CPU shares makes it harder to groom the heap and sustain the race calibration loops. Validate these against normal workload behavior first so you don’t break legitimate applications.
Consider microVM isolation for untrusted workloads. DepthFirst’s main recommendation is moving untrusted or multi-tenant workloads to microVM isolation, like Firecracker (used by AWS Lambda and Fargate) or Kata Containers. Each workload gets its own guest kernel, so a kernel exploit inside the microVM never reaches the host. It’s the only measure here that addresses the actual architectural problem: containers share the host kernel, so a kernel bug is a container-escape bug by definition.
Restrict sendmsg with SCM_RIGHTS where you can. For workloads that never transfer file descriptors between processes, blocking or tightly confining sendmsg calls carrying SCM_RIGHTS data shrinks the attack surface. Generic seccomp can’t easily tell an ordinary Unix socket send apart from a malicious SCM_RIGHTS payload, though, so broadly blocking sendmsg is usually too disruptive for most environments.
Detection and response
Worth instrumenting for:
- Unusual socket creation rates — a container creating large numbers of AF_UNIX sockets in a short window.
- Repeated maximum-sized SCM_RIGHTS messages — batches of 252+ descriptors passed through Unix sockets.
- AF_PACKET RX-ring allocation bursts, especially in containers that don’t normally do packet capture.
- Reads of kernel PFN interfaces (
/proc/kpagecount,/proc/kpageflags,/proc/self/pagemap) from containerized processes. - Rapid UID changes — a single process cycling credentials across many UIDs, consistent with the exploit’s inflight-pressure technique.
- Kernel oops and soft-lockup events. A failed attempt may produce a fault in
unix_scc_dead()followed by tasks blocked inunix_del_edges()onunix_gc_lock— watch kernel oops logs, soft-lockup warnings, and AF_UNIX workqueue events.
If a failed exploit attempt leaves the GC worker holding unix_gc_lock, stopping the container isn’t enough — the lock and any memory corruption are host-kernel state. Isolate the affected host from the network and reboot into a patched kernel before restoring workloads. Don’t try to recover the host in place.
The bigger picture
CVE-2026-80521 fits a pattern that’s been building through 2026. The kernel’s attack surface keeps growing, complex rewrites like the SCC garbage collector get backported to long-term stable branches where they see less testing than in mainline, and AI-assisted vulnerability discovery tools can now audit subsystems at a pace that wasn’t practical before. Nearly 5,700 kernel CVEs published in a single year is a real jump from prior norms.
Container security has long operated on the assumption that containers aren’t a security boundary the way VMs are, since they share the host kernel and any kernel privilege-escalation bug is effectively a container escape. In practice, though, the difficulty of finding and exploiting kernel bugs provided a de facto margin of safety. That margin is narrowing: an AI-assisted research effort found a race condition in a recently rewritten garbage collector, built a multi-stage exploit that defeats KASLR, slab randomization, freelist hardening, and init-on-alloc, and packaged it as a reproducible PoC, all within weeks of the code landing.
The practical response isn’t to abandon containers, but to treat kernel patching cadence as a security-critical process rather than routine maintenance, run untrusted or multi-tenant workloads in microVMs or other hardware-isolated environments, cut container capabilities down to what the application actually needs, and assume during monitoring that a compromised container may already have reached the host.
Timeline
| Date | Event |
|---|---|
| 2024 (approx.) | SCC garbage collector introduced in kernel 6.10; backported to 6.1 and 6.6 stable branches. |
| July 24, 2026 | DepthFirst wins a Google kernelCTF slot with the exploit. |
| August 5, 2026 | DepthFirst reports CVE-2026-80521 to the Linux kernel security team; kernel maintainers note an independent report from an OpenAI researcher. |
| August 6, 2026 | Upstream fix lands in mainline 7.2 and stable 7.1.10 (commit 594d905195024b228c962627ae5ae7c17bd582a4). |
| September 22, 2026 | DepthFirst publishes research and exploit code targeting Ubuntu 26.04. |
| September 23, 2026 | Ubuntu security tracker still lists affected packages as “vulnerable, work in progress.” No distribution patch shipped. |
This article will be updated as Ubuntu ships patched kernel packages and as additional vendor advisories are published. Last updated: September 23, 2026.









