vxlan: few locking fixes in nexthop event handler
authorRoopa Prabhu <roopa@cumulusnetworks.com>
Fri, 29 May 2020 05:12:36 +0000 (22:12 -0700)
committerDavid S. Miller <davem@davemloft.net>
Sun, 31 May 2020 04:47:08 +0000 (21:47 -0700)
- remove fdb from nh_list before the rcu grace period
- protect fdb->vdev with rcu
- hold spin lock before destroying fdb

Fixes: c7cdbe2efc40 ("vxlan: support for nexthop notifiers")
Signed-off-by: Roopa Prabhu <roopa@cumulusnetworks.com>
Reviewed-by: Nikolay Aleksandrov <nikolay@cumulusnetworks.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/vxlan.c

index fe606c6..39bc10a 100644 (file)
@@ -81,7 +81,7 @@ struct vxlan_fdb {
        u16               flags;        /* see ndm_flags and below */
        struct list_head  nh_list;
        struct nexthop __rcu *nh;
-       struct vxlan_dev  *vdev;
+       struct vxlan_dev  __rcu *vdev;
 };
 
 #define NTF_VXLAN_ADDED_BY_USER 0x100
@@ -837,7 +837,7 @@ static struct vxlan_fdb *vxlan_fdb_alloc(struct vxlan_dev *vxlan, const u8 *mac,
        f->updated = f->used = jiffies;
        f->vni = src_vni;
        f->nh = NULL;
-       f->vdev = vxlan;
+       RCU_INIT_POINTER(f->vdev, vxlan);
        INIT_LIST_HEAD(&f->nh_list);
        INIT_LIST_HEAD(&f->remotes);
        memcpy(f->eth_addr, mac, ETH_ALEN);
@@ -963,7 +963,7 @@ static void __vxlan_fdb_free(struct vxlan_fdb *f)
        nh = rcu_dereference_raw(f->nh);
        if (nh) {
                rcu_assign_pointer(f->nh, NULL);
-               list_del_rcu(&f->nh_list);
+               rcu_assign_pointer(f->vdev, NULL);
                nexthop_put(nh);
        }
 
@@ -1000,7 +1000,7 @@ static void vxlan_fdb_destroy(struct vxlan_dev *vxlan, struct vxlan_fdb *f,
        }
 
        hlist_del_rcu(&f->hlist);
-       f->vdev = NULL;
+       list_del_rcu(&f->nh_list);
        call_rcu(&f->rcu, vxlan_fdb_free);
 }
 
@@ -4615,17 +4615,35 @@ static struct notifier_block vxlan_switchdev_notifier_block __read_mostly = {
        .notifier_call = vxlan_switchdev_event,
 };
 
+static void vxlan_fdb_nh_flush(struct nexthop *nh)
+{
+       struct vxlan_fdb *fdb;
+       struct vxlan_dev *vxlan;
+       u32 hash_index;
+
+       rcu_read_lock();
+       list_for_each_entry_rcu(fdb, &nh->fdb_list, nh_list) {
+               vxlan = rcu_dereference(fdb->vdev);
+               WARN_ON(!vxlan);
+               hash_index = fdb_head_index(vxlan, fdb->eth_addr,
+                                           vxlan->default_dst.remote_vni);
+               spin_lock_bh(&vxlan->hash_lock[hash_index]);
+               if (!hlist_unhashed(&fdb->hlist))
+                       vxlan_fdb_destroy(vxlan, fdb, false, false);
+               spin_unlock_bh(&vxlan->hash_lock[hash_index]);
+       }
+       rcu_read_unlock();
+}
+
 static int vxlan_nexthop_event(struct notifier_block *nb,
                               unsigned long event, void *ptr)
 {
        struct nexthop *nh = ptr;
-       struct vxlan_fdb *fdb, *tmp;
 
        if (!nh || event != NEXTHOP_EVENT_DEL)
                return NOTIFY_DONE;
 
-       list_for_each_entry_safe(fdb, tmp, &nh->fdb_list, nh_list)
-               vxlan_fdb_destroy(fdb->vdev, fdb, false, false);
+       vxlan_fdb_nh_flush(nh);
 
        return NOTIFY_DONE;
 }