2
0
mirror of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git synced 2025-09-04 20:19:47 +08:00

ipv6: mcast: Check inet6_dev->dead under idev->mc_lock in __ipv6_dev_mc_inc().

Since commit 63ed8de4be ("mld: add mc_lock for protecting
per-interface mld data"), every multicast resource is protected
by inet6_dev->mc_lock.

RTNL is unnecessary in terms of protection but still needed for
synchronisation between addrconf_ifdown() and __ipv6_dev_mc_inc().

Once we removed RTNL, there would be a race below, where we could
add a multicast address to a dead inet6_dev.

  CPU1                            CPU2
  ====                            ====
  addrconf_ifdown()               __ipv6_dev_mc_inc()
                                    if (idev->dead) <-- false
    dead = true                       return -ENODEV;
    ipv6_mc_destroy_dev() / ipv6_mc_down()
      mutex_lock(&idev->mc_lock)
      ...
      mutex_unlock(&idev->mc_lock)
                                    mutex_lock(&idev->mc_lock)
                                    ...
                                    mutex_unlock(&idev->mc_lock)

The race window can be easily closed by checking inet6_dev->dead
under inet6_dev->mc_lock in __ipv6_dev_mc_inc() as addrconf_ifdown()
will acquire it after marking inet6_dev dead.

Let's check inet6_dev->dead under mc_lock in __ipv6_dev_mc_inc().

Note that now __ipv6_dev_mc_inc() no longer depends on RTNL and
we can remove ASSERT_RTNL() there and the RTNL comment above
addrconf_join_solict().

Signed-off-by: Kuniyuki Iwashima <kuniyu@google.com>
Reviewed-by: Eric Dumazet <edumazet@google.com>
Link: https://patch.msgid.link/20250702230210.3115355-4-kuni1840@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Kuniyuki Iwashima 2025-07-02 16:01:20 -07:00 committed by Jakub Kicinski
parent 818ae1a5ec
commit dbd40f318c
2 changed files with 8 additions and 10 deletions

View File

@ -2229,13 +2229,12 @@ errdad:
in6_ifa_put(ifp); in6_ifa_put(ifp);
} }
/* Join to solicited addr multicast group. /* Join to solicited addr multicast group. */
* caller must hold RTNL */
void addrconf_join_solict(struct net_device *dev, const struct in6_addr *addr) void addrconf_join_solict(struct net_device *dev, const struct in6_addr *addr)
{ {
struct in6_addr maddr; struct in6_addr maddr;
if (dev->flags&(IFF_LOOPBACK|IFF_NOARP)) if (READ_ONCE(dev->flags) & (IFF_LOOPBACK | IFF_NOARP))
return; return;
addrconf_addr_solict_mult(addr, &maddr); addrconf_addr_solict_mult(addr, &maddr);
@ -3865,7 +3864,7 @@ static int addrconf_ifdown(struct net_device *dev, bool unregister)
* Do not dev_put! * Do not dev_put!
*/ */
if (unregister) { if (unregister) {
idev->dead = 1; WRITE_ONCE(idev->dead, 1);
/* protected by rtnl_lock */ /* protected by rtnl_lock */
RCU_INIT_POINTER(dev->ip6_ptr, NULL); RCU_INIT_POINTER(dev->ip6_ptr, NULL);

View File

@ -952,23 +952,22 @@ error:
static int __ipv6_dev_mc_inc(struct net_device *dev, static int __ipv6_dev_mc_inc(struct net_device *dev,
const struct in6_addr *addr, unsigned int mode) const struct in6_addr *addr, unsigned int mode)
{ {
struct ifmcaddr6 *mc;
struct inet6_dev *idev; struct inet6_dev *idev;
struct ifmcaddr6 *mc;
ASSERT_RTNL();
/* we need to take a reference on idev */ /* we need to take a reference on idev */
idev = in6_dev_get(dev); idev = in6_dev_get(dev);
if (!idev) if (!idev)
return -EINVAL; return -EINVAL;
if (idev->dead) { mutex_lock(&idev->mc_lock);
if (READ_ONCE(idev->dead)) {
mutex_unlock(&idev->mc_lock);
in6_dev_put(idev); in6_dev_put(idev);
return -ENODEV; return -ENODEV;
} }
mutex_lock(&idev->mc_lock);
for_each_mc_mclock(idev, mc) { for_each_mc_mclock(idev, mc) {
if (ipv6_addr_equal(&mc->mca_addr, addr)) { if (ipv6_addr_equal(&mc->mca_addr, addr)) {
mc->mca_users++; mc->mca_users++;