kern/148857: [patch] [ip6] Panics due to insufficient locking in netinet6/nd6.c

Dmitrij Tejblum tejblum at yandex-team.ru
Fri Jul 23 07:50:04 UTC 2010


>Number:         148857
>Category:       kern
>Synopsis:       [patch] [ip6] Panics due to insufficient locking in netinet6/nd6.c
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Fri Jul 23 07:50:03 UTC 2010
>Closed-Date:
>Last-Modified:
>Originator:     Dmitrij Tejblum
>Release:        8.1-PRERELEASE
>Organization:
Yandex
>Environment:
>Description:
nd6_llinfo_timer() heavily use and modify an `struct llentry' called `ln'. It should lock it, to protect against someone else work with the llentry. However, it  does not.
>How-To-Repeat:
It is better to run kernel with INVARIANTS.

Run ping6 to an on-link IPv6 address that is not assigned to any node. The system will panic after a few seconds. The panic is caused by immediate generation of DST_UNREACH icmp response (since the address is unreachable) and ping6 sending the next ping. Both of these actions work with ln->la_hold queue of packets.
>Fix:
I've tested the attached patch with INVARIANTS and WITNESS

Patch attached with submission follows:

--- sys/netinet6/nd6.c	2010-07-19 17:46:38.000000000 +0400
+++ sys/netinet6/nd6.c	2010-07-23 00:05:57.000000000 +0400
@@ -400,12 +400,13 @@ skip1:
  */
 void
 nd6_llinfo_settimer_locked(struct llentry *ln, long tick)
 {
 	int canceled;
 
+	LLE_WLOCK_ASSERT(ln);
 	if (tick < 0) {
 		ln->la_expire = 0;
 		ln->ln_ntick = 0;
 		canceled = callout_stop(&ln->ln_timer_ch);
 	} else {
 		ln->la_expire = time_second + tick / hz;
@@ -437,31 +438,33 @@ static void
 nd6_llinfo_timer(void *arg)
 {
 	struct llentry *ln;
 	struct in6_addr *dst;
 	struct ifnet *ifp;
 	struct nd_ifinfo *ndi = NULL;
+	struct mbuf *m = NULL;
 
 	ln = (struct llentry *)arg;
 	if (ln == NULL) {
 		panic("%s: NULL entry!\n", __func__);
 		return;
 	}
+	LLE_WLOCK_ASSERT(ln);
 
 	if ((ifp = ((ln->lle_tbl != NULL) ? ln->lle_tbl->llt_ifp : NULL)) == NULL)
 		panic("ln ifp == NULL");
 
 	CURVNET_SET(ifp->if_vnet);
 
 	if (ln->ln_ntick > 0) {
 		if (ln->ln_ntick > INT_MAX) {
 			ln->ln_ntick -= INT_MAX;
-			nd6_llinfo_settimer(ln, INT_MAX);
+			nd6_llinfo_settimer_locked(ln, INT_MAX);
 		} else {
 			ln->ln_ntick = 0;
-			nd6_llinfo_settimer(ln, ln->ln_ntick);
+			nd6_llinfo_settimer_locked(ln, ln->ln_ntick);
 		}
 		goto done;
 	}
 
 	ndi = ND_IFINFO(ifp);
 	dst = &L3_ADDR_SIN6(ln)->sin6_addr;
@@ -476,39 +479,38 @@ nd6_llinfo_timer(void *arg)
 	}
 
 	switch (ln->ln_state) {
 	case ND6_LLINFO_INCOMPLETE:
 		if (ln->la_asked < V_nd6_mmaxtries) {
 			ln->la_asked++;
-			nd6_llinfo_settimer(ln, (long)ndi->retrans * hz / 1000);
+			nd6_llinfo_settimer_locked(ln, (long)ndi->retrans * hz / 1000);
 			nd6_ns_output(ifp, NULL, dst, ln, 0);
 		} else {
-			struct mbuf *m = ln->la_hold;
+			m = ln->la_hold;
 			if (m) {
 				struct mbuf *m0;
 
 				/*
 				 * assuming every packet in la_hold has the
 				 * same IP header
 				 */
 				m0 = m->m_nextpkt;
 				m->m_nextpkt = NULL;
-				icmp6_error2(m, ICMP6_DST_UNREACH,
-				    ICMP6_DST_UNREACH_ADDR, 0, ifp);
+				/* send error after unlock, to avoid reversal */
 
 				ln->la_hold = m0;
 				clear_llinfo_pqueue(ln);
 			}
 			(void)nd6_free(ln, 0);
 			ln = NULL;
 		}
 		break;
 	case ND6_LLINFO_REACHABLE:
 		if (!ND6_LLINFO_PERMANENT(ln)) {
 			ln->ln_state = ND6_LLINFO_STALE;
-			nd6_llinfo_settimer(ln, (long)V_nd6_gctimer * hz);
+			nd6_llinfo_settimer_locked(ln, (long)V_nd6_gctimer * hz);
 		}
 		break;
 
 	case ND6_LLINFO_STALE:
 		/* Garbage Collection(RFC 2461 5.3) */
 		if (!ND6_LLINFO_PERMANENT(ln)) {
@@ -519,33 +521,36 @@ nd6_llinfo_timer(void *arg)
 
 	case ND6_LLINFO_DELAY:
 		if (ndi && (ndi->flags & ND6_IFF_PERFORMNUD) != 0) {
 			/* We need NUD */
 			ln->la_asked = 1;
 			ln->ln_state = ND6_LLINFO_PROBE;
-			nd6_llinfo_settimer(ln, (long)ndi->retrans * hz / 1000);
+			nd6_llinfo_settimer_locked(ln, (long)ndi->retrans * hz / 1000);
 			nd6_ns_output(ifp, dst, dst, ln, 0);
 		} else {
 			ln->ln_state = ND6_LLINFO_STALE; /* XXX */
-			nd6_llinfo_settimer(ln, (long)V_nd6_gctimer * hz);
+			nd6_llinfo_settimer_locked(ln, (long)V_nd6_gctimer * hz);
 		}
 		break;
 	case ND6_LLINFO_PROBE:
 		if (ln->la_asked < V_nd6_umaxtries) {
 			ln->la_asked++;
-			nd6_llinfo_settimer(ln, (long)ndi->retrans * hz / 1000);
+			nd6_llinfo_settimer_locked(ln, (long)ndi->retrans * hz / 1000);
 			nd6_ns_output(ifp, dst, dst, ln, 0);
 		} else {
 			(void)nd6_free(ln, 0);
 			ln = NULL;
 		}
 		break;
 	}
 done:
 	if (ln != NULL)
-		LLE_FREE(ln);
+		LLE_FREE_LOCKED(ln);
+	if (m != NULL)
+		icmp6_error2(m, ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADDR, 
+		    0, ifp);
 	CURVNET_RESTORE();
 }
 
 
 /*
  * ND6 timer routine to expire default route list and prefix list
@@ -851,13 +856,13 @@ nd6_lookup(struct in6_addr *addr6, int f
 	if (flags & ND6_EXCLUSIVE)
 	    llflags |= LLE_EXCLUSIVE;	
 	
 	ln = lla_lookup(LLTABLE6(ifp), llflags, (struct sockaddr *)&sin6);
 	if ((ln != NULL) && (flags & LLE_CREATE)) {
 		ln->ln_state = ND6_LLINFO_NOSTATE;
-		callout_init(&ln->ln_timer_ch, 0);
+		callout_init_rw(&ln->ln_timer_ch, &ln->lle_lock, CALLOUT_RETURNUNLOCKED);
 	}
 	
 	return (ln);
 }
 
 /*
@@ -997,19 +1002,20 @@ static struct llentry *
 nd6_free(struct llentry *ln, int gc)
 {
         struct llentry *next;
 	struct nd_defrouter *dr;
 	struct ifnet *ifp=NULL;
 
+	LLE_WLOCK_ASSERT(ln);
 	/*
 	 * we used to have pfctlinput(PRC_HOSTDEAD) here.
 	 * even though it is not harmful, it was not really necessary.
 	 */
 
 	/* cancel timer */
-	nd6_llinfo_settimer(ln, -1);
+	nd6_llinfo_settimer_locked(ln, -1);
 
 	if (!V_ip6_forwarding) {
 		int s;
 		s = splnet();
 		dr = defrouter_lookup(&L3_ADDR_SIN6(ln)->sin6_addr, ln->lle_tbl->llt_ifp);
 
@@ -1025,18 +1031,17 @@ nd6_free(struct llentry *ln, int gc)
 			 * thing, especially when we're using router preference
 			 * values.
 			 * XXX: the check for ln_state would be redundant,
 			 *      but we intentionally keep it just in case.
 			 */
 			if (dr->expire > time_second)
-				nd6_llinfo_settimer(ln,
+				nd6_llinfo_settimer_locked(ln,
 				    (dr->expire - time_second) * hz);
 			else
-				nd6_llinfo_settimer(ln, (long)V_nd6_gctimer * hz);
+				nd6_llinfo_settimer_locked(ln, (long)V_nd6_gctimer * hz);
 			splx(s);
-			LLE_WLOCK(ln);
 			LLE_REMREF(ln);
 			LLE_WUNLOCK(ln);
 			return (LIST_NEXT(ln, lle_next));
 		}
 
 		if (ln->ln_router || dr) {
@@ -1086,12 +1091,13 @@ nd6_free(struct llentry *ln, int gc)
 	 * might have freed other entries (particularly the old next entry) as
 	 * a side effect (XXX).
 	 */
 	next = LIST_NEXT(ln, lle_next);
 
 	ifp = ln->lle_tbl->llt_ifp;
+	LLE_WUNLOCK(ln);
 	IF_AFDATA_LOCK(ifp);
 	LLE_WLOCK(ln);
 	LLE_REMREF(ln);
 	llentry_free(ln);
 	IF_AFDATA_UNLOCK(ifp);
 
@@ -1829,33 +1835,33 @@ nd6_output_lle(struct ifnet *ifp, struct
 			i--;
 		}
 	} else {
 		ln->la_hold = m;
 	}
 	/*
+	 * If there has been no NS for the neighbor after entering the
+	 * INCOMPLETE state, send the first solicitation.
+	 */
+	if (!ND6_LLINFO_PERMANENT(ln) && ln->la_asked == 0) {
+		ln->la_asked++;
+		
+		nd6_llinfo_settimer_locked(ln,
+		    (long)ND_IFINFO(ifp)->retrans * hz / 1000);
+		nd6_ns_output(ifp, NULL, &dst->sin6_addr, ln, 0);
+	}
+	/*
 	 * We did the lookup (no lle arg) so we
 	 * need to do the unlock here
 	 */
 	if (lle == NULL) {
 		if (flags & LLE_EXCLUSIVE)
 			LLE_WUNLOCK(ln);
 		else
 			LLE_RUNLOCK(ln);
 	}
 	
-	/*
-	 * If there has been no NS for the neighbor after entering the
-	 * INCOMPLETE state, send the first solicitation.
-	 */
-	if (!ND6_LLINFO_PERMANENT(ln) && ln->la_asked == 0) {
-		ln->la_asked++;
-		
-		nd6_llinfo_settimer(ln,
-		    (long)ND_IFINFO(ifp)->retrans * hz / 1000);
-		nd6_ns_output(ifp, NULL, &dst->sin6_addr, ln, 0);
-	}
 	return (0);
 
   sendpkt:
 	/* discard the packet if IPv6 operation is disabled on the interface */
 	if ((ND_IFINFO(ifp)->flags & ND6_IFF_IFDISABLED)) {
 		error = ENETDOWN; /* better error? */
--- sys/netinet6/nd6_nbr.c	2010-07-19 17:46:38.000000000 +0400
+++ sys/netinet6/nd6_nbr.c	2010-07-22 17:39:39.000000000 +0400
@@ -421,12 +421,17 @@ nd6_ns_output(struct ifnet *ifp, const s
 		}
 	}
 	if (m == NULL)
 		return;
 	m->m_pkthdr.rcvif = NULL;
 
+	if (ln != NULL) {
+		LLE_WLOCK_ASSERT(ln);
+		LLE_WUNLOCK(ln);
+	}
+
 	if (daddr6 == NULL || IN6_IS_ADDR_MULTICAST(daddr6)) {
 		m->m_flags |= M_MCAST;
 		im6o.im6o_multicast_ifp = ifp;
 		im6o.im6o_multicast_hlim = 255;
 		im6o.im6o_multicast_loop = 0;
 	}
@@ -473,23 +478,27 @@ nd6_ns_output(struct ifnet *ifp, const s
 		 * - saddr6 belongs to the outgoing interface.
 		 * Otherwise, we perform the source address selection as usual.
 		 */
 		struct ip6_hdr *hip6;		/* hold ip6 */
 		struct in6_addr *hsrc = NULL;
 
-		if ((ln != NULL) && ln->la_hold) {
-			/*
-			 * assuming every packet in la_hold has the same IP
-			 * header
-			 */
-			hip6 = mtod(ln->la_hold, struct ip6_hdr *);
-			/* XXX pullup? */
-			if (sizeof(*hip6) < ln->la_hold->m_len)
-				hsrc = &hip6->ip6_src;
-			else
-				hsrc = NULL;
+		if (ln != NULL) {
+			LLE_RLOCK(ln);
+			if (ln->la_hold) {
+				/*
+				 * assuming every packet in la_hold has the same IP
+				 * header
+				 */
+				hip6 = mtod(ln->la_hold, struct ip6_hdr *);
+				/* XXX pullup? */
+				if (sizeof(*hip6) < ln->la_hold->m_len)
+					hsrc = &hip6->ip6_src;
+				else
+					hsrc = NULL;
+			}
+			LLE_RUNLOCK(ln);
 		}
 		if (hsrc && (ifa = (struct ifaddr *)in6ifa_ifpwithaddr(ifp,
 		    hsrc)) != NULL) {
 			src = hsrc;
 			ifa_free(ifa);
 		} else {
@@ -570,19 +579,23 @@ nd6_ns_output(struct ifnet *ifp, const s
 	icmp6_ifstat_inc(ifp, ifs6_out_neighborsolicit);
 	ICMP6STAT_INC(icp6s_outhist[ND_NEIGHBOR_SOLICIT]);
 
 	if (ro.ro_rt) {		/* we don't cache this route. */
 		RTFREE(ro.ro_rt);
 	}
+	if (ln)
+		LLE_WLOCK(ln);
 	return;
 
   bad:
 	if (ro.ro_rt) {
 		RTFREE(ro.ro_rt);
 	}
 	m_freem(m);
+	if (ln)
+		LLE_WLOCK(ln);
 	return;
 }
 
 /*
  * Neighbor advertisement input handling.
  *


>Release-Note:
>Audit-Trail:
>Unformatted:


More information about the freebsd-bugs mailing list