Currently neigh ha address is read directly, but that can result in
torn/partial reads if the neigh is being updated. Use neigh_ha_snapshot
to take a stable snapshot of the address.

Fixes: 057658cb33fb ("bridge: suppress arp pkts on BR_NEIGH_SUPPRESS ports")
Fixes: ed842faeb2bd ("bridge: suppress nd pkts on BR_NEIGH_SUPPRESS ports")
Signed-off-by: Nikolay Aleksandrov <[email protected]>
---
v2: - use ETH_ALEN instead of MAX_ADDR_LEN, the bridge devices all use
      ETH_ALEN and vxlan allows arp/nd reduce only when not in raw/gpe
      so it also always uses ETH_ALEN
    - align ha to 2 bytes because ether_addr_copy() expects it (Sashiko)

 net/bridge/br_arp_nd_proxy.c | 24 ++++++++++++++----------
 1 file changed, 14 insertions(+), 10 deletions(-)

diff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c
index 23eb6931a2b4..fd65d6af4f35 100644
--- a/net/bridge/br_arp_nd_proxy.c
+++ b/net/bridge/br_arp_nd_proxy.c
@@ -195,13 +195,15 @@ void br_do_proxy_suppress_arp(struct sk_buff *skb, struct 
net_bridge *br,
        n = neigh_lookup(&arp_tbl, &tip, vlandev);
        if (n) {
                struct net_bridge_fdb_entry *f;
+               u8 ha[ETH_ALEN] __aligned(2);
 
                if (!(READ_ONCE(n->nud_state) & NUD_VALID)) {
                        neigh_release(n);
                        return;
                }
 
-               f = br_fdb_find_rcu(br, n->ha, vid);
+               neigh_ha_snapshot(ha, n, n->dev);
+               f = br_fdb_find_rcu(br, ha, vid);
                if (f) {
                        const struct net_bridge_port *dst = READ_ONCE(f->dst);
                        bool replied = false;
@@ -211,10 +213,10 @@ void br_do_proxy_suppress_arp(struct sk_buff *skb, struct 
net_bridge *br,
                            br_is_neigh_suppress_enabled(dst, vid)) {
                                if (!vid)
                                        br_arp_send(br, p, skb->dev, sip, tip,
-                                                   sha, n->ha, sha, 0, 0);
+                                                   sha, ha, sha, 0, 0);
                                else
                                        br_arp_send(br, p, skb->dev, sip, tip,
-                                                   sha, n->ha, sha,
+                                                   sha, ha, sha,
                                                    skb->vlan_proto,
                                                    skb_vlan_tag_get(skb));
                                replied = true;
@@ -252,7 +254,7 @@ struct nd_msg *br_is_nd_neigh_msg(const struct sk_buff 
*skb, struct nd_msg *msg)
 }
 
 static void br_nd_send(struct net_bridge *br, struct net_bridge_port *p,
-                      struct sk_buff *request, struct neighbour *n,
+                      struct sk_buff *request, struct neighbour *n, u8 *ha,
                       __be16 vlan_proto, u16 vlan_tci)
 {
        struct net_device *dev = request->dev;
@@ -310,7 +312,7 @@ static void br_nd_send(struct net_bridge *br, struct 
net_bridge_port *p,
                ipv6_eth_mc_map(&in6addr_linklocal_allnodes, 
eth_hdr(reply)->h_dest);
        else
                ether_addr_copy(eth_hdr(reply)->h_dest, daddr);
-       ether_addr_copy(eth_hdr(reply)->h_source, n->ha);
+       ether_addr_copy(eth_hdr(reply)->h_source, ha);
        eth_hdr(reply)->h_proto = htons(ETH_P_IPV6);
        reply->protocol = htons(ETH_P_IPV6);
 
@@ -340,7 +342,7 @@ static void br_nd_send(struct net_bridge *br, struct 
net_bridge_port *p,
        na->icmph.icmp6_override = 1;
        na->icmph.icmp6_solicited = dad ? 0 : 1;
        na->target = ns->target;
-       ether_addr_copy(&na->opt[2], n->ha);
+       ether_addr_copy(&na->opt[2], ha);
        na->opt[0] = ND_OPT_TARGET_LL_ADDR;
        na->opt[1] = na_olen >> 3;
 
@@ -369,7 +371,7 @@ static void br_nd_send(struct net_bridge *br, struct 
net_bridge_port *p,
                __vlan_hwaccel_put_tag(reply, vlan_proto, vlan_tci);
 
        netdev_dbg(dev, "nd send dev %s dst %pI6 dst_hw %pM src %pI6 src_hw 
%pM\n",
-                  dev->name, &pip6->daddr, daddr, &pip6->saddr, n->ha);
+                  dev->name, &pip6->daddr, daddr, &pip6->saddr, ha);
 
        if (p) {
                dev_queue_xmit(reply);
@@ -472,24 +474,26 @@ void br_do_suppress_nd(struct sk_buff *skb, struct 
net_bridge *br,
        n = neigh_lookup(&nd_tbl, &msg->target, vlandev);
        if (n) {
                struct net_bridge_fdb_entry *f;
+               u8 ha[ETH_ALEN] __aligned(2);
 
                if (!(READ_ONCE(n->nud_state) & NUD_VALID)) {
                        neigh_release(n);
                        return;
                }
 
-               f = br_fdb_find_rcu(br, n->ha, vid);
+               neigh_ha_snapshot(ha, n, n->dev);
+               f = br_fdb_find_rcu(br, ha, vid);
                if (f) {
                        const struct net_bridge_port *dst = READ_ONCE(f->dst);
                        bool replied = false;
 
                        if (br_is_neigh_suppress_enabled(dst, vid)) {
                                if (vid != 0)
-                                       br_nd_send(br, p, skb, n,
+                                       br_nd_send(br, p, skb, n, ha,
                                                   skb->vlan_proto,
                                                   skb_vlan_tag_get(skb));
                                else
-                                       br_nd_send(br, p, skb, n, 0, 0);
+                                       br_nd_send(br, p, skb, n, ha, 0, 0);
                                replied = true;
                        }
 
-- 
2.47.3


Reply via email to