Re: [net,v1] net/ipv6: mcast: fix circular locking dependency in __ipv6_dev_mc_inc()
From: Paolo Abeni
Date: Thu Mar 19 2026 - 08:36:32 EST
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
---
net/ipv6: mcast: fix circular locking dependency in __ipv6_dev_mc_inc()
This commit fixes a circular locking dependency deadlock by moving the
GFP_KERNEL allocation outside the mc_lock critical section. The fix
splits mca_alloc() into allocation and initialization phases and also
moves inet6_ifmcaddr_notify() outside the lock.
Link: https://lore.kernel.org/all/69b7dc76.050a0220.248e02.0113.GAE@xxxxxxxxxx/T/
> diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c
> --- a/net/ipv6/mcast.c
> +++ b/net/ipv6/mcast.c
[ ... ]
> @@ -953,25 +957,23 @@ static int __ipv6_dev_mc_inc(struct net_device *dev,
> mc->mca_users++;
> ip6_mc_add_src(idev, &mc->mca_addr, mode, 0, NULL, 0);
> mutex_unlock(&idev->mc_lock);
> + kfree(mc_alloced);
> in6_dev_put(idev);
> return 0;
> }
> }
>
> - mc = mca_alloc(idev, addr, mode);
> - if (!mc) {
> - mutex_unlock(&idev->mc_lock);
> - in6_dev_put(idev);
> - return -ENOMEM;
> - }
> + mca_init(idev, addr, mode, mc_alloced);
> + mc = mc_alloced;
>
> rcu_assign_pointer(mc->next, idev->mc_list);
> rcu_assign_pointer(idev->mc_list, mc);
>
> mld_del_delrec(idev, mc);
> igmp6_group_added(mc);
> + mutex_unlock(&idev->mc_lock);
> +
> inet6_ifmcaddr_notify(dev, mc, RTM_NEWMULTICAST);
> - mutex_unlock(&idev->mc_lock);
^^^^
Can this lead to a use-after-free on mc?
After mutex_unlock(&idev->mc_lock), the mc object has been published to
idev->mc_list with mca_refcnt=1. At this point, a concurrent
ipv6_mc_destroy_dev() could acquire mc_lock, iterate mc_list, and call
ma_put() on mc:
net/ipv6/mcast.c:ipv6_mc_destroy_dev() {
mutex_lock(&idev->mc_lock);
while ((mc = rcu_dereference_protected(idev->mc_list, ...)) != NULL) {
...
ma_put(mc);
}
mutex_unlock(&idev->mc_lock);
}
Since mca_refcnt starts at 1 (set by mca_init), ma_put() would decrement
it to 0 and call kfree_rcu(mc, rcu):
net/ipv6/mcast.c:ma_put() {
if (refcount_dec_and_test(&mc->mca_refcnt)) {
in6_dev_put(mc->idev);
kfree_rcu(mc, rcu);
}
}
Then inet6_ifmcaddr_notify() calls nlmsg_new(..., GFP_KERNEL) which can
sleep and create an RCU quiescent state, allowing the kfree_rcu callback
to execute and free mc before inet6_fill_ifmcaddr() accesses mc->idev,
mc->idev->dev->ifindex, and mc->mca_addr.
Should the fix take an additional refcount on mc (refcount_inc(&mc->mca_refcnt))
before releasing mc_lock, then drop it with ma_put(mc) after
inet6_ifmcaddr_notify() returns?
>
> return 0;
> }