Racing the patch: catching and blocking CVE-2026-98432 with eBPF Anthropic on 27 October demonstrated a working exploit for CVE-2026-98432, a use-after-free in the Linux kernel's IPv4 TCP handshake rated 9.8 out of 10, that yields a root shell 53 minutes after crafted packets reach a server running a Multipath TCP (MPTCP) listener. The two-line fix is already in every maintained stable kernel, but Ubuntu's fixed kernels remained "work in progress" on every supported release as of the distribution's CVE tracker update on 30 October, leaving a window between public disclosure and patched systems. A technical post dated 2 November 2026 details how to detect and block exploitation with eBPF programs at the XDP, tc, BPF LSM and kprobe hooks, tested on Linux 7.2, and argues eBPF earlier in the stack can beat a netfilter rule in some setups. Send the right packets to a Linux server, and in Anthropic’s proof of concept you have a root shell fifty-three minutes later. No password, no login, no one clicking anything. That is CVE-2026-98432 , a use-after-free in the Linux kernel’s IPv4 TCP handshake, rated 9.8 out of 10 https://nvd.nist.gov/vuln/detail/CVE-2026-98432 . On 27 October, Anthropic showed the exploit https://www.anthropic.com/research/discovering-vulnerabilities-with-claude working against servers with a Multipath TCP MPTCP listener. The fix is two lines, and it is already in every maintained stable kernel. Your distribution’s kernel is another matter: as I write this, Ubuntu’s fixed kernels are still “work in progress” https://ubuntu.com/security/CVE-2026-98432 on every supported release. That gap, between “the bug is public” and “my kernel is fixed”, is where the danger is, and it is what this post is about: how to detect anyone trying the bug, how to block it before the patch arrives, why doing that with eBPF https://ebpf.io/what-is-ebpf/ earlier in the stack beats a netfilter https://www.netfilter.org/ rule in some setups and where it does not , the other mitigations that matter, and how to patch Ubuntu the moment the update lands. 📖 Terms in this post - Use-after-free UAF . Code keeps using memory after it has been freed. If an attacker can get their own data into that memory first, they control what the code reads. CWE-416 https://cwe.mitre.org/data/definitions/416.html - RCU. Read-copy-update: a kernel scheme that lets readers proceed without locks, provided writers wait for a “grace period” before freeing anything a reader might still hold. RCU https://docs.kernel.org/RCU/whatisRCU.html - IP options. Optional fields at the end of an IPv4 header, such as record-route or timestamps. A header with options is longer than the usual 20 bytes, so its IHL header length, in 32-bit words is greater than 5. They are rare on today’s internet. RFC 7126 https://www.rfc-editor.org/rfc/rfc7126 - Request socket. The small object the kernel creates when a SYN arrives on a listening socket, before the handshake completes. Here it holds ireq opt , a copy of the SYN’s IP options. request sock.h https://github.com/torvalds/linux/blob/master/include/net/request sock.h - MPTCP. Multipath TCP: one connection spread over several paths. A server uses it only if the application asks for it. RFC 8684 https://www.rfc-editor.org/rfc/rfc8684.html · mptcp.dev https://www.mptcp.dev/ - XDP. eXpress Data Path: an eBPF hook in the network driver that sees each packet before the kernel builds its packet structure sk buff . It can pass, drop or redirect. XDP https://docs.ebpf.io/linux/program-type/BPF PROG TYPE XDP/ - tc / netfilter. Later hooks in the receive path. tc runs eBPF or classifiers on the sk buff tc-bpf https://man7.org/linux/man-pages/man8/tc-bpf.8.html ; netfilter is what nftables and iptables program. Netfilter hooks https://wiki.nftables.org/wiki-nftables/index.php/Netfilter hooks - BPF LSM. eBPF programs attached to the kernel’s security hooks, which can refuse an operation by returning an error. BPF LSM https://docs.kernel.org/bpf/prog lsm.html · program type https://docs.ebpf.io/linux/program-type/BPF PROG TYPE LSM/ - kprobe. An eBPF program attached to an arbitrary kernel function. It can read the function’s arguments but, in general, cannot change what the function does. kprobes https://docs.kernel.org/trace/kprobes.html · program type https://docs.ebpf.io/linux/program-type/BPF PROG TYPE KPROBE/ Point-in-time 2 November 2026 . Ubuntu’s status comes from its CVE tracker as last updated on 30 October; check it again before you rely on anything here. I tested on a Linux 7.2: both eBPF programs pass the verifier; the BPF LSM block and the bpftrace tripwire work end to end over loopback; the XDP program detects and drops live traffic in native mode on a veth pair testing -testing-it-before-you-trust-it ; all four nftables and iptables rules below drop SYNs that carry IP options and let ordinary connections through; and every Go setting below was checked against a real listener with ss . Try them on a test host in detect mode before you put them on anything that matters. 🐛 The bug, briefly When a SYN arrives with IPv4 options, the kernel copies those options into the request socket ireq- ireq opt . When the handshake completes, tcp v4 syn recv sock hands that copy to the new child socket without making its own copy. Meanwhile, on another CPU, a retransmitted SYN for the same connection can make the kernel send another SYN-ACK, which reads ireq opt under RCU protection only. Child sockets free their options with a plain kfree , not after an RCU grace period. If the child is torn down at the wrong moment, the SYN-ACK path reads freed memory in ip options build . CVE record https://nvd.nist.gov/vuln/detail/CVE-2026-98432 A few properties matter for everything that follows: - It needs IP options on the SYN. ireq opt is only filled in when the SYN carries options; otherwise it stays empty. tcp v4 save options https://github.com/torvalds/linux/blob/master/include/net/tcp.h That is why every network mitigation below is “drop IPv4 TCP packets with IP options”. - It is IPv4 only, but that includes IPv4 connections to IPv6 sockets. The IPv6 handshake copies options instead of sharing them. Red Hat Bugzilla https://bugzilla.redhat.com/show bug.cgi?id=2553390 A server listening on :: without IPV6 V6ONLY , though, also accepts IPv4 clients, and the kernel hands those connections straight to the vulnerable IPv4 code. tcp ipv6.c https://github.com/torvalds/linux/blob/master/net/ipv6/tcp ipv6.c “It listens on IPv6” is no protection. Those packets are still IPv4 on the wire, so every mitigation below covers them. - MPTCP is how it was exploited, not what causes it. Anthropic’s exploit targets servers with an MPTCP listener. Red Hat is explicit that “Multipath TCP is not a required condition for this flaw” and that nodes with MPTCP disabled “remain affected”. Anthropic https://www.anthropic.com/research/discovering-vulnerabilities-with-claude · Red Hat https://access.redhat.com/security/vulnerabilities/RHSB-2026-016 - Losing the race is cheap. “A lost race only fails the handshake without crashing the whole system”, so an attacker simply retries. The proof of concept needed 48 minutes to leak a kernel pointer and five more to run a command. Anthropic https://www.anthropic.com/research/discovering-vulnerabilities-with-claude Retries are good news for a defender: they are noisy. - There is a second trigger. The same problem exists when request sockets are migrated between listeners net.ipv4.tcp migrate req=1 https://docs.kernel.org/networking/ip-sysctl.html , or a reuseport BPF program https://docs.ebpf.io/linux/program-type/BPF PROG TYPE SK REUSEPORT/ that migrates . CVE record https://nvd.nist.gov/vuln/detail/CVE-2026-98432 The upstream fix changes kfree to kfree rcu in two destructors, so readers already inside an RCU section finish before the memory goes. The structure involved already had the rcu head that needs. af inet.c https://github.com/torvalds/linux/blob/master/net/ipv4/af inet.c · tcp ipv4.c https://github.com/torvalds/linux/blob/master/net/ipv4/tcp ipv4.c · inet sock.h https://github.com/torvalds/linux/blob/master/include/net/inet sock.h 📨 Following the SYN-ACK through the kernel The bug lives in the middle packet of the handshake, the SYN-ACK: the only one of the three that the server sends . Here is what a Linux server does with it, step by step, from the current kernel source. The shaded step is where the bug lives: every SYN-ACK, including one triggered by a retransmitted SYN, is built from the request socket’s copy of the options. 1. It is built from the request socket. A SYN does not get a full socket straight away. The kernel creates a lightweight request socket and builds the SYN-ACK from it in tcp v4 send synack , which hands ireq opt to ip build and send pkt inside an RCU read section. tcp ipv4.c https://github.com/torvalds/linux/blob/master/net/ipv4/tcp ipv4.c That is the detail that matters: if the client’s SYN carried IP options, the server echoes them back in its SYN-ACK. In an ordinary handshake there are no options, ireq opt is empty, and the SYN-ACK has a plain 20-byte IP header. 2. Only the request socket is kept. After sending, the kernel does not keep the packet. It starts a retransmit timer on the request socket and, if no ACK arrives, builds and sends the SYN-ACK again from the same request socket, up to net.ipv4.tcp synack retries times 5 by default . ip-sysctl https://docs.kernel.org/networking/ip-sysctl.html 3. A retransmitted SYN gets an immediate reply. If the client never saw the SYN-ACK and sends its SYN again, tcp check req recognises a “pure retransmitted SYN” for the same request and sends a fresh SYN-ACK at once, subject to a rate limit, through the same code, reading ireq opt again. tcp minisocks.c https://github.com/torvalds/linux/blob/master/net/ipv4/tcp minisocks.c 4. The ACK ends it. When the client’s ACK arrives, tcp v4 syn recv sock creates the real socket for the connection, and the request socket goes away. From then on, nothing on the server refers to the SYN-ACK. Where the bug fits. Steps 3 and 4 can happen at the same moment on two different CPUs. In step 4, the new socket takes over the request socket’s options memory rather than copying it: ireq opt simply becomes the new socket’s inet opt . If that new socket is closed quickly, the memory is freed straight away with kfree . Meanwhile, on the other CPU, step 3 may still be building a SYN-ACK from that same memory. It is now reading memory that has been freed, and that an attacker may already have refilled. The fix frees it with kfree rcu instead, which waits until every reader that started before the free, including that SYN-ACK, has finished. This is also why tcp v4 send synack makes a good tripwire, below: every SYN-ACK built for a request that carries options passes through it. What each defence in this post does to that SYN-ACK: - Dropping the SYN XDP, nftables, iptables : the SYN never reaches TCP, so there is no request socket and no SYN-ACK. - The BPF LSM: the request is refused in security inet conn request , before tcp v4 send synack ever runs, so the SYN-ACK is never built. - A patched kernel: the SYN-ACK is built exactly as before, options and all, but the memory it reads cannot be freed underneath it. - Ordinary connections: no options means ireq opt is empty, the SYN-ACK goes out as usual, and the bug is out of reach. 🧭 Where you can intercept a SYN Every mitigation is a choice of where on a SYN’s path through the kernel to act. In order, for a packet arriving on a NIC: | | Point in the receive path | Who can act there | Can block? | |---|---|---|---| | 1 | NIC driver, before an sk buff exists | XDP native mode | ✅ | | 2 | sk buff built; generic XDP for drivers without native support | XDP generic mode | ✅ | | 3 | Device ingress | tc clsact https://man7.org/linux/man-pages/man8/tc-bpf.8.html , nftables netdev ingress https://wiki.nftables.org/wiki-nftables/index.php/Nftables families | ✅ | | 4 | IP layer, before routing | nftables/iptables prerouting priorities https://wiki.nftables.org/wiki-nftables/index.php/Netfilter hooks : defrag −400, raw −300, conntrack −200 | ✅ | | 5 | After routing: local delivery, or forward to a container | nftables/iptables input Red Hat’s rule, −310 or forward | ✅ | | 6 | TCP: request socket created, SYN’s options saved | BPF LSM inet conn request | ✅ | | 7 | SYN-ACK sent the racing reader | kprobe on tcp v4 send synack | ❌ observe only | | 8 | Final ACK: child socket created the racing writer | kprobe on tcp v4 syn recv sock | ❌ observe only | Points 1–5 filter packets . Point 6 refuses connection requests from inside the TCP code. Points 7 and 8 are where the bug actually happens, and they are where you can see it, but not stop it. ⚖️ Why earlier in the stack, and when it does not matter Red Hat’s official mitigation is an nftables rule at the input hook point 5 . It works, and if it fits your setup, use it. Moving the same check to XDP point 1 buys you five things. 1. It covers everything behind the NIC. Netfilter rules belong to one network namespace. Red Hat’s rule runs in the host’s input chain, so it never sees traffic that is routed onward to a container, and it says as much for OpenShift: rules on a node “act only on traffic traversing that node’s host network namespace”. Red Hat https://access.redhat.com/security/vulnerabilities/RHSB-2026-016 Every container shares the host’s kernel, so every container listener is vulnerable too. An XDP program on the physical NIC sees every frame the NIC receives, whether it is headed for the host, a Docker bridge, or a macvlan https://docs.docker.com/engine/network/drivers/macvlan/ container, before any namespace is chosen. 2. It is cheaper per packet. In native mode, XDP drops a packet before the kernel allocates an sk buff , runs conntrack or walks any rules. An attacker racing this bug sends handshakes for tens of minutes. Dropping them at the driver costs almost nothing. 3. It is independent of your firewall manager. Docker https://docs.docker.com/engine/network/packet-filtering-firewalls/ , ufw https://help.ubuntu.com/community/UFW , firewalld https://firewalld.org/ and configuration-management tools all rewrite netfilter rules. A hand-added table can disappear on the next reload or be ordered behind someone else’s. An XDP program is attached to the interface and stays until you detach it. 4. It tells you who. A rule counter tells you how many packets matched. An eBPF map can tell you which source addresses sent them, and the same program can switch from counting to dropping with a one-byte map update, no reload. 5. Attach and detach are atomic. There is no window during a ruleset reload where the rule is half-applied. It is not free, and it is not always better. - It only sees what arrives on that NIC. Traffic from one local container to another over a bridge, loopback, and the inner packets of tunnels WireGuard https://www.wireguard.com/ , VXLAN https://www.rfc-editor.org/rfc/rfc7348 never pass the physical NIC’s XDP hook. A malicious local container is out of its reach. - You own the parser. The program below handles plain Ethernet and one VLAN tag. Double tags, or unusual encapsulation, pass straight through. - Generic mode loses most of the speed. If your driver lacks native XDP, the program runs after the sk buff exists. It still runs before netfilter and still covers every namespace, but point 2 above mostly goes away. Common virtual NICs support native mode: virtio-net since 4.10 and Hyper-V’s netvsc since 5.6. - It is more to build and maintain. You need clang https://clang.llvm.org/ and bpftool https://github.com/libbpf/bpftool , the verifier https://docs.kernel.org/bpf/verifier.html can reject code, and fewer people on a team can read eBPF than can read an nft rule. - netfilter can be early too. An nftables netdev ingress chain point 3 runs on the device before routing and before namespaces, close to tc. If the namespace problem is all you care about, that is the low-effort fix; see the other mitigations -other-mitigations below. The BPF LSM program point 6 is a different trade-off again, covered in its own section below. 🧮 Which kernels: the bug, and each mitigation Kernel versions decide two separate things: whether you have the bug at all, and which of the mitigations below your kernel can run. Which kernels have the bug Every kernel from 4.4 onwards, where commit 079096f103fa introduced the race. The fix is in these stable releases and everything after them on each branch: | Branch | Fixed in | |---|---| | 5.10 | 5.10.272 | | 5.15 | 5.15.223 | | 6.1 | 6.1.190 | | 6.6 | 6.6.159 | | 6.12 | 6.12.113 | | 6.18 | 6.18.56 | | 7.2 | 7.2.10 | | mainline | 7.3 | CVE record https://cveawg.mitre.org/api/cve/CVE-2026-98432 Branches that are no longer maintained upstream, such as 4.19 and 5.4, have no upstream fix at all. On those, you depend entirely on your distribution backporting it. Distribution kernels also carry their own version numbers, so for Ubuntu, go by the package versions -patching-ubuntu below, not the upstream numbers. What each mitigation needs | Mitigation | Minimum kernel | Also needs | |---|---|---| | nftables rules inet input or prerouting | 3.14 | nftables | | nftables netdev ingress rule | 4.2 | nftables | | iptables u32 rule | any affected kernel | iptables with the u32 match | | XDP guard, generic mode xdpgeneric | 4.12 | clang, bpftool | | XDP guard, native mode xdpdrv | 4.8, if the driver supports it: virtio-net 4.10, ixgbe 4.12, i40e 4.13, ice 5.5, Hyper-V netvsc 5.6, veth 4.19 | clang, bpftool | | BPF LSM block | 5.7 | CONFIG BPF LSM=y ; bpf in the active LSM list; kernel BTF /sys/kernel/btf/vmlinux , 5.4+, CONFIG DEBUG INFO BTF=y ; a bpftool from kernel 6.2 or later for autoattach | | bpftrace tripwire | 4.1 for kprobes | kernel BTF 5.4+ for the struct casts, or kernel headers | | net.mptcp.enabled=0 | 5.6 | none. Older kernels have no MPTCP, so no MPTCP listeners, but they still have the bug | | net.ipv4.tcp migrate req=0 | 5.14 | none. Older kernels cannot migrate request sockets, so that second trigger does not exist there | Sources: the feature list in BCC’s kernel-versions.md https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md kprobes, XDP, generic XDP, driver support, LRU hash maps, BTF, bpf probe read kernel , BPF LSM , and the introducing commits for the rest: veth native XDP 948d4f214fde https://github.com/torvalds/linux/commit/948d4f214fde43743c57aae0c708bff44f6345f2 4.19 , BTF in sysfs 341dfcf8d78e https://github.com/torvalds/linux/commit/341dfcf8d78e 5.4 , MPTCP f870fa0b5768 https://github.com/torvalds/linux/commit/f870fa0b5768 5.6 , tcp migrate req f9ac779f881c https://github.com/torvalds/linux/commit/f9ac779f881c 5.14 , bpftool autoattach 19526e701ea0 https://github.com/torvalds/linux/commit/19526e701ea0 6.2 , nftables inet 1d49144c0aaa https://github.com/torvalds/linux/commit/1d49144c0aaa 3.14 and netdev ed6c4136f157 https://github.com/torvalds/linux/commit/ed6c4136f157 4.2 . What that means on each Ubuntu release With each release’s original GA kernel: | Ubuntu | Kernel | nftables / iptables | XDP guard | BPF LSM | Tripwire | MPTCP sysctl | tcp migrate req | |---|---|---|---|---|---|---|---| | 26.10 | 7.3 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 26.04 LTS | 7.0 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 24.04 LTS | 6.8 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | 22.04 LTS | 5.15 | ✅ | ✅ | ✅, but the stock bpftool 5.15 lacks autoattach | ✅ | ✅ | ✅ | | 20.04 LTS | 5.4 | ✅ | ✅ | ❌ needs 5.7 | ✅ if kernel BTF is present | no MPTCP | no migration | | 18.04 LTS | 4.15 | ✅ | ✅ | ❌ | needs kernel headers | no MPTCP | no migration | | 16.04 LTS | 4.4 | ✅ | ❌ generic XDP needs 4.12, virtio-net native 4.10 | ❌ | needs kernel headers | no MPTCP | no migration | HWE kernels move a release up the table: 20.04 with its 5.15 HWE kernel behaves like 22.04, for example. Check what you are actually running: uname -r kernel version grep -E 'CONFIG BPF LSM|CONFIG DEBUG INFO BTF=' /boot/config-$ uname -r cat /sys/kernel/security/lsm is "bpf" active? ls /sys/kernel/btf/vmlinux kernel BTF present? bpftool prog help 2 &1 | grep -c autoattach 1 means your bpftool has autoattach 🛡️ Block option 1: XDP guard , at the driver This program counts IPv4 TCP packets that carry IP options per source address, and, once you switch it to enforce mode, drops them. It matches the same packets as Red Hat’s nftables rule. It keeps its per-source counts in an LRU hash map https://docs.kernel.org/bpf/map hash.html and its mode in a one-slot array map https://docs.kernel.org/bpf/map array.html , and builds with libbpf https://github.com/libbpf/libbpf ’s headers. // SPDX-License-Identifier: GPL-2.0 // XDP guard for CVE-2026-98432: count, and optionally drop, IPv4 TCP // packets that carry IP options IHL 5 , before the kernel allocates // an skb for them. Mode 0 = detect count and pass , 1 = enforce drop . include