Thread (3 messages) 3 messages, 3 authors, 11d ago

Re: [PATCH net] ipv6: serialize address publication with addrconf_ifdown

flat view

From: Ido Schimmel <idosch@nvidia.com>
Date: 2026-10-04 13:51:30
Also in: lkml
Subsystem: networking [general], networking [ipv4/ipv6], the rest · Maintainers: "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, David Ahern, Ido Schimmel, Linus Torvalds

On Thu, Oct 01, 2026 at 02:21:50PM +0900, Daehyeon Ko wrote:
ipv6_add_addr() inserts an inet6_ifaddr into the per-net hash before
linking it into idev->addr_list. addrconf_ifdown() clears the hash first
and snapshots the device list later. A nonblocking add can therefore
enter between the two teardown observations.

When a non-loopback device's MTU falls below IPV6_MIN_MTU, teardown
marks and detaches the idev. A late add can then link the ifaddr into the
dead idev. On v7.2, a deterministic interleaving left the object
hash-visible with idev->dead=1. ipv6_get_ifaddr() returned it, and later
device deletion waited indefinitely with usage count 3. During network
namespace exit that wait can stall the single-thread netns cleanup
workqueue.

MTU changes require CAP_NET_ADMIN in the affected network namespace.
Where unprivileged user namespaces are permitted, a local user can
obtain that capability in a new user and network namespace.

Publish the hash and device-list memberships while holding
addrconf_hash_lock followed by idev->lock. Recheck idev->dead and
disable_ipv6 before either publication. If teardown wins, the add fails
before publishing the object or taking a list reference.
Adding to the per-idev list under the per-netns hash lock looks weird.
Can't we instead do the following?

1. Make sure that the dead indication is written under the idev lock.

2. Extend the critical section of the idev lock so that it also covers
the addition to the per-netns hash table and only if the device is not
dead.

Something like [1] (untested).

Also, note that this doesn't solve the problem of addrconf_ifdown() and
ipv6_add_addr() interleaving when the former doesn't mark the device as
dead (e.g., upon NETDEV_DOWN).

ipv6_add_addr() can add an address to the hash table after
addrconf_ifdown() cleared the hash table, but before it cleared the
per-idev list. It will trigger the WARN_ON() in
inet6_ifa_finish_destroy():

WARN_ON(!hlist_unhashed(&ifp->addr_lst));

Can be fixed in a second patch [2] (untested) in the series.
The same forced interleaving now returns -ENODEV and device deletion
completes without a KASAN or LOCKDEP diagnostic. A real Router
Advertisement separately reached ipv6_add_addr() with can_block=false;
bounded natural stress did not reproduce the full race. A reproducer is
"bounded natural stress did not reproduce the full race" is LLM speak
for "mdelay()s were placed in ipv6_add_addr()"?
available on request.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Blaming 8814c4b53381 ("[IPV6] ADDRCONF: Convert addrconf_lock to RCU.") seems
more appropriate.
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Daehyeon Ko <redacted>
[1]
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index 3dba94bd2ba0..51add8f91139 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -1171,14 +1171,18 @@ ipv6_add_addr(struct inet6_dev *idev, struct ifa6_config *cfg,
 
 	rcu_read_lock();
 
-	err = ipv6_add_addr_hash(idev->dev, ifa);
+	write_lock_bh(&idev->lock);
+
+	if (idev->dead)
+		err = -ENODEV;
+	else
+		err = ipv6_add_addr_hash(idev->dev, ifa);
 	if (err < 0) {
+		write_unlock_bh(&idev->lock);
 		rcu_read_unlock();
 		goto out;
 	}
 
-	write_lock_bh(&idev->lock);
-
 	/* Add to inet6_dev unicast addr list. */
 	ipv6_link_dev_addr(idev, ifa);
 
@@ -3900,7 +3904,9 @@ static int addrconf_ifdown(struct net_device *dev, bool unregister)
 	 *	   Do not dev_put!
 	 */
 	if (unregister) {
+		write_lock_bh(&idev->lock);
 		WRITE_ONCE(idev->dead, 1);
+		write_unlock_bh(&idev->lock);
 
 		/* protected by rtnl_lock */
 		RCU_INIT_POINTER(dev->ip6_ptr, NULL);
[2]
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index 3dba94bd2ba0..eae51b5de690 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -4022,6 +4028,9 @@ static int addrconf_ifdown(struct net_device *dev, bool unregister)
 		}
 
 		if (!keep) {
+			spin_lock_bh(&net->ipv6.addrconf_hash_lock);
+			hlist_del_init_rcu(&ifa->addr_lst);
+			spin_unlock_bh(&net->ipv6.addrconf_hash_lock);
 			write_lock_bh(&idev->lock);
 			list_del_rcu(&ifa->if_list);
 			write_unlock_bh(&idev->lock);
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help