]>
Commit | Line | Data |
---|---|---|
c9e0edd4 WB |
1 | From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 |
2 | From: Wolfgang Bumiller <w.bumiller@proxmox.com> | |
3 | Date: Mon, 9 Apr 2018 14:56:29 +0200 | |
4cae8e32 | 4 | Subject: [PATCH] net: fix deadlock while clearing neighbor proxy table |
c9e0edd4 WB |
5 | |
6 | When coming from ndisc_netdev_event() in net/ipv6/ndisc.c, | |
7 | neigh_ifdown() is called with &nd_tbl, locking this while | |
8 | clearing the proxy neighbor entries when eg. deleting an | |
9 | interface. Calling the table's pndisc_destructor() with the | |
10 | lock still held, however, can cause a deadlock: When a | |
11 | multicast listener is available an IGMP packet of type | |
12 | ICMPV6_MGM_REDUCTION may be sent out. When reaching | |
13 | ip6_finish_output2(), if no neighbor entry for the target | |
14 | address is found, __neigh_create() is called with &nd_tbl, | |
15 | which it'll want to lock. | |
16 | ||
17 | Move the elements into their own list, then unlock the table | |
18 | and perform the destruction. | |
19 | ||
20 | Bugzilla: https://bugzilla.kernel.org/show_bug.cgi?id=199289 | |
21 | Fixes: 6fd6ce2056de ("ipv6: Do not depend on rt->n in ip6_finish_output2().") | |
22 | Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com> | |
23 | --- | |
24 | net/core/neighbour.c | 28 ++++++++++++++++++---------- | |
25 | 1 file changed, 18 insertions(+), 10 deletions(-) | |
26 | ||
27 | diff --git a/net/core/neighbour.c b/net/core/neighbour.c | |
4cae8e32 | 28 | index d0713627deb6..3b495739bf65 100644 |
c9e0edd4 WB |
29 | --- a/net/core/neighbour.c |
30 | +++ b/net/core/neighbour.c | |
4cae8e32 | 31 | @@ -55,7 +55,8 @@ static void neigh_timer_handler(unsigned long arg); |
c9e0edd4 WB |
32 | static void __neigh_notify(struct neighbour *n, int type, int flags, |
33 | u32 pid); | |
34 | static void neigh_update_notify(struct neighbour *neigh, u32 nlmsg_pid); | |
35 | -static int pneigh_ifdown(struct neigh_table *tbl, struct net_device *dev); | |
36 | +static int pneigh_ifdown_and_unlock(struct neigh_table *tbl, | |
37 | + struct net_device *dev); | |
38 | ||
39 | #ifdef CONFIG_PROC_FS | |
40 | static const struct file_operations neigh_stat_seq_fops; | |
41 | @@ -291,8 +292,7 @@ int neigh_ifdown(struct neigh_table *tbl, struct net_device *dev) | |
42 | { | |
43 | write_lock_bh(&tbl->lock); | |
44 | neigh_flush_dev(tbl, dev); | |
45 | - pneigh_ifdown(tbl, dev); | |
46 | - write_unlock_bh(&tbl->lock); | |
47 | + pneigh_ifdown_and_unlock(tbl, dev); | |
48 | ||
49 | del_timer_sync(&tbl->proxy_timer); | |
50 | pneigh_queue_purge(&tbl->proxy_queue); | |
51 | @@ -681,9 +681,10 @@ int pneigh_delete(struct neigh_table *tbl, struct net *net, const void *pkey, | |
52 | return -ENOENT; | |
53 | } | |
54 | ||
55 | -static int pneigh_ifdown(struct neigh_table *tbl, struct net_device *dev) | |
56 | +static int pneigh_ifdown_and_unlock(struct neigh_table *tbl, | |
57 | + struct net_device *dev) | |
58 | { | |
59 | - struct pneigh_entry *n, **np; | |
60 | + struct pneigh_entry *n, **np, *freelist = NULL; | |
61 | u32 h; | |
62 | ||
63 | for (h = 0; h <= PNEIGH_HASHMASK; h++) { | |
64 | @@ -691,16 +692,23 @@ static int pneigh_ifdown(struct neigh_table *tbl, struct net_device *dev) | |
65 | while ((n = *np) != NULL) { | |
66 | if (!dev || n->dev == dev) { | |
67 | *np = n->next; | |
68 | - if (tbl->pdestructor) | |
69 | - tbl->pdestructor(n); | |
70 | - if (n->dev) | |
71 | - dev_put(n->dev); | |
72 | - kfree(n); | |
73 | + n->next = freelist; | |
74 | + freelist = n; | |
75 | continue; | |
76 | } | |
77 | np = &n->next; | |
78 | } | |
79 | } | |
80 | + write_unlock_bh(&tbl->lock); | |
81 | + while ((n = freelist)) { | |
82 | + freelist = n->next; | |
83 | + n->next = NULL; | |
84 | + if (tbl->pdestructor) | |
85 | + tbl->pdestructor(n); | |
86 | + if (n->dev) | |
87 | + dev_put(n->dev); | |
88 | + kfree(n); | |
89 | + } | |
90 | return -ENOENT; | |
91 | } | |
92 | ||
93 | -- | |
4cae8e32 | 94 | 2.14.2 |
c9e0edd4 | 95 |