Contents

Container Escape to Host Root Through a Linux Kernel Bug

 

Ethical Hacking Complete Course Zero to Expert

Hack like black hat hackers. Penetration testing, Kali Linux, WiFi and web hacking, and the hacker mindset behind it.

→ Take the full course
 
Contents

A 2-year-old hole in the Linux kernel took a process from inside a container to root on the host machine. 82 percent of container users run Kubernetes in production. One exploit, starting with no permissions at all. It won Google’s kernel hacking contest in July and paid out 71,337 dollars.

Containers carry a large share of modern infrastructure, and that share keeps climbing. Three years ago it was 66 percent. Cloud apps, websites that scale up when traffic arrives, AI workloads that spin up and disappear again, internal tooling that never leaves the company: that is all containers. And the reason companies are willing to put code they do not trust into a container is that a container is supposed to keep that code away from everything else on the machine.

A container is not a separate computer. It is not a virtual machine with its own operating system. It is a set of ordinary processes running on the same kernel as everything else on that machine, fenced off with namespaces, cgroups and seccomp filters. The fence is built by the kernel. Break the kernel and there is no fence.

That is what happened here.

A process inside the container, running as a normal user with no special rights, triggers a bug in the kernel and takes over. From there it is root on the host. Not root inside the container, which means very little, because the container is a fiction the kernel maintains. Root on the physical machine underneath. From that position an attacker reaches the other containers on that host, the container runtime itself, and on a cluster, the node it belongs to.

A year ago I showed how container isolation falls apart: a privileged flag, an exposed API, secrets in plain sight. All three are a misconfiguration someone left behind. This bug needs none of that. A default container and one kernel bug.

The bug lives in a corner of the kernel that gets very little attention. Unix domain sockets, the sockets two processes on the same machine use to talk to each other, can carry more than data. They can carry an open file. You hand a file descriptor to another process in a message called SCM_RIGHTS, and on the receiving end that process now has the file open, with the same access you had.

A service hands its live connections to the service manager this way before it restarts, and gets them back afterwards, so nothing drops while it is down. A browser hands its sandboxed renderer one end of a fresh socket pair this way, so that renderer can ask a helper process for the things the sandbox stops it doing itself. The mechanism is older than most of the code around it and it runs constantly on machines where it draws no attention.

Passing file descriptors around like this creates one problem. A socket can send a file descriptor for another socket. Two sockets can each end up holding a descriptor for the other. Now neither one can be cleaned up, because each is keeping the other alive, and no process is using either of them. That is a reference cycle, and it leaks kernel memory until the machine reboots.

So the kernel runs a garbage collector for Unix sockets. It builds a graph. A socket with descriptors in flight becomes a struct unix_vertex, and a descriptor in flight becomes a struct unix_edge pointing at the receiving socket. The collector walks that graph looking for groups of sockets that point only at each other and at nothing outside. Those groups are called strongly connected components. If nothing outside a group still needs it, the collector frees it.

Rebuilding that graph from scratch each time would be expensive, so the kernel caches the components it found. A vertex keeps a pointer into a ring of the other vertices in its component, called scc_entry. On the next pass, the fast path walks that cached ring instead of recomputing anything.

Two things go wrong, and they have to happen together.

The first is timing. When a process sends a message with a file descriptor in it, the kernel publishes the new edge in the graph before it puts the message in the receiving queue. unix_add_edges() runs first, __skb_queue_tail() second, and in between, the code lets go of unix_gc_lock. That opens a window where the garbage collector can see a connection that has been announced but not delivered.

The second is cleanup. When an edge goes away, unix_del_edge() moves the vertex onto a free list. It never takes that vertex out of the scc_entry ring it was cached in. The vertex gets freed. The ring still points at it.

Two components exist, and one points at the other. A process sends a socket a message containing itself, and at the same moment both sockets are closed. The close finishes first. The collector runs and marks the pair dead, but the self-referencing edge stops one of the two from being cleaned up. On the next pass the collector takes the fast path through the cached ring, follows the stale pointer, and reads and writes memory that has already gone back to the kernel.

That is a use-after-free in kernel memory, reachable by a local user with an ordinary account. The score is 7.8, vector AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H. Local access, low complexity, low privileges, no interaction needed. Once you are through, you can read what is on that machine, change it, and switch it off.

This has been reachable since Linux 6.10, released on 14 July 2024. Two years and three weeks before the fix landed. And because the collector was backported into the stable branches, kernels from 6.1.141 and 6.6.93 onward carry it as well, even though the code was written for a much later kernel.

The commit that put the bug there was a cleanup. In March 2024 the maintainer of the Unix socket code replaced the old garbage collection algorithm with the one described above. Three files changed, 64 lines added, 258 lines removed. Fields like inflight, link and gc_flags came out of struct unix_sock, and the functions unix_inflight() and unix_notinflight() disappeared completely. 194 fewer lines of kernel code, doing the same job faster. It was also missing one list_del.

The exploit is the work of Zhenpeng Lin, and that name carries weight in this field. He did his PhD at Northwestern. In 2022 he published DirtyCred, a technique that swaps unprivileged kernel credential objects for privileged ones and turns a broad class of kernel bugs into root access. That same year he published GREBE, a system for working out how exploitable a given kernel bug actually is. He now works at DepthFirst, the firm behind this research.

The bug came out of dfs-large1, a model the firm built on top of GLM 5.2 and then post-trained inside their own agent harness with reinforcement learning. It is trained on three jobs at once: find vulnerabilities in application code, find them in low-level systems code, and judge whether a candidate finding holds up. They put a budget penalty on the number of findings it returns, so the model loses credit for guessing.

While the firm was working on this, someone else found the same bug independently, by hand. Kyle Zeng reported it to the kernel security team, and his is the name in the commit:

1
Reported-by: Kyle Zeng

PhD from Arizona State, core developer of angr, maintainer of how2heap. Pwn2Own winner in 2022, 2023 and 2024. In August 2022 he took the first maximum bounty in the history of the kCTF program, 91,337 dollars, and he has escaped containers in Google Kubernetes Engine five separate times using four different techniques.

A model and a researcher with that record found the same hole within weeks of each other, in code that had been sitting untouched for two years.

The fix was written by the person who wrote the bug. Kuniyuki Iwashima maintains the Unix socket code. He authored the March 2024 rewrite from an Amazon address and the fix on 4 August 2026 from a Google address. Same maintainer, same file, two years apart.

The patch is this:

1
2
3
4
5
6
7
@@ -186,6 +186,7 @@ static void unix_del_edge(struct scm_fp_list *fpl, struct unix_edge *edge)
 	if (!vertex->out_degree) {
 		edge->predecessor->vertex = NULL;
 		list_move_tail(&vertex->entry, &fpl->vertices);
+		list_del(&vertex->scc_entry);
 	}
 }

One line. Take the vertex out of the ring before it goes away.

That one line got a review two days later, and not from a person. The kernel networking list had started running patches past an AI reviewer that summer. On 6 August the networking maintainer forwarded its output into the thread by hand, with a note on top saying this was an AI-generated review.

The review left the fix alone and went after the counter that tracks how many cyclic components the graph still holds. The fast path decrements that counter without checking, so a leftover ring could push the count down while cyclic components are still there. And if two leftover rings turn up in one pass while the counter sits at 1, the second decrement wraps an unsigned long around to ULONG_MAX. That pins the graph in the cyclic state, and from then on fd-passing sends take the slow synchronous path.

Thirty minutes later the author of the patch answered: good point, looks like Claude is now better than Gemini. He added a second line, if (list_empty(&scc)) cyclic_sccs--;, so the counter only drops when the component is empty.

A model found the bug and a model reviewed the fix, on the same patch, in the same week. The man who wrote both the bug and the fix compared the two models out loud on a public mailing list.

The exploit did not go straight to the internet. On 24 July 2026 it went into kernelCTF, the program that pays a flat rate for a working exploit against a hardened kernel target. The submission won its slot, the firm was told on 5 August, and it reported the bug to the kernel security team that same day. The upstream fix landed on 6 August. The CVE was published on 26 August.

The submission is public: pull request 431 on the security-research repository, opened on 14 September against the lts-6.12.95 target, with the source, the writeup and a precompiled binary. On 18 September an engineer added the label that counts, vuln OK, meaning a person verified by hand that the exploit does what it claims.

The kernel was fixed on 6 August. Ubuntu has not shipped it. The research and the working exploit went public on 22 September. As of 24 September, Ubuntu’s own tracker reads like this:

  • โ†’ 26.04: twelve kernel packages vulnerable, the main one marked work in progress
  • โ†’ 24.04: fourteen kernel packages vulnerable, including linux-gke
  • โ†’ 22.04: the default kernel not affected, but linux-hwe-6.8, linux-ibm-6.8 and linux-oracle-6.8 vulnerable

That last line is where most of the coverage went wrong, in both directions. Run 22.04 with the stock kernel and you are outside this. Run 22.04 with the hardware enablement kernel, which is exactly what you install when your machine is newer than the release you put on it, and you are inside. Same distribution, same version number, opposite answer.

The published exploits target Ubuntu 26.04 on 7.0.0-31-generic and Ubuntu 24.04 on 6.8.0-139-generic, and they are hard-coded to those builds. They check the release string and quit if it does not match. That is less comfort than it sounds, because the source is sitting in public.

One more detail in that pairing. The second exploit covers CVE-2026-52910, a use-after-free in the reuseport code for classic BPF programs in net/core/sock_reuseport.c. On Ubuntu 26.04 that one is already fixed, in 7.0.0-31.31. Which is the exact build the first exploit targets. The machine they walked into had one patch and not the other.

For the researchers this is bigger than one bad month for one distribution. The barrier to escaping a container through the kernel has dropped far enough that you should assume an attacker gets out whenever they decide to. They counted 5,976 unique Linux kernel CVEs published in 2026 up to mid-September. August alone accounted for 1,650, more than a quarter of the year. And of the 36 that have come out of kernelCTF publicly, 13 are reachable from ordinary unprivileged interfaces, which is the kind of interface sitting inside the container images in use right now.

They want the industry to stop treating a shared kernel as a boundary for code you do not trust, and to move that work to microVMs. Firecracker and Kata Containers give each workload its own small kernel, so a kernel exploit costs the attacker their own instance and nothing beyond it. That is the difference between losing one job and losing the machine that job was on.

What to do if this touches anything you run:

  • โ†’ Check the kernel you are on: uname -r
  • โ†’ 6.10 or newer puts you in range, as does 6.1 at .141 or above, or 6.6 at .93 or above
  • โ†’ The fixed versions are 6.12.111, 6.18.53, 7.1.10 and 7.2 and up
  • โ†’ On 22.04, find out whether you are on the stock kernel or a hardware enablement kernel, because the answer differs
  • โ†’ Do not run code you do not trust in a container on a host that matters to you while that host kernel is unpatched
  • โ†’ Follow the distribution’s page for this CVE instead of waiting for a mail to arrive

Run uname -r on your own machine tonight and see whether the kernel you are trusting is one of the ones in range. My Ethical Hacking Complete Course Zero to Expert takes you there step by step: reconnaissance, scanning, exploitation and traffic analysis, hands-on, from your first day with no Linux or hacking background.

โ†’ Join my complete ethical hacking course

Hacking is not a hobby but a way of life.

Sources:

depthfirst | netdev patch thread | Ubuntu Security Tracker | Google kernelCTF PR 431

 
NEWSLETTER

Stay updated

Get the latest posts in your inbox every week. Ethical hacking, security news, tutorials, and everything that catches my attention. If that sounds useful, drop your email below.

By Bulls Eye

Jolanda de koff โ€ข email โ€ข donate

My name is Jolanda de Koff and on the internet, I'm also known as Bulls Eye. Ethical Hacker, Penetration tester, Researcher, Programmer, Self Learner, and forever n00b. Not necessarily in that order. Like to make my own hacking tools and I sometimes share them with you. "You can create art & beauty with a computer and Hacking is not a hobby but a way of life ...

I โ™ฅ open-source and Linux