diff options
author | Eric Dumazet <edumazet@google.com> | 2012-08-19 06:31:48 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2012-08-23 01:39:46 -0400 |
commit | ef8531b64c3e2443da52e9f05d74a988230eedc5 (patch) | |
tree | 685df5cd6a04c761d3f7d3927185e4c0678c4dbb /net/xfrm | |
parent | 0115e8e30d6fcdd4b8faa30d3ffd90859a591f51 (diff) |
xfrm: fix RCU bugs
This patch reverts commit 56892261ed1a (xfrm: Use rcu_dereference_bh to
deference pointer protected by rcu_read_lock_bh), and fixes bugs
introduced in commit 418a99ac6ad ( Replace rwlock on xfrm_policy_afinfo
with rcu )
1) We properly use RCU variant in this file, not a mix of RCU/RCU_BH
2) We must defer some writes after the synchronize_rcu() call or a reader
can crash dereferencing NULL pointer.
3) Now we use the xfrm_policy_afinfo_lock spinlock only from process
context, we no longer need to block BH in xfrm_policy_register_afinfo()
and xfrm_policy_unregister_afinfo()
4) Can use RCU_INIT_POINTER() instead of rcu_assign_pointer() in
xfrm_policy_unregister_afinfo()
5) Remove a forward inline declaration (xfrm_policy_put_afinfo()),
and also move xfrm_policy_get_afinfo() declaration.
Signed-off-by: Eric Dumazet <edumazet@google.com>
Cc: Fan Du <fan.du@windriver.com>
Cc: Priyanka Jain <Priyanka.Jain@freescale.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/xfrm')
-rw-r--r-- | net/xfrm/xfrm_policy.c | 76 |
1 files changed, 39 insertions, 37 deletions
diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c index 2ed698c190b5..741a32aa512e 100644 --- a/net/xfrm/xfrm_policy.c +++ b/net/xfrm/xfrm_policy.c | |||
@@ -48,8 +48,6 @@ static struct xfrm_policy_afinfo __rcu *xfrm_policy_afinfo[NPROTO] | |||
48 | 48 | ||
49 | static struct kmem_cache *xfrm_dst_cache __read_mostly; | 49 | static struct kmem_cache *xfrm_dst_cache __read_mostly; |
50 | 50 | ||
51 | static struct xfrm_policy_afinfo *xfrm_policy_get_afinfo(unsigned short family); | ||
52 | static inline void xfrm_policy_put_afinfo(struct xfrm_policy_afinfo *afinfo); | ||
53 | static void xfrm_init_pmtu(struct dst_entry *dst); | 51 | static void xfrm_init_pmtu(struct dst_entry *dst); |
54 | static int stale_bundle(struct dst_entry *dst); | 52 | static int stale_bundle(struct dst_entry *dst); |
55 | static int xfrm_bundle_ok(struct xfrm_dst *xdst); | 53 | static int xfrm_bundle_ok(struct xfrm_dst *xdst); |
@@ -96,6 +94,24 @@ bool xfrm_selector_match(const struct xfrm_selector *sel, const struct flowi *fl | |||
96 | return false; | 94 | return false; |
97 | } | 95 | } |
98 | 96 | ||
97 | static struct xfrm_policy_afinfo *xfrm_policy_get_afinfo(unsigned short family) | ||
98 | { | ||
99 | struct xfrm_policy_afinfo *afinfo; | ||
100 | |||
101 | if (unlikely(family >= NPROTO)) | ||
102 | return NULL; | ||
103 | rcu_read_lock(); | ||
104 | afinfo = rcu_dereference(xfrm_policy_afinfo[family]); | ||
105 | if (unlikely(!afinfo)) | ||
106 | rcu_read_unlock(); | ||
107 | return afinfo; | ||
108 | } | ||
109 | |||
110 | static void xfrm_policy_put_afinfo(struct xfrm_policy_afinfo *afinfo) | ||
111 | { | ||
112 | rcu_read_unlock(); | ||
113 | } | ||
114 | |||
99 | static inline struct dst_entry *__xfrm_dst_lookup(struct net *net, int tos, | 115 | static inline struct dst_entry *__xfrm_dst_lookup(struct net *net, int tos, |
100 | const xfrm_address_t *saddr, | 116 | const xfrm_address_t *saddr, |
101 | const xfrm_address_t *daddr, | 117 | const xfrm_address_t *daddr, |
@@ -2421,7 +2437,7 @@ int xfrm_policy_register_afinfo(struct xfrm_policy_afinfo *afinfo) | |||
2421 | return -EINVAL; | 2437 | return -EINVAL; |
2422 | if (unlikely(afinfo->family >= NPROTO)) | 2438 | if (unlikely(afinfo->family >= NPROTO)) |
2423 | return -EAFNOSUPPORT; | 2439 | return -EAFNOSUPPORT; |
2424 | spin_lock_bh(&xfrm_policy_afinfo_lock); | 2440 | spin_lock(&xfrm_policy_afinfo_lock); |
2425 | if (unlikely(xfrm_policy_afinfo[afinfo->family] != NULL)) | 2441 | if (unlikely(xfrm_policy_afinfo[afinfo->family] != NULL)) |
2426 | err = -ENOBUFS; | 2442 | err = -ENOBUFS; |
2427 | else { | 2443 | else { |
@@ -2444,7 +2460,7 @@ int xfrm_policy_register_afinfo(struct xfrm_policy_afinfo *afinfo) | |||
2444 | afinfo->garbage_collect = xfrm_garbage_collect_deferred; | 2460 | afinfo->garbage_collect = xfrm_garbage_collect_deferred; |
2445 | rcu_assign_pointer(xfrm_policy_afinfo[afinfo->family], afinfo); | 2461 | rcu_assign_pointer(xfrm_policy_afinfo[afinfo->family], afinfo); |
2446 | } | 2462 | } |
2447 | spin_unlock_bh(&xfrm_policy_afinfo_lock); | 2463 | spin_unlock(&xfrm_policy_afinfo_lock); |
2448 | 2464 | ||
2449 | rtnl_lock(); | 2465 | rtnl_lock(); |
2450 | for_each_net(net) { | 2466 | for_each_net(net) { |
@@ -2477,23 +2493,26 @@ int xfrm_policy_unregister_afinfo(struct xfrm_policy_afinfo *afinfo) | |||
2477 | return -EINVAL; | 2493 | return -EINVAL; |
2478 | if (unlikely(afinfo->family >= NPROTO)) | 2494 | if (unlikely(afinfo->family >= NPROTO)) |
2479 | return -EAFNOSUPPORT; | 2495 | return -EAFNOSUPPORT; |
2480 | spin_lock_bh(&xfrm_policy_afinfo_lock); | 2496 | spin_lock(&xfrm_policy_afinfo_lock); |
2481 | if (likely(xfrm_policy_afinfo[afinfo->family] != NULL)) { | 2497 | if (likely(xfrm_policy_afinfo[afinfo->family] != NULL)) { |
2482 | if (unlikely(xfrm_policy_afinfo[afinfo->family] != afinfo)) | 2498 | if (unlikely(xfrm_policy_afinfo[afinfo->family] != afinfo)) |
2483 | err = -EINVAL; | 2499 | err = -EINVAL; |
2484 | else { | 2500 | else |
2485 | struct dst_ops *dst_ops = afinfo->dst_ops; | 2501 | RCU_INIT_POINTER(xfrm_policy_afinfo[afinfo->family], |
2486 | rcu_assign_pointer(xfrm_policy_afinfo[afinfo->family], | 2502 | NULL); |
2487 | NULL); | 2503 | } |
2488 | dst_ops->kmem_cachep = NULL; | 2504 | spin_unlock(&xfrm_policy_afinfo_lock); |
2489 | dst_ops->check = NULL; | 2505 | if (!err) { |
2490 | dst_ops->negative_advice = NULL; | 2506 | struct dst_ops *dst_ops = afinfo->dst_ops; |
2491 | dst_ops->link_failure = NULL; | 2507 | |
2492 | afinfo->garbage_collect = NULL; | 2508 | synchronize_rcu(); |
2493 | } | 2509 | |
2510 | dst_ops->kmem_cachep = NULL; | ||
2511 | dst_ops->check = NULL; | ||
2512 | dst_ops->negative_advice = NULL; | ||
2513 | dst_ops->link_failure = NULL; | ||
2514 | afinfo->garbage_collect = NULL; | ||
2494 | } | 2515 | } |
2495 | spin_unlock_bh(&xfrm_policy_afinfo_lock); | ||
2496 | synchronize_rcu(); | ||
2497 | return err; | 2516 | return err; |
2498 | } | 2517 | } |
2499 | EXPORT_SYMBOL(xfrm_policy_unregister_afinfo); | 2518 | EXPORT_SYMBOL(xfrm_policy_unregister_afinfo); |
@@ -2502,32 +2521,15 @@ static void __net_init xfrm_dst_ops_init(struct net *net) | |||
2502 | { | 2521 | { |
2503 | struct xfrm_policy_afinfo *afinfo; | 2522 | struct xfrm_policy_afinfo *afinfo; |
2504 | 2523 | ||
2505 | rcu_read_lock_bh(); | 2524 | rcu_read_lock(); |
2506 | afinfo = rcu_dereference_bh(xfrm_policy_afinfo[AF_INET]); | 2525 | afinfo = rcu_dereference(xfrm_policy_afinfo[AF_INET]); |
2507 | if (afinfo) | 2526 | if (afinfo) |
2508 | net->xfrm.xfrm4_dst_ops = *afinfo->dst_ops; | 2527 | net->xfrm.xfrm4_dst_ops = *afinfo->dst_ops; |
2509 | #if IS_ENABLED(CONFIG_IPV6) | 2528 | #if IS_ENABLED(CONFIG_IPV6) |
2510 | afinfo = rcu_dereference_bh(xfrm_policy_afinfo[AF_INET6]); | 2529 | afinfo = rcu_dereference(xfrm_policy_afinfo[AF_INET6]); |
2511 | if (afinfo) | 2530 | if (afinfo) |
2512 | net->xfrm.xfrm6_dst_ops = *afinfo->dst_ops; | 2531 | net->xfrm.xfrm6_dst_ops = *afinfo->dst_ops; |
2513 | #endif | 2532 | #endif |
2514 | rcu_read_unlock_bh(); | ||
2515 | } | ||
2516 | |||
2517 | static struct xfrm_policy_afinfo *xfrm_policy_get_afinfo(unsigned short family) | ||
2518 | { | ||
2519 | struct xfrm_policy_afinfo *afinfo; | ||
2520 | if (unlikely(family >= NPROTO)) | ||
2521 | return NULL; | ||
2522 | rcu_read_lock(); | ||
2523 | afinfo = rcu_dereference(xfrm_policy_afinfo[family]); | ||
2524 | if (unlikely(!afinfo)) | ||
2525 | rcu_read_unlock(); | ||
2526 | return afinfo; | ||
2527 | } | ||
2528 | |||
2529 | static inline void xfrm_policy_put_afinfo(struct xfrm_policy_afinfo *afinfo) | ||
2530 | { | ||
2531 | rcu_read_unlock(); | 2533 | rcu_read_unlock(); |
2532 | } | 2534 | } |
2533 | 2535 | ||