The packet-wide vtag check in nf_conntrack_sctp_packet() is skipped when
the bundle map contains any chunk type with special vtag handling. The
per-chunk path re-checks INIT, ABORT, SHUTDOWN_COMPLETE, and
COOKIE_ECHO, but other state-changing chunks still fall through to
sctp_new_state() without validating sh->vtag.
This lets a wrong-vtag packet bundle HEARTBEAT with COOKIE_ACK, ERROR,
SHUTDOWN, or SHUTDOWN_ACK and still advance conntrack state. That can
desynchronize conntrack from the real SCTP association and cause denial
of service for SCTP traffic behind a stateful firewall.
Check sh->vtag before processing those chunks when conntrack already
knows the expected direction vtag. This keeps the existing
HEARTBEAT/HEARTBEAT_ACK learning and connection-reuse behavior for the
`vtag == 0` cases.
Fixes: 9fb9cbb1082d ("[NETFILTER]: Add nf_conntrack subsystem.")
Cc: stable@vger.kernel.org
Reported-by: Yizhou Zhao <redacted>
Reported-by: Yuxiang Yang <redacted>
Reported-by: Ao Wang <redacted>
Reported-by: Xuewei Feng <redacted>
Reported-by: Qi Li <redacted>
Reported-by: Ke Xu <redacted>
Assisted-by: Claude-Code:GLM-5.2
Signed-off-by: Yizhou Zhao <redacted>
---
net/netfilter/nf_conntrack_proto_sctp.c | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/net/netfilter/nf_conntrack_proto_sctp.c b/net/netfilter/nf_conntrack_proto_sctp.c
index 7e10fa65cbdd..9978ecd05b1e 100644
--- a/net/netfilter/nf_conntrack_proto_sctp.c
+++ b/net/netfilter/nf_conntrack_proto_sctp.c
@@ -407,9 +407,17 @@ int nf_conntrack_sctp_packet(struct nf_conn *ct,
/* (D) vtag must be same as init_vtag as found in INIT_ACK */
if (sh->vtag != ct->proto.sctp.vtag[dir])
goto out_unlock;
- } else if (sch->type == SCTP_CID_COOKIE_ACK) {
- ct->proto.sctp.init[dir] = 0;
- ct->proto.sctp.init[!dir] = 0;
+ } else if (sch->type == SCTP_CID_COOKIE_ACK ||
+ sch->type == SCTP_CID_ERROR ||
+ sch->type == SCTP_CID_SHUTDOWN ||
+ sch->type == SCTP_CID_SHUTDOWN_ACK) {
+ if (ct->proto.sctp.vtag[dir] &&
+ sh->vtag != ct->proto.sctp.vtag[dir])
+ goto out_unlock;
+ if (sch->type == SCTP_CID_COOKIE_ACK) {
+ ct->proto.sctp.init[dir] = 0;
+ ct->proto.sctp.init[!dir] = 0;
+ }
} else if (sch->type == SCTP_CID_HEARTBEAT) {
if (ct->proto.sctp.vtag[dir] == 0) {
pr_debug("Setting %d vtag %x for dir %d\n", sch->type, sh->vtag, dir);--
2.47.3