* [PATCH net 1/1] net: bridge: use a stable FDB dst snapshot in RCU readers
[not found] <cover.1776043229.git.zcliangcn@gmail.com>
@ 2026-04-13 9:08 ` Ren Wei
2026-04-14 8:05 ` Ido Schimmel
` (2 more replies)
0 siblings, 3 replies; 5+ messages in thread
From: Ren Wei @ 2026-04-13 9:08 UTC (permalink / raw)
To: bridge, netdev
Cc: razor, idosch, davem, edumazet, kuba, pabeni, horms,
makita.toshiaki, vyasevic, yifanwucs, tomapufckgml, yuantan098,
bird, enjou1224z, zcliangcn, n05ec
From: Zhengchuan Liang <zcliangcn@gmail.com>
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: 960b589f86c7 ("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>
---
net/bridge/br_arp_nd_proxy.c | 8 +++++---
net/bridge/br_fdb.c | 28 ++++++++++++++++++----------
2 files changed, 23 insertions(+), 13 deletions(-)
diff --git a/net/bridge/br_arp_nd_proxy.c b/net/bridge/br_arp_nd_proxy.c
index 6b5595868a39c..7ace0f4941bb6 100644
--- a/net/bridge/br_arp_nd_proxy.c
+++ b/net/bridge/br_arp_nd_proxy.c
@@ -202,11 +202,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);
@@ -470,9 +471,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,
diff --git a/net/bridge/br_fdb.c b/net/bridge/br_fdb.c
index e2c17f620f009..6eb3ab69a5140 100644
--- a/net/bridge/br_fdb.c
+++ b/net/bridge/br_fdb.c
@@ -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,
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH net 1/1] net: bridge: use a stable FDB dst snapshot in RCU readers
2026-04-13 9:08 ` [PATCH net 1/1] net: bridge: use a stable FDB dst snapshot in RCU readers Ren Wei
@ 2026-04-14 8:05 ` Ido Schimmel
2026-04-16 10:41 ` Paolo Abeni
2026-04-14 9:33 ` Nikolay Aleksandrov
2026-04-16 11:00 ` patchwork-bot+netdevbpf
2 siblings, 1 reply; 5+ messages in thread
From: Ido Schimmel @ 2026-04-14 8:05 UTC (permalink / raw)
To: Ren Wei
Cc: bridge, netdev, razor, davem, edumazet, kuba, pabeni, horms,
makita.toshiaki, vyasevic, yifanwucs, tomapufckgml, yuantan098,
bird, enjou1224z, zcliangcn
On Mon, Apr 13, 2026 at 05:08:46PM +0800, Ren Wei wrote:
> From: Zhengchuan Liang <zcliangcn@gmail.com>
>
> 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.
Sashiko is complaining [1] about missing READ_ONCE() annotations in some
places, but I can handle them in net-next in a similar fashion to commit
3e19ae7c6fd6 ("net: bridge: use READ_ONCE() and WRITE_ONCE() compiler
barriers for fdb->dst").
It's also complaining [2] about a not very interesting possible bug in
br_fdb_dump() which is pre-existing.
>
> Fixes: 960b589f86c7 ("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>
[1]
"
Are there other RCU readers that still need this protection?
For instance, in br_dev_xmit(), br_fdb_find_rcu() returns a local FDB entry
which is then passed to br_forward(). If a concurrent fdb_delete_local()
sets the entry's dst to NULL, could this cause a NULL pointer dereference if
br_forward() is inlined and the compiler emits multiple loads?
Similarly, br_handle_frame_finish() appears to perform an unmarked read of
dst->dst, which might race with br_fdb_update().
Also, in br_fdb_delete_by_port(), f->dst is read directly without
READ_ONCE(). While called under br->hash_lock, the br_fdb_update()
fast path updates f->dst locklessly. Could this trigger KCSAN warnings due
to an unmarked data race?
"
[2]
"
Does passing f to fdb_fill_info() allow a concurrent update to change
the destination port after the filtering check?
fdb_fill_info() executes a new READ_ONCE(fdb->dst). If f->dst changes
between the filter_dev check above and the call to fdb_fill_info(), the
dumped entry might claim to be on a device that doesn't match the requested
filter_dev.
Should fdb_fill_info() be updated to accept the dst snapshot instead?
"
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net 1/1] net: bridge: use a stable FDB dst snapshot in RCU readers
2026-04-13 9:08 ` [PATCH net 1/1] net: bridge: use a stable FDB dst snapshot in RCU readers Ren Wei
2026-04-14 8:05 ` Ido Schimmel
@ 2026-04-14 9:33 ` Nikolay Aleksandrov
2026-04-16 11:00 ` patchwork-bot+netdevbpf
2 siblings, 0 replies; 5+ messages in thread
From: Nikolay Aleksandrov @ 2026-04-14 9:33 UTC (permalink / raw)
To: Ren Wei, bridge, netdev
Cc: idosch, davem, edumazet, kuba, pabeni, horms, makita.toshiaki,
vyasevic, yifanwucs, tomapufckgml, yuantan098, bird, enjou1224z,
zcliangcn
On 13/04/2026 12:08, Ren Wei wrote:
> From: Zhengchuan Liang <zcliangcn@gmail.com>
>
> 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: 960b589f86c7 ("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>
> ---
> net/bridge/br_arp_nd_proxy.c | 8 +++++---
> net/bridge/br_fdb.c | 28 ++++++++++++++++++----------
> 2 files changed, 23 insertions(+), 13 deletions(-)
>
Acked-by: Nikolay Aleksandrov <razor@blackwall.org>
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net 1/1] net: bridge: use a stable FDB dst snapshot in RCU readers
2026-04-14 8:05 ` Ido Schimmel
@ 2026-04-16 10:41 ` Paolo Abeni
0 siblings, 0 replies; 5+ messages in thread
From: Paolo Abeni @ 2026-04-16 10:41 UTC (permalink / raw)
To: Ido Schimmel, Ren Wei
Cc: bridge, netdev, razor, davem, edumazet, kuba, horms,
makita.toshiaki, vyasevic, yifanwucs, tomapufckgml, yuantan098,
bird, enjou1224z, zcliangcn
On 4/14/26 10:05 AM, Ido Schimmel wrote:
> On Mon, Apr 13, 2026 at 05:08:46PM +0800, Ren Wei wrote:
>> From: Zhengchuan Liang <zcliangcn@gmail.com>
>>
>> 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.
>
> Sashiko is complaining [1] about missing READ_ONCE() annotations in some
> places, but I can handle them in net-next in a similar fashion to commit
> 3e19ae7c6fd6 ("net: bridge: use READ_ONCE() and WRITE_ONCE() compiler
> barriers for fdb->dst").
I agree they can be handled separately, because they don't look harmful.
I think a 'net' patch could be used for such follow-up (data race)
/P
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net 1/1] net: bridge: use a stable FDB dst snapshot in RCU readers
2026-04-13 9:08 ` [PATCH net 1/1] net: bridge: use a stable FDB dst snapshot in RCU readers Ren Wei
2026-04-14 8:05 ` Ido Schimmel
2026-04-14 9:33 ` Nikolay Aleksandrov
@ 2026-04-16 11:00 ` patchwork-bot+netdevbpf
2 siblings, 0 replies; 5+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-04-16 11:00 UTC (permalink / raw)
To: Ren Wei
Cc: bridge, netdev, razor, idosch, davem, edumazet, kuba, pabeni,
horms, makita.toshiaki, vyasevic, yifanwucs, tomapufckgml,
yuantan098, bird, enjou1224z, zcliangcn
Hello:
This patch was applied to netdev/net.git (main)
by Paolo Abeni <pabeni@redhat.com>:
On Mon, 13 Apr 2026 17:08:46 +0800 you wrote:
> From: Zhengchuan Liang <zcliangcn@gmail.com>
>
> 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.
>
> [...]
Here is the summary with links:
- [net,1/1] net: bridge: use a stable FDB dst snapshot in RCU readers
https://git.kernel.org/netdev/net/c/df4601653201
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-04-16 11:00 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <cover.1776043229.git.zcliangcn@gmail.com>
2026-04-13 9:08 ` [PATCH net 1/1] net: bridge: use a stable FDB dst snapshot in RCU readers Ren Wei
2026-04-14 8:05 ` Ido Schimmel
2026-04-16 10:41 ` Paolo Abeni
2026-04-14 9:33 ` Nikolay Aleksandrov
2026-04-16 11:00 ` patchwork-bot+netdevbpf
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox