sctp: re-point retained control chunks on association migration

sctp_sock_migrate() transfers DATA chunk ownership but leaves retained
control chunks pointing at the old socket. A later retransmission can
therefore access the socket after it has been freed.

Extend the migration walk to cover retained control chunks and use
sctp_control_set_owner_w() to assign their new owner. Reuse the DATA
ownership check so each chunk is processed once per pass, even when it
is reachable through multiple lists or retained pointers.

Preserve a control chunk's existing authentication key across migration
so its final release can still generate SCTP_AUTH_FREE_KEY when needed.

Fixes: d04adf1b35 ("sctp: reset owner sk for data chunks on out queues when migrating a sock")
Cc: stable@kernel.org
Reported-by: TencentOS Corvus AI <corvus@tencent.com>
Signed-off-by: Jun Yang <junvyyang@tencent.com>
Acked-by: Xin Long <lucien.xin@gmail.com>
Link: https://patch.msgid.link/20261009074244.3718-3-juny24602@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jun Yang 2026-10-09 15:42:32 +08:00 committed by Jakub Kicinski
parent ad1320865b
commit 52ffeaa358
3 changed files with 44 additions and 15 deletions

View File

@ -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);

View File

@ -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;
@ -107,7 +107,8 @@ static void sctp_control_set_owner_w(struct sctp_chunk *chunk)
* For now don't do anything for now.
*/
if (chunk->auth) {
chunk->shkey = asoc->shkey;
if (!chunk->shkey)
chunk->shkey = asoc->shkey;
sctp_auth_shkey_hold(chunk->shkey);
}
skb->sk = asoc ? asoc->base.sk : NULL;

View File

@ -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);
}
static void sctp_for_each_rx_skb(struct sctp_association *asoc, struct sock *sk,
@ -9645,9 +9672,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.