
A report on Dirty Frag, which is a Linux Local Privilege Escalation (LPE) vulnerability chain that allows an unprivileged user to gain root access
Exploit Reproduction and Detection Lab for a Linux kernel local privilege escalation chain.
Status: VERIFIED. I completed the reproduction, the fileless verification, and the syscall-level detection in the lab (kernel 6.18.9+kali-amd64). This document is a lab log. Every claim below was observed during the reproduction run. The screenshots and artifacts are real captures from the VM.
Dirty Frag combines two deterministic logic bugs in the Linux kernel. These bugs allow an unprivileged local user to overwrite the page cache of read-only files (for example, /usr/bin/su) and obtain a root shell:
| Variant | CVE | Sink | Trigger path | Needs unprivileged userns |
|---|---|---|---|---|
| xfrm‑ESP Page‑Cache Write | CVE‑2026‑43284 | crypto_authenc_esn_decrypt() in esp_input() | socket(AF_INET) with UDP‑encap, then xfrm_input() | Yes (CAP_NET_ADMIN) |
| RxRPC Page‑Cache Write | CVE‑2026‑43500 | rxkad_verify_packet_1() (pcbc(fcrypt)) | socket(AF_RXRPC) | No |
Both variants use the same root pattern as Dirty Pipe and Copy Fail. The splice(2) syscall places a reference to a page-cache page of a file into the frag slot of a sender-side sk_buff. The attacker can only read this file. Receive-side kernel code then performs an in-place crypto STORE on top of that frag. This mutates the page cache in RAM. No disk write occurs, so file integrity monitoring (AIDE, Tripwire) cannot see it. The attack is deterministic. It has no race window and no kernel panic on failure.
cac2661c53f3 (2017‑01) to f4c50a4034e6 (patched 2026‑05‑05)2dc334f1a63a (2023‑06) to aa54b1d27fe0 (patched 2026‑05‑10)CVE-2026-43284 = 8.8 (High), CVE-2026-43500 = 7.8 (High)Dirty Frag is a fileless LPE. It corrupts the in-memory page cache, not the file on disk. Traditional file integrity monitoring cannot see it. Detection must happen at the syscall layer. The chain uses these syscall primitives: socket(AF_ALG)/socket(AF_RXRPC), splice, and unshare(CLONE_NEWUSER|CLONE_NEWNET). The ESP path also creates AF_INET UDP and netlink sockets. This layer is the focus of the detection engineering in this repo.
Both variants use the same sink: in-place crypto that STOREs bytes onto a page-cache page the attacker places with splice(2).
UDP_ENCAP_ESPINUDP.vmsplice, then 16 bytes from /usr/bin/su at the target file offset with splice.splice pushes the pipe into the send socket. splice_to_socket() sets MSG_SPLICE_PAGES. This places the page-cache page of /usr/bin/su directly into skb->frags[0].xfrm4_udp_encap_rcv, then xfrm_input, then esp_input(). The vulnerable skip_cow branch (!skb_cloned() && !skb_has_frag_list()) bypasses skb_cow_data(). It performs in-place AEAD decryption with the page-cache page as both source and destination.crypto_authenc_esn_decrypt() emits a STORE of the high-order 32 bits of the ESN. That value is replay_esn->seq_hi. The attacker chooses this value at SA registration with the XFRMA_REPLAY_ESN_VAL netlink attribute.The attacker controls both the location (splice offset) and the value (4 bytes). Authentication verification runs after the store, so the crypto layer never flags the write. This variant requires CAP_NET_ADMIN and uses unshare(CLONE_NEWUSER|CLONE_NEWNET).
rxkad_verify_packet_1() performs a single-block pcbc(fcrypt) decrypt directly on the splice-pinned skb frag. It does not copy the data first. The attacker picks a session key (add_key("rxrpc", …)) so that decrypt(ciphertext) equals desired_plaintext. This produces an 8-byte STORE. This variant targets /etc/passwd. It needs no user namespace. It requires the rxrpc.ko module (loaded by default on Ubuntu).
The public PoC targets /usr/bin/su. It writes 48 ESP stores of 4 bytes each (192 bytes at file offset 0). It replaces the first page-cache bytes with a static root-shell ELF. The ELF entry point runs setgid(0); setuid(0); setgroups(0,NULL); execve("/bin/sh", …). A single execve("/usr/bin/su") then yields a root shell.
The ESP patch (mainline f4c50a4034e6) marks page frags that arrive through splice() with the SKBFL_SHARED_FRAG flag. The skip_cow branch in esp_input() now also checks this flag. Shared-frag skbs go through skb_cow_data() before the in-place AEAD decryption.
The RxRPC patch (mainline aa54b1d27fe0) adds an skb->data_len check next to the existing skb_cloned() check. The kernel copies a non-linear skb with paged data before the in-place pcbc(fcrypt) decryption.
| Component | Details |
|---|---|
| Hypervisor | VirtualBox |
| Target VM | Kali Linux 2026.1 (snapshot restored to a vulnerable state) |
| Kernel | 6.18.9+kali‑amd64 (older than the May 2026 fixes) |
| Exploit PoC | V4bel/dirtyfrag (single C file) |
| Detection | auditd (rules in detection/dirtyfrag.rules) |
Screenshot of the VirtualBox lab setup:

.
├── README.md # this lab log
├── detection/
│ ├── dirtyfrag.rules # auditd syscall‑level detection rules
│ ├── ausearch_dirtyfrag_observed.txt # real exploit detection output
│ ├── sigma/
│ │ └── dirty_frag_exploit.yml # Sigma rule for SIEM detection
│ └── yara/
│ └── dirty_frag_exploit.yar # YARA rule for PoC code on disk/memory
├── mitigation/
│ └── dirtyfrag_mitigation.sh # module blacklist + page cache flush
├── poc/
│ └── check_vulnerable.py # non‑destructive pre‑flight checker
├── reports/
│ └── incident-dirtyfrag.md # incident response playbook
└── screenshots/ # real captures from the lab VM
poc/check_vulnerable.py and confirm kernel/modules/usernstestuser/usr/bin/su hashesdrop_caches or reboot)detection/dirtyfrag.rules and validate auditd alertsreports/incident-dirtyfrag.md)