mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-05-16 00:51:51 -04:00
net: bridge: use a stable FDB dst snapshot in RCU readers
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: 960b589f86 ("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>
This commit is contained in:
committed by
Paolo Abeni
parent
fa92a77b0e
commit
df46016532
@@ -201,11 +201,12 @@ void br_do_proxy_suppress_arp(struct sk_buff *skb, struct net_bridge *br,
|
||||
|
||||
f = br_fdb_find_rcu(br, n->ha, vid);
|
||||
if (f) {
|
||||
const struct net_bridge_port *dst = READ_ONCE(f->dst);
|
||||
bool replied = false;
|
||||
|
||||
if ((p && (p->flags & BR_PROXYARP)) ||
|
||||
(f->dst && (f->dst->flags & BR_PROXYARP_WIFI)) ||
|
||||
br_is_neigh_suppress_enabled(f->dst, vid)) {
|
||||
(dst && (dst->flags & BR_PROXYARP_WIFI)) ||
|
||||
br_is_neigh_suppress_enabled(dst, vid)) {
|
||||
if (!vid)
|
||||
br_arp_send(br, p, skb->dev, sip, tip,
|
||||
sha, n->ha, sha, 0, 0);
|
||||
@@ -469,9 +470,10 @@ void br_do_suppress_nd(struct sk_buff *skb, struct net_bridge *br,
|
||||
|
||||
f = br_fdb_find_rcu(br, n->ha, vid);
|
||||
if (f) {
|
||||
const struct net_bridge_port *dst = READ_ONCE(f->dst);
|
||||
bool replied = false;
|
||||
|
||||
if (br_is_neigh_suppress_enabled(f->dst, vid)) {
|
||||
if (br_is_neigh_suppress_enabled(dst, vid)) {
|
||||
if (vid != 0)
|
||||
br_nd_send(br, p, skb, n,
|
||||
skb->vlan_proto,
|
||||
|
||||
@@ -236,6 +236,7 @@ struct net_device *br_fdb_find_port(const struct net_device *br_dev,
|
||||
const unsigned char *addr,
|
||||
__u16 vid)
|
||||
{
|
||||
const struct net_bridge_port *dst;
|
||||
struct net_bridge_fdb_entry *f;
|
||||
struct net_device *dev = NULL;
|
||||
struct net_bridge *br;
|
||||
@@ -248,8 +249,11 @@ struct net_device *br_fdb_find_port(const struct net_device *br_dev,
|
||||
br = netdev_priv(br_dev);
|
||||
rcu_read_lock();
|
||||
f = br_fdb_find_rcu(br, addr, vid);
|
||||
if (f && f->dst)
|
||||
dev = f->dst->dev;
|
||||
if (f) {
|
||||
dst = READ_ONCE(f->dst);
|
||||
if (dst)
|
||||
dev = dst->dev;
|
||||
}
|
||||
rcu_read_unlock();
|
||||
|
||||
return dev;
|
||||
@@ -346,7 +350,7 @@ static void fdb_delete_local(struct net_bridge *br,
|
||||
vg = nbp_vlan_group(op);
|
||||
if (op != p && ether_addr_equal(op->dev->dev_addr, addr) &&
|
||||
(!vid || br_vlan_find(vg, vid))) {
|
||||
f->dst = op;
|
||||
WRITE_ONCE(f->dst, op);
|
||||
clear_bit(BR_FDB_ADDED_BY_USER, &f->flags);
|
||||
return;
|
||||
}
|
||||
@@ -357,7 +361,7 @@ static void fdb_delete_local(struct net_bridge *br,
|
||||
/* Maybe bridge device has same hw addr? */
|
||||
if (p && ether_addr_equal(br->dev->dev_addr, addr) &&
|
||||
(!vid || (v && br_vlan_should_use(v)))) {
|
||||
f->dst = NULL;
|
||||
WRITE_ONCE(f->dst, NULL);
|
||||
clear_bit(BR_FDB_ADDED_BY_USER, &f->flags);
|
||||
return;
|
||||
}
|
||||
@@ -928,6 +932,7 @@ int br_fdb_test_addr(struct net_device *dev, unsigned char *addr)
|
||||
int br_fdb_fillbuf(struct net_bridge *br, void *buf,
|
||||
unsigned long maxnum, unsigned long skip)
|
||||
{
|
||||
const struct net_bridge_port *dst;
|
||||
struct net_bridge_fdb_entry *f;
|
||||
struct __fdb_entry *fe = buf;
|
||||
unsigned long delta;
|
||||
@@ -944,7 +949,8 @@ int br_fdb_fillbuf(struct net_bridge *br, void *buf,
|
||||
continue;
|
||||
|
||||
/* ignore pseudo entry for local MAC address */
|
||||
if (!f->dst)
|
||||
dst = READ_ONCE(f->dst);
|
||||
if (!dst)
|
||||
continue;
|
||||
|
||||
if (skip) {
|
||||
@@ -956,8 +962,8 @@ int br_fdb_fillbuf(struct net_bridge *br, void *buf,
|
||||
memcpy(fe->mac_addr, f->key.addr.addr, ETH_ALEN);
|
||||
|
||||
/* due to ABI compat need to split into hi/lo */
|
||||
fe->port_no = f->dst->port_no;
|
||||
fe->port_hi = f->dst->port_no >> 8;
|
||||
fe->port_no = dst->port_no;
|
||||
fe->port_hi = dst->port_no >> 8;
|
||||
|
||||
fe->is_local = test_bit(BR_FDB_LOCAL, &f->flags);
|
||||
if (!test_bit(BR_FDB_STATIC, &f->flags)) {
|
||||
@@ -1083,9 +1089,11 @@ int br_fdb_dump(struct sk_buff *skb,
|
||||
|
||||
rcu_read_lock();
|
||||
hlist_for_each_entry_rcu(f, &br->fdb_list, fdb_node) {
|
||||
const struct net_bridge_port *dst = READ_ONCE(f->dst);
|
||||
|
||||
if (*idx < ctx->fdb_idx)
|
||||
goto skip;
|
||||
if (filter_dev && (!f->dst || f->dst->dev != filter_dev)) {
|
||||
if (filter_dev && (!dst || dst->dev != filter_dev)) {
|
||||
if (filter_dev != dev)
|
||||
goto skip;
|
||||
/* !f->dst is a special case for bridge
|
||||
@@ -1093,10 +1101,10 @@ int br_fdb_dump(struct sk_buff *skb,
|
||||
* Therefore need a little more filtering
|
||||
* we only want to dump the !f->dst case
|
||||
*/
|
||||
if (f->dst)
|
||||
if (dst)
|
||||
goto skip;
|
||||
}
|
||||
if (!filter_dev && f->dst)
|
||||
if (!filter_dev && dst)
|
||||
goto skip;
|
||||
|
||||
err = fdb_fill_info(skb, br, f,
|
||||
|
||||
Reference in New Issue
Block a user