Skip to main contentSkip to main content
Room Banner
Back to all walkthroughs
Room Icon

CVE-2026-43284: Dirty Frag

Exploit Dirty Frag, a chained Linux LPE that bypasses every major distro's defences.

easy

30 min

2,275

User profile photo.
User profile photo.

To access material, start machines and answer questions login.

Set up your virtual environment

To successfully complete this room, you'll need to set up your virtual environment. This involves starting the Target Machine, ensuring you're equipped with the necessary tools and access to tackle the challenges ahead.
Target machine
Status:Off

Dirty Frag is a vulnerability class that chains two separate kernel bugs to gain root on every major distribution. The two bugs sit in unrelated subsystems, but they share an identical underlying pattern, and chaining them lets a single exploit binary cover the blind spots that would otherwise stop each bug individually. The class was discovered and reported by @v4bel.

The two component vulnerabilities are:

  • xfrm-ESP Page-Cache WriteCVE-2026-43284, in the IPsec ESP input path. Merged to the netdev tree on 7 May 2026 and accepted into mainline on 8 May 2026 as commit f4c50a4034e6 (opens in new tab).
  • RxRPC Page-Cache WriteCVE-2026-43500 reserved, in the AFS RxRPC verify path. No patch exists in any tree at the time of disclosure.

If you have completed the Copy Fail room, the underlying primitive will be familiar. Both Copy Fail and Dirty Frag write into the in-memory page cache of a file that the attacker only has read access to, and both bypass file integrity monitoring as a result. The subsystems involved differ, but the class of bug is the same.

Dirty Frag is named for the kernel structure it abuses. Where Dirty Pipe corrupted struct pipe_buffer and Copy Fail corrupted the AF_ALG output buffer, Dirty Frag corrupts the frag slot of struct sk_buff. The full disclosure document, including the proof-of-concept, lives at the V4bel/dirtyfrag (opens in new tab) repository.

The combined impact is severe. The chained exploit was tested successfully on Ubuntu 24.04.4, RHEL 10.1, AlmaLinux 10, openSUSE Tumbleweed, CentOS Stream 10, and Fedora 44. The xfrm-ESP variant was introduced by commit cac2661c53f3 on 17 January 2017, giving it nine years of effective lifetime. The RxRPC variant was introduced by commit 2dc334f1a63a in June 2023.

A short comparison against the previous two room subjects:

Property Dirty Pipe (CVE-2022-0847) Copy Fail (CVE-2026-31431) Dirty Frag
Kernel structure abused struct pipe_buffer AF_ALG output buffer struct sk_buff frag
Primitive size Arbitrary 4-byte controlled 4 or 8-byte controlled
Race condition required Originally yes No No
On-disk file changed No No No
File integrity bypass Yes Yes Yes
Affects all major distros Yes Yes Yes (by chaining)

Learning Objectives

By the end of this room, you will be able to:

  • Explain the shared zero-copy splice pattern that links Dirty Pipe, Copy Fail, and Dirty Frag
  • Describe the xfrm-ESP variant and how attacker-controlled seq_hi lands in the page cache
  • Describe the RxRPC variant and why brute-forcing in user-space is required
  • Explain why the two variants are chained and how the chain bypasses distribution-specific defences
  • Apply the published modprobe mitigation on a vulnerable host

Prerequisites

This room assumes comfort with Linux command-line operation and a working understanding of local privilege escalation, as covered in Linux Fundamentals and Linux Privilege Escalation. Familiarity with the Copy Fail room is strongly recommended; Dirty Frag builds directly on the page cache write primitive introduced there.

Connecting to the Machine

Click the Start Machine button at the top of this task and allow about a minute for it to boot. The room launches an in-browser split-view terminal already logged in as the unprivileged user karen. Credentials for access from your own machine are provided in Task 5.

Answer the questions below

I have successfully started my machine.

Dirty Frag, Copy Fail, and Dirty Pipe are all instances of the same class of bug. Understanding that class makes both component variants of Dirty Frag straightforward to follow, and explains why the chain works the way it does.

struct sk_buff and the frag Slot

A socket buffer (struct sk_buff, usually shortened to skb) is the kernel data structure that represents a network packet as it passes through the stack. An skb has a linear data area, plus an array of page references called fragments (frags), plus an optional list of further skbs (frag_list). The combination lets the kernel represent a single logical packet as a chain of memory regions without copying any of them into a single contiguous buffer.

The fragment slots are the relevant detail for Dirty Frag. Each fragment is a reference to a memory page along with an offset and length. When a fragment is added to an skb, the page is not copied. The skb simply records that this region of memory belongs to it for the duration of the packet's life.

The MSG_SPLICE_PAGES Path

splice() is a Linux system call that moves data between file descriptors by transferring page references rather than copying bytes. When a process splices from a file into a socket, the kernel does not allocate a new buffer and copy the file's contents into it. The kernel attaches a reference to the file's page cache page directly to the outgoing skb's fragment list. The flag that signals this behaviour is MSG_SPLICE_PAGES, set automatically by splice_to_socket().

The performance benefit is significant. The expensive bit of network I/O on a busy server is the memory copy from user buffer to kernel buffer, and zero-copy paths skip it entirely. The trade-off, however, is that the page reference now sits in an skb whose contents the kernel may want to mutate. If the kernel performs an in-place operation on that fragment, it ends up writing into the page cache of a file the sending process only had read access to.

SKBFL_SHARED_FRAG and Why It Matters

The TCP stack is aware of this risk. After skb_splice_from_iter() plants a page cache page into a skb, the stack sets the SKBFL_SHARED_FRAG flag on the skb. Any later code path that wants to modify the skb's contents checks this flag and copies the data into a private buffer first, using skb_cow_data(), before performing any mutation. This protects the page cache from corruption.

The IPv4 and IPv6 datagram append paths, which UDP relies on, did not set this flag. That oversight is the entire root cause of the xfrm-ESP variant of Dirty Frag. UDP skbs constructed via splice carried attacker-pinned page cache pages with no shared-frag marking, looking to the rest of the kernel like ordinary uncloned non-linear skbs.

The patch for CVE-2026-43284 makes the IPv4 and IPv6 datagram paths set SKBFL_SHARED_FRAG to match TCP, and adjusts the ESP input path to honour the flag. Once the flag is set, esp_input is forced through skb_cow_data() and the in-place crypto can no longer reach the attacker's page.

In-Place Crypto: The Common Sink

What both Dirty Frag variants share is the moment the kernel performs an in-place cryptographic operation on top of an attacker-pinned page. Whether the operation is AEAD decryption, single-block decrypt, or scratch byte rearrangement, the side effect is the same: bytes are written into the page cache of a file the attacker only had read access to.

The pattern in pseudocode:

1. Attacker reads a target file (e.g. /usr/bin/su) into the page cache
2. Attacker constructs a packet via splice() so the file's page is in frag[0]
3. Kernel's network stack delivers the packet to a verify or decrypt path
4. That path does in-place crypto on src=dst, where both point at the page
5. The crypto operation's side effect modifies the page cache
6. Subsequent reads of the file return the modified bytes

The on-disk file is never touched. File integrity monitoring tools that hash the file from disk continue to report the file as clean. Every process that subsequently reads the file, including the kernel loading it for execve(), gets the corrupted in-memory copy.

Diagram of the shared Dirty Frag primitive. An unprivileged user holds a read-only file descriptor to /usr/bin/su. The Linux kernel page cache region holds the cached page P. A red arrow labelled splice() plants the page cache reference into the frags[0] slot of a struct sk_buff. From the frag slot, two parallel boxes show the two crypto sinks: the xfrm-ESP variant performing a 4-byte STORE via crypto_authenc_esn_decrypt with attacker-controlled seq_hi at offset assoclen plus cryptlen, and the RxRPC variant performing an 8-byte STORE via pcbc(fcrypt) with the value derived from fcrypt_decrypt of attacker-supplied K. A red arrow loops back from the crypto path into page P, labelled writes back into the same page. On the right, an integrity tool icon reads from the disk region and reports the file as clean

The first half of Dirty Frag is a 4-byte controlled write into the page cache, delivered through the kernel's IPsec ESP input path. The bug has been present since January 2017, giving it the same nine-year lifespan as Copy Fail.

Where the Bug Lives

The relevant function is esp_input() in net/ipv4/esp4.c (and its IPv6 sibling in net/ipv6/esp6.c). Before the patch, the input path took a fast path that skipped skb_cow_data() whenever the skb was uncloned and had no frag_list, even if the skb carried fragments planted via splice. The decision branch looked like this:

if (!skb_cloned(skb)) {
    if (!skb_is_nonlinear(skb)) {
        nfrags = 1;
        goto skip_cow;
    } else if (!skb_has_frag_list(skb)) {
        nfrags = skb_shinfo(skb)->nr_frags;
        nfrags++;
        goto skip_cow;
    }
}
err = skb_cow_data(skb, 0, &trailer);

A non-linear skb with no frag list, made out of attacker-pinned page cache pages, jumped straight to skip_cow. From there, crypto_authenc_esn_decrypt() ran in-place AEAD decryption with src and dst pointing at the same scatter-gather list, both rooted in the attacker's page.

The 4-Byte STORE

Inside crypto_authenc_esn_decrypt(), before the actual decryption, the algorithm rearranges the sequence number bytes. As part of that rearrangement, it performs a 4-byte write at assoclen + cryptlen within the dst scatter-gather list:

scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1);

The value being written is the high-order 32 bits of the sequence number (seq_hi). That value is set by the user when registering the IPsec security association via the XFRMA_REPLAY_ESN_VAL netlink attribute. The user fully controls it.

The position of the write is also controllable. By tuning the payload length and the splice offset so that the attacker's page sits at exactly the assoclen + cryptlen position of the dst SGL, the 4 bytes land at any chosen offset within the page cache.

The HMAC verification afterwards fails (the attacker has no idea what the legitimate authentication key is, and supplies a random one). An -EBADMSG is returned to userspace. However, the 4-byte write has already completed before the verification step. The error has no effect on the corruption.

Privilege Requirement

esp_input() only fires for traffic destined for a registered IPsec security association, and registering an SA requires CAP_NET_ADMIN. The exploit obtains that capability by entering a new user namespace via unshare(CLONE_NEWUSER | CLONE_NEWNET). Inside the the process maps itself to root (uid_map: 0 <real_uid> 1) and gains CAP_NET_ADMIN for the namespace's network operations.

This is where Ubuntu's AppArmor policy becomes relevant. Ubuntu often blocks unprivileged user namespace creation through a system-wide AppArmor profile. On hosts where that policy is active, unshare(CLONE_NEWUSER) returns -EPERM and the xfrm-ESP variant cannot be triggered at all. That blind spot is what the RxRPC variant in Task 4 exists to cover.

How the Trigger Reaches esp_input

A direct socket(AF_INET, SOCK_RAW, IPPROTO_ESP) to inject ESP traffic would be visible to any kernel filter watching for raw sockets. The exploit instead routes through the UDP encapsulation path:

  • A receiver UDP socket is bound to 127.0.0.1:4500 and configured with setsockopt(SOL_UDP, UDP_ENCAP, UDP_ENCAP_ESPINUDP)
  • A sender UDP socket connects to the same port
  • A pipe is filled with a forged ESP wire header (8-byte header plus 16-byte IV) via vmsplice, then 16 bytes of /usr/bin/su's page cache via splice
  • A final splice from pipe to sender socket sends the packet

Once the ESPINUDP-marked socket receives a packet, udp_queue_rcv_one_skb() routes it through xfrm4_udp_encap_rcv() and into xfrm_input(), which delivers it to esp_input(). The flow runs entirely over loopback.

The packet's skb arrives at esp_input() looking like this:

skb {
    head/linear: ESP_hdr(8) + IV(16) = 24 bytes
    frags[0]:    page=&P, off=i*4, size=16    // page cache page of /usr/bin/su
}

The fast path triggers, the in-place decrypt runs, and a 4-byte write lands at offset i*4 of the page. By cycling i from 0 through 47, the exploit assembles a 192-byte static ELF over the first 192 bytes of the cached /usr/bin/su binary. That static ELF executes setgid(0); setuid(0); execve("/bin/sh", ...) at its entry point.

The setuid bit on /usr/bin/su is intact throughout. The kernel still grants effective 0 when the binary is executed. The shellcode runs as root.

The second half of Dirty Frag is structurally similar to the first, but lives in a different subsystem and has different operational properties. It is the variant that runs on hosts where the xfrm-ESP variant is blocked.

Where the Bug Lives

The relevant function is rxkad_verify_packet_1() in the RxRPC kernel module (rxrpc.ko). RxRPC is the kernel-side implementation of the RPC protocol used by the Andrew File System. The verify function performs an in-place single-block decrypt on the first 8 bytes of the rxrpc payload:

sg_init_table(sg, ARRAY_SIZE(sg));
ret = skb_to_sgvec(skb, sg, sp->offset, 8);
memset(&iv, 0, sizeof(iv));
skcipher_request_set_sync_tfm(req, call->conn->rxkad.cipher);
skcipher_request_set_crypt(req, sg, sg, 8, iv.x);   // src == dst
ret = crypto_skcipher_decrypt(req);

skb_to_sgvec() translates the skb's frag directly into the scatter-gather list. If the frag was planted via splice, the list now points at the attacker's page cache page. The set_crypt call with sg, sg makes the operation in-place. The decrypt runs and writes 8 bytes back to the same page.

The 8-Byte STORE

The cipher in use is pcbc(fcrypt), with the IV set to zero. For a single block, PCBC reduces to a plain fcrypt_decrypt(C, K). fcrypt is an Andrew File System cipher with a 56-bit key and an 8-byte block.

This is where the RxRPC variant differs sharply from the xfrm-ESP variant. With xfrm-ESP, the 4 bytes written are seq_hi, a value the attacker hands to the kernel directly. With RxRPC, the 8 bytes written are fcrypt_decrypt(C, K), where C is the actual ciphertext bytes that already exist at that file offset, and K is the session key the attacker registers in user-space.

The attacker can therefore choose K, but cannot directly choose what gets written. To plant a desired 8-byte plaintext, the attacker has to brute force K in user-space until fcrypt_decrypt(C, K) returns the desired pattern. The cost grows exponentially with the number of constrained plaintext bytes. Constraining all 8 bytes is infeasible (around 2⁵⁶ keys), but constraining only 2 or 3 bytes is fast.

Privilege Requirement

The session key is registered via add_key("rxrpc", desc, payload, ..., KEY_SPEC_PROCESS_KEYRING). Registering an RxRPC key requires no special capabilities. The trigger uses a UDP socket pair and an AF_RXRPC socket, both of which are accessible to unprivileged users. No namespace creation is required.

The constraint that affects this variant is module availability. The RxRPC variant only works if the rxrpc.ko module is loaded. The V4bel writeup specifically calls out RHEL 10.1's default build as not shipping rxrpc.ko, and notes that the module is absent from most distributions in general. On Ubuntu, however, the module is loaded by default, which is precisely where the namespace-blocking AppArmor policy makes the xfrm-ESP variant unreliable. Each variant covers the other's blind spot.

Choosing /etc/passwd Instead of /usr/bin/su

The xfrm-ESP variant overwrites a 192-byte ELF over /usr/bin/su in 48 separate 4-byte writes. The RxRPC variant cannot do this. Each 8-byte write requires its own user-space brute force, and even with fcrypt running at around 18 million keys per second, writing 192 bytes of fully constrained shellcode would take longer than several lifetimes.

The variant targets /etc/passwd instead. The first line of /etc/passwd reads:

root:x:0:0:root:/root:/bin/bash

The exploit overwrites characters 4 through 15 with the pattern ::0:0:GGGGGG: using three overlapping 8-byte writes at offsets 4, 6, and 8. Last-write-wins resolves the overlap. The result is:

root::0:0:GGGGGG:/root:/bin/bash

Note that the password field (between the first two colons) is now empty. PAM's pam_unix.so nullok accepts an empty password and returns PAM_SUCCESS without prompting. A subsequent su - runs without any authentication challenge, drops to UID 0, and execs /bin/bash.

Only 12 bytes of plaintext need to be controlled, of which 5 of them carry the very weak constraint of "anything other than colon, newline, or null". The brute force completes in roughly five milliseconds for the first two writes and around one second for the third.

Why Chaining Is Necessary

Each variant has an environmental constraint that prevents it from working on certain distributions. The xfrm-ESP variant requires user creation, and Ubuntu often blocks that via AppArmor. The RxRPC variant requires rxrpc.ko, and most enterprise distributions do not ship it. Neither variant alone covers Ubuntu and RHEL simultaneously.

Chaining the two variants makes their blind spots cancel out. The exploit attempts the xfrm-ESP variant first. If unshare(CLONE_NEWUSER) returns -EPERM, or if SA registration fails, the exploit falls back to the RxRPC variant. Between the two, every tested distribution has at least one path that works.

The fallback logic in pseudocode:

1. Fork a child process
2. In the child, attempt the xfrm-ESP variant:
     unshare(USER|NET) -> register XFRM SA -> splice -> modify /usr/bin/su
3. Check whether the first byte of the shellcode landed at the entry offset
4. If yes: parent forkpty + execve("/usr/bin/su") -> root shell
5. If no: fall back to RxRPC variant:
     brute force K -> three splice triggers -> /etc/passwd password emptied
     parent forkpty + execve("/usr/bin/su") -> PAM nullok -> root shell

Diagram of the Dirty Frag chain decision logic. An unprivileged user at the top branches into two parallel columns. The left column, labelled xfrm-ESP variant CVE-2026-43284 with a 4-byte STORE, lists requirements: needs CAP_NET_ADMIN, obtained via unshare CLONE_NEWUSER, requires unprivileged user namespaces enabled. Its per-distribution status table shows ok for RHEL 10.1 / AlmaLinux 10, CentOS Stream 10, openSUSE Tumbleweed, and Fedora 44, and blocked for Ubuntu 24.04. The right column, labelled RxRPC variant CVE-2026-43500 with an 8-byte STORE, lists requirements: no namespace privilege required, uses add_key and AF_RXRPC socket, needs rxrpc.ko module loaded. Its status table shows ok for Ubuntu 24.04, Fedora 44, and openSUSE Tumbleweed, blocked for RHEL 10.1, and varies for AlmaLinux and CentOS Stream. The two columns converge at the bottom on a red panel reading root on every major distro, with the caption that the two variants' blind spots are disjoint.

Connecting to the Lab Machine

When the machine is started, the room opens a split-view terminal already logged in as the unprivileged user karen. The steps below should be followed in order in that terminal. To connect from your own machine over SSH instead, use the username karen and the password dirtyfrag2026:

ssh karen@MACHINE_IP

Either approach lands you at the same prompt.

Step 1: Confirm Your Context

Confirm the current user has no elevated privileges:

karen@ubuntu:~$ id
uid=1001(karen) gid=1001(karen) groups=1001(karen)

This is the starting context the exploit requires. No special groups, no sudo entitlements.

Step 2: Inspect the Proof of Concept

The published Dirty Frag PoC is hosted at the V4bel/dirtyfrag (opens in new tab) repository. A pre-built copy lives at /home/karen/dirtyfrag/exp.c on the lab machine. Take a quick look at the structure:

ls /home/karen/dirtyfrag/
head -50 /home/karen/dirtyfrag/exp.c

The exploit is a single C file. It has no build dependencies beyond a working C toolchain and libutil (used for forkpty). It is compiled with the same one-line command shown in the upstream README.

Step 3: Build and Run the Exploit

Build the binary:

cd /home/karen/dirtyfrag
gcc -O0 -Wall -o exp exp.c -lutil

Run it:

./exp

The exploit prints progress as it works through the variant logic. On Ubuntu 24.04 with AppArmor's standard policy, the xfrm-ESP variant fails on the unshare() step and the RxRPC fallback kicks in. The brute-force phase prints the keys as they are recovered, then the three splice triggers fire in sequence. After the modifications complete, the exploit calls forkpty() and runs su -, dropping into a root shell.

karen@ubuntu:~$ ./exp
[+] xfrm-ESP variant: unshare(CLONE_NEWUSER) -> EPERM (AppArmor)
[+] falling back to RxRPC variant
[+] brute forcing K_A for offset 4..11   "::"      ...done in 4.7ms
[+] brute forcing K_B for offset 6..13   "0:"      ...done in 5.1ms
[+] brute forcing K_C for offset 8..15   "0:GGG..."...done in 1.04s
[+] splice trigger A: ok
[+] splice trigger B: ok
[+] splice trigger C: ok
[+] /etc/passwd line 1 modified, calling su
# id
uid=0(root) gid=0(root) groups=0(root)

Step 4: Read the Flag

cat /root/flag.txt

Step 5: Verify the On-Disk Files

While the root shell is still active, check whether either of the files modified in memory was actually written to disk:

sha256sum /etc/passwd /usr/bin/su

Both hashes match the values from a freshly-installed system. The page cache was modified, but the on-disk content was never written. AIDE, Tripwire, and IMA would all report both files as clean.

The exploit cleans up after itself with posix_fadvise(POSIX_FADV_DONTNEED) on the affected pages. After the corrupted process exits, the kernel evicts those pages from the cache, and the next read of /etc/passwd or /usr/bin/su reloads the clean copy from disk.

Step 6: Exit the Root Shell

exit

The exploitation window is short. Once cleanup runs, no on-disk forensic evidence remains, and the page cache no longer reflects the attack. Detection has to happen at the time of exploitation, not afterwards.

Answer the questions below

What is the content of /root/flag.txt?

The Dirty Frag exploit leaves no on-disk forensic trace, just like Copy Fail and Dirty Pipe. What it does leave is a distinctive sequence of system calls that no legitimate application produces. Detection works by monitoring for that sequence in real time. Mitigation works by removing the kernel modules that deliver each variant.

Detection

xfrm-ESP Variant

The xfrm-ESP variant produces an unusual chain of system calls in a single process:

  • unshare(CLONE_NEWUSER | CLONE_NEWNET) followed by writes to /proc/self/uid_map
  • A burst of around 48 XFRM_MSG_NEWSA netlink messages, each registering a different SPI
  • A socket(AF_INET, SOCK_DGRAM) with setsockopt(SOL_UDP, UDP_ENCAP, UDP_ENCAP_ESPINUDP) set to value 1
  • Repeated vmsplice and splice calls feeding /usr/bin/su (or another setuid binary) into the encap socket
  • An eventual execve("/usr/bin/su", ...)

A Falco rule that fires on the splice-from-setuid-binary-to-encap-socket pattern catches this variant before the page cache corruption is complete. Falco does not expose a direct user-namespace flag, so the rule below leans on the splice target plus the binary being spliced as the high-fidelity signal:

- list: setuid_binaries
  items: [/usr/bin/su, /bin/su, /usr/bin/sudo, /usr/bin/passwd, /usr/bin/chsh]

- rule: Potential Dirty Frag xfrm-ESP exploit
  desc: >
    Detects splice() of a setuid binary file descriptor into a network
    socket, the trigger pattern for CVE-2026-43284 (xfrm-ESP variant).
  condition: >
    evt.type = splice and
    fd.name in (setuid_binaries) and
    not proc.name in (kcapi-enc, kcapi-dgst, charon, charon-systemd)
  output: >
    Splice from setuid binary into network socket
    (user=%user.name uid=%user.uid command=%proc.cmdline pid=%proc.pid
     fd=%fd.name container_id=%container.id)
  priority: CRITICAL
  tags: [host, exploit, privilege_escalation, cve_2026_43284]

RxRPC Variant

The RxRPC variant has a different signature. It does not use namespaces, but it does the following:

  • socket(AF_RXRPC, SOCK_DGRAM, ...) (domain value 33)
  • Three or more add_key("rxrpc", ...) calls in quick succession with different descriptions
  • Repeated vmsplice and splice calls feeding /etc/passwd into a UDP socket
  • An eventual execve("/usr/bin/su", ...) followed immediately by a child process running /bin/bash as UID 0

The socket(AF_RXRPC) call is by itself a high-fidelity signal. RxRPC is a narrowly-used protocol, primarily deployed for AFS clients. Most production hosts never legitimately call this socket family. A SIEM rule that alerts on any process opening an AF_RXRPC socket outside a known AFS client process list is a strong starting point:

-a always,exit -F arch=b64 -S socket -F a0=33 -k dirty_frag_rxrpc
-a always,exit -F arch=b64 -S add_key -k dirty_frag_addkey

Key Syscalls to Monitor

Syscall What to Watch For Variant
socket(AF_RXRPC, ...) Any process outside known AFS clients RxRPC
socket(AF_INET, SOCK_DGRAM) with UDP_ENCAP_ESPINUDP Process not in charon/charon-systemd/pluto xfrm-ESP
unshare(CLONE_NEWUSER) followed by ~40 netlink XFRM_MSG_NEWSA High-confidence xfrm-ESP indicator xfrm-ESP
add_key("rxrpc", ...) Three or more in quick succession RxRPC
splice() from setuid binary or /etc/passwd to socket fd Page cache page entering the network stack Both

MITRE ATT&CK Mapping

Technique MITRE ID Primary Signal
Local Privilege Escalation via kernel flaw T1068 splice from sensitive file into encap or rxrpc socket
Setuid binary abuse T1548.001 execve of /usr/bin/su after page cache modification
Indicator Removal via page eviction T1070 posix_fadvise(DONTNEED) on /etc/passwd or /usr/bin/su

Mitigation

The mainline patch for CVE-2026-43284 is commit f4c50a4034e62ab75f1d5cdd191dd5f9c77fdff4. It was merged to the netdev tree on 7 May 2026 and accepted into Linus's mainline tree on 8 May 2026, the same day the NVD record was published. The patch sets SKBFL_SHARED_FRAG on splice'd page fragments in the IPv4 and IPv6 datagram append paths, and updates ESP input to honour the flag. Once the flag is set, attacker-pinned pages are routed through skb_cow_data() before any in-place crypto runs.

CVE-2026-43500 (RxRPC) has no patch in any tree at the time of disclosure. The author submitted a patch to netdev, but it has not been merged or backported.

Until a patched kernel is available from your distribution, the published interim mitigation removes both attack surfaces by disabling the kernel modules that contain them.

Step 1: Apply the Module Denylist

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
sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'

The first command writes a modprobe configuration file that intercepts loads of esp4, esp6, and rxrpc, redirecting them to /bin/false so the modules never load. The second unloads any currently-loaded copies. The third drops the page cache, evicting any pages that may already have been corrupted.

Step 2: Verify the Block Is Active

sudo modprobe esp4
sudo modprobe rxrpc

Both commands should now fail. If either succeeds, check that the configuration file was written correctly and that no other higher-priority modprobe configuration is overriding it.

Step 3: Confirm the PoC Fails

Re-run the exploit:

/home/karen/dirtyfrag/exp

Both the xfrm-ESP and RxRPC paths should fail at the socket() call with EAFNOSUPPORT, because the modules that handle those socket families are no longer loadable.

Warning: This mitigation breaks any legitimate use of IPsec ESP (IPv4 and IPv6) and RxRPC on the host. Production hosts running strongSwan, libreswan, or AFS clients require a more targeted approach, typically a vendor-backported patch. For development, lab, and most server workloads, the modules are unused and the denylist is safe.

Patch Status at Disclosure

The disclosure timeline runs roughly as follows. The xfrm-ESP patch was submitted to netdev on 30 April 2026 and merged into the netdev tree on 7 May 2026, followed by mainline acceptance on 8 May 2026. The RxRPC patch was submitted on 29 April 2026 but has not been merged. The embargo with the linux-distros mailing list was broken by an unrelated third party publishing the exploit on 7 May 2026, prompting the author to release the full Dirty Frag document. NVD published the CVE-2026-43284 record on 8 May 2026.

Distribution patches are now rolling out. Apply the appropriate update as soon as your distribution makes one available.

Dirty Frag is the third vulnerability in a now-recognisable bug class. Dirty Pipe abused the pipe subsystem, Copy Fail abused AF_ALG, and Dirty Frag abuses two unrelated network paths, all to achieve the same end: an unprivileged user writes into the page cache of a file they only have read access to. The on-disk content is never touched, file monitoring is silent, and the corruption persists in memory until the kernel evicts the affected page.

The class is durable because the underlying pattern, in-place crypto on a non-private skb fragment, recurs across many subsystems. Each instance has been independently introduced, often by performance-motivated optimisations that did not consider how splice could plant attacker-pinned pages into a downstream skb. xfrm-ESP was introduced in 2017, RxRPC in 2023, and the Copy Fail equivalent in 2017. Reviewing other in-place crypto sites for the same pattern is an active area of research.

For defenders, the key takeaways are unchanged from Copy Fail. Filesystem monitoring is not sufficient on its own. Detection requires syscall-level visibility, ideally in real time, with rules tuned for the specific socket types and splice targets each variant relies on. Mitigation requires a patched kernel; module denylists are an effective interim but break legitimate functionality where the modules are needed.

The patch for CVE-2026-43284 is upstream. The patch for CVE-2026-43500 is not. Until both are merged and backported, the module denylist is the recommended interim control on hosts that do not depend on IPsec or RxRPC.

Answer the questions below

I can now exploit CVE-2026-43284!