Re: [PATCH net] sctp: fix NULL deref on untransmitted RECONF completion

Xin Long <[email protected]>
Newsgroups org.kernel.vger.linux-sctp,org.kernel.vger.netdev,org.kernel.vger.stable
Message-ID <CADvbK_cC8A3vjV0mweyBX_RyHFk_-ruyQ-Wp6UDmWpeAvNMvTQ@mail.gmail.com>
On Thu, Aug 20, 2026 at 12:23 PM <[email protected]> wrote:
>
> From: Weiming Shi <[email protected]>
>
> sctp_process_strreset_outreq(), sctp_process_strreset_addstrm_out() and
> sctp_process_strreset_resp() finish a pending stream reconfiguration
> request by stopping the reconf timer on the transport the request was
> sent on:
>
>         t = asoc->strreset_chunk->transport;
>         if (timer_delete(&t->reconf_timer))
>                 sctp_transport_put(t);
>
> On the transmit path chunk->transport is assigned by
> __sctp_packet_append_chunk(), that is, when the chunk is actually
> appended to an outbound packet. A RECONF chunk that was queued but never
> transmitted keeps transport == NULL, and its reconf timer is never armed
> either, since that too only happens once the chunk has been appended.
>
> sctp_outq_flush_ctrl() skips every non-ASCONF control chunk while
> asoc->src_out_of_asoc_ok is set and leaves it on control_chunk_list, so
> in that state a RECONF is never put on the wire. The sender side has
> already published the chunk in asoc->strreset_chunk and armed
> asoc->strreset_outstanding, so an incoming RECONF that drives
> strreset_outstanding down to 0 dereferences the NULL transport.
>
> sctp_send_asconf_del_ip() reaches that state without any peer
> interaction: when the address being removed is the association's last
> one it stashes the address in asoc->asconf_addr_del_pending, sets
> src_out_of_asoc_ok and skips chunk creation and transmission
> ("stored = 1; goto skip_mkasconf"). As only sctp_process_asconf_ack()
> clears the flag, it stays set until a later bindx() ADD picks the pending
> delete up. An unprivileged process that removes the last address of an
> ASCONF-enabled association and then asks for a stream reset panics the
> kernel from softirq:
>
>   Oops: general protection fault, probably for non-canonical address
>   0xdffffc000000003d: 0000 [#1] SMP KASAN NOPTI
>   KASAN: null-ptr-deref in range [0x00000000000001e8-0x00000000000001ef]
>   RIP: 0010:timer_delete+0x67/0x110
>   Call Trace:
>    <IRQ>
>    sctp_process_strreset_addstrm_out (net/sctp/stream.c:832)
>    sctp_sf_do_reconf (net/sctp/sm_statefuns.c:4212)
>    sctp_do_sm (net/sctp/sm_sideeffect.c:1172)
>    sctp_assoc_bh_rcv (net/sctp/associola.c:1044)
>    sctp_rcv (net/sctp/input.c:243)
>    ip_protocol_deliver_rcu (net/ipv4/ip_input.c:207)
>    ip_local_deliver (net/ipv4/ip_input.c:262)
>    ip_rcv (net/ipv4/ip_input.c:612)
>    process_backlog (net/core/dev.c:6680)
>    net_rx_action (net/core/dev.c:7959)
>    handle_softirqs (kernel/softirq.c:622)
>    </IRQ>
>   Kernel panic - not syncing: Fatal exception in interrupt
>
> Skip the timer deletion when the RECONF chunk never reached a packet:
> there is no armed reconf timer and no transport reference to drop.
>
> Fixes: 810544764536 ("sctp: implement receiver-side procedures for the Outgoing SSN Reset Request Parameter")
> Cc: [email protected]
> Reported-by: Xiang Mei <[email protected]>
> Assisted-by: Claude:claude-opus-5
> Signed-off-by: Weiming Shi <[email protected]>
> ---
> Note to reviewers: this only stops the crash, and I want to be upfront that it
> does not remove the state that produces it.
>
> That state has exactly one producer. sctp_send_asconf_del_ip() latches
> src_out_of_asoc_ok on the branch that stashes the association's last address
> without building or sending an ASCONF, and sctp_process_asconf_ack() is the
> only place that clears it -- so the flag survives until the application issues
> the follow-up bindx() ADD that sctp_make_asconf_update_ip()'s del_pickup needs.
> 8a07eb0a50ae ("sctp: Add ASCONF operation on the single-homed host") describes
> this as a transient state ("the other chunks cannot be transmitted in this
> state"), but nothing bounds how long it lasts: an application that never adds a
> replacement address leaves the association unable to send any DATA or
> non-ASCONF control chunk for the rest of its life, with the RECONF sitting on
> control_chunk_list and strreset_outstanding armed.
>
> I kept this patch to the dereference because it is what the rest of net/sctp
> already does -- every other chunk->transport dereference is NULL-checked -- and
> because it backports cleanly. But would you prefer the latch itself to be
> addressed, or the reconf request to be abandoned here rather than completed
> against a chunk that never went out? Happy to follow up with either for
> net-next. net/sctp/stream.c | 6 +++---
>  1 file changed, 3 insertions(+), 3 deletions(-)
>
> diff --git a/net/sctp/stream.c b/net/sctp/stream.c
> index 34ffe6c945a4..1cb30def2eb9 100644
> --- a/net/sctp/stream.c
> +++ b/net/sctp/stream.c
> @@ -577,7 +577,7 @@ struct sctp_chunk *sctp_process_strreset_outreq(
>                         struct sctp_transport *t;
>
>                         t = asoc->strreset_chunk->transport;
> -                       if (timer_delete(&t->reconf_timer))
> +                       if (t && timer_delete(&t->reconf_timer))
>                                 sctp_transport_put(t);
>
>                         sctp_chunk_put(asoc->strreset_chunk);
> @@ -829,7 +829,7 @@ struct sctp_chunk *sctp_process_strreset_addstrm_out(
>                         struct sctp_transport *t;
>
>                         t = asoc->strreset_chunk->transport;
> -                       if (timer_delete(&t->reconf_timer))
> +                       if (t && timer_delete(&t->reconf_timer))
>                                 sctp_transport_put(t);
>
>                         sctp_chunk_put(asoc->strreset_chunk);
> @@ -1084,7 +1084,7 @@ struct sctp_chunk *sctp_process_strreset_resp(
>         /* remove everything for this reconf request */
>         if (!asoc->strreset_outstanding) {
>                 t = asoc->strreset_chunk->transport;
> -               if (timer_delete(&t->reconf_timer))
> +               if (t && timer_delete(&t->reconf_timer))
>                         sctp_transport_put(t);
>
>                 sctp_chunk_put(asoc->strreset_chunk);
> --
> 2.55.0
>
If the STRRESET chunk has never been sent, we should NOT allow an incoming
chunk to “ACK” it.

Could you try fixing this by returning NULL from
sctp_chunk_lookup_strreset_param() if the STRRESET chunk has not been
transmitted yet? like:

@@ -488,7 +488,7 @@ static struct sctp_paramhdr
*sctp_chunk_lookup_strreset_param(
        struct sctp_reconf_chunk *hdr;
        union sctp_params param;

-       if (!chunk)
+       if (!chunk || !chunk->transport)
                return NULL;

Please also share the PoC with the maintainers, if there is one.

Thanks.
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.