diff options
author | Pavel Emelyanov <xemul@openvz.org> | 2007-11-29 08:08:14 -0500 |
---|---|---|
committer | Herbert Xu <herbert@gondor.apana.org.au> | 2007-11-29 08:08:14 -0500 |
commit | 076931989fe96823a577259cc6bc205d7ec31754 (patch) | |
tree | 9a940f50bfef9b9b9e190696668612aed316b23e /net | |
parent | 82de382ce8e1c7645984616728dc7aaa057821e4 (diff) |
[INET]: Fix inet_diag register vs rcv race
The following race is possible when one cpu unregisters the handler
while other one is trying to receive a message and call this one:
CPU1: CPU2:
inet_diag_rcv() inet_diag_unregister()
mutex_lock(&inet_diag_mutex);
netlink_rcv_skb(skb, &inet_diag_rcv_msg);
if (inet_diag_table[nlh->nlmsg_type] ==
NULL) /* false handler is still registered */
...
netlink_dump_start(idiagnl, skb, nlh,
inet_diag_dump, NULL);
cb = kzalloc(sizeof(*cb), GFP_KERNEL);
/* sleep here freeing memory
* or preempt
* or sleep later on nlk->cb_mutex
*/
spin_lock(&inet_diag_register_lock);
inet_diag_table[type] = NULL;
... spin_unlock(&inet_diag_register_lock);
synchronize_rcu();
/* CPU1 is sleeping - RCU quiescent
* state is passed
*/
return;
/* inet_diag_dump is finally called: */
inet_diag_dump()
handler = inet_diag_table[cb->nlh->nlmsg_type];
BUG_ON(handler == NULL);
/* OOPS! While we slept the unregister has set
* handler to NULL :(
*/
Grep showed, that the register/unregister functions are called
from init/fini module callbacks for tcp_/dccp_diag, so it's OK
to use the inet_diag_mutex to synchronize manipulations with the
inet_diag_table and the access to it.
Besides, as Herbert pointed out, asynchronous dumps should hold
this mutex as well, and thus, we provide the mutex as cb_mutex one.
Signed-off-by: Pavel Emelyanov <xemul@openvz.org>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
Diffstat (limited to 'net')
-rw-r--r-- | net/ipv4/inet_diag.c | 14 |
1 files changed, 5 insertions, 9 deletions
diff --git a/net/ipv4/inet_diag.c b/net/ipv4/inet_diag.c index b0170732b5e9..6b3fffb554b6 100644 --- a/net/ipv4/inet_diag.c +++ b/net/ipv4/inet_diag.c | |||
@@ -853,8 +853,6 @@ static void inet_diag_rcv(struct sk_buff *skb) | |||
853 | mutex_unlock(&inet_diag_mutex); | 853 | mutex_unlock(&inet_diag_mutex); |
854 | } | 854 | } |
855 | 855 | ||
856 | static DEFINE_SPINLOCK(inet_diag_register_lock); | ||
857 | |||
858 | int inet_diag_register(const struct inet_diag_handler *h) | 856 | int inet_diag_register(const struct inet_diag_handler *h) |
859 | { | 857 | { |
860 | const __u16 type = h->idiag_type; | 858 | const __u16 type = h->idiag_type; |
@@ -863,13 +861,13 @@ int inet_diag_register(const struct inet_diag_handler *h) | |||
863 | if (type >= INET_DIAG_GETSOCK_MAX) | 861 | if (type >= INET_DIAG_GETSOCK_MAX) |
864 | goto out; | 862 | goto out; |
865 | 863 | ||
866 | spin_lock(&inet_diag_register_lock); | 864 | mutex_lock(&inet_diag_mutex); |
867 | err = -EEXIST; | 865 | err = -EEXIST; |
868 | if (inet_diag_table[type] == NULL) { | 866 | if (inet_diag_table[type] == NULL) { |
869 | inet_diag_table[type] = h; | 867 | inet_diag_table[type] = h; |
870 | err = 0; | 868 | err = 0; |
871 | } | 869 | } |
872 | spin_unlock(&inet_diag_register_lock); | 870 | mutex_unlock(&inet_diag_mutex); |
873 | out: | 871 | out: |
874 | return err; | 872 | return err; |
875 | } | 873 | } |
@@ -882,11 +880,9 @@ void inet_diag_unregister(const struct inet_diag_handler *h) | |||
882 | if (type >= INET_DIAG_GETSOCK_MAX) | 880 | if (type >= INET_DIAG_GETSOCK_MAX) |
883 | return; | 881 | return; |
884 | 882 | ||
885 | spin_lock(&inet_diag_register_lock); | 883 | mutex_lock(&inet_diag_mutex); |
886 | inet_diag_table[type] = NULL; | 884 | inet_diag_table[type] = NULL; |
887 | spin_unlock(&inet_diag_register_lock); | 885 | mutex_unlock(&inet_diag_mutex); |
888 | |||
889 | synchronize_rcu(); | ||
890 | } | 886 | } |
891 | EXPORT_SYMBOL_GPL(inet_diag_unregister); | 887 | EXPORT_SYMBOL_GPL(inet_diag_unregister); |
892 | 888 | ||
@@ -901,7 +897,7 @@ static int __init inet_diag_init(void) | |||
901 | goto out; | 897 | goto out; |
902 | 898 | ||
903 | idiagnl = netlink_kernel_create(&init_net, NETLINK_INET_DIAG, 0, | 899 | idiagnl = netlink_kernel_create(&init_net, NETLINK_INET_DIAG, 0, |
904 | inet_diag_rcv, NULL, THIS_MODULE); | 900 | inet_diag_rcv, &inet_diag_mutex, THIS_MODULE); |
905 | if (idiagnl == NULL) | 901 | if (idiagnl == NULL) |
906 | goto out_free_table; | 902 | goto out_free_table; |
907 | err = 0; | 903 | err = 0; |