Re: [PATCH net 1/2] sctp: factor out INIT verification failure handling

Xin Long <[email protected]> Thu, 18 Jun 2026 17:53:10 -0400
Newsgroups org.kernel.vger.linux-sctp,org.kernel.vger.netdev
Message-ID <CADvbK_eCmu6vTWPhTXXua46wP9HJ6LB8PD7pjPo163jqvKjJZw@mail.gmail.com>
On Wed, Jun 17, 2026 at 6:40 AM Simon Horman <[email protected]> wrote:
>
> This is an AI-generated review of your patch. The human sending this
> email has considered the AI review valid, or at least plausible.
> Full review at: https://netdev-ai.bots.linux.dev/sashiko/
> ---
> sctp: factor out INIT verification failure handling
>
> This refactor moves the duplicated INIT/INIT-ACK error handling out of
> sctp_sf_do_5_1B_init(), sctp_sf_do_5_1C_ack(), and
> sctp_sf_do_unexpected_init() into a new helper sctp_abort_on_init_err().
> The commit message states "No functional change intended."
>
> > diff --git a/net/sctp/sm_statefuns.c b/net/sctp/sm_statefuns.c
> > index 9b23c11cbb9e..544f308ee527 100644
> > --- a/net/sctp/sm_statefuns.c
> > +++ b/net/sctp/sm_statefuns.c
>
> [ ... ]
>
> > @@ -548,50 +531,9 @@ enum sctp_disposition sctp_sf_do_5_1C_ack(struct net *net,
> >       err_chunk = NULL;
> >       if (!sctp_verify_init(net, ep, asoc, chunk->chunk_hdr->type,
> >                             (struct sctp_init_chunk *)chunk->chunk_hdr, chunk,
> > -                           &err_chunk)) {
> > -
> > -             enum sctp_error error = SCTP_ERROR_NO_RESOURCE;
> > -
> > -             /* This chunk contains fatal error. It is to be discarded.
> > -              * Send an ABORT, with causes.  If there are no causes,
> > -              * then there wasn't enough memory.  Just terminate
> > -              * the association.
> > -              */
> > -             if (err_chunk) {
> > -                     packet = sctp_abort_pkt_new(net, ep, asoc, arg,
> > -                                     (__u8 *)(err_chunk->chunk_hdr) +
> > -                                     sizeof(struct sctp_chunkhdr),
> > -                                     ntohs(err_chunk->chunk_hdr->length) -
> > -                                     sizeof(struct sctp_chunkhdr));
> > -
> > -                     sctp_chunk_free(err_chunk);
> > -
> > -                     if (packet) {
> > -                             sctp_add_cmd_sf(commands, SCTP_CMD_SEND_PKT,
> > -                                             SCTP_PACKET(packet));
> > -                             SCTP_INC_STATS(net, SCTP_MIB_OUTCTRLCHUNKS);
> > -                             error = SCTP_ERROR_INV_PARAM;
> > -                     }
> > -             }
> > -
> > -             /* SCTP-AUTH, Section 6.3:
> > -              *    It should be noted that if the receiver wants to tear
> > -              *    down an association in an authenticated way only, the
> > -              *    handling of malformed packets should not result in
> > -              *    tearing down the association.
> > -              *
> > -              * This means that if we only want to abort associations
> > -              * in an authenticated way (i.e AUTH+ABORT), then we
> > -              * can't destroy this association just because the packet
> > -              * was malformed.
> > -              */
> > -             if (sctp_auth_recv_cid(SCTP_CID_ABORT, asoc))
> > -                     return sctp_sf_pdiscard(net, ep, asoc, type, arg, commands);
> > -
> > -             SCTP_INC_STATS(net, SCTP_MIB_ABORTEDS);
> > -             return sctp_stop_t1_and_abort(net, commands, error, ECONNREFUSED,
> > -                                             asoc, chunk->transport);
> > -     }
> > +                           &err_chunk))
> > +             return sctp_abort_on_init_err(net, ep, asoc, chunk, arg,
> > +                                           commands, err_chunk);
>
> [High]
> In the original sctp_sf_do_5_1C_ack() the "if (err_chunk) { ... }"
> block is non-controlling: when err_chunk is NULL the code skips the
> block, falls through with error == SCTP_ERROR_NO_RESOURCE, consults
> sctp_auth_recv_cid(SCTP_CID_ABORT, asoc) and either pdiscards or runs
> sctp_stop_t1_and_abort() to tear the half-open association down.
>
> [High]
> In the new helper, can the INIT-ACK + NULL err_chunk case still reach
> that path?  Looking at sctp_abort_on_init_err():
>
>   if (!err_chunk)
>           return sctp_sf_tabort_8_4_8(net, ep, asoc, SCTP_ST_CHUNK(0),
>                                       arg, commands);
>
>   ...
>   if (chunk->chunk_hdr->type != SCTP_CID_INIT_ACK) {
>           ...
>   }
>   /* SCTP-AUTH, Section 6.3 ... */
>   if (sctp_auth_recv_cid(SCTP_CID_ABORT, asoc))
>           return sctp_sf_pdiscard(...);
>   SCTP_INC_STATS(net, SCTP_MIB_ABORTEDS);
>   return sctp_stop_t1_and_abort(net, commands, error, ECONNREFUSED,
>                                 asoc, chunk->transport);
>
> [High]
> The !err_chunk early return runs before the INIT-ACK type check, so
> when sctp_verify_init() returns 0 with *errp left as NULL on an
> INIT-ACK, control now goes to sctp_sf_tabort_8_4_8() rather than the
> auth-check + sctp_stop_t1_and_abort() path.
>
> [High]
> Is the NULL-err_chunk case actually reachable on an INIT-ACK?  In
> sctp_process_inv_mandatory() and sctp_process_inv_paramlength() in
> net/sctp/sm_make_chunk.c, *errp is allocated via sctp_make_op_error_space()
> or sctp_make_violation_paramlen() and the function returns 0
> unconditionally:
>
>   if (!*errp)
>           *errp = sctp_make_op_error_space(asoc, chunk, 0);
>   if (*errp)
>           sctp_init_cause(...);
>   return 0;
>
> So an allocation failure on a malformed INIT-ACK reaches the helper
> with err_chunk == NULL.
>
> [High]
> On that path, does the new code skip several actions that the original
> performed?  sctp_sf_tabort_8_4_8() emits an OOTB ABORT and pdiscards;
> it does not stop T1-init, does not call SCTP_CMD_SET_SK_ERR(ECONNREFUSED),
> does not issue SCTP_CMD_INIT_FAILED, does not transition the asoc to
> SCTP_STATE_CLOSED, and does not increment SCTP_MIB_ABORTEDS.
>
> [High]
> As a result, would connect(2) wait for T1-init to exhaust its
> retransmissions instead of receiving ECONNREFUSED promptly, and would
> SCTP_MIB_ABORTEDS no longer be incremented (with SCTP_MIB_IN_PKT_DISCARDS
> being incremented in its place)?
>
> [High]
> Does this also bypass the SCTP-AUTH section 6.3 check quoted in the
> comment?  Previously, when AUTH-ABORT was negotiated and err_chunk
> was NULL, sctp_auth_recv_cid() caused sctp_sf_pdiscard() to be returned
> and no ABORT was emitted on the wire.  In the new path, sctp_sf_tabort_8_4_8()
> sends an unauthenticated OOTB ABORT regardless of AUTH negotiation.
>
> [High]
> Finally, does the disposition return value also change from
> SCTP_DISPOSITION_ABORT to SCTP_DISPOSITION_CONSUME on this path?
>
> [High]
> Would moving the chunk->chunk_hdr->type check ahead of the !err_chunk
> early return restore the original semantics for INIT-ACK while keeping
> the sctp_sf_tabort_8_4_8() fallback for INIT?
>
All above is talking about one regression, the (!err_chunk) check should be
after (chunk->chunk_hdr->type != SCTP_CID_INIT_ACK) check.

This is a good catch, and I will fix it on v2.

Thanks.

> [ ... ]
>
> > @@ -6485,6 +6403,61 @@ static void sctp_send_stale_cookie_err(struct net *net,
> >       }
> >  }
> >
> > +static enum sctp_disposition sctp_abort_on_init_err(
> > +                                     struct net *net,
> > +                                     const struct sctp_endpoint *ep,
> > +                                     const struct sctp_association *asoc,
> > +                                     const struct sctp_chunk *chunk,
> > +                                     void *arg,
> > +                                     struct sctp_cmd_seq *commands,
> > +                                     struct sctp_chunk *err_chunk)
> > +{
> > +     enum sctp_error error = SCTP_ERROR_NO_RESOURCE;
> > +     struct sctp_packet *packet;
> > +     struct sctp_chunkhdr *ch;
> > +
> > +     if (!err_chunk)
> > +             return sctp_sf_tabort_8_4_8(net, ep, asoc, SCTP_ST_CHUNK(0),
> > +                                         arg, commands);
> > +
> > +     ch = err_chunk->chunk_hdr;
> > +     packet = sctp_abort_pkt_new(net, ep, asoc, arg,
> > +                                 (__u8 *)ch + sizeof(*ch),
> > +                                 ntohs(ch->length) - sizeof(*ch));
> > +
> > +     sctp_chunk_free(err_chunk);
> > +
> > +     if (packet) {
> > +             sctp_add_cmd_sf(commands, SCTP_CMD_SEND_PKT,
> > +                             SCTP_PACKET(packet));
> > +             SCTP_INC_STATS(net, SCTP_MIB_OUTCTRLCHUNKS);
> > +             error = SCTP_ERROR_INV_PARAM;
> > +     }
> > +
> > +     if (chunk->chunk_hdr->type != SCTP_CID_INIT_ACK) {
> > +             if (!packet)
> > +                     return SCTP_DISPOSITION_NOMEM;
> > +             return SCTP_DISPOSITION_CONSUME;
> > +     }
> > +     /* SCTP-AUTH, Section 6.3:
> > +      *    It should be noted that if the receiver wants to tear
> > +      *    down an association in an authenticated way only, the
> > +      *    handling of malformed packets should not result in
> > +      *    tearing down the association.
> > +      *
> > +      * This means that if we only want to abort associations
> > +      * in an authenticated way (i.e AUTH+ABORT), then we
> > +      * can't destroy this association just because the packet
> > +      * was malformed.
> > +      */
> > +     if (sctp_auth_recv_cid(SCTP_CID_ABORT, asoc))
> > +             return sctp_sf_pdiscard(net, ep, asoc, SCTP_ST_CHUNK(0), arg,
> > +                                     commands);
> > +
> > +     SCTP_INC_STATS(net, SCTP_MIB_ABORTEDS);
> > +     return sctp_stop_t1_and_abort(net, commands, error, ECONNREFUSED,
> > +                                   asoc, chunk->transport);
> > +}