git: 6b3bfb16e53d - stable/13 - pf: cope with SCTP port re-use

From: Kristof Provost <kp_at_FreeBSD.org>
Date: Tue, 27 Aug 2024 12:18:38 UTC
The branch stable/13 has been updated by kp:

URL: https://cgit.FreeBSD.org/src/commit/?id=6b3bfb16e53d6cf7afae27e0bf3f6fd09254cfd6

commit 6b3bfb16e53d6cf7afae27e0bf3f6fd09254cfd6
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2024-08-12 16:18:36 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2024-08-27 12:17:59 +0000

    pf: cope with SCTP port re-use
    
    Some SCTP implementations will abort connections and then later re-use the same
    port numbers (i.e. both src and dst) for a new connection, before pf has fully
    purged the old connection.
    
    Apply the same hack we already have for similarly misbehaving TCP
    implementations and forcibly remove the old state so we can create a new one.
    
    MFC after:      2 weeks
    Sponsored by:   Rubicon Communications, LLC ("Netgate")
    
    (cherry picked from commit 82e021443a76b1f210cfb929a495185179606868)
---
 sys/netpfil/pf/pf.c          |  9 +++++++
 tests/sys/netpfil/pf/sctp.sh | 59 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 68 insertions(+)

diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c
index dfef2d132e85..f56037c58572 100644
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -5513,6 +5513,15 @@ pf_test_state_sctp(struct pf_kstate **state, struct pfi_kkif *kif,
 		psrc = PF_PEER_DST;
 	}
 
+	if ((src->state >= SCTP_SHUTDOWN_SENT || src->state == SCTP_CLOSED) &&
+	    (dst->state >= SCTP_SHUTDOWN_SENT || dst->state == SCTP_CLOSED) &&
+	    pd->sctp_flags & PFDESC_SCTP_INIT) {
+		pf_set_protostate(*state, PF_PEER_BOTH, SCTP_CLOSED);
+		pf_unlink_state(*state, PF_ENTER_LOCKED);
+		*state = NULL;
+		return (PF_DROP);
+	}
+
 	/* Track state. */
 	if (pd->sctp_flags & PFDESC_SCTP_INIT) {
 		if (src->state < SCTP_COOKIE_WAIT) {
diff --git a/tests/sys/netpfil/pf/sctp.sh b/tests/sys/netpfil/pf/sctp.sh
index d07d1122048b..95a780747d82 100644
--- a/tests/sys/netpfil/pf/sctp.sh
+++ b/tests/sys/netpfil/pf/sctp.sh
@@ -181,6 +181,64 @@ basic_v6_cleanup()
 	pft_cleanup
 }
 
+atf_test_case "reuse" "cleanup"
+reuse_head()
+{
+	atf_set descr 'Test handling dumb clients that reuse source ports'
+	atf_set require.user root
+}
+
+reuse_body()
+{
+	sctp_init
+
+	j="sctp:reuse"
+	epair=$(vnet_mkepair)
+
+	vnet_mkjail ${j}a ${epair}a
+	vnet_mkjail ${j}b ${epair}b
+
+	jexec ${j}a ifconfig ${epair}a 192.0.2.1/24 up
+	jexec ${j}b ifconfig ${epair}b 192.0.2.2/24 up
+	# Sanity check
+	atf_check -s exit:0 -o ignore \
+	    jexec ${j}a ping -c 1 192.0.2.2
+
+	jexec ${j}a pfctl -e
+	pft_set_rules ${j}a \
+		"block" \
+		"pass in proto sctp to port 1234"
+
+	echo "foo" | jexec ${j}a nc --sctp -N -l 1234 &
+
+	# Wait for the server to start
+	sleep 1
+
+	out=$(jexec ${j}b nc --sctp -N -w 3 -p 1234 192.0.2.1 1234)
+	if [ "$out" != "foo" ]; then
+		atf_fail "SCTP connection failed"
+	fi
+
+	# Now do the same thing again, with the same port numbers
+	jexec ${j}a pfctl -ss -v
+
+	echo "foo" | jexec ${j}a nc --sctp -N -l 1234 &
+
+	# Wait for the server to start
+	sleep 1
+
+	out=$(jexec ${j}b nc --sctp -N -w 3 -p 1234 192.0.2.1 1234)
+	if [ "$out" != "foo" ]; then
+		atf_fail "SCTP connection failed"
+	fi
+	jexec ${j}a pfctl -ss -v
+}
+
+reuse_cleanup()
+{
+	pft_cleanup
+}
+
 atf_test_case "abort_v4" "cleanup"
 abort_v4_head()
 {
@@ -691,6 +749,7 @@ atf_init_test_cases()
 {
 	atf_add_test_case "basic_v4"
 	atf_add_test_case "basic_v6"
+	atf_add_test_case "reuse"
 	atf_add_test_case "abort_v4"
 	atf_add_test_case "abort_v6"
 	atf_add_test_case "nat_v4"