diff options
author | Jiri Benc <jbenc@redhat.com> | 2013-08-01 04:41:28 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2013-08-01 17:16:20 -0400 |
commit | 8a226b2cfa776db6011fc84b71578513161cd3d3 (patch) | |
tree | 7d9cc6e14f0e99e576f761f82c1d350378ef44e8 | |
parent | 3f8f52982ad020f0704548c46de66bf464d3b967 (diff) |
ipv6: prevent race between address creation and removal
There's a race in IPv6 automatic addess assignment. The address is created
with zero lifetime when it's added to various address lists. Before it gets
assigned the correct lifetime, there's a window where a new address may be
configured. This causes the semi-initiated address to be deleted in
addrconf_verify.
This was discovered as a reference leak caused by concurrent run of
__ipv6_ifa_notify for both RTM_NEWADDR and RTM_DELADDR with the same
address.
Fix this by setting the lifetime before the address is added to
inet6_addr_lst.
A few notes:
1. In addrconf_prefix_rcv, by setting update_lft to zero, the
if (update_lft) { ... } condition is no longer executed for newly
created addresses. This is okay, as the ifp fields are set in
ipv6_add_addr now and ipv6_ifa_notify is called (and has been called)
through addrconf_dad_start.
2. The removal of the whole block under ifp->lock in inet6_addr_add is okay,
too, as tstamp is initialized to jiffies in ipv6_add_addr.
Signed-off-by: Jiri Benc <jbenc@redhat.com>
Signed-off-by: Jiri Pirko <jiri@resnulli.us>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | net/ipv6/addrconf.c | 31 |
1 files changed, 15 insertions, 16 deletions
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c index a0ce957fb671..da4241c8c7da 100644 --- a/net/ipv6/addrconf.c +++ b/net/ipv6/addrconf.c | |||
@@ -815,7 +815,7 @@ static u32 inet6_addr_hash(const struct in6_addr *addr) | |||
815 | static struct inet6_ifaddr * | 815 | static struct inet6_ifaddr * |
816 | ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr, | 816 | ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr, |
817 | const struct in6_addr *peer_addr, int pfxlen, | 817 | const struct in6_addr *peer_addr, int pfxlen, |
818 | int scope, u32 flags) | 818 | int scope, u32 flags, u32 valid_lft, u32 prefered_lft) |
819 | { | 819 | { |
820 | struct inet6_ifaddr *ifa = NULL; | 820 | struct inet6_ifaddr *ifa = NULL; |
821 | struct rt6_info *rt; | 821 | struct rt6_info *rt; |
@@ -875,6 +875,8 @@ ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr, | |||
875 | ifa->scope = scope; | 875 | ifa->scope = scope; |
876 | ifa->prefix_len = pfxlen; | 876 | ifa->prefix_len = pfxlen; |
877 | ifa->flags = flags | IFA_F_TENTATIVE; | 877 | ifa->flags = flags | IFA_F_TENTATIVE; |
878 | ifa->valid_lft = valid_lft; | ||
879 | ifa->prefered_lft = prefered_lft; | ||
878 | ifa->cstamp = ifa->tstamp = jiffies; | 880 | ifa->cstamp = ifa->tstamp = jiffies; |
879 | ifa->tokenized = false; | 881 | ifa->tokenized = false; |
880 | 882 | ||
@@ -1127,7 +1129,8 @@ retry: | |||
1127 | ift = !max_addresses || | 1129 | ift = !max_addresses || |
1128 | ipv6_count_addresses(idev) < max_addresses ? | 1130 | ipv6_count_addresses(idev) < max_addresses ? |
1129 | ipv6_add_addr(idev, &addr, NULL, tmp_plen, | 1131 | ipv6_add_addr(idev, &addr, NULL, tmp_plen, |
1130 | ipv6_addr_scope(&addr), addr_flags) : NULL; | 1132 | ipv6_addr_scope(&addr), addr_flags, |
1133 | tmp_valid_lft, tmp_prefered_lft) : NULL; | ||
1131 | if (IS_ERR_OR_NULL(ift)) { | 1134 | if (IS_ERR_OR_NULL(ift)) { |
1132 | in6_ifa_put(ifp); | 1135 | in6_ifa_put(ifp); |
1133 | in6_dev_put(idev); | 1136 | in6_dev_put(idev); |
@@ -1139,8 +1142,6 @@ retry: | |||
1139 | 1142 | ||
1140 | spin_lock_bh(&ift->lock); | 1143 | spin_lock_bh(&ift->lock); |
1141 | ift->ifpub = ifp; | 1144 | ift->ifpub = ifp; |
1142 | ift->valid_lft = tmp_valid_lft; | ||
1143 | ift->prefered_lft = tmp_prefered_lft; | ||
1144 | ift->cstamp = now; | 1145 | ift->cstamp = now; |
1145 | ift->tstamp = tmp_tstamp; | 1146 | ift->tstamp = tmp_tstamp; |
1146 | spin_unlock_bh(&ift->lock); | 1147 | spin_unlock_bh(&ift->lock); |
@@ -2185,14 +2186,16 @@ ok: | |||
2185 | ifp = ipv6_add_addr(in6_dev, &addr, NULL, | 2186 | ifp = ipv6_add_addr(in6_dev, &addr, NULL, |
2186 | pinfo->prefix_len, | 2187 | pinfo->prefix_len, |
2187 | addr_type&IPV6_ADDR_SCOPE_MASK, | 2188 | addr_type&IPV6_ADDR_SCOPE_MASK, |
2188 | addr_flags); | 2189 | addr_flags, valid_lft, |
2190 | prefered_lft); | ||
2189 | 2191 | ||
2190 | if (IS_ERR_OR_NULL(ifp)) { | 2192 | if (IS_ERR_OR_NULL(ifp)) { |
2191 | in6_dev_put(in6_dev); | 2193 | in6_dev_put(in6_dev); |
2192 | return; | 2194 | return; |
2193 | } | 2195 | } |
2194 | 2196 | ||
2195 | update_lft = create = 1; | 2197 | update_lft = 0; |
2198 | create = 1; | ||
2196 | ifp->cstamp = jiffies; | 2199 | ifp->cstamp = jiffies; |
2197 | ifp->tokenized = tokenized; | 2200 | ifp->tokenized = tokenized; |
2198 | addrconf_dad_start(ifp); | 2201 | addrconf_dad_start(ifp); |
@@ -2213,7 +2216,7 @@ ok: | |||
2213 | stored_lft = ifp->valid_lft - (now - ifp->tstamp) / HZ; | 2216 | stored_lft = ifp->valid_lft - (now - ifp->tstamp) / HZ; |
2214 | else | 2217 | else |
2215 | stored_lft = 0; | 2218 | stored_lft = 0; |
2216 | if (!update_lft && stored_lft) { | 2219 | if (!update_lft && !create && stored_lft) { |
2217 | if (valid_lft > MIN_VALID_LIFETIME || | 2220 | if (valid_lft > MIN_VALID_LIFETIME || |
2218 | valid_lft > stored_lft) | 2221 | valid_lft > stored_lft) |
2219 | update_lft = 1; | 2222 | update_lft = 1; |
@@ -2459,15 +2462,10 @@ static int inet6_addr_add(struct net *net, int ifindex, const struct in6_addr *p | |||
2459 | prefered_lft = timeout; | 2462 | prefered_lft = timeout; |
2460 | } | 2463 | } |
2461 | 2464 | ||
2462 | ifp = ipv6_add_addr(idev, pfx, peer_pfx, plen, scope, ifa_flags); | 2465 | ifp = ipv6_add_addr(idev, pfx, peer_pfx, plen, scope, ifa_flags, |
2466 | valid_lft, prefered_lft); | ||
2463 | 2467 | ||
2464 | if (!IS_ERR(ifp)) { | 2468 | if (!IS_ERR(ifp)) { |
2465 | spin_lock_bh(&ifp->lock); | ||
2466 | ifp->valid_lft = valid_lft; | ||
2467 | ifp->prefered_lft = prefered_lft; | ||
2468 | ifp->tstamp = jiffies; | ||
2469 | spin_unlock_bh(&ifp->lock); | ||
2470 | |||
2471 | addrconf_prefix_route(&ifp->addr, ifp->prefix_len, dev, | 2469 | addrconf_prefix_route(&ifp->addr, ifp->prefix_len, dev, |
2472 | expires, flags); | 2470 | expires, flags); |
2473 | /* | 2471 | /* |
@@ -2559,7 +2557,8 @@ static void add_addr(struct inet6_dev *idev, const struct in6_addr *addr, | |||
2559 | { | 2557 | { |
2560 | struct inet6_ifaddr *ifp; | 2558 | struct inet6_ifaddr *ifp; |
2561 | 2559 | ||
2562 | ifp = ipv6_add_addr(idev, addr, NULL, plen, scope, IFA_F_PERMANENT); | 2560 | ifp = ipv6_add_addr(idev, addr, NULL, plen, |
2561 | scope, IFA_F_PERMANENT, 0, 0); | ||
2563 | if (!IS_ERR(ifp)) { | 2562 | if (!IS_ERR(ifp)) { |
2564 | spin_lock_bh(&ifp->lock); | 2563 | spin_lock_bh(&ifp->lock); |
2565 | ifp->flags &= ~IFA_F_TENTATIVE; | 2564 | ifp->flags &= ~IFA_F_TENTATIVE; |
@@ -2685,7 +2684,7 @@ static void addrconf_add_linklocal(struct inet6_dev *idev, const struct in6_addr | |||
2685 | #endif | 2684 | #endif |
2686 | 2685 | ||
2687 | 2686 | ||
2688 | ifp = ipv6_add_addr(idev, addr, NULL, 64, IFA_LINK, addr_flags); | 2687 | ifp = ipv6_add_addr(idev, addr, NULL, 64, IFA_LINK, addr_flags, 0, 0); |
2689 | if (!IS_ERR(ifp)) { | 2688 | if (!IS_ERR(ifp)) { |
2690 | addrconf_prefix_route(&ifp->addr, ifp->prefix_len, idev->dev, 0, 0); | 2689 | addrconf_prefix_route(&ifp->addr, ifp->prefix_len, idev->dev, 0, 0); |
2691 | addrconf_dad_start(ifp); | 2690 | addrconf_dad_start(ifp); |