
Black-box remote probe that detects CVE-2026-98432, a Linux IPv4 TCP ireq_opt use-after-free, via differential SYN-ACK option echo, plus static vmlinux verification.
Hello. CVE-2026-98432 is remotely exploitable Use-After-Free in the Linux Kernel. Some reporting states that AI labs exploited it after ~45min via Multi-Path TCP (MPTCP). The vulnerability is actually in the IPv4 TCP stack itself, it's just that MPTCP is the only even remotely practical way to reach the vulnerable code path. Even then, ~45min should receive a very deep squint of scrutiny.
ireq_opt load and the first dereference.There are surely custom linux kernels which use MPTCP with per-packet steering options which likely make this vulnerability "reachable" in the purest sense. Practically speaking, you are almost certainly not running such a kernel configuration, and also aren't that unllucky for someone to be latency-close-enough and persistent enough to shoot that gap.
The primary factor that makes exploitation even remotely practical are virtualized/oversubscribed VMs, with PREEMPT_RT, threadirqs, etc... which is not something an attacker has any control or influence over unless that have some additional privileged/authenticated access. I have some friends in the industry who probably know a trick or two that would change my mind, but I don't know of anything, AI came up blank, and at the very least that "trick" if it exists, isn't widely known.
You should patch your servers, but probably not panic about it. Everything else in this repo, including the below lines is AI generated, but I steered the session and have reviewed the artifacts for accuracy.
As follows is:
A remote black-box probe that demonstrates the presence of CVE-2026-98432
(Linux IPv4 TCP ireq_opt / inet_opt use-after-free) with packet-level
evidence — not by matching kernel versions.
It stops at the use-after-free: it proves the kernel read freed memory and said so on the wire. It does not bypass KASLR, hijack control flow, or execute code.
Authorized testing only. The race probe can destabilise a target; a crashed kernel is a plausible outcome. Use it only on hosts you own or have written permission to test.
The fix is e3f33b0a1d89d738e2b913dab519226eaf76e15b
(ipv4: free inet_opt and ireq_opt after an RCU grace period), a two-line
change from kfree() to kfree_rcu() in inet_sock_destruct() and
tcp_v4_reqsk_destructor().
tcp_v4_init_req() stores the SYN's echoed IP options (Record Route /
Timestamp) in ireq->ireq_opt.tcp_v4_syn_recv_sock() transfers the pointer to the child socket instead
of copying it.ireq->ireq_opt under
rcu_read_lock() and uses it in ip_options_build().That last point is the detector: the kernel echoes the stale option bytes back to the sender.
Scope correction: the CVE record is generic IPv4 TCP. Only Anthropic's blog ties it to a "multipath TCP listener"; no CVE/NVD/Red Hat/Debian/Ubuntu text mentions MPTCP. The probe tests plain TCP first.
Three independent legs, weakest to strongest:
| Leg | What it shows | Artifact |
|---|---|---|
| Static (build-id pinned) | the running kernel's code shape: plain kfree at both destructors = vulnerable; kvfree_call_rcu = patched | verify-local, table below |
| Synthetic oracle | the wire detector fires when a responder echoes reused slab memory | scripts/validate_synthetic.sh → CONFIRMED VULNERABLE |
| Dynamic, kernel-level (KVM lab) | KASAN reports the actual slab-use-after-free in ip_options_build, with the commit's stacks; a kfree_rcu kernel with identical traffic is clean | evidence/guest-inst-vuln6-KASAN.log, evidence/guest-inst-patched2-CONTROL.log |
| Wire-visible, real kernel (non-KASAN lab) | the SYN-ACK on the wire carried the sprayed option block (42 04 be ef…) instead of the echoed timestamp; the kfree_rcu control stayed clean | evidence/race-stage1-wire-uaf.json, evidence/race-control-kfree_rcu.json |
The dynamic leg is a purpose-built 7.0.14 guest (KASAN + a boot-parameter
window stretch) with per-packet CPU steering: the retransmitted SYN is
processed on one vCPU, the ACK/transfer on a second, and the victim's
accept()+close() on a third, so the override is routine instead of
accidental. The kernel itself reported:
BUG: KASAN: slab-use-after-free in ip_options_build
Read of size 16 ... by task swapper/0 (cpu0, softirq)
__asan_memcpy <- ip_options_build <- ip_build_and_send_pkt
<- tcp_v4_send_synack <- tcp_rtx_synack <- tcp_check_req <- tcp_v4_rcv
Freed by task 1 (victim, cpu3):
kfree <- inet_sock_destruct <- __sk_destruct <- inet_release <- __x64_sys_close
object: kmalloc-96, 16 bytes in (struct ip_options at the rcu_head offset)
plus 30 reader-side READ-AFTER-FREE records (read 71–322 µs after the free,
optlen=40, KASAN shadow 0xfb). The patched control (kfree_rcu, verified
in the object file) ran the same race and hold: 0 KASAN, 0 read-after-free.
That is the vulnerability, not a version guess.
The reader is not a long loop. tcp_v4_send_synack() loads ireq->ireq_opt
and passes it to ip_build_and_send_pkt()/ip_options_build(), whose memcpy
is the use-after-free read; the load and the read are a few instructions apart
(tens to hundreds of nanoseconds). The free side — child destruction after the
victim's accept()+close() — is much later: the measured close cycle alone
is 61–92 µs, and the ACK→destruction latency adds the cross-CPU wakeup.
So the two events are not merely rare together; they are separated by ~90 µs by construction, and the read is over long before the free. The natural-timing experiment quantifies it: with the same instrumented kernel, no window stretching, ~140k race attempts and a concurrent half-open churn,
stats: frees=180223 overlaps=0 raf=0 KASAN: 0
the free never once landed inside the reader's window. A second, stronger attempt used the lab's slab spray: 8,000 attempts each carrying 200 retransmitted SYNs (1.6M independent reader windows) against a pristine kernel whose reader vCPU was kept runnable and preempted by host CFS hogs - still zero SYN-ACKs carrying the spray. The reader vCPU is preempted about once per 5 ms of runtime; 2M windows of ~10 ns against 20-40 s of runtime is a handful of hits even under a perfectly uniform model, and the observed zero says the preemption points are not uniform in the guest instruction stream.
The wire proof therefore needs the reader to be delayed deterministically,
which the lab does with a boot-parameter window stretch (reader_hold_us); on
a real target only a hypervisor or scheduler stall inside those ~10 ns can do
it. That is why the remote probe treats a positive as proof and a negative as
a bound, never as "patched".