How much damage can a four-line kernel patch prevent? For CVE-2026-98432, the answer is a remote, unauthenticated use-after-free in the IPv4 TCP handshake, scored CVSS 3.1 9.8 Critical by the Linux kernel's own CVE Numbering Authority. Every Linux host that accepts an IPv4 connection runs the vulnerable path before any application — and before any authentication — gets a say. The bug traces back to Linux 4.4. The upstream fix, posted by Eric Dumazet on October 1 and shipped in the stable kernels on October 11 and 15, changes two kfree() calls into kfree_rcu(). The distance between those two spellings is where the whole story lives.
A race older than some of your servers
The precondition for this bug was laid down years ago, when commit 079096f ("tcp/dccp: install syn_recv requests into ehash table") allowed request sockets to be processed without holding the listener lock. That was a sensible scalability change: the half-open connection table no longer serialized behind one lock. But it also meant that two CPUs could legitimately touch the same connection state at the same time, and every piece of memory hanging off that state needed a lifetime rule that matched.
The affected memory is the saved copy of the IPv4 options from the peer's SYN packet. The window of exposure is the few instructions between a handshake completing on one CPU and a retransmitted SYN being answered on another. And because losing the race only fails one handshake — the machine does not crash — Anthropic's writeup notes that a determined attacker can simply retry indefinitely until the timing lines up.
The kernel CNA scores the result 9.8 with the vector AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H: network-reachable, low complexity, no privileges, no user interaction. Red Hat rates the same bug 8.1, arguing attack complexity is high and the likely outcome is a crash. Anthropic's lab result — a remote root shell, built by Claude — is the reason not to hide behind the lower score.
What the kernel does with a SYN's options
When a SYN arrives, Linux does not build a full socket. It parks the half-open handshake in a lightweight request socket and waits for the final ACK. If the peer's IPv4 header carried options — Record Route or Timestamp are the two the CNA's notes mention — the kernel keeps them, because it is expected to echo those options in its replies.
When the handshake completes, tcp_v4_syn_recv_sock() builds the real child socket and hands the saved options over: ireq->ireq_opt becomes newinet->inet_opt, transferred, not copied. That handover is the design decision at the center of the CVE.
Meanwhile, other code paths still hold the request socket. If the peer's final ACK got lost, the peer retransmits its SYN, and a second CPU can match the still-listed request socket in tcp_check_req() and start building a SYNACK. The readers of the option block — tcp_v4_send_synack() and inet_csk_route_req() — run under rcu_read_lock() and nothing else.
The bug: a handover without a copy
Put those two facts together and the race writes itself:
CPU A CPU B ----- ----- handshake completes child socket created ireq_opt moved to child retransmitted SYN arrives (not copied) tcp_check_req() matches reqsk tcp_v4_send_synack() reads ireq_opt under rcu_read_lock() connection torn down inet_sock_destruct(): kfree(inet_opt) -----> ip_options_build() reads freed memory (UAF) Two details make this worse than a plain stale read. First, child sockets do not have SOCK_RCU_FREE, so nothing delays the free until readers drain. Second, ip_build_and_send_pkt() and ip_options_build() read opt->optlen twice; if an attacker reuses the freed allocation between those two reads, the length can change mid-build, turning an information leak into an out-of-bounds write. The CNA's scoring notes list all three outcomes: kernel heap contents disclosed to the remote peer, kernel memory corruption, or a crash.
One nearby suspect is explicitly innocent: the SYNACK retransmit timer holds its own reference on the request socket, and inet_csk_reqsk_queue_drop() calls timer_delete_sync() before the child can be freed. The commit message calls this out so reviewers do not chase it.
The second race: request-socket migration
The same commit fixes a sibling bug on a less-traveled path. With net.ipv4.tcp_migrate_req=1 — or a BPF_SK_REUSEPORT_SELECT_OR_MIGRATE program — a retransmitted SYN can cause the request socket to be migrated between listeners. reqsk_timer_handler() clones the request socket with inet_reqsk_clone(), so old and clone share the same ireq_opt, and reqsk_migrate_reset() then clears the pointer in the old one.
A CPU that still holds a reference on the old request socket — busy sending a SYNACK or creating a child — keeps using those shared options. When the clone is later freed, tcp_v4_reqsk_destructor() released them with a plain kfree(). Same pattern, same primitive, second door into the house.
The fix is two kfree_rcu() calls
Here is the entire upstream change, commit e3f33b0:
// net/ipv4/af_inet.c, inet_sock_destruct() - kfree(rcu_dereference_protected(inet->inet_opt, 1)); + kfree_rcu(rcu_dereference_protected(inet->inet_opt, 1), rcu); // net/ipv4/tcp_ipv4.c, tcp_v4_reqsk_destructor() - kfree(rcu_dereference_protected(inet_rsk(req)->ireq_opt, 1)); + kfree_rcu(rcu_dereference_protected(inet_rsk(req)->ireq_opt, 1), rcu); The reason this is sufficient is that the readers were already doing their half of the contract: every read of the option block happens under rcu_read_lock(). kfree_rcu() simply holds the reclamation until a grace period has passed, which by definition means every reader that could still hold the pointer has finished. The writers were the only side breaking the agreement.
They were also the only side still breaking it. Other paths that replace inet_opt — do_ip_setsockopt(), cipso_v4_sock_setattr() — already freed the old block with kfree_rcu(). The two destructors were outliers, which is exactly the kind of inconsistency a codebase-wide audit (or a persistent model) is good at finding. IPv6 escapes the first issue because tcp_v6_syn_recv_sock() duplicates the options instead of transferring them; the v6 migration case, reachable only through CALIPSO, gets its own patch later.
Who is exposed and who has shipped a fix
Any kernel from 4.4 onward that lacks the fix and accepts IPv4 TCP connections: web servers, SSH, databases, load balancers, Kubernetes nodes. Containers do not help — a container runs on the host's kernel, so rebuilding an image changes nothing.
Upstream shipped the fix in 7.2.10 and 6.18.56 on October 11; in 6.12.113, 6.6.159, 6.1.190, 5.15.223, and 5.10.272 on October 15; and in 7.3 on October 18. Distribution status as of October 29 was split. Debian fixed bookworm and trixie. SUSE fixed SLES 16.0 and the 15 SP4 through SP6 LTSS kernels, with SP7 in progress. Ubuntu's tracker listed the linux package as vulnerable on every release, with work in progress on the newer ones. Red Hat listed RHEL 8, 9, and 10 plus OpenShift 4 as affected with no fix and no mitigation meeting its support criteria.
The exploitation data point that should set your patching order: Anthropic's Frontier Red Team reports that Claude, after initial prompting, produced a working exploit that runs arbitrary commands as root with no credentials on servers running a multipath TCP listener — 48 minutes to leak a kernel pointer, about five more to reach command execution. The exploit is not public, but the recipe is now proven.
How to check a host in one minute
uname -r # running kernel dpkg -l 'linux-image*' | grep ^ii # Debian/Ubuntu: installed kernels rpm -q kernel # RHEL family: installed kernels needs-restarting -r # RHEL: is a reboot still pending? Compare against your distribution's tracker, not the upstream version numbers — distro kernels carry backports under their own numbering. A newer installed kernel is not protection until the machine boots into it, so a pending reboot is a pending vulnerability. Run these on container hosts and Kubernetes nodes, not inside containers.
Then check the multiplier:
ss -Mlnp # MPTCP listening sockets and their owners sysctl net.mptcp.enabled # 1 means applications may open MPTCP sockets ss -M lists only MPTCP sockets, so empty output means no listener. Note that net.mptcp.enabled = 1 by itself creates nothing — an application has to open an MPTCP socket — but any host with an actual listener belongs at the front of the patch queue.
If you cannot reboot this week
Red Hat publishes no mitigation, so the interim option comes from the disclosure analysis: the attacking SYN must carry IPv4 options, and legitimate TCP traffic almost never does. Dropping TCP packets whose IPv4 header exceeds the 20-byte minimum costs essentially nothing:
nft add table ip cve_2026_98432 nft add chain ip cve_2026_98432 pre '{ type filter hook prerouting priority -300; }' nft add rule ip cve_2026_98432 pre meta l4proto tcp ip hdrlength gt 5 counter drop The rule hooks prerouting before connection tracking, ignores IPv6 and non-TCP traffic, and vanishes on reboot — remove it manually with nft delete table ip cve_2026_98432 if needed. If your edge firewall can drop IPv4 packets with options, doing it there covers every host behind it at once. This narrows exposure; it does not fix the kernel. Until the reboot happens, keep MPTCP listeners off the public internet entirely.
The pattern behind the patch
RCU's deal is simple: readers run cheap and lockless, and in exchange whoever frees a shared object must wait until no reader can still be looking. This bug class — freed while a lockless reader still looks — is what that deal exists to prevent, and it survives anywhere one code path forgets the convention its neighbors follow. Two destructors missed a rule that do_ip_setsockopt() had been obeying all along.
The disclosure side carries its own lesson. A general-purpose model found a years-old race in one of the most-reviewed codebases on earth and then demonstrated the exploit end to end. Offense is getting cheaper and more patient. The defender's remaining advantages are boring ones: knowing which hosts run which kernel, shrinking the window between fix and reboot, and trimming exposed surface — like an MPTCP listener nobody remembered opening — before someone else's model comes looking. Check your fleet, drop IPv4 options at the edge if you can, and reboot into a fixed kernel.
Originally published on Dispatch.

