Tech 8 min read

Fragnesia (CVE-2026-46300): Linux page cache LPE survives Dirty Frag mitigations

IkesanContents

TL;DR

What happened Linux kernel XFRM ESP-in-TCP path. CVE-2026-46300, CVSS 7.8 HIGH, local privilege escalation from regular user to root

Fix Update to a patched kernel from your distro. The upstream fix is a 2-line patch to skb_try_coalesce() that preserves SKBFL_SHARED_FRAG across fragment moves

Workaround Same as Dirty Frag: block esp4, esp6, and rxrpc from loading. Check side effects first on VPN gateways that terminate IPsec ESP


Fragnesia is a Linux kernel local privilege escalation disclosed on May 13, 2026 by William Bowling of V12 Security.
The CVE is CVE-2026-46300.
The Hacker News covered it on May 14 as a new variant of the Dirty Frag bug class.

Last month’s Copy Fail entered through AF_ALG and algif_aead.
The follow-up, Dirty Frag, corrupted the page cache through ESP and RxRPC receive paths.
Fragnesia is not a re-announcement of Dirty Frag.
It is a separate bug in the same ESP/XFRM neighborhood, with a public PoC already out.

It does not modify /usr/bin/su on disk; it only dirties the in-RAM page cache copy, same shape as the previous bugs.
File hashes look clean, but execve() reads the modified cache.
That gives local users and processes inside containers a path to host root, which makes the impact heavier than a plain “local bug” on shared servers, CI runners, and Kubernetes nodes.

All that was missing was a flag

The central piece is skb_try_coalesce().
The Linux network stack’s sk_buff can hold packet contents not just in a linear buffer but also as page fragments.
After splice() pushes file-backed pages into the TCP receive queue, if the socket switches to the espintcp ULP mode, those pages flow into ESP’s crypto processing.

Page-cache-backed frags need a marker that says “this is a shared external page, do not write to it in place.”
That marker is SKBFL_SHARED_FRAG.
According to the patch posted to netdev, skb_try_coalesce() did not propagate this flag when moving page fragments between skbs.

The ESP input side checks skb_has_shared_frag() to decide whether to fall back to copy-on-write or do in-place decryption.
When the flag goes missing, page-cache-backed pages look like ordinary skb fragments.
Result: ESP runs AES-GCM decryption directly on top of the page cache.

flowchart TD
    A["Readable file"] --> B["splice into TCP receive queue"]
    B --> C["skb_try_coalesce moves the fragment"]
    C --> D["SKBFL_SHARED_FRAG is dropped"]
    D --> E["espintcp ULP runs ESP processing"]
    E --> F["No copy-on-write,<br/>XOR onto the page cache"]
    F --> G["In-memory copy of /usr/bin/su<br/>rewritten with a small ELF stub"]

V12’s write-up uses the AES-GCM keystream to coerce target bytes one at a time, byte by byte, toward a chosen value.
By building a mapping of 256 possible keystream bytes to nonces, the attacker writes a tiny ELF stub into the first 192 bytes of /usr/bin/su that calls setresuid(0,0,0) and execs /bin/sh.
Because this is a page cache modification, the on-disk binary is untouched.

After the PoC runs, as long as the same page cache stays warm, executing su runs the injected stub.
V12 recommends running drop_caches or rebooting after testing to clean up.
Testing “are we vulnerable?” against the live system means dirtying your own setuid binary’s cache.

The same Dirty Frag workaround stops it

Fragnesia is a different bug from Dirty Frag, but the entry point is on the same ESP/XFRM surface.
V12, CloudLinux, and Ubuntu all point to the same workaround as Dirty Frag.
Block esp4, esp6, and rxrpc from loading, and unload them if already loaded.

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 || true

If you already applied this for Dirty Frag, you do not need a separate file for Fragnesia.
CloudLinux notes that customers who already applied the same mitigation need no extra steps until the patched kernel lands.

The side effects are the same as Dirty Frag too.
esp4 and esp6 are the kernel-side IPsec ESP handlers, so hosts that terminate IPsec VPNs with strongSwan or Libreswan will break.
This blog has its own IKEv2 (strongSwan) setup notes; on that kind of host, check tunnel mode and impact before applying the workaround.
Many ordinary web servers and CI runners do not use these modules at all.

AWS published a slightly different judgment.
Amazon Linux does not ship the loadable espintcp module the PoC depends on, so its advisory says Amazon Linux is not affected.
Even so, AWS plans to land a correctness patch into core networking code to harden similar protocol implementations.
”Linux is Linux” is not enough here; what modules a distribution ships changes the verdict.

Things to check while waiting for the patch

The fix patches net/core/skbuff.c with two added lines.
When skb_try_coalesce() moves a fragment, it now carries the source’s SKBFL_SHARED_FRAG over to the destination.
That keeps the ESP input side from missing shared frags and forces copy-on-write instead of in-place decryption.

The actual change posted to netdev is just this much.

// net/core/skbuff.c, in skb_try_coalesce()
        skb_shinfo(to)->nr_frags = to_shinfo->nr_frags + from_shinfo->nr_frags;
+       if (from_shinfo->flags & SKBFL_SHARED_FRAG)
+               to_shinfo->flags |= SKBFL_SHARED_FRAG;
        if (skb_cloned(to))
                return false;

Even when a splice()-derived page moves into a merged-destination skb, the shared-frag marker is reattached.
The ESP input path’s skb_has_shared_frag() correctly returns true, and decryption happens into a freshly allocated buffer.
It is a flag-propagation fix, not a logic change.

AlmaLinux pushed a patched kernel into its testing repository on May 13, 2026, ahead of Red Hat’s update.
Ubuntu’s CVE page marks each release as “Needs evaluation” as of the same day, and references the same Dirty Frag workaround.
CloudLinux says kernel updates and KernelCare livepatches are being built and tested across the supported streams.

uname -r alone is not a safety check.
Distributions backport fixes without bumping the upstream version number.
What to look at: the advisory for your kernel package, the fixed version for CVE-2026-46300, and whether esp4, esp6, rxrpc, and espintcp are loaded.

uname -r
lsmod | grep -E '^(esp4|esp6|rxrpc|espintcp)\b'
modprobe -n -v esp4
modprobe -n -v esp6
modprobe -n -v rxrpc

As noted for Dirty Frag, page cache corruption goes away on reboot, but anything an attacker did after getting root is a separate problem.
On suspect hosts, kernel update, module restriction, user namespace settings, and CI/container execution history are higher-priority checks than reproducing with the PoC.

On multi-tenant nodes, “one node done” is not enough

Containers share the kernel with the host.
For a kernel-entry bug like Fragnesia, the impact radius is decided by whether a regular user inside a container can write to the host’s page cache.
Every container sitting on a shared node points at the same root.

Configurations that need extra care.

  • Kubernetes nodes with user namespaces disabled: When userns is off, container UID 0 means the same thing as host UID 0. A PoC (proof-of-concept exploit) that grabs root grabs the whole node
  • Multi-tenant CI runners: Setups that execute arbitrary code per PR (GitHub Actions self-hosted runners, GitLab shared runners, etc.) let an attacker walk in as a normal user
  • Shared development servers: University or corporate boxes with per-developer non-root accounts
  • eBPF and PaaS nodes: Anything that runs customer code in close proximity

At the node level, check whether esp4, esp6, and rxrpc blocking has reached every node.
On Kubernetes, ship /etc/modprobe.d/ via a DaemonSet, bake it into the image, or roll it out with Ansible.
”I did one node” leaves an opening where the next CI job runs on a different node.

# Check across nodes
for node in $(kubectl get nodes -o name); do
  kubectl debug $node -it --image=busybox -- sh -c \
    'lsmod | grep -E "^(esp4|esp6|rxrpc|espintcp)\b"; cat /etc/modprobe.d/*.conf 2>/dev/null | grep -E "esp4|esp6|rxrpc"'
done

Because the PoC runs from local user privilege, the realistic attack surface is containers and CI, not SSH-borne intrusion.
Until the kernel update lands, check accounts and module state at both the node entry and the job entry.

References