Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 16 Oct 2019 09:11:50 +0000 (UTC)
From:      Hans Petter Selasky <hselasky@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r353635 - in head/sys: netinet netinet6
Message-ID:  <201910160911.x9G9BonH076337@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: hselasky
Date: Wed Oct 16 09:11:49 2019
New Revision: 353635
URL: https://svnweb.freebsd.org/changeset/base/353635

Log:
  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.
  
  Panic backtrace for IPv6:
  
  panic()
  icmp6_reflect() # tries to access rcvif->if_afdata[AF_INET6]->xxx
  icmp6_error()
  frag6_freef()
  frag6_slowtimo()
  pfslowtimo()
  softclock_call_cc()
  softclock()
  ithread_loop()
  
  Reviewed by:	bz
  Differential Revision:	https://reviews.freebsd.org/D19622
  MFC after:	1 week
  Sponsored by:	Mellanox Technologies

Modified:
  head/sys/netinet/ip_reass.c
  head/sys/netinet6/frag6.c

Modified: head/sys/netinet/ip_reass.c
==============================================================================
--- head/sys/netinet/ip_reass.c	Wed Oct 16 09:04:53 2019	(r353634)
+++ head/sys/netinet/ip_reass.c	Wed Oct 16 09:11:49 2019	(r353635)
@@ -47,7 +47,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>
@@ -181,6 +184,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;
@@ -241,6 +245,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.
 	 */
@@ -490,8 +499,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);
 
@@ -607,6 +619,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.
@@ -617,6 +666,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: head/sys/netinet6/frag6.c
==============================================================================
--- head/sys/netinet6/frag6.c	Wed Oct 16 09:04:53 2019	(r353634)
+++ head/sys/netinet6/frag6.c	Wed Oct 16 09:11:49 2019	(r353635)
@@ -246,7 +246,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 *);
@@ -272,6 +272,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,
@@ -307,6 +344,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;
@@ -338,6 +376,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 */);
@@ -534,6 +577,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 *);
 
@@ -720,6 +766,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?201910160911.x9G9BonH076337>