needhelp
← Back to blog

Dirty Frag: A New Linux Kernel Zero-Copy Privilege Escalation Vulnerability

by xingwangzhe
Linux
Kernel
Security
LPE
Dirty Frag
CVE

Dirty Frag: A vulnerability chain on the Linux kernel’s zero-copy path that poisons page cache writes, chaining together the xfrm-ESP and RxRPC vulnerabilities. It affects nearly all mainstream distributions since 2017 and allows passwordless privilege escalation to root.


Here We Go Again

To be honest, I never thought I’d be writing another kernel privilege escalation post so soon.

It’s only been eight days since the Copy Fail article. Eight days! The algif_aead blacklist from Copy Fail had barely been typed into the terminal, the patch wasn’t even warm, and then—Dirty Frag dropped.

What’s even more absurd is that Copy Fail’s mitigations are completely ineffective against Dirty Frag. This time, the attack targets entirely different kernel subsystems—the xfrm-ESP and RxRPC encryption paths—so whether you disable algif_aead or not makes absolutely no difference.

Dirty Pipe (2022), Copy Fail (2026.04), Dirty Frag (2026.05)… Every time it’s “affects nearly all distributions since 2017.” Do you know what that means? It means for nearly the past decade, anyone who logged into any unprivileged account on your system had a chance to quietly become root.

But thinking about it, is this entirely the kernel developers’ fault? Not necessarily.

The explosive growth of AI-assisted code auditing tools in recent years allows security researchers to scan out vulnerabilities lurking for nearly a decade within hours. Bugs that previously required manual line-by-line review are now swept out in batches by AI. Copy Fail was located by the AI tool Xint Code within one hour. Although Dirty Frag came from manual auditing by Korean researcher Hyunwoo Kim, the code paths it exploits belong to the same family as Copy Fail—page cache writes on the zero-copy path—indicating that this type of design flaw is far from an isolated case. Better tools, higher efficiency, and naturally more vulnerabilities are “discovered.”

What’s even more disturbing is the disclosure process this time. The researcher originally submitted vulnerability details to the kernel security team with an agreed 5-day embargo to give distributions time to patch. But on the same day, an unrelated third party directly published the complete details and exploit for the ESP(xfrm) vulnerability. No CVE, no patch, but the PoC was available to everyone. This isn’t responsible disclosure—it’s stabbing every Linux user in the back. The patching window was blown away, and everyone was forced to run naked.

Alright, rant over. Let’s take a serious look at what this vulnerability is all about.


Timeline

Date Event
2017-01 xfrm-ESP vulnerability introduced with commit cac2661c53f3 (lurked for 9 years)
2023-06 RxRPC vulnerability introduced with commit 2dc334f1a63a
2026-04-29 Korean researcher Hyunwoo Kim (@v4bel) reports RxRPC vulnerability and full exploit to security@kernel.org
2026-05-07 Vulnerability details submitted to linux-distros mailing list with agreed 5-day embargo
2026-05-07 Same day, third party publicly releases ESP(xfrm) vulnerability details and exploit, embargo broken immediately
2026-05-07 After consultation with distribution maintainers, full Dirty Frag documentation publicly released. At this point no CVE, no official patch
2026-05-08 At time of writing, major distributions are still waiting for upstream patch merge

Impact Analysis

Metric Details
CVE None yet (NVD had no time to assign before embargo broke)
Vulnerability Type Deterministic logic flaw, not a race condition
Exploitation Success Rate 100%, guaranteed success on first execution
Affected Range Nearly all mainstream Linux distributions since 2017 (as of May 2026, latest kernel 7.0.3 also affected)
Exploitation Method 192-byte payload, assembling a root-shell ELF through 48 4-byte writes
Disk Footprint No persistent modification — only pollutes in-memory page cache, bypasses inotify; restored on reboot
Bypasses Copy Fail Mitigation Copy Fail’s algif_aead disable is completely ineffective as it attacks different subsystems

Comparison with historical kernel LPE vulnerabilities:

Feature Dirty Cow (2016) Dirty Pipe (2022) Copy Fail (2026) Dirty Frag (2026)
Race condition Required Not required Not required Not required
Affected range Specific versions 5.8+ All mainstream distros since 2017+ All mainstream distros since 2017+
Exploit code complexity Complex Complex 10 lines Python Single-file C program
Disk footprint Yes Yes No No
Patch status Fixed Fixed Fixed No official patch yet

Confirmed affected distributions:

Distribution Tested Kernel Version
Ubuntu 24.04.4 6.17.0-23-generic
RHEL 10.1 6.12.0-124.49.1
CentOS Stream 10 6.12.0-224
AlmaLinux 10 6.12.0-124.52.3
Fedora 44 6.19.14-300
openSUSE Tumbleweed 7.0.2-1
Arch Linux 7.0.3

From 6.12 to 7.0, from Ubuntu to Arch—full platform coverage. Yes, pretty much any mainstream distribution you have installed is likely affected.


Technical Principles: Why “Dirty Frag”?

The Core in One Sentence

splice() implants a read-only file’s page cache reference into the network send buffer’s (skb) frag slot → the receiving kernel performs in-place cryptographic operations on the frag (in-place crypto, src == dst pointing to the same memory) → what should have been a decryption operation on ciphertext becomes a direct STORE primitive writing to read-only page cache → replacing su’s machine code with a root-shell ELF.

“Dirty” refers to polluting the page cache, “Frag” refers to exploiting the skb (socket buffer) fragment mechanism. Together—Dirty Frag.

Zero-Copy Path skb Frag Pollution

This part is key to understanding the entire vulnerability, so let’s expand on it.

Normally, when you write data to a socket, the kernel copies the data from userspace into its skb. But splice() takes the zero-copy path—it directly stuffs the pointer to the file page cache page (page struct + offset) into the skb’s frag array without copying the data itself:

struct skb_shared_info {
struct sk_buff *frag_list; // frag linked list
skb_frag_t frags[MAX_SKB_FRAGS]; // frag array, each is {page, offset, size}
// ...
};

The key point here: the page passed to splice() is your file’s (e.g., /usr/bin/su) in-memory page cache page—you only have read permissions, but a reference to this page has already been stuffed into the network protocol stack’s skb.

Next, it depends on whether the receiving network protocol stack “gets itchy hands” and writes to this page.

Vulnerability 1: xfrm-ESP Page-Cache Write

The first vulnerability lies on the IPsec ESP (Encapsulating Security Payload) decryption path.

The esp_input() function is used to decrypt ciphertext data. Normally, if the skb’s data region would be shared with a frag, the kernel should first call skb_cow_data() (Copy-on-Write) to copy the shared page before operating on it. However, there exists a code path in esp_input() that bypasses COW:

static int esp_input(struct xfrm_state *x, struct sk_buff *skb)
{
if (!skb_cloned(skb)) {
if (!skb_is_nonlinear(skb)) {
// [1] Linear skb: only head data, no frag, safe
nfrags = 1;
goto skip_cow;
} else if (!skb_has_frag_list(skb)) {
// [2] Has frag, but no frag_list → directly skips cow!
nfrags = skb_shinfo(skb)->nr_frags;
nfrags++;
goto skip_cow; // The bomb is here
}
}
// Normal path: copy data, safe
err = skb_cow_data(skb, 0, &trailer);
}

When the skb is nonlinear (has frags holding the page cache passed by splice) but frag_list is empty, the code directly jumps to skip_cow. Then, the subsequent crypto_authenc_esn_decrypt() performs in-place AEAD decryption—src and dst point to the same scatterlist, which is the page cache page implanted by the attacker.

During the decryption process, there’s one particularly critical line of code:

// Move the high 4 bytes of sequence number to the end of dst SGL
scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1);

This line writes 4 bytes to dst (your su page cache page) at offset assoclen + cryptlen. The value of tmp + 1 comes from the high 32 bits of the ESP header’s sequence number—something the attacker fully controls through XFRMA_REPLAY_ESN_VAL.seq_hi when registering the XFRM SA (Security Association).

So the attacker simultaneously controls:

  • Where to write (file offset, positioned by adjusting payload length)
  • What value to write (4 bytes, specified via seq_hi)

By registering 48 different XFRM SAs, each with its seq_hi storing one 4-byte fragment of the ELF, and looping 48 times, a complete root-shell ELF is assembled into su’s page cache.

However, this vulnerability has one limitation: registering XFRM SAs requires CAP_NET_ADMIN privileges. Attackers can obtain this by creating user namespaces (unshare(CLONE_NEWUSER | CLONE_NEWNET))—but Ubuntu’s AppArmor happens to prevent unprivileged users from creating network namespaces.

This is why a second vulnerability is needed.

Vulnerability 2: RxRPC Page-Cache Write

The second vulnerability lies on the RxRPC protocol’s Kerberos authentication decryption path.

The rxkad_verify_packet_1() function performs in-place pcbc(fcrypt) decryption on the first 8 bytes of received data packets:

skcipher_request_set_crypt(req, sg, sg, 8, iv.x);
// ^^ ^^
// src==dst → in-place operation!
ret = crypto_skcipher_decrypt(req); // 8-byte write happens here

skb_to_sgvec() directly converts the skb’s frag (which contains the page cache page spliced in by the attacker) into a scatterlist, with src and dst being the same sg. So the decryption operation directly writes the 8-byte “decryption result” back to that read-only page cache page.

Compared to xfrm-ESP:

Feature xfrm-ESP RxRPC
Write size 4 bytes 8 bytes
Value control Direct control (seq_hi) Indirect (requires fcrypt key brute-force)
Privilege required User namespace No privilege required
Introduction time 2017-01 2023-06

The values written via the RxRPC path cannot be directly controlled—they are the result of fcrypt_decrypt(C, K). The attacker needs to first register a key K via add_key("rxrpc", ...), then brute-force in userspace to find the K that produces the target 8-byte plaintext. Fortunately, fcrypt is a 56-bit key, 8-byte block cipher dedicated to the Andrew File System, so brute-forcing isn’t a big problem.

The most critical point: the RxRPC path requires absolutely no privileges. No need to create user namespaces, no need for network namespaces, no need for CAP_NET_ADMIN. And Ubuntu loads the rxrpc.ko module by default.

Chaining Logic

The two vulnerabilities complement each other, providing full coverage:

Scenario Vulnerability Used Reason
Ubuntu (AppArmor blocks namespaces) RxRPC rxrpc.ko loaded by default, no privilege required
RHEL / Fedora / openSUSE xfrm-ESP Namespaces available, ESP writes precisely controllable
Other distributions xfrm-ESP or RxRPC Choose based on module loading, at least one path available

This is what makes Dirty Frag terrifying—no matter how you configure it, there’s always a path to root.


PoC Analysis

The complete exploit is open-sourced at github.com/V4bel/dirtyfrag. Compiling and running requires just one line:

Terminal window
git clone https://github.com/V4bel/dirtyfrag.git
cd dirtyfrag && gcc -O0 -Wall -o exp exp.c -lutil && ./exp

The core idea isn’t complicated:

  1. Prepare a 192-byte minimal ELF—a root-shell that executes with privilege escalation
  2. Split this 192 bytes into 48 4-byte chunks (if using the xfrm-ESP path)
  3. Register one XFRM SA for each 4-byte chunk, with its seq_hi set to that chunk’s value
  4. Each time, use splice() to send su’s page cache into the skb, triggering one in-place write
  5. Loop 48 times, and su’s page cache region (first 192 bytes) is completely replaced with the root-shell ELF
  6. execve("/usr/bin/su") → root shell

The entire process never touches disk files. The on-disk /usr/bin/su remains untouched, its md5 still the original md5. But in the kernel’s page cache, it has been secretly replaced.

When any process initiates execve on su, the kernel reads from the page cache—oops, it runs the binary you injected. Root obtained.


Emergency Response: Temporarily Disable Vulnerable Modules

As of May 8, 2026, official kernel patches have not yet been released. Until patches are available, the only temporary mitigation is to unload and blacklist the three vulnerable modules.

First, confirm whether your system is affected:

Terminal window
lsmod | grep -E 'esp4|esp6|rxrpc'

If there’s any output, you’re affected.

Execute the following commands immediately (no reboot required):

Terminal window
sudo sh -c "printf 'install esp4 /bin/false\ninstall esp6 /bin/false\ninstall rxrpc /bin/false\n' > /etc/modprobe.d/dirtyfrag.conf"
sudo rmmod esp4 esp6 rxrpc 2>/dev/null

Verify:

Terminal window
lsmod | grep -E 'esp4|esp6|rxrpc'

Important: If you suspect the PoC has already been executed, you must clear the page cache:

Terminal window
echo 3 | sudo tee /proc/sys/vm/drop_caches

If you don’t do this, even after disabling the modules, the polluted page cache remains in memory—running su still gives a root shell.

Side effects:

  • Disabling esp4/esp6 will interrupt IPsec VPN tunnels. Desktop users and most servers are unaffected, but if you rely on IPsec VPN (such as strongSwan, Libreswan), please evaluate the impact first.
  • Disabling rxrpc has minimal impact unless you’re using AFS (Andrew File System).

AI-Accelerated Vulnerability Arms Race

The exposure of Dirty Frag forces us to think about a deeper question: Why are kernel LPE vulnerabilities popping up one after another recently?

Vulnerability Discovery Time Key Tool From Report to Public
Dirty Pipe 2022 Manual audit Standard process
Copy Fail 2026-04 Xint Code (AI) About one month
Dirty Frag 2026-05 Manual audit 8 days (embargo broken same day)

Kernel LPE vulnerabilities went from one per year to one per month, and now from one per month to… one per week?

Behind this is the explosive growth of AI-assisted code auditing tools. Previously, relying on human line-by-line review, a vulnerability sitting undiscovered for ten years was normal. Now AI can scan an entire subsystem within hours, pulling out all potential zero-copy paths, in-place operations, and shared references. Better tools, higher efficiency, and naturally more vulnerabilities are “discovered”—the kernel hasn’t suddenly gotten worse, it’s just that old debts hidden in corners are being uncovered one by one by AI.

What’s even more alarming is the change in the disclosure ecosystem. Dirty Frag’s embargo was deliberately broken by a third party, with vulnerability details and PoC released on the same day. What does this mean? It means from the researcher reporting the vulnerability to security@kernel.org to hackers worldwide getting their weapons, only eight days passed. The patching window—gone.

We can blame the person who broke the embargo for being unethical. But the reality is, this will only happen more and more. AI makes finding vulnerabilities faster, and weaponization faster too. You can’t always count on everyone abiding by embargo agreements.

And don’t forget, the same vulnerability family is still being dug out:

Vulnerability Subsystem Status
Copy Fail AF_ALG + authencesn Fixed
Dirty Frag xfrm-ESP + RxRPC No patch
Copy Fail 2 ESP-in-UDP Publicly disclosed
ZCRX Freelist io_uring ZCRX Publicly disclosed

The core principles of these four vulnerabilities are strikingly similar—splice() stuffs read-only page cache references into kernel subsystems, and the subsystems write in-place on the frags. What they expose is actually the same class of design problem:

Component Reason for Introduction Side Effect
splice() Zero-copy, performance optimization Read-only page cache references sent into kernel subsystems
AF_ALG Expose kernel crypto capabilities Unprivileged users can directly initiate crypto sessions
xfrm-ESP IPsec acceleration In-place decryption, using read-only pages as output buffers
RxRPC AFS network protocol support Same as above, doesn’t even need namespace privileges

Each design, taken individually, is a reasonable performance optimization or functional requirement. But pieced together, they form a vulnerability chain where any local user can become root without a password.

Unless upstream kernel thoroughly re-examines the paradigm of “in-place operations on zero-copy paths,” I guarantee you—this won’t be the last one.

For ordinary users, my advice is simple:

  1. Execute the blacklist commands above right now. Change them back when the official patch arrives.
  2. Closely monitor kernel updates from your package manager. Once a fixed version is available, upgrade and reboot immediately.
  3. Regularly lsmod | grep to check if these modules have been accidentally loaded.

References


Infographics

Dirty Frag Overview Diagram

Figure 1: Dirty Frag vulnerability overview — how zero-copy page cache references are exploited through skb frags

Share this page