diff options
author | Rabin Vincent <rabinv@axis.com> | 2017-04-10 02:36:39 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2017-05-03 11:36:36 -0400 |
commit | c86872a43400c1e5df2acbd9bb23e3941ef1c98d (patch) | |
tree | a0bd883569e964a9e6f357c0b521d26c3f70bffa /net/ipv6 | |
parent | 479beb4c6554bab1f944a5cfb9672a0f9c6892b2 (diff) |
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: 6a923934c33 (Revert "ipv6: Revert optional address flusing on ifdown.")
Signed-off-by: Rabin Vincent <rabinv@axis.com>
Acked-by: David Ahern <dsa@cumulusnetworks.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'net/ipv6')
-rw-r--r-- | net/ipv6/addrconf.c | 11 |
1 files changed, 7 insertions, 4 deletions
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c index 95dfcba38ff6..cbcc991f844f 100644 --- a/net/ipv6/addrconf.c +++ b/net/ipv6/addrconf.c | |||
@@ -3602,14 +3602,19 @@ restart: | |||
3602 | INIT_LIST_HEAD(&del_list); | 3602 | INIT_LIST_HEAD(&del_list); |
3603 | list_for_each_entry_safe(ifa, tmp, &idev->addr_list, if_list) { | 3603 | list_for_each_entry_safe(ifa, tmp, &idev->addr_list, if_list) { |
3604 | struct rt6_info *rt = NULL; | 3604 | struct rt6_info *rt = NULL; |
3605 | bool keep; | ||
3605 | 3606 | ||
3606 | addrconf_del_dad_work(ifa); | 3607 | addrconf_del_dad_work(ifa); |
3607 | 3608 | ||
3609 | keep = keep_addr && (ifa->flags & IFA_F_PERMANENT) && | ||
3610 | !addr_is_local(&ifa->addr); | ||
3611 | if (!keep) | ||
3612 | list_move(&ifa->if_list, &del_list); | ||
3613 | |||
3608 | write_unlock_bh(&idev->lock); | 3614 | write_unlock_bh(&idev->lock); |
3609 | spin_lock_bh(&ifa->lock); | 3615 | spin_lock_bh(&ifa->lock); |
3610 | 3616 | ||
3611 | if (keep_addr && (ifa->flags & IFA_F_PERMANENT) && | 3617 | if (keep) { |
3612 | !addr_is_local(&ifa->addr)) { | ||
3613 | /* set state to skip the notifier below */ | 3618 | /* set state to skip the notifier below */ |
3614 | state = INET6_IFADDR_STATE_DEAD; | 3619 | state = INET6_IFADDR_STATE_DEAD; |
3615 | ifa->state = 0; | 3620 | ifa->state = 0; |
@@ -3621,8 +3626,6 @@ restart: | |||
3621 | } else { | 3626 | } else { |
3622 | state = ifa->state; | 3627 | state = ifa->state; |
3623 | ifa->state = INET6_IFADDR_STATE_DEAD; | 3628 | ifa->state = INET6_IFADDR_STATE_DEAD; |
3624 | |||
3625 | list_move(&ifa->if_list, &del_list); | ||
3626 | } | 3629 | } |
3627 | 3630 | ||
3628 | spin_unlock_bh(&ifa->lock); | 3631 | spin_unlock_bh(&ifa->lock); |