
Read-only bash audit script that triages CVE-2026-98432, a Linux kernel IPv4 TCP use-after-free, by checking kernel changelogs, distro fix matrices, and TCP listener exposure.
Read-only audit script for CVE-2026-98432, a use-after-free in the Linux kernel's IPv4 TCP handshake. A SYN-ACK can be built from IP options that another CPU has just freed. The trigger is network traffic against an ordinary listening socket, so unlike the page-cache and checksum bugs in the sibling audits it needs no local access.
When a connection completes, tcp_v4_syn_recv_sock() hands the request socket's IP options (ireq->ireq_opt) to the new child socket without copying them. A retransmitted SYN for the same request socket, handled on another CPU, can still be building a SYN-ACK from those options. Since 079096f103fa (4.4) request sockets run without the listener lock, the child's options are freed with a plain kfree(), and ip_options_build() reads freed memory (CWE-416).
A second race exists in request socket migration, which is only on with net.ipv4.tcp_migrate_req=1 or a BPF SK_REUSEPORT_SELECT_OR_MIGRATE program. The fix switches both frees to kfree_rcu().
| CVE | CVE-2026-98432 |
| Component | Linux kernel net/ipv4 (tcp_v4_syn_recv_sock, inet_sock_destruct, tcp_v4_reqsk_destructor) |
| Class | Use-after-free, remotely reachable over a listening TCP socket |
| Introduced | 079096f103fa "tcp/dccp: install syn_recv requests into ehash table" (4.4); migration part c905dee62232 |
| Upstream fix | e3f33b0a1d89 "ipv4: free inet_opt and ireq_opt after an RCU grace period" (Eric Dumazet), in 7.3-rc7 |
| Stable fixes | 5.10.272, 5.15.223, 6.1.190, 6.6.159, 6.12.113, 6.18.56, 7.2.10 |
| Reporter | Xinyang Ge |
| Severity | CNA: 9.8 (AC:L). Red Hat: 8.1 (AC:H, because of the race), no mitigation available. Ubuntu: High |
| Debian | bookworm fixed in 6.1.190-1, trixie in 6.12.113-1, forky 7.2.12-1 |
| Ubuntu | fix in progress for every release, none released yet |
| Red Hat | no affected-products table or erratum published |
The stable versions were checked against the kernel.org ChangeLog-* files on 2026-10-29. In each branch the listed release is the first to carry the fix and the release before it lacks it. Mainline was checked by comparing tags: 7.3-rc6 lacks the commit and 7.3-rc7 has it. Series that have no fix row (4.x, 5.4, 6.2–6.5, 6.7–6.11, 6.13–6.17, 6.19, 7.0, 7.1) are end-of-life and are reported as vulnerable. Distro states are as of the same date.
The bug needs a listening TCP socket and a SYN that carries IP options, plus a lost race between two CPUs. The script cannot judge how reachable your network makes those options, so it reports what it can see (listeners) and leaves the rest to you.
It only triages and changes nothing. It never loads a module, never opens a socket, never touches sysctl or firewall rules, and never runs a PoC, so it is safe to run in production.
changelog.Debian.gz or rpm -q --changelog) for the CVE ID or the upstream commit subject. A fix found there always wins./proc/net/tcp and /proc/net/tcp6:
net.ipv6.bindv6only=1;net.ipv4.tcp_migrate_req and notes container runtimes, whose published ports and private network namespaces it cannot see. They count as exposure.--json.The distro rows are a snapshot (2026-10-29). Vendors will ship fixes after it, so an old copy can report "fix in progress" for a kernel that has long been patched. The report prints the snapshot age and warns once it passes 14 days. The warning never changes the verdict, and the changelog check and the upstream thresholds do not age. With --quiet, a second NOTE: line appears only when stale, so a parser of line 1 is unaffected. JSON carries matrix_date, matrix_age_days and matrix_stale.
When it warns, pull the latest release of this script.
| Verdict | Meaning | Exit |
|---|---|---|
| PATCHED | Fix found in the changelog or in the upstream/vendor tables | 0 |
| NOT_APPLICABLE | Kernel predates 4.4 | 0 |
| MITIGATED | Unfixed, but no TCP listener is reachable over IPv4 right now. Still patch: starting any service re-opens it | 1 |
| VULNERABLE | Unfixed and at least one listener (or an unreadable /proc/net/tcp, or a container runtime) | 2 |
| UNKNOWN | Cannot tell, for example no changelog and no matrix row | 3 |
| ERROR | Bad option or missing tool | 4 |
MITIGATED is rare on purpose: almost every server listens on something. A local user can also run a listener in a private user+network namespace; that route is noted in the report but not counted, because its exploitability is not verified.
Two cases where a plain version compare would be wrong are handled:
ID=debian but runs its own proxmox-kernel-* series. The Debian floors do not apply, so it clears only through a changelog hit....-microsoft-standard-WSL2). The distro's rule does not apply; the upstream table does. Update it from Windows with wsl --update.sudo ./cve-2026-98432-audit.sh # text report
./cve-2026-98432-audit.sh --json # JSON on stdout
./cve-2026-98432-audit.sh --quiet # one line, exit code carries the verdict
./cve-2026-98432-audit.sh --since 2026-09-01 --no-hunt
It runs without root, but then the journal hunt and some package queries may be empty. See examples/ for a real report.
Update the kernel and reboot. There is no runtime switch that fixes it, and Red Hat lists no mitigation. Until then you can lower exposure:
net.ipv4.tcp_migrate_req=0 (the default). This closes the migration race only.bash tests/run_tests.sh # 95 cases, run as a normal user