RDMA/siw: Fix IPv6 addr_list locking
authorBernard Metzler <bmt@zurich.ibm.com>
Wed, 28 Aug 2019 13:03:55 +0000 (15:03 +0200)
committerDoug Ledford <dledford@redhat.com>
Wed, 28 Aug 2019 14:29:19 +0000 (10:29 -0400)
Walking the address list of an inet6_dev requires
appropriate locking. Since the called function
siw_listen_address() may sleep, we have to use
rtnl_lock() instead of read_lock_bh().

Also introduces sanity checks if we got a device
from in_dev_get() or in6_dev_get().

Reported-by: Bart Van Assche <bvanassche@acm.org>
Fixes: 6c52fdc244b5 ("rdma/siw: connection management")
Signed-off-by: Bernard Metzler <bmt@zurich.ibm.com>
Link: https://lore.kernel.org/r/20190828130355.22830-1-bmt@zurich.ibm.com
Signed-off-by: Doug Ledford <dledford@redhat.com>
drivers/infiniband/sw/siw/siw_cm.c

index 1db5ad3..8c1931a 100644 (file)
@@ -1962,6 +1962,10 @@ int siw_create_listen(struct iw_cm_id *id, int backlog)
                struct sockaddr_in s_laddr, *s_raddr;
                const struct in_ifaddr *ifa;
 
+               if (!in_dev) {
+                       rv = -ENODEV;
+                       goto out;
+               }
                memcpy(&s_laddr, &id->local_addr, sizeof(s_laddr));
                s_raddr = (struct sockaddr_in *)&id->remote_addr;
 
@@ -1991,22 +1995,27 @@ int siw_create_listen(struct iw_cm_id *id, int backlog)
                struct sockaddr_in6 *s_laddr = &to_sockaddr_in6(id->local_addr),
                        *s_raddr = &to_sockaddr_in6(id->remote_addr);
 
+               if (!in6_dev) {
+                       rv = -ENODEV;
+                       goto out;
+               }
                siw_dbg(id->device,
                        "laddr %pI6:%d, raddr %pI6:%d\n",
                        &s_laddr->sin6_addr, ntohs(s_laddr->sin6_port),
                        &s_raddr->sin6_addr, ntohs(s_raddr->sin6_port));
 
-               read_lock_bh(&in6_dev->lock);
+               rtnl_lock();
                list_for_each_entry(ifp, &in6_dev->addr_list, if_list) {
-                       struct sockaddr_in6 bind_addr;
-
+                       if (ifp->flags & (IFA_F_TENTATIVE | IFA_F_DEPRECATED))
+                               continue;
                        if (ipv6_addr_any(&s_laddr->sin6_addr) ||
                            ipv6_addr_equal(&s_laddr->sin6_addr, &ifp->addr)) {
-                               bind_addr.sin6_family = AF_INET6;
-                               bind_addr.sin6_port = s_laddr->sin6_port;
-                               bind_addr.sin6_flowinfo = 0;
-                               bind_addr.sin6_addr = ifp->addr;
-                               bind_addr.sin6_scope_id = dev->ifindex;
+                               struct sockaddr_in6 bind_addr  = {
+                                       .sin6_family = AF_INET6,
+                                       .sin6_port = s_laddr->sin6_port,
+                                       .sin6_flowinfo = 0,
+                                       .sin6_addr = ifp->addr,
+                                       .sin6_scope_id = dev->ifindex };
 
                                rv = siw_listen_address(id, backlog,
                                                (struct sockaddr *)&bind_addr,
@@ -2015,12 +2024,12 @@ int siw_create_listen(struct iw_cm_id *id, int backlog)
                                        listeners++;
                        }
                }
-               read_unlock_bh(&in6_dev->lock);
-
+               rtnl_unlock();
                in6_dev_put(in6_dev);
        } else {
-               return -EAFNOSUPPORT;
+               rv = -EAFNOSUPPORT;
        }
+out:
        if (listeners)
                rv = 0;
        else if (!rv)