Re: [PATCH] sctp: avoid requeueing pending control chunks
From: Xin Long
Date: Tue Nov 03 2026 - 09:29:33 EST
On Fri, Oct 30, 2026 at 12:23 AM Kyle Zeng <kylebot@xxxxxxxxxx> wrote:
>
> The T4 and RE-CONFIG timeout handlers take a reference to their cached
> control chunk and submit it through SCTP_CMD_REPLY. A previous transmission
> can still be on the control outqueue when output is corked or suppressed,
> or when sctp_packet_transmit_chunk() could not append the chunk.
>
> sctp_outq_tail() then adds the same list node a second time. In particular,
> re-adding a non-tail entry can corrupt the list without being caught by
> the insertion-time list checks. A subsequent flush or association teardown
> can walk the corrupted list and access an already freed chunk. The T4 path
> is reproducible by an unprivileged SCTP socket owner in a user/network
> namespace, with KASAN reporting a use-after-free in __sctp_outq_teardown().
>
> Check sctp_chunk_pending() before adding a control chunk to the outqueue.
> If it is already linked, release the extra reference supplied for this
> enqueue and leave the existing queue entry in place. Continue to the usual
> flush so that the pending transmission can be retried.
>
> This also covers RE-CONFIG retransmission after sctp_assoc_rm_peer() moves
> the request to another transport and starts its timer. Keep the existing
> timer handling, corking rules and data-chunk path unchanged.
>
> Signed-off-by: Kyle Zeng <kylebot@xxxxxxxxxx>
Please add a "Fixes: " tag for this patch.
> ---
> I have a reproducer for this issue and can share via DM
>
> net/sctp/outqueue.c | 5 +++++
> 1 file changed, 5 insertions(+)
>
> diff --git a/net/sctp/outqueue.c b/net/sctp/outqueue.c
> index e92fb9da4647..a5f9f0fcf5fd 100644
> --- a/net/sctp/outqueue.c
> +++ b/net/sctp/outqueue.c
> @@ -304,6 +304,11 @@ void sctp_outq_tail(struct sctp_outq *q, struct sctp_chunk *chunk, gfp_t gfp)
> SCTP_INC_STATS(net, SCTP_MIB_OUTUNORDERCHUNKS);
> else
> SCTP_INC_STATS(net, SCTP_MIB_OUTORDERCHUNKS);
> + } else if (sctp_chunk_pending(chunk)) {
> + /* A retransmitted control chunk may still be queued.
> + * Drop the extra reference and retry the flush below.
> + */
> + sctp_chunk_free(chunk);
> } else {
> list_add_tail(&chunk->list, &q->control_chunk_list);
> SCTP_INC_STATS(net, SCTP_MIB_OUTCTRLCHUNKS);
>
Can you try to fix it by not sending it in sctp_sf_t4_timer_expire() and
sctp_sf_send_reconf(), instead of touching the common path? like:
diff --git a/net/sctp/sm_statefuns.c b/net/sctp/sm_statefuns.c
index 8dc65498763c..7671bb0ce946 100644
--- a/net/sctp/sm_statefuns.c
+++ b/net/sctp/sm_statefuns.c
@@ -1122,9 +1122,11 @@ enum sctp_disposition
sctp_sf_send_reconf(struct net *net,
return SCTP_DISPOSITION_DELETE_TCB;
}
- sctp_chunk_hold(asoc->strreset_chunk);
- sctp_add_cmd_sf(commands, SCTP_CMD_REPLY,
- SCTP_CHUNK(asoc->strreset_chunk));
+ if (!sctp_chunk_pending(asoc->strreset_chunk)) {
+ sctp_chunk_hold(asoc->strreset_chunk);
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY,
+ SCTP_CHUNK(asoc->strreset_chunk));
+ }
sctp_add_cmd_sf(commands, SCTP_CMD_STRIKE, SCTP_TRANSPORT(transport));
return SCTP_DISPOSITION_CONSUME;
@@ -6197,9 +6199,11 @@ enum sctp_disposition sctp_sf_t4_timer_expire(
* chunk, it MUST be the same (including its serial number) as the last
* ASCONF sent.
*/
- sctp_chunk_hold(asoc->addip_last_asconf);
- sctp_add_cmd_sf(commands, SCTP_CMD_REPLY,
- SCTP_CHUNK(asoc->addip_last_asconf));
+ if (!sctp_chunk_pending(asoc->addip_last_asconf)) {
+ sctp_chunk_hold(asoc->addip_last_asconf);
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY,
+ SCTP_CHUNK(asoc->addip_last_asconf));
+ }
Thanks.