# CVE-2026-43284: Dirty Frag (TryHackMe)

Link to the Walkthrough on TryHackMe: [**CVE-2026-43284: Dirty Frag**](https://tryhackme.com/room/cve202643284)

## Introduction

Dirty Frag is a vulnerability class that chains two separate Linux 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 Write** - `CVE-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)](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=f4c50a4034e62ab75f1d5cdd191dd5f9c77fdff4).
    
*   **RxRPC Page-Cache Write** - `CVE-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](https://tryhackme.com/room/cve202631341), 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)](https://github.com/V4bel/dirtyfrag) 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](https://tryhackme.com/module/linux-fundamentals) and [Linux Privilege Escalation](https://tryhackme.com/room/linprivesc). Familiarity with the [Copy Fail room](https://tryhackme.com/room/cve202631341) is strongly recommended; Dirty Frag builds directly on the page cache write primitive introduced there.

## The Shared Primitive

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 TCP 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](https://tryhackme-images.s3.amazonaws.com/user-uploads/645b19f5d5848d004ab9c9e2/room-content/645b19f5d5848d004ab9c9e2-1778274526407.svg align="center")

## xfrm-ESP Page-Cache Write (CVE-2026-43284)

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:

```shell
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:

```shell
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 namespace 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:

```c
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 UID 0 when the binary is executed. The shellcode runs as root.

## RxRPC Page-Cache Write (CVE-2026-43500)

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:

```shell
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:

```shell
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:

```shell
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.

## Chaining and Running the Exploit

Why Chaining Is Necessary Each variant has an environmental constraint that prevents it from working on certain distributions. The xfrm-ESP variant requires user namespace 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
    

![](https://cdn.hashnode.com/uploads/covers/5f4a98085ee1ba597542e097/c6c8ecae-c25a-435e-986b-85e2701d7dff.png align="center")

```bash
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:

```bash
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)](https://github.com/V4bel/dirtyfrag) repository. A pre-built copy lives at `/home/karen/dirtyfrag/exp.c` on the lab machine. Take a quick look at the structure:

```bash
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:

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

Run it:

```bash
./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.

```bash
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**

```bash
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:

```bash
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**

```bash
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?

```shell
gcc -O0 -Wall -o exp exp.c -lutil
karen@dirtyfrag:~/dirtyfrag$ ./exp
root@dirtyfrag:~# id
uid=0(root) gid=0(root) groups=0(root)
root@dirtyfrag:~# cat /root/flag.txt
THM{dirty_frag_skb_corruption}
```

## Detection and Mitigation

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:

```shell
- 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:

```shell
-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
```

![](https://cdn.hashnode.com/uploads/covers/5f4a98085ee1ba597542e097/447bea7f-ede7-4fde-8e6b-b6fac966a3e2.png align="center")

## **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**

```bash
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**

```bash
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:

```bash
/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.

## Conclusion

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 integrity 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 integrity 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.
