Remove adsMember
Theme: SystemContrast: Standard
On this page
  1. Why now
  2. Handing over a pointer
  3. Not “only MPTCP”
  4. Patching

CVE-2026-98432: a Linux TCP use-after-free in IP options

By 10 min readExpert
Copy article
Share this article

A server’s whole public surface is one listening TCP port. No login page, no upload form, no application code that has seen a single byte. Just the kernel finishing a handshake.

Someone sends it a SYN with a few extra bytes in the IP header. Then the same SYN again, a fraction too late. CVE-2026-98432 lives in that gap: a use-after-free in the Linux kernel’s IPv4 TCP connection setup, scored CVSS 9.8 by the kernel’s own CNA [1]. The short version is that the fix is a one-word change from kfree() to kfree_rcu(), and you need a patched kernel and a reboot [1][3][4].

Why now

The kernel CNA published the record on 15 October 2026 and updated it on 31 October [1]. The fix is older: Eric Dumazet wrote it on 1 October and it was committed on 5 October [8]. What changed last week is the context around it.

On 29 October the Go security team wrote to oss-security, pointing at an exploit Anthropic describes and warning that Go programs listen with MPTCP by default [4]. AlmaLinux put patched kernels in its testing repository on 30 October [3], and on 31 October someone proposed one more interim hardening option [5]. Red Hat rates it Critical at 9.8, and calls its own scores preliminary. Reliable code execution needs precise heap control, it says, but the chance of knocking systems over is what pushes it above Important [7].

Timeline of CVE-2026-98432 from fix to advisories

Handing over a pointer

During a TCP handshake the kernel keeps a small request socket for each half-open connection. If the SYN carried IPv4 options, such as Record Route or Timestamp, they’re stored in that request socket’s ireq_opt [1]. When the handshake completes, tcp_v4_syn_recv_sock() creates the real child socket and hands it that pointer. Not a copy, the pointer. Ownership just moves [1].

Fine, as long as nobody is still looking at the old request socket. But a retransmitted SYN can land on another CPU. That CPU runs tcp_check_req() and builds a SYNACK, reading ireq_opt under rcu_read_lock() and nothing stronger. Then ip_build_and_send_pkt() and ip_options_build() read opt->optlen. Twice [1].

Since commit 079096f103fa, request sockets are processed without the listener lock, so nothing stops the child from being freed halfway through [1]. Child sockets don’t have SOCK_RCU_FREE, and inet_sock_destruct() frees inet_opt with a plain kfree() [1]. Result: a use-after-free in ip_options_build(). The record lists 4.4 as the first affected version [1].

It’s easy to miss in review, because each piece looks reasonable on its own. A toy model, not kernel source:

c
struct opts { int optlen; char data[40]; };

/* reader: holds rcu_read_lock() but no lock the freer takes */
void build_synack(struct opts *o) {
    if (o->optlen > 40) return;
    memcpy(pkt, o->data, o->optlen);   /* optlen fetched again */
}

void destroy_buggy(struct opts *o) { kfree(o); }           /* gone now */
void destroy_fixed(struct opts *o) { kfree_rcu(o, rcu); }  /* gone after readers leave */

RCU is a promise to readers: whatever you were looking at when you entered the read section won’t disappear until you leave it. The readers here were already using RCU. The free path just didn’t keep the promise. The fix makes it keep it, with kfree_rcu() in inet_sock_destruct() and in tcp_v4_reqsk_destructor(). Other places that replace inet_opt, like do_ip_setsockopt(), already did it this way [1][8].

Two CPUs racing: one frees the options, the other reads them

Two more routes in

The record describes a second way to reach the same object. With request-socket migration (net.ipv4.tcp_migrate_req=1, or a BPF reuseport program that migrates), reqsk_timer_handler() clones the request socket, so the old one and the clone share ireq_opt. The old one is cleared, and a CPU still holding it can use the options when the clone is freed with a plain kfree() [1]. AlmaLinux notes that this setting is off by default [3].

IPv6 isn’t affected by the first issue, because tcp_v6_syn_recv_sock() duplicates the options. The IPv6 migration path has the same shape for ipv6_opt, which only CALIPSO sets, and the record says a separate patch will deal with it [1].

Not “only MPTCP”

The CNA’s scoring notes make the case for the full 9.8. An unauthenticated peer on the network supplies the option bytes. The handshake happens before any application authentication. Both sides of the race are attacker-induced. And reclaiming the freed object could leak neighbouring kernel heap into the SYNACK, turn a copy into an out-of-bounds write, or push past the packet headroom and panic the kernel [1]. That’s the CNA’s reasoning for the score, not a measured exploit.

The exploit that does exist is Anthropic’s. Its Frontier Red Team used an unreleased research model that wrote an exploit giving a remote attacker root on a Linux server with an MPTCP listener [2]. It works in two stages. First it uses the race to get the kernel to send back extra bytes containing a kernel pointer, which defeats KASLR. Then it uses the same bug to overwrite a callback pointer so that existing kernel functions run a chosen command. In the proof of concept the first stage took 48 minutes and the second five more. A lost race only fails the handshake; the machine doesn’t crash, so the attacker just tries again [2].

So don’t read “MPTCP listener” as “only MPTCP”. The Go security team says the demonstrated exploit needs one, but the bug itself isn’t specific to MPTCP, and systems without MPTCP listeners may fall to similar attacks [4]. AlmaLinux ships with MPTCP disabled and still tells its users to patch, because the flaw is in ordinary IPv4 TCP code [3].

And if you write Go: since Go 1.24, Go programs enable MPTCP by default for listen sockets on Linux [4]. You may be running an MPTCP listener without ever having chosen to.

Patching

Run uname -r on every host. Containers share the host kernel, so a clean image proves nothing; the node is what you patch.

Install a fixed kernel and reboot. Compare against your vendor’s fixed version rather than the upstream number, because distributions backport. Upstream stable fixes:

Kernel line Fixed in Source
5.10 5.10.272 [1]
5.15 5.15.223 [1]
6.1 6.1.190 [1]
6.6 6.6.159 [1]
6.12 6.12.113 [1]
6.18 6.18.56 [1]
7.2 7.2.10 [1]

Older stable lines such as 5.4, 4.19, 4.14 and 4.9 are inside the affected range, but the record names no fixed version for them [1]. If you’re on one of those, your vendor’s backport is the only way out.

Debian lists 6.1.190-1 as fixed in bookworm (DLA-4841-1), 6.12.113-1~deb12u1 for bookworm’s linux-6.12 package (DLA-4850-1) and 6.12.113-1 in trixie (DSA-6568-1), and notes that the mainline fix landed in 7.3-rc7 [6]. AlmaLinux’s patched builds were in the testing repository on 30 October, with production to follow after community testing. Installing from there means sudo dnf install -y almalinux-release-testing, then sudo dnf update 'kernel*' --enablerepo=almalinux-testing and a reboot. Once uname -r shows the right build, switch the testing repo off again with sudo dnf config-manager --disable almalinux-testing; a production box shouldn’t keep it [3]:

Release Patched kernel
AlmaLinux 8 kernel-4.18.0-553.180.2.el8_10
AlmaLinux 9 kernel-5.14.0-687.61.2.el9_8
AlmaLinux 10 kernel-6.12.0-211.69.2.el10_2

Red Hat’s CVE page points to security bulletin RHSB-2026-016 [7]. When I read the bulletin, it listed no fixed kernel yet and called the investigation ongoing, so watch your subscription’s errata. It also reminds you that OpenShift, OpenStack, Virtualization and RHEL CoreOS run on the RHEL kernel, so their nodes need the update too [10].

Then find your MPTCP listeners and decide whether you need them. If you don’t, turn MPTCP off system-wide. Any host that can’t run a fixed kernel yet gets the stopgaps further down, with a name next to it for whoever removes them later.

Checking MPTCP

bash
sysctl net.mptcp.enabled                 # 0 means off
sudo ss -lnMp                            # open MPTCP listeners
echo 'net.mptcp.enabled = 0' | sudo tee /etc/sysctl.d/90-disable-mptcp.conf
sudo sysctl -w net.mptcp.enabled=0

The sysctl only stops new MPTCP sockets, so restart the services that own existing listeners [3]. One oss-security reply adds that newly started Go programs cope with this, while running ones may see errors on new connections [9].

For Go you can also set GODEBUG=multipathtcp=0 in the environment, or godebug multipathtcp=0 in go.mod. Programs can override both per socket through net.ListenConfig.SetMultipathTCP [4]. The Go team calls the per-program route fragile, since one forgotten listener leaves the host exposed, and it assumes every workable exploit needs MPTCP [4].

Until a fixed kernel is running

Three stopgaps turn up in the sources. None of them fixes the bug, and all three sources say so.

Drop TCP packets that carry IPv4 options. AlmaLinux publishes an nftables rule that drops inbound IPv4 TCP packets with options, meaning an IP header longer than five 32-bit words [3]:

bash
sudo nft -f - <<'EOF'
add table ip cve_2026_98432
delete table ip cve_2026_98432
table ip cve_2026_98432 {
    chain prerouting {
        type filter hook prerouting priority -300; policy accept;
        meta l4proto tcp ip hdrlength > 5 counter drop
    }
}
EOF
sudo nft list table ip cve_2026_98432   # the counter shows what it dropped

Red Hat expects ordinary traffic to pass, since standard TCP options live in the TCP header, not the IP header [10]. Read AlmaLinux’s caveats before you rely on it, though [3]. The rule doesn’t survive a reboot, so apply it again if you reboot before the patched kernel is in. Traffic between containers on the same host may never pass through it. On a host that routes, it also drops forwarded TCP packets with IPv4 options. Don’t use it on networks with CIPSO labels (NetLabel), because there the kernel adds those options itself. IPv6 doesn’t need it. And you need the nftables package installed.

Red Hat’s bulletin has its own version: an inet table on the input hook at priority -310, with ip protocol tcp ip hdrlength > 5 counter drop. It suggests saving it to a file and checking it with sudo nft --check --file cve-2026-98432.nft before loading it. Red Hat says it “may disrupt necessary workloads”, so try it outside production first [10]. The two rules differ in one way that matters. AlmaLinux’s prerouting hook also sees forwarded packets. Red Hat’s input hook only sees traffic addressed to the host itself.

Turn off MPTCP, with the sysctl shown above. It removes the setup Anthropic’s exploit used, but the ordinary IPv4 TCP path is still vulnerable [3][4].

Zero freed memory with init_on_free=1. On 31 October Rui Sousa proposed this kernel boot parameter on oss-security as an extra layer [5]. It makes the kernel zero slab objects and pages when they’re freed. The stale pointer still gets dereferenced, but a use-after-free read then sees zeroes instead of the old object’s contents, which “narrows what an attacker can learn through it” [5]:

bash
sudo grubby --update-kernel=ALL --args="init_on_free=1"
sudo reboot
grep -o init_on_free=1 /proc/cmdline

Yes, that needs a reboot. It’s for a host that can reboot but has no fixed kernel to boot into yet, because its vendor hasn’t shipped one. According to the post, the AlmaLinux 9 and 10 kernels it checked have this off by default. The post puts the cost at roughly 1 to 5% on workloads that free a lot of memory, though it cites no source for that figure, so measure your own workload. It also points out that this helps where the packet filter doesn’t, such as traffic between containers on the same host. Keep the weight of the evidence in mind: it’s one person’s post, not vendor guidance, and the only track record it reports is “a handful” of hosts running it next to the nftables rule “with no problems so far” [5].

There’s also something none of the sources suggest, but that follows from the record. The second route to the bug only exists when net.ipv4.tcp_migrate_req is 1, or a BPF reuseport program migrates requests [1]. It’s off by default [3], so check that sysctl net.ipv4.tcp_migrate_req prints 0 and leave it that way until you’ve patched.

Taking the stopgaps out

Write down where you applied each one. After the host is running a fixed kernel:

bash
sudo nft delete table ip cve_2026_98432          # AlmaLinux rule
sudo nft delete table inet cve_2026_98432        # Red Hat rule
sudo grubby --update-kernel=ALL --remove-args="init_on_free=1"

If you want MPTCP back, delete /etc/sysctl.d/90-disable-mptcp.conf and set net.mptcp.enabled to 1 again. And if you didn’t need MPTCP in the first place, you might as well leave it off.

Share this article

Share this article

Sources

  1. CVE.org: CVE-2026-98432 record (Linux kernel CNA)12345678910111213141516171819202122
  2. Anthropic: discovering vulnerabilities with Claude12
  3. AlmaLinux: CVE-2026-98432 fixes and interim mitigations12345678910
  4. oss-security: Protecting against CVE-2026-98432 when using Go programs1234567
  5. oss-security: init_on_free=1 as an additional interim mitigation1234
  6. Debian security tracker: CVE-2026-984321
  7. Red Hat: CVE-2026-9843212
  8. Linux kernel: ipv4: free inet_opt and ireq_opt after an RCU grace period12
  9. oss-security: Re: Protecting against CVE-2026-98432 when using Go programs1
  10. Red Hat: RHSB-2026-016, kernel IPv4 options use-after-free123

Keyboard shortcuts

Close

Everywhere

Search tools and actions
Ctrl K
Show this help
?
Switch theme
Ctrl Shift L
Pin the selected tool (in search)
Ctrl P

On a tool

Send the output to the first suggestion
Ctrl ↵
Open a file into the input
Ctrl O
Copy the output
Ctrl Shift C
Close searchesc
toolsBase64 encoder and decoderEncode text to Base64 or decode Base64 back to text, with standard and URL-safe alphabets.EncodingJSON formatter and validatorFormat, minify and validate JSON, with the exact line and column of any error.FormattingRegex testerTest JavaScript regular expressions with live highlighting, capture groups and a plain-language explanation.TextUUID and ULID generatorGenerate UUID v4, UUID v7 and ULID in bulk, or paste an ID to see its version, variant and timestamp.GeneratorsUnix timestamp converterConvert Unix timestamps to dates and back, in any time zone, with ISO 8601, RFC 2822 and relative time.Date and timeHash and HMAC generatorCalculate MD5, SHA-1, SHA-2, SHA-3, BLAKE2, BLAKE3 and CRC-32 hashes of text or a file, with optional HMAC.SecurityPassword generator and strength checkerGenerate random passwords, passphrases and PINs, or check how strong a password is.SecurityJWT decoder and verifierDecode a JSON Web Token, check its expiry and claims, and verify its signature with a secret or public key.SecurityCIDR and IP subnet calculatorCalculate the range, mask and size of IPv4 and IPv6 networks, split them into subnets, and check what contains or overlaps what.NetworkCron expression explainerRead a cron expression in plain language, see its next runs in any time zone, and tidy it into its standard form.Date and timeSort linesSort lines of text A to Z or Z to A, in natural order (file2 before file10), by code point, by number or by length.TextRemove duplicate linesRemove duplicate lines from a list, keep the first or last copy, ignore case or spaces, or drop every line that repeats.TextCompare lists and textCompare two lists line by line: find what is in both, only in one, or in either, or see a marked line diff side by side.TextText case converter and slugifyConvert text to camelCase, snake_case, kebab-case, Title Case and more, or turn a title into a URL slug with accents handled.TextURL encode, decode and parsePercent-encode and decode text for URLs, or take a URL apart into scheme, host, path and query and edit the query as a table.EncodingValidators: IBAN, BSN, VAT, card and ISBNCheck an IBAN, Dutch BSN, credit card number, EU VAT number, email address, URL or ISBN, with the reason when it fails. One value per line.ValidationHTML entity encoder and decoderEscape text for HTML with named, decimal or hex entities, or turn entities such as & and &eacute; back into characters.EncodingEscape and unescape stringsEscape text for a JSON or JavaScript string, a SQL string, a CSV field, a regular expression or a shell word, or undo the escaping.EncodingEAN, UPC and GTIN check digitsCheck the check digit of an EAN-8, EAN-13, UPC-A or GTIN-14, add a missing one, read the country from the GS1 prefix and convert between ISBN-10 and EAN-13. One code per line.ValidationTest data generatorGenerate valid-looking test IBANs, BSNs, EAN barcodes and EU VAT numbers in bulk. They pass the checks, but belong to no one.GeneratorsSQL formatter and minifierFormat SQL so each clause sits on its own line, or minify it onto one line, for MySQL, PostgreSQL, SQLite, SQL Server and BigQuery.FormattingNumber base converterConvert numbers between binary, octal, decimal, hex and any base from 2 to 36, with big numbers, two's complement and a bit view.EncodingYAML, TOML and JSON converterConvert between JSON, YAML and TOML, or validate and pretty-print a document, with the line and column of every error.FormattingCSV and JSON converterConvert CSV to a JSON array of objects or arrays, or JSON back to CSV. Detects the delimiter and reads Dutch Excel files with semicolons and decimal commas.DataGlob and .gitignore testerTest glob patterns and .gitignore rules against a list of paths and see which paths match and which rule matched them.TextColour converterConvert a colour between HEX, RGB, HSL, HWB and OKLCH, check its WCAG contrast against a background, or build a Material 3 tonal palette from it.Formattingchmod calculatorConvert Unix file permissions between octal (755), letters (rwxr-xr-x), ls -l and chmod clauses, with a checkbox grid and the setuid, setgid and sticky bits.SecurityUser-agent parserRead a user-agent string: browser, engine, OS, device type and whether it is a bot. Leave it empty to read your own browser.NetworkConvert cURL to codePaste a cURL command and get the same request as fetch, Axios, Python requests, Go or PHP. Nothing is run and nothing is sent.NetworkMarkdown previewWrite Markdown and see it rendered as you type, or copy the HTML. Raw HTML is shown as text and links are checked, so nothing in the preview can run.FormattingBarcode and QR code generatorMake a QR code, EAN-13, UPC-A, Code 128, ITF-14 or Data Matrix barcode, download it as SVG or PNG, or read a code from an image.GeneratorspagesChangelogWhat changed in each releasepagesAboutWhat codeknife.dev is and who makes itpagesIdeasSuggest improvements and vote on other visitors’ ideas.pagesPrivacyWhat leaves your browser and what is storedpagesAccessibilityConformance, known limitations and how to report a problempagesMembershipRemove ads and raise the limitspagesactions