* [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-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: 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-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