ipv6: fix a potential deadlock in do_ipv6_setsockopt()
authorWANG Cong <xiyou.wangcong@gmail.com>
Thu, 20 Oct 2016 06:35:12 +0000 (23:35 -0700)
committerDavid S. Miller <davem@davemloft.net>
Fri, 21 Oct 2016 15:29:02 +0000 (11:29 -0400)
Baozeng reported this deadlock case:

       CPU0                    CPU1
       ----                    ----
  lock([  165.136033] sk_lock-AF_INET6);
                               lock([  165.136033] rtnl_mutex);
                               lock([  165.136033] sk_lock-AF_INET6);
  lock([  165.136033] rtnl_mutex);

Similar to commit 87e9f0315952
("ipv4: fix a potential deadlock in mcast getsockopt() path")
this is due to we still have a case, ipv6_sock_mc_close(),
where we acquire sk_lock before rtnl_lock. Close this deadlock
with the similar solution, that is always acquire rtnl lock first.

Fixes: baf606d9c9b1 ("ipv4,ipv6: grab rtnl before locking the socket")
Reported-by: Baozeng Ding <sploving1@gmail.com>
Tested-by: Baozeng Ding <sploving1@gmail.com>
Cc: Marcelo Ricardo Leitner <marcelo.leitner@gmail.com>
Signed-off-by: Cong Wang <xiyou.wangcong@gmail.com>
Reviewed-by: Marcelo Ricardo Leitner <marcelo.leitner@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/net/addrconf.h
net/ipv6/ipv6_sockglue.c
net/ipv6/mcast.c

index f2d0727879472451e0c2815fc68f90b66a4c388c..8f998afc138434f672ab28883287e463f60f4733 100644 (file)
@@ -174,6 +174,7 @@ int ipv6_sock_mc_join(struct sock *sk, int ifindex,
                      const struct in6_addr *addr);
 int ipv6_sock_mc_drop(struct sock *sk, int ifindex,
                      const struct in6_addr *addr);
+void __ipv6_sock_mc_close(struct sock *sk);
 void ipv6_sock_mc_close(struct sock *sk);
 bool inet6_mc_check(struct sock *sk, const struct in6_addr *mc_addr,
                    const struct in6_addr *src_addr);
index 5330262ab673c022fbf700d22782a74ccd1494fe..636ec56f5f5028277fc69721464ba734621a91e0 100644 (file)
@@ -120,6 +120,7 @@ struct ipv6_txoptions *ipv6_update_options(struct sock *sk,
 static bool setsockopt_needs_rtnl(int optname)
 {
        switch (optname) {
+       case IPV6_ADDRFORM:
        case IPV6_ADD_MEMBERSHIP:
        case IPV6_DROP_MEMBERSHIP:
        case IPV6_JOIN_ANYCAST:
@@ -198,7 +199,7 @@ static int do_ipv6_setsockopt(struct sock *sk, int level, int optname,
                        }
 
                        fl6_free_socklist(sk);
-                       ipv6_sock_mc_close(sk);
+                       __ipv6_sock_mc_close(sk);
 
                        /*
                         * Sock is moving from IPv6 to IPv4 (sk_prot), so
index 75c1fc54f188939c4ed78a7323e8907c5e3b9be9..14a3903f1c82d83d44c39befdfe827833d09b13c 100644 (file)
@@ -276,16 +276,14 @@ static struct inet6_dev *ip6_mc_find_dev_rcu(struct net *net,
        return idev;
 }
 
-void ipv6_sock_mc_close(struct sock *sk)
+void __ipv6_sock_mc_close(struct sock *sk)
 {
        struct ipv6_pinfo *np = inet6_sk(sk);
        struct ipv6_mc_socklist *mc_lst;
        struct net *net = sock_net(sk);
 
-       if (!rcu_access_pointer(np->ipv6_mc_list))
-               return;
+       ASSERT_RTNL();
 
-       rtnl_lock();
        while ((mc_lst = rtnl_dereference(np->ipv6_mc_list)) != NULL) {
                struct net_device *dev;
 
@@ -303,8 +301,17 @@ void ipv6_sock_mc_close(struct sock *sk)
 
                atomic_sub(sizeof(*mc_lst), &sk->sk_omem_alloc);
                kfree_rcu(mc_lst, rcu);
-
        }
+}
+
+void ipv6_sock_mc_close(struct sock *sk)
+{
+       struct ipv6_pinfo *np = inet6_sk(sk);
+
+       if (!rcu_access_pointer(np->ipv6_mc_list))
+               return;
+       rtnl_lock();
+       __ipv6_sock_mc_close(sk);
        rtnl_unlock();
 }