Skip to contentSkip to conversation

sctp: fix a vtag verification failure caused by stale INITs

PATCH Applied Xin Long 3 participants 6 messages Apr 26 – Apr 29

Subsystems (4)
lore
Share & tools
lore

Xin Long

author maintainer
#0 lore raw

v2 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
author maintainer
#1 lore raw

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

Fixes 9fb9cbb1082d ("[NETFILTER]: Add nf_conntrack subsystem.")
---
 net/netfilter/nf_conntrack_proto_sctp.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)
net/netfilter/nf_conntrack_proto_sctp.c +7 -3
index 645d2c43ebf7..7e10fa65cbdd 100644
@@ -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
author maintainer
#2 lore raw

PATCH v2 2/2 sctp: discard stale INIT after handshake completion

In reply to Xin Long

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

Fixes 1da177e4c3f4 ("Linux-2.6.12-rc2")

---
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
index 7b823d759141..8e89a870780c 100644
@@ -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
reviewer
#3 lore raw
In reply to Xin Long
Quoted 42 lines · Xin Long

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.

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.

author maintainer
#4 lore raw
Quoted 48 lines · Simon Horman
Quoted 46 lines · Xin Long

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.

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.

#5 lore raw
In reply to Xin Long
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 in nf_conntrack_proto_sctp"):

Scenario B: INIT_ACK is 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