Skip to content

Commit 012e5e5

Browse files
Blubgregkh
authored andcommitted
net: fix deadlock while clearing neighbor proxy table
[ Upstream commit 53b76cd ] 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: 6fd6ce2 ("ipv6: Do not depend on rt->n in ip6_finish_output2().") Signed-off-by: Wolfgang Bumiller <[email protected]> Signed-off-by: David S. Miller <[email protected]> Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent d5387e6 commit 012e5e5

File tree

1 file changed

+18
-10
lines changed

1 file changed

+18
-10
lines changed

net/core/neighbour.c

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ static void neigh_timer_handler(unsigned long arg);
5555
static void __neigh_notify(struct neighbour *n, int type, int flags,
5656
u32 pid);
5757
static void neigh_update_notify(struct neighbour *neigh, u32 nlmsg_pid);
58-
static int pneigh_ifdown(struct neigh_table *tbl, struct net_device *dev);
58+
static int pneigh_ifdown_and_unlock(struct neigh_table *tbl,
59+
struct net_device *dev);
5960

6061
#ifdef CONFIG_PROC_FS
6162
static const struct file_operations neigh_stat_seq_fops;
@@ -291,8 +292,7 @@ int neigh_ifdown(struct neigh_table *tbl, struct net_device *dev)
291292
{
292293
write_lock_bh(&tbl->lock);
293294
neigh_flush_dev(tbl, dev);
294-
pneigh_ifdown(tbl, dev);
295-
write_unlock_bh(&tbl->lock);
295+
pneigh_ifdown_and_unlock(tbl, dev);
296296

297297
del_timer_sync(&tbl->proxy_timer);
298298
pneigh_queue_purge(&tbl->proxy_queue);
@@ -681,26 +681,34 @@ int pneigh_delete(struct neigh_table *tbl, struct net *net, const void *pkey,
681681
return -ENOENT;
682682
}
683683

684-
static int pneigh_ifdown(struct neigh_table *tbl, struct net_device *dev)
684+
static int pneigh_ifdown_and_unlock(struct neigh_table *tbl,
685+
struct net_device *dev)
685686
{
686-
struct pneigh_entry *n, **np;
687+
struct pneigh_entry *n, **np, *freelist = NULL;
687688
u32 h;
688689

689690
for (h = 0; h <= PNEIGH_HASHMASK; h++) {
690691
np = &tbl->phash_buckets[h];
691692
while ((n = *np) != NULL) {
692693
if (!dev || n->dev == dev) {
693694
*np = n->next;
694-
if (tbl->pdestructor)
695-
tbl->pdestructor(n);
696-
if (n->dev)
697-
dev_put(n->dev);
698-
kfree(n);
695+
n->next = freelist;
696+
freelist = n;
699697
continue;
700698
}
701699
np = &n->next;
702700
}
703701
}
702+
write_unlock_bh(&tbl->lock);
703+
while ((n = freelist)) {
704+
freelist = n->next;
705+
n->next = NULL;
706+
if (tbl->pdestructor)
707+
tbl->pdestructor(n);
708+
if (n->dev)
709+
dev_put(n->dev);
710+
kfree(n);
711+
}
704712
return -ENOENT;
705713
}
706714

0 commit comments

Comments
 (0)