Module Name:    src
Committed By:   knakahara
Date:           Fri Dec 11 07:59:14 UTC 2015

Modified Files:
        src/sys/net: if_gif.c if_gif.h
        src/sys/netinet: in_gif.c
        src/sys/netinet6: in6_gif.c

Log Message:
PR kern/50522: gif(4) ioctl causes panic while someone is using the gif(4) 
interface.

It is required to wait other CPU's softint completion before disestablishing
the softint handler.


To generate a diff of this commit:
cvs rdiff -u -r1.101 -r1.102 src/sys/net/if_gif.c
cvs rdiff -u -r1.19 -r1.20 src/sys/net/if_gif.h
cvs rdiff -u -r1.65 -r1.66 src/sys/netinet/in_gif.c
cvs rdiff -u -r1.62 -r1.63 src/sys/netinet6/in6_gif.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/net/if_gif.c
diff -u src/sys/net/if_gif.c:1.101 src/sys/net/if_gif.c:1.102
--- src/sys/net/if_gif.c:1.101	Fri Dec 11 04:29:24 2015
+++ src/sys/net/if_gif.c	Fri Dec 11 07:59:14 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: if_gif.c,v 1.101 2015/12/11 04:29:24 knakahara Exp $	*/
+/*	$NetBSD: if_gif.c,v 1.102 2015/12/11 07:59:14 knakahara Exp $	*/
 /*	$KAME: if_gif.c,v 1.76 2001/08/20 02:01:02 kjc Exp $	*/
 
 /*
@@ -31,7 +31,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: if_gif.c,v 1.101 2015/12/11 04:29:24 knakahara Exp $");
+__KERNEL_RCSID(0, "$NetBSD: if_gif.c,v 1.102 2015/12/11 07:59:14 knakahara Exp $");
 
 #ifdef _KERNEL_OPT
 #include "opt_inet.h"
@@ -53,6 +53,7 @@ __KERNEL_RCSID(0, "$NetBSD: if_gif.c,v 1
 #include <sys/cpu.h>
 #include <sys/intr.h>
 #include <sys/kmem.h>
+#include <sys/atomic.h>
 
 #include <net/if.h>
 #include <net/if_types.h>
@@ -146,6 +147,10 @@ void
 gifattach0(struct gif_softc *sc)
 {
 
+	sc->gif_si_lock = mutex_obj_alloc(MUTEX_DEFAULT, IPL_NONE);
+	KASSERT(sc->gif_si_lock != NULL);
+	cv_init(&sc->gif_si_cv, "if_gif_cv");
+	sc->gif_si_refs = 0;
 	sc->encap_cookie4 = sc->encap_cookie6 = NULL;
 
 	sc->gif_if.if_addrlen = 0;
@@ -174,6 +179,8 @@ gif_clone_destroy(struct ifnet *ifp)
 	if_detach(ifp);
 	rtcache_free(&sc->gif_ro);
 
+	cv_destroy(&sc->gif_si_cv);
+	mutex_obj_free(sc->gif_si_lock);
 	kmem_free(sc, sizeof(struct gif_softc));
 
 	return (0);
@@ -295,7 +302,8 @@ gif_output(struct ifnet *ifp, struct mbu
 
 	m->m_flags &= ~(M_BCAST|M_MCAST);
 	if (!(ifp->if_flags & IFF_UP) ||
-	    sc->gif_psrc == NULL || sc->gif_pdst == NULL) {
+	    sc->gif_psrc == NULL || sc->gif_pdst == NULL ||
+	    sc->gif_si == NULL) {
 		m_freem(m);
 		error = ENETDOWN;
 		goto end;
@@ -321,9 +329,11 @@ gif_output(struct ifnet *ifp, struct mbu
 		splx(s);
 		goto end;
 	}
-	splx(s);
 
+	/* softint_schedule() must be called with kpreempt_disabled() */
 	softint_schedule(sc->gif_si);
+	splx(s);
+
 	error = 0;
 
   end:
@@ -346,6 +356,24 @@ gifintr(void *arg)
 	sc = arg;
 	ifp = &sc->gif_if;
 
+	atomic_inc_uint(&sc->gif_si_refs);
+
+	/*
+	 * pattern (a) (see also gif_set_tunnel())
+	 * other CPUs does {set,delete}_tunnel after curcpu have done
+	 * softint_schedule().
+	 */
+	if (sc->gif_pdst == NULL || sc->gif_psrc == NULL) {
+		IFQ_PURGE(&ifp->if_snd);
+
+		if (atomic_dec_uint_nv(&sc->gif_si_refs) == 0) {
+			mutex_enter(sc->gif_si_lock);
+			cv_broadcast(&sc->gif_si_cv);
+			mutex_exit(sc->gif_si_lock);
+		}
+		return;
+	}
+
 	/* output processing */
 	while (1) {
 		s = splnet();
@@ -397,6 +425,16 @@ gifintr(void *arg)
 			ifp->if_obytes += len;
 		}
 	}
+
+	/*
+	 * pattern (b) (see also gif_set_tunnel())
+	 * other CPUs begin {set,delete}_tunnel while curcpu si doing gifintr.
+	 */
+	if (atomic_dec_uint_nv(&sc->gif_si_refs) == 0) {
+		mutex_enter(sc->gif_si_lock);
+		cv_broadcast(&sc->gif_si_cv);
+		mutex_exit(sc->gif_si_lock);
+	}
 }
 
 void
@@ -744,6 +782,7 @@ gif_set_tunnel(struct ifnet *ifp, struct
 	struct gif_softc *sc2;
 	struct sockaddr *osrc, *odst;
 	struct sockaddr *nsrc, *ndst;
+	void *osi;
 	int s;
 	int error;
 
@@ -777,8 +816,38 @@ gif_set_tunnel(struct ifnet *ifp, struct
 
 	/* Firstly, clear old configurations. */
 	if (sc->gif_si) {
-		softint_disestablish(sc->gif_si);
+		osrc = sc->gif_psrc;
+		odst = sc->gif_pdst;
+		osi = sc->gif_si;
+		sc->gif_psrc = NULL;
+		sc->gif_pdst = NULL;
 		sc->gif_si = NULL;
+
+		/*
+		 * At this point, gif_output() does not softint_schedule()
+		 * any more. However, there are below 2 fears of other CPUs.
+		 *     (a) gif_output() has done softint_schedule(),and softint
+		 *         (gifintr()) is waiting for execution
+		 *     (b) gifintr() is already running
+		 * see also gifintr()
+		 */
+
+		/*
+		 * To avoid the above fears, wait for gifintr() completion of
+		 * all CPUs here.
+		 */
+		mutex_enter(sc->gif_si_lock);
+		while (sc->gif_si_refs > 0) {
+			aprint_debug("%s: cv_wait on gif_softc\n", __func__);
+			cv_wait(&sc->gif_si_cv, sc->gif_si_lock);
+		}
+		mutex_exit(sc->gif_si_lock);
+
+		softint_disestablish(osi);
+		sc->gif_psrc = osrc;
+		sc->gif_pdst = odst;
+		osrc = NULL;
+		odst = NULL;
 	}
 	/* XXX we can detach from both, but be polite just in case */
 	if (sc->gif_psrc)
@@ -845,13 +914,31 @@ void
 gif_delete_tunnel(struct ifnet *ifp)
 {
 	struct gif_softc *sc = ifp->if_softc;
+	struct sockaddr *osrc, *odst;
+	void *osi;
 	int s;
 
 	s = splsoftnet();
 
 	if (sc->gif_si) {
-		softint_disestablish(sc->gif_si);
+		osrc = sc->gif_psrc;
+		odst = sc->gif_pdst;
+		osi = sc->gif_si;
+
+		sc->gif_psrc = NULL;
+		sc->gif_pdst = NULL;
 		sc->gif_si = NULL;
+
+		mutex_enter(sc->gif_si_lock);
+		while (sc->gif_si_refs > 0) {
+			aprint_debug("%s: cv_wait on gif_softc\n", __func__);
+			cv_wait(&sc->gif_si_cv, sc->gif_si_lock);
+		}
+		mutex_exit(sc->gif_si_lock);
+
+		softint_disestablish(osi);
+		sc->gif_psrc = osrc;
+		sc->gif_pdst = odst;
 	}
 	if (sc->gif_psrc) {
 		sockaddr_free(sc->gif_psrc);

Index: src/sys/net/if_gif.h
diff -u src/sys/net/if_gif.h:1.19 src/sys/net/if_gif.h:1.20
--- src/sys/net/if_gif.h:1.19	Wed Nov 12 12:36:28 2008
+++ src/sys/net/if_gif.h	Fri Dec 11 07:59:14 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: if_gif.h,v 1.19 2008/11/12 12:36:28 ad Exp $	*/
+/*	$NetBSD: if_gif.h,v 1.20 2015/12/11 07:59:14 knakahara Exp $	*/
 /*	$KAME: if_gif.h,v 1.23 2001/07/27 09:21:42 itojun Exp $	*/
 
 /*
@@ -38,6 +38,8 @@
 #define _NET_IF_GIF_H_
 
 #include <sys/queue.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
 
 #ifdef _KERNEL_OPT
 #include "opt_inet.h"
@@ -60,11 +62,21 @@ struct gif_softc {
 	const struct encaptab *encap_cookie6;
 	LIST_ENTRY(gif_softc) gif_list;	/* list of all gifs */
 	void	*gif_si;		/* softintr handle */
+
+	struct si_sync { /* can access without gif_lock */
+		unsigned int	si_refs;	/* reference count for gif_si */
+		kcondvar_t	si_cv;		/* wait for softint completion */
+		kmutex_t	*si_lock;	/* lock for gif_si_sync */
+	} gif_si_sync;
 };
 #define GIF_ROUTE_TTL	10
 
 #define gif_ro gifsc_gifscr.gifscr_ro
 
+#define gif_si_refs	gif_si_sync.si_refs
+#define gif_si_cv	gif_si_sync.si_cv
+#define gif_si_lock	gif_si_sync.si_lock
+
 #define GIF_MTU		(1280)	/* Default MTU */
 #define	GIF_MTU_MIN	(1280)	/* Minimum MTU */
 #define	GIF_MTU_MAX	(8192)	/* Maximum MTU */
@@ -81,4 +93,8 @@ void	gif_delete_tunnel(struct ifnet *);
 int	gif_encapcheck(struct mbuf *, int, int, void *);
 #endif
 
+/*
+ * Locking notes:
+ * - All members of struct si_sync are protected by si_lock (an adaptive mutex)
+ */
 #endif /* !_NET_IF_GIF_H_ */

Index: src/sys/netinet/in_gif.c
diff -u src/sys/netinet/in_gif.c:1.65 src/sys/netinet/in_gif.c:1.66
--- src/sys/netinet/in_gif.c:1.65	Mon Aug 24 22:21:26 2015
+++ src/sys/netinet/in_gif.c	Fri Dec 11 07:59:14 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: in_gif.c,v 1.65 2015/08/24 22:21:26 pooka Exp $	*/
+/*	$NetBSD: in_gif.c,v 1.66 2015/12/11 07:59:14 knakahara Exp $	*/
 /*	$KAME: in_gif.c,v 1.66 2001/07/29 04:46:09 itojun Exp $	*/
 
 /*
@@ -31,7 +31,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: in_gif.c,v 1.65 2015/08/24 22:21:26 pooka Exp $");
+__KERNEL_RCSID(0, "$NetBSD: in_gif.c,v 1.66 2015/12/11 07:59:14 knakahara Exp $");
 
 #ifdef _KERNEL_OPT
 #include "opt_inet.h"
@@ -223,13 +223,20 @@ in_gif_input(struct mbuf *m, ...)
 		return;
 	}
 #ifndef GIF_ENCAPCHECK
-	if (!gif_validate4(ip, gifp->if_softc, m->m_pkthdr.rcvif)) {
+	struct gif_softc *sc = (struct gif_softc *)gifp->if_softc;
+	/* other CPU do delete_tunnel */
+	if (sc->gif_psrc == NULL || sc->gif_pdst == NULL) {
 		m_freem(m);
 		ip_statinc(IP_STAT_NOGIF);
 		return;
 	}
-#endif
 
+	if (!gif_validate4(ip, sc, m->m_pkthdr.rcvif)) {
+		m_freem(m);
+		ip_statinc(IP_STAT_NOGIF);
+		return;
+	}
+#endif
 	otos = ip->ip_tos;
 	m_adj(m, off);
 

Index: src/sys/netinet6/in6_gif.c
diff -u src/sys/netinet6/in6_gif.c:1.62 src/sys/netinet6/in6_gif.c:1.63
--- src/sys/netinet6/in6_gif.c:1.62	Mon Aug 24 22:21:27 2015
+++ src/sys/netinet6/in6_gif.c	Fri Dec 11 07:59:14 2015
@@ -1,4 +1,4 @@
-/*	$NetBSD: in6_gif.c,v 1.62 2015/08/24 22:21:27 pooka Exp $	*/
+/*	$NetBSD: in6_gif.c,v 1.63 2015/12/11 07:59:14 knakahara Exp $	*/
 /*	$KAME: in6_gif.c,v 1.62 2001/07/29 04:27:25 itojun Exp $	*/
 
 /*
@@ -31,7 +31,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: in6_gif.c,v 1.62 2015/08/24 22:21:27 pooka Exp $");
+__KERNEL_RCSID(0, "$NetBSD: in6_gif.c,v 1.63 2015/12/11 07:59:14 knakahara Exp $");
 
 #ifdef _KERNEL_OPT
 #include "opt_inet.h"
@@ -224,7 +224,15 @@ in6_gif_input(struct mbuf **mp, int *off
 		return IPPROTO_DONE;
 	}
 #ifndef GIF_ENCAPCHECK
-	if (!gif_validate6(ip6, gifp->if_softc, m->m_pkthdr.rcvif)) {
+	struct gif_softc *sc = (struct gif_softc *)gifp->if_softc;
+	/* other CPU do delete_tunnel */
+	if (sc->gif_psrc == NULL || sc->gif_pdst == NULL) {
+		m_freem(m);
+		IP6_STATINC(IP6_STAT_NOGIF);
+		return IPPROTO_DONE;
+	}
+
+	if (!gif_validate6(ip6, sc, m->m_pkthdr.rcvif)) {
 		m_freem(m);
 		IP6_STATINC(IP6_STAT_NOGIF);
 		return IPPROTO_DONE;

Reply via email to