Re: [PATCH net v2] sctp: re-point retained control chunks on association migration

Xin Long <[email protected]>
Newsgroups org.kernel.vger.linux-sctp,org.kernel.vger.linux-kernel,org.kernel.vger.netdev
Message-ID <CADvbK_fUdKDT11-H44yhKxv8_hgToRUnjggjm71HbFZ5A+HeeQ@mail.gmail.com>
On Tue, Aug 4, 2026 at 7:37 AM Jun Yang <[email protected]> wrote:
>
> From: Jun Yang <[email protected]>
>
> sctp_control_set_owner_w() records the owning socket in a control chunk's
> skb->sk.  sctp_sock_migrate() re-owns the association's DATA chunks via
> sctp_for_each_tx_datachunk(), but that walk keys off chunk->msg and so
> skips control chunks: any control chunk the association still holds (for
> example the saved stream-reset request asoc->strreset_chunk, the ASCONF
> request/ack lists, or asoc->addip_last_asconf) keeps pointing at the old
> socket after the association is moved to the new one.
>
> Once the old socket is freed, a later retransmit reaches
> sctp_packet_transmit() -> skb_set_owner_w(head, chunk->skb->sk) and
> operates on the freed socket -- refcount_add() on its sk_wmem_alloc,
> then sk->sk_write_space() from sock_wfree() -- a use-after-free of
> struct sock.
>
> Rename sctp_for_each_tx_datachunk() to sctp_for_each_tx_chunk() and walk
> the control chunks the association retains there as well, so migration
> re-owns them with the same clear/set bracketing already used for DATA
> chunks.  sctp_set_owner_w_migrate() picks the right owner helper by
> testing chunk->msg, which is NULL for control chunks.
>
> The per-chunk owner test that traverse_and_process() already applies is
> split out into sctp_process_tx_chunk() and reused for the control lists.
> A chunk can sit on two of them at once -- asoc->strreset_chunk and
> asoc->addip_last_asconf both stay queued on outqueue.control_chunk_list
> until they are flushed -- and the test keeps such a chunk from being
> cleared or re-owned twice, which would otherwise leak an shkey reference.
>
> sctp_control_set_owner_w() re-reads chunk->shkey from asoc->shkey, so
> sctp_set_owner_w_migrate() releases the reference sctp_clear_owner_w()
> took by value instead of re-reading chunk->shkey, which would drop the
> wrong key if the active key changed while the chunk was queued.
>
> Fixes: d04adf1b3551 ("sctp: reset owner sk for data chunks on out queues when migrating a sock")
> Cc: [email protected]
> Reported-by: TencentOS Corvus AI <[email protected]>
> Assisted-by: tencentos-corvus-ai:kimi-k3
> Signed-off-by: Jun Yang <[email protected]>
> ---
> This is based on David Lee's
>
>   [PATCH] sctp: hold shkey across socket migration
>   https://lore.kernel.org/netdev/[email protected]/
>
> which adds sctp_set_owner_w_migrate()
>
> v2:
>  - Rename sctp_for_each_tx_datachunk() to sctp_for_each_tx_chunk() and
>    move the control-chunk traversal into it, rather than adding a
>    separate sctp_for_each_tx_ctrlchunk() helper (Xin Long).
>  - Handle control chunks in sctp_set_owner_w_migrate() by testing
>    chunk->msg, dropping the sctp_ctrl_set_owner_w() helper (Xin Long).
>    Control chunks now go through the full clear/set bracketing instead of
>    a bare skb->sk store, so sctp_control_set_owner_w() is no longer
>    static.
>  - Factor the existing owner test out of traverse_and_process() into
>    sctp_process_tx_chunk() so the control lists get it too.
>
> v1: https://lore.kernel.org/netdev/[email protected]/
>
>  include/net/sctp/sm.h    |  1 +
>  net/sctp/sm_make_chunk.c |  2 +-
>  net/sctp/socket.c        | 53 ++++++++++++++++++++++++++++++----------
>  3 files changed, 42 insertions(+), 14 deletions(-)
>
> diff --git a/include/net/sctp/sm.h b/include/net/sctp/sm.h
> index 3bfd261a53cc..76605d1ee839 100644
> --- a/include/net/sctp/sm.h
> +++ b/include/net/sctp/sm.h
> @@ -252,6 +252,7 @@ struct sctp_chunk *sctp_make_fwdtsn(const struct sctp_association *asoc,
>                                     struct sctp_fwdtsn_skip *skiplist);
>  struct sctp_chunk *sctp_make_auth(const struct sctp_association *asoc,
>                                   __u16 key_id);
> +void sctp_control_set_owner_w(struct sctp_chunk *chunk);
>  struct sctp_chunk *sctp_make_strreset_req(const struct sctp_association *asoc,
>                                           __u16 stream_num, __be16 *stream_list,
>                                           bool out, bool in);
> diff --git a/net/sctp/sm_make_chunk.c b/net/sctp/sm_make_chunk.c
> index 0ae30c3c8913..7684686798cf 100644
> --- a/net/sctp/sm_make_chunk.c
> +++ b/net/sctp/sm_make_chunk.c
> @@ -94,7 +94,7 @@ static void sctp_control_release_owner(struct sk_buff *skb)
>         }
>  }
>
> -static void sctp_control_set_owner_w(struct sctp_chunk *chunk)
> +void sctp_control_set_owner_w(struct sctp_chunk *chunk)
>  {
>         struct sctp_association *asoc = chunk->asoc;
>         struct sk_buff *skb = chunk->skb;
> diff --git a/net/sctp/socket.c b/net/sctp/socket.c
> index 4a08023d52aa..d09b9f139070 100644
> --- a/net/sctp/socket.c
> +++ b/net/sctp/socket.c
> @@ -155,9 +155,24 @@ static void sctp_clear_owner_w(struct sctp_chunk *chunk)
>
>  static void sctp_set_owner_w_migrate(struct sctp_chunk *chunk)
>  {
> -       sctp_set_owner_w(chunk);
> -       if (chunk->shkey)
> -               sctp_auth_shkey_release(chunk->shkey);
> +       struct sctp_shared_key *shkey = chunk->shkey;
> +
> +       if (chunk->msg)
> +               sctp_set_owner_w(chunk);
> +       else
> +               sctp_control_set_owner_w(chunk);
> +
> +       if (shkey)
> +               sctp_auth_shkey_release(shkey);
> +}
> +
> +static void sctp_process_tx_chunk(struct sctp_association *asoc,
> +                                 struct sctp_chunk *chunk, bool clear,
> +                                 void (*cb)(struct sctp_chunk *))
> +{
> +       if ((clear && asoc->base.sk == chunk->skb->sk) ||
> +           (!clear && asoc->base.sk != chunk->skb->sk))
> +               cb(chunk);
>  }
>
>  #define traverse_and_process() \
> @@ -165,17 +180,14 @@ do {                              \
>         msg = chunk->msg;       \
>         if (msg == prev_msg)    \
>                 continue;       \
> -       list_for_each_entry(c, &msg->chunks, frag_list) {       \
> -               if ((clear && asoc->base.sk == c->skb->sk) ||   \
> -                   (!clear && asoc->base.sk != c->skb->sk))    \
> -                       cb(c);  \
> -       }                       \
> +       list_for_each_entry(c, &msg->chunks, frag_list) \
> +               sctp_process_tx_chunk(asoc, c, clear, cb);      \
>         prev_msg = msg;         \
>  } while (0)
>
> -static void sctp_for_each_tx_datachunk(struct sctp_association *asoc,
> -                                      bool clear,
> -                                      void (*cb)(struct sctp_chunk *))
> +static void sctp_for_each_tx_chunk(struct sctp_association *asoc,
> +                                  bool clear,
> +                                  void (*cb)(struct sctp_chunk *))
>
>  {
>         struct sctp_datamsg *msg, *prev_msg = NULL;
> @@ -198,6 +210,21 @@ static void sctp_for_each_tx_datachunk(struct sctp_association *asoc,
>
>         list_for_each_entry(chunk, &q->out_chunk_list, list)
>                 traverse_and_process();
> +
> +       list_for_each_entry(chunk, &q->control_chunk_list, list)
> +               sctp_process_tx_chunk(asoc, chunk, clear, cb);
> +
> +       list_for_each_entry(chunk, &asoc->asconf_ack_list, transmitted_list)
> +               sctp_process_tx_chunk(asoc, chunk, clear, cb);
> +
> +       list_for_each_entry(chunk, &asoc->addip_chunk_list, list)
> +               sctp_process_tx_chunk(asoc, chunk, clear, cb);
> +
> +       if (asoc->strreset_chunk)
> +               sctp_process_tx_chunk(asoc, asoc->strreset_chunk, clear, cb);
> +
> +       if (asoc->addip_last_asconf)
> +               sctp_process_tx_chunk(asoc, asoc->addip_last_asconf, clear, cb);
>  }
You can just use cb(...) here, no need to extract sctp_process_tx_chunk().

These checks of clear and asoc->base.sk were introduced by:

5c3e82fe1596 ("sctp: fix refcount bug in sctp_wfree")

The issue only existed on DATA chunks.

Also, please hold this patch until upstream applies the dependence:

https://lore.kernel.org/netdev/[email protected]/

Otherwise, your patch will fail on these sashiko reviews as "Failed To Apply".

Thanks.

>
>  static void sctp_for_each_rx_skb(struct sctp_association *asoc, struct sock *sk,
> @@ -9640,9 +9667,9 @@ static int sctp_sock_migrate(struct sock *oldsk, struct sock *newsk,
>          * paths won't try to lock it and then oldsk.
>          */
>         lock_sock_nested(newsk, SINGLE_DEPTH_NESTING);
> -       sctp_for_each_tx_datachunk(assoc, true, sctp_clear_owner_w);
> +       sctp_for_each_tx_chunk(assoc, true, sctp_clear_owner_w);
>         sctp_assoc_migrate(assoc, newsk);
> -       sctp_for_each_tx_datachunk(assoc, false, sctp_set_owner_w_migrate);
> +       sctp_for_each_tx_chunk(assoc, false, sctp_set_owner_w_migrate);
>
>         /* If the association on the newsk is already closed before accept()
>          * is called, set RCV_SHUTDOWN flag.
> --
> 2.55.0
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.