Re: [PATCH net] sctp: fix transport UAF via the retransmit queue
Xin Long <[email protected]> Mon, 27 Jul 2026 13:42:25 -0400
| Newsgroups | org.kernel.vger.linux-sctp,org.kernel.vger.linux-kernel,org.kernel.vger.netdev,org.kernel.vger.stable |
|---|---|
| Message-ID | <CADvbK_f5RdD0DAzXZJ8c4-Vm5EDEWRoz4Jz-FsfJBvdEDVS7BA@mail.gmail.com> |
On Sun, Jul 26, 2026 at 2:05=E2=80=AFAM Baul Lee <[email protected]> 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 =3D=3D &q->retransmit and for gap-acked chunks. Tw= o > 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 =3D=3D 0, so the next SACK reaches > > tchunk->transport->flight_size -=3D 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 <[email protected]> > 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 ch= unks 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") > Reported-by: Federico Kirschbaum <[email protected]> > Reported-by: Baul Lee <[email protected]> > Cc: [email protected] > Signed-off-by: Baul Lee <[email protected]> > --- > 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 *aso= c, > sctp_transport_hold(active); > } > > + list_for_each_entry(ch, &asoc->outqueue.retransmit, transmitted_l= ist) > + if (ch->transport =3D=3D peer) > + ch->transport =3D 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.