Re: [PATCH nf] netfilter: conntrack: sctp: verify vtag before state changes

Pablo Neira Ayuso <[email protected]> Tue, 28 Jul 2026 21:26:37 +0200
Newsgroups gmane.linux.kernel.stable,gmane.comp.security.firewalls.netfilter.devel,gmane.linux.network,gmane.linux.kernel
Message-ID <amkCbQ1UDRKcZBez@chamomile>
Hi,

On Tue, Jul 28, 2026 at 03:35:06PM +0800, Yizhou Zhao wrote:
> 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.

Are you assuming a specific policy in place? The connection tracking
does not police packets, it just provides tracking.

Can you provide a more specific scenario?

We are seeing several reports related to the connection tracking from
your university lately.

> 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.

Proposed patches to hardening the connection tracking state machine
should be targeted at nf-next.

Thanks.

> Fixes: 9fb9cbb1082d ("[NETFILTER]: Add nf_conntrack subsystem.")
> Cc: [email protected]
> 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
> 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
>