Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 8 Jan 2020 15:41:04 +0000 (UTC)
From:      "Bjoern A. Zeeb" <bz@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-stable@freebsd.org, svn-src-stable-12@freebsd.org
Subject:   svn commit: r356489 - in stable/12/sys: netinet netinet6
Message-ID:  <202001081541.008Ff460008705@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: bz
Date: Wed Jan  8 15:41:04 2020
New Revision: 356489
URL: https://svnweb.freebsd.org/changeset/base/356489

Log:
  MFC r353635 (by hselasky):
  
    Fix panic in network stack due to use after free when receiving
    partial fragmented packets before a network interface is detached.
  
    When sending IPv4 or IPv6 fragmented packets and a fragment is lost
    before the network device is freed, the mbuf making up the fragment
    will remain in the temporary hashed fragment list and cause a panic
    when it times out due to accessing a freed network interface
    structure.
  
    1) Make sure the m_pkthdr.rcvif always points to a valid network
    interface. Else the rcvif field should be set to NULL.
  
    2) Use the rcvif of the last received fragment as m_pkthdr.rcvif for
    the fully defragged packet, instead of the first received fragment.

Modified:
  stable/12/sys/netinet/ip_reass.c
  stable/12/sys/netinet6/frag6.c
Directory Properties:
  stable/12/   (props changed)

Modified: stable/12/sys/netinet/ip_reass.c
==============================================================================
--- stable/12/sys/netinet/ip_reass.c	Wed Jan  8 14:01:15 2020	(r356488)
+++ stable/12/sys/netinet/ip_reass.c	Wed Jan  8 15:41:04 2020	(r356489)
@@ -46,7 +46,10 @@ __FBSDID("$FreeBSD$");
 #include <sys/lock.h>
 #include <sys/mutex.h>
 #include <sys/sysctl.h>
+#include <sys/socket.h>
 
+#include <net/if.h>
+#include <net/if_var.h>
 #include <net/rss_config.h>
 #include <net/netisr.h>
 #include <net/vnet.h>
@@ -180,6 +183,7 @@ ip_reass(struct mbuf *m)
 	struct ip *ip;
 	struct mbuf *p, *q, *nq, *t;
 	struct ipq *fp;
+	struct ifnet *srcifp;
 	struct ipqhead *head;
 	int i, hlen, next, tmpmax;
 	u_int8_t ecn, ecn0;
@@ -240,6 +244,11 @@ ip_reass(struct mbuf *m)
 	}
 
 	/*
+	 * Store receive network interface pointer for later.
+	 */
+	srcifp = m->m_pkthdr.rcvif;
+
+	/*
 	 * Attempt reassembly; if it succeeds, proceed.
 	 * ip_reass() will return a different mbuf.
 	 */
@@ -489,8 +498,11 @@ ip_reass(struct mbuf *m)
 	m->m_len += (ip->ip_hl << 2);
 	m->m_data -= (ip->ip_hl << 2);
 	/* some debugging cruft by sklower, below, will go away soon */
-	if (m->m_flags & M_PKTHDR)	/* XXX this should be done elsewhere */
+	if (m->m_flags & M_PKTHDR) {	/* XXX this should be done elsewhere */
 		m_fixhdr(m);
+		/* set valid receive interface pointer */
+		m->m_pkthdr.rcvif = srcifp;
+	}
 	IPSTAT_INC(ips_reassembled);
 	IPQ_UNLOCK(hash);
 
@@ -606,6 +618,43 @@ ipreass_drain(void)
 	}
 }
 
+/*
+ * Drain off all datagram fragments belonging to
+ * the given network interface.
+ */
+static void
+ipreass_cleanup(void *arg __unused, struct ifnet *ifp)
+{
+	struct ipq *fp, *temp;
+	struct mbuf *m;
+	int i;
+
+	KASSERT(ifp != NULL, ("%s: ifp is NULL", __func__));
+
+	/*
+	 * Skip processing if IPv4 reassembly is not initialised or
+	 * torn down by ipreass_destroy().
+	 */ 
+	if (V_ipq_zone == NULL)
+		return;
+
+	CURVNET_SET_QUIET(ifp->if_vnet);
+	for (i = 0; i < IPREASS_NHASH; i++) {
+		IPQ_LOCK(i);
+		/* Scan fragment list. */
+		TAILQ_FOREACH_SAFE(fp, &V_ipq[i].head, ipq_list, temp) {
+			for (m = fp->ipq_frags; m != NULL; m = m->m_nextpkt) {
+				/* clear no longer valid rcvif pointer */
+				if (m->m_pkthdr.rcvif == ifp)
+					m->m_pkthdr.rcvif = NULL;
+			}
+		}
+		IPQ_UNLOCK(i);
+	}
+	CURVNET_RESTORE();
+}
+EVENTHANDLER_DEFINE(ifnet_departure_event, ipreass_cleanup, NULL, 0);
+
 #ifdef VIMAGE
 /*
  * Destroy IP reassembly structures.
@@ -616,6 +665,7 @@ ipreass_destroy(void)
 
 	ipreass_drain();
 	uma_zdestroy(V_ipq_zone);
+	V_ipq_zone = NULL;
 	for (int i = 0; i < IPREASS_NHASH; i++)
 		mtx_destroy(&V_ipq[i].lock);
 }

Modified: stable/12/sys/netinet6/frag6.c
==============================================================================
--- stable/12/sys/netinet6/frag6.c	Wed Jan  8 14:01:15 2020	(r356488)
+++ stable/12/sys/netinet6/frag6.c	Wed Jan  8 15:41:04 2020	(r356489)
@@ -234,7 +234,7 @@ frag6_freef(struct ip6q *q6, uint32_t bucket)
 		 * Return ICMP time exceeded error for the 1st fragment.
 		 * Just free other fragments.
 		 */
-		if (af6->ip6af_off == 0) {
+		if (af6->ip6af_off == 0 && m->m_pkthdr.rcvif != NULL) {
 
 			/* Adjust pointer. */
 			ip6 = mtod(m, struct ip6_hdr *);
@@ -260,6 +260,43 @@ frag6_freef(struct ip6q *q6, uint32_t bucket)
 }
 
 /*
+ * Drain off all datagram fragments belonging to
+ * the given network interface.
+ */
+static void
+frag6_cleanup(void *arg __unused, struct ifnet *ifp)
+{
+	struct ip6q *q6, *q6n, *head;
+	struct ip6asfrag *af6;
+	struct mbuf *m;
+	int i;
+
+	KASSERT(ifp != NULL, ("%s: ifp is NULL", __func__));
+
+	CURVNET_SET_QUIET(ifp->if_vnet);
+	for (i = 0; i < IP6REASS_NHASH; i++) {
+		IP6QB_LOCK(i);
+		head = IP6QB_HEAD(i);
+		/* Scan fragment list. */
+		for (q6 = head->ip6q_next; q6 != head; q6 = q6n) {
+			q6n = q6->ip6q_next;
+
+			for (af6 = q6->ip6q_down; af6 != (struct ip6asfrag *)q6;
+			     af6 = af6->ip6af_down) {
+				m = IP6_REASS_MBUF(af6);
+
+				/* clear no longer valid rcvif pointer */
+				if (m->m_pkthdr.rcvif == ifp)
+					m->m_pkthdr.rcvif = NULL;
+			}
+		}
+		IP6QB_UNLOCK(i);
+	}
+	CURVNET_RESTORE();
+}
+EVENTHANDLER_DEFINE(ifnet_departure_event, frag6_cleanup, NULL, 0);
+
+/*
  * Like in RFC2460, in RFC8200, fragment and reassembly rules do not agree with
  * each other, in terms of next header field handling in fragment header.
  * While the sender will use the same value for all of the fragmented packets,
@@ -295,6 +332,7 @@ int
 frag6_input(struct mbuf **mp, int *offp, int proto)
 {
 	struct ifnet *dstifp;
+	struct ifnet *srcifp;
 	struct in6_ifaddr *ia6;
 	struct ip6_hdr *ip6;
 	struct ip6_frag *ip6f;
@@ -326,6 +364,11 @@ frag6_input(struct mbuf **mp, int *offp, int proto)
 		return (IPPROTO_DONE);
 #endif
 
+	/*
+	 * Store receive network interface pointer for later.
+	 */
+	srcifp = m->m_pkthdr.rcvif;
+
 	dstifp = NULL;
 	/* Find the destination interface of the packet. */
 	ia6 = in6ifa_ifwithaddr(&ip6->ip6_dst, 0 /* XXX */);
@@ -523,6 +566,9 @@ frag6_input(struct mbuf **mp, int *offp, int proto)
 				frag6_deq(af6, bucket);
 				free(af6, M_FRAG6);
 
+				/* Set a valid receive interface pointer. */
+				merr->m_pkthdr.rcvif = srcifp;
+				
 				/* Adjust pointer. */
 				ip6err = mtod(merr, struct ip6_hdr *);
 
@@ -709,6 +755,8 @@ insert:
 		for (t = m; t; t = t->m_next)
 			plen += t->m_len;
 		m->m_pkthdr.len = plen;
+		/* Set a valid receive interface pointer. */
+		m->m_pkthdr.rcvif = srcifp;
 	}
 
 #ifdef RSS



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?202001081541.008Ff460008705>