Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c86872a4 authored by Rabin Vincent's avatar Rabin Vincent Committed by Greg Kroah-Hartman
Browse files

ipv6: Fix idev->addr_list corruption



[ Upstream commit a2d6cbb0670d54806f18192cb0db266b4a6d285a ]

addrconf_ifdown() removes elements from the idev->addr_list without
holding the idev->lock.

If this happens while the loop in __ipv6_dev_get_saddr() is handling the
same element, that function ends up in an infinite loop:

  NMI watchdog: BUG: soft lockup - CPU#1 stuck for 23s! [test:1719]
  Call Trace:
   ipv6_get_saddr_eval+0x13c/0x3a0
   __ipv6_dev_get_saddr+0xe4/0x1f0
   ipv6_dev_get_saddr+0x1b4/0x204
   ip6_dst_lookup_tail+0xcc/0x27c
   ip6_dst_lookup_flow+0x38/0x80
   udpv6_sendmsg+0x708/0xba8
   sock_sendmsg+0x18/0x30
   SyS_sendto+0xb8/0xf8
   syscall_common+0x34/0x58

Fixes: 6a923934 (Revert "ipv6: Revert optional address flusing on ifdown.")
Signed-off-by: default avatarRabin Vincent <rabinv@axis.com>
Acked-by: default avatarDavid Ahern <dsa@cumulusnetworks.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 479beb4c
Loading
Loading
Loading
Loading
+7 −4
Original line number Diff line number Diff line
@@ -3602,14 +3602,19 @@ static int addrconf_ifdown(struct net_device *dev, int how)
	INIT_LIST_HEAD(&del_list);
	list_for_each_entry_safe(ifa, tmp, &idev->addr_list, if_list) {
		struct rt6_info *rt = NULL;
		bool keep;

		addrconf_del_dad_work(ifa);

		keep = keep_addr && (ifa->flags & IFA_F_PERMANENT) &&
			!addr_is_local(&ifa->addr);
		if (!keep)
			list_move(&ifa->if_list, &del_list);

		write_unlock_bh(&idev->lock);
		spin_lock_bh(&ifa->lock);

		if (keep_addr && (ifa->flags & IFA_F_PERMANENT) &&
		    !addr_is_local(&ifa->addr)) {
		if (keep) {
			/* set state to skip the notifier below */
			state = INET6_IFADDR_STATE_DEAD;
			ifa->state = 0;
@@ -3621,8 +3626,6 @@ static int addrconf_ifdown(struct net_device *dev, int how)
		} else {
			state = ifa->state;
			ifa->state = INET6_IFADDR_STATE_DEAD;

			list_move(&ifa->if_list, &del_list);
		}

		spin_unlock_bh(&ifa->lock);