CVSS 8.1, Network-Rated, No Long-Term Fix: Is the Linux TCP Use-After-Free CVE-2026-99270 Reachable in Your Product?

Linux kernelTCPReachabilityProduct security

On 3 November 2026 the kernel’s CVE team published CVE-2026-99270, a use-after-free in the TCP stack. The score that comes with it is CVSS 8.1, AV:N/AC:H/PR:N: network, no privileges, and vulnerability aggregators already repeat it. The patch has been in mainline since May, but none of the current long-term kernels has it.

The first reaction in most product teams is “every box we ship speaks TCP”. That is true and it is the wrong place to stop. The vulnerable branch only runs when a server has opted into a specific listener-migration feature that is off by default, and that switch is not a Kconfig option but a runtime sysctl. This post covers what the bug is, which kernels really carry it (fewer than the CVE record says), who can actually reach it, and how to decide in a minute.

Developing story. We will update this article when the fix reaches the long-term branches. Last checked: 4 November 2026, against the releases of 30 October (5.15.224, 6.1.191, 6.6.161, 6.12.115, 6.18.60).
Update, 4 November: the fix is now queued in the stable queue for 5.15, 6.1, 6.6, 6.12 and 6.18. It ships with the next release of each branch once it passes review; until then, no long-term release contains it.

The facts

CVE-2026-99270
Componentreqsk_timer_handler() in net/ipv4/inet_connection_sock.c, used by TCP over IPv4 and IPv6
Bug classUse-after-free: a request socket is freed while its retransmit timer is still armed
Runtime gateTCP listener migration: net.ipv4.tcp_migrate_req=1 (default 0) or a BPF_SK_REUSEPORT_SELECT_OR_MIGRATE program on an SO_REUSEPORT group
Kconfig gateNone beyond CONFIG_INET; the migration code is always built (a kernel without both CONFIG_SYSCTL and CONFIG_BPF_SYSCALL has no way to switch it on)
Introduced bye8c526f2bdf1 “tcp/dccp: Don’t use timer_pending() in reqsk_queue_unlink()”, the October 2024 fix for CVE-2024-50154 (our analysis; the CVE record cites an older commit, see below)
Affected kernels5.15 from 5.15.170, 6.1 from 6.1.115, 6.6 from 6.6.59, 6.11 from 6.11.6, every 6.12 up to and including 7.0
Not affectedAnything before 5.14 (no migration code); 5.15 before .170, 6.1 before .115, 6.6 before .59; 6.7–6.10; 6.11 before .6; 7.1 and later
Fix97c8a3c1f73d “tcp: Fix potential UAF in reqsk_timer_handler().”, first released in 7.1; no Cc: stable tag
Long-term statusNot fixed in 5.15.224, 6.1.191, 6.6.161, 6.12.115 or 6.18.60 (30 October 2026). Queued for the next release of all five on 4 November 2026
CNA CVSS8.1 AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H; NVD status “Received”, no NVD analysis yet
ExploitNone public; not in CISA KEV

How the bug works

Between the SYN and the final ACK of a handshake, the kernel keeps a small request socket and a timer that retransmits the SYN+ACK. With SO_REUSEPORT, several listening sockets can share one port. When one of them is closed, its pending handshakes would normally be dropped. Listener migration, added in 5.14, lets the kernel move them to another listener in the group instead. One of the places where that happens is the retransmit timer: reqsk_timer_handler() sees that its listener is gone, clones the request socket for a live listener (nreq), arms the clone’s timer, and swaps the clone into the connection hash table.

If the swap fails, because another CPU processed the client’s ACK or a reset for the original request in the meantime, the handler frees the clone right away. Before freeing it has to cancel the clone’s timer, and that is the line that was wrong:

 		if (!inet_ehash_insert(req_to_sk(nreq), req_to_sk(oreq), NULL)) {
 			/* delete timer */
-			__inet_csk_reqsk_queue_drop(sk_listener, nreq, true);
+			__inet_csk_reqsk_queue_drop(sk_listener, nreq, false);
 			goto no_ownership;	/* frees nreq */
 		}

The last argument says “we are running inside this socket’s own timer, do not wait for it”. That is true for the original request, not for the clone. With true, the clone’s timer is never cancelled, the clone is freed, and when the timer fires later it runs on freed memory.

The gate is a sysctl, not a Kconfig option

Every kernel with networking builds this code; there is no CONFIG_* symbol that removes it. What decides whether the vulnerable branch can run is whether listener migration is switched on:

# Off by default. Must be 1 in the network namespace that owns the listener ...
sysctl net.ipv4.tcp_migrate_req          # net.ipv4.tcp_migrate_req = 0

# ... or a reuseport BPF program attached with the migrate attach type
bpftool prog show | grep -i sk_reuseport

On top of that, the server application has to use SO_REUSEPORT with at least two listeners on the same port, and close one of them while handshakes are still pending. That is the zero-downtime-restart pattern that the feature was built for. A product whose services do not use SO_REUSEPORT, or that never enables migration, never runs the vulnerable branch.

Network-rated ≠ remotely reachable. The CVSS vector describes the worst case: a remote client whose SYN and ACK packets are the attacker’s input. It does not say that the precondition (migration enabled, a listener being closed) is not under the attacker’s control. The AC:H in the vector is where that precondition is hidden. And because the gate is a sysctl, a tool that only reads your .config cannot see it.

Two attackers, two different answers

There is no module to autoload here: TCP is built in, and nothing about this bug changes when someone plugs in a device. The useful split is between an attacker on the network and an attacker who already runs code on the box.

Remote clientLocal, unprivileged user
What they controlThe SYN, ACK and RST packets of their own connectionsEverything: they can create their own user and network namespace, set tcp_migrate_req=1 inside it, open both listeners, close one, and send both sides of the handshake over loopback
What they need from youMigration enabled on a listening service, and that service closing a listener during their handshakesUnprivileged user namespaces allowed (CONFIG_USER_NS, CONFIG_NET_NS, and not disabled at runtime)
The raceA very short window between the server’s timer and the attacker’s ACK, timed across the networkSame window, but the attacker controls both sides on the same machine and can retry freely
Realistic outcomeA kernel crash if it hits. The attacker’s own SYNs can refill the freed memory, but code execution would also need a KASLR bypass from the network; we consider that unlikely in practice, and nothing public demonstrates itMemory corruption in the kernel; a privilege escalation is plausible but has not been shown publicly

The setting in your root network namespace does not protect against the local case: the attacker’s own namespace has its own copy of tcp_migrate_req, and root inside a user namespace may change it. For products with untrusted local code (containers, apps, multi-tenant hosts), the user-namespace policy is the real gate.

Is it a local root risk? Compared with exploited bugs of the same class

Race-condition use-after-frees are a recurring source of this year’s public Linux root exploits. To judge this one, we compared it with the race-condition UAFs in our exploit database that have a working public exploit, plus the earlier bug in the same function. The columns are the properties that decide how hard such a bug is to turn into root: where the freed memory lives, and what makes the kernel use it again. The score is our estimate of the exploit difficulty, from 1 (easy) to 5 (hard).

CVEFreed memoryUse triggered byExploitScore
CVE-2026-23272
nf_tables race
Generic kmalloc-cg-64; refilled from the same cacheAttacker’s syscalls and packetskernelCTF, probabilistic2 / 5
CVE-2026-31419
bonding race
Dedicated skbuff_head_cache; refilled from the same cacheAttacker’s syscallskernelCTF, retry loop3 / 5
CVE-2026-46242
Bad Epoll
Dangling struct file in a type-stable (SLAB_TYPESAFE_BY_RCU) cache; cross-cacheAttacker’s close() racekernelCTF + PoCs, ~99 %3 / 5
CVE-2024-26583
kernel TLS race
Generic kmalloc-512; cross-cacheAttacker’s recv()/close() racekernelCTF, “very unreliable”4 / 5
CVE-2024-50154
same function
Child socket behind req->sk, type-stable TCP socket cacheLeftover retransmit timerNone—
CVE-2026-99270
this bug
request_sock, type-stable dedicated cache; cross-cacheThe retransmit timer, on the kernel’s scheduleNone4–5 / 5

On access, the field is level: the two kernelCTF network exploits ran inside an unprivileged user namespace, Bad Epoll needs no special privileges, and this bug needs an unprivileged user namespace with listener migration switched on inside it. CVE-2024-50154 was reported through a privileged BPF tracing program.

Two properties put this bug at the hard end of the class. First, the freed request socket sits in a type-stable cache: freed slots go only to other request sockets, so the attacker has to free whole slab pages and win them back as different memory. That “cross-cache” step is what Bad Epoll and the TLS exploit had to solve as well. Second, in every exploited bug in the table the attacker chooses the moment the kernel touches the freed memory, with a syscall or a packet. Here the stale retransmit timer fires on the kernel’s own schedule, so the attacker has to have the memory in place before it fires rather than trigger the use when ready.

Our reading: a crash today, privilege escalation plausible but unproven. As of 4 November 2026, our exploit database, which tracks public proof-of-concept repositories and the kernelCTF submissions, has no exploit for CVE-2026-99270. Bad Epoll shows that a type-stable cache is no wall for a determined author, so treat products that let untrusted code create user namespaces as at risk. The difficulty scores are our own assessment from the public write-ups, not a measurement.

The timeline NVD won’t show yet

DateEvent
June 2021Listener migration added (c905dee62232), first released in 5.14. Not yet vulnerable
14 Oct 2024e8c526f2bdf1 fixes CVE-2024-50154 and introduces this bug; in 6.12-rc4
1 Nov 2024Backported to 5.15.170, 6.1.115, 6.6.59 and 6.11.6
6–8 May 2026Fix 97c8a3c1f73d written (reported by Damiano Melotti, Google) and applied to the networking tree, together with a second fix for the same error path
14 May 2026Merged into mainline; released in 7.1-rc4 (17 May) and 7.1 (14 June)
3 Nov 2026CVE-2026-99270 published by the kernel CNA, with CVSS 8.1
4 Nov 2026NVD status “Received”; no long-term release contains the fix. The only fixed kernels are 7.1 and later (stable 7.3.1, and 7.2.15 before it reached end of life)
4 Nov 2026Greg Kroah-Hartman queues the fix for 5.15, 6.1, 6.6, 6.12 and 6.18; it lands in the next release of each branch after review

Which kernels really have it

The CVE record marks everything from 4.1.11 and 4.2 onward as affected, because the fix’s Fixes: tag points at a 2015 commit. Reading the code gives a narrower range. Kernels before 5.14 have no migration code at all. From 5.14 until the October 2024 change, the clone’s timer was cancelled with a timer_pending() check, and since the handler had just armed that timer on the same CPU, the check always succeeded. The bug appears when e8c526f2bdf1 replaced that check with the true/false argument. The second patch in the same series supports this: it fixes the same migration-failure path and carries Fixes: e8c526f2bdf1. This range is our own analysis; expect scanners that follow the CVE record to flag older kernels too.

Was the risk visible at merge?

The commit message says “UAF” in the subject, so it was not a silent fix. But it carries no Cc: stable tag, which is the usual signal for the stable maintainers to pick a patch up. Six months later, 5.15, 6.1, 6.6, 6.12 and 6.18 still ship the bug, and 7.0 reached end of life without the fix. The day after the CVE was published, the fix was queued for all five long-term branches, so the gap should close with their next releases. 7.1 and later releases have it. The fix is a one-line change and applies to the long-term branches without conflicts, so product teams that cannot wait can carry it themselves.

What the distributions say

The distributions follow the long-term branches, so their kernels have the same gap. Their own ratings are notably lower than the CNA’s 8.1 (status of their trackers on 4 November 2026):

DistributionStatusTheir rating
Debian12 “bookworm” (6.1.190) and 13 “trixie” (6.12.115): vulnerable. Fixed only in testing and unstable (7.2)—
UbuntuNeeds evaluation, every release from 14.04 to 26.10Priority medium
SUSEPending. Affected: SLES 15 SP7 and SLED 15 SP4, SP5, SP7; older service packs listed as not affected7.0 AV:L/AC:H/PR:L, “important”
Red HatBugzilla entry open, no CVE page or product status yetMedium, titled “Denial of Service via use-after-free”

SUSE scores it as a local attack and Red Hat as a denial of service, which matches the threat-model split above more closely than the CNA’s network score.

What your tools will show

NVD has not analysed the CVE yet, so there is no NVD score and no CPE version range. Dependency-Track and other NVD-fed scanners will either show it unscored or not match it to your kernel until that analysis lands. When it does, the range will likely follow the CVE record and start at 4.1.11.

30-second checklist

  1. Is your kernel version in the affected range? Before 5.14, 5.15 before .170, 6.1 before .115, 6.6 before .59, 6.7–6.10, 6.11 before .6, or 7.1 and later: not_affected, justification code_not_present. Otherwise continue.
  2. Can untrusted local code create user namespaces? If your product runs third-party apps, containers or other users’ code, and unprivileged user namespaces are allowed, treat it as affected: carry the fix, or disable unprivileged user namespaces (user.max_user_namespaces=0, or your distribution’s switch). If there is no untrusted local code, or user namespaces are not available to it, continue.
  3. Does any listening service enable migration? Check net.ipv4.tcp_migrate_req in every network namespace that has listeners, and whether any service attaches a reuseport BPF program. If migration is off everywhere: not_affected, justification requires_configuration. If it is on, a remote client can reach the bug while that service restarts: patch, or turn migration off until you can.

The score is not the answer

An 8.1 network score on a TCP bug sounds like every product is exposed. For most of them the honest answer is that the vulnerable branch never runs, and for the rest it depends on who can run code on the box. Upload your .config to KernelScan and every kernel CVE is checked against your exact kernel version, the long-term backports and the options your product builds. On paid plans, the security factors you declare for a product, such as whether it runs untrusted code, feed into the assessment of each CVE.

References: CVE record · NVD · fix 97c8a3c1f73d and its sibling 7eca3292cac7 · introducing commit e8c526f2bdf1 · ip-sysctl documentation (tcp_migrate_req) · distribution trackers as linked in the table above. The affected range, the per-branch first vulnerable releases, the unfixed status of the long-term releases (checked in the source of each release tag), the threat-model split and the exploitability assessment are our own analysis as of 4 November 2026.