ENGINEERING

CVE-2026-98432: how to block the Linux TCP bug until Ubuntu and Red Hat ship a kernel

CVE-2026-98432 is a use-after-free in the Linux kernel’s IPv4 TCP handshake, and the kernel team scored it 9.8. Red Hat scores it lower because the race is hard to win. However, Anthropic’s red team reported that one of its AI models built a working exploit that gets a root shell with no credentials on a server with a multipath TCP (MPTCP) listener. Their write-up says it took about 48 minutes to get past KASLR and another 5 to get a shell. They haven’t released the exploit, and so far there are no reports of the bug being used in attacks.

The bug goes back to kernel 4.4. The upstream fix is in 5.10.272, 5.15.223, 6.1.190, 6.6.159, 6.12.113, 6.18.56, 7.2.10 and 7.3, and Debian, Oracle Linux and SUSE already have fixed kernels out (Amazon Linux 2023 only for its optional 6.12 and 6.18 kernels so far). Ubuntu and Red Hat haven’t yet. Ubuntu lists every supported release as “work in progress” and Red Hat’s fix is deferred, so for most Linux servers there’s no kernel to install today.

What triggers it

It’s triggered by a TCP connection request (the SYN) with an IPv4 Record Route or Timestamp option. The kernel keeps those options with the half-open connection, and once the connection is set up, it passes them to the new socket without making a copy. If another CPU is handling a retransmitted SYN for the same connection at that moment, it can read the options after they’ve been freed. IPv6 isn’t affected, because the kernel copies the options there.

Normal traffic doesn’t carry IPv4 options. RFC 7126 already recommends that routers and firewalls drop packets with Record Route or Timestamp options, so you can drop them on the server too without breaking anything.

Start with servers that have an MPTCP listener

Their exploit needs a server with an MPTCP listener. Anthropic hasn’t said why, and the CVE doesn’t mention MPTCP at all, so I wouldn’t call a server without one safe. It’s still where I’d look first.

I figured MPTCP listeners would be rare, but my own Ubuntu lab VM had one I never set up: step-ca, on port 9000. Since Go 1.24, servers written in Go listen with MPTCP by default on Linux whenever the kernel allows it, and Ubuntu 26.04 allows it out of the box (net.mptcp.enabled = 1). My Rocky 9.8 VM had it turned off.

Check one machine

Is the running kernel fixed. Check your distribution’s kernel changelog instead of the version number. Ubuntu and Red Hat backport fixes into their own kernels, so a kernel with an older-looking version can already have the fix. On Ubuntu:

zgrep -c CVE-2026-98432 /usr/share/doc/linux-modules-$(uname -r)/changelog.Debian.gz

On RHEL, Rocky and Alma:

rpm -q --changelog kernel-core-$(uname -r) | grep -c CVE-2026-98432

Anything above 0 means the kernel you’re running has the fix. On Ubuntu and RHEL you’ll get 0 for now.

MPTCP listeners.

sudo ss -lnMp

If it prints nothing, nothing is listening with MPTCP. On my Ubuntu VM it printed step-ca on *:9000.

You’ll also see sysctl -w net.mptcp.enabled=0 going around as a fix. It stops programs from opening new MPTCP sockets (Go programs fall back to plain TCP), but it doesn’t touch listeners that are already open. When I turned it off on my VM, step-ca kept listening with MPTCP, even though a new MPTCP socket was refused. If you go that way, restart whatever ss -lnMp lists and run it again to make sure it’s empty.

For Go services specifically, the Go team’s advice is GODEBUG=multipathtcp=0 (as an environment variable, or godebug multipathtcp=0 in go.mod if you build it yourself), which turns MPTCP off for that program. Canonical has already shipped that change in Pebble, and there’s an open proposal to turn MPTCP off by default in Go. And since nobody has confirmed that a server without an MPTCP listener is safe, block the trigger.

Block the trigger

This nftables rule drops any IPv4 TCP packet with IP options. Without options, an IPv4 header is always 20 bytes, which ip hdrlength reports as 5 (it counts 4-byte words). So ip hdrlength gt 5 only matches packets that have options. It runs at priority -300, before connection tracking:

sudo nft add table ip cve_2026_98432
sudo nft add chain ip cve_2026_98432 pre '{ type filter hook prerouting priority -300; }'
sudo nft add rule ip cve_2026_98432 pre meta l4proto tcp ip hdrlength gt 5 counter drop

The rule only drops packets, so it can’t open anything your firewall blocks, and it runs fine next to ufw and firewalld. IPv6, UDP and ICMP aren’t affected. If the server also routes traffic for other machines, TCP packets with IP options passing through it get dropped too. The one network where IP options are normal is a labeled network using CIPSO (SELinux or Smack NetLabel), so don’t add this rule there. As written it’s gone after a reboot (the RFF script below takes care of that).

How I tested it

On Linux you can put IP options on a normal TCP socket with setsockopt(IP_OPTIONS), and the kernel adds them to every packet it sends, the SYN included, which makes it easy to test. From my Rocky VM I connected to SSH on the Ubuntu VM twice, once plain and once with a Record Route option:

Ubuntu VMplain SYNSYN with Record Route
without the ruleconnectedconnected
with the ruleconnecteddropped (the rule’s counter went up)

I did the same test the other way around, with the rule on Rocky (where firewalld is running), and got the same result. The rule was still there after firewall-cmd --reload and after a reboot.

That shows the trigger is blocked, but I didn’t run the exploit itself.

Doing this across a fleet

There are two new Linux entries in the RFF Script Library:

Here’s the check on my Ubuntu VM before I ran the block:

RFF job output on Ubuntu 26.04: VULNERABLE, the kernel changelog doesn't list CVE-2026-98432 and step-ca listens with MPTCP

Here’s the block itself:

RFF job output on Ubuntu 26.04: the nftables rule written, its boot unit installed, and the table verified in the live ruleset

And here’s the same check afterwards:

RFF job output on Ubuntu 26.04: vulnerable, mitigated, the nftables rule drops IPv4 TCP with IP options

One thing I ran into on Rocky: my first version of the script wrote the rule file to /tmp and then moved it into /etc. mv keeps the file’s SELinux label, so nftables wasn’t allowed to read it and the rule never loaded at boot. If you write your own unit for this, create the file directly in /etc.

Once your distribution ships the fixed kernel, install it, reboot and run the check again. After that you can remove the rule.

RFF is free for your first 100 endpoints: getrff.com.

Sources

Comments

Comments use GitHub Discussions, so you'll need a GitHub account to post. Corrections are especially welcome.