* [PATCH net v2 0/2] sctp: validate INIT in COOKIE-ECHO when auth disabled
@ 2026-06-20 15:09 Xin Long
2026-06-20 15:09 ` [PATCH net v2 1/2] sctp: factor out INIT verification failure handling Xin Long
2026-06-20 15:09 ` [PATCH net v2 2/2] sctp: add INIT verification after cookie unpacking Xin Long
0 siblings, 2 replies; 3+ messages in thread
From: Xin Long @ 2026-06-20 15:09 UTC (permalink / raw)
To: network dev, linux-sctp
Cc: davem, kuba, Eric Dumazet, Paolo Abeni, Simon Horman,
Marcelo Ricardo Leitner
This series fixes a security gap in SCTP's COOKIE-ECHO handling when
cookie authentication is disabled.
Currently, INIT chunks embedded in cookies are not re-verified after
unpacking, creating a vulnerability when cookie_auth_enable=0. This
series first refactors error handling, then adds the missing validation.
Changes in v2: see individual patch changelogs for details.
Xin Long (2):
sctp: factor out INIT verification failure handling
sctp: add INIT verification after cookie unpacking
net/sctp/sm_make_chunk.c | 3 +-
net/sctp/sm_statefuns.c | 220 ++++++++++++++++++++-------------------
2 files changed, 117 insertions(+), 106 deletions(-)
--
2.47.1
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH net v2 1/2] sctp: factor out INIT verification failure handling
2026-06-20 15:09 [PATCH net v2 0/2] sctp: validate INIT in COOKIE-ECHO when auth disabled Xin Long
@ 2026-06-20 15:09 ` Xin Long
2026-06-20 15:09 ` [PATCH net v2 2/2] sctp: add INIT verification after cookie unpacking Xin Long
1 sibling, 0 replies; 3+ messages in thread
From: Xin Long @ 2026-06-20 15:09 UTC (permalink / raw)
To: network dev, linux-sctp
Cc: davem, kuba, Eric Dumazet, Paolo Abeni, Simon Horman,
Marcelo Ricardo Leitner
Extract the duplicated INIT/INIT-ACK error handling logic into a new
helper, sctp_abort_on_init_err().
Several state functions open-code the same pattern after
sctp_verify_init() fails: construct an ABORT with error causes if
available, send it when allocation succeeds, or fall back to T-bit ABORT
handling when no error chunk is present. INIT-ACK handling also includes
additional teardown logic for malformed packets.
Move this logic into sctp_abort_on_init_err() to reduce duplication and
centralize INIT/INIT-ACK failure handling.
No functional change intended. The helper will be used in a subsequent
patch.
Signed-off-by: Xin Long <lucien.xin@gmail.com>
---
v2:
- Pass cid to sctp_abort_on_init_err().
- Delete chunk param from sctp_abort_on_init_err() and get chunk from
arg param.
- Jump to label 'out:' when err_chunk is NULL and cid is INIT_ACK in
sctp_abort_on_init_err(), noted by Sashiko.
---
net/sctp/sm_statefuns.c | 187 ++++++++++++++++++----------------------
1 file changed, 85 insertions(+), 102 deletions(-)
diff --git a/net/sctp/sm_statefuns.c b/net/sctp/sm_statefuns.c
index 9b23c11cbb9e..8c636f045e45 100644
--- a/net/sctp/sm_statefuns.c
+++ b/net/sctp/sm_statefuns.c
@@ -68,6 +68,13 @@ static void sctp_send_stale_cookie_err(struct net *net,
const struct sctp_chunk *chunk,
struct sctp_cmd_seq *commands,
struct sctp_chunk *err_chunk);
+static enum sctp_disposition sctp_abort_on_init_err(
+ struct net *net,
+ const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ enum sctp_cid cid, void *arg,
+ struct sctp_cmd_seq *commands,
+ struct sctp_chunk *err_chunk);
static enum sctp_disposition sctp_sf_do_5_2_6_stale(
struct net *net,
const struct sctp_endpoint *ep,
@@ -325,7 +332,7 @@ enum sctp_disposition sctp_sf_do_5_1B_init(struct net *net,
struct sctp_chunk *chunk = arg, *repl, *err_chunk;
struct sctp_unrecognized_param *unk_param;
struct sctp_association *new_asoc;
- struct sctp_packet *packet;
+ enum sctp_cid cid;
int len;
/* 6.10 Bundling
@@ -373,34 +380,12 @@ enum sctp_disposition sctp_sf_do_5_1B_init(struct net *net,
/* Verify the INIT chunk before processing it. */
err_chunk = NULL;
- if (!sctp_verify_init(net, ep, asoc, chunk->chunk_hdr->type,
+ cid = chunk->chunk_hdr->type;
+ if (!sctp_verify_init(net, ep, asoc, cid,
(struct sctp_init_chunk *)chunk->chunk_hdr, chunk,
- &err_chunk)) {
- /* This chunk contains fatal error. It is to be discarded.
- * Send an ABORT, with causes if there is any.
- */
- 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);
- return SCTP_DISPOSITION_CONSUME;
- } else {
- return SCTP_DISPOSITION_NOMEM;
- }
- } else {
- return sctp_sf_tabort_8_4_8(net, ep, asoc, type, arg,
- commands);
- }
- }
+ &err_chunk))
+ return sctp_abort_on_init_err(net, ep, asoc, cid, arg, commands,
+ err_chunk);
/* Grab the INIT header. */
chunk->subh.init_hdr = (struct sctp_inithdr *)chunk->skb->data;
@@ -525,7 +510,7 @@ enum sctp_disposition sctp_sf_do_5_1C_ack(struct net *net,
struct sctp_init_chunk *initchunk;
struct sctp_chunk *chunk = arg;
struct sctp_chunk *err_chunk;
- struct sctp_packet *packet;
+ enum sctp_cid cid;
if (!sctp_vtag_verify(chunk, asoc))
return sctp_sf_pdiscard(net, ep, asoc, type, arg, commands);
@@ -546,52 +531,12 @@ enum sctp_disposition sctp_sf_do_5_1C_ack(struct net *net,
/* Verify the INIT chunk before processing it. */
err_chunk = NULL;
- if (!sctp_verify_init(net, ep, asoc, chunk->chunk_hdr->type,
+ cid = chunk->chunk_hdr->type;
+ if (!sctp_verify_init(net, ep, asoc, cid,
(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, cid, arg, commands,
+ err_chunk);
/* Tag the variable length parameters. Note that we never
* convert the parameters in an INIT chunk.
@@ -1522,7 +1467,7 @@ static enum sctp_disposition sctp_sf_do_unexpected_init(
struct sctp_unrecognized_param *unk_param;
struct sctp_association *new_asoc;
enum sctp_disposition retval;
- struct sctp_packet *packet;
+ enum sctp_cid cid;
int len;
/* 6.10 Bundling
@@ -1564,33 +1509,12 @@ static enum sctp_disposition sctp_sf_do_unexpected_init(
/* Verify the INIT chunk before processing it. */
err_chunk = NULL;
- if (!sctp_verify_init(net, ep, asoc, chunk->chunk_hdr->type,
+ cid = chunk->chunk_hdr->type;
+ if (!sctp_verify_init(net, ep, asoc, cid,
(struct sctp_init_chunk *)chunk->chunk_hdr, chunk,
- &err_chunk)) {
- /* This chunk contains fatal error. It is to be discarded.
- * Send an ABORT, with causes if there is any.
- */
- 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));
-
- if (packet) {
- sctp_add_cmd_sf(commands, SCTP_CMD_SEND_PKT,
- SCTP_PACKET(packet));
- SCTP_INC_STATS(net, SCTP_MIB_OUTCTRLCHUNKS);
- retval = SCTP_DISPOSITION_CONSUME;
- } else {
- retval = SCTP_DISPOSITION_NOMEM;
- }
- goto cleanup;
- } else {
- return sctp_sf_tabort_8_4_8(net, ep, asoc, type, arg,
- commands);
- }
- }
+ &err_chunk))
+ return sctp_abort_on_init_err(net, ep, asoc, cid, arg, commands,
+ err_chunk);
/*
* Other parameters for the endpoint SHOULD be copied from the
@@ -1691,7 +1615,6 @@ static enum sctp_disposition sctp_sf_do_unexpected_init(
nomem_retval:
if (new_asoc)
sctp_association_free(new_asoc);
-cleanup:
if (err_chunk)
sctp_chunk_free(err_chunk);
return retval;
@@ -6485,6 +6408,66 @@ 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,
+ enum sctp_cid cid, void *arg,
+ struct sctp_cmd_seq *commands,
+ struct sctp_chunk *err_chunk)
+{
+ enum sctp_error error = SCTP_ERROR_NO_RESOURCE;
+ struct sctp_chunk *chunk = arg;
+ struct sctp_packet *packet;
+ struct sctp_chunkhdr *ch;
+
+ if (!err_chunk) {
+ if (cid == SCTP_CID_INIT_ACK)
+ goto out;
+ 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 (cid != SCTP_CID_INIT_ACK) {
+ if (!packet)
+ return SCTP_DISPOSITION_NOMEM;
+ return SCTP_DISPOSITION_CONSUME;
+ }
+
+out:
+ /* 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);
+}
/* Process a data chunk */
static int sctp_eat_data(const struct sctp_association *asoc,
--
2.47.1
^ permalink raw reply related [flat|nested] 3+ messages in thread
* [PATCH net v2 2/2] sctp: add INIT verification after cookie unpacking
2026-06-20 15:09 [PATCH net v2 0/2] sctp: validate INIT in COOKIE-ECHO when auth disabled Xin Long
2026-06-20 15:09 ` [PATCH net v2 1/2] sctp: factor out INIT verification failure handling Xin Long
@ 2026-06-20 15:09 ` Xin Long
1 sibling, 0 replies; 3+ messages in thread
From: Xin Long @ 2026-06-20 15:09 UTC (permalink / raw)
To: network dev, linux-sctp
Cc: davem, kuba, Eric Dumazet, Paolo Abeni, Simon Horman,
Marcelo Ricardo Leitner
In SCTP handshake, the INIT chunk is initially processed by the server
and embedded into the cookie carried in INIT-ACK. The client then
returns this cookie via COOKIE-ECHO, where the server unpacks it and
reconstructs the original INIT chunk.
When cookie authentication is enabled, the cookie contents are protected
against tampering, so reusing the unpacked INIT without re-verification
is safe.
However, when cookie authentication is disabled, the reconstructed INIT
can no longer be trusted. In this case, the INIT must be explicitly
validated after unpacking to avoid processing potentially tampered data.
Add sctp_verify_init() checks after cookie unpacking in COOKIE-ECHO
processing paths (sctp_sf_do_5_1D_ce() and sctp_sf_do_5_2_4_dupcook())
when cookie_auth_enable is disabled. On failure, the association is
freed and an ABORT is generated via sctp_abort_on_init_err().
Also update sctp_verify_init() to validate parameter bounds against the
actual peer_init length rather than chunk->chunk_end, since peer_init
may not span the full chunk buffer in COOKIE-ECHO.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Xin Long <lucien.xin@gmail.com>
---
v2:
- Because of sctp_abort_on_init_err() param change in patch 1/2,
pass cid and not chunk.
- Use SCTP_PAD4() around ntohs(peer_init->chunk_hdr.length) when
checking param.v in sctp_verify_init() to make Sashiko happy.
---
net/sctp/sm_make_chunk.c | 3 ++-
net/sctp/sm_statefuns.c | 33 ++++++++++++++++++++++++++++++---
2 files changed, 32 insertions(+), 4 deletions(-)
diff --git a/net/sctp/sm_make_chunk.c b/net/sctp/sm_make_chunk.c
index 41958b8e59fd..d5ee81934d93 100644
--- a/net/sctp/sm_make_chunk.c
+++ b/net/sctp/sm_make_chunk.c
@@ -2298,7 +2298,8 @@ int sctp_verify_init(struct net *net, const struct sctp_endpoint *ep,
* VIOLATION error. We build the ERROR chunk here and let the normal
* error handling code build and send the packet.
*/
- if (param.v != (void *)chunk->chunk_end)
+ if (param.v != (void *)peer_init +
+ SCTP_PAD4(ntohs(peer_init->chunk_hdr.length)))
return sctp_process_inv_paramlength(asoc, param.p, chunk, errp);
/* The only missing mandatory param possible today is
diff --git a/net/sctp/sm_statefuns.c b/net/sctp/sm_statefuns.c
index 8c636f045e45..6967e889d1bd 100644
--- a/net/sctp/sm_statefuns.c
+++ b/net/sctp/sm_statefuns.c
@@ -650,11 +650,12 @@ enum sctp_disposition sctp_sf_do_5_1D_ce(struct net *net,
struct sctp_cmd_seq *commands)
{
struct sctp_ulpevent *ev, *ai_ev = NULL, *auth_ev = NULL;
+ struct sctp_chunk *err_chk_p = NULL;
struct sctp_association *new_asoc;
struct sctp_init_chunk *peer_init;
struct sctp_chunk *chunk = arg;
- struct sctp_chunk *err_chk_p;
struct sctp_chunk *repl;
+ enum sctp_cid cid;
struct sock *sk;
int error = 0;
@@ -728,6 +729,18 @@ enum sctp_disposition sctp_sf_do_5_1D_ce(struct net *net,
}
}
+ peer_init = (struct sctp_init_chunk *)(chunk->subh.cookie_hdr + 1);
+ cid = peer_init->chunk_hdr.type;
+ if (!sctp_sk(sk)->cookie_auth_enable &&
+ !sctp_verify_init(net, ep, asoc, cid, peer_init, chunk,
+ &err_chk_p)) {
+ sctp_association_free(new_asoc);
+ return sctp_abort_on_init_err(net, ep, asoc, cid, arg, commands,
+ err_chk_p);
+ }
+ if (err_chk_p)
+ sctp_chunk_free(err_chk_p);
+
if (security_sctp_assoc_request(new_asoc, chunk->head_skb ?: chunk->skb)) {
sctp_association_free(new_asoc);
return sctp_sf_pdiscard(net, ep, asoc, type, arg, commands);
@@ -741,7 +754,6 @@ enum sctp_disposition sctp_sf_do_5_1D_ce(struct net *net,
/* This is a brand-new association, so these are not yet side
* effects--it is safe to run them here.
*/
- peer_init = (struct sctp_init_chunk *)(chunk->subh.cookie_hdr + 1);
if (!sctp_process_init(new_asoc, chunk,
&chunk->subh.cookie_hdr->c.peer_addr,
peer_init, GFP_ATOMIC))
@@ -2133,10 +2145,12 @@ enum sctp_disposition sctp_sf_do_5_2_4_dupcook(
void *arg,
struct sctp_cmd_seq *commands)
{
+ struct sctp_chunk *err_chk_p = NULL;
struct sctp_association *new_asoc;
+ struct sctp_init_chunk *peer_init;
struct sctp_chunk *chunk = arg;
enum sctp_disposition retval;
- struct sctp_chunk *err_chk_p;
+ enum sctp_cid cid;
int error = 0;
char action;
@@ -2205,6 +2219,19 @@ enum sctp_disposition sctp_sf_do_5_2_4_dupcook(
switch (action) {
case 'A': /* Association restart. */
case 'B': /* Collision case B. */
+ peer_init = (struct sctp_init_chunk *)
+ (chunk->subh.cookie_hdr + 1);
+ cid = peer_init->chunk_hdr.type;
+ if (!sctp_sk(ep->base.sk)->cookie_auth_enable &&
+ !sctp_verify_init(net, ep, asoc, cid, peer_init, chunk,
+ &err_chk_p)) {
+ sctp_association_free(new_asoc);
+ return sctp_abort_on_init_err(net, ep, asoc, cid, arg,
+ commands, err_chk_p);
+ }
+ if (err_chk_p)
+ sctp_chunk_free(err_chk_p);
+ fallthrough;
case 'D': /* Collision case D. */
/* Update socket peer label if first association. */
if (security_sctp_assoc_request((struct sctp_association *)asoc,
--
2.47.1
^ permalink raw reply related [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-06-20 15:10 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-20 15:09 [PATCH net v2 0/2] sctp: validate INIT in COOKIE-ECHO when auth disabled Xin Long
2026-06-20 15:09 ` [PATCH net v2 1/2] sctp: factor out INIT verification failure handling Xin Long
2026-06-20 15:09 ` [PATCH net v2 2/2] sctp: add INIT verification after cookie unpacking Xin Long
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.