FIELD NOTES · LINUX SECURITY
CVE-2026-98432: what I run until the fixed kernel ships
A post in my feed said a 9.8 Linux kernel bug was out, and to go patch it. On Ubuntu I could not: the fixed kernels were still pending. Networking is not my home ground, so I learned as I went, took it slow and kept asking questions until the workaround made sense. These are my notes: what the bug is, the one rule I run meanwhile, how I tested it, and what it does not cover. All files are linked at the end.
Sandeep Shelke · Stavanger ·
In short
- CVE-2026-98432 is a use-after-free in the Linux kernel's IPv4 TCP code. It starts with a TCP SYN that carries IP options.
- Ubuntu rates it 9.8. When I checked on 31 Oct 2026, Ubuntu's fixed kernels for 22.04, 24.04, 26.04 and 26.10 were still pending, and 20.04 and older had no fix version listed.
- IP options make the IPv4 header longer than 20 bytes. None of my use cases need them, so one nft rule drops every IPv4 TCP packet that carries them, before TCP sees it.
- I loaded it with a 5-minute timer that undoes it, checked SSH from a new session, and rebooted twice. Both times the rule was up about 6 seconds into boot, before networking and SSH.
- It is a workaround, not a fix. When the fixed kernel runs, it goes.
The header
What made it click for me: the IPv4 header has a length field, and it counts 4-byte words. A normal header is 5 words, 20 bytes. Anything longer carries IP options, up to 40 more bytes. A packet's length field alone tells you whether it carries options.
The bug
When a SYN with IP options arrives, the kernel keeps the options with the half-open connection. When the connection is accepted, the new socket gets the same options block instead of a copy. A retransmitted SYN for the same connection can be handled at the same moment on another CPU, which reads the options to build a SYN-ACK. If the new socket is freed while that read runs, the other CPU reads freed memory.
The upstream fix, "ipv4: free inet_opt and ireq_opt after an RCU grace period", changes kfree() to kfree_rcu() in two places, so the block is freed only after every reader is done. A second path goes through request-socket migration, which is off unless net.ipv4.tcp_migrate_req is 1 or a reuseport BPF program asks for it.
Anyone who can reach an open IPv4 TCP port on the host can send such a SYN. No login is needed. IPv6 is not affected by the main issue.
How serious
- Ubuntu: CVSS 3.1 9.8, priority high. CVSS 4.0: 8.3.
- Red Hat's bug report lists it as urgent.
- In the public review of the fix I found no crash trace and no reproducer, and it does not say how the bug was found. An AI review bot found that the first version of the fix missed the migration path, and the fix's author sent a second version that covers both.
- A comment on Red Hat's bug report, a question from another user, says remote root was shown against a server with an MPTCP listener. It cites a research post I could not find, so I treat it as unverified.
So I did not panic and I did not wait. Proportionate, not paranoid.
Am I exposed?
I wrote a small read-only script that answers that for one Ubuntu host. It looks for the CVE in the running kernel's changelog, for an installed fixed kernel that is not running yet, for open IPv4 TCP ports, and, as root, for the rule below. It prints ports only, no addresses.
sudo bash check-cve-2026-98432.sh; echo "exit $?"| Exit | Verdict |
|---|---|
| 0 | PATCHED: the running kernel's changelog lists the CVE |
| 1 | EXPOSED: no fix found. Also used when no ports are open (low remote exposure) |
| 2 | FIX INSTALLED, NOT ACTIVE: reboot into it |
| 3 | MITIGATED: no fix yet, the drop rule is loaded |
Without root it cannot read the firewall, so a mitigated host still says EXPOSED, with a note. The part that checks for the rule, shortened:
if [ "$(id -u)" = 0 ]; then
if command -v nft >/dev/null 2>&1 && NFT="$(nft list table inet cve98432 2>/dev/null)"; then
if echo "$NFT" | grep -q 'hook prerouting' && echo "$NFT" | grep -q 'hdrlength' && echo "$NFT" | grep -q 'drop'; then
MIT_STATE=active
else
MIT_STATE=broken
fi
else
MIT_STATE=absent
fi
fiIt says MITIGATED only when all three parts are found. If nft prints something else on another version, it falls back to EXPOSED, not to a false MITIGATED.
The rule
The rule file, /etc/nftables-cve98432.nft:
table inet cve98432
flush table inet cve98432
table inet cve98432 {
chain pre {
type filter hook prerouting priority -310; policy accept;
ip protocol tcp ip hdrlength gt 5 counter drop
}
}ip hdrlength gt 5is the length field from Fig. 01: more than 5 words means the packet carries IP options.ip protocol tcpkeeps it to IPv4 TCP. Everything else passes.- The first two lines make the file safe to load more than once.
Load it safely
Before anything, check whether nftables.service is enabled. Its /etc/nftables.conf usually starts with flush ruleset, which wipes this table when it loads.
systemctl is-enabled nftables.serviceIf it is enabled, it can load after this rule at boot and wipe it. The unit below orders itself after nftables.service, so the rule loads last. If you restart nftables.service later, restart this unit too: sudo systemctl restart cve98432-nft.service.
Then load the rule with a safety undo. If anything breaks, the timer deletes the table after 5 minutes.
sudo install -m 0644 rules.nft /etc/nftables-cve98432.nft
sudo systemd-run --unit=cve98432-revert --on-active=5min nft delete table inet cve98432
sudo nft -f /etc/nftables-cve98432.nftFrom a new SSH session, check that you can still log in and that the services you need still answer. Then keep it:
sudo systemctl stop cve98432-revert.timer
sudo nft list table inet cve98432The listing shows the counter. Every dropped packet adds to it.
Keep it across reboots
The unit /etc/systemd/system/cve98432-nft.service loads the rule before networking starts, and deletes the table when it stops:
[Unit]
Description=CVE-2026-98432 interim mitigation (drop IPv4 TCP with IP options)
DefaultDependencies=no
After=nftables.service
Before=network-pre.target
Wants=network-pre.target
[Service]
Type=oneshot
ExecStart=/usr/sbin/nft -f /etc/nftables-cve98432.nft
ExecStop=-/usr/sbin/nft delete table inet cve98432
RemainAfterExit=yes
[Install]
WantedBy=multi-user.targetsudo install -m 0644 cve98432-nft.service /etc/systemd/system/
sudo nft -c -f /etc/nftables-cve98432.nft && echo OK
sudo systemctl daemon-reload
sudo systemctl enable --now cve98432-nft.service
sudo bash check-cve-2026-98432.sh; echo "exit $?" # expect MITIGATED, exit 3I added After=nftables.service after my boot tests. It only changes the order on hosts where nftables.service is enabled.
To undo it at any time: sudo systemctl disable --now cve98432-nft.service. The stop deletes the table.
What I checked
Before touching a real machine, I tested the rule in a throw-away network namespace: unshare -rn gives a private network with only loopback, and everything in it disappears when the test ends. The test sends a SYN with IP options from Python, one connection per case:
IP_OPTIONS = getattr(socket, "IP_OPTIONS", 4)
RR = bytes([7, 7, 4, 0, 0, 0, 0, 0]) # Record Route, 1 slot, padded to 8 bytes
TS = bytes([68, 12, 5, 0] + [0] * 8) # Timestamp, 2 slots, 12 bytes
c = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
c.settimeout(3.0)
c.setsockopt(socket.IPPROTO_IP, IP_OPTIONS, RR)
c.connect(("127.0.0.1", 5000)) # times out when the rule drops the SYNtimeout 90 unshare -rn python3 test_cve98432.pyIf your host blocks unprivileged user namespaces, sudo unshare -n python3 test_cve98432.py does the same.
- Without the rule, a SYN with a Record Route or Timestamp option is accepted, and the new socket holds the options. That is the hand-off the bug is about.
- With the rule, both are dropped and plain connects still work. 28 of 28 checks passed, and again after I upgraded the machine to Ubuntu 26.04.
- An IPv4 SYN with options to a socket that accepts both IPv6 and IPv4 is dropped too.
- Loading
/etc/nftables.confwipes the table, and the options SYN is accepted again. Reloading the rule file restores the drop. - Live with the 5-minute timer: a new SSH session worked, a test connect with IP options timed out, and the counter went up by 3.
- Two reboots on one machine: the unit was active about 6 seconds into boot, before networking and SSH. After each boot the table was loaded, the options connect was dropped, and SSH worked.
- Then I put it on the other machines I run, with the same timer and the same checks.
What it does not cover
- It is not a fix. The kernel bug stays until a fixed kernel runs.
- It drops every IPv4 TCP packet with IP options, also legitimate ones. Normal traffic rarely uses them, but I have not measured it on every network. Watch the counter.
- It also drops forwarded IPv4 TCP packets with IP options, so on a router or a Docker or VM host it affects that traffic too. I have not tested it on a router.
- Options the host adds itself are not seen. NetLabel can add CIPSO options through SELinux or Smack. None of my machines runs either.
- The IPv6 issue with CALIPSO options has its own patch. The rule matches IPv4 only.
- The race itself was not tested. The tests show that the trigger is dropped.
- A normal user cannot see the rule.
sudo nft list table inet cve98432can. - If
nftfails at boot, the host boots without it.systemctl is-active cve98432-nftshows it.
Remove it later
Remove it once a fixed kernel is installed and running. Ubuntu has announced these versions, still marked work in progress when I checked: 24.04: 6.8.0-149, 22.04: 5.15.0-203, 26.04: 7.0.0-43, 26.10: 7.3.0-10. The changelog check in the script decides, not the version number:
- Before the reboot, the check script should say FIX INSTALLED, NOT ACTIVE, exit 2.
- After the reboot, as root, it should say PATCHED, exit 0.
sudo systemctl disable --now cve98432-nft.servicesudo rm /etc/nftables-cve98432.nft /etc/systemd/system/cve98432-nft.service && sudo systemctl daemon-reload
Do not skip understanding what it is doing. It is one line, and I only trusted it once I knew what each part does.
Files
- rules.nft: the rule
- cve98432-nft.service: the systemd unit
- check-cve-2026-98432.sh: the read-only exposure check
- test_cve98432.py: the namespace test, run with
unshare -rn
Shared as they are, for your own judgement. Read them before you run them.
Contact
Tell me which data and which work you have in mind.
Stavanger, Norway
Remote across EU and India time zones, on-site when needed