CVE-2026-46116: use-after-free in xfrm, distros are just now patching it
Published by RodHat

CVE-2026-46116 is a slab-use-after-free in __xfrm_state_delete(), the function
that tears down IPsec Security Associations in the kernel’s xfrm subsystem. Upstream
fixed it in May. Patched versions landed across all the LTS branches: 5.15.210,
6.1.176, 6.6.140, 6.12.88, 6.18.30, 7.0.7. Ubuntu is shipping those fixes this
week. SUSE pushed patches on October 9. Older Ubuntu LTS releases (20.04, 18.04,
16.04) are still working through the queue.
CVSS 7.8. Local privilege escalation. No remote component. You need local access and the ability to touch network namespaces or trigger IPsec state churn. On most Linux distros, that bar is lower than you’d like.
What xfrm is and why it’s in everything
xfrm is the kernel’s transform framework. It’s the plumbing underneath IPsec: every Security Association (SA) created for an IKE tunnel, every in-kernel encrypt/decrypt operation on ESP or AH packets, every policy rule mapping a flow to a transform runs through it. WireGuard punted on xfrm entirely and does its own crypto handling in userspace-adjacent kernel code, but IPsec is not going away. Enterprise VPNs, site-to-site tunnels, and anything talking to Cisco or Juniper gear is almost certainly running xfrm under the hood, often without the sysadmin realizing it.
There’s a lot of state involved. xfrm keeps hash tables of xfrm_state objects keyed
by SPI, source address, destination address, and protocol. When a tunnel tears down
or a namespace is cleaned up, those objects need to be removed from the hash tables
and freed. That teardown is where this bug lives.
The bug
__xfrm_state_delete() is responsible for removing an xfrm_state from all the hash
tables that reference it. The problem is that it was checking whether a state was
already off a list by testing a value predicate (“is this field set to X?”) rather
than by testing actual list membership. Under syzkaller load, specifically during
namespace cleanup racing with xfrm_state expiry, you could end up with a state that
had already been removed from a list but whose predicate said it was still there.
The result: hlist_del_rcu() runs on a node that’s already been removed. At that
point you’re writing through list pointers into freed slab memory. Classic
use-after-free, with all the attendant possibilities for corrupting kernel heap layout
until something useful is at the right offset.
Red Hat’s syzkaller confirmation run on v6.12.47 (post-patch) ran 7.1 million executions across 32 VMs for over an hour with zero UAF hits. Pre-patch, the harness was reliably trigging it. The bug reproduces under load; it’s not theoretical.
The fix is instructive
The patch switches from hlist_del_rcu() to hlist_del_init_rcu() and adds explicit
“am I on a list?” checks before any removal.
The distinction matters. hlist_del_rcu() removes the node from the list but leaves
its own next and pprev pointers pointing wherever they were. A double-deletion runs
those stale pointers through hlist_del_rcu() again and writes garbage into whatever
slab memory they now point at. hlist_del_init_rcu() does the same removal, then
zeroes out next and pprev. A double-deletion now dereferences null and panics the
kernel. That’s a hard crash, which is annoying, but it’s not a privilege escalation.
This pattern, using _init_rcu variants that null out their own pointers after
removal, is the standard defensive approach for any list node that might be
encountered in multiple teardown paths. The bug is that xfrm wasn’t consistently doing
it. The fix is adding that consistency, plus guard checks so that __xfrm_state_delete
refuses to operate on a state that’s already been removed.
What you should do
Run uname -r and check whether your distro has shipped a patched kernel:
- Ubuntu 26.04 LTS: linux 7.0.0-28.28 or later
- Ubuntu 24.04 LTS: linux 6.8.0-136.136 or later
- SUSE Linux Enterprise 16: kernel 6.12.0-160000.28.1 (October 9, 2026)
- Upstream LTS branches: 5.15.210, 6.1.176, 6.6.140, 6.12.88, 7.0.7
If you’re on Ubuntu 20.04, 18.04, or 16.04: check your distro’s errata, patches are
in progress. If you’re running bare-metal IPsec and can’t patch immediately, restricting
unprivileged user namespace creation (kernel.unprivileged_userns_clone=0 or the
equivalent in your distro’s sysctl.d) closes the most accessible attack path. It’s a
blunt instrument, but it raises the bar while you wait for the patch.
The prctl hardening tip covers the per-process privilege controls that matter for reducing attack surface in this class of bug. The mseal work is a different angle on the same underlying problem: kernel memory layouts that don’t have enough invariants enforced at allocation time.
Five months
From upstream fix (May 28) to distro patches (October) is a long time for a CVSS 7.8 local privilege escalation. Ubuntu’s priority classification of this as “Medium” rather than “High” is probably why it took this long to land in the LTS queues.
Rod’s position on CVSS scoring is already on record. The number is often wrong in both directions, and a local priv esc in xfrm affects any Linux box running IPsec with untrusted local users. Most servers. Most containers. Most anything with network namespaces enabled.
Patch your kernels.
Sources
- CVE-2026-46116: kernel: xfrm: defensively unhash xfrm_state lists in __xfrm_state_delete (Red Hat Bugzilla)
- CVE-2026-46116 Ubuntu Security Notice (Ubuntu Security)