Skip to content

Commit 0b9e4bb

Browse files
ARC-CPSgregkh
authored andcommitted
net: bridge: use a stable FDB dst snapshot in RCU readers
[ Upstream commit df46016 ] Local FDB entries can be rewritten in place by `fdb_delete_local()`, which updates `f->dst` to another port or to `NULL` while keeping the entry alive. Several bridge RCU readers inspect `f->dst`, including `br_fdb_fillbuf()` through the `brforward_read()` sysfs path. These readers currently load `f->dst` multiple times and can therefore observe inconsistent values across the check and later dereference. In `br_fdb_fillbuf()`, this means a concurrent local-FDB update can change `f->dst` after the NULL check and before the `port_no` dereference, leading to a NULL-ptr-deref. Fix this by taking a single `READ_ONCE()` snapshot of `f->dst` in each affected RCU reader and using that snapshot for the rest of the access sequence. Also publish the in-place `f->dst` updates in `fdb_delete_local()` with `WRITE_ONCE()` so the readers and writer use matching access patterns. Fixes: 960b589 ("bridge: Properly check if local fdb entry can be deleted in br_fdb_change_mac_address") Cc: stable@kernel.org Reported-by: Yifan Wu <yifanwucs@gmail.com> Reported-by: Juefei Pu <tomapufckgml@gmail.com> Co-developed-by: Yuan Tan <yuantan098@gmail.com> Signed-off-by: Yuan Tan <yuantan098@gmail.com> Suggested-by: Xin Liu <bird@lzu.edu.cn> Tested-by: Ren Wei <enjou1224z@gmail.com> Signed-off-by: Zhengchuan Liang <zcliangcn@gmail.com> Signed-off-by: Ren Wei <n05ec@lzu.edu.cn> Reviewed-by: Ido Schimmel <idosch@nvidia.com> Acked-by: Nikolay Aleksandrov <razor@blackwall.org> Link: https://patch.msgid.link/6570fabb85ecadb8baaf019efe856f407711c7b9.1776043229.git.zcliangcn@gmail.com Signed-off-by: Paolo Abeni <pabeni@redhat.com> [ kept `*idx < cb->args[2]` instead of `*idx < ctx->fdb_idx` ] Signed-off-by: Sasha Levin <sashal@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent 218b772 commit 0b9e4bb

2 files changed

Lines changed: 23 additions & 13 deletions

File tree

net/bridge/br_arp_nd_proxy.c

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,11 +199,12 @@ void br_do_proxy_suppress_arp(struct sk_buff *skb, struct net_bridge *br,
199199

200200
f = br_fdb_find_rcu(br, n->ha, vid);
201201
if (f) {
202+
const struct net_bridge_port *dst = READ_ONCE(f->dst);
202203
bool replied = false;
203204

204205
if ((p && (p->flags & BR_PROXYARP)) ||
205-
(f->dst && (f->dst->flags & BR_PROXYARP_WIFI)) ||
206-
br_is_neigh_suppress_enabled(f->dst, vid)) {
206+
(dst && (dst->flags & BR_PROXYARP_WIFI)) ||
207+
br_is_neigh_suppress_enabled(dst, vid)) {
207208
if (!vid)
208209
br_arp_send(br, p, skb->dev, sip, tip,
209210
sha, n->ha, sha, 0, 0);
@@ -463,9 +464,10 @@ void br_do_suppress_nd(struct sk_buff *skb, struct net_bridge *br,
463464

464465
f = br_fdb_find_rcu(br, n->ha, vid);
465466
if (f) {
467+
const struct net_bridge_port *dst = READ_ONCE(f->dst);
466468
bool replied = false;
467469

468-
if (br_is_neigh_suppress_enabled(f->dst, vid)) {
470+
if (br_is_neigh_suppress_enabled(dst, vid)) {
469471
if (vid != 0)
470472
br_nd_send(br, p, skb, n,
471473
skb->vlan_proto,

net/bridge/br_fdb.c

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ struct net_device *br_fdb_find_port(const struct net_device *br_dev,
246246
const unsigned char *addr,
247247
__u16 vid)
248248
{
249+
const struct net_bridge_port *dst;
249250
struct net_bridge_fdb_entry *f;
250251
struct net_device *dev = NULL;
251252
struct net_bridge *br;
@@ -258,8 +259,11 @@ struct net_device *br_fdb_find_port(const struct net_device *br_dev,
258259
br = netdev_priv(br_dev);
259260
rcu_read_lock();
260261
f = br_fdb_find_rcu(br, addr, vid);
261-
if (f && f->dst)
262-
dev = f->dst->dev;
262+
if (f) {
263+
dst = READ_ONCE(f->dst);
264+
if (dst)
265+
dev = dst->dev;
266+
}
263267
rcu_read_unlock();
264268

265269
return dev;
@@ -349,7 +353,7 @@ static void fdb_delete_local(struct net_bridge *br,
349353
vg = nbp_vlan_group(op);
350354
if (op != p && ether_addr_equal(op->dev->dev_addr, addr) &&
351355
(!vid || br_vlan_find(vg, vid))) {
352-
f->dst = op;
356+
WRITE_ONCE(f->dst, op);
353357
clear_bit(BR_FDB_ADDED_BY_USER, &f->flags);
354358
return;
355359
}
@@ -360,7 +364,7 @@ static void fdb_delete_local(struct net_bridge *br,
360364
/* Maybe bridge device has same hw addr? */
361365
if (p && ether_addr_equal(br->dev->dev_addr, addr) &&
362366
(!vid || (v && br_vlan_should_use(v)))) {
363-
f->dst = NULL;
367+
WRITE_ONCE(f->dst, NULL);
364368
clear_bit(BR_FDB_ADDED_BY_USER, &f->flags);
365369
return;
366370
}
@@ -790,6 +794,7 @@ int br_fdb_test_addr(struct net_device *dev, unsigned char *addr)
790794
int br_fdb_fillbuf(struct net_bridge *br, void *buf,
791795
unsigned long maxnum, unsigned long skip)
792796
{
797+
const struct net_bridge_port *dst;
793798
struct net_bridge_fdb_entry *f;
794799
struct __fdb_entry *fe = buf;
795800
unsigned long delta;
@@ -806,7 +811,8 @@ int br_fdb_fillbuf(struct net_bridge *br, void *buf,
806811
continue;
807812

808813
/* ignore pseudo entry for local MAC address */
809-
if (!f->dst)
814+
dst = READ_ONCE(f->dst);
815+
if (!dst)
810816
continue;
811817

812818
if (skip) {
@@ -818,8 +824,8 @@ int br_fdb_fillbuf(struct net_bridge *br, void *buf,
818824
memcpy(fe->mac_addr, f->key.addr.addr, ETH_ALEN);
819825

820826
/* due to ABI compat need to split into hi/lo */
821-
fe->port_no = f->dst->port_no;
822-
fe->port_hi = f->dst->port_no >> 8;
827+
fe->port_no = dst->port_no;
828+
fe->port_hi = dst->port_no >> 8;
823829

824830
fe->is_local = test_bit(BR_FDB_LOCAL, &f->flags);
825831
if (!test_bit(BR_FDB_STATIC, &f->flags)) {
@@ -940,20 +946,22 @@ int br_fdb_dump(struct sk_buff *skb,
940946

941947
rcu_read_lock();
942948
hlist_for_each_entry_rcu(f, &br->fdb_list, fdb_node) {
949+
const struct net_bridge_port *dst = READ_ONCE(f->dst);
950+
943951
if (*idx < cb->args[2])
944952
goto skip;
945-
if (filter_dev && (!f->dst || f->dst->dev != filter_dev)) {
953+
if (filter_dev && (!dst || dst->dev != filter_dev)) {
946954
if (filter_dev != dev)
947955
goto skip;
948956
/* !f->dst is a special case for bridge
949957
* It means the MAC belongs to the bridge
950958
* Therefore need a little more filtering
951959
* we only want to dump the !f->dst case
952960
*/
953-
if (f->dst)
961+
if (dst)
954962
goto skip;
955963
}
956-
if (!filter_dev && f->dst)
964+
if (!filter_dev && dst)
957965
goto skip;
958966

959967
err = fdb_fill_info(skb, br, f,

0 commit comments

Comments
 (0)