Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

udp: Fix wildcard bind conflict check when using hash2

When binding a udp_sock to a local address and port, UDP uses
two hashes (udptable->hash and udptable->hash2) for collision
detection. The current code switches to "hash2" when
hslot->count > 10.

"hash2" is keyed by local address and local port.
"hash" is keyed by local port only.

The issue can be shown in the following bind sequence (pseudo code):

bind(fd1, "[fd00::1]:8888")
bind(fd2, "[fd00::2]:8888")
bind(fd3, "[fd00::3]:8888")
bind(fd4, "[fd00::4]:8888")
bind(fd5, "[fd00::5]:8888")
bind(fd6, "[fd00::6]:8888")
bind(fd7, "[fd00::7]:8888")
bind(fd8, "[fd00::8]:8888")
bind(fd9, "[fd00::9]:8888")
bind(fd10, "[fd00::10]:8888")

/* Correctly return -EADDRINUSE because "hash" is used
* instead of "hash2". udp_lib_lport_inuse() detects the
* conflict.
*/
bind(fail_fd, "[::]:8888")

/* After one more socket is bound to "[fd00::11]:8888",
* hslot->count exceeds 10 and "hash2" is used instead.
*/
bind(fd11, "[fd00::11]:8888")
bind(fail_fd, "[::]:8888") /* succeeds unexpectedly */

The same issue applies to the IPv4 wildcard address "0.0.0.0"
and the IPv4-mapped wildcard address "::ffff:0.0.0.0". For
example, if there are existing sockets bound to
"192.168.1.[1-11]:8888", then binding "0.0.0.0:8888" or
"[::ffff:0.0.0.0]:8888" can also miss the conflict when
hslot->count > 10.

TCP inet_csk_get_port() already has the correct check in
inet_use_bhash2_on_bind(). Rename it to
inet_use_hash2_on_bind() and move it to inet_hashtables.h
so udp.c can reuse it in this fix.

Fixes: 30fff9231fad ("udp: bind() optimisation")
Reported-by: Andrew Onyshchuk <oandrew@meta.com>
Signed-off-by: Martin KaFai Lau <martin.lau@kernel.org>
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
Link: https://patch.msgid.link/20260319181817.1901357-1-martin.lau@linux.dev
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Martin KaFai Lau and committed by
Jakub Kicinski
e537dd15 3f0f591b

+18 -18
+14
include/net/inet_hashtables.h
··· 264 264 return &hinfo->bhash2[hash & (hinfo->bhash_size - 1)]; 265 265 } 266 266 267 + static inline bool inet_use_hash2_on_bind(const struct sock *sk) 268 + { 269 + #if IS_ENABLED(CONFIG_IPV6) 270 + if (sk->sk_family == AF_INET6) { 271 + if (ipv6_addr_any(&sk->sk_v6_rcv_saddr)) 272 + return false; 273 + 274 + if (!ipv6_addr_v4mapped(&sk->sk_v6_rcv_saddr)) 275 + return true; 276 + } 277 + #endif 278 + return sk->sk_rcv_saddr != htonl(INADDR_ANY); 279 + } 280 + 267 281 struct inet_bind_hashbucket * 268 282 inet_bhash2_addr_any_hashbucket(const struct sock *sk, const struct net *net, int port); 269 283
+3 -17
net/ipv4/inet_connection_sock.c
··· 154 154 } 155 155 EXPORT_SYMBOL(inet_sk_get_local_port_range); 156 156 157 - static bool inet_use_bhash2_on_bind(const struct sock *sk) 158 - { 159 - #if IS_ENABLED(CONFIG_IPV6) 160 - if (sk->sk_family == AF_INET6) { 161 - if (ipv6_addr_any(&sk->sk_v6_rcv_saddr)) 162 - return false; 163 - 164 - if (!ipv6_addr_v4mapped(&sk->sk_v6_rcv_saddr)) 165 - return true; 166 - } 167 - #endif 168 - return sk->sk_rcv_saddr != htonl(INADDR_ANY); 169 - } 170 - 171 157 static bool inet_bind_conflict(const struct sock *sk, struct sock *sk2, 172 158 kuid_t uid, bool relax, 173 159 bool reuseport_cb_ok, bool reuseport_ok) ··· 245 259 * checks separately because their spinlocks have to be acquired/released 246 260 * independently of each other, to prevent possible deadlocks 247 261 */ 248 - if (inet_use_bhash2_on_bind(sk)) 262 + if (inet_use_hash2_on_bind(sk)) 249 263 return tb2 && inet_bhash2_conflict(sk, tb2, uid, relax, 250 264 reuseport_cb_ok, reuseport_ok); 251 265 ··· 362 376 head = &hinfo->bhash[inet_bhashfn(net, port, 363 377 hinfo->bhash_size)]; 364 378 spin_lock_bh(&head->lock); 365 - if (inet_use_bhash2_on_bind(sk)) { 379 + if (inet_use_hash2_on_bind(sk)) { 366 380 if (inet_bhash2_addr_any_conflict(sk, port, l3mdev, relax, false)) 367 381 goto next_port; 368 382 } ··· 548 562 check_bind_conflict = false; 549 563 } 550 564 551 - if (check_bind_conflict && inet_use_bhash2_on_bind(sk)) { 565 + if (check_bind_conflict && inet_use_hash2_on_bind(sk)) { 552 566 if (inet_bhash2_addr_any_conflict(sk, port, l3mdev, true, true)) 553 567 goto fail_unlock; 554 568 }
+1 -1
net/ipv4/udp.c
··· 287 287 } else { 288 288 hslot = udp_hashslot(udptable, net, snum); 289 289 spin_lock_bh(&hslot->lock); 290 - if (hslot->count > 10) { 290 + if (inet_use_hash2_on_bind(sk) && hslot->count > 10) { 291 291 int exist; 292 292 unsigned int slot2 = udp_sk(sk)->udp_portaddr_hash ^ snum; 293 293