
nftables ingress rulesets that drop IPv4 TCP SYNs carrying IP options to mitigate the CVE-2026-98432 use-after-free, with per-source counters and logging.
nftables mitigation for CVE-2026-98432: drop IPv4 TCP SYNs that carry IP options, as early as nftables can.
The CVE is a use-after-free in the IPv4 TCP request-socket path
(ireq->ireq_opt / inet_opt, read in ip_options_build()). The proper fix is
a kernel update; this only reduces exposure until you have one.
ireq->ireq_opt is set in one
place, tcp_v4_init_req(), from the options of the SYN that opens the request
socket; the racing retransmitted SYN only reads what was saved there, and the CVE
scenario has the attacker send a SYN with Record Route or Timestamp options. So a
SYN with options is the packet that matters. Request socket migration reuses the
same saved ireq_opt. This rests on the kernel source and the CVE text, not on a
statement by the CVE authors that SYN-only is sufficient; remove the
tcp flags & (syn | ack) == syn match from the rules for the conservative
version.
Set IFACES to the NICs that receive external traffic, then
sudo nft -f simple.nft. The ruleset is:
define IFACES = { eth0 }
table netdev cve_2026_98432 {
chain ingress {
type filter hook ingress devices = $IFACES priority -500; policy accept;
ip protocol tcp ip hdrlength > 5 tcp flags & (syn | ack) == syn limit rate 5/second burst 10 packets log prefix "CVE-2026-98432 drop: " level warn
ip protocol tcp ip hdrlength > 5 tcp flags & (syn | ack) == syn counter drop
}
}
netdev ingress at priority -500 is the first netfilter hook, ahead of
prerouting, conntrack and routing. XDP still runs before it.ip hdrlength > 5 is the IPv4 IHL in 32-bit words; above 5 means options.tcp flags & (syn | ack) == syn is a SYN without ACK.counter on the drop rule is the total.Watch it: sudo nft list chain netdev cve_2026_98432 ingress and
journalctl -k -g 'CVE-2026-98432'.
fancy.nftfancy.nft is the same drop plus a named total counter and per-address counters,
kept in two dynamic sets. It also counts and logs every other TCP packet that has
IP options (passed, not dropped), with the option bytes in the log line
(flags ip options, prefix CVE-2026-98432 ip-options: ):
| Counter | Where | What it shows |
|---|---|---|
| total | named counter dropped_total | SYNs with IP options, dropped |
| other | named counter options_other | other TCP with IP options, passed |
| per source IP | set by_src | packets and bytes per dropped source address |
| per source . destination | set by_pair | the same per (source, destination) pair |
| any IP using options | set options_by_src | packets and bytes per source address for every IPv4 packet with options, any protocol (TCP, UDP, ICMP...), including the dropped SYNs |
sudo nft -f fancy.nft
sudo ./cve-log.sh # counters, then follow the kernel log
sudo ./cve-log.sh -c # counters only
sudo ./cve-log.sh -r # reset the total and the per-IP sets
Or directly:
sudo nft list counter netdev cve_2026_98432 dropped_total
sudo nft list counter netdev cve_2026_98432 options_other
sudo nft list set netdev cve_2026_98432 by_src
sudo nft list set netdev cve_2026_98432 by_pair
sudo nft list set netdev cve_2026_98432 options_by_src
Each element has its own packet and byte counter and expires 1h after its last hit. Each set holds at most 65535 entries; when one is full new addresses are not tracked, but the packet is still dropped. Source addresses can be spoofed, so the per-IP counters show what packets claimed, not who sent them.
sudo nft delete table netdev cve_2026_98432.
Neither persists across a reboot; include the file from /etc/nftables.conf.
fancy.nft adds the table and rebuilds only the rules, so reloading keeps the
counters; run nft delete table netdev cve_2026_98432 first to reset them or to
change a set. Counters live in kernel memory and do not survive a reboot.