CopyFail and the Splice Attack Parade
Table of Contents
Usual disclaimer: I don’t work for any of the vendors involved and I have no inside information. This is my read as a homelab operator and a security nerd, stitched together from the public advisories (Microsoft, Unit 42, CISA) and the proof-of-concepts that followed. If I got something wrong, tell me.
I keep a small pile of Linux boxes in a closet. A hypervisor, a few VMs, a stack of containers doing boring jobs while I sleep. For most of May, my morning threat feed read like an episodic TV show: every few days, a new named Linux privilege-escalation bug, a fresh logo, and the same punchline. Any local user can become root.
CopyFail. Dirty Frag. Fragnesia. DirtyDecrypt. CIFSwitch. Five of them in about a month, each with a public proof-of-concept, a couple seen in or near live attacks. If you run Linux anywhere (a server, a VPS, a Kubernetes cluster, a Pi on a shelf) this was your May too, whether you were watching or not.
I want to walk through the first and cleanest of them, CopyFail (CVE-2026-31431), because once you understand how it works, you understand the whole parade. They’re all riffs on one old, load-bearing idea.
Before the mechanics, a few reasons a rash of these matters even if no single one is the end of the world:
- Local privilege escalation is the quiet second half of almost every real breach. The phishing email or the exposed service gets an attacker a shell; an LPE gets them root. The headlines love the front door; bugs like this are how the house actually gets taken.
- “Just patch your kernel” sounds trivial until you count how many kernels you actually run. Every VM, every distinct container base image, that one VPS you forgot about. A kernel patch means a reboot, and a reboot means scheduling downtime on things you’d rather not touch.
- The page cache is some of the oldest, most trusted, most-optimized code in the kernel. When the bug lives there, it tends to affect every distro at once and reach back years.
- In shared tenancy (containers, multi-tenant cloud) “local root” can quietly become “everyone on the box is root.” The blast radius is the whole host.
But what’s actually going on under the hood…
First, the one idea you need: the page cache
Here’s the thing the whole parade depends on.
When your computer reads a file off disk, the kernel doesn’t throw the bytes away when it’s done. It keeps them in memory, in something called the page cache, so the next read is instant. This is why the second time you open a program it launches faster than the first. The file is already sitting in RAM.
Crucially, that cached copy is shared. If ten programs read /usr/bin/sudo, the kernel doesn’t keep ten copies; it keeps one set of pages in the cache and hands every process a reference to them. Efficient. Elegant. And, it turns out, a fantastic place to cause trouble, because some of the files sitting in that cache are setuid binaries.
A setuid binary is a program that runs with the privileges of its owner, not the user who launched it. sudo, su, passwd, mount. They’re owned by root and carry a special bit that says “when anyone runs me, run me as root.” That’s by design; it’s how an unprivileged user is allowed to, say, change their own password. Normally you can execute these files but you absolutely cannot write to them.
Now hold those two facts next to each other: the kernel keeps a single shared in-memory copy of a root-owned program, and that program will happily run as root the next time anyone launches it. If you could sneak even a few bytes of your own into that cached copy, you’d be writing code that the system runs as root for you.
That’s not a new idea. It’s the family business.
- Dirty COW (CVE-2016-5195) abused a race in copy-on-write memory to write into read-only files.
- Dirty Pipe (CVE-2022-0847) used Linux pipes and the
splice()system call to inject data into the page cache of files you only had read access to.
CopyFail is the next one in the bloodline. Not the identical bug (Dirty COW was a copy-on-write race, not a splice trick) but the same destination: a few bytes ending up in a page-cache page you should never have been able to touch.
What splice() has to do with anything
splice() is a system call built for speed. Its whole job is to move data between two file descriptors without copying it through your program’s memory first. Normally, to move bytes from a file to a socket, the data makes a round trip: disk into the kernel, kernel up into your program, your program back down into the kernel, kernel out to the socket. splice() skips the round trip. It tells the kernel “just move the reference to these pages from here to there.” Zero-copy. Very fast. The backbone of high-performance servers everywhere.
You can probably already smell the problem. Zero-copy means the kernel is shuffling around references to the real page-cache pages, the shared ones, instead of safe private copies. Get the bookkeeping wrong on who’s allowed to write to a spliced page, and you’ve handed an unprivileged user a pen pointed straight at the cache. Dirty Pipe was exactly that mistake in the pipe code. CopyFail is the same class of mistake in a different subsystem.
What actually happened: CopyFail
CopyFail lives in the Linux kernel’s algif_aead module, part of AF_ALG, the interface that lets userspace programs ask the kernel to do crypto for them over a socket. It’s a useful, obscure corner of the kernel, exactly the kind of code that doesn’t get the same scrutiny as the parts everyone touches daily.
The formal classification is a mouthful, “Incorrect Resource Transfer Between Spheres,” which is just a precise way of saying data crossed a boundary it shouldn’t have. In practice, per Microsoft’s and Unit 42’s write-ups, an unprivileged local user drives the AF_ALG crypto interface together with splice() to land a tiny write, on the order of four bytes, into the cached copy of a readable setuid binary. And here’s the unusual part: it isn’t a race at all. It’s a deterministic logic flaw, so it works on the first try, every time, with no timing window to hit. Four bytes is plenty. You corrupt the right spot in a root-owned program, wait for it to run, and your code executes as root. Game over, locally.
A few details that make this one worth respecting:
- The public exploit is a tiny, deterministic Python script, reportedly around 732 bytes, and it just works unmodified across distros. No spray-and-pray, no flaky timing window you have to hit a thousand times. That is the difference between a paper and a weapon.
- It reaches back to roughly 2017. Because the flaw is in long-standing core code, it hit Ubuntu, RHEL, Amazon Linux, SUSE, Debian, Arch, and Fedora more or less simultaneously. Almost nobody was not affected.
- It breaks out of containers. In shared Kubernetes or multi-tenant cloud, page-cache pages are shared across the host, so “root in my container” becomes “root on the node, and on the neighbors.”
The timeline tells you how fast this stuff moves now. The proof-of-concept dropped publicly on April 29. CISA added it to the Known Exploited Vulnerabilities catalog on May 1, and Microsoft’s MSTIC analysis landed the same day. Within days, vendors were reporting exploitation in the wild. From “here’s a bug” to “people are getting rooted with it” in under a week. The patch cycle has to win that race, and it usually doesn’t.
The rest of the parade
Once you can see the shape of CopyFail, the others snap into focus. They’re not all the same bug, but they’re the same neighborhood: low-level kernel code that moves memory around, where a subtle accounting error hands an unprivileged user a write or a root they shouldn’t have.
- Dirty Frag (CVE-2026-43284 / CVE-2026-43500) lives in the kernel’s network memory-fragment handling (
esp4,esp6,rxrpc). MSTIC watched it used in live post-compromise activity, the textbook “we already have a shell, now we want root” move. One half got patched May 8; therxrpcvariant sat unpatched for a while after. - Fragnesia (CVE-2026-46300), and by various accounts DirtyDecrypt (reportedly in the
rxgkcode) and CIFSwitch, rounded out the month. Different subsystems, same theme.
Notice the pattern in the names. “Dirty” anything is a deliberate nod to the lineage. These researchers know exactly which family tree they’re standing in, and they’re telling you. The page cache and the kernel’s zero-copy plumbing are a rich, under-audited seam, and once a few people start digging there with modern fuzzers, you get a vein, not a nugget.
So what does an operator actually do
Nothing here is exotic to fix. It’s just tedious, which is its own kind of hard.
- Patch kernels and reboot. That’s the real fix, and the annoying one. The kernel patch itself is almost anticlimactic: they reverted the 2017 in-place optimization and went back to doing the crypto out-of-place, so source and destination never share memory again. But a patch isn’t live until you reboot, so this is a scheduling problem as much as a security one. Live-patching tools (kpatch, Ubuntu Livepatch) help for some of these and not others.
- Shrink the attack surface. Several of these live in kernel modules most boxes never use. If you’re not running AF_ALG crypto offload or
rxrpc, there’s a real argument for blacklisting the modules so the vulnerable code can’t even load. Less kernel exposed, less to exploit. - Watch for the tell. Every one of these ends the same way: an unprivileged process suddenly becomes root. If you have any host telemetry at all, alert on unexpected transitions to UID 0 from processes that have no business getting there. You won’t always catch the exploit, but you’ll catch the success.
The uncomfortable truth is that for the window between disclosure and your next reboot, a local attacker on an unpatched box owns it. The mitigation buys you detection and surface reduction; it doesn’t make the bug not work.
Final thoughts
Local privilege escalation never gets the headlines. There’s no dramatic outage, no blue screens in an airport, no logo on the news. It’s the unglamorous middle of the kill chain, and that’s exactly why it’s worth understanding: almost every breach that actually hurts involves a moment where the attacker turns a foothold into total control, and bugs like CopyFail are how that moment happens.
What May really showed me is that the page cache, this decades-old, deeply trusted, performance-critical bit of plumbing, is a frontier again. The same property that makes splice() and the page cache fast (don’t copy, just share references) is the property that keeps making them exploitable. That tension isn’t getting resolved; it’s the design. So the parade isn’t over. There will be another “Dirty” something, with another logo, and the way you’ll understand it in five minutes flat is by remembering this: shared memory, a setuid binary, and a few bytes that ended up somewhere they shouldn’t have.
Patch your kernels. Reboot the boxes you’ve been avoiding. And maybe go check whether that VPS you forgot about is still running a 2024 kernel. I’ll wait.