Sashiko reported a bug [1] that br_multicast_del_port_group unlists the port group not using proper rcu helper that preserves the next pointer and after that immediately frees the port group without waiting for rcu grace period. The only rcu walker of mglist is br_multicast_list_adjacent() and it turns out that function has always been buggy because mglist was never converted to RCU. Fix it by acquiring the bridge's multicast lock for the mglist walk. Return -ENOMEM on allocation error.
[1] https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260826014200.362304-1-littleddfu%40gmail.com Reviewed-by: Ido Schimmel <[email protected]> Fixes: 07f8ac4a1e26 ("bridge: add export of multicast database adjacent to net_dev") Signed-off-by: Nikolay Aleksandrov <[email protected]> --- v2: while changing this fn, return -ENOMEM on error and document it in the kdoc (sashiko) net/bridge/br_multicast.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c index 3ef5d8bbf552..97686984de6d 100644 --- a/net/bridge/br_multicast.c +++ b/net/bridge/br_multicast.c @@ -4936,13 +4936,15 @@ void br_multicast_set_startup_query_intvl(struct net_bridge_mcast *brmctx, * snooping feature on all bridge ports of dev's bridge device, excluding * the addresses from dev itself. * - * Returns the number of items added to br_ip_list. + * Return: The number of items added to br_ip_list or -ENOMEM on memory + * allocation error * * Notes: * - br_ip_list needs to be initialized by caller * - br_ip_list might contain duplicates in the end * (needs to be taken care of by caller) * - br_ip_list needs to be freed by caller + * - on -ENOMEM the caller must free any allocated entries */ int br_multicast_list_adjacent(struct net_device *dev, struct list_head *br_ip_list) @@ -4967,15 +4969,20 @@ int br_multicast_list_adjacent(struct net_device *dev, if (!port->dev || port->dev == dev) continue; - hlist_for_each_entry_rcu(group, &port->mglist, mglist) { + spin_lock_bh(&br->multicast_lock); + hlist_for_each_entry(group, &port->mglist, mglist) { entry = kmalloc_obj(*entry, GFP_ATOMIC); - if (!entry) + if (!entry) { + spin_unlock_bh(&br->multicast_lock); + count = -ENOMEM; goto unlock; + } entry->addr = group->key.addr; list_add(&entry->list, br_ip_list); count++; } + spin_unlock_bh(&br->multicast_lock); } unlock: -- 2.47.3
