[PATCH nf-next] netfilter: conntrack: sctp: validate vtag before state changes

Yizhou Zhao <[email protected]> Fri, 31 Jul 2026 22:53:41 +0800
Newsgroups gmane.linux.network,gmane.comp.security.firewalls.netfilter.devel,gmane.linux.kernel
Message-ID <[email protected]>
The packet-wide vtag check in nf_conntrack_sctp_packet() is skipped when
the bundle map contains a chunk type with special vtag handling.  The
per-chunk path re-checks INIT, ABORT, SHUTDOWN_COMPLETE, and COOKIE_ECHO,
but other state-changing chunks can still reach sctp_new_state() without
validating sh->vtag.

This lets a wrong-vtag packet bundle HEARTBEAT with COOKIE_ACK, ERROR,
SHUTDOWN, or SHUTDOWN_ACK and update SCTP conntrack state.  The resulting
state can diverge from that of the SCTP association.

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.

Reported-by: Yizhou Zhao <[email protected]>
Reported-by: Yuxiang Yang <[email protected]>
Reported-by: Ao Wang <[email protected]>
Reported-by: Xuewei Feng <[email protected]>
Reported-by: Qi Li <[email protected]>
Reported-by: Ke Xu <[email protected]>
Assisted-by: Claude Code:GLM-5.2
Suggested-by: Pablo Neira Ayuso <[email protected]>
Link: https://lore.kernel.org/netfilter-devel/amx58O5Jb6B29XDJ@chamomile/
Signed-off-by: Yizhou Zhao <[email protected]>
---
 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