git: 78b620b781b0 - main - pfsync: avoid underflow on different timeouts
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Fri, 30 Oct 2026 14:14:46 UTC
The branch main has been updated by kp:
URL: https://cgit.FreeBSD.org/src/commit/?id=78b620b781b0cdc85378e439ad2974e6aae771ff
commit 78b620b781b0cdc85378e439ad2974e6aae771ff
Author: Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2026-10-21 15:18:59 +0000
Commit: Kristof Provost <kp@FreeBSD.org>
CommitDate: 2026-10-30 14:14:12 +0000
pfsync: avoid underflow on different timeouts
If two pfsync hosts have different timeout values set it's possible for
an underflow to occor when importing a state. If the destination host
has a shorter timeout set than the exporting host it's possible for the
state to be older than the importing host's timeout value, which results
in an underflow and a state with an unreasonable expiry time.
Check for this, and avoid the underflow. While here also account for the
timeout being longer than our uptime.
Sponsored by: Rubicon Communications, LLC ("Netgate")
Differential Revision: https://reviews.freebsd.org/D60950
---
sys/netpfil/pf/if_pfsync.c | 12 +++++-
tests/sys/netpfil/pf/pfsync.sh | 90 ++++++++++++++++++++++++++++++++++++++++++
2 files changed, 100 insertions(+), 2 deletions(-)
diff --git a/sys/netpfil/pf/if_pfsync.c b/sys/netpfil/pf/if_pfsync.c
index 523a71846215..fa72958788da 100644
--- a/sys/netpfil/pf/if_pfsync.c
+++ b/sys/netpfil/pf/if_pfsync.c
@@ -861,12 +861,20 @@ pfsync_state_import(union pfsync_state_union *sp, int flags, int msg_version)
st->expire = pf_get_uptime();
if (sp->pfs_1301.expire) {
uint32_t timeout;
+ uint32_t expire;
+
timeout = r->timeout[st->timeout];
if (!timeout)
timeout = V_pf_default_rule.timeout[st->timeout];
- /* sp->expire may have been adaptively scaled by export. */
- st->expire -= (timeout - ntohl(sp->pfs_1301.expire)) * 1000;
+ expire = ntohl(sp->pfs_1301.expire);
+ /*
+ * Ensure we don't over/under flow if our local timeout or
+ * uptime is lower than the remote timeout.
+ */
+ if (timeout > expire && st->expire > (timeout - expire))
+ st->expire -= (timeout - expire)
+ * 1000;
}
if (! (st->act.rtableid == -1 ||
diff --git a/tests/sys/netpfil/pf/pfsync.sh b/tests/sys/netpfil/pf/pfsync.sh
index 0e1d83d21823..1b07a6a3c620 100644
--- a/tests/sys/netpfil/pf/pfsync.sh
+++ b/tests/sys/netpfil/pf/pfsync.sh
@@ -1882,6 +1882,95 @@ stack_key_collision_cleanup()
pfsynct_cleanup
}
+atf_test_case "expire_underflow" "cleanup"
+expire_underflow_head()
+{
+ atf_set descr 'pfsync test for expiration underflow'
+ atf_set require.user root
+}
+
+expire_underflow_body()
+{
+ pfsynct_init
+
+ epair_sync=$(vnet_mkepair)
+ epair_one=$(vnet_mkepair)
+ epair_two=$(vnet_mkepair)
+
+ vnet_mkjail one ${epair_one}a ${epair_sync}a
+ vnet_mkjail two ${epair_two}a ${epair_sync}b
+
+ # 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 pfsync0 \
+ syncdev ${epair_sync}a \
+ maxupd 1 \
+ up
+ jexec two ifconfig ${epair_two}a 198.51.100.2/24 up
+
+ # Enable pf!
+ jexec one pfctl -e
+ pft_set_rules one \
+ "set timeout tcp.closed 5000" \
+ "set skip on ${epair_sync}a" \
+ "pass out keep state"
+ jexec two pfctl -e
+ pft_set_rules two \
+ "set timeout tcp.closed 5" \
+ "set skip on ${epair_sync}b" \
+ "pass out keep state (tcp.closed 5)"
+
+ hostid_one=$(jexec one pfctl -si -v | awk '/Hostid:/ { gsub(/0x/, "", $2); printf($2); }')
+
+ ifconfig ${epair_one}b 198.51.100.254/24 up
+ /usr/sbin/inetd -p ${PWD}/inetd.pid \
+ $(atf_get_srcdir)/echo_inetd.conf
+
+ echo one | jexec one nc -N 198.51.100.254 7
+
+ sleep 15
+
+ # Allow for a bulk update
+ jexec two ifconfig ${epair_sync}b 192.0.2.2/24 up
+ jexec two ifconfig pfsync0 \
+ syncdev ${epair_sync}b \
+ maxupd 1 \
+ up
+
+ # Give pfsync time to do its thing
+ sleep 2
+
+ echo "One"
+ jexec one pfctl -s states -vv
+ echo "Two"
+ jexec two pfctl -s states -vv
+
+ if ! jexec two pfctl -s states | grep tcp | grep 198.51.100.1 | \
+ grep 198.51.100.254 ; then
+ atf_fail "state not found on synced host"
+ fi
+
+ if ! jexec two pfctl -sc | grep ""${hostid_one}"";
+ then
+ jexec two pfctl -sc
+ atf_fail "HostID for host one not found on two"
+ fi
+
+ sleep 10
+
+ jexec two pfctl -s states -vv
+ if jexec two pfctl -s states | grep tcp | grep 198.51.100.1 | \
+ grep 198.51.100.254 ; then
+ atf_fail "state failed to expire on two"
+ fi
+}
+
+expire_underflow_cleanup()
+{
+ pfsynct_cleanup
+}
+
atf_init_test_cases()
{
atf_add_test_case "basic"
@@ -1908,4 +1997,5 @@ atf_init_test_cases()
atf_add_test_case "rt_af"
atf_add_test_case "flush_by_intf"
atf_add_test_case "stack_key_collision"
+ atf_add_test_case "expire_underflow"
}