git: 0b42158e5ef5 - releng/14.0 - pf: cope with missing rpool.cur

From: Kristof Provost <kp_at_FreeBSD.org>
Date: Sat, 07 Oct 2023 17:16:55 UTC
The branch releng/14.0 has been updated by kp:

URL: https://cgit.FreeBSD.org/src/commit/?id=0b42158e5ef5b2d9ed05bc8dff7fbfd106e1bcac

commit 0b42158e5ef5b2d9ed05bc8dff7fbfd106e1bcac
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2023-10-03 15:11:44 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2023-10-07 17:16:25 +0000

    pf: cope with missing rpool.cur
    
    If we're evaluating a pfsync'd state (and have different rules on both
    ends) our state may point to the default rule, which does not have
    rpool.cur set. As a result we can end up dereferencing a NULL pointer.
    
    Explicitly check for this when we try to re-construct the route-to interface.
    
    Also add a test case which can trigger this issue.
    
    MFC after:      3 days
    See also:       https://redmine.pfsense.org/issues/14804
    Sponsored by:   Rubicon Communications, LLC ("Netgate")
    Approved by:    gjb (re)
    
    (cherry picked from commit 74c2461386ea5eeb41e674df6b16a44b0509a882)
    (cherry picked from commit f69181e9de1b021f4689ce50b420f9c694268ec8)
---
 sys/netpfil/pf/pf.c            |  9 ++--
 tests/sys/netpfil/pf/pfsync.sh | 96 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 101 insertions(+), 4 deletions(-)

diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c
index a5d7c1ba0155..0973c829644a 100644
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -6737,7 +6737,7 @@ pf_route(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp,
 			} else {
 				ifp = s->rt_kif ? s->rt_kif->pfik_ifp : NULL;
 				/* If pfsync'd */
-				if (ifp == NULL)
+				if (ifp == NULL && r->rpool.cur != NULL)
 					ifp = r->rpool.cur->kif ?
 					    r->rpool.cur->kif->pfik_ifp : NULL;
 				PF_STATE_UNLOCK(s);
@@ -6794,9 +6794,10 @@ pf_route(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp,
 			    s->rt_addr.v4.s_addr;
 		ifp = s->rt_kif ? s->rt_kif->pfik_ifp : NULL;
 		/* If pfsync'd */
-		if (ifp == NULL)
+		if (ifp == NULL && r->rpool.cur != NULL) {
 			ifp = r->rpool.cur->kif ?
 			    r->rpool.cur->kif->pfik_ifp : NULL;
+		}
 		PF_STATE_UNLOCK(s);
 	}
 
@@ -6950,7 +6951,7 @@ pf_route6(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp,
 			} else {
 				ifp = s->rt_kif ? s->rt_kif->pfik_ifp : NULL;
 				/* If pfsync'd */
-				if (ifp == NULL)
+				if (ifp == NULL && r->rpool.cur != NULL)
 					ifp = r->rpool.cur->kif ?
 					    r->rpool.cur->kif->pfik_ifp : NULL;
 				PF_STATE_UNLOCK(s);
@@ -7008,7 +7009,7 @@ pf_route6(struct mbuf **m, struct pf_krule *r, struct ifnet *oifp,
 			    &s->rt_addr, AF_INET6);
 		ifp = s->rt_kif ? s->rt_kif->pfik_ifp : NULL;
 		/* If pfsync'd */
-		if (ifp == NULL)
+		if (ifp == NULL && r->rpool.cur != NULL)
 			ifp = r->rpool.cur->kif ?
 			    r->rpool.cur->kif->pfik_ifp : NULL;
 	}
diff --git a/tests/sys/netpfil/pf/pfsync.sh b/tests/sys/netpfil/pf/pfsync.sh
index d62cdddd504a..91c7b8410a08 100644
--- a/tests/sys/netpfil/pf/pfsync.sh
+++ b/tests/sys/netpfil/pf/pfsync.sh
@@ -825,6 +825,101 @@ basic_ipv6_cleanup()
 	pfsynct_cleanup
 }
 
+atf_test_case "route_to" "cleanup"
+route_to_head()
+{
+	atf_set descr 'Test route-to with default rule'
+	atf_set require.user root
+	atf_set require.progs scapy
+}
+
+route_to_body()
+{
+	pfsynct_init
+
+	epair_sync=$(vnet_mkepair)
+	epair_one=$(vnet_mkepair)
+	epair_two=$(vnet_mkepair)
+	epair_out_one=$(vnet_mkepair)
+	epair_out_two=$(vnet_mkepair)
+
+	vnet_mkjail one ${epair_one}a ${epair_sync}a ${epair_out_one}a
+	vnet_mkjail two ${epair_two}a ${epair_sync}b ${epair_out_two}a
+
+	# pfsync interface
+	jexec one ifconfig ${epair_sync}a 192.0.2.1/24 up
+	jexec one ifconfig ${epair_one}a 198.51.100.1/24 up
+	jexec one ifconfig ${epair_out_one}a 203.0.113.1/24 up
+	jexec one ifconfig ${epair_out_one}a name outif
+	jexec one sysctl net.inet.ip.forwarding=1
+	jexec one arp -s 203.0.113.254 00:01:02:03:04:05
+	jexec one ifconfig pfsync0 \
+		syncdev ${epair_sync}a \
+		maxupd 1 \
+		up
+
+	jexec two ifconfig ${epair_sync}b 192.0.2.2/24 up
+	jexec two ifconfig ${epair_two}a 198.51.100.2/24 up
+	jexec two ifconfig ${epair_out_two}a 203.0.113.2/24 up
+	#jexec two ifconfig ${epair_out_two}a name outif
+	jexec two sysctl net.inet.ip.forwarding=1
+	jexec two arp -s 203.0.113.254 00:01:02:03:04:05
+	jexec two ifconfig pfsync0 \
+		syncdev ${epair_sync}b \
+		maxupd 1 \
+		up
+
+	# Enable pf!
+	jexec one pfctl -e
+	pft_set_rules one \
+		"set skip on ${epair_sync}a" \
+		"pass out route-to (outif 203.0.113.254)"
+	jexec two pfctl -e
+
+	# Make sure we have different rulesets so the synced state is associated with
+	# V_pf_default_rule
+	pft_set_rules two \
+		"set skip on ${epair_sync}b" \
+		"pass out route-to (outif 203.0.113.254)" \
+		"pass out proto tcp"
+
+	ifconfig ${epair_one}b 198.51.100.254/24 up
+	ifconfig ${epair_two}b 198.51.100.253/24 up
+	route add -net 203.0.113.0/24 198.51.100.1
+	ifconfig ${epair_two}b up
+	ifconfig ${epair_out_one}b up
+	ifconfig ${epair_out_two}b up
+
+	atf_check -s exit:0 env PYTHONPATH=${common_dir} \
+		${common_dir}/pft_ping.py \
+		--sendif ${epair_one}b \
+		--fromaddr 198.51.100.254 \
+		--to 203.0.113.254 \
+		--recvif ${epair_out_one}b
+
+	# Allow time for sync
+	ifconfig ${epair_one}b inet 198.51.100.254 -alias
+	route del -net 203.0.113.0/24 198.51.100.1
+	route add -net 203.0.113.0/24 198.51.100.2
+
+	sleep 2
+
+	# Now try to trigger the state on the other pfsync member
+	env PYTHONPATH=${common_dir} \
+		${common_dir}/pft_ping.py \
+		--sendif ${epair_two}b \
+		--fromaddr 198.51.100.254 \
+		--to 203.0.113.254 \
+		--recvif ${epair_out_two}b
+
+	true
+}
+
+route_to_cleanup()
+{
+	pfsynct_cleanup
+}
+
 atf_init_test_cases()
 {
 	atf_add_test_case "basic"
@@ -837,4 +932,5 @@ atf_init_test_cases()
 	atf_add_test_case "timeout"
 	atf_add_test_case "basic_ipv6_unicast"
 	atf_add_test_case "basic_ipv6"
+	atf_add_test_case "route_to"
 }