Module Name:    src
Committed By:   ozaki-r
Date:           Tue Mar  6 07:24:01 UTC 2018

Modified Files:
        src/sys/net: if_llatbl.c
        src/sys/netinet: if_arp.c in.c
        src/sys/netinet6: in6.c nd6.c

Log Message:
Fix reference leaks of llentry

callout_reset and callout_halt can cancel a pending callout without telling us.
Detect a cancel and remove a reference by using callout_pending and
callout_stop (it's a bit tricy though, we can detect it).

While here, we can remove remaining abuses of mutex_owned for softnet_lock.


To generate a diff of this commit:
cvs rdiff -u -r1.23 -r1.24 src/sys/net/if_llatbl.c
cvs rdiff -u -r1.269 -r1.270 src/sys/netinet/if_arp.c
cvs rdiff -u -r1.220 -r1.221 src/sys/netinet/in.c
cvs rdiff -u -r1.261 -r1.262 src/sys/netinet6/in6.c
cvs rdiff -u -r1.245 -r1.246 src/sys/netinet6/nd6.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_llatbl.c
diff -u src/sys/net/if_llatbl.c:1.23 src/sys/net/if_llatbl.c:1.24
--- src/sys/net/if_llatbl.c:1.23	Wed Feb 14 14:15:53 2018
+++ src/sys/net/if_llatbl.c	Tue Mar  6 07:24:01 2018
@@ -1,4 +1,4 @@
-/*	$NetBSD: if_llatbl.c,v 1.23 2018/02/14 14:15:53 maxv Exp $	*/
+/*	$NetBSD: if_llatbl.c,v 1.24 2018/03/06 07:24:01 ozaki-r Exp $	*/
 /*
  * Copyright (c) 2004 Luigi Rizzo, Alessandro Cerri. All rights reserved.
  * Copyright (c) 2004-2008 Qing Li. All rights reserved.
@@ -358,6 +358,18 @@ llentry_free(struct llentry *lle)
 		llt->llt_unlink_entry(lle);
 	}
 
+	/*
+	 * Stop a pending callout if one exists.  If we cancel one, we have to
+	 * remove a reference to avoid a leak.  callout_pending is required to
+	 * to exclude the case that the callout has never been scheduled.
+	 */
+	/* XXX once softnet_lock goes away, we should use callout_halt */
+	if (callout_pending(&lle->la_timer)) {
+		bool expired = callout_stop(&lle->la_timer);
+		if (!expired)
+			LLE_REMREF(lle);
+	}
+
 	pkts_dropped = lltable_drop_entry_queue(lle);
 
 	LLE_FREE_LOCKED(lle);
@@ -428,28 +440,8 @@ lltable_purge_entries(struct lltable *ll
 	llentries_unlink(llt, &dchain);
 	IF_AFDATA_WUNLOCK(llt->llt_ifp);
 
-	LIST_FOREACH_SAFE(lle, &dchain, lle_chain, next) {
-		/*
-		 * We need to release the lock here to lle_timer proceeds;
-		 * lle_timer should stop immediately if LLE_LINKED isn't set.
-		 * Note that we cannot pass lle->lle_lock to callout_halt
-		 * because it's a rwlock.
-		 */
-		LLE_ADDREF(lle);
-		LLE_WUNLOCK(lle);
-#ifdef NET_MPSAFE
-		callout_halt(&lle->la_timer, NULL);
-#else
-		if (mutex_owned(softnet_lock))
-			callout_halt(&lle->la_timer, softnet_lock);
-		else
-			callout_halt(&lle->la_timer, NULL);
-#endif
-		LLE_WLOCK(lle);
-		LLE_REMREF(lle);
-		llentry_free(lle);
-	}
-
+	LIST_FOREACH_SAFE(lle, &dchain, lle_chain, next)
+		(void)llentry_free(lle);
 }
 
 /*

Index: src/sys/netinet/if_arp.c
diff -u src/sys/netinet/if_arp.c:1.269 src/sys/netinet/if_arp.c:1.270
--- src/sys/netinet/if_arp.c:1.269	Tue Mar  6 07:19:03 2018
+++ src/sys/netinet/if_arp.c	Tue Mar  6 07:24:01 2018
@@ -1,4 +1,4 @@
-/*	$NetBSD: if_arp.c,v 1.269 2018/03/06 07:19:03 ozaki-r Exp $	*/
+/*	$NetBSD: if_arp.c,v 1.270 2018/03/06 07:24:01 ozaki-r Exp $	*/
 
 /*
  * Copyright (c) 1998, 2000, 2008 The NetBSD Foundation, Inc.
@@ -68,7 +68,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: if_arp.c,v 1.269 2018/03/06 07:19:03 ozaki-r Exp $");
+__KERNEL_RCSID(0, "$NetBSD: if_arp.c,v 1.270 2018/03/06 07:24:01 ozaki-r Exp $");
 
 #ifdef _KERNEL_OPT
 #include "opt_ddb.h"
@@ -318,28 +318,17 @@ arptimer(void *arg)
 	KASSERT((lle->la_flags & LLE_STATIC) == 0);
 
 	LLE_WLOCK(lle);
-	if (callout_pending(&lle->la_timer)) {
-		/*
-		 * Here we are a bit odd in the treatment of
-		 * active/pending. If the pending bit is set, it got
-		 * rescheduled before I ran. The active
-		 * bit we ignore, since if it was stopped
-		 * in ll_tablefree() and was currently running
-		 * it would have return 0 so the code would
-		 * not have deleted it since the callout could
-		 * not be stopped so we want to go through
-		 * with the delete here now. If the callout
-		 * was restarted, the pending bit will be back on and
-		 * we just want to bail since the callout_reset would
-		 * return 1 and our reference would have been removed
-		 * by arpresolve() below.
-		 */
-		LLE_WUNLOCK(lle);
+
+	/*
+	 * This shortcut is required to avoid trying to touch ifp that may be
+	 * being destroyed.
+	 */
+	if ((lle->la_flags & LLE_LINKED) == 0) {
+		LLE_FREE_LOCKED(lle);
 		return;
 	}
-	ifp = lle->lle_tbl->llt_ifp;
 
-	callout_stop(&lle->la_timer);
+	ifp = lle->lle_tbl->llt_ifp;
 
 	/* XXX: LOR avoidance. We still have ref on lle. */
 	LLE_WUNLOCK(lle);
@@ -369,6 +358,19 @@ arp_settimer(struct llentry *la, int sec
 	LLE_WLOCK_ASSERT(la);
 	KASSERT((la->la_flags & LLE_STATIC) == 0);
 
+	/*
+	 * We have to take care of a reference leak which occurs if
+	 * callout_reset overwrites a pending callout schedule.  Unfortunately
+	 * we don't have a mean to know the overwrite, so we need to know it
+	 * using callout_stop.  We need to call callout_pending first to exclude
+	 * the case that the callout has never been scheduled.
+	 */
+	if (callout_pending(&la->la_timer)) {
+		bool expired = callout_stop(&la->la_timer);
+		if (!expired)
+			/* A pending callout schedule is canceled. */
+			LLE_REMREF(la);
+	}
 	LLE_ADDREF(la);
 	callout_reset(&la->la_timer, hz * sec, arptimer, la);
 }

Index: src/sys/netinet/in.c
diff -u src/sys/netinet/in.c:1.220 src/sys/netinet/in.c:1.221
--- src/sys/netinet/in.c:1.220	Tue Mar  6 07:20:41 2018
+++ src/sys/netinet/in.c	Tue Mar  6 07:24:01 2018
@@ -1,4 +1,4 @@
-/*	$NetBSD: in.c,v 1.220 2018/03/06 07:20:41 ozaki-r Exp $	*/
+/*	$NetBSD: in.c,v 1.221 2018/03/06 07:24:01 ozaki-r Exp $	*/
 
 /*
  * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
@@ -91,7 +91,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: in.c,v 1.220 2018/03/06 07:20:41 ozaki-r Exp $");
+__KERNEL_RCSID(0, "$NetBSD: in.c,v 1.221 2018/03/06 07:24:01 ozaki-r Exp $");
 
 #include "arp.h"
 
@@ -1992,44 +1992,13 @@ in_lltable_match_prefix(const struct soc
 static void
 in_lltable_free_entry(struct lltable *llt, struct llentry *lle)
 {
-	struct ifnet *ifp __diagused;
 	size_t pkts_dropped;
-	bool locked = false;
 
 	LLE_WLOCK_ASSERT(lle);
 	KASSERT(llt != NULL);
 
-	/* Unlink entry from table if not already */
-	if ((lle->la_flags & LLE_LINKED) != 0) {
-		ifp = llt->llt_ifp;
-		IF_AFDATA_WLOCK_ASSERT(ifp);
-		lltable_unlink_entry(llt, lle);
-		locked = true;
-	}
-
-	/*
-	 * We need to release the lock here to lle_timer proceeds;
-	 * lle_timer should stop immediately if LLE_LINKED isn't set.
-	 * Note that we cannot pass lle->lle_lock to callout_halt
-	 * because it's a rwlock.
-	 */
-	LLE_ADDREF(lle);
-	LLE_WUNLOCK(lle);
-	if (locked)
-		IF_AFDATA_WUNLOCK(ifp);
-
-	/* cancel timer */
-	callout_halt(&lle->lle_timer, NULL);
-
-	LLE_WLOCK(lle);
-	LLE_REMREF(lle);
-
-	/* Drop hold queue */
 	pkts_dropped = llentry_free(lle);
 	arp_stat_add(ARP_STAT_DFRDROPPED, (uint64_t)pkts_dropped);
-
-	if (locked)
-		IF_AFDATA_WLOCK(ifp);
 }
 
 static int

Index: src/sys/netinet6/in6.c
diff -u src/sys/netinet6/in6.c:1.261 src/sys/netinet6/in6.c:1.262
--- src/sys/netinet6/in6.c:1.261	Tue Mar  6 07:20:41 2018
+++ src/sys/netinet6/in6.c	Tue Mar  6 07:24:01 2018
@@ -1,4 +1,4 @@
-/*	$NetBSD: in6.c,v 1.261 2018/03/06 07:20:41 ozaki-r Exp $	*/
+/*	$NetBSD: in6.c,v 1.262 2018/03/06 07:24:01 ozaki-r Exp $	*/
 /*	$KAME: in6.c,v 1.198 2001/07/18 09:12:38 itojun Exp $	*/
 
 /*
@@ -62,7 +62,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: in6.c,v 1.261 2018/03/06 07:20:41 ozaki-r Exp $");
+__KERNEL_RCSID(0, "$NetBSD: in6.c,v 1.262 2018/03/06 07:24:01 ozaki-r Exp $");
 
 #ifdef _KERNEL_OPT
 #include "opt_inet.h"
@@ -2446,45 +2446,9 @@ in6_lltable_match_prefix(const struct so
 static void
 in6_lltable_free_entry(struct lltable *llt, struct llentry *lle)
 {
-	struct ifnet *ifp = llt->llt_ifp;
-	bool locked = false;
 
 	LLE_WLOCK_ASSERT(lle);
-
-	/* Unlink entry from table */
-	if ((lle->la_flags & LLE_LINKED) != 0) {
-		IF_AFDATA_WLOCK_ASSERT(ifp);
-		lltable_unlink_entry(llt, lle);
-		KASSERT((lle->la_flags & LLE_LINKED) == 0);
-		locked = true;
-	}
-	/*
-	 * We need to release the lock here to lle_timer proceeds;
-	 * lle_timer should stop immediately if LLE_LINKED isn't set.
-	 * Note that we cannot pass lle->lle_lock to callout_halt
-	 * because it's a rwlock.
-	 */
-	LLE_ADDREF(lle);
-	LLE_WUNLOCK(lle);
-	if (locked)
-		IF_AFDATA_WUNLOCK(ifp);
-
-#ifdef NET_MPSAFE
-	callout_halt(&lle->lle_timer, NULL);
-#else
-	if (mutex_owned(softnet_lock))
-		callout_halt(&lle->lle_timer, softnet_lock);
-	else
-		callout_halt(&lle->lle_timer, NULL);
-#endif
-	LLE_WLOCK(lle);
-	LLE_REMREF(lle);
-
-	lltable_drop_entry_queue(lle);
-	LLE_FREE_LOCKED(lle);
-
-	if (locked)
-		IF_AFDATA_WLOCK(ifp);
+	(void) llentry_free(lle);
 }
 
 static int

Index: src/sys/netinet6/nd6.c
diff -u src/sys/netinet6/nd6.c:1.245 src/sys/netinet6/nd6.c:1.246
--- src/sys/netinet6/nd6.c:1.245	Mon Jan 29 19:51:15 2018
+++ src/sys/netinet6/nd6.c	Tue Mar  6 07:24:01 2018
@@ -1,4 +1,4 @@
-/*	$NetBSD: nd6.c,v 1.245 2018/01/29 19:51:15 christos Exp $	*/
+/*	$NetBSD: nd6.c,v 1.246 2018/03/06 07:24:01 ozaki-r Exp $	*/
 /*	$KAME: nd6.c,v 1.279 2002/06/08 11:16:51 itojun Exp $	*/
 
 /*
@@ -31,7 +31,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: nd6.c,v 1.245 2018/01/29 19:51:15 christos Exp $");
+__KERNEL_RCSID(0, "$NetBSD: nd6.c,v 1.246 2018/03/06 07:24:01 ozaki-r Exp $");
 
 #ifdef _KERNEL_OPT
 #include "opt_net_mpsafe.h"
@@ -402,6 +402,19 @@ nd6_llinfo_settimer(struct llentry *ln, 
 
 	KASSERT(xtick >= 0);
 
+	/*
+	 * We have to take care of a reference leak which occurs if
+	 * callout_reset overwrites a pending callout schedule.  Unfortunately
+	 * we don't have a mean to know the overwrite, so we need to know it
+	 * using callout_stop.  We need to call callout_pending first to exclude
+	 * the case that the callout has never been scheduled.
+	 */
+	if (callout_pending(&ln->la_timer)) {
+		bool expired = callout_stop(&ln->la_timer);
+		if (!expired)
+			LLE_REMREF(ln);
+	}
+
 	ln->ln_expire = time_uptime + xtick / hz;
 	LLE_ADDREF(ln);
 	if (xtick > INT_MAX) {
@@ -451,6 +464,7 @@ nd6_llinfo_timer(void *arg)
 	const struct in6_addr *daddr6 = NULL;
 
 	SOFTNET_KERNEL_LOCK_UNLESS_NET_MPSAFE();
+
 	LLE_WLOCK(ln);
 	if ((ln->la_flags & LLE_LINKED) == 0)
 		goto out;
@@ -459,28 +473,8 @@ nd6_llinfo_timer(void *arg)
 		goto out;
 	}
 
-	if (callout_pending(&ln->la_timer)) {
-		/*
-		 * Here we are a bit odd here in the treatment of
-		 * active/pending. If the pending bit is set, it got
-		 * rescheduled before I ran. The active
-		 * bit we ignore, since if it was stopped
-		 * in ll_tablefree() and was currently running
-		 * it would have return 0 so the code would
-		 * not have deleted it since the callout could
-		 * not be stopped so we want to go through
-		 * with the delete here now. If the callout
-		 * was restarted, the pending bit will be back on and
-		 * we just want to bail since the callout_reset would
-		 * return 1 and our reference would have been removed
-		 * by nd6_llinfo_settimer above since canceled
-		 * would have been 1.
-		 */
-		goto out;
-	}
 
 	ifp = ln->lle_tbl->llt_ifp;
-
 	KASSERT(ifp != NULL);
 
 	ndi = ND_IFINFO(ifp);

Reply via email to