Re: [PATCH net] sctp: hold socket lock when dumping endpoints in sctp_diag
Xin Long <[email protected]> Mon, 15 Jun 2026 15:13:41 -0400
| Newsgroups | org.kernel.vger.linux-sctp,org.kernel.vger.netdev |
|---|---|
| Message-ID | <CADvbK_dt0ripQmj6JrLDBmdpxZPSOcs1zuuNqJ39AnjjTi1JsA@mail.gmail.com> |
On Sat, Jun 13, 2026 at 3:10 AM Willy Tarreau <[email protected]> wrote: > > Hi, > > On Fri, Jun 12, 2026 at 01:59:38PM -0400, Xin Long wrote: > > SCTP_DIAG endpoint dumping currently walks the endpoint hash table > > without taking the socket lock before calling inet_sctp_diag_fill(). > > > > This is problematic because inet_sctp_diag_fill() eventually calls > > inet_diag_msg_sctpladdrs_fill(), which traverses the endpoint's local > > address list twice: once to count entries for nla_reserve(), and once > > again to copy the addresses into the netlink buffer. > > > > Since these two traversals are protected only by separate RCU read-side > > critical sections, concurrent socket operations such as > > SCTP_SOCKOPT_BINDX_REM may remove entries from the address list between > > them. In that case, the number of copied addresses becomes smaller than > > the originally reserved buffer size, leaving part of the netlink payload > > uninitialized and potentially leaking kernel memory to user space. > > > > Fix this by changing sctp_for_each_endpoint() to iterate with net and > > position awareness while taking a reference on each socket, then release > > the endpoint hash bucket read_lock_bh() before invoking the callback. > > > > A socket reference is required because the callback acquires lock_sock(), > > which must be called outside of read_lock_bh() since lock_sock() may > > sleep. Holding a socket reference ensures the socket remains valid after > > dropping the bucket lock and before acquiring the socket lock. > > > > With the socket lock held, concurrent bind-address modifications are > > serialized against the diagnostic dump, ensuring the local address list > > remains stable during buffer sizing and initialization. > > > > This also simplifies endpoint traversal by removing the temporary > > callback local position tracking args[4] and moving dump progress > > tracking into sctp_for_each_endpoint() itself. > > > > While at it, fix the idiag_states check in sctp_ep_dump() and skip ep > > dumping when non LISTEN|CLOSE states are also requested and the ep has > > assocs, since such cases will be handled later by sctp_sock_dump(). > > > > Reported-by: Zero Day Initiative <[email protected]> > > Please note that the original report suggested this reporter: > > Nico Yip (@_cyeaa_) working with TrendAI Zero Day Initiative > I could find a public email for Nico Yip (@_cyeaa_), and I will add this into the changelog as: This issue was reported by Nico Yip (@_cyeaa_) working with TrendAI Zero Day Initiative. Thanks. > > Fixes: 8f840e47f190 ("sctp: add the sctp_diag.c file") > > Signed-off-by: Xin Long <[email protected]> > > --- > > include/net/sctp/sctp.h | 3 +- > > net/sctp/diag.c | 62 +++++++++++++++++++---------------------- > > net/sctp/socket.c | 34 +++++++++++++++++----- > > 3 files changed, 57 insertions(+), 42 deletions(-) > > > > diff --git a/include/net/sctp/sctp.h b/include/net/sctp/sctp.h > > index 58242b37b47a..cd82b05354a3 100644 > > --- a/include/net/sctp/sctp.h > > +++ b/include/net/sctp/sctp.h > > @@ -111,7 +111,8 @@ int sctp_transport_lookup_process(sctp_callback_t cb, struct net *net, > > const union sctp_addr *paddr, void *p, int dif); > > int sctp_transport_traverse_process(sctp_callback_t cb, sctp_callback_t cb_done, > > struct net *net, int *pos, void *p); > > -int sctp_for_each_endpoint(int (*cb)(struct sctp_endpoint *, void *), void *p); > > +int sctp_for_each_endpoint(int (*cb)(struct sctp_endpoint *, void *), > > + struct net *net, int *pos, void *p); > > int sctp_get_sctp_info(struct sock *sk, struct sctp_association *asoc, > > struct sctp_info *info); > > > > diff --git a/net/sctp/diag.c b/net/sctp/diag.c > > index d758f5c3e06e..9108272ca527 100644 > > --- a/net/sctp/diag.c > > +++ b/net/sctp/diag.c > > @@ -92,6 +92,7 @@ static int inet_diag_msg_sctpladdrs_fill(struct sk_buff *skb, > > if (!--addrcnt) > > break; > > } > > + WARN_ON_ONCE(addrcnt); > > rcu_read_unlock(); > > > > return 0; > > @@ -373,42 +374,36 @@ static int sctp_ep_dump(struct sctp_endpoint *ep, void *p) > > struct sk_buff *skb = commp->skb; > > struct netlink_callback *cb = commp->cb; > > const struct inet_diag_req_v2 *r = commp->r; > > - struct net *net = sock_net(skb->sk); > > struct inet_sock *inet = inet_sk(sk); > > int err = 0; > > > > - if (!net_eq(sock_net(sk), net)) > > + lock_sock(sk); > > + if (sctp_sstate(sk, CLOSED)) > > goto out; > > > > - if (cb->args[4] < cb->args[1]) > > - goto next; > > - > > - if (!(r->idiag_states & TCPF_LISTEN) && !list_empty(&ep->asocs)) > > - goto next; > > + if ((r->idiag_states & ~(TCPF_LISTEN | TCPF_CLOSE)) && > > + !list_empty(&ep->asocs)) > > + goto out; > > > > if (r->sdiag_family != AF_UNSPEC && > > sk->sk_family != r->sdiag_family) > > - goto next; > > + goto out; > > > > if (r->id.idiag_sport != inet->inet_sport && > > r->id.idiag_sport) > > - goto next; > > + goto out; > > > > if (r->id.idiag_dport != inet->inet_dport && > > r->id.idiag_dport) > > - goto next; > > - > > - if (inet_sctp_diag_fill(sk, NULL, skb, r, > > - sk_user_ns(NETLINK_CB(cb->skb).sk), > > - NETLINK_CB(cb->skb).portid, > > - cb->nlh->nlmsg_seq, NLM_F_MULTI, > > - cb->nlh, commp->net_admin) < 0) { > > - err = 2; > > goto out; > > - } > > -next: > > - cb->args[4]++; > > + > > + err = inet_sctp_diag_fill(sk, NULL, skb, r, > > + sk_user_ns(NETLINK_CB(cb->skb).sk), > > + NETLINK_CB(cb->skb).portid, > > + cb->nlh->nlmsg_seq, NLM_F_MULTI, > > + cb->nlh, commp->net_admin); > > out: > > + release_sock(sk); > > return err; > > } > > > > @@ -479,41 +474,40 @@ static void sctp_diag_dump(struct sk_buff *skb, struct netlink_callback *cb, > > .r = r, > > .net_admin = netlink_net_capable(cb->skb, CAP_NET_ADMIN), > > }; > > - int pos = cb->args[2]; > > + int pos; > > > > /* eps hashtable dumps > > * args: > > * 0 : if it will traversal listen sock > > * 1 : to record the sock pos of this time's traversal > > - * 4 : to work as a temporary variable to traversal list > > */ > > if (cb->args[0] == 0) { > > - if (!(idiag_states & TCPF_LISTEN)) > > - goto skip; > > - if (sctp_for_each_endpoint(sctp_ep_dump, &commp)) > > - goto done; > > -skip: > > + if (idiag_states & TCPF_LISTEN) { > > + pos = cb->args[1]; > > + if (sctp_for_each_endpoint(sctp_ep_dump, net, &pos, > > + &commp)) { > > + cb->args[1] = pos; > > + return; > > + } > > + } > > cb->args[0] = 1; > > cb->args[1] = 0; > > - cb->args[4] = 0; > > } > > > > + if (!(idiag_states & ~(TCPF_LISTEN | TCPF_CLOSE))) > > + return; > > + > > /* asocs by transport hashtable dump > > * args: > > * 1 : to record the assoc pos of this time's traversal > > * 2 : to record the transport pos of this time's traversal > > * 3 : to mark if we have dumped the ep info of the current asoc > > * 4 : to work as a temporary variable to traversal list > > - * 5 : to save the sk we get from travelsing the tsp list. > > */ > > - if (!(idiag_states & ~(TCPF_LISTEN | TCPF_CLOSE))) > > - goto done; > > - > > + pos = cb->args[2]; > > sctp_transport_traverse_process(sctp_sock_filter, sctp_sock_dump, > > net, &pos, &commp); > > cb->args[2] = pos; > > - > > -done: > > cb->args[1] = cb->args[4]; > > cb->args[4] = 0; > > } > > diff --git a/net/sctp/socket.c b/net/sctp/socket.c > > index 66e12fb0c646..1ed405dedc01 100644 > > --- a/net/sctp/socket.c > > +++ b/net/sctp/socket.c > > @@ -5369,24 +5369,44 @@ struct sctp_transport *sctp_transport_get_idx(struct net *net, > > } > > > > int sctp_for_each_endpoint(int (*cb)(struct sctp_endpoint *, void *), > > - void *p) { > > - int err = 0; > > - int hash = 0; > > - struct sctp_endpoint *ep; > > + struct net *net, int *pos, void *p) { > > + int err, hash = 0, idx = 0, start; > > struct sctp_hashbucket *head; > > + struct sctp_endpoint *ep; > > + struct sock *sk; > > > > for (head = sctp_ep_hashtable; hash < sctp_ep_hashsize; > > hash++, head++) { > > + start = idx; > > +again: > > + sk = NULL; > > read_lock_bh(&head->lock); > > sctp_for_each_hentry(ep, &head->chain) { > > - err = cb(ep, p); > > - if (err) > > + if (sock_net(ep->base.sk) != net) > > + continue; > > + if (idx++ >= *pos) { > > + sk = ep->base.sk; > > + sock_hold(sk); > > break; > > + } > > } > > read_unlock_bh(&head->lock); > > + > > + if (sk) { > > + err = cb(ep, p); > > + if (err) { > > + sock_put(sk); > > + return err; > > + } > > + sock_put(sk); > > + (*pos)++; > > + > > + idx = start; > > + goto again; > > + } > > } > > > > - return err; > > + return 0; > > } > > EXPORT_SYMBOL_GPL(sctp_for_each_endpoint); > > > > -- > > 2.47.1