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

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

Impact Analysis

MetricDetails
CVENone yet (NVD had no time to assign before embargo broke)
Vulnerability TypeDeterministic logic flaw, not a race condition
Exploitation Success Rate100%, guaranteed success on first execution
Affected RangeNearly all mainstream Linux distributions since 2017 (as of May 2026, latest kernel 7.0.3 also affected)
Exploitation Method192-byte payload, assembling a root-shell ELF through 48 4-byte writes
Disk FootprintNo persistent modification — only pollutes in-memory page cache, bypasses inotify; restored on reboot
Bypasses Copy Fail MitigationCopy Fail’s algif_aead disable is completely ineffective as it attacks different subsystems

Comparison with historical kernel LPE vulnerabilities:

FeatureDirty Cow (2016)Dirty Pipe (2022)Copy Fail (2026)Dirty Frag (2026)
Race conditionRequiredNot requiredNot requiredNot required
Affected rangeSpecific versions5.8+All mainstream distros since 2017+All mainstream distros since 2017+
Exploit code complexityComplexComplex10 lines PythonSingle-file C program
Disk footprintYesYesNoNo
Patch statusFixedFixedFixedNo official patch yet

Confirmed affected distributions:

DistributionTested Kernel Version
Ubuntu 24.04.46.17.0-23-generic
RHEL 10.16.12.0-124.49.1
CentOS Stream 106.12.0-224
AlmaLinux 106.12.0-124.52.3
Fedora 446.19.14-300
openSUSE Tumbleweed7.0.2-1
Arch Linux7.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:

Featurexfrm-ESPRxRPC
Write size4 bytes8 bytes
Value controlDirect control (seq_hi)Indirect (requires fcrypt key brute-force)
Privilege requiredUser namespaceNo privilege required
Introduction time2017-012023-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:

ScenarioVulnerability UsedReason
Ubuntu (AppArmor blocks namespaces)RxRPCrxrpc.ko loaded by default, no privilege required
RHEL / Fedora / openSUSExfrm-ESPNamespaces available, ESP writes precisely controllable
Other distributionsxfrm-ESP or RxRPCChoose 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:

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:

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

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

Execute the following commands immediately (no reboot required):

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:

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

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

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?

VulnerabilityDiscovery TimeKey ToolFrom Report to Public
Dirty Pipe2022Manual auditStandard process
Copy Fail2026-04Xint Code (AI)About one month
Dirty Frag2026-05Manual audit8 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:

VulnerabilitySubsystemStatus
Copy FailAF_ALG + authencesnFixed
Dirty Fragxfrm-ESP + RxRPCNo patch
Copy Fail 2ESP-in-UDPPublicly disclosed
ZCRX Freelistio_uring ZCRXPublicly 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:

ComponentReason for IntroductionSide Effect
splice()Zero-copy, performance optimizationRead-only page cache references sent into kernel subsystems
AF_ALGExpose kernel crypto capabilitiesUnprivileged users can directly initiate crypto sessions
xfrm-ESPIPsec accelerationIn-place decryption, using read-only pages as output buffers
RxRPCAFS network protocol supportSame 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