Two Linux kernel privilege escalations landed this spring, and if you run Kubernetes you should treat both as node-level incidents waiting to happen. They are unrelated bugs in unrelated subsystems, disclosed about a week apart. What makes them worth writing about together is that they share a punchline: an unprivileged process in one pod becomes root on the node, and from there, every other pod on that node is yours.
That shared punchline is not a coincidence. It is the defining property of containers - they share one kernel - and it is why the defence for both is nearly identical.
Copy Fail (CVE-2026-31431)
Copy Fail is a logic flaw in algif_aead, part of the kernel's AF_ALG userspace crypto interface. An in-place optimisation from 2017 lets an unprivileged user drive a deterministic, controlled 4-byte write into the page cache of any readable file - no race, no disk write, and no prior container escape needed to reach a victim outside the pod.
In Kubernetes that page cache is shared. When two pods run images built on the same base layer, the kernel serves those identical files from the same physical pages. So an attacker in a throwaway pod can corrupt the in-memory copy of a binary that a neighbouring pod - or a node daemon - is about to execute. Nothing on disk changes; integrity scanners see clean files.
It carries a CVSS of 7.8, a working public PoC has been validated against ACK, EKS and GKE, and it was added to CISA's Known Exploited Vulnerabilities list on 1 May 2026. The upstream fix shipped in 7.0 and the stable backports - 6.19.12, 6.18.22, 6.12.85, 6.6.137, 6.1.170, 5.15.204 and 5.10.254 - plus the distro kernels built from them. (Kernel 6.13 was already end-of-life before the fix, so there is no fixed 6.13.x; the version was vulnerable up to the 6.18 backport.)
Dirty Frag (CVE-2026-43284, CVE-2026-43500)
Dirty Frag is a local privilege escalation chain in two niche networking subsystems: ESP (IPsec) and RxRPC. Like Copy Fail it abuses page-cache-backed buffers the kernel does not exclusively own, this time reachable through splice()-style paths. The outcome is the familiar one: any local unprivileged user gets root. There is no remote vector.
The important nuance for cluster operators is the precondition, and it is subtler than "needs a capability". The ESP variant does need CAP_NET_ADMIN to register its IPsec state over XFRM netlink - but a normal pod can grant that to itself by unsharing into a new user namespace, where it is root, so simply not having CAP_NET_ADMIN in the pod spec does not save you. The RxRPC variant needs no privilege at all. What actually blocks both published chains is stopping the pod from creating that user namespace and reaching those subsystems - which, as the next section shows, is what the runtime's default seccomp profile already does. (A third, related bug, CVE-2026-46300, "Fragnesia", was disclosed separately about a week later; it lives in the same ESP-in-TCP area and needs its own patch - the Dirty Frag fix does not cover it.)
The network is not your security boundary, and neither is the container. The kernel is. Treat every node as a single trust domain and design for the day a pod turns hostile.
The defence is a stack, not a patch
Patching is necessary and not sufficient: kernel CVEs of this shape arrive several times a year, and you are always exposed in the window before a fix ships and reboots. So build the layers that make the next one a non-event too.
1. Patch the kernel - and automate the reboot
This is the only fix that closes the actual bug. What separates teams that shrug off kernel CVEs from teams that scramble is automation: a node-image pipeline that rebuilds on a cadence, plus orchestrated draining and rolling replacement.
# confirm the running kernel on every node, fast
kubectl get nodes -o custom-columns=\
'NODE:.metadata.name,KERNEL:.status.nodeInfo.kernelVersion'
Prefer replacing nodes over patching them in place - immutable nodes from a known-good image beat apt upgrade on a pet. Tools like the Kured reboot daemon or your managed pool's node auto-upgrade turn "patch the fleet" into a background process instead of a fire drill.
2. Turn on seccomp - the cheapest win you are probably skipping
This is the single highest-value line, and most clusters are missing it: Kubernetes runs pods Unconfined unless you ask for a profile. Turn on the runtime's default:
securityContext:
seccompProfile:
type: RuntimeDefault
What that profile buys you against these two bugs is worth being precise about, because it is easy to overstate. It blocks unshare without CAP_SYS_ADMIN and it blocks add_key/keyctl - and that is what stops the published Dirty Frag chains, which climb through a user namespace and the keyring, not the capability you did or didn't grant. Recent runtime versions (Docker 29.4.2, containerd 1.7.32 / 2.x from May 2026) also block the AF_ALG socket family that Copy Fail rides. What RuntimeDefault does not block is AF_KEY or AF_RXRPC - the default profile allows every socket family below AF_ALG - and CRI-O's profile does not yet block AF_ALG either. So treat it as a strong, cheap control, not a complete one, and apply it cluster-wide with the kubelet's --seccomp-default flag (stable since 1.27) rather than pod by pod.
If you want to deny the socket families directly, do it by narrowing a copy of the runtime's default profile - not with a fresh allow-everything profile. A pod gets exactly one seccomp profile, so a Localhost profile like this replaces RuntimeDefault, and one that defaults to allow would hand back unshare, add_key and everything else the default denies - a net loss:
{
"defaultAction": "SCMP_ACT_ERRNO",
"syscalls": [{ "names": ["socket"], "action": "SCMP_ACT_ALLOW", "args": [] }]
}
The safe shape is: start from the default profile and change only its socket rule to reject AF_ALG (38), AF_RXRPC (33) and AF_KEY/PF_KEY (15). Note that closing PF_KEY does not stop CVE-2026-43284 on its own - the ESP variant sets up its state over XFRM netlink and fires over a normal UDP socket - so this is defence in depth on top of the namespace and keyring blocks, not a substitute for patching.
3. Drop capabilities and stop running as root
The ESP variant of Dirty Frag wants CAP_NET_ADMIN, and almost nothing you ship legitimately needs it. Dropping it is good hygiene - but on its own it does not stop the exploit, which mints that capability inside a user namespace it creates. The control that matters is the pairing below: refuse to run as root and forbid privilege escalation, which (with the seccomp default above) is what closes the namespace path. The "restricted" Pod Security Standard bundles all of this - including requiring a seccompProfile of RuntimeDefault or Localhost, so the snippet below is a subset, not a substitute for the label:
securityContext:
runAsNonRoot: true
allowPrivilegeEscalation: false
seccompProfile:
type: RuntimeDefault
capabilities:
drop: ["ALL"]
Enforce it at the namespace boundary so a forgotten manifest cannot opt out:
apiVersion: v1
kind: Namespace
metadata:
name: workloads
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
The point is least privilege as a stack: non-root plus no-privilege-escalation plus the default seccomp profile together deny the user-namespace-and-keyring route both chains take. No single one of them is the whole answer - which is exactly why the layers matter.
4. Shrink the blast radius with the scheduler
The shared kernel means co-tenancy is a security decision, not just a bin-packing one. Keep untrusted or multi-tenant workloads off the same nodes as sensitive ones using a dedicated pool, a taint, and a toleration:
kubectl taint nodes -l pool=untrusted dedicated=untrusted:NoSchedule
For genuinely untrusted code - CI runners, customer workloads, anything that evaluates input as instructions - put a second kernel boundary under it with a sandboxed runtime. gVisor is the light option: a user-space kernel (runsc) that intercepts syscalls without a full VM. Kata Containers is the stronger one - it runs each pod inside a real microVM with its own guest kernel. For the hardest boundary, back Kata with Firecracker, the minimal KVM-based VMM that boots a microVM in under 125ms and is the isolation behind AWS Lambda. Firecracker is not a Kubernetes runtime on its own - Kata is the layer that wires it into containerd, and it registers as the kata-fc RuntimeClass (kata-deploy ships it but does not enable the Firecracker handler by default; it needs the devmapper snapshotter configured first):
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: kata-fc
handler: kata-fc
Firecracker needs /dev/kvm, which means a host with nested virtualisation. That used to mean bare metal - an .metal EC2 instance or a physical host - but as of February 2026 EC2 also exposes it on virtual C8i/M8i/R8i instances, and GCE and Azure have offered it for years, so this is no longer a bare-metal-only story (though nested is slower and outside Firecracker's own tested configuration). A pod under this runtime gets its own kernel, so a Copy Fail attempt corrupts that microVM's page cache, not the host's. The blast radius stops at the VM boundary.
5. Keep untrusted workloads off shared nodes
This one is specific to Copy Fail. The first published PoC works through shared page-cache pages: it reaches a victim that reads the same file from the same base layer, so giving sensitive workloads their own minimal, distinct base images does defeat that variant. But do not mistake layer separation for a boundary. A later write-up escapes pod-to-host by poisoning the host's runc binary, which the runtime bind-mounts read-only into every container regardless of image - no shared layer required. What reliably limits the damage is the node isolation above: workloads that never share a node cannot share a poisoned page or a poisoned host binary.
6. Strip unused kernel modules from your nodes
If you do not run IPsec or AFS, the ESP and RxRPC modules are pure attack surface. Same for algif_aead if nothing legitimately uses kernel crypto sockets. Blacklist them in your node image or a privileged bootstrap DaemonSet:
cat > /etc/modprobe.d/harden.conf <<'EOF'
install esp4 /bin/false
install esp6 /bin/false
install rxrpc /bin/false
install algif_aead /bin/false
EOF
rmmod rxrpc esp6 esp4 algif_aead 2>/dev/null || true
A module that cannot load is a vulnerability that cannot be reached. Two caveats: verify the workloads on those nodes do not need IPsec first, because this breaks it if they do; and check the code is actually a module - modinfo algif_aead reporting (builtin) means it is compiled into the kernel, where install and rmmod do nothing and you need a boot-line fallback such as initcall_blacklist=algif_aead_init instead.
7. Detect the attempt, and assume breach
None of the above is perfect, so watch for the behaviours these exploits require. A runtime sensor like Falco or any eBPF-based tool can flag the tells cheaply:
- a non-allowlisted process opening an
AF_ALG,AF_RXRPC, orAF_KEYsocket - an unexpected kernel module load
- a process writing to another's memory or executing a binary whose on-disk hash does not match what is running
And accept the premise behind all of it: a container RCE on an unpatched node is a node compromise. Wire your response so that a compromised pod triggers cordon, drain, and replacement of the node, not just a pod restart. Recycling the node evicts an attacker who has poisoned page-cache pages that no file scan will ever find.
The takeaway
Copy Fail and Dirty Frag will be patched and forgotten. The class will not. Shared-kernel escapes are a permanent feature of how containers work, and the controls that blunt them - seccomp on, capabilities off, sensitive and untrusted workloads on separate nodes, untrusted code in a sandbox, unused modules gone, and node recycling as your incident reflex - are the same every time.
Turn them on now, while these two are the example and not the incident.
References
- CVE-2026-31431 "Copy Fail" enables Linux root privilege escalation - Microsoft Security Blog
- Dirty Frag: Linux kernel LPE via ESP and RxRPC - Wiz
- Detecting Dirty Frag (CVE-2026-43284, CVE-2026-43500) - Sysdig
- CVE-2026-43284 detail - NVD
- Mitigating "Copy Fail" (CVE-2026-31431) - Red Hat
- Mitigating "Dirty Frag" / "Fragnesia" on OpenShift 4 - Red Hat