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