From 3e19ae7c6fd62978ae518b17ae0e30ab8d17ed07 Mon Sep 17 00:00:00 2001 From: Vladimir Oltean Date: Tue, 29 Jun 2021 17:06:44 +0300 Subject: [PATCH] net: bridge: use READ_ONCE() and WRITE_ONCE() compiler barriers for fdb->dst Annotate the writer side of fdb->dst: - fdb_create() - br_fdb_update() - fdb_add_entry() - br_fdb_external_learn_add() with WRITE_ONCE() and the reader side: - br_fdb_test_addr() - br_fdb_update() - fdb_fill_info() - fdb_add_entry() - fdb_delete_by_addr_and_port() - br_fdb_external_learn_add() - br_switchdev_fdb_notify() with compiler barriers such that the readers do not attempt to reload fdb->dst multiple times, leading to potentially different destination ports when the fdb entry is updated concurrently. This is especially important in read-side sections where fdb->dst is used more than once, but let's convert all accesses for the sake of uniformity. Suggested-by: Nikolay Aleksandrov Signed-off-by: Vladimir Oltean Signed-off-by: David S. Miller --- net/bridge/br_fdb.c | 28 +++++++++++++++++----------- net/bridge/br_switchdev.c | 7 ++++--- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/net/bridge/br_fdb.c b/net/bridge/br_fdb.c index 16f9434fdb5d..dc3ecf2d5637 100644 --- a/net/bridge/br_fdb.c +++ b/net/bridge/br_fdb.c @@ -440,9 +440,14 @@ int br_fdb_test_addr(struct net_device *dev, unsigned char *addr) if (!port) ret = 0; else { + const struct net_bridge_port *dst = NULL; + fdb = br_fdb_find_rcu(port->br, addr, 0); - ret = fdb && fdb->dst && fdb->dst->dev != dev && - fdb->dst->state == BR_STATE_FORWARDING; + if (fdb) + dst = READ_ONCE(fdb->dst); + + ret = dst && dst->dev != dev && + dst->state == BR_STATE_FORWARDING; } rcu_read_unlock(); @@ -509,7 +514,7 @@ static struct net_bridge_fdb_entry *fdb_create(struct net_bridge *br, fdb = kmem_cache_alloc(br_fdb_cache, GFP_ATOMIC); if (fdb) { memcpy(fdb->key.addr.addr, addr, ETH_ALEN); - fdb->dst = source; + WRITE_ONCE(fdb->dst, source); fdb->key.vlan_id = vid; fdb->flags = flags; fdb->updated = fdb->used = jiffies; @@ -600,10 +605,10 @@ void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source, } /* fastpath: update of existing entry */ - if (unlikely(source != fdb->dst && + if (unlikely(source != READ_ONCE(fdb->dst) && !test_bit(BR_FDB_STICKY, &fdb->flags))) { br_switchdev_fdb_notify(fdb, RTM_DELNEIGH); - fdb->dst = source; + WRITE_ONCE(fdb->dst, source); fdb_modified = true; /* Take over HW learned entry */ if (unlikely(test_bit(BR_FDB_ADDED_BY_EXT_LEARN, @@ -650,6 +655,7 @@ static int fdb_fill_info(struct sk_buff *skb, const struct net_bridge *br, const struct net_bridge_fdb_entry *fdb, u32 portid, u32 seq, int type, unsigned int flags) { + const struct net_bridge_port *dst = READ_ONCE(fdb->dst); unsigned long now = jiffies; struct nda_cacheinfo ci; struct nlmsghdr *nlh; @@ -665,7 +671,7 @@ static int fdb_fill_info(struct sk_buff *skb, const struct net_bridge *br, ndm->ndm_pad2 = 0; ndm->ndm_flags = 0; ndm->ndm_type = 0; - ndm->ndm_ifindex = fdb->dst ? fdb->dst->dev->ifindex : br->dev->ifindex; + ndm->ndm_ifindex = dst ? dst->dev->ifindex : br->dev->ifindex; ndm->ndm_state = fdb_to_nud(br, fdb); if (test_bit(BR_FDB_OFFLOADED, &fdb->flags)) @@ -964,8 +970,8 @@ static int fdb_add_entry(struct net_bridge *br, struct net_bridge_port *source, if (flags & NLM_F_EXCL) return -EEXIST; - if (fdb->dst != source) { - fdb->dst = source; + if (READ_ONCE(fdb->dst) != source) { + WRITE_ONCE(fdb->dst, source); modified = true; } } @@ -1132,7 +1138,7 @@ static int fdb_delete_by_addr_and_port(struct net_bridge *br, struct net_bridge_fdb_entry *fdb; fdb = br_fdb_find(br, addr, vlan); - if (!fdb || fdb->dst != p) + if (!fdb || READ_ONCE(fdb->dst) != p) return -ENOENT; fdb_delete(br, fdb, true); @@ -1281,8 +1287,8 @@ int br_fdb_external_learn_add(struct net_bridge *br, struct net_bridge_port *p, } else { fdb->updated = jiffies; - if (fdb->dst != p) { - fdb->dst = p; + if (READ_ONCE(fdb->dst) != p) { + WRITE_ONCE(fdb->dst, p); modified = true; } diff --git a/net/bridge/br_switchdev.c b/net/bridge/br_switchdev.c index a5e601e41cb9..192293fe37fd 100644 --- a/net/bridge/br_switchdev.c +++ b/net/bridge/br_switchdev.c @@ -110,6 +110,7 @@ int br_switchdev_set_port_flag(struct net_bridge_port *p, void br_switchdev_fdb_notify(const struct net_bridge_fdb_entry *fdb, int type) { + const struct net_bridge_port *dst = READ_ONCE(fdb->dst); struct switchdev_notifier_fdb_info info = { .addr = fdb->key.addr.addr, .vid = fdb->key.vlan_id, @@ -118,17 +119,17 @@ br_switchdev_fdb_notify(const struct net_bridge_fdb_entry *fdb, int type) .offloaded = test_bit(BR_FDB_OFFLOADED, &fdb->flags), }; - if (!fdb->dst) + if (!dst) return; switch (type) { case RTM_DELNEIGH: call_switchdev_notifiers(SWITCHDEV_FDB_DEL_TO_DEVICE, - fdb->dst->dev, &info.info, NULL); + dst->dev, &info.info, NULL); break; case RTM_NEWNEIGH: call_switchdev_notifiers(SWITCHDEV_FDB_ADD_TO_DEVICE, - fdb->dst->dev, &info.info, NULL); + dst->dev, &info.info, NULL); break; } }