git: 485bd1630810 - stable/14 - 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/14 has been updated by kp:

URL: https://cgit.FreeBSD.org/src/commit/?id=485bd16308108f84df7b2768011a65f3dc97db9b

commit 485bd16308108f84df7b2768011a65f3dc97db9b
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2024-08-12 16:18:36 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2024-08-27 08:09:10 +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 381d0f8b193e..fe3ae843f68a 100644
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -6106,6 +6106,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);
+		*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"