]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/blobdiff - releases/3.6.7/netlink-use-kfree_rcu-in-netlink_release.patch
Linux 3.6.7
[thirdparty/kernel/stable-queue.git] / releases / 3.6.7 / netlink-use-kfree_rcu-in-netlink_release.patch
diff --git a/releases/3.6.7/netlink-use-kfree_rcu-in-netlink_release.patch b/releases/3.6.7/netlink-use-kfree_rcu-in-netlink_release.patch
new file mode 100644 (file)
index 0000000..75dc9f4
--- /dev/null
@@ -0,0 +1,102 @@
+From adfd10093e7f1cf3e2687e8cfc40855bc4d33041 Mon Sep 17 00:00:00 2001
+From: Eric Dumazet <edumazet@google.com>
+Date: Thu, 18 Oct 2012 03:21:55 +0000
+Subject: netlink: use kfree_rcu() in netlink_release()
+
+
+From: Eric Dumazet <edumazet@google.com>
+
+[ Upstream commit 6d772ac5578f711d1ce7b03535d1c95bffb21dff ]
+
+On some suspend/resume operations involving wimax device, we have
+noticed some intermittent memory corruptions in netlink code.
+
+Stéphane Marchesin tracked this corruption in netlink_update_listeners()
+and suggested a patch.
+
+It appears netlink_release() should use kfree_rcu() instead of kfree()
+for the listeners structure as it may be used by other cpus using RCU
+protection.
+
+netlink_release() must set to NULL the listeners pointer when
+it is about to be freed.
+
+Also have to protect netlink_update_listeners() and
+netlink_has_listeners() if listeners is NULL.
+
+Add a nl_deref_protected() lockdep helper to properly document which
+locks protects us.
+
+Reported-by: Jonathan Kliegman <kliegs@google.com>
+Signed-off-by: Eric Dumazet <edumazet@google.com>
+Cc: Stéphane Marchesin <marcheu@google.com>
+Cc: Sam Leffler <sleffler@google.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/netlink/af_netlink.c |   19 +++++++++++++++----
+ 1 file changed, 15 insertions(+), 4 deletions(-)
+
+--- a/net/netlink/af_netlink.c
++++ b/net/netlink/af_netlink.c
+@@ -138,6 +138,8 @@ static int netlink_dump(struct sock *sk)
+ static DEFINE_RWLOCK(nl_table_lock);
+ static atomic_t nl_table_users = ATOMIC_INIT(0);
++#define nl_deref_protected(X) rcu_dereference_protected(X, lockdep_is_held(&nl_table_lock));
++
+ static ATOMIC_NOTIFIER_HEAD(netlink_chain);
+ static inline u32 netlink_group_mask(u32 group)
+@@ -345,6 +347,11 @@ netlink_update_listeners(struct sock *sk
+       struct hlist_node *node;
+       unsigned long mask;
+       unsigned int i;
++      struct listeners *listeners;
++
++      listeners = nl_deref_protected(tbl->listeners);
++      if (!listeners)
++              return;
+       for (i = 0; i < NLGRPLONGS(tbl->groups); i++) {
+               mask = 0;
+@@ -352,7 +359,7 @@ netlink_update_listeners(struct sock *sk
+                       if (i < NLGRPLONGS(nlk_sk(sk)->ngroups))
+                               mask |= nlk_sk(sk)->groups[i];
+               }
+-              tbl->listeners->masks[i] = mask;
++              listeners->masks[i] = mask;
+       }
+       /* this function is only called with the netlink table "grabbed", which
+        * makes sure updates are visible before bind or setsockopt return. */
+@@ -536,7 +543,11 @@ static int netlink_release(struct socket
+       if (netlink_is_kernel(sk)) {
+               BUG_ON(nl_table[sk->sk_protocol].registered == 0);
+               if (--nl_table[sk->sk_protocol].registered == 0) {
+-                      kfree(nl_table[sk->sk_protocol].listeners);
++                      struct listeners *old;
++
++                      old = nl_deref_protected(nl_table[sk->sk_protocol].listeners);
++                      RCU_INIT_POINTER(nl_table[sk->sk_protocol].listeners, NULL);
++                      kfree_rcu(old, rcu);
+                       nl_table[sk->sk_protocol].module = NULL;
+                       nl_table[sk->sk_protocol].registered = 0;
+               }
+@@ -978,7 +989,7 @@ int netlink_has_listeners(struct sock *s
+       rcu_read_lock();
+       listeners = rcu_dereference(nl_table[sk->sk_protocol].listeners);
+-      if (group - 1 < nl_table[sk->sk_protocol].groups)
++      if (listeners && group - 1 < nl_table[sk->sk_protocol].groups)
+               res = test_bit(group - 1, listeners->masks);
+       rcu_read_unlock();
+@@ -1620,7 +1631,7 @@ int __netlink_change_ngroups(struct sock
+               new = kzalloc(sizeof(*new) + NLGRPSZ(groups), GFP_ATOMIC);
+               if (!new)
+                       return -ENOMEM;
+-              old = rcu_dereference_protected(tbl->listeners, 1);
++              old = nl_deref_protected(tbl->listeners);
+               memcpy(new->masks, old->masks, NLGRPSZ(tbl->groups));
+               rcu_assign_pointer(tbl->listeners, new);