Module Name: src Committed By: ozaki-r Date: Wed Dec 21 08:47:02 UTC 2016
Modified Files: src/sys/net: if_llatbl.c if_llatbl.h src/sys/netinet6: in6.c nd6.c Log Message: Fix deadlock between llentry timers and destruction of llentry llentry timer (of nd6) holds both llentry's lock and softnet_lock. A caller also holds them and calls callout_halt to wait for the timer to quit. However we can pass only one lock to callout_halt, so passing either of them can cause a deadlock. Fix it by avoid calling callout_halt without holding llentry's lock. BTW in the first place we cannot pass llentry's lock to callout_halt because it's a rwlock... To generate a diff of this commit: cvs rdiff -u -r1.15 -r1.16 src/sys/net/if_llatbl.c cvs rdiff -u -r1.9 -r1.10 src/sys/net/if_llatbl.h cvs rdiff -u -r1.225 -r1.226 src/sys/netinet6/in6.c cvs rdiff -u -r1.221 -r1.222 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.15 src/sys/net/if_llatbl.c:1.16 --- src/sys/net/if_llatbl.c:1.15 Tue Oct 11 12:32:30 2016 +++ src/sys/net/if_llatbl.c Wed Dec 21 08:47:02 2016 @@ -1,4 +1,4 @@ -/* $NetBSD: if_llatbl.c,v 1.15 2016/10/11 12:32:30 roy Exp $ */ +/* $NetBSD: if_llatbl.c,v 1.16 2016/12/21 08:47:02 ozaki-r Exp $ */ /* * Copyright (c) 2004 Luigi Rizzo, Alessandro Cerri. All rights reserved. * Copyright (c) 2004-2008 Qing Li. All rights reserved. @@ -424,8 +424,24 @@ lltable_purge_entries(struct lltable *ll IF_AFDATA_WUNLOCK(llt->llt_ifp); LIST_FOREACH_SAFE(lle, &dchain, lle_chain, next) { - if (callout_halt(&lle->la_timer, &lle->lle_lock)) - LLE_REMREF(lle); + /* + * 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); } @@ -557,6 +573,13 @@ lltable_unlink_entry(struct lltable *llt } void +lltable_free_entry(struct lltable *llt, struct llentry *lle) +{ + + llt->llt_free_entry(llt, lle); +} + +void lltable_fill_sa_entry(const struct llentry *lle, struct sockaddr *sa) { struct lltable *llt; Index: src/sys/net/if_llatbl.h diff -u src/sys/net/if_llatbl.h:1.9 src/sys/net/if_llatbl.h:1.10 --- src/sys/net/if_llatbl.h:1.9 Mon Apr 4 07:37:07 2016 +++ src/sys/net/if_llatbl.h Wed Dec 21 08:47:02 2016 @@ -1,4 +1,4 @@ -/* $NetBSD: if_llatbl.h,v 1.9 2016/04/04 07:37:07 ozaki-r Exp $ */ +/* $NetBSD: if_llatbl.h,v 1.10 2016/12/21 08:47:02 ozaki-r Exp $ */ /* * Copyright (c) 2004 Luigi Rizzo, Alessandro Cerri. All rights reserved. * Copyright (c) 2004-2008 Qing Li. All rights reserved. @@ -273,6 +273,7 @@ struct llentry *lltable_create_lle(struc const void *paddr); void lltable_link_entry(struct lltable *llt, struct llentry *lle); void lltable_unlink_entry(struct lltable *llt, struct llentry *lle); +void lltable_free_entry(struct lltable *llt, struct llentry *lle); void lltable_fill_sa_entry(const struct llentry *lle, struct sockaddr *sa); struct ifnet *lltable_get_ifp(const struct lltable *llt); int lltable_get_af(const struct lltable *llt); Index: src/sys/netinet6/in6.c diff -u src/sys/netinet6/in6.c:1.225 src/sys/netinet6/in6.c:1.226 --- src/sys/netinet6/in6.c:1.225 Mon Dec 19 07:51:34 2016 +++ src/sys/netinet6/in6.c Wed Dec 21 08:47:02 2016 @@ -1,4 +1,4 @@ -/* $NetBSD: in6.c,v 1.225 2016/12/19 07:51:34 ozaki-r Exp $ */ +/* $NetBSD: in6.c,v 1.226 2016/12/21 08:47:02 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.225 2016/12/19 07:51:34 ozaki-r Exp $"); +__KERNEL_RCSID(0, "$NetBSD: in6.c,v 1.226 2016/12/21 08:47:02 ozaki-r Exp $"); #ifdef _KERNEL_OPT #include "opt_inet.h" @@ -2362,28 +2362,42 @@ in6_lltable_match_prefix(const struct so static void in6_lltable_free_entry(struct lltable *llt, struct llentry *lle) { - struct ifnet *ifp __diagused; + struct ifnet *ifp = llt->llt_ifp; + IF_AFDATA_WLOCK_ASSERT(ifp); LLE_WLOCK_ASSERT(lle); - KASSERT(llt != NULL); /* Unlink entry from table */ if ((lle->la_flags & LLE_LINKED) != 0) { - ifp = llt->llt_ifp; - IF_AFDATA_WLOCK_ASSERT(ifp); lltable_unlink_entry(llt, lle); + KASSERT((lle->la_flags & LLE_LINKED) == 0); } + /* + * 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_AFDATA_WUNLOCK(ifp); #ifdef NET_MPSAFE callout_halt(&lle->lle_timer, NULL); #else - KASSERT(mutex_owned(softnet_lock)); - callout_halt(&lle->lle_timer, softnet_lock); + 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); - llentry_free(lle); + lltable_drop_entry_queue(lle); + LLE_FREE_LOCKED(lle); + + IF_AFDATA_WLOCK(ifp); } static int Index: src/sys/netinet6/nd6.c diff -u src/sys/netinet6/nd6.c:1.221 src/sys/netinet6/nd6.c:1.222 --- src/sys/netinet6/nd6.c:1.221 Wed Dec 21 04:08:47 2016 +++ src/sys/netinet6/nd6.c Wed Dec 21 08:47:02 2016 @@ -1,4 +1,4 @@ -/* $NetBSD: nd6.c,v 1.221 2016/12/21 04:08:47 ozaki-r Exp $ */ +/* $NetBSD: nd6.c,v 1.222 2016/12/21 08:47:02 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.221 2016/12/21 04:08:47 ozaki-r Exp $"); +__KERNEL_RCSID(0, "$NetBSD: nd6.c,v 1.222 2016/12/21 08:47:02 ozaki-r Exp $"); #ifdef _KERNEL_OPT #include "opt_net_mpsafe.h" @@ -401,22 +401,18 @@ nd6_llinfo_settimer(struct llentry *ln, CTASSERT(sizeof(time_t) > sizeof(int)); LLE_WLOCK_ASSERT(ln); - if (xtick < 0) { - ln->ln_expire = 0; - ln->ln_ntick = 0; - callout_halt(&ln->ln_timer_ch, &ln->lle_lock); + KASSERT(xtick >= 0); + + ln->ln_expire = time_uptime + xtick / hz; + LLE_ADDREF(ln); + if (xtick > INT_MAX) { + ln->ln_ntick = xtick - INT_MAX; + callout_reset(&ln->ln_timer_ch, INT_MAX, + nd6_llinfo_timer, ln); } else { - ln->ln_expire = time_uptime + xtick / hz; - LLE_ADDREF(ln); - if (xtick > INT_MAX) { - ln->ln_ntick = xtick - INT_MAX; - callout_reset(&ln->ln_timer_ch, INT_MAX, - nd6_llinfo_timer, ln); - } else { - ln->ln_ntick = 0; - callout_reset(&ln->ln_timer_ch, xtick, - nd6_llinfo_timer, ln); - } + ln->ln_ntick = 0; + callout_reset(&ln->ln_timer_ch, xtick, + nd6_llinfo_timer, ln); } } @@ -456,6 +452,8 @@ nd6_llinfo_timer(void *arg) const struct in6_addr *daddr6 = NULL; LLE_WLOCK(ln); + if ((ln->la_flags & LLE_LINKED) == 0) + goto out; if (ln->ln_ntick > 0) { nd6_llinfo_settimer(ln, ln->ln_ntick); goto out; @@ -1208,9 +1206,6 @@ nd6_free(struct llentry *ln, int gc) * even though it is not harmful, it was not really necessary. */ - /* cancel timer */ - nd6_llinfo_settimer(ln, -1); - if (!ip6_forwarding) { ND6_WLOCK(); dr = nd6_defrouter_lookup(in6, ifp); @@ -1309,12 +1304,7 @@ nd6_free(struct llentry *ln, int gc) IF_AFDATA_LOCK(ifp); LLE_WLOCK(ln); - /* Guard against race with other llentry_free(). */ - if (ln->la_flags & LLE_LINKED) { - LLE_REMREF(ln); - llentry_free(ln); - } else - LLE_FREE_LOCKED(ln); + lltable_free_entry(LLTABLE6(ifp), ln); IF_AFDATA_UNLOCK(ifp); }