Re: [PATCH net] sctp: fix transport UAF via the retransmit queue
From: Xin Long <lucien.xin@gmail.com>
Date: 2026-07-27 17:42:38
Also in:
linux-sctp, lkml, stable
On Sun, Jul 26, 2026 at 2:05 AM Baul Lee [off-list ref] wrote:
sctp_assoc_rm_peer() clears the cached chunk->transport back-pointer on
only two of the output queue's chunk lists before freeing the transport:
peer->transmitted and asoc->outqueue.out_chunk_list. A DATA chunk parked
on asoc->outqueue.retransmit keeps pointing at the transport, so once
sctp_transport_free() drops the last reference and the object is
RCU-freed, that chunk is left with a dangling pointer.
While the chunk sits on the retransmit queue the dangling pointer is not
followed: sctp_check_transmitted() skips the flight-size accounting both
for transmitted_queue == &q->retransmit and for gap-acked chunks. Two
steps remove that cover. First, sctp_outq_flush_rtx() moves a gap-acked
chunk onto a live transport's transmitted list without reassigning
chunk->transport, unlike the ordinary resend path, which rebinds it in
__sctp_packet_append_chunk(). Second, a later SACK that reneges on the
TSN clears tsn_gap_acked. The chunk now sits on a transmitted list with
tsn_gap_acked == 0, so the next SACK reaches
tchunk->transport->flight_size -= sctp_data_size(tchunk);
a read-modify-write inside the freed sctp_transport.
The transport is freed by an ASCONF Delete-IP and the faulting accesses
are driven by ordinary SACKs, both coming from the association peer, so
an application that speaks SCTP and accepts multihoming is enough to
reach this. KASAN reports a slab-use-after-free read of 4 bytes at
offset 216 of a freed kmalloc-1k sctp_transport in
sctp_check_transmitted(), the object having been freed from
sctp_assoc_rm_peer() via sctp_process_asconf().
Clear ->transport for chunks on the retransmit queue as well, mirroring
the existing out_chunk_list handling. The sink already guards with
if (tchunk->transport), so a cleared back-pointer is skipped exactly like
an already-acked chunk. The sacked and abandoned queues do not need the
same treatment: chunks there never have ->transport dereferenced and are
never migrated onto the retransmit queue or onto a transport's
transmitted list.
Discovered by XBOW, triaged by Baul Lee [off-list ref]
Reported privately to the maintainers on 2026-07-10 with root-cause
analysis, a PoC, a KASAN log and this fix; posting to the list was
requested as the follow-up.
Fixes: df132eff4638 ("sctp: clear the transport of some out_chunk_list chunks in sctp_assoc_rm_peer")I think the issue exists since the very beginning, Fixes tag should be:
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
quoted hunk ↗ jump to hunk
Reported-by: Federico Kirschbaum <redacted> Reported-by: Baul Lee <redacted> Cc: stable@vger.kernel.org Signed-off-by: Baul Lee <redacted> --- net/sctp/associola.c | 4 ++++ 1 file changed, 4 insertions(+)diff --git a/net/sctp/associola.c b/net/sctp/associola.c index 62d3cc155809..c95f68d21670 100644 --- a/net/sctp/associola.c +++ b/net/sctp/associola.c@@ -569,6 +569,10 @@ void sctp_assoc_rm_peer(struct sctp_association *asoc, sctp_transport_hold(active); } + list_for_each_entry(ch, &asoc->outqueue.retransmit, transmitted_list) + if (ch->transport == peer) + ch->transport = NULL; +
[sashiko-genimi]
This isn't a problem introduced by this patch, but does the new loop cover
the ordering where the gap-acked migration happens before the ASCONF
Delete-IP?
[sashiko-claude]
This is a pre-existing issue, but can gap-acked data chunks bypass this
cleanup if they were already migrated to another active transport's
transmitted list?
Both AI reviews report that the change may not cover the case where the gap-acked data chunks bypass this cleanup if they were already migrated to another active transport's transmitted list. As you described in the changelog, the issue actually was caused by:
... sctp_outq_flush_rtx() moves a gap-acked chunk onto a live transport's transmitted list without reassigning chunk->transport.
Can you try fixing this in sctp_outq_flush_rtx() by updating chunk->transport after both list_move_tail() calls? This makes sure the chunk's transport pointer always matches the list it is queued on. Thanks.