mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH net] ipv6: serialize address publication with addrconf_ifdown
@ 2026-10-01  5:21 Daehyeon Ko
  0 siblings, 0 replies; only message in thread
From: Daehyeon Ko @ 2026-10-01  5:21 UTC (permalink / raw)
  To: David Ahern, Ido Schimmel
  Cc: David S . Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, netdev, linux-kernel, Daehyeon Ko

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.

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
available on request.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Daehyeon Ko <4ncienth@gmail.com>
---
 net/ipv6/addrconf.c | 37 ++++++++++++++++++++-----------------
 1 file changed, 20 insertions(+), 17 deletions(-)

diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index c90ee6dd7446c..899e9a47f7585 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -1047,8 +1047,9 @@ static bool ipv6_chk_same_addr(struct net *net, const struct in6_addr *addr,
 	return false;
 }
 
-static int ipv6_add_addr_hash(struct net_device *dev, struct inet6_ifaddr *ifa)
+static int ipv6_add_addr_hash(struct inet6_dev *idev, struct inet6_ifaddr *ifa)
 {
+	struct net_device *dev = idev->dev;
 	struct net *net = dev_net(dev);
 	unsigned int hash = inet6_addr_hash(net, &ifa->addr);
 	int err = 0;
@@ -1060,7 +1061,23 @@ static int ipv6_add_addr_hash(struct net_device *dev, struct inet6_ifaddr *ifa)
 		netdev_dbg(dev, "ipv6_add_addr: already assigned\n");
 		err = -EEXIST;
 	} else {
-		hlist_add_head_rcu(&ifa->addr_lst, &net->ipv6.inet6_addr_lst[hash]);
+		write_lock(&idev->lock);
+		if (idev->dead || idev->cnf.disable_ipv6) {
+			err = idev->dead ? -ENODEV : -EACCES;
+		} else {
+			hlist_add_head_rcu(&ifa->addr_lst,
+					   &net->ipv6.inet6_addr_lst[hash]);
+			ipv6_link_dev_addr(idev, ifa);
+
+			if (ifa->flags & IFA_F_TEMPORARY) {
+				/* manage_tempaddrs() relies on addresses being added to the head */
+				list_add(&ifa->tmp_list, &idev->tempaddr_list);
+				in6_ifa_hold(ifa);
+			}
+
+			in6_ifa_hold(ifa);
+		}
+		write_unlock(&idev->lock);
 	}
 
 	spin_unlock_bh(&net->ipv6.addrconf_hash_lock);
@@ -1168,26 +1185,12 @@ ipv6_add_addr(struct inet6_dev *idev, struct ifa6_config *cfg,
 
 	rcu_read_lock();
 
-	err = ipv6_add_addr_hash(idev->dev, ifa);
+	err = ipv6_add_addr_hash(idev, ifa);
 	if (err < 0) {
 		rcu_read_unlock();
 		goto out;
 	}
 
-	write_lock_bh(&idev->lock);
-
-	/* Add to inet6_dev unicast addr list. */
-	ipv6_link_dev_addr(idev, ifa);
-
-	if (ifa->flags&IFA_F_TEMPORARY) {
-		/* manage_tempaddrs() relies on addresses being added to the head */
-		list_add(&ifa->tmp_list, &idev->tempaddr_list);
-		in6_ifa_hold(ifa);
-	}
-
-	in6_ifa_hold(ifa);
-	write_unlock_bh(&idev->lock);
-
 	rcu_read_unlock();
 
 	inet6addr_notifier_call_chain(NETDEV_UP, ifa);

base-commit: 7375d38364a9aa66fb31716bcefef38aecad75d8

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2026-10-01  5:22 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-01  5:21 [PATCH net] ipv6: serialize address publication with addrconf_ifdown Daehyeon Ko

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®