* [PATCH] net: bridge: mcast: don't truncate the port group walk on teardown
@ 2026-08-12 11:34 Jun Yang
2026-08-12 12:17 ` Nikolay Aleksandrov
0 siblings, 1 reply; 3+ messages in thread
From: Jun Yang @ 2026-08-12 11:34 UTC (permalink / raw)
To: Nikolay Aleksandrov, Ido Schimmel
Cc: stable, Jun Yang, TencentOS Corvus AI, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, bridge,
netdev, linux-kernel
__br_multicast_disable_port_ctx() and br_multicast_del_port() walk
port->mglist with hlist_for_each_entry_safe(), which only guarantees that
the *current* node may be removed by the loop body.
The body is br_multicast_find_del_pg() -> br_multicast_del_pg(), and that
deletes further port groups of the very same port. br_multicast_del_pg()
drops the group's sources, and br_multicast_fwd_src_remove()
(net/bridge/br_multicast.c:583) deletes the (S,G) port group installed on
that same port; br_multicast_star_g_handle_mode() -> __fwd_del_star_excl()
(net/bridge/br_multicast.c:330) deletes the automatically installed
MDB_PG_FLAGS_STAR_EXCL entries, again on that same port. All of those sit
on the same port->mglist.
When one of them happens to be the node the iterator already latched as
"next", hlist_del_init() clears its ->next, the walk sees NULL and stops.
Every port group after it is silently left on the port. port->mglist is
head-inserted, so this needs the cascade victim to be older than the (*,G)
entry owning the source - a user-added, non-permanent (S,G) MDB entry added
before the (*,G) join produces exactly that ordering.
Hitting it once truncates the disable walk in
__br_multicast_disable_port_ctx() and once more truncates the flush in
br_multicast_del_port(), so del_nbp() goes on to free the port with port
groups still on port->mglist - and still linked in the bridge's mdb, with a
dangling ->key.port. Any subsequent mdb dump reads the freed port:
BUG: KASAN: slab-use-after-free in __mdb_fill_info+0x1191/0x1320
Read of size 8 at addr ffff88803065d008 by task bridge/9527
__mdb_fill_info+0x1191/0x1320
br_mdb_dump+0x594/0xe40
rtnl_mdb_dump+0x1cf/0x5d0
Freed by task 0:
kfree+0x265/0x740
kobject_put+0x212/0x6a0
rcu_core+0x5c6/0x1140
Last potentially related work creation:
__call_rcu_common.constprop.0+0xb7/0x9e0
br_del_if+0xdd/0x260
Don't rely on the pre-latched next pointer. br_multicast_del_port() deletes
everything, so just take the current list head each round. The filtered
walk in __br_multicast_disable_port_ctx() keeps its iterator but restarts
whenever the latched node has left the list; port groups are only freed by
the multicast GC work, which takes br->multicast_lock, so the node is still
valid memory for that check.
Fixes: b08123684bd5 ("net: bridge: mcast: install S,G entries automatically based on reports")
Cc: stable@vger.kernel.org
Reported-by: TencentOS Corvus AI <corvus@tencent.com>
Assisted-by: tencentos-corvus-ai:kimi-k3
Signed-off-by: Jun Yang <junvyyang@tencent.com>
---
A KASAN reproducer for this issue is available if requested.
net/bridge/br_multicast.c | 33 +++++++++++++++++++++++++--------
1 file changed, 25 insertions(+), 8 deletions(-)
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c
index 00aa9b2879d6..624dfca4066b 100644
--- a/net/bridge/br_multicast.c
+++ b/net/bridge/br_multicast.c
@@ -2065,12 +2065,17 @@ void br_multicast_del_port(struct net_bridge_port *port)
{
struct net_bridge *br = port->br;
struct net_bridge_port_group *pg;
- struct hlist_node *n;
- /* Take care of the remaining groups, only perm ones should be left */
+ /* Take care of the remaining groups, only perm ones should be left.
+ * Deleting one can delete others on this same port->mglist, so
+ * always restart from the head.
+ */
spin_lock_bh(&br->multicast_lock);
- hlist_for_each_entry_safe(pg, n, &port->mglist, mglist)
+ while (!hlist_empty(&port->mglist)) {
+ pg = hlist_entry(port->mglist.first,
+ struct net_bridge_port_group, mglist);
br_multicast_find_del_pg(br, pg);
+ }
spin_unlock_bh(&br->multicast_lock);
flush_work(&br->mcast_gc_work);
br_multicast_port_ctx_deinit(&port->multicast_ctx);
@@ -2126,11 +2132,23 @@ static void __br_multicast_disable_port_ctx(struct net_bridge_mcast_port *pmctx)
struct hlist_node *n;
bool del = false;
- hlist_for_each_entry_safe(pg, n, &pmctx->port->mglist, mglist)
- if (!(pg->flags & MDB_PG_FLAGS_PERMANENT) &&
- (!br_multicast_port_ctx_is_vlan(pmctx) ||
- pg->key.addr.vid == pmctx->vlan->vid))
- br_multicast_find_del_pg(pmctx->port->br, pg);
+ /* br_multicast_find_del_pg() can delete further entries of this same
+ * port->mglist, so the node latched in @n may be unlinked by the loop
+ * body. Port groups are only freed by the GC work under multicast_lock,
+ * so @n is still valid here; if it left the list, restart.
+ */
+restart:
+ hlist_for_each_entry_safe(pg, n, &pmctx->port->mglist, mglist) {
+ if ((pg->flags & MDB_PG_FLAGS_PERMANENT) ||
+ (br_multicast_port_ctx_is_vlan(pmctx) &&
+ pg->key.addr.vid != pmctx->vlan->vid))
+ continue;
+
+ br_multicast_find_del_pg(pmctx->port->br, pg);
+
+ if (n && hlist_unhashed(n))
+ goto restart;
+ }
del |= br_ip4_multicast_rport_del(pmctx);
timer_delete(&pmctx->ip4_mc_router_timer);
--
2.55.0
^ permalink raw reply related [flat|nested] 3+ messages in thread* Re: [PATCH] net: bridge: mcast: don't truncate the port group walk on teardown 2026-08-12 11:34 [PATCH] net: bridge: mcast: don't truncate the port group walk on teardown Jun Yang @ 2026-08-12 12:17 ` Nikolay Aleksandrov 2026-08-12 12:21 ` Nikolay Aleksandrov 0 siblings, 1 reply; 3+ messages in thread From: Nikolay Aleksandrov @ 2026-08-12 12:17 UTC (permalink / raw) To: Jun Yang, Ido Schimmel Cc: stable, Jun Yang, TencentOS Corvus AI, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, bridge, netdev, linux-kernel On 12/08/2026 14:34, Jun Yang wrote: > __br_multicast_disable_port_ctx() and br_multicast_del_port() walk > port->mglist with hlist_for_each_entry_safe(), which only guarantees that > the *current* node may be removed by the loop body. > > The body is br_multicast_find_del_pg() -> br_multicast_del_pg(), and that > deletes further port groups of the very same port. br_multicast_del_pg() > drops the group's sources, and br_multicast_fwd_src_remove() > (net/bridge/br_multicast.c:583) deletes the (S,G) port group installed on > that same port; br_multicast_star_g_handle_mode() -> __fwd_del_star_excl() > (net/bridge/br_multicast.c:330) deletes the automatically installed > MDB_PG_FLAGS_STAR_EXCL entries, again on that same port. All of those sit > on the same port->mglist. > > When one of them happens to be the node the iterator already latched as > "next", hlist_del_init() clears its ->next, the walk sees NULL and stops. > Every port group after it is silently left on the port. port->mglist is > head-inserted, so this needs the cascade victim to be older than the (*,G) > entry owning the source - a user-added, non-permanent (S,G) MDB entry added > before the (*,G) join produces exactly that ordering. > > Hitting it once truncates the disable walk in > __br_multicast_disable_port_ctx() and once more truncates the flush in > br_multicast_del_port(), so del_nbp() goes on to free the port with port > groups still on port->mglist - and still linked in the bridge's mdb, with a > dangling ->key.port. Any subsequent mdb dump reads the freed port: > > BUG: KASAN: slab-use-after-free in __mdb_fill_info+0x1191/0x1320 > Read of size 8 at addr ffff88803065d008 by task bridge/9527 > __mdb_fill_info+0x1191/0x1320 > br_mdb_dump+0x594/0xe40 > rtnl_mdb_dump+0x1cf/0x5d0 > Freed by task 0: > kfree+0x265/0x740 > kobject_put+0x212/0x6a0 > rcu_core+0x5c6/0x1140 > Last potentially related work creation: > __call_rcu_common.constprop.0+0xb7/0x9e0 > br_del_if+0xdd/0x260 > > Don't rely on the pre-latched next pointer. br_multicast_del_port() deletes > everything, so just take the current list head each round. The filtered > walk in __br_multicast_disable_port_ctx() keeps its iterator but restarts > whenever the latched node has left the list; port groups are only freed by > the multicast GC work, which takes br->multicast_lock, so the node is still > valid memory for that check. > > Fixes: b08123684bd5 ("net: bridge: mcast: install S,G entries automatically based on reports") > Cc: stable@vger.kernel.org > Reported-by: TencentOS Corvus AI <corvus@tencent.com> > Assisted-by: tencentos-corvus-ai:kimi-k3 > Signed-off-by: Jun Yang <junvyyang@tencent.com> > --- > A KASAN reproducer for this issue is available if requested. > > net/bridge/br_multicast.c | 33 +++++++++++++++++++++++++-------- > 1 file changed, 25 insertions(+), 8 deletions(-) > > diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c > index 00aa9b2879d6..624dfca4066b 100644 > --- a/net/bridge/br_multicast.c > +++ b/net/bridge/br_multicast.c > @@ -2065,12 +2065,17 @@ void br_multicast_del_port(struct net_bridge_port *port) > { > struct net_bridge *br = port->br; > struct net_bridge_port_group *pg; > - struct hlist_node *n; > > - /* Take care of the remaining groups, only perm ones should be left */ > + /* Take care of the remaining groups, only perm ones should be left. > + * Deleting one can delete others on this same port->mglist, so > + * always restart from the head. > + */ > spin_lock_bh(&br->multicast_lock); > - hlist_for_each_entry_safe(pg, n, &port->mglist, mglist) > + while (!hlist_empty(&port->mglist)) { > + pg = hlist_entry(port->mglist.first, > + struct net_bridge_port_group, mglist); > br_multicast_find_del_pg(br, pg); > + } > spin_unlock_bh(&br->multicast_lock); > flush_work(&br->mcast_gc_work); > br_multicast_port_ctx_deinit(&port->multicast_ctx); > @@ -2126,11 +2132,23 @@ static void __br_multicast_disable_port_ctx(struct net_bridge_mcast_port *pmctx) > struct hlist_node *n; > bool del = false; > > - hlist_for_each_entry_safe(pg, n, &pmctx->port->mglist, mglist) > - if (!(pg->flags & MDB_PG_FLAGS_PERMANENT) && > - (!br_multicast_port_ctx_is_vlan(pmctx) || > - pg->key.addr.vid == pmctx->vlan->vid)) > - br_multicast_find_del_pg(pmctx->port->br, pg); > + /* br_multicast_find_del_pg() can delete further entries of this same > + * port->mglist, so the node latched in @n may be unlinked by the loop > + * body. Port groups are only freed by the GC work under multicast_lock, > + * so @n is still valid here; if it left the list, restart. > + */ > +restart: > + hlist_for_each_entry_safe(pg, n, &pmctx->port->mglist, mglist) { > + if ((pg->flags & MDB_PG_FLAGS_PERMANENT) || > + (br_multicast_port_ctx_is_vlan(pmctx) && > + pg->key.addr.vid != pmctx->vlan->vid)) > + continue; > + > + br_multicast_find_del_pg(pmctx->port->br, pg); > + > + if (n && hlist_unhashed(n)) > + goto restart; > + } > > del |= br_ip4_multicast_rport_del(pmctx); > timer_delete(&pmctx->ip4_mc_router_timer); Thanks for the report, but instead of all these restarts and checks, can't we just do: diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c index 75e1e2a8fc83..62c4008c5bb8 100644 --- a/net/bridge/br_multicast.c +++ b/net/bridge/br_multicast.c @@ -808,7 +808,8 @@ void br_multicast_del_pg(struct net_bridge_mdb_entry *mp, struct hlist_node *tmp; rcu_assign_pointer(*pp, pg->next); - hlist_del_init(&pg->mglist); + /* use _rcu to preserve the next pointer because it might be in use */ + hlist_del_init_rcu(&pg->mglist); br_multicast_eht_clean_sets(pg); hlist_for_each_entry_safe(ent, tmp, &pg->src_list, node) br_multicast_del_group_src(ent, false); I have old patches that remove the mcast open-coded list implementations, I must revive them and clean all of this up finally. :) Cheers, Nik ^ permalink raw reply related [flat|nested] 3+ messages in thread
* Re: [PATCH] net: bridge: mcast: don't truncate the port group walk on teardown 2026-08-12 12:17 ` Nikolay Aleksandrov @ 2026-08-12 12:21 ` Nikolay Aleksandrov 0 siblings, 0 replies; 3+ messages in thread From: Nikolay Aleksandrov @ 2026-08-12 12:21 UTC (permalink / raw) To: Jun Yang, Ido Schimmel Cc: stable, Jun Yang, TencentOS Corvus AI, David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman, bridge, netdev, linux-kernel On 12/08/2026 15:17, Nikolay Aleksandrov wrote: > On 12/08/2026 14:34, Jun Yang wrote: >> __br_multicast_disable_port_ctx() and br_multicast_del_port() walk >> port->mglist with hlist_for_each_entry_safe(), which only guarantees that >> the *current* node may be removed by the loop body. >> >> The body is br_multicast_find_del_pg() -> br_multicast_del_pg(), and that >> deletes further port groups of the very same port. br_multicast_del_pg() >> drops the group's sources, and br_multicast_fwd_src_remove() >> (net/bridge/br_multicast.c:583) deletes the (S,G) port group installed on >> that same port; br_multicast_star_g_handle_mode() -> __fwd_del_star_excl() >> (net/bridge/br_multicast.c:330) deletes the automatically installed >> MDB_PG_FLAGS_STAR_EXCL entries, again on that same port. All of those sit >> on the same port->mglist. >> >> When one of them happens to be the node the iterator already latched as >> "next", hlist_del_init() clears its ->next, the walk sees NULL and stops. >> Every port group after it is silently left on the port. port->mglist is >> head-inserted, so this needs the cascade victim to be older than the (*,G) >> entry owning the source - a user-added, non-permanent (S,G) MDB entry added >> before the (*,G) join produces exactly that ordering. >> >> Hitting it once truncates the disable walk in >> __br_multicast_disable_port_ctx() and once more truncates the flush in >> br_multicast_del_port(), so del_nbp() goes on to free the port with port >> groups still on port->mglist - and still linked in the bridge's mdb, with a >> dangling ->key.port. Any subsequent mdb dump reads the freed port: >> >> BUG: KASAN: slab-use-after-free in __mdb_fill_info+0x1191/0x1320 >> Read of size 8 at addr ffff88803065d008 by task bridge/9527 >> __mdb_fill_info+0x1191/0x1320 >> br_mdb_dump+0x594/0xe40 >> rtnl_mdb_dump+0x1cf/0x5d0 >> Freed by task 0: >> kfree+0x265/0x740 >> kobject_put+0x212/0x6a0 >> rcu_core+0x5c6/0x1140 >> Last potentially related work creation: >> __call_rcu_common.constprop.0+0xb7/0x9e0 >> br_del_if+0xdd/0x260 >> >> Don't rely on the pre-latched next pointer. br_multicast_del_port() deletes >> everything, so just take the current list head each round. The filtered >> walk in __br_multicast_disable_port_ctx() keeps its iterator but restarts >> whenever the latched node has left the list; port groups are only freed by >> the multicast GC work, which takes br->multicast_lock, so the node is still >> valid memory for that check. >> >> Fixes: b08123684bd5 ("net: bridge: mcast: install S,G entries automatically based on reports") >> Cc: stable@vger.kernel.org >> Reported-by: TencentOS Corvus AI <corvus@tencent.com> >> Assisted-by: tencentos-corvus-ai:kimi-k3 >> Signed-off-by: Jun Yang <junvyyang@tencent.com> >> --- >> A KASAN reproducer for this issue is available if requested. >> >> net/bridge/br_multicast.c | 33 +++++++++++++++++++++++++-------- >> 1 file changed, 25 insertions(+), 8 deletions(-) >> >> diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c >> index 00aa9b2879d6..624dfca4066b 100644 >> --- a/net/bridge/br_multicast.c >> +++ b/net/bridge/br_multicast.c >> @@ -2065,12 +2065,17 @@ void br_multicast_del_port(struct net_bridge_port *port) >> { >> struct net_bridge *br = port->br; >> struct net_bridge_port_group *pg; >> - struct hlist_node *n; >> - /* Take care of the remaining groups, only perm ones should be left */ >> + /* Take care of the remaining groups, only perm ones should be left. >> + * Deleting one can delete others on this same port->mglist, so >> + * always restart from the head. >> + */ >> spin_lock_bh(&br->multicast_lock); >> - hlist_for_each_entry_safe(pg, n, &port->mglist, mglist) >> + while (!hlist_empty(&port->mglist)) { >> + pg = hlist_entry(port->mglist.first, >> + struct net_bridge_port_group, mglist); >> br_multicast_find_del_pg(br, pg); >> + } >> spin_unlock_bh(&br->multicast_lock); >> flush_work(&br->mcast_gc_work); >> br_multicast_port_ctx_deinit(&port->multicast_ctx); >> @@ -2126,11 +2132,23 @@ static void __br_multicast_disable_port_ctx(struct net_bridge_mcast_port *pmctx) >> struct hlist_node *n; >> bool del = false; >> - hlist_for_each_entry_safe(pg, n, &pmctx->port->mglist, mglist) >> - if (!(pg->flags & MDB_PG_FLAGS_PERMANENT) && >> - (!br_multicast_port_ctx_is_vlan(pmctx) || >> - pg->key.addr.vid == pmctx->vlan->vid)) >> - br_multicast_find_del_pg(pmctx->port->br, pg); >> + /* br_multicast_find_del_pg() can delete further entries of this same >> + * port->mglist, so the node latched in @n may be unlinked by the loop >> + * body. Port groups are only freed by the GC work under multicast_lock, >> + * so @n is still valid here; if it left the list, restart. >> + */ >> +restart: >> + hlist_for_each_entry_safe(pg, n, &pmctx->port->mglist, mglist) { >> + if ((pg->flags & MDB_PG_FLAGS_PERMANENT) || >> + (br_multicast_port_ctx_is_vlan(pmctx) && >> + pg->key.addr.vid != pmctx->vlan->vid)) >> + continue; >> + >> + br_multicast_find_del_pg(pmctx->port->br, pg); >> + >> + if (n && hlist_unhashed(n)) >> + goto restart; >> + } >> del |= br_ip4_multicast_rport_del(pmctx); >> timer_delete(&pmctx->ip4_mc_router_timer); > > Thanks for the report, but instead of all these restarts and checks, > can't we just do: > diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c > index 75e1e2a8fc83..62c4008c5bb8 100644 > --- a/net/bridge/br_multicast.c > +++ b/net/bridge/br_multicast.c > @@ -808,7 +808,8 @@ void br_multicast_del_pg(struct net_bridge_mdb_entry *mp, > struct hlist_node *tmp; > > rcu_assign_pointer(*pp, pg->next); > - hlist_del_init(&pg->mglist); > + /* use _rcu to preserve the next pointer because it might be in use */ Just to be clear - I'd expand the comment to include why it is safe to do so and under what conditions (multicast_lock held) > + hlist_del_init_rcu(&pg->mglist); > br_multicast_eht_clean_sets(pg); > hlist_for_each_entry_safe(ent, tmp, &pg->src_list, node) > br_multicast_del_group_src(ent, false); > > > I have old patches that remove the mcast open-coded list implementations, I must > revive them and clean all of this up finally. :) > > Cheers, > Nik > ^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-08-12 12:21 UTC | newest] Thread overview: 3+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-08-12 11:34 [PATCH] net: bridge: mcast: don't truncate the port group walk on teardown Jun Yang 2026-08-12 12:17 ` Nikolay Aleksandrov 2026-08-12 12:21 ` Nikolay Aleksandrov
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox