Dirty Frag, Fragnesia, Copy Fail: The Page-Cache Vulnerability Class
Between April 29 and May 13, 2026, three separate CVEs demonstrated a shared structural weakness in the Linux kernel: network protocol handlers that decrypt or transform packet data in place can write directly into the page cache. No race conditions. No kernel panics. Deterministic local privilege escalation on every kernel since 2017. Most distributions remain unpatched.
The vulnerability class spans four CVEs across two attack surfaces. Copy Fail (CVE-2026-31431) wrote four bytes into page-cache pages through the AEAD crypto API. Dirty Frag (CVE-2026-43284, CVE-2026-43500) chained two network protocol handlers — xfrm-ESP and RxRPC — to write arbitrary data into cached files. Fragnesia (CVE-2026-46300) reopened the same xfrm-ESP path through a logic bug in TCP receive coalescing that the Dirty Frag fix itself introduced.
Each CVE is a distinct root cause. Together they form a pattern: the kernel trusts that socket buffer fragment metadata reflects reality, and every code path that silently drops the SKBFL_SHARED_FRAG flag creates a window where in-place decryption overwrites pages shared with the filesystem. The fix is small — a two-line patch. The impact is not.
The Vulnerability Class: Page-Cache Corruption via Network Protocol Abuse
Dirty Pipe (CVE-2022-0847) established that overwriting page-cache pages yields root. The 2026 CVEs operate through a different primitive: instead of stale pipe buffer flags, they exploit the kernel networking stack itself. When a network protocol handler — ESP decryption, RxRPC decryption, or AEAD encryption — operates on socket buffer fragments that are backed by page-cache pages, the transformation writes directly into the page cache.
The shared structure is the sk_buff fragment list. Each fragment points to a page. When a file is spliced into a socket via splice(), the fragment points to the same physical page that the VFS page cache holds. If the kernel then decrypts or transforms that fragment in place, the operation modifies the cached copy of the file on disk.
"skb_try_coalesce() can attach paged frags from @from to @to. If @from has SKBFL_SHARED_FRAG set, the resulting @to skb can contain the same externally-owned or page-cache-backed frags, but the shared-frag marker is currently lost. That breaks the invariant relied on by later in-place writers." — netdev mailing list patch
The result: any unprivileged local user can modify any file readable by their UID in the page cache. Modify /usr/bin/su, call setresuid(0,0,0), get a root shell.
The Four CVEs: How Each One Works
Copy Fail — CVE-2026-31431
Discovered by Theori Research and disclosed April 29. The crypto_authenc_esn_decrypt() function performs a four-byte store during internal byte rearrangement on the algif_aead transmit scatter-gather list. When that SGL contains page-cache-backed fragments, the four bytes land in the cached file. No special privileges required — the algif_aead interface is accessible to any unprivileged user via AF_ALG sockets.
Dirty Frag — CVE-2026-43284 (xfrm-ESP) and CVE-2026-43500 (RxRPC)
Discovered by Hyunwoo Kim and disclosed May 7. Two network protocol paths that perform in-place decryption on socket buffer fragments without checking whether those fragments are shared with the page cache.
The xfrm-ESP path (CVE-2026-43284): esp_input bypasses skb_cow_data() and runs crypto_authenc_esn_decrypt() directly on the fragment. Requires a user/network namespace for CAP_NET_ADMIN. On distributions that allow unprivileged user namespaces — RHEL, Fedora, openSUSE, AlmaLinux — exploitation is straightforward.
"On distributions that allow unprivileged user namespaces (RHEL, Fedora, openSUSE, AlmaLinux), [ESP] is easily achieved. However, on systems like Ubuntu, AppArmor sometimes blocks unprivileged user-namespace creation. This is exactly why the second CVE in the chain exists." — Qualys
The RxRPC path (CVE-2026-43500): rxkad_verify_packet_1() performs in-place single-block decryption with pcbc(fcrypt) directly on the fragment. No namespace creation needed — only unprivileged APIs: add_key("rxrpc", ...), socket(AF_RXRPC), socket(AF_ALG), splice(), recvmsg(). This is the bypass for distributions that restrict user namespaces.
Fragnesia — CVE-2026-46300
Discovered by William Bowling (V12 Security / Zellic) and disclosed May 13. The Dirty Frag fix added a check: esp_input now calls skb_has_shared_frag() before deciding whether to skip skb_cow_data(). If shared frags are present, it copies instead of decrypting in place. But skb_try_coalesce() — the TCP receive coalescing function — silently drops the SKBFL_SHARED_FRAG flag when moving fragment descriptors between socket buffers.
After TCP coalescing, the flag is gone. ESP sees no shared fragments. It decrypts in place. The page cache is corrupted.
"This vulnerability is a path that was accidentally activated after the introduction of f4c50a4034e6 (2026-05-05), the patch for CVE-2026-43284 in the Dirty Frag chain. In other words, the effective vulnerability window is from f4c50a4034e6 (2026-05-05) to upstream — approximately 9 days." — Hyunwoo Kim, oss-sec
The irony: the patch that fixed Dirty Frag created the code path that Fragnesia exploits. The V3 fix — propagating SKBFL_SHARED_FRAG through all five code paths that drop it — is a two-line addition per path. But as of May 15, it has not landed in any stable kernel release.
Five Code Paths That Drop The Flag
CIQ kernel engineers identified five separate locations where the kernel silently drops the SKBFL_SHARED_FRAG flag. Each one is an independent attack surface.
| Path | Function | Source File | Discovered By |
|---|---|---|---|
| 1 | skb_try_coalesce() |
net/core/skbuff.c |
William Bowling (V12) |
| 2 | __pskb_copy_fclone() |
net/core/skbuff.c |
Hyunwoo Kim (v2 patch) |
| 3 | skb_gro_receive() |
GRO subsystem | Sultan Alsawaf (CIQ) |
| 4–5 | Additional GRO variants | GRO subsystem | Hyunwoo Kim (v3 patch) |
The original Fragnesia PoC used path 1 (TCP coalescing). The V3 upstream patch covers all five. Until it merges, each unresolved path is exploitable.
The Exploit Chain: Byte-by-Byte Root
The Fragnesia exploit builds a precise write primitive from the AES-GCM keystream. Each invocation of the exploit chain writes exactly one byte at a chosen offset in a target file. The attacker selects which byte value to write by choosing the IV nonce that produces the desired keystream byte.
- Namespace setup —
unshare(CLONE_NEWUSER | CLONE_NEWNET)to gainCAP_NET_ADMINin a new network namespace. - Install xfrm SA — Transport-mode ESP-in-TCP security association via
NETLINK_XFRM, AES-128-GCM, chosen SPI. - Build keystream table — Encrypt 256 counter blocks under the known key. Each yields a different keystream byte at offset zero. This is the lookup table for arbitrary byte writes.
- Splice-then-ULP trigger — Sender splices 4096 bytes from the target file into a TCP stream with an ESP header carrying the chosen IV. Receiver delays
TCP_ULP espintcpuntil data sits in the socket buffer. Kernel decrypts the queued ESP record in place, XORing the keystream byte into the mapped file page — the same physical page held by the VFS page cache. - Repeat — Each invocation modifies one byte. Overwriting 192 bytes in
/usr/bin/suinserts asetresuid(0,0,0)/setresgid(0,0,0)sequence. - Execute —
execve("/usr/bin/su")runs the in-memory modified binary. Root shell acquired.
"By selecting the IV nonce to produce a desired keystream byte, any target byte in the file can be set to any value — one byte per trigger invocation." — V12 Security PoC README
No race conditions. No kernel panics. No crashes. The write is deterministic because the page-cache page and the socket buffer fragment point to the same physical memory. One triggers the exploit, one reaps the root.
Container Escape: Shared Kernels, Shared Risk
The page cache is shared across every container on a host. A process in one container that corrupts a page-cache page modifies what every other container (and the host) reads from that file. This is the container-escape mechanism: corrupt a setuid binary in the page cache, execute it from the same or another container, gain host root.
Ubuntu/Canonical stated explicitly: "In container deployments that may execute arbitrary third-party workloads, the vulnerability may additionally facilitate container escape scenarios."
Juliet Security tested the exploit chain across major Kubernetes distributions with three seccomp profiles:
| Environment | Seccomp Unset | RuntimeDefault | PSS Restricted |
|---|---|---|---|
| EKS (Dirty Frag xfrm) | Exploitable | Blocked | Blocked |
| EKS (Fragnesia espintcp) | Blocked | Blocked | Blocked |
| GKE (Dirty Frag xfrm) | Exploitable | Blocked | Blocked |
| Talos (either) | Blocked | Blocked | Blocked |
EKS AL2023 blocks Fragnesia even with unconfined seccomp because the host kernel lacks the CONFIG_INET_ESPINTCP module. But the original Dirty Frag xfrm path remains exploitable when seccomp is unset or unconfined. Talos blocks everything at the node level: user.max_user_namespaces=0 prevents namespace creation, and CONFIG_INET_ESPINTCP is unset.
"Do not translate this into 'Fragnesia does not matter in Kubernetes.' Translate it into a checklist." — Juliet Security
Patch Status: Most Distributions Unfixed
As of May 15, 2026, the V3 upstream patch covering all five code paths has been submitted to the netdev mailing list but has not merged into Linus's tree or any stable kernel release.
| Distribution | Fragnesia Status | Dirty Frag Status |
|---|---|---|
| Debian (all releases) | Unfixed | Unfixed |
| Ubuntu (all releases) | "Needs evaluation" | Pending |
| RHEL | RHSA-2026:16314 (validating) | RHSB-2026-003 |
| Fedora 44 | Fixed (kernel 7.0.6) | Fixed |
| AlmaLinux | Patched kernels in testing | Patched |
| CloudLinux | KernelCare live patches | Live-patched |
| Amazon Linux 2023 | Not affected (no espintcp) | Pending |
Debian security tracker lists Fragnesia as unfixed across bullseye (5.10), bookworm (6.1), trixie (6.12), and forky/sid (7.0). Ubuntu marks all releases as "Needs evaluation." Red Hat released RHSA-2026:16314 but states "existing mitigations are believed to apply while validation continues."
Immediate Mitigation: Block the Protocol Modules
The exploit chain requires three kernel modules: esp4, esp6, and rxrpc. Blocking these modules prevents all four CVEs in the class. The mitigation does not require a reboot — it takes effect when the modules are unloaded and the page cache is flushed.
printf 'install esp4 /bin/false\ninstall esp6 /bin/false\ninstall rxrpc /bin/false\n' > /etc/modprobe.d/dirtyfrag.conf
rmmod esp4 esp6 rxrpc 2>/dev/null
echo 3 > /proc/sys/vm/drop_caches
Impact of blocking these modules: IPsec VPN tunnels using ESP-in-transport mode and RxRPC-based services (AFS filesystem) will break. Organizations running IPsec VPNs must weigh the risk of an unpatched privilege-escalation vulnerability against the temporary loss of ESP transport. ESP-in-UDP (NAT traversal) and ESP-in-TCP tunnel modes that do not use the espintcp ULP path appear unaffected. Test before deploying to production.
For Kubernetes clusters: enforce RuntimeDefault or PSS Restricted seccomp profiles on all pods. This blocks the namespace creation step required by the xfrm-ESP exploit path. Audit for pods running with Unconfined or unset seccomp — these are exploitable on EKS and GKE for the Dirty Frag xfrm path.
The Pattern: When Fixes Introduce New Attack Surfaces
Fragnesia is not an isolated incident. The Dirty Frag fix (commit f4c50a4034e6) added the skb_has_shared_frag() check to esp_input. That check depends on SKBFL_SHARED_FRAG being set correctly. When skb_try_coalesce() drops the flag, the fix itself becomes the gateway: ESP now has a fast-path that skips skb_cow_data() — a fast-path that did not exist before the Dirty Frag patch was applied.
The pattern is structural. The kernel networking stack has multiple code paths that merge, copy, or reorganize socket buffer fragments. Each one must correctly propagate the shared-frag flag. When one path is fixed, the remaining unfixed paths become the new attack surface. The V3 patch covers all five identified paths. Whether a sixth exists depends on how thoroughly the kernel community audits every function that touches skb_shinfo(skb)->flags.
This is the same structural pattern as Dirty Pipe: the kernel assumes a flag accurately reflects a property of a shared data structure. When the flag is stale or lost, the kernel acts on wrong assumptions. Page-cache corruption follows. The fix for each individual instance is small. The audit surface — every code path that touches socket buffer flags — is large and still being mapped.
Actionable Takeaways
- Apply the modprobe mitigation immediately on all Linux hosts that do not require ESP transport or RxRPC. This blocks all four CVEs in the class without a kernel upgrade.
- Enforce RuntimeDefault or PSS Restricted seccomp on every Kubernetes pod. Audit for
Unconfinedor unset seccomp — these are exploitable for the Dirty Frag xfrm path on EKS and GKE. - Flush the page cache after mitigation (
echo 3 > /proc/sys/vm/drop_caches). Corrupted pages persist until evicted. - Monitor for the upstream V3 patch at the netdev mailing list. Once merged, rebuild or update kernels promptly — the fix closes all five flag-dropping paths.
- Test module blocking before production deployment. IPsec VPNs, AFS filesystems, and any service using RxRPC will be affected.
- Track all four CVEs independently in vulnerability management: CVE-2026-31431 (Copy Fail), CVE-2026-43284 (Dirty Frag xfrm-ESP), CVE-2026-43500 (Dirty Frag RxRPC), CVE-2026-46300 (Fragnesia). Patch one does not mean patched for all.