sctp: fix stream->outcnt underflow on duplicate RECONF responses

A cached RECONF chunk may contain more than one request parameter.  A
duplicate response can therefore find and process the same ADD_OUT request
again while another parameter is still outstanding, rolling back outcnt
twice and possibly underflowing it.

Track outstanding request types as bits and clear each bit after its first
response.  Later responses for the same request are then ignored.

Fixes: 11ae76e67a ("sctp: implement receiver-side procedures for the Reconf Response Parameter")
Cc: stable@kernel.org
Reported-by: TencentOS Corvus AI <corvus@tencent.com>
Link: https://lore.kernel.org/netdev/20260730110225.37371-1-juny24602@gmail.com/
Suggested-by: Xin Long <lucien.xin@gmail.com>
Assisted-by: tencentos-corvus-ai:kimi-k3
Signed-off-by: Jun Yang <junvyyang@tencent.com>
Link: https://patch.msgid.link/20260824081832.98717-3-juny24602@gmail.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
Jun Yang 2026-08-24 16:18:20 +08:00 committed by Paolo Abeni
parent 63f44178f0
commit 3faf13aff2
2 changed files with 29 additions and 12 deletions

View File

@ -2057,7 +2057,7 @@ struct sctp_association {
force_delay:1;
__u8 strreset_enable;
__u8 strreset_outstanding; /* request param count on the fly */
__u8 strreset_outstanding; /* request param bitmask on the fly */
__u32 strreset_outseq; /* Update after receiving response */
__u32 strreset_inseq; /* Update after receiving request */

View File

@ -22,6 +22,15 @@
#include <net/sctp/sm.h>
#include <net/sctp/stream_sched.h>
#define SCTP_STRRESET_MASK(type) \
BIT(ntohs(type) - ntohs(SCTP_PARAM_RESET_OUT_REQUEST))
#define SCTP_STRRESET_TEST(asoc, type) \
((asoc)->strreset_outstanding & SCTP_STRRESET_MASK(type))
#define SCTP_STRRESET_SET(asoc, type) \
((asoc)->strreset_outstanding |= SCTP_STRRESET_MASK(type))
#define SCTP_STRRESET_CLEAR(asoc, type) \
((asoc)->strreset_outstanding &= ~SCTP_STRRESET_MASK(type))
static void sctp_stream_shrink_out(struct sctp_stream *stream, __u16 outcnt)
{
struct sctp_association *asoc;
@ -372,7 +381,10 @@ int sctp_send_reset_streams(struct sctp_association *asoc,
goto out;
}
asoc->strreset_outstanding = out + in;
if (out)
SCTP_STRRESET_SET(asoc, SCTP_PARAM_RESET_OUT_REQUEST);
if (in)
SCTP_STRRESET_SET(asoc, SCTP_PARAM_RESET_IN_REQUEST);
out:
return retval;
@ -417,7 +429,7 @@ int sctp_send_reset_assoc(struct sctp_association *asoc)
return retval;
}
asoc->strreset_outstanding = 1;
SCTP_STRRESET_SET(asoc, SCTP_PARAM_RESET_TSN_REQUEST);
return 0;
}
@ -474,7 +486,10 @@ int sctp_send_add_streams(struct sctp_association *asoc,
goto out;
}
asoc->strreset_outstanding = !!out + !!in;
if (out)
SCTP_STRRESET_SET(asoc, SCTP_PARAM_RESET_ADD_OUT_STREAMS);
if (in)
SCTP_STRRESET_SET(asoc, SCTP_PARAM_RESET_ADD_IN_STREAMS);
out:
return retval;
@ -564,13 +579,14 @@ struct sctp_chunk *sctp_process_strreset_outreq(
if (asoc->strreset_chunk) {
if (!sctp_chunk_lookup_strreset_param(
asoc, outreq->response_seq,
SCTP_PARAM_RESET_IN_REQUEST, true)) {
SCTP_PARAM_RESET_IN_REQUEST, true) ||
!SCTP_STRRESET_TEST(asoc, SCTP_PARAM_RESET_IN_REQUEST)) {
/* same process with outstanding isn't 0 */
result = SCTP_STRRESET_ERR_IN_PROGRESS;
goto out;
}
asoc->strreset_outstanding--;
SCTP_STRRESET_CLEAR(asoc, SCTP_PARAM_RESET_IN_REQUEST);
asoc->strreset_outseq++;
if (!asoc->strreset_outstanding) {
@ -669,7 +685,7 @@ struct sctp_chunk *sctp_process_strreset_inreq(
SCTP_SO(stream, i)->state = SCTP_STREAM_CLOSED;
asoc->strreset_chunk = chunk;
asoc->strreset_outstanding = 1;
SCTP_STRRESET_SET(asoc, SCTP_PARAM_RESET_OUT_REQUEST);
sctp_chunk_hold(asoc->strreset_chunk);
result = SCTP_STRRESET_PERFORMED;
@ -816,13 +832,14 @@ struct sctp_chunk *sctp_process_strreset_addstrm_out(
if (asoc->strreset_chunk) {
if (!sctp_chunk_lookup_strreset_param(
asoc, 0, SCTP_PARAM_RESET_ADD_IN_STREAMS, false)) {
asoc, 0, SCTP_PARAM_RESET_ADD_IN_STREAMS, false) ||
!SCTP_STRRESET_TEST(asoc, SCTP_PARAM_RESET_ADD_IN_STREAMS)) {
/* same process with outstanding isn't 0 */
result = SCTP_STRRESET_ERR_IN_PROGRESS;
goto out;
}
asoc->strreset_outstanding--;
SCTP_STRRESET_CLEAR(asoc, SCTP_PARAM_RESET_ADD_IN_STREAMS);
asoc->strreset_outseq++;
if (!asoc->strreset_outstanding) {
@ -899,7 +916,7 @@ struct sctp_chunk *sctp_process_strreset_addstrm_in(
goto out;
asoc->strreset_chunk = chunk;
asoc->strreset_outstanding = 1;
SCTP_STRRESET_SET(asoc, SCTP_PARAM_RESET_ADD_OUT_STREAMS);
sctp_chunk_hold(asoc->strreset_chunk);
stream->outcnt = outcnt;
@ -929,7 +946,7 @@ struct sctp_chunk *sctp_process_strreset_resp(
req = sctp_chunk_lookup_strreset_param(asoc, resp->response_seq, 0,
true);
if (!req)
if (!req || !SCTP_STRRESET_TEST(asoc, req->type))
return NULL;
result = ntohl(resp->result);
@ -1079,7 +1096,7 @@ struct sctp_chunk *sctp_process_strreset_resp(
nums, 0, GFP_ATOMIC);
}
asoc->strreset_outstanding--;
SCTP_STRRESET_CLEAR(asoc, req->type);
asoc->strreset_outseq++;
/* remove everything for this reconf request */