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 Assisted-by: LLM Signed-off-by: Zihan Xi --- 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 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