sctp: fix a vtag verification failure caused by stale INITs
Subsystems (4)
Xin Long
maintainerv2 cover sctp: fix a vtag verification failure caused by stale INITs
Similar to Scenario B in commit 8e56b063c865 ( netfilter: handle the connecting collision properly in nf_conntrack_proto_sctp"):
Scenario B: INIT_ACK is delayed until the peer completes its own handshake
192.168.1.2 > 192.168.1.1: sctp (1) [INIT] [init tag: 3922216408]
192.168.1.1 > 192.168.1.2: sctp (1) [INIT] [init tag: 144230885]
192.168.1.2 > 192.168.1.1: sctp (1) [INIT ACK] [init tag: 3922216408]
192.168.1.1 > 192.168.1.2: sctp (1) [COOKIE ECHO]
192.168.1.2 > 192.168.1.1: sctp (1) [COOKIE ACK]192.168.1.1 > 192.168.1.2: sctp (1) [INIT ACK] [init tag: 3914796021] *
There is another case:
Scenario F: INIT is delayed until the peer completes its own handshake
192.168.1.2 > 192.168.1.1: sctp (1) [INIT] [init tag: 3922216408] (OVS upcall)
192.168.1.1 > 192.168.1.2: sctp (1) [INIT] [init tag: 144230885]
192.168.1.2 > 192.168.1.1: sctp (1) [INIT ACK] [init tag: 3922216408]
192.168.1.1 > 192.168.1.2: sctp (1) [COOKIE ECHO]
192.168.1.2 > 192.168.1.1: sctp (1) [COOKIE ACK] 192.168.1.2 > 192.168.1.1: sctp (1) [INIT] [init tag: 3922216408] (delayed)
192.168.1.1 > 192.168.1.2: sctp (1) [INIT ACK] [init tag: 3914796021] *
In this case, the delayed INIT (e.g. due to OVS upcall) is recorded by conntrack, which prevents vtag verification from dropping the unexpected INIT-ACK in nf_conntrack_sctp_packet():
vtag = ct->proto.sctp.vtag[!dir];
if (!ct->proto.sctp.init[!dir] && vtag && vtag != ih->init_tag)
goto out_unlock;This happens because ct->proto.sctp.init[!dir] is set by the delayed INIT, even though it is stale.
Fix this in two parts:
- In netfilter: Do not record INITs whose init_tag matches the peer vtag, as they carry no new handshake state in the 1st patch.
- In SCTP: Prevent endpoints from responding to such INITs with INIT-ACK, ensuring correctness even when middleboxes lack the netfilter fix in the 2nd patch.
A follow-up selftest for this scenario will be posted in a separate patch by Yi Chen.
Xin Long (2):
netfilter: skip recording stale or retransmitted INIT
sctp: discard stale INIT after handshake completion
net/netfilter/nf_conntrack_proto_sctp.c | 10 +++++++--- net/sctp/sm_statefuns.c | 6 ++++++ 2 files changed, 13 insertions(+), 3 deletions(-)
Signature · 1 line
2.47.1
Xin Long
maintainerPATCH v2 1/2 netfilter: skip recording stale or retransmitted INIT
An INIT whose init_tag matches the peer's vtag does not provide new state information. It indicates either:
- a stale INIT (after INIT-ACK has already been seen on the same side), or
- a retransmitted INIT (after INIT has already been recorded on the same side).
In both cases, the INIT must not update ct->proto.sctp.init[] state, since it does not advance the handshake tracking and may otherwise corrupt INIT/INIT-ACK validation logic.
Allow INIT processing only when the conntrack entry is newly created (SCTP_CONNTRACK_NONE), or when the init_tag differs from the stored peer vtag.
Note it skips the check for the ct with old_state SCTP_CONNTRACK_NONE in nf_conntrack_sctp_packet(), as it is just created in sctp_new() where it set ct->proto.sctp.vtag[IP_CT_DIR_REPLY] = ih->init_tag.
--- net/netfilter/nf_conntrack_proto_sctp.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-)
net/netfilter/nf_conntrack_proto_sctp.c +7 -3
@@ -466,9 +466,13 @@ int nf_conntrack_sctp_packet(struct nf_conn *ct,if (!ih) goto out_unlock; -if (ct->proto.sctp.init[dir] && ct->proto.sctp.init[!dir]) - ct->proto.sctp.init[!dir] =0 ; - ct->proto.sctp.init[dir] =1 ; +/* Do not record INIT matching peer vtag (stale or retransmitted INIT). */ +if (old_state ==SCTP_CONNTRACK_NONE || + ct->proto.sctp.vtag[!dir] != ih->init_tag) { +if (ct->proto.sctp.init[dir] && ct->proto.sctp.init[!dir]) + ct->proto.sctp.init[!dir] =0 ; + ct->proto.sctp.init[dir] =1 ; + }pr_debug ("Setting vtag %x for dir %d\n" , ih->init_tag, !dir); ct->proto.sctp.vtag[!dir] = ih->init_tag;
Signature · 1 line
2.47.1
Xin Long
maintainerPATCH v2 2/2 sctp: discard stale INIT after handshake completion
In reply to Xin LongAfter an association reaches ESTABLISHED, the peer’s init_tag is already known from the handshake. Any subsequent INIT with the same init_tag is not a valid restart, but a delayed or duplicate INIT.
Drop such INIT chunks in sctp_sf_do_unexpected_init() instead of processing them as new association attempts.
---
v2:
- Fix INIT tag comparison by converting the on-wire init_tag to host byte
order before comparing it with asoc->peer.i.init_tag. --- net/sctp/sm_statefuns.c | 6 ++++++ 1 file changed, 6 insertions(+)
net/sctp/sm_statefuns.c +6 -0
@@ -1556,6 +1556,12 @@ static enum sctp_disposition sctp_sf_do_unexpected_init(/* Tag the variable length parameters. */ chunk->param_hdr.v =skb_pull (chunk->skb,sizeof (struct sctp_inithdr )); +if (asoc->state >=SCTP_STATE_ESTABLISHED ) { +/* Discard INIT matching peer vtag after handshake completion (stale INIT). */ +if (ntohl (chunk->subh.init_hdr->init_tag) == asoc->peer.i.init_tag) +return sctp_sf_pdiscard (net, ep, asoc, type, arg, commands); + } +/* Verify the INIT chunk before processing it. */ err_chunk =NULL ; if (!sctp_verify_init(net, ep, asoc, chunk->chunk_hdr->type,
Signature · 1 line
2.47.1
Simon Horman
reviewerQuoted 42 lines · Xin Long
Similar to Scenario B in commit
8e56b063c865( netfilter: handle the connecting collision properly innf_conntrack_proto_sctp"):Scenario B:
INIT_ACKis delayed until the peer completes its own handshake192.168.1.2 > 192.168.1.1: sctp (1) [INIT] [init tag: 3922216408]
192.168.1.1 > 192.168.1.2: sctp (1) [INIT] [init tag: 144230885] 192.168.1.2 > 192.168.1.1: sctp (1) [INIT ACK] [init tag: 3922216408] 192.168.1.1 > 192.168.1.2: sctp (1) [COOKIE ECHO] 192.168.1.2 > 192.168.1.1: sctp (1) [COOKIE ACK]192.168.1.1 > 192.168.1.2: sctp (1) [INIT ACK] [init tag: 3914796021] *
There is another case:
Scenario F: INIT is delayed until the peer completes its own handshake
192.168.1.2 > 192.168.1.1: sctp (1) [INIT] [init tag: 3922216408] (OVS upcall)
192.168.1.1 > 192.168.1.2: sctp (1) [INIT] [init tag: 144230885] 192.168.1.2 > 192.168.1.1: sctp (1) [INIT ACK] [init tag: 3922216408] 192.168.1.1 > 192.168.1.2: sctp (1) [COOKIE ECHO] 192.168.1.2 > 192.168.1.1: sctp (1) [COOKIE ACK]192.168.1.2 > 192.168.1.1: sctp (1) [INIT] [init tag: 3922216408] (delayed)
192.168.1.1 > 192.168.1.2: sctp (1) [INIT ACK] [init tag: 3914796021] *In this case, the delayed INIT (e.g. due to OVS upcall) is recorded by conntrack, which prevents vtag verification from dropping the unexpected INIT-ACK in
nf_conntrack_sctp_packet():vtag = ct->proto.sctp.vtag[!dir]; if (!ct->proto.sctp.init[!dir] && vtag && vtag != ih->init_tag) goto out_unlock;This happens because ct->proto.sctp.init[!dir] is set by the delayed INIT, even though it is stale.
Fix this in two parts:
- In netfilter: Do not record INITs whose
init_tagmatches the peer vtag, as they carry no new handshake state in the 1st patch.
Quoted 6 lines
- In SCTP: Prevent endpoints from responding to such INITs with INIT-ACK, ensuring correctness even when middleboxes lack the netfilter fix in the 2nd patch.
A follow-up selftest for this scenario will be posted in a separate patch by Yi Chen.
Hi Xin,
FTR: There is an AI generated review of this patchset available on sashiko.dev. I have looked over this and I do not believe the feedback there should block progress of this patchset.
Xin Long
maintainerQuoted 48 lines · Simon Horman
Quoted 46 lines · Xin Long
Similar to Scenario B in commit
8e56b063c865( netfilter: handle the connecting collision properly innf_conntrack_proto_sctp"):Scenario B:
INIT_ACKis delayed until the peer completes its own handshake192.168.1.2 > 192.168.1.1: sctp (1) [INIT] [init tag: 3922216408]
192.168.1.1 > 192.168.1.2: sctp (1) [INIT] [init tag: 144230885] 192.168.1.2 > 192.168.1.1: sctp (1) [INIT ACK] [init tag: 3922216408] 192.168.1.1 > 192.168.1.2: sctp (1) [COOKIE ECHO] 192.168.1.2 > 192.168.1.1: sctp (1) [COOKIE ACK]192.168.1.1 > 192.168.1.2: sctp (1) [INIT ACK] [init tag: 3914796021] *
There is another case:
Scenario F: INIT is delayed until the peer completes its own handshake
192.168.1.2 > 192.168.1.1: sctp (1) [INIT] [init tag: 3922216408] (OVS upcall)
192.168.1.1 > 192.168.1.2: sctp (1) [INIT] [init tag: 144230885] 192.168.1.2 > 192.168.1.1: sctp (1) [INIT ACK] [init tag: 3922216408] 192.168.1.1 > 192.168.1.2: sctp (1) [COOKIE ECHO] 192.168.1.2 > 192.168.1.1: sctp (1) [COOKIE ACK]192.168.1.2 > 192.168.1.1: sctp (1) [INIT] [init tag: 3922216408] (delayed)
192.168.1.1 > 192.168.1.2: sctp (1) [INIT ACK] [init tag: 3914796021] *In this case, the delayed INIT (e.g. due to OVS upcall) is recorded by conntrack, which prevents vtag verification from dropping the unexpected INIT-ACK in
nf_conntrack_sctp_packet():vtag = ct->proto.sctp.vtag[!dir]; if (!ct->proto.sctp.init[!dir] && vtag && vtag != ih->init_tag) goto out_unlock;This happens because ct->proto.sctp.init[!dir] is set by the delayed INIT, even though it is stale.
Fix this in two parts:
- In netfilter: Do not record INITs whose
init_tagmatches the peer vtag, as they carry no new handshake state in the 1st patch.- In SCTP: Prevent endpoints from responding to such INITs with INIT-ACK, ensuring correctness even when middleboxes lack the netfilter fix in the 2nd patch.
Quoted 8 lines
Quoted 2 lines
A follow-up selftest for this scenario will be posted in a separate patch by Yi Chen.
Hi Xin,
FTR: There is an AI generated review of this patchset available on sashiko.dev. I have looked over this and I do not believe the feedback there should block progress of this patchset.
Right, the feedback is false in practice::
- "No response" is not a clean signal
(could be loss, firewall, rate limiting, etc.).
- Even guessing this init_tag does not let attackers hijack the association (they still lack the correct verification tag and state).
Thanks.
This series was applied to netdev/net.git (main) by Jakub Kicinski <[email protected]>:
Hello:
This series was applied to netdev/net.git (main)
by Jakub Kicinski <[email protected]>:
Quoted 5 lines · On Sun, 26 Apr 2026 10:46:39 -0400 you wrote:
Similar to Scenario B in commit
8e56b063c865( netfilter: handle the connecting collision properly innf_conntrack_proto_sctp"):Scenario B:
INIT_ACKis delayed until the peer completes its own handshake
Quoted 8 lines
192.168.1.2 > 192.168.1.1: sctp (1) [INIT] [init tag: 3922216408]
192.168.1.1 > 192.168.1.2: sctp (1) [INIT] [init tag: 144230885] 192.168.1.2 > 192.168.1.1: sctp (1) [INIT ACK] [init tag: 3922216408] 192.168.1.1 > 192.168.1.2: sctp (1) [COOKIE ECHO] 192.168.1.2 > 192.168.1.1: sctp (1) [COOKIE ACK]192.168.1.1 > 192.168.1.2: sctp (1) [INIT ACK] [init tag: 3914796021] *
[...]
Here is the summary with links:
- [net,v2,1/2] netfilter: skip recording stale or retransmitted INIT
https://git.kernel.org/netdev/net/c/576a5d2bad48 - [net,v2,2/2] sctp: discard stale INIT after handshake completion https://git.kernel.org/netdev/net/c/8a92cb475ca9
You are awesome, thank you!
Signature · 2 lines
Deet-doot-dot, I am a bot. https://korg.docs.kernel.org/patchwork/pwbot.html