
nftables ruleset and systemd unit that drop IPv4 TCP packets carrying IP options to mitigate the CVE-2026-98432 Linux kernel use-after-free until a patched kernel ships.
A stopgap firewall mitigation for CVE-2026-98432 on Ubuntu 24.04 (noble), for hosts exposing a public TCP listener (e.g. a Geth node on port 30303) while a fixed kernel is not yet available.
CVE-2026-98432 is a use-after-free in the Linux kernel's IPv4 TCP handshake code. When an incoming SYN carries IPv4 IP options, those options are handed to the new child socket without being copied; a retransmitted SYN processed concurrently on another CPU can then read them after they have been freed. A second trigger exists via request-socket migration (net.ipv4.tcp_migrate_req=1 or a BPF_SK_REUSEPORT_SELECT_OR_MIGRATE program).
Ubuntu rates it High priority (CVSS 3: 9.8). At the time of writing, the noble linux kernel fix is still in progress.
MPTCP listeners are not a separate case. Go 1.24+ binaries (including recent Geth) open listeners as MPTCP by default, so ss shows them as mptcp. MPTCP subflows go through the same IPv4 TCP SYN path, so they are exactly as exposed as plain TCP. Disabling MPTCP (GODEBUG=multipathtcp=0) does not mitigate this.
A single nftables rule, in its own table, drops any IPv4 TCP packet whose header length is not 5 (i.e. anything carrying IP options) before the kernel's TCP stack sees it:
ip protocol tcp ip hdrlength != 5 counter drop
ip hdrlength != 5) also dropped IGMP membership queries, which carry the Router Alert option, and can quietly break LAN multicast (mDNS/Avahi, casting).inet cvefix), so it coexists with ufw and is not touched by ufw reloads.| File | Install to | Purpose |
|---|---|---|
nftables-cve-2026-98432.nft | /etc/nftables-cve-2026-98432.nft | The ruleset (idempotent; safe to re-run) |
cve-2026-98432.service | /etc/systemd/system/cve-2026-98432.service | Oneshot unit that loads the ruleset at boot |
Do not put this rule in
/etc/nftables.conf. That file begins withflush ruleset, which would wipe ufw's rules ifnftables.serviceruns.
git clone <this-repo> && cd <this-repo>
sudo install -m 0644 nftables-cve-2026-98432.nft /etc/nftables-cve-2026-98432.nft
sudo install -m 0644 cve-2026-98432.service /etc/systemd/system/cve-2026-98432.service
sudo systemctl daemon-reload
sudo systemctl enable --now cve-2026-98432.service
sudo nft list table inet cvefix
Also confirm the migration trigger is off (default):
sysctl net.ipv4.tcp_migrate_req # expect: 0
Install nping. On Ubuntu it ships in the nmap package; there is no separate nping package:
sudo apt install nmap
First reload the ruleset to reset the counter to 0, then send a normal SYN (no IP options) over loopback:
sudo nft -f /etc/nftables-cve-2026-98432.nft # reload; resets the counter to 0
sudo nping --tcp -p 30303 --flags syn -c 3 127.0.0.1
sudo nft list table inet cvefix
Expected: SA (SYN-ACK) replies, showing the listener is still reachable, and the counter stays at packets 0 bytes 0. If you get no replies, nping may be failing to capture on loopback; in that case, run this test and the next one against the host's LAN IP instead of 127.0.0.1.
Send the same SYN, this time carrying an IP option (Record Route). Loopback traffic passes through the prerouting hook, so this exercises the rule with nothing in between:
sudo nping --tcp -p 30303 --flags syn --ip-options R -c 3 127.0.0.1
sudo nft list table inet cvefix
Expected: nping receives no replies (Lost: 3 (100.00%)), and the counter reads exactly:
ip protocol tcp ip hdrlength != 5 counter packets 3 bytes 240 drop
(3 probes × 80 bytes.)
Remote tests only exercise the rule if the probe actually reaches the host. On another machine on the same LAN, target the host's LAN address:
sudo nping --tcp -p 30303 --flags syn --ip-options R -c 3 <host-lan-ip>
Then check the counter on the protected host:
sudo nft list table inet cvefix
Expected: no replies, and the counter increases by 3 (to packets 6 bytes 480 if you ran step 2 first).
Probing the public IP from outside is usually inconclusive. Many routers and ISP devices reject IP-options packets themselves. A typical sign is an RA (reset) reply with a high TTL such as ttl=250, which points to a device with an initial TTL of 255 rather than a Linux host (initial TTL 64). The probe never reaches the host, so the counter does not move. Step 4 shows how to tell.
Run these on the protected host.
Confirm remote probes arrive. Start this, then repeat the step 3 probe from the other machine:
sudo tcpdump -ni any 'tcp port 30303 and (ip[0] & 0x0f) > 5'
| tcpdump on host | nft counter | nping reply | Meaning |
|---|---|---|---|
| shows packets | increases | none | Mitigation working |
| shows packets | unchanged | SA | Rule not active; check nft list table inet cvefix |
| nothing | unchanged | none or RA | Probe stopped upstream; test from the LAN or loopback |
Confirm nothing else is being caught. Leave the counter alone for several minutes; it should stay at your test count. To see non-TCP option-carrying traffic arriving (which this rule intentionally lets through):
sudo tcpdump -ni any '(ip[0] & 0x0f) > 5 and not tcp'
IGMP queries from the LAN router (e.g. igmp query v3 to 224.0.0.1) are normal and should not increment the counter.
systemctl is-enabled cve-2026-98432.service # expect: enabled
sudo nft list table inet cvefix # table present
sysctl net.ipv4.tcp_migrate_req # expect: 0
This is a stopgap. Check periodically for the patched kernel:
uname -r
sudo pro fix CVE-2026-98432
When a fix is available:
sudo apt update && sudo apt full-upgrade
sudo reboot
Livepatch (via a free personal Ubuntu Pro subscription; pro status, canonical-livepatch status) may avoid the reboot, but there is no guarantee a livepatch will ship for this CVE.
Once running a fixed kernel (keeping it is also harmless):
sudo systemctl disable --now cve-2026-98432.service
sudo nft delete table inet cvefix
sudo rm /etc/systemd/system/cve-2026-98432.service /etc/nftables-cve-2026-98432.nft
sudo systemctl daemon-reload
For extra margin until patched, inbound 30303 can be closed entirely (Geth keeps syncing via outbound peers, with fewer peers):
sudo ufw deny 30303/tcp
# later:
sudo ufw delete deny 30303/tcp