]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
ipv6: mcast: Check inet6_dev->dead under idev->mc_lock in __ipv6_dev_mc_inc().
authorKuniyuki Iwashima <kuniyu@google.com>
Wed, 2 Jul 2025 23:01:20 +0000 (16:01 -0700)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 20 Aug 2025 16:30:33 +0000 (18:30 +0200)
[ Upstream commit dbd40f318cf2f59759bd170c401adc20ba360a3e ]

Since commit 63ed8de4be81 ("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>
Signed-off-by: Sasha Levin <sashal@kernel.org>
net/ipv6/addrconf.c
net/ipv6/mcast.c

index be51b8792b96f494a4269cce4dc6d1200ad5652f..49ec223f2eda4918e71a1ac82147c40a486dc199 100644 (file)
@@ -2228,13 +2228,12 @@ errdad:
        in6_ifa_put(ifp);
 }
 
-/* Join to solicited addr multicast group.
- * caller must hold RTNL */
+/* Join to solicited addr multicast group. */
 void addrconf_join_solict(struct net_device *dev, const struct in6_addr *addr)
 {
        struct in6_addr maddr;
 
-       if (dev->flags&(IFF_LOOPBACK|IFF_NOARP))
+       if (READ_ONCE(dev->flags) & (IFF_LOOPBACK | IFF_NOARP))
                return;
 
        addrconf_addr_solict_mult(addr, &maddr);
@@ -3883,7 +3882,7 @@ static int addrconf_ifdown(struct net_device *dev, bool unregister)
         *         Do not dev_put!
         */
        if (unregister) {
-               idev->dead = 1;
+               WRITE_ONCE(idev->dead, 1);
 
                /* protected by rtnl_lock */
                RCU_INIT_POINTER(dev->ip6_ptr, NULL);
index 9949554e3211b557c391e750f7f92f6aa5b94082..e2a11a2f3b255d6aa5f8d4b5e8f430918b1c9c06 100644 (file)
@@ -907,23 +907,22 @@ static struct ifmcaddr6 *mca_alloc(struct inet6_dev *idev,
 static int __ipv6_dev_mc_inc(struct net_device *dev,
                             const struct in6_addr *addr, unsigned int mode)
 {
-       struct ifmcaddr6 *mc;
        struct inet6_dev *idev;
-
-       ASSERT_RTNL();
+       struct ifmcaddr6 *mc;
 
        /* we need to take a reference on idev */
        idev = in6_dev_get(dev);
-
        if (!idev)
                return -EINVAL;
 
-       if (idev->dead) {
+       mutex_lock(&idev->mc_lock);
+
+       if (READ_ONCE(idev->dead)) {
+               mutex_unlock(&idev->mc_lock);
                in6_dev_put(idev);
                return -ENODEV;
        }
 
-       mutex_lock(&idev->mc_lock);
        for_each_mc_mclock(idev, mc) {
                if (ipv6_addr_equal(&mc->mca_addr, addr)) {
                        mc->mca_users++;