mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Xin Long <lucien.xin@gmail.com>
To: netdev-bot+sashiko@kernel.org
Cc: 18983559317@163.com, marcelo.leitner@gmail.com,
	davem@davemloft.net,  edumazet@google.com, kuba@kernel.org,
	pabeni@redhat.com, horms@kernel.org,  linux-sctp@vger.kernel.org,
	netdev@vger.kernel.org,  linux-kernel@vger.kernel.org
Subject: Re: [PATCH v2] sctp: fix race between addr_wq timer handler and backlog draining
Date: Thu, 12 Nov 2026 17:30:36 -0500	[thread overview]
Message-ID: <CADvbK_fQBb2FTBOFiQ_4nmSGG7aQspD4X03bhENKb74prxorCw@mail.gmail.com> (raw)
In-Reply-To: <179438669316.2199510.365044781249431849@kernel.org>

On Wed, Nov 11, 2026 at 3:44 AM <netdev-bot+sashiko@kernel.org> wrote:
>
> Thank you for your contribution! Sashiko AI review found 5 potential
> issue(s) to consider.
>
> Critical: 0 · High: 1 · Medium: 3 · Low: 1
>
> If you believe none of the issues are legitimate, please reply to each
> issue of Medium severity or higher and explain why the AI is wrong.
> If any of them are legitimate and you plan to respin, please let patchwork
> know by including "pw-bot: cr" as a separate line at the end of your reply
> (one such reply per series is enough).
>
> - [High] Kernel memory can grow without limit through repeated DEL_IP
>   ASCONF chunks.
> - [Medium] The new early exit breaks an invariant that
>   sctp_addr_wq_mgmt() depends on.
> - [Medium] One socket can now stall address-event processing for the
>   whole netns.
> - [Medium] The commit message says that sockets processed earlier 'will
>   see a duplicate ASCONF on retry; the peer reports that in an ASCONF-…
> - [Low] The new sock_owned_by_user() deferral branch in
>   sctp_addr_wq_timeout_handler() (net/sctp/protocol.c) sets retry=true…
>
> --
>
> sctp: fix race between addr_wq timer handler and backlog draining
>
> This patch makes sctp_addr_wq_timeout_handler() check sock_owned_by_user()
> on each auto-asconf socket before it calls sctp_asconf_mgmt(). If the
> socket is owned, processing stops and the addr_waitq entry stays queued.
> The addr_wq_timer is then re-armed to retry after SCTP_ADDRESS_TICK_DELAY,
> in a single mod_timer() shared with the existing IPv6 DAD retry.
>
> > Sockets processed earlier for the same
> > address event will see a duplicate ASCONF on retry; the peer reports
> > that in an ASCONF-ACK error indication, which the association
> > tolerates.
>
> [Severity: Medium]
> Is this description accurate for both event types?
>
> For an SCTP_ADDR_NEW entry, the first pass through
> sctp_send_asconf_add_ip() has already added the address with
> sctp_add_bind_addr(..., SCTP_ADDR_NEW). On retry the lookup finds it and
> skips the association, so no duplicate is sent:
>
> net/sctp/socket.c:sctp_send_asconf_add_ip() {
>     ...
>                         if (sctp_assoc_lookup_laddr(asoc, addr))
>                                 break;
>     ...
>                 if (i < addrcnt)
>                         continue;
>     ...
> }
>
> For an SCTP_ADDR_DEL entry, the first pass in sctp_send_asconf_del_ip()
> only marks the bind address:
>
>                                 if (sctp_cmp_addr_exact(&saddr->a, laddr))
>                                         saddr->state = SCTP_ADDR_DEL;
>
> The entry stays valid until the ASCONF-ACK arrives. sctp_bind_addr_match()
> only tests ->valid, so the !sctp_assoc_lookup_laddr() check still passes.
> Each retry builds another DEL_IP chunk and queues it on addip_chunk_list
> behind the outstanding ASCONF. That is one new chunk per retry, not a
> single duplicate.
>
> Could the commit message describe what actually happens on retry?
>
> > diff --git a/net/sctp/protocol.c b/net/sctp/protocol.c
> > index 27c26e12f95d8..267dc9b7a0e73 100644
> > --- a/net/sctp/protocol.c
> > +++ b/net/sctp/protocol.c
>
> [ ... ]
>
> > @@ -645,16 +643,27 @@ static void sctp_addr_wq_timeout_handler(struct timer_list *t)
> >                       if (!sctp_is_ep_boundall(sk))
> >                               continue;
> >                       bh_lock_sock(sk);
> > +                     if (sock_owned_by_user(sk)) {
> > +                             bh_unlock_sock(sk);
> > +                             retry = true;
> > +                             break;
> > +                     }
>
> [Severity: Medium]
> Can a single owned socket stall address event processing for the whole
> netns here?
>
> The first user-owned boundall socket in net->sctp.auto_asconf_splist
> breaks out of both loops. No later socket gets processed, and neither does
> any later addr_waitq entry, including events for unrelated addresses.
>
> Each retry starts again at the head of both lists. Every socket ahead of
> the owned one therefore gets sctp_asconf_mgmt() again on every tick.
>
> User context can hold the socket lock for an unbounded time. For example,
> sctp_recvmsg() holds lock_sock() across skb_copy_datagram_msg(). A copy
> that faults on a FUSE or userfaultfd backed page keeps sk_lock.owned set
> for as long as the user wants.
>
> The other async entry points defer per socket or per association: the
> sctp_rcv() backlog queueing and the timer handlers in sm_sideeffect.c. One
> busy socket does not block the others there.
>
> Would it make sense to skip only this socket and track which sockets
> still need the event, instead of halting all processing?
>
> [Severity: Low]
> This isn't a bug, but the sibling deferral sites in
> net/sctp/sm_sideeffect.c log before they reschedule. One example is
> sctp_generate_heartbeat_event():
>
>         if (sock_owned_by_user(sk)) {
>                 pr_debug("%s: sock is busy\n", __func__);
>
> The DAD retry in this same handler also logs. Should this branch have a
> similar pr_debug() so repeated deferrals show up in dynamic debug?
>
> >                       if (sctp_asconf_mgmt(sp, addrw) < 0)
> >                               pr_debug("%s: sctp_asconf_mgmt failed\n", __func__);
> >                       bh_unlock_sock(sk);
> >               }
> > +             if (retry)
> > +                     break;
>
> [Severity: Medium]
> Does leaving a partly delivered addrw on addr_waitq break the offset logic
> in sctp_addr_wq_mgmt()?
>
> By this point sctp_asconf_mgmt() has run for every boundall socket ahead
> of the owned one, but the entry stays queued after addr_wq_lock is
> dropped. sctp_addr_wq_mgmt() assumes no socket has seen a queued entry
> yet:
>
> net/sctp/protocol.c:sctp_addr_wq_mgmt() {
>     ...
>         /* Offsets existing events in addr_wq */
>         addrw = sctp_addr_wq_lookup(net, addr);
>         if (addrw) {
>                 if (addrw->state != cmd) {
>                         ...
>                         list_del(&addrw->list);
>                         kfree(addrw);
>                 }
>                 spin_unlock_bh(&net->sctp.addr_wq_lock);
>                 return;
>         }
>     ...
> }
>
> Take a partly delivered SCTP_ADDR_NEW entry, and suppose RTM_DELADDR or
> NETDEV_DOWN for the same address arrives before the retry. Both events are
> dropped. The earlier sockets' associations have already sent ADD_IP for
> the address. They keep it in asoc->base.bind_addr after it is gone, and no
> DEL_IP is ever sent.
>
> The reverse happens with a partly delivered SCTP_ADDR_DEL entry if the
> address comes back before the retry. The earlier sockets' associations
> remove the address when the ASCONF-ACK arrives and never re-add it.
>
> Before this patch, the only path that left an entry queued was the IPv6
> DAD break. That break runs before the socket loop, so no socket had seen
> the entry yet.
>
> >  #if IS_ENABLED(CONFIG_IPV6)
> >  free_next:
> >  #endif
> >               list_del(&addrw->list);
> >               kfree(addrw);
> >       }
> > +
> > +     if (retry)
> > +             mod_timer(&net->sctp.addr_wq_timer,
> > +                     jiffies + msecs_to_jiffies(SCTP_ADDRESS_TICK_DELAY));
>
> [Severity: High]
> Can this retry grow kernel memory without bound for an SCTP_ADDR_DEL
> entry?
>
> The retry has no limit and keeps no record of which sockets were already
> handled. Every SCTP_ADDRESS_TICK_DELAY (500 ms), the timer replays the
> same DEL addrw on every boundall socket ahead of the owned one:
>
> sctp_addr_wq_timeout_handler()
>   sctp_asconf_mgmt()
>     sctp_send_asconf_del_ip()
>       sctp_assoc_lookup_laddr()     <- still matches, ->valid is 1
>       sctp_find_unmatch_addr()      <- still returns another address
>       sctp_make_asconf_update_ip()  <- new skb and chunk, GFP_ATOMIC
>       sctp_send_asconf()
>
> While asoc->addip_last_asconf is set, sctp_send_asconf() appends the new
> chunk with no length limit:
>
>         if (asoc->addip_last_asconf) {
>                 list_add_tail(&chunk->list, &asoc->addip_chunk_list);
>                 goto out;
>         }
>
> Each retry also calls sctp_transport_route() on every transport of each
> association. These allocations happen in timer softirq context and are
> not charged to any memcg.
>
> The growth only stops when the owned socket is released or the ASCONF-ACK
> arrives. A peer that drops only ASCONF-ACKs keeps the association alive,
> because heartbeat ACKs and data SACKs reset overall_error_count. So
> sctp_sf_t4_timer_expire() never reaches max_retrans.
>
> A local unprivileged user with user and net namespaces can set this up:
>
>   - enable net.sctp.addip_enable in their own netns
>   - flap an address on a dummy interface
>   - filter ASCONF-ACKs
>   - keep a socket owned through a stalled user copy
>
> Before this patch, one address event produced exactly one DEL ASCONF per
> association.
>
> >       spin_unlock_bh(&net->sctp.addr_wq_lock);
> >  }
>
> --
> Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261109024219.347985-1-18983559317%40163.com

Looks like we can't re-arm the timer in this case.

Can you try adding a list to struct sctp_sock to store the addr
entries and continue the loop when it occurs, and implement
sctp_release_cb() to handle them?

Thanks.

      reply	other threads:[~2026-11-12 22:30 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-09  2:42 Binbin Deng
2026-11-11  8:44 ` netdev-bot+sashiko
2026-11-12 22:30   ` Xin Long [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=CADvbK_fQBb2FTBOFiQ_4nmSGG7aQspD4X03bhENKb74prxorCw@mail.gmail.com \
    --to=lucien.xin@gmail.com \
    --cc=18983559317@163.com \
    --cc=davem@davemloft.net \
    --cc=edumazet@google.com \
    --cc=horms@kernel.org \
    --cc=kuba@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-sctp@vger.kernel.org \
    --cc=marcelo.leitner@gmail.com \
    --cc=netdev-bot+sashiko@kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®