]>
Commit | Line | Data |
---|---|---|
8cbcfc8d GKH |
1 | From foo@baz Fri Mar 15 21:00:09 PDT 2019 |
2 | From: Xin Long <lucien.xin@gmail.com> | |
3 | Date: Fri, 8 Mar 2019 14:50:54 +0800 | |
4 | Subject: route: set the deleted fnhe fnhe_daddr to 0 in ip_del_fnhe to fix a race | |
5 | ||
6 | From: Xin Long <lucien.xin@gmail.com> | |
7 | ||
8 | [ Upstream commit ee60ad219f5c7c4fb2f047f88037770063ef785f ] | |
9 | ||
10 | The race occurs in __mkroute_output() when 2 threads lookup a dst: | |
11 | ||
12 | CPU A CPU B | |
13 | find_exception() | |
14 | find_exception() [fnhe expires] | |
15 | ip_del_fnhe() [fnhe is deleted] | |
16 | rt_bind_exception() | |
17 | ||
18 | In rt_bind_exception() it will bind a deleted fnhe with the new dst, and | |
19 | this dst will get no chance to be freed. It causes a dev defcnt leak and | |
20 | consecutive dmesg warnings: | |
21 | ||
22 | unregister_netdevice: waiting for ethX to become free. Usage count = 1 | |
23 | ||
24 | Especially thanks Jon to identify the issue. | |
25 | ||
26 | This patch fixes it by setting fnhe_daddr to 0 in ip_del_fnhe() to stop | |
27 | binding the deleted fnhe with a new dst when checking fnhe's fnhe_daddr | |
28 | and daddr in rt_bind_exception(). | |
29 | ||
30 | It works as both ip_del_fnhe() and rt_bind_exception() are protected by | |
31 | fnhe_lock and the fhne is freed by kfree_rcu(). | |
32 | ||
33 | Fixes: deed49df7390 ("route: check and remove route cache when we get route") | |
34 | Signed-off-by: Jon Maxwell <jmaxwell37@gmail.com> | |
35 | Signed-off-by: Xin Long <lucien.xin@gmail.com> | |
36 | Reviewed-by: David Ahern <dsahern@gmail.com> | |
37 | Signed-off-by: David S. Miller <davem@davemloft.net> | |
38 | Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> | |
39 | --- | |
40 | net/ipv4/route.c | 4 ++++ | |
41 | 1 file changed, 4 insertions(+) | |
42 | ||
43 | --- a/net/ipv4/route.c | |
44 | +++ b/net/ipv4/route.c | |
45 | @@ -1613,6 +1613,10 @@ static void ip_del_fnhe(struct fib_nh *n | |
46 | if (fnhe->fnhe_daddr == daddr) { | |
47 | rcu_assign_pointer(*fnhe_p, rcu_dereference_protected( | |
48 | fnhe->fnhe_next, lockdep_is_held(&fnhe_lock))); | |
49 | + /* set fnhe_daddr to 0 to ensure it won't bind with | |
50 | + * new dsts in rt_bind_exception(). | |
51 | + */ | |
52 | + fnhe->fnhe_daddr = 0; | |
53 | fnhe_flush_routes(fnhe); | |
54 | kfree_rcu(fnhe, rcu); | |
55 | break; |