git: af83288e24f0 - stable/14 - pf: Do not hash pool address pointers into rule hashes

From: R. Christian McDonald <rcm_at_FreeBSD.org>
Date: Wed, 28 Oct 2026 20:41:37 UTC
The branch stable/14 has been updated by rcm:

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

commit af83288e24f0f199eb43ac31e800831980a085cd
Author:     R. Christian McDonald <rcm@FreeBSD.org>
AuthorDate: 2026-10-13 19:17:48 +0000
Commit:     R. Christian McDonald <rcm@FreeBSD.org>
CommitDate: 2026-10-28 20:38:09 +0000

    pf: Do not hash pool address pointers into rule hashes
    
    pf_hash_pool() hashes the first address of a pool as a whole struct
    pf_addr_wrap, including the union that holds the kernel's pfi_dynaddr
    or pfr_ktable pointer.
    
    An interface pool such as "-> ($ext_if)" gets a new pfi_dynaddr on
    every load, so with "set keepcounters" the rule never matches its
    predecessor and loses its counters on every reload.  The pointers also
    differ between hosts: once a filter rule in the main ruleset has an
    interface or table pool, pfsync peers disagree on the ruleset checksum
    and bind every synced state to the default rule.
    
    Hash the address with that union cleared.  The rest of the address,
    including the prefix length of an interface address, is hashed as
    before, and the hash of a rule with a plain address pool does not
    change.
    
    Reviewed by:            kp
    Fixes:                  c6bcf6e6fd50 ("pf: include all elements when hashing rules")
    MFC after:              1 week
    Sponsored by:           Rubicon Communications, LLC ("Netgate")
    Differential Revision:  https://reviews.freebsd.org/D60370
    
    (cherry picked from commit e6dc001d7a387100125718322fa741444a9025fc)
---
 sys/netpfil/pf/pf_ioctl.c             |  6 +++-
 tests/sys/netpfil/pf/rules_counter.sh | 53 +++++++++++++++++++++++++++++++++++
 2 files changed, 58 insertions(+), 1 deletion(-)

diff --git a/sys/netpfil/pf/pf_ioctl.c b/sys/netpfil/pf/pf_ioctl.c
index 619e8e18c900..37827068c909 100644
--- a/sys/netpfil/pf/pf_ioctl.c
+++ b/sys/netpfil/pf/pf_ioctl.c
@@ -1286,11 +1286,15 @@ pf_hash_rule_addr(MD5_CTX *ctx, struct pf_rule_addr *pfr)
 static void
 pf_hash_pool(MD5_CTX *ctx, struct pf_kpool *pool)
 {
+	struct pf_addr_wrap addr;
 	uint16_t x;
 	int y;
 
 	if (pool->cur) {
-		PF_MD5_UPD(pool, cur->addr);
+		/* Leave out the pointer to the kernel's dynaddr or table. */
+		bcopy(&pool->cur->addr, &addr, sizeof(addr));
+		bzero(&addr.p, sizeof(addr.p));
+		MD5Update(ctx, (u_int8_t *)&addr, sizeof(addr));
 		PF_MD5_UPD_STR(pool, cur->ifname);
 	}
 	PF_MD5_UPD(pool, key);
diff --git a/tests/sys/netpfil/pf/rules_counter.sh b/tests/sys/netpfil/pf/rules_counter.sh
index 962e7c93ba93..60878c2bd99c 100644
--- a/tests/sys/netpfil/pf/rules_counter.sh
+++ b/tests/sys/netpfil/pf/rules_counter.sh
@@ -148,6 +148,58 @@ keepcounters_body()
 	    jexec alcatraz pfctl -s r -v
 }
 
+atf_test_case "keepcounters_pool" "cleanup"
+keepcounters_pool_head()
+{
+	atf_set descr 'Test keepcounters for a rule with an interface pool'
+	atf_set require.user root
+}
+
+keepcounters_pool_body()
+{
+	pft_init
+
+	epair=$(vnet_mkepair)
+	ifconfig ${epair}a 192.0.2.1/24 up
+
+	vnet_mkjail alcatraz ${epair}b
+	jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
+	jexec alcatraz pfctl -e
+
+	pft_set_rules alcatraz \
+		"set keepcounters" \
+		"nat on ${epair}b inet from any to any -> (${epair}b)"
+
+	atf_check -s exit:0 -o ignore jexec alcatraz ping -c 1 192.0.2.1
+
+	atf_check -s exit:0 -e ignore \
+	    -o match:'Evaluations: [1-9]' \
+	    jexec alcatraz pfctl -s nat -v
+
+	# The same rule again keeps its counters
+	pft_set_rules noflush alcatraz \
+		"set keepcounters" \
+		"nat on ${epair}b inet from any to any -> (${epair}b)"
+
+	atf_check -s exit:0 -e ignore \
+	    -o match:'Evaluations: [1-9]' \
+	    jexec alcatraz pfctl -s nat -v
+
+	# Another prefix length makes it a different rule
+	pft_set_rules noflush alcatraz \
+		"set keepcounters" \
+		"nat on ${epair}b inet from any to any -> (${epair}b)/24"
+
+	atf_check -s exit:0 -e ignore \
+	    -o match:'Evaluations: 0[[:space:]]' \
+	    jexec alcatraz pfctl -s nat -v
+}
+
+keepcounters_pool_cleanup()
+{
+	pft_cleanup
+}
+
 keepcounters_cleanup()
 {
 	pft_cleanup
@@ -157,4 +209,5 @@ atf_init_test_cases()
 {
 	atf_add_test_case "get_clear"
 	atf_add_test_case "keepcounters"
+	atf_add_test_case "keepcounters_pool"
 }