git: b86fc3284bf8 - stable/15 - sys/netipsec: ensure sah stability during input callback processing

From: Konstantin Belousov <kib_at_FreeBSD.org>
Date: Mon, 29 Dec 2025 01:01:46 UTC
The branch stable/15 has been updated by kib:

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

commit b86fc3284bf8eff23cc2606bf7ab0c8117e6023e
Author:     Konstantin Belousov <kib@FreeBSD.org>
AuthorDate: 2025-12-09 08:55:11 +0000
Commit:     Konstantin Belousov <kib@FreeBSD.org>
CommitDate: 2025-12-29 00:57:03 +0000

    sys/netipsec: ensure sah stability during input callback processing
    
    (cherry picked from commit 183513d15f2290a42b542753aaffe9de2152575b)
---
 sys/net/if_ipsec.c          | 12 ++++++++++--
 sys/netipsec/ipsec.h        |  7 +++++--
 sys/netipsec/ipsec6.h       |  5 ++++-
 sys/netipsec/ipsec_input.c  | 17 +++++++++++++----
 sys/netipsec/xform_ah.c     | 16 ++++++++++++++--
 sys/netipsec/xform_esp.c    | 10 ++++++++--
 sys/netipsec/xform_ipcomp.c | 16 ++++++++++++++--
 7 files changed, 68 insertions(+), 15 deletions(-)

diff --git a/sys/net/if_ipsec.c b/sys/net/if_ipsec.c
index df10af66471c..fd4fb37debfa 100644
--- a/sys/net/if_ipsec.c
+++ b/sys/net/if_ipsec.c
@@ -450,7 +450,8 @@ ipsec_output(struct ifnet *ifp, struct mbuf *m, const struct sockaddr *dst,
 }
 
 int
-ipsec_if_input(struct mbuf *m, struct secasvar *sav, uint32_t af)
+ipsec_if_input(struct mbuf *m, struct secasvar *sav, uint32_t af,
+    struct rm_priotracker *sahtree_tracker)
 {
 	IPSEC_RLOCK_TRACKER;
 	struct secasindex *saidx;
@@ -459,13 +460,16 @@ ipsec_if_input(struct mbuf *m, struct secasvar *sav, uint32_t af)
 
 	if (sav->state != SADB_SASTATE_MATURE &&
 	    sav->state != SADB_SASTATE_DYING) {
+		ipsec_sahtree_runlock(sahtree_tracker);
 		m_freem(m);
 		return (ENETDOWN);
 	}
 
 	if (sav->sah->saidx.mode != IPSEC_MODE_TUNNEL ||
-	    sav->sah->saidx.proto != IPPROTO_ESP)
+	    sav->sah->saidx.proto != IPPROTO_ESP) {
+		ipsec_sahtree_runlock(sahtree_tracker);
 		return (0);
+	}
 
 	IPSEC_RLOCK();
 	CK_LIST_FOREACH(sc, ipsec_idhash(sav->sah->saidx.reqid), idhash) {
@@ -487,6 +491,7 @@ ipsec_if_input(struct mbuf *m, struct secasvar *sav, uint32_t af)
 	}
 	if (sc == NULL) {
 		IPSEC_RUNLOCK();
+		ipsec_sahtree_runlock(sahtree_tracker);
 		/* Tunnel was not found. Nothing to do. */
 		return (0);
 	}
@@ -494,6 +499,7 @@ ipsec_if_input(struct mbuf *m, struct secasvar *sav, uint32_t af)
 	if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0 ||
 	    (ifp->if_flags & IFF_UP) == 0) {
 		IPSEC_RUNLOCK();
+		ipsec_sahtree_runlock(sahtree_tracker);
 		m_freem(m);
 		return (ENETDOWN);
 	}
@@ -503,6 +509,8 @@ ipsec_if_input(struct mbuf *m, struct secasvar *sav, uint32_t af)
 	 */
 	m->m_pkthdr.rcvif = ifp;
 
+	ipsec_sahtree_runlock(sahtree_tracker);
+
 	m_clrprotoflags(m);
 	M_SETFIB(m, ifp->if_fib);
 	BPF_MTAP2(ifp, &af, sizeof(af), m);
diff --git a/sys/netipsec/ipsec.h b/sys/netipsec/ipsec.h
index 44e7d9667d5b..b8969e88abbe 100644
--- a/sys/netipsec/ipsec.h
+++ b/sys/netipsec/ipsec.h
@@ -330,12 +330,14 @@ VNET_DECLARE(int, natt_cksum_policy);
 struct inpcb;
 struct ip;
 struct m_tag;
+struct rm_priotracker;
 struct secasvar;
 struct sockopt;
 struct tcphdr;
 union sockaddr_union;
 
-int ipsec_if_input(struct mbuf *, struct secasvar *, uint32_t);
+int ipsec_if_input(struct mbuf *, struct secasvar *, uint32_t,
+    struct rm_priotracker *);
 
 struct ipsecrequest *ipsec_newisr(void);
 void ipsec_delisr(struct ipsecrequest *);
@@ -357,7 +359,8 @@ void ipsec_setspidx_inpcb(struct inpcb *, struct secpolicyindex *, u_int);
 
 void ipsec4_setsockaddrs(const struct mbuf *, const struct ip *,
     union sockaddr_union *, union sockaddr_union *);
-int ipsec4_common_input_cb(struct mbuf *, struct secasvar *, int, int);
+int ipsec4_common_input_cb(struct mbuf *, struct secasvar *, int, int,
+    struct rm_priotracker *sahtree_tracker);
 int ipsec4_check_pmtu(struct ifnet *, struct mbuf *, struct ip *ip1,
     struct secpolicy *, int);
 int ipsec4_process_packet(struct ifnet *, struct mbuf *, struct ip *ip1,
diff --git a/sys/netipsec/ipsec6.h b/sys/netipsec/ipsec6.h
index a7410733c43f..0747b88059ab 100644
--- a/sys/netipsec/ipsec6.h
+++ b/sys/netipsec/ipsec6.h
@@ -60,12 +60,15 @@ VNET_DECLARE(int, ip6_ipsec_ecn);
 #define	V_ip6_ipsec_ecn		VNET(ip6_ipsec_ecn)
 
 struct inpcb;
+struct rm_priotracker;
+
 struct secpolicy *ipsec6_checkpolicy(const struct mbuf *,
     struct inpcb *, int *, int);
 
 void ipsec6_setsockaddrs(const struct mbuf *, union sockaddr_union *,
     union sockaddr_union *);
-int ipsec6_common_input_cb(struct mbuf *, struct secasvar *, int, int);
+int ipsec6_common_input_cb(struct mbuf *, struct secasvar *, int, int,
+    struct rm_priotracker *sahtree_tracker);
 int ipsec6_check_pmtu(struct ifnet *, struct mbuf *, struct secpolicy *, int);
 int ipsec6_process_packet(struct ifnet *, struct mbuf *, struct secpolicy *,
     struct inpcb *, u_long);
diff --git a/sys/netipsec/ipsec_input.c b/sys/netipsec/ipsec_input.c
index 221ac879cb18..34767420086a 100644
--- a/sys/netipsec/ipsec_input.c
+++ b/sys/netipsec/ipsec_input.c
@@ -339,7 +339,7 @@ ipsec4_ctlinput(ipsec_ctlinput_param_t param)
  */
 int
 ipsec4_common_input_cb(struct mbuf *m, struct secasvar *sav, int skip,
-    int protoff)
+    int protoff, struct rm_priotracker *sahtree_tracker)
 {
 	IPSEC_DEBUG_DECLARE(char buf[IPSEC_ADDRSTRLEN]);
 	struct epoch_tracker et;
@@ -492,7 +492,9 @@ ipsec4_common_input_cb(struct mbuf *m, struct secasvar *sav, int skip,
 
 	/* Handle virtual tunneling interfaces */
 	if (saidx->mode == IPSEC_MODE_TUNNEL)
-		error = ipsec_if_input(m, sav, af);
+		error = ipsec_if_input(m, sav, af, sahtree_tracker);
+	else
+		ipsec_sahtree_runlock(sahtree_tracker);
 	if (error == 0) {
 		error = netisr_queue_src(isr_prot, (uintptr_t)sav->spi, m);
 		if (error) {
@@ -507,6 +509,7 @@ ipsec4_common_input_cb(struct mbuf *m, struct secasvar *sav, int skip,
 bad:
 	NET_EPOCH_EXIT(et);
 bad_noepoch:
+	ipsec_sahtree_runlock(sahtree_tracker);
 	key_freesav(&sav);
 	if (m != NULL)
 		m_freem(m);
@@ -590,7 +593,7 @@ extern ipproto_input_t	*ip6_protox[];
  */
 int
 ipsec6_common_input_cb(struct mbuf *m, struct secasvar *sav, int skip,
-    int protoff)
+    int protoff, struct rm_priotracker *sahtree_tracker)
 {
 	IPSEC_DEBUG_DECLARE(char buf[IPSEC_ADDRSTRLEN]);
 	struct epoch_tracker et;
@@ -734,7 +737,9 @@ ipsec6_common_input_cb(struct mbuf *m, struct secasvar *sav, int skip,
 		}
 		/* Handle virtual tunneling interfaces */
 		if (saidx->mode == IPSEC_MODE_TUNNEL)
-			error = ipsec_if_input(m, sav, af);
+			error = ipsec_if_input(m, sav, af, sahtree_tracker);
+		else
+			ipsec_sahtree_runlock(sahtree_tracker);
 		if (error == 0) {
 			error = netisr_queue_src(isr_prot,
 			    (uintptr_t)sav->spi, m);
@@ -748,6 +753,9 @@ ipsec6_common_input_cb(struct mbuf *m, struct secasvar *sav, int skip,
 		key_freesav(&sav);
 		return (error);
 	}
+
+	ipsec_sahtree_runlock(sahtree_tracker);
+
 	/*
 	 * See the end of ip6_input for this logic.
 	 * IPPROTO_IPV[46] case will be processed just like other ones
@@ -787,6 +795,7 @@ ipsec6_common_input_cb(struct mbuf *m, struct secasvar *sav, int skip,
 	return (0);
 bad:
 	NET_EPOCH_EXIT(et);
+	ipsec_sahtree_runlock(sahtree_tracker);
 	key_freesav(&sav);
 	if (m)
 		m_freem(m);
diff --git a/sys/netipsec/xform_ah.c b/sys/netipsec/xform_ah.c
index 8f08872eb38a..65c67db983d6 100644
--- a/sys/netipsec/xform_ah.c
+++ b/sys/netipsec/xform_ah.c
@@ -692,6 +692,7 @@ ah_input_cb(struct cryptop *crp)
 {
 	IPSEC_DEBUG_DECLARE(char buf[IPSEC_ADDRSTRLEN]);
 	unsigned char calc[AH_ALEN_MAX];
+	struct rm_priotracker sahtree_tracker;
 	struct mbuf *m;
 	struct xform_data *xd;
 	struct secasvar *sav;
@@ -711,6 +712,14 @@ ah_input_cb(struct cryptop *crp)
 	nxt = xd->nxt;
 	protoff = xd->protoff;
 	cryptoid = xd->cryptoid;
+	ipsec_sahtree_rlock(&sahtree_tracker);
+	if (sav->state >= SADB_SASTATE_DEAD) {
+		/* saidx is freed */
+		DPRINTF(("%s: dead SA %p spi %#x\n", __func__, sav, sav->spi));
+		AHSTAT_INC(ahs_notdb);
+		error = ESRCH;
+		goto bad;
+	}
 	saidx = &sav->sah->saidx;
 	IPSEC_ASSERT(saidx->dst.sa.sa_family == AF_INET ||
 		saidx->dst.sa.sa_family == AF_INET6,
@@ -808,12 +817,14 @@ ah_input_cb(struct cryptop *crp)
 	switch (saidx->dst.sa.sa_family) {
 #ifdef INET6
 	case AF_INET6:
-		error = ipsec6_common_input_cb(m, sav, skip, protoff);
+		error = ipsec6_common_input_cb(m, sav, skip, protoff,
+		    &sahtree_tracker);
 		break;
 #endif
 #ifdef INET
 	case AF_INET:
-		error = ipsec4_common_input_cb(m, sav, skip, protoff);
+		error = ipsec4_common_input_cb(m, sav, skip, protoff,
+		    &sahtree_tracker);
 		break;
 #endif
 	default:
@@ -823,6 +834,7 @@ ah_input_cb(struct cryptop *crp)
 	CURVNET_RESTORE();
 	return error;
 bad:
+	ipsec_sahtree_runlock(&sahtree_tracker);
 	CURVNET_RESTORE();
 	if (sav)
 		key_freesav(&sav);
diff --git a/sys/netipsec/xform_esp.c b/sys/netipsec/xform_esp.c
index 7aec10549e6c..be8c18da60b3 100644
--- a/sys/netipsec/xform_esp.c
+++ b/sys/netipsec/xform_esp.c
@@ -492,6 +492,7 @@ static int
 esp_input_cb(struct cryptop *crp)
 {
 	IPSEC_DEBUG_DECLARE(char buf[128]);
+	struct rm_priotracker sahtree_tracker;
 	uint8_t lastthree[3];
 	const struct auth_hash *esph;
 	struct mbuf *m;
@@ -507,6 +508,7 @@ esp_input_cb(struct cryptop *crp)
 	xd = crp->crp_opaque;
 	CURVNET_SET(xd->vnet);
 	sav = xd->sav;
+	ipsec_sahtree_rlock(&sahtree_tracker);
 	if (sav->state >= SADB_SASTATE_DEAD) {
 		/* saidx is freed */
 		DPRINTF(("%s: dead SA %p spi %#x\n", __func__, sav, sav->spi));
@@ -527,6 +529,7 @@ esp_input_cb(struct cryptop *crp)
 			if (ipsec_updateid(sav, &crp->crp_session, &cryptoid) != 0)
 				crypto_freesession(cryptoid);
 			xd->cryptoid = crp->crp_session;
+			ipsec_sahtree_runlock(&sahtree_tracker);
 			CURVNET_RESTORE();
 			return (crypto_dispatch(crp));
 		}
@@ -658,12 +661,14 @@ esp_input_cb(struct cryptop *crp)
 	switch (saidx->dst.sa.sa_family) {
 #ifdef INET6
 	case AF_INET6:
-		error = ipsec6_common_input_cb(m, sav, skip, protoff);
+		error = ipsec6_common_input_cb(m, sav, skip, protoff,
+		    &sahtree_tracker);
 		break;
 #endif
 #ifdef INET
 	case AF_INET:
-		error = ipsec4_common_input_cb(m, sav, skip, protoff);
+		error = ipsec4_common_input_cb(m, sav, skip, protoff,
+		    &sahtree_tracker);
 		break;
 #endif
 	default:
@@ -673,6 +678,7 @@ esp_input_cb(struct cryptop *crp)
 	CURVNET_RESTORE();
 	return error;
 bad:
+	ipsec_sahtree_runlock(&sahtree_tracker);
 	if (sav != NULL)
 		key_freesav(&sav);
 	if (m != NULL)
diff --git a/sys/netipsec/xform_ipcomp.c b/sys/netipsec/xform_ipcomp.c
index 737d4a50098a..778cd955310a 100644
--- a/sys/netipsec/xform_ipcomp.c
+++ b/sys/netipsec/xform_ipcomp.c
@@ -283,6 +283,7 @@ static int
 ipcomp_input_cb(struct cryptop *crp)
 {
 	IPSEC_DEBUG_DECLARE(char buf[IPSEC_ADDRSTRLEN]);
+	struct rm_priotracker sahtree_tracker;
 	struct xform_data *xd;
 	struct mbuf *m;
 	struct secasvar *sav;
@@ -300,6 +301,14 @@ ipcomp_input_cb(struct cryptop *crp)
 	skip = xd->skip;
 	protoff = xd->protoff;
 	cryptoid = xd->cryptoid;
+	ipsec_sahtree_rlock(&sahtree_tracker);
+	if (sav->state >= SADB_SASTATE_DEAD) {
+		/* saidx is freed */
+		DPRINTF(("%s: dead SA %p spi %#x\n", __func__, sav, sav->spi));
+		IPCOMPSTAT_INC(ipcomps_notdb);
+		error = ESRCH;
+		goto bad;
+	}
 	saidx = &sav->sah->saidx;
 	IPSEC_ASSERT(saidx->dst.sa.sa_family == AF_INET ||
 		saidx->dst.sa.sa_family == AF_INET6,
@@ -365,12 +374,14 @@ ipcomp_input_cb(struct cryptop *crp)
 	switch (saidx->dst.sa.sa_family) {
 #ifdef INET6
 	case AF_INET6:
-		error = ipsec6_common_input_cb(m, sav, skip, protoff);
+		error = ipsec6_common_input_cb(m, sav, skip, protoff,
+		    &sahtree_tracker);
 		break;
 #endif
 #ifdef INET
 	case AF_INET:
-		error = ipsec4_common_input_cb(m, sav, skip, protoff);
+		error = ipsec4_common_input_cb(m, sav, skip, protoff,
+		    &sahtree_tracker);
 		break;
 #endif
 	default:
@@ -380,6 +391,7 @@ ipcomp_input_cb(struct cryptop *crp)
 	CURVNET_RESTORE();
 	return error;
 bad:
+	ipsec_sahtree_runlock(&sahtree_tracker);
 	CURVNET_RESTORE();
 	if (sav != NULL)
 		key_freesav(&sav);