git: 349360b17eab - main - pf: return errors from pf_route() and pf_route6()

From: Kristof Provost <kp_at_FreeBSD.org>
Date: Mon, 04 Aug 2025 08:12:02 UTC
The branch main has been updated by kp:

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

commit 349360b17eaba47be3d4e7a452e28e70a133b807
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2025-07-28 16:00:50 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2025-08-04 06:08:31 +0000

    pf: return errors from pf_route() and pf_route6()
    
    If we fail to route the packet in pf_route()/pf_route6() (e.g. because it
    hit the TTL limit) we free the mbuf. If that packet is an SCTP packet that
    establishes extra (i.e. multihome) states we have a queued job to handle that.
    These jobs reference the now freed mbuf.
    
    Pass the error from pf_route()/pf_route6() on, so that
    pf_sctp_multihome_delayed() doesn't attempt to use the invalid mbuf pointer (or
    establishes states for a packet we're not passing).
    
    PR:             288274
    Reported by:    Robert Morris <rtm@lcs.mit.edu>
    Sponsored by:   Rubicon Communications, LLC ("Netgate")
    Differential Revision:  https://reviews.freebsd.org/D51627
---
 sys/netpfil/pf/pf.c           | 66 ++++++++++++++++++++++++++++++-------------
 tests/sys/netpfil/pf/nat64.py | 28 ++++++++++++++++++
 2 files changed, 75 insertions(+), 19 deletions(-)

diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c
index f6ee02626624..4801b3e2c766 100644
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -409,14 +409,14 @@ static void		 pf_mtag_free(struct m_tag *);
 static void		 pf_packet_rework_nat(struct pf_pdesc *, int,
 			    struct pf_state_key *);
 #ifdef INET
-static void		 pf_route(struct pf_krule *,
+static int		 pf_route(struct pf_krule *,
 			    struct ifnet *, struct pf_kstate *,
 			    struct pf_pdesc *, struct inpcb *);
 #endif /* INET */
 #ifdef INET6
 static void		 pf_change_a6(struct pf_addr *, u_int16_t *,
 			    struct pf_addr *, u_int8_t);
-static void		 pf_route6(struct pf_krule *,
+static int		 pf_route6(struct pf_krule *,
 			    struct ifnet *, struct pf_kstate *,
 			    struct pf_pdesc *, struct inpcb *);
 #endif /* INET6 */
@@ -8914,7 +8914,7 @@ pf_routable(struct pf_addr *addr, sa_family_t af, struct pfi_kkif *kif,
 }
 
 #ifdef INET
-static void
+static int
 pf_route(struct pf_krule *r, struct ifnet *oifp,
     struct pf_kstate *s, struct pf_pdesc *pd, struct inpcb *inp)
 {
@@ -8929,6 +8929,7 @@ pf_route(struct pf_krule *r, struct ifnet *oifp,
 	uint16_t		 tmp;
 	int			 r_dir;
 	bool			 skip_test = false;
+	int			 action = PF_PASS;
 
 	KASSERT(pd->m && r && oifp, ("%s: invalid parameters", __func__));
 
@@ -8950,6 +8951,7 @@ pf_route(struct pf_krule *r, struct ifnet *oifp,
 		m0 = pd->m;
 		pd->m = NULL;
 		SDT_PROBE1(pf, ip, route_to, drop, __LINE__);
+		action = PF_DROP;
 		goto bad_locked;
 	}
 
@@ -8963,11 +8965,12 @@ pf_route(struct pf_krule *r, struct ifnet *oifp,
 			}
 			if (ifp == oifp) {
 				/* When the 2nd interface is not skipped */
-				return;
+				return (action);
 			} else {
 				m0 = pd->m;
 				pd->m = NULL;
 				SDT_PROBE1(pf, ip, route_to, drop, __LINE__);
+				action = PF_DROP;
 				goto bad;
 			}
 		} else {
@@ -8975,7 +8978,7 @@ pf_route(struct pf_krule *r, struct ifnet *oifp,
 			if (((m0 = m_dup(pd->m, M_NOWAIT)) == NULL)) {
 				if (s)
 					PF_STATE_UNLOCK(s);
-				return;
+				return (action);
 			}
 		}
 	} else {
@@ -8984,7 +8987,7 @@ pf_route(struct pf_krule *r, struct ifnet *oifp,
 				pf_dummynet(pd, s, r, &pd->m);
 				if (s)
 					PF_STATE_UNLOCK(s);
-				return;
+				return (action);
 			} else {
 				if (r_dir == PF_IN) {
 					skip_test = true;
@@ -9024,6 +9027,7 @@ pf_route(struct pf_krule *r, struct ifnet *oifp,
 				pf_send_icmp(m0, ICMP_TIMXCEED,
 				    ICMP_TIMXCEED_INTRANS, 0, pd->af, r,
 				    pd->act.rtableid);
+			action = PF_DROP;
 			goto bad_locked;
 		}
 		ip->ip_ttl -= IPTTLDEC;
@@ -9070,6 +9074,7 @@ pf_route(struct pf_krule *r, struct ifnet *oifp,
 	if (ifp == NULL) {
 		m0 = pd->m;
 		pd->m = NULL;
+		action = PF_DROP;
 		SDT_PROBE1(pf, ip, route_to, drop, __LINE__);
 		goto bad;
 	}
@@ -9080,9 +9085,11 @@ pf_route(struct pf_krule *r, struct ifnet *oifp,
 	if (pd->dir == PF_IN && !skip_test) {
 		if (pf_test(AF_INET, PF_OUT, PFIL_FWD, ifp, &m0, inp,
 		    &pd->act) != PF_PASS) {
+			action = PF_DROP;
 			SDT_PROBE1(pf, ip, route_to, drop, __LINE__);
 			goto bad;
 		} else if (m0 == NULL) {
+			action = PF_DROP;
 			SDT_PROBE1(pf, ip, route_to, drop, __LINE__);
 			goto done;
 		}
@@ -9090,6 +9097,7 @@ pf_route(struct pf_krule *r, struct ifnet *oifp,
 			DPFPRINTF(PF_DEBUG_URGENT,
 			    "%s: m0->m_len < sizeof(struct ip)", __func__);
 			SDT_PROBE1(pf, ip, route_to, drop, __LINE__);
+			action = PF_DROP;
 			goto bad;
 		}
 		ip = mtod(m0, struct ip *);
@@ -9171,12 +9179,14 @@ pf_route(struct pf_krule *r, struct ifnet *oifp,
 			   ifp->if_mtu, pd->af, r, pd->act.rtableid);
 		}
 		SDT_PROBE1(pf, ip, route_to, drop, __LINE__);
+		action = PF_DROP;
 		goto bad;
 	}
 
 	error = ip_fragment(ip, &m0, ifp->if_mtu, ifp->if_hwassist);
 	if (error) {
 		SDT_PROBE1(pf, ip, route_to, drop, __LINE__);
+		action = PF_DROP;
 		goto bad;
 	}
 
@@ -9203,7 +9213,9 @@ pf_route(struct pf_krule *r, struct ifnet *oifp,
 done:
 	if (pd->act.rt != PF_DUPTO)
 		pd->m = NULL;
-	return;
+	else
+		action = PF_PASS;
+	return (action);
 
 bad_locked:
 	if (s)
@@ -9215,7 +9227,7 @@ bad:
 #endif /* INET */
 
 #ifdef INET6
-static void
+static int
 pf_route6(struct pf_krule *r, struct ifnet *oifp,
     struct pf_kstate *s, struct pf_pdesc *pd, struct inpcb *inp)
 {
@@ -9226,6 +9238,7 @@ pf_route6(struct pf_krule *r, struct ifnet *oifp,
 	struct ifnet		*ifp = NULL;
 	int			 r_dir;
 	bool			 skip_test = false;
+	int			 action = PF_PASS;
 
 	KASSERT(pd->m && r && oifp, ("%s: invalid parameters", __func__));
 
@@ -9246,6 +9259,7 @@ pf_route6(struct pf_krule *r, struct ifnet *oifp,
 	    pd->pf_mtag->routed++ > 3) {
 		m0 = pd->m;
 		pd->m = NULL;
+		action = PF_DROP;
 		SDT_PROBE1(pf, ip6, route_to, drop, __LINE__);
 		goto bad_locked;
 	}
@@ -9260,10 +9274,11 @@ pf_route6(struct pf_krule *r, struct ifnet *oifp,
 			}
 			if (ifp == oifp) {
 				/* When the 2nd interface is not skipped */
-				return;
+				return (action);
 			} else {
 				m0 = pd->m;
 				pd->m = NULL;
+				action = PF_DROP;
 				SDT_PROBE1(pf, ip6, route_to, drop, __LINE__);
 				goto bad;
 			}
@@ -9272,7 +9287,7 @@ pf_route6(struct pf_krule *r, struct ifnet *oifp,
 			if (((m0 = m_dup(pd->m, M_NOWAIT)) == NULL)) {
 				if (s)
 					PF_STATE_UNLOCK(s);
-				return;
+				return (action);
 			}
 		}
 	} else {
@@ -9281,7 +9296,7 @@ pf_route6(struct pf_krule *r, struct ifnet *oifp,
 				pf_dummynet(pd, s, r, &pd->m);
 				if (s)
 					PF_STATE_UNLOCK(s);
-				return;
+				return (action);
 			} else {
 				if (r_dir == PF_IN) {
 					skip_test = true;
@@ -9321,6 +9336,7 @@ pf_route6(struct pf_krule *r, struct ifnet *oifp,
 				pf_send_icmp(m0, ICMP6_TIME_EXCEEDED,
 				    ICMP6_TIME_EXCEED_TRANSIT, 0, pd->af, r,
 				    pd->act.rtableid);
+			action = PF_DROP;
 			goto bad_locked;
 		}
 		ip6->ip6_hlim -= IPV6_HLIMDEC;
@@ -9375,6 +9391,7 @@ pf_route6(struct pf_krule *r, struct ifnet *oifp,
 	if (ifp == NULL) {
 		m0 = pd->m;
 		pd->m = NULL;
+		action = PF_DROP;
 		SDT_PROBE1(pf, ip6, route_to, drop, __LINE__);
 		goto bad;
 	}
@@ -9385,9 +9402,11 @@ pf_route6(struct pf_krule *r, struct ifnet *oifp,
 	if (pd->dir == PF_IN && !skip_test) {
 		if (pf_test(AF_INET6, PF_OUT, PFIL_FWD | PF_PFIL_NOREFRAGMENT,
 		    ifp, &m0, inp, &pd->act) != PF_PASS) {
+			action = PF_DROP;
 			SDT_PROBE1(pf, ip6, route_to, drop, __LINE__);
 			goto bad;
 		} else if (m0 == NULL) {
+			action = PF_DROP;
 			SDT_PROBE1(pf, ip6, route_to, drop, __LINE__);
 			goto done;
 		}
@@ -9395,6 +9414,7 @@ pf_route6(struct pf_krule *r, struct ifnet *oifp,
 			DPFPRINTF(PF_DEBUG_URGENT,
 			    "%s: m0->m_len < sizeof(struct ip6_hdr)",
 			    __func__);
+			action = PF_DROP;
 			SDT_PROBE1(pf, ip6, route_to, drop, __LINE__);
 			goto bad;
 		}
@@ -9470,6 +9490,7 @@ pf_route6(struct pf_krule *r, struct ifnet *oifp,
 				pf_send_icmp(m0, ICMP6_PACKET_TOO_BIG, 0,
 				    ifp->if_mtu, pd->af, r, pd->act.rtableid);
 		}
+		action = PF_DROP;
 		SDT_PROBE1(pf, ip6, route_to, drop, __LINE__);
 		goto bad;
 	}
@@ -9477,7 +9498,9 @@ pf_route6(struct pf_krule *r, struct ifnet *oifp,
 done:
 	if (pd->act.rt != PF_DUPTO)
 		pd->m = NULL;
-	return;
+	else
+		action = PF_PASS;
+	return (action);
 
 bad_locked:
 	if (s)
@@ -11033,15 +11056,18 @@ done:
 			break;
 		}
 #ifdef INET
-		if (pd.naf == AF_INET)
-			pf_route(r, kif->pfik_ifp, s, &pd, inp);
+		if (pd.naf == AF_INET) {
+			action = pf_route(r, kif->pfik_ifp, s, &pd,
+			    inp);
+		}
 #endif /* INET */
 #ifdef INET6
-		if (pd.naf == AF_INET6)
-			pf_route6(r, kif->pfik_ifp, s, &pd, inp);
+		if (pd.naf == AF_INET6) {
+			action = pf_route6(r, kif->pfik_ifp, s, &pd,
+			    inp);
+}
 #endif /* INET6 */
 		*m0 = pd.m;
-		action = PF_PASS;
 		goto out;
 		break;
 	default:
@@ -11050,13 +11076,15 @@ done:
 #ifdef INET
 			case AF_INET:
 				/* pf_route() returns unlocked. */
-				pf_route(r, kif->pfik_ifp, s, &pd, inp);
+				action = pf_route(r, kif->pfik_ifp, s, &pd,
+				    inp);
 				break;
 #endif /* INET */
 #ifdef INET6
 			case AF_INET6:
 				/* pf_route6() returns unlocked. */
-				pf_route6(r, kif->pfik_ifp, s, &pd, inp);
+				action = pf_route6(r, kif->pfik_ifp, s, &pd,
+				    inp);
 				break;
 #endif /* INET6 */
 			}
diff --git a/tests/sys/netpfil/pf/nat64.py b/tests/sys/netpfil/pf/nat64.py
index a5890fc4a161..0908c90c34a0 100644
--- a/tests/sys/netpfil/pf/nat64.py
+++ b/tests/sys/netpfil/pf/nat64.py
@@ -326,3 +326,31 @@ class TestNAT64(VnetTestTemplate):
         packets = sp.sniff(iface=ifname, timeout=5)
         for r in packets:
             r.show()
+
+    @pytest.mark.require_user("root")
+    @pytest.mark.require_progs(["scapy"])
+    def test_ttl_zero(self):
+        """
+            PR 288274: we can use an mbuf after free on TTL = 0
+        """
+        ifname = self.vnet.iface_alias_map["if1"].name
+        gw_mac = self.vnet.iface_alias_map["if1"].epairb.ether
+        ToolsHelper.print_output("/sbin/route -6 add default 2001:db8::1")
+
+        import scapy.all as sp
+
+        pkt = sp.Ether(dst=gw_mac) \
+            / sp.IPv6(dst="64:ff9b::192.0.2.2", hlim=0) \
+            / sp.SCTP(sport=1111, dport=2222) \
+            / sp.SCTPChunkInit(init_tag=1, n_in_streams=1, n_out_streams=1, \
+                a_rwnd=1500, params=[ \
+                    sp.SCTPChunkParamIPv4Addr() \
+                ])
+        pkt.show()
+        sp.hexdump(pkt)
+        s = DelayedSend(pkt, sendif=ifname)
+
+        packets = sp.sniff(iface=ifname, timeout=5)
+        for r in packets:
+            r.show()
+