git: 9485d2228660 - main - pf: send a challenge ACK for SYN's matching existing states

From: Kristof Provost <kp_at_FreeBSD.org>
Date: Sat, 03 May 2025 08:14:26 UTC
The branch main has been updated by kp:

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

commit 9485d22286603ef2ac2e2009dd7dfcc6a59e2283
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2025-05-02 12:24:27 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2025-05-03 08:13:52 +0000

    pf: send a challenge ACK for SYN's matching existing states
    
    PF should send a challenge ACK as response to SYN, which matches existing
    state. Extra thanks goes to bluhm@ for careful testing and fixing patch I've
    sent to tech@
    
    O.K. henning@ bluhm@
    
    Obtained from:  OpenBSD, sashan <sashan@openbsd.org>, 6679bb1581
    Sponsored by:   Rubicon Communications, LLC ("Netgate")
---
 sys/netpfil/pf/pf.c | 62 ++++++++++++++++++++++++++++++++++++++++-------------
 1 file changed, 47 insertions(+), 15 deletions(-)

diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c
index a1301e59533b..8c74b15ef991 100644
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -4373,6 +4373,25 @@ pf_icmp_to_bandlim(uint8_t type)
 	}
 }
 
+static void
+pf_send_challenge_ack(struct pf_pdesc *pd, struct pf_kstate *s,
+    struct pf_state_peer *src, struct pf_state_peer *dst)
+{
+	/*
+	 * We are sending challenge ACK as a response to SYN packet, which
+	 * matches existing state (modulo TCP window check). Therefore packet
+	 * must be sent on behalf of destination.
+	 *
+	 * We expect sender to remain either silent, or send RST packet
+	 * so both, firewall and remote peer, can purge dead state from
+	 * memory.
+	 */
+	pf_send_tcp(s->rule, pd->af, pd->dst, pd->src,
+	    pd->hdr.tcp.th_dport, pd->hdr.tcp.th_sport, dst->seqlo,
+	    src->seqlo, TH_ACK, 0, 0, s->rule->return_ttl, 0, 0, 0,
+	    s->rule->rtableid);
+}
+
 static void
 pf_send_icmp(struct mbuf *m, u_int8_t type, u_int8_t code, sa_family_t af,
     struct pf_krule *r, int rtableid)
@@ -7013,22 +7032,35 @@ pf_test_state(struct pf_kstate **state, struct pf_pdesc *pd, u_short *reason)
 
 		if ((action = pf_synproxy(pd, *state, reason)) != PF_PASS)
 			return (action);
-		if ((*state)->src.state >= TCPS_FIN_WAIT_2 &&
-		    (*state)->dst.state >= TCPS_FIN_WAIT_2 &&
-		    (((tcp_get_flags(th) & (TH_SYN|TH_ACK)) == TH_SYN) ||
-		    ((tcp_get_flags(th) & (TH_SYN|TH_ACK|TH_RST)) == TH_ACK &&
-		    pf_syncookie_check(pd) && pd->dir == PF_IN))) {
-			if (V_pf_status.debug >= PF_DEBUG_MISC) {
-				printf("pf: state reuse ");
-				pf_print_state(*state);
-				pf_print_flags(tcp_get_flags(th));
-				printf("\n");
+		if (((tcp_get_flags(th) & (TH_SYN | TH_ACK)) == TH_SYN) ||
+		    ((th->th_flags & (TH_SYN | TH_ACK | TH_RST)) == TH_ACK &&
+		    pf_syncookie_check(pd) && pd->dir == PF_IN)) {
+			if ((*state)->src.state >= TCPS_FIN_WAIT_2 &&
+			    (*state)->dst.state >= TCPS_FIN_WAIT_2) {
+				if (V_pf_status.debug >= PF_DEBUG_MISC) {
+					printf("pf: state reuse ");
+					pf_print_state(*state);
+					pf_print_flags(tcp_get_flags(th));
+					printf("\n");
+				}
+				/* XXX make sure it's the same direction ?? */
+				pf_set_protostate(*state, PF_PEER_BOTH, TCPS_CLOSED);
+				pf_remove_state(*state);
+				*state = NULL;
+				return (PF_DROP);
+			} else if ((*state)->src.state >= TCPS_ESTABLISHED &&
+			    (*state)->dst.state >= TCPS_ESTABLISHED) {
+				/*
+				 * SYN matches existing state???
+				 * Typically happens when sender boots up after
+				 * sudden panic. Certain protocols (NFSv3) are
+				 * always using same port numbers. Challenge
+				 * ACK enables all parties (firewall and peers)
+				 * to get in sync again.
+				 */
+				pf_send_challenge_ack(pd, *state, src, dst);
+				return (PF_DROP);
 			}
-			/* XXX make sure it's the same direction ?? */
-			pf_set_protostate(*state, PF_PEER_BOTH, TCPS_CLOSED);
-			pf_remove_state(*state);
-			*state = NULL;
-			return (PF_DROP);
 		}
 		if ((*state)->state_flags & PFSTATE_SLOPPY) {
 			if (pf_tcp_track_sloppy(*state, pd, reason, src, dst,