Thread (2 messages) flat view 2 messages, 1 author, 20h ago
DORMANTno replies

[PATCH net v2 1/1] udp: diag: bound bucket lock hold time

From: Zihan Xi <hidden>
Date: 2026-09-06 10:35:44
Also in: lkml, stable
Subsystem: networking [general], the rest · Maintainers: "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds

udp_diag_dump() currently keeps the UDP hash bucket spinlock held while
running the request's bytecode filter and filling a netlink response for
every socket in the bucket.  A large filter and a heavily populated bucket
can therefore keep bottom halves disabled for an attacker-scaled amount of
time.

Collect at most SKARR_SZ matching sockets under the bucket lock, taking a
reference for each socket, then run the filter and fill the response after
releasing the lock.  Resume with the existing (slot, s_num) dump state so
the next batch can skip already-walked sockets.  Leave bytecode filtering
until after unlock; unlike tcp_diag, a rejecting inet_diag bytecode program
is the expensive part of this walk.

Fixes: b6d640c2286d ("udp_diag: Implement the dump-all functionality")
Cc: stable@vger.kernel.org
Reported-by: Vega <redacted>
Assisted-by: LLM
Signed-off-by: Zihan Xi <redacted>
---
changes in v2:
  - Drop the hash-list cursor, dump_done callback, extra module
    reference, and inet_diag core changes.  Dump state is only
    (slot, s_num), so the existing handler get/put around dump() is
    enough and udp_diag no longer manages its own module lifetime.
  - Batch at most SKARR_SZ matching sockets under the bucket lock
    like tcp_diag, and run bytecode filtering plus netlink fill after
    unlock.
  - v1 Link: https://lore.kernel.org/all/133b6aee9e2c908c9da37d5585b3d2cd016906cd.1788187473.git.zihanx@nebusec.ai (local)

 net/ipv4/udp_diag.c | 41 ++++++++++++++++++++++++++++++++++++-----
 1 file changed, 36 insertions(+), 5 deletions(-)
diff --git a/net/ipv4/udp_diag.c b/net/ipv4/udp_diag.c
index f4b24e628cf8d..f049204abdf85 100644
--- a/net/ipv4/udp_diag.c
+++ b/net/ipv4/udp_diag.c
@@ -86,6 +86,11 @@ static int udp_diag_dump_one(struct netlink_callback *cb,
 	return err;
 }
 
+/* Process a maximum of SKARR_SZ sockets at a time when walking hash buckets
+ * with bh disabled.
+ */
+#define SKARR_SZ 16
+
 static void udp_diag_dump(struct sk_buff *skb, struct netlink_callback *cb,
 			  const struct inet_diag_req_v2 *r)
 {
@@ -100,13 +105,18 @@ static void udp_diag_dump(struct sk_buff *skb, struct netlink_callback *cb,
 
 	for (slot = s_slot; slot <= table->mask; s_num = 0, slot++) {
 		struct udp_hslot *hslot = &table->hash[slot];
-		struct sock *sk;
+		struct sock *sk_arr[SKARR_SZ], *sk;
+		int num_arr[SKARR_SZ];
+		int idx, accum, res;
 
 		num = 0;
 
 		if (hlist_empty(&hslot->head))
 			continue;
 
+resume_walk:
+		num = 0;
+		accum = 0;
 		spin_lock_bh(&hslot->lock);
 		sk_for_each(sk, &hslot->head) {
 			struct inet_sock *inet = inet_sk(sk);
@@ -127,14 +137,35 @@ static void udp_diag_dump(struct sk_buff *skb, struct netlink_callback *cb,
 			    r->id.idiag_dport)
 				goto next;
 
-			if (sk_diag_dump(sk, skb, cb, r, net_admin) < 0) {
-				spin_unlock_bh(&hslot->lock);
-				goto done;
-			}
+			sock_hold(sk);
+			num_arr[accum] = num;
+			sk_arr[accum] = sk;
+			if (++accum == SKARR_SZ)
+				break;
 next:
 			num++;
 		}
 		spin_unlock_bh(&hslot->lock);
+
+		res = 0;
+		for (idx = 0; idx < accum; idx++) {
+			if (res >= 0) {
+				res = sk_diag_dump(sk_arr[idx], skb, cb, r,
+						   net_admin);
+				if (res < 0)
+					num = num_arr[idx];
+			}
+			sock_put(sk_arr[idx]);
+		}
+		if (res < 0)
+			goto done;
+
+		cond_resched();
+
+		if (accum == SKARR_SZ) {
+			s_num = num + 1;
+			goto resume_walk;
+		}
 	}
 done:
 	cb->args[0] = slot;
-- 
2.43.0
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help