ipv6: kill sk_dst_lock
While testing the np->opt RCU conversion, I found that UDP/IPv6 was using a mixture of xchg() and sk_dst_lock to protect concurrent changes to sk->sk_dst_cache, leading to possible corruptions and crashes. ip6_sk_dst_lookup_flow() uses sk_dst_check() anyway, so the simplest way to fix the mess is to remove sk_dst_lock completely, as we did for IPv4. __ip6_dst_store() and ip6_dst_store() share same implementation. sk_setup_caps() being called with socket lock being held or not, we have to use sk_dst_set() instead of __sk_dst_set() Note that I had to move the "np->dst_cookie = rt6_get_cookie(rt);" in ip6_dst_store() before the sk_setup_caps(sk, dst) call. This is because ip6_dst_store() can be called from process context, without any lock held. As soon as the dst is installed in sk->sk_dst_cache, dst can be freed from another cpu doing a concurrent ip6_dst_store() Doing the dst dereference before doing the install is needed to make sure no use after free would trigger. Signed-off-by: Eric Dumazet <edumazet@google.com> Reported-by: Dmitry Vyukov <dvyukov@google.com> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:

committed by
David S. Miller

parent
c836a8ba93
commit
6bd4f355df
@@ -673,7 +673,7 @@ int inet6_sk_rebuild_header(struct sock *sk)
|
||||
return PTR_ERR(dst);
|
||||
}
|
||||
|
||||
__ip6_dst_store(sk, dst, NULL, NULL);
|
||||
ip6_dst_store(sk, dst, NULL, NULL);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@@ -834,11 +834,6 @@ void icmpv6_flow_init(struct sock *sk, struct flowi6 *fl6,
|
||||
security_sk_classify_flow(sk, flowi6_to_flowi(fl6));
|
||||
}
|
||||
|
||||
/*
|
||||
* Special lock-class for __icmpv6_sk:
|
||||
*/
|
||||
static struct lock_class_key icmpv6_socket_sk_dst_lock_key;
|
||||
|
||||
static int __net_init icmpv6_sk_init(struct net *net)
|
||||
{
|
||||
struct sock *sk;
|
||||
@@ -860,15 +855,6 @@ static int __net_init icmpv6_sk_init(struct net *net)
|
||||
|
||||
net->ipv6.icmp_sk[i] = sk;
|
||||
|
||||
/*
|
||||
* Split off their lock-class, because sk->sk_dst_lock
|
||||
* gets used from softirqs, which is safe for
|
||||
* __icmpv6_sk (because those never get directly used
|
||||
* via userspace syscalls), but unsafe for normal sockets.
|
||||
*/
|
||||
lockdep_set_class(&sk->sk_dst_lock,
|
||||
&icmpv6_socket_sk_dst_lock_key);
|
||||
|
||||
/* Enough space for 2 64K ICMP packets, including
|
||||
* sk_buff struct overhead.
|
||||
*/
|
||||
|
@@ -110,14 +110,6 @@ void inet6_csk_addr2sockaddr(struct sock *sk, struct sockaddr *uaddr)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(inet6_csk_addr2sockaddr);
|
||||
|
||||
static inline
|
||||
void __inet6_csk_dst_store(struct sock *sk, struct dst_entry *dst,
|
||||
const struct in6_addr *daddr,
|
||||
const struct in6_addr *saddr)
|
||||
{
|
||||
__ip6_dst_store(sk, dst, daddr, saddr);
|
||||
}
|
||||
|
||||
static inline
|
||||
struct dst_entry *__inet6_csk_dst_check(struct sock *sk, u32 cookie)
|
||||
{
|
||||
@@ -153,7 +145,7 @@ static struct dst_entry *inet6_csk_route_socket(struct sock *sk,
|
||||
dst = ip6_dst_lookup_flow(sk, fl6, final_p);
|
||||
|
||||
if (!IS_ERR(dst))
|
||||
__inet6_csk_dst_store(sk, dst, NULL, NULL);
|
||||
ip6_dst_store(sk, dst, NULL, NULL);
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
@@ -257,7 +257,7 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr *uaddr,
|
||||
inet->inet_rcv_saddr = LOOPBACK4_IPV6;
|
||||
|
||||
sk->sk_gso_type = SKB_GSO_TCPV6;
|
||||
__ip6_dst_store(sk, dst, NULL, NULL);
|
||||
ip6_dst_store(sk, dst, NULL, NULL);
|
||||
|
||||
if (tcp_death_row.sysctl_tw_recycle &&
|
||||
!tp->rx_opt.ts_recent_stamp &&
|
||||
@@ -1060,7 +1060,7 @@ static struct sock *tcp_v6_syn_recv_sock(const struct sock *sk, struct sk_buff *
|
||||
*/
|
||||
|
||||
newsk->sk_gso_type = SKB_GSO_TCPV6;
|
||||
__ip6_dst_store(newsk, dst, NULL, NULL);
|
||||
ip6_dst_store(newsk, dst, NULL, NULL);
|
||||
inet6_sk_rx_dst_set(newsk, skb);
|
||||
|
||||
newtcp6sk = (struct tcp6_sock *)newsk;
|
||||
|
Reference in New Issue
Block a user