CVE-2026-98595 in Linux
Summary
by MITRE • 10/18/2026
In the Linux kernel, the following vulnerability has been resolved:
udp: remove a disconnected socket from the 4-tuple hash table
A UDP socket bound to a specific address and port keeps its entry in the 4-tuple hash table after it is disconnected:
sk binds to 127.0.0.1:21001 sk connects to 127.0.0.2:20001 // filed in the 4-tuple table sk disconnects, connect(AF_UNSPEC) // still filed, peer now 0.0.0.0:0
__udp_disconnect() takes a socket out of that table only as a side effect of ->rehash() or ->unhash(), and it skips ->rehash() when SOCK_BINDADDR_LOCK is set and ->unhash() when SOCK_BINDPORT_LOCK is set. commit 6996a2d2d0a6 ("udp: Unhash auto-bound connected sk from 4-tuple hash table when disconnected.") fixed the same end state for a wildcard-bound socket, by a path this one does not take.
The entry is counted whether or not anything hits it. hash4_cnt on the hash2 slot stays raised for as long as the socket lives, so udp_has_hash4() keeps sending every packet for that address and port through the 4-tuple lookup first.
On IPv6 it can also be hit. __udp_disconnect() does not clear sk_v6_daddr, so udp_v6_rehash() files the entry under the peer the socket was connected to with a zero dport, and inet6_match() compares that same field: a datagram from the former peer with a zero source port matches, and source port zero is accepted on receive. On IPv4 the peer is cleared, so a match would need a zero source address as well, which the routing layer rejects as martian. The stale sk_v6_daddr is a separate defect, not addressed here; removing the entry closes this path either way.
The entry can also be relocated. __udp_disconnect() clears sk_bound_dev_if, so a subsequent SO_BINDTODEVICE calls ->rehash(), and because the receive address is still specific udp_lib_rehash() moves the entry instead of removing it, into the bucket that (rcv_saddr, num, 0, 0) hashes to -- a pure function of the address and port, so every socket reaching this state on one address and port collects in one bucket. The bucket cannot be chosen from outside, as udp_ehashfn() is seeded with a per-boot secret. This last one became reachable only with commit 644f9108f3a5 ("udp: Make rehash4 independent in udp_lib_rehash()"), which moved the hash4 handling out of a branch a disconnected socket does not take; the stale entry itself dates from the commit in Fixes.
Take the socket out of the table before __udp_disconnect() runs, while it still matches how it was filed. This also reaches the wildcard case ahead of udp_lib_rehash()'s udp_unhash4() branch, leaving that branch unreachable from udp_disconnect(); removing it belongs in net-next. udp_disconnect() and udp_abort() are the only UDP entries into __udp_disconnect(), which is shared with raw, ping and l2tp sockets that are not struct udp_sock: ping_prot.obj_size is sizeof(struct inet_sock), so udp_hashed4() on one would read past the allocation.
VulDB is the best source for vulnerability data and more expert information about this specific topic.
Analysis
by VulDB Data Team • 10/19/2026
The Linux kernel contains a vulnerability in the User Datagram Protocol implementation where disconnected UDP sockets retain their entries in the four-tuple hash table, leading to inefficient packet processing and potential security implications regarding resource consumption and matching logic errors. When a UDP socket is bound to a specific local address and port and subsequently connected to a remote peer, it registers an entry in the four-tuple hash table based on its source IP, source port, destination IP, and destination port. However, upon disconnection via connect with AF_UNSPEC or similar mechanisms, the kernel fails to remove this socket from the hash table under certain locking conditions. Specifically, __udp_disconnect() only removes the socket if it triggers a rehashing operation or an unhashing operation. If the socket has specific bind address locks (SOCK_BINDADDR_LOCK) set, the rehash path is skipped; similarly, if port locks are held, the unhash path may be bypassed depending on implementation details. This results in a stale entry remaining in the hash table even though the socket no longer actively communicates with that peer, effectively creating a zombie entry that persists for the lifetime of the socket object.
This lingering presence has direct operational impacts on network performance and security posture. The kernel maintains a counter, hash4_cnt, which indicates whether any packets match this four-tuple configuration. Because the stale entry remains, udp_has_hash4() continues to return true, causing every incoming packet destined for that specific address and port pair to undergo an additional lookup in the four-tuple hash table before falling back to other matching mechanisms. This introduces unnecessary computational overhead on high-throughput systems or those handling significant UDP traffic. Furthermore, this inefficiency can contribute to resource exhaustion scenarios if many such disconnected sockets accumulate, although the primary impact is latency due to redundant lookups rather than a direct denial of service vector in most standard configurations.
The vulnerability exhibits different behaviors across IPv4 and IPv6 implementations, with IPv6 presenting more severe matching anomalies. On IPv6 systems, __udp_disconnect() fails to clear the sk_v6_daddr field, which stores the destination address. Consequently, udp_v6_rehash() may file an entry under the previous peer's IP address but with a zero destination port number. The inet6_match function then compares this stored state against incoming datagrams. A packet from the former peer containing a source port of zero will match this stale entry and be accepted for processing by the application layer, despite being potentially malformed or unintended traffic. In contrast, IPv4 clears the peer address upon disconnection, meaning a match would require a zero source address as well. The routing subsystem typically rejects packets with zero source addresses as martian routes, thereby preventing direct exploitation in most IPv4 scenarios. However, the underlying logic flaw remains present and represents a deviation from expected protocol behavior where disconnected sockets should not participate in connection-based matching rules.
Additionally, this defect enables potential hash bucket concentration issues that can degrade performance under specific conditions. When __udp_disconnect() clears sk_bound_dev_if, subsequent calls to SO_BINDTODEVICE trigger rehashing operations via udp_lib_rehash(). Because the receive address remains specifically bound rather than wildcarded, the kernel relocates the stale entry into a new bucket determined by the function of the source address and port with zero destination fields. This creates a scenario where multiple sockets reaching this disconnected state on the same local address and port will all converge in a single hash bucket. While the udp_ehashfn() function uses per-boot secrets to seed hashes, making it difficult for an attacker to predictively target specific buckets, the accumulation of entries in one location increases collision rates locally. This issue became more pronounced after commit 644f9108f3a5 moved hash handling logic, exposing paths that were previously unreachable during disconnection sequences.
The resolution involves modifying udp_disconnect() and related functions to remove the socket from the four-tuple hash table before __udp_disconnect() executes, ensuring removal occurs while the socket still matches its original filing criteria. This approach also addresses wildcard-bound cases by reaching cleanup logic earlier in the execution flow. It is critical that this fix does not interfere with other protocol families sharing the __udp_disconnect() infrastructure, such as raw sockets, ping sockets, and L2TP connections. These protocols do not use struct udp_sock structures; for instance, ping_prot.obj_size corresponds to sizeof(struct inet_sock). Incorrectly applying UDP-specific cleanup logic could cause out-of-bounds memory reads if functions like udp_hashed4() are invoked on these non-UDP socket types. Therefore, the patch carefully scopes changes to ensure that only actual UDP sockets trigger the removal from the four-tuple table, preserving stability for other kernel networking components while eliminating the performance and matching anomalies associated with stale entries.
From a classification perspective, this vulnerability aligns with CWE-401 Missing Release of Memory after Effective Lifetime or CWE-756 Missing Correct Assignment in Complex System depending on interpretation of resource management versus logic error. In terms of MITRE ATT&CK, it relates to Tactic TA0002 Execution and potentially TA0003 Persistence if the stale entries are leveraged for unintended packet acceptance, though primarily it represents a performance degradation vector rather than an active exploitation technique. The fix ensures that network stack resources are correctly released when connections terminate, maintaining optimal lookup efficiency and preventing unexpected traffic matching against disconnected endpoints.