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);