Kai Ole Hartwig
23 min read
By

Linux kernel CVE-2026-46300 (Fragnesia): A lost flag lets IPsec write into the page cache

When the Linux kernel decrypts an IPsec packet, it prefers to do so in place, without copying the data first. That is only safe as long as the memory pages belong to the packet alone, and a single bit called SKBFL_SHARED_FRAG is meant to signal exactly that. With Fragnesia (CVE-2026-46300), this bit got lost whenever the TCP stack merged two buffers. Decryption then wrote straight into the page cache of files the attacker was only allowed to read, such as the cached copy of /usr/bin/su. Since 28 October the flaw has been on the CISA KEV list, because attackers chained it with PHP2Shell to gain root on reachable servers.

TL;DR — 90 seconds

How a single bit prevents the copy

The Linux network stack manages packets in structures called sk_buff. The payload often does not sit in one contiguous buffer but in a list of fragments that point to individual memory pages. That saves copies. When a program sends a file over a socket with splice(), the kernel attaches the page cache pages directly to the packet as fragments. Those pages still belong to the file, though. So that no later processing step forgets this, TCP sets the SKBFL_SHARED_FRAG flag after splicing, according to the commit message of the Dirty Frag fix. It tells anyone who wants to modify the data that they need a private copy first.

On the other side sits IPsec. ESP decrypts incoming packets, and to save copies, esp_input() has a fast path: if the packet is not cloned and has no frag_list, ESP decrypts directly in the existing fragments instead of creating a private copy with skb_cow_data(). ESP-in-TCP as defined in RFC 8229 additionally wraps ESP packets in a TCP stream so that IPsec gets through networks that only let TCP pass. In the kernel this is handled by an upper layer protocol called espintcp, which a socket can switch on after the fact.

In May, Dirty Frag (CVE-2026-43284) showed that this fast path also hit fragments made from spliced pipe pages. The fix f4c50a4034e6 therefore set the flag in the UDP paths as well and made ESP fall back to skb_cow_data() whenever skb_has_shared_frag() returns true. From then on, safety rested on a single assumption: the flag is set wherever foreign pages sit in a packet.

That is exactly where the bug was. skb_try_coalesce() merges consecutive buffers in the TCP receive path by appending the fragment descriptors of one to the other. It did not carry over the marker from the source's flags field. A packet with foreign pages thus turned into one that looked like an ordinary private packet, and ESP took the fast path again. William Bowling of V12 Security published the bug on 13 May together with an exploit and a patch, without a prior embargo. The patch's Fixes tag explicitly points to the Dirty Frag fix. Fragnesia is exactly the spot where that fix did not take hold.

Why this leads to root comes down to cryptography. AES-GCM decrypts by XORing the data with a keystream. Whoever sets the key of the security association knows that stream and can use it to write chosen bytes into the foreign page. According to V12 and Tenable, the public exploit targets the cached copy of /usr/bin/su. The file on disk stays unchanged, but every process that starts su gets the manipulated version from memory until the page is evicted or the machine reboots. No race is needed, and the write can be repeated at will.

That leaves the question of why an ordinary user may create an ESP security association at all. This happens over netlink with the NETLINK_XFRM protocol, and the kernel requires CAP_NET_ADMIN in the relevant network namespace. An unprivileged process gets that capability by creating its own user namespace and a network namespace inside it. That is intentional design, because user namespaces are meant to enable containers without root. The price is that every local user can reach large parts of the network stack that used to be reachable only by root. Fragnesia is one more example in a long series of such flaws.

What the fix commit changes

The fix f84eca581739 ("net: skbuff: preserve shared-frag marker during coalescing") consists of two lines in net/core/skbuff.c. After skb_try_coalesce() has appended the source's fragments to the destination, the function now copies the SKBFL_SHARED_FRAG bit from the source's flags, provided it actually transferred fragments. The commit leaves the function's second path untouched, the one that copies data into free tailroom of the destination. There, bytes are copied and no references to foreign pages are passed on. Eric Dumazet reviewed the patch, and the Fixes tags point to a commit from 2013 and to the Dirty Frag fix.

More revealing than the patch itself is what followed two days later. The commit 48f6a5356a33 ("net: skbuff: propagate shared-frag marker through frag-transfer helpers"), now tracked as CVE-2026-43503, sets the bit in __pskb_copy_fclone() and skb_shift() as well and covers further helpers such as skb_gro_receive(), tcp_clone_payload() and skb_segment(). This reveals the actual root cause. The guarantee that foreign pages are copied before writing does not live in one place. It lives in a bit that every function moving fragments between buffers has to pass on correctly. One forgotten spot is enough. What I take from this: as long as this invariant stays spread across so many functions, more flaws of the same class are to be expected, and hardening should assume exactly that.

From web shell user to root on the host

For Fragnesia, an attacker first has to run their own code on the system, and the privileges of that code do not matter. Add a kernel without the May fix, available ESP-in-TCP support and permission to create user namespaces. On most distributions that is the default: according to Tenable, unprivileged user namespaces are usually enabled, and the kernel loads the ESP modules on demand by itself. Amazon Linux and Bottlerocket are the exception. According to AWS bulletin 2026-029, they do not ship the espintcp module at all and are therefore not affected.

The whole sequence stays local. In its own namespaces the attacker obtains the required capability, sets up an ESP security association with a key of their choosing and feeds pages of a readable file through a TCP connection into the ESP receive path. Decryption then overwrites bytes of that file in the page cache. When someone later starts the modified setuid program, the injected code runs as root. Nothing crashes, and no trace is left on disk.

That this did not stay theoretical is shown by a Dream Research report from 26 October, which the authors updated on 29 October. According to it, an attacker combined a custom-built PHP2Shell exploit with Fragnesia and gained root on 25 servers. 24 of them were PAN-OS firewalls from Palo Alto Networks, the twenty-fifth an internal management server that the attacker reached through a tunnel from a firewall already taken over. The targets were in the US, Japan, Taiwan and South Korea, including an environment affiliated with the US government. Dream does not name a specific actor but sees several indicators of a China-nexus operation, and Unit 42 tracks the activity as CL-UNK-1280.

One detail is telling for hardening. The attacker initially ran as the unprivileged user nobody, additionally confined by SELinux, and still reached root through the kernel flaw. According to Dream, the attacker changed the PAM configuration of su with a rule for pam_permit.so, which waves every login through. Palo Alto Networks warned on 29 October about active exploitation of PHP2Shell against reachable PAN-OS management interfaces. CISA had already added the kernel flaw to the KEV catalog on 28 October and explicitly pointed to the chaining with CVE-2026-109618 and CVE-2026-109619.

For TYPO3 and PHP operators, this chain is the interesting part. PHP2Shell delivers code execution as the PHP user, which in the Kubernetes case means inside a container. Fragnesia turns that into root on the node, because all containers share the host kernel. If you have not yet moved PHP to the October releases and also run nodes with an unpatched kernel, both links of the chain sit in your own cluster.

Truly affected are therefore hosts and nodes with kernels from Debian, RHEL derivatives or custom builds that have not received a kernel update with reboot since May. You can stand down, on the other hand, for EKS nodes on Amazon Linux 2023 or Bottlerocket. A third group feels safe when it should not: if you only applied the kernel patch against Dirty Frag in May, you are not protected against Fragnesia, according to Tenable. If you also blocked the ESP modules back then, you are.

CVEAffectedFixPreconditionCVSS
CVE-2026-46300 (Fragnesia)Linux kernel before the May 2026 stable releases, ESP-in-TCP path via skb_try_coalesce()7.0.10, 6.18.33, 6.12.91, 6.6.141, 6.1.174, 5.15.208, 5.10.257Local user, user namespaces, ESP and espintcp available7.8 (high)
CVE-2026-43503 (follow-up fix)Further fragment helpers such as __pskb_copy_fclone() and skb_shift()Debian 5.10.257-1, 6.1.174-1, 6.12.90-1; Amazon Linux via the ALAS of 15 MayLocal user, higher attack complexity7.0 (high)

The public exploit has been available from V12 on GitHub since 13 May, and Tenable reproduced it on a stock kernel. Since 28 October the flaw has been in the KEV catalog with a deadline of 31 October for US federal agencies. On 1 November, EPSS put the probability of exploitation in the next 30 days at about 16 percent, after the value had first crossed 10 percent on 25 October.

Kernel updates and status per distribution

Upstream, the fix is in the stable releases 7.0.10, 6.18.33, 6.12.91, 6.6.141, 6.1.174, 5.15.208 and 5.10.257. Debian shipped it with DSA-6306-1 for Bookworm (6.1.174-1), DSA-6295-1 for Trixie (6.12.90-1) and DLA-4606-1 for Bullseye (5.10.257-1). According to the Debian tracker, these packages also contain the follow-up fix for CVE-2026-43503. RHEL and its derivatives also released kernel updates in May, and the respective advisories list the exact package versions. The reboot is what matters, because an installed but not booted kernel package protects nothing.

Amazon Linux deserves a closer look. In bulletin 2026-029, AWS states that Amazon Linux and Bottlerocket do not ship the espintcp module and are therefore not affected. As an additional safeguard, AWS shipped the correction in the networking core anyway. According to the ALAS database, CVE-2026-46300 is fixed for Amazon Linux 2 in kernel (ALAS2-2026-3307) and in the extras kernel-5.4 (ALAS2KERNEL-5.4-2026-122), kernel-5.10 (ALAS2KERNEL-5.10-2026-119) and kernel-5.15 (ALAS2KERNEL-5.15-2026-103). For Amazon Linux 2023 the same applies to kernel (ALAS2023-2026-1710), kernel6.12 (ALAS2023-2026-1708) and kernel6.18 (ALAS2023-2026-1709). All advisories date from 15 May and also cover CVE-2026-43503, with livepatches following on 25 May.

Wolfi does not ship a host kernel. Containers on Wolfi or Alpine images run on the node's kernel, so a new base image changes nothing about this flaw. The same holds for every container image, which is also why image scanners cannot report Fragnesia in a meaningful way. You have to check the kernel at the node level.

Three pitfalls show up in practice. First, uname -r is not a reliable indicator on distribution kernels with backports, and the package version compared against the advisory is what counts. Second, the reboot is at odds with forensics, because it erases a possible manipulation in the page cache and with it the evidence. If you suspect an attack, check first and reboot afterwards, as described in the detection section. Third, a module block only works if ESP is built as a module. If it is compiled into the kernel, there is nothing to block, which modprobe -n -v esp4 shows quickly.

Hardening beyond the kernel patch

First, some context. The bug sits in the kernel, not in an application, and it requires that the attacker already runs code on the system. Everything that blocks the path to the vulnerable code is therefore effective, meaning modules, namespaces and syscalls, as is everything that reduces what the write access is worth. If you cannot use setuid binaries and cannot see files that a privileged process reads, the write access gains you little. Measures that only protect the file system or the network miss the mark, because the write happens in memory, past all file permissions.

Block the ESP modules where no IPsec runs

The vulnerable path is only reachable when ESP is loaded. A file in /etc/modprobe.d/ with install esp4 /bin/false and install esp6 /bin/false prevents loading. TuxCare and Tenable also list rxrpc, which plays no role for Fragnesia itself. Modules that are already loaded can be removed with rmmod. This block was already the recommended mitigation for Dirty Frag in May, and anyone who applied it back then was protected against Fragnesia from day one. That is where its real value lies: it closes a whole class of bugs in the ESP code without anyone having to know the next flaw.

For Kubernetes, one point matters. Loading a module is always done by the host kernel, even when a process inside a container triggers it. A block at node level therefore protects all pods, but it has to reach every node through the node image, configuration management or a DaemonSet. The limit is IPsec itself: strongSwan and Libreswan stop working, while WireGuard is unaffected. Check beforehand whether your CNI uses IPsec encryption, which Cilium and Calico offer as an option. In that case the block breaks encryption between nodes. If you want a host to load no modules at all after boot, you can set kernel.modules_disabled=1. According to the kernel documentation, this switch cannot be reverted until the next reboot, so it only suits nodes with a fixed set of modules.

Forbid user namespaces for application pods

Without its own user namespace, an unprivileged process gets no CAP_NET_ADMIN and cannot create an ESP security association. The most effective measure at pod level is therefore seccomp, and the runtime's default profile already helps here. In the source of containerd's default profile, unshare, setns and clone3 are only open to processes with CAP_SYS_ADMIN, and the profile rejects clone as soon as flags for new namespaces such as CLONE_NEWUSER or CLONE_NEWNET are set. clone3 passes its flags in a structure in memory that seccomp cannot read. The profile therefore answers the call with ENOSYS, and the C library falls back to clone, which can be filtered. Docker's profile is built the same way in its current state, even though the table in the Docker documentation still mentions an exception for unshare --user. The easiest way to check that this holds on your nodes is to run unshare -U true in a test pod, which should fail under RuntimeDefault. The trap is the Kubernetes default, because without a setting pods run as Unconfined, without any seccomp at all. seccompDefault: true in the kubelet configuration and the Pod Security Standard “restricted”, which requires a seccomp profile, fix that.

With a custom profile of type Localhost, socket() with the AF_NETLINK family and the NETLINK_XFRM protocol can also be blocked, because seccomp can compare simple syscall arguments. A PHP process has no reason to configure IPsec. The operational effort lies in distributing the profiles to the nodes, for which the Security Profiles Operator is a good fit, and in testing whether tools in the image unexpectedly need namespaces. At host level, user.max_user_namespaces=0 is the blunter option, which according to TuxCare breaks rootless containers, Flatpak and browser sandboxes. According to the kernel documentation, the limit applies to any user in the respective user namespace, root included. I conclude from this that the container runtime then cannot create a user namespace for pods with hostUsers: false either. The two measures therefore exclude each other, and for Kubernetes nodes the seccomp profile is the better route.

No setuid gain inside the container

The public exploit modifies su and then needs that setuid program to be started. If a container sets allowPrivilegeEscalation: false, the runtime enables no_new_privs for its processes. According to the kernel documentation, execve() then ignores setuid and setgid bits, so the modified su runs with the caller's privileges. Together with runAsNonRoot: true and capabilities: drop: ["ALL"], this is the core of the Pod Security Standard "restricted". The container then has no process that executes a manipulated file with higher privileges.

The limit of this measure lies in the nature of the bug, and this is where it gets subtle. The write hits the page cache of a file, meaning every copy the system holds of that file in memory. What I derive from this: if several containers on a node share the same image layer, they also share its pages in the page cache, and an attacker in a harmless pod could modify a file that a privileged container from the same base image executes. The same applies to hostPath volumes a pod can read. Privileged DaemonSets should therefore not run on the same base image as application pods, and hostPath should be forbidden in tenant namespaces, which the Pod Security Standard "baseline" already enforces.

Pod user namespaces with hostUsers: false

Kubernetes can start pods with hostUsers: false in their own user namespace. Root in the container then maps to an unprivileged user on the host. If an attacker succeeds via a manipulated setuid program inside the container, by this logic they only end up as a root without privileges outside the pod. That is my own derivation from the user namespace model, not a statement from the Fragnesia analyses. The option does not prevent the write itself, which depends on the kernel and not on the UID. It therefore complements the seccomp profile without replacing it. It requires a recent kernel with ID-mapped mounts and a container runtime that supports the feature.

Make the initial foothold harder

Fragnesia is a local flaw, and the attacker first has to run their own code. In the observed attack, PHP2Shell provided that foothold. A minimal image without shell and compiler, plus a PHP configuration that blocks exec(), proc_open() and related functions through disable_functions and does not enable FFI, makes it harder to start a native program from a PHP flaw. This cannot prevent it completely, but every extra hurdle between web shell and kernel exploit costs the attacker time and leaves traces.

What does not help here

Some standard measures remain right but miss this mechanism. readOnlyRootFilesystem protects the disk, while Fragnesia writes into memory, and the target su is read-only anyway. For the same reason, integrity checks such as AIDE and image signatures see nothing, because the file on disk stays unchanged. Dropped capabilities alone do not help, because a new user namespace hands all capabilities back to the process inside it. NetworkPolicies and egress blocks have no effect, because the whole attack runs locally. SELinux alone was not enough in the observed attack either, because according to Dream the attacker ran as a confined user and still reached root. And a patch against Dirty Frag alone does not protect you, because it relies on exactly the flag that Fragnesia loses.

You will find more on Pod Security, seccomp profiles and node hardening in the series TYPO3 on Kubernetes under IT-Grundschutz.

Detection and verification

Three steps tell you if a host is affected. First, compare the installed and booted kernel package version with your distribution's advisory. Then check with lsmod whether esp4, esp6 or espintcp are loaded, and with modprobe -n -v whether they could be loaded. Finally, sysctl user.max_user_namespaces shows whether unprivileged processes may create user namespaces at all. For Kubernetes, add the question of which pods run without allowPrivilegeEscalation: false and without a seccomp profile.

It is harder to tell if someone has already exploited the flaw, because the manipulation lives only in memory. One documented indicator from the campaign is a /etc/pam.d/su file that contains pam_permit.so. Corgea recommends reading suspicious files once normally and once with O_DIRECT, bypassing the cache, and comparing the results, as well as running rpm -Va or debsums -s. If what is read differs from the disk, the page cache was modified. In terms of behavior, Corgea lists newly created user and network namespaces in workers or build jobs, XFRM netlink activity from processes with no IPsec role, and ESP modules being loaded on hosts that do not run IPsec.

If you suspect an attack, follow a clear order. Secure the evidence first, while the page cache still holds the manipulation, because a reboot or a cache drop removes backdoor and trace at the same time. Then block the modules or install the kernel and reboot. Treat a confirmed case as a full compromise of the host. On a Kubernetes node that means treating the kubelet credentials and all secrets and service account tokens of the pods on that node as leaked and rotating them.

Recommendation for operators

Act now if hosts or Kubernetes nodes run a kernel without the May fix and execute third-party code there, whether a PHP application, CI job or tenant workload. This is especially urgent if PHP is not yet on the October releases against PHP2Shell. In that case, block the ESP modules right away, provided IPsec is not needed, and schedule the kernel update and reboot for the coming days, not the next quarter.

Monitoring is enough if your nodes run Amazon Linux or Bottlerocket, or if the kernel is verifiably patched and booted. The seccomp profile against user namespaces and allowPrivilegeEscalation: false are still worth it, because they defuse the next flaw of this class before a patch exists.

Regardless of the individual case, what I say about every flaw applies: patches and updates belong in automation, and for the kernel that means the node images. If Renovate or Dependabot propose new AMI versions, node images and base images as merge requests on their own, and the pipeline rolls the nodes out to staging first, a kernel fix like this one is a matter of hours rather than weeks. Fragnesia shows what happens otherwise: the fix was ready in May, the attacks in the wild became known in October, and in between lay five months in which every forgotten update stayed an open window. On self-managed Debian nodes, automatic package updates combined with a service such as kured, which reboots the nodes one after another in an orderly way, take care of this part. A cooldown period for routine updates protects you against compromised releases, and security updates are exempt from it. If a new kernel breaks something, such as the CNI's IPsec encryption, you notice it in testing and not in production. If you update nodes by hand, you quickly end up with weeks without a patch during the holiday season, followed by one big batch update where nobody knows which change caused what.

Frequently asked questions about CVE-2026-46300

Is the Dirty Frag patch enough?+

No. The Dirty Frag fix relies on exactly the flag that Fragnesia loses. You are only protected if you booted the new kernel or blocked the ESP modules.

Are EKS nodes on Amazon Linux affected?+

According to AWS they are not, because Amazon Linux and Bottlerocket do not ship the espintcp module. AWS still shipped the kernel correction on 15 May, so keeping your nodes current is worth it.

Can I simply block esp4 and esp6?+

Yes, if the host does not run IPsec. Check beforehand whether your CNI encrypts traffic between nodes with IPsec. WireGuard is not affected by the block.

When may I reboot after a suspected attack?+

Only after you have secured the evidence. The manipulation lives only in the page cache, and a reboot erases it together with the trace. After that you patch, reboot and rotate all credentials of the host.

Conclusion

Fragnesia is essentially the Dirty Frag bug in a different place. Dirty Frag was fixed by making ESP trust a flag, and Fragnesia found a function that did not pass that flag on. Two days later, CVE-2026-43503 followed with the next group of such functions. The transferable lesson: when a security guarantee depends on many code paths correctly passing on a marker, each patch is only as good as the next forgotten spot. Hardening that makes the whole path unreachable, on the other hand, holds against the next variant too. The module block from May proved that, and a seccomp profile against user namespaces does the same for every kernel flaw that an attacker can only reach through their own user namespace.

Sources

I check your Linux nodes for Fragnesia and related kernel flaws, close unused attack surface and automate kernel updates in your cluster.

Node and kernel audits for Kubernetes, module blocks and seccomp profiles for PHP workloads, Pod Security "restricted" and automated rollouts of new node images.

Platform operations instead of advice on paper: I check, patch and harden your infrastructure continuously.

About the author

Photo of Kai Ole Hartwig.

Kai Ole Hartwig

Freelance DevSecOps consultant · OnlyOle Consulting

Programming since 2002 – self-taught, set up my own business with KO-Web in 2012. Over 100 projects, with a focus on security, performance, automation and quality. Today freelance: DevSecOps consulting, training and software development.