| From: Wolfgang Bumiller <w.bumiller@proxmox.com> |
| Date: Thu, 12 Apr 2018 10:46:55 +0200 |
| Subject: net: fix deadlock while clearing neighbor proxy table |
| |
| commit 53b76cdf7e8fecec1d09e38aad2f8579882591a8 upstream. |
| |
| When coming from ndisc_netdev_event() in net/ipv6/ndisc.c, |
| neigh_ifdown() is called with &nd_tbl, locking this while |
| clearing the proxy neighbor entries when eg. deleting an |
| interface. Calling the table's pndisc_destructor() with the |
| lock still held, however, can cause a deadlock: When a |
| multicast listener is available an IGMP packet of type |
| ICMPV6_MGM_REDUCTION may be sent out. When reaching |
| ip6_finish_output2(), if no neighbor entry for the target |
| address is found, __neigh_create() is called with &nd_tbl, |
| which it'll want to lock. |
| |
| Move the elements into their own list, then unlock the table |
| and perform the destruction. |
| |
| Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=199289 |
| Fixes: 6fd6ce2056de ("ipv6: Do not depend on rt->n in ip6_finish_output2().") |
| Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com> |
| Signed-off-by: David S. Miller <davem@davemloft.net> |
| [bwh: Backported to 3.16: |
| - Move the (useless) call to release_net() as well |
| - Adjust context] |
| Signed-off-by: Ben Hutchings <ben@decadent.org.uk> |
| --- |
| --- a/net/core/neighbour.c |
| +++ b/net/core/neighbour.c |
| @@ -54,7 +54,8 @@ do { \ |
| static void neigh_timer_handler(unsigned long arg); |
| static void __neigh_notify(struct neighbour *n, int type, int flags); |
| static void neigh_update_notify(struct neighbour *neigh); |
| -static int pneigh_ifdown(struct neigh_table *tbl, struct net_device *dev); |
| +static int pneigh_ifdown_and_unlock(struct neigh_table *tbl, |
| + struct net_device *dev); |
| |
| static struct neigh_table *neigh_tables; |
| #ifdef CONFIG_PROC_FS |
| @@ -260,8 +261,7 @@ int neigh_ifdown(struct neigh_table *tbl |
| { |
| write_lock_bh(&tbl->lock); |
| neigh_flush_dev(tbl, dev); |
| - pneigh_ifdown(tbl, dev); |
| - write_unlock_bh(&tbl->lock); |
| + pneigh_ifdown_and_unlock(tbl, dev); |
| |
| del_timer_sync(&tbl->proxy_timer); |
| pneigh_queue_purge(&tbl->proxy_queue); |
| @@ -659,9 +659,10 @@ int pneigh_delete(struct neigh_table *tb |
| return -ENOENT; |
| } |
| |
| -static int pneigh_ifdown(struct neigh_table *tbl, struct net_device *dev) |
| +static int pneigh_ifdown_and_unlock(struct neigh_table *tbl, |
| + struct net_device *dev) |
| { |
| - struct pneigh_entry *n, **np; |
| + struct pneigh_entry *n, **np, *freelist = NULL; |
| u32 h; |
| |
| for (h = 0; h <= PNEIGH_HASHMASK; h++) { |
| @@ -669,17 +670,24 @@ static int pneigh_ifdown(struct neigh_ta |
| while ((n = *np) != NULL) { |
| if (!dev || n->dev == dev) { |
| *np = n->next; |
| - if (tbl->pdestructor) |
| - tbl->pdestructor(n); |
| - if (n->dev) |
| - dev_put(n->dev); |
| - release_net(pneigh_net(n)); |
| - kfree(n); |
| + n->next = freelist; |
| + freelist = n; |
| continue; |
| } |
| np = &n->next; |
| } |
| } |
| + write_unlock_bh(&tbl->lock); |
| + while ((n = freelist)) { |
| + freelist = n->next; |
| + n->next = NULL; |
| + if (tbl->pdestructor) |
| + tbl->pdestructor(n); |
| + if (n->dev) |
| + dev_put(n->dev); |
| + release_net(pneigh_net(n)); |
| + kfree(n); |
| + } |
| return -ENOENT; |
| } |
| |