[Acked] [PATCH Wily SRU] UBUNTU: SAUCE: (noup) net: fix IP early demux races
Andy Whitcroft
apw at canonical.com
Fri Dec 18 14:12:56 UTC 2015
On Thu, Dec 17, 2015 at 05:29:53PM -0700, tim.gardner at canonical.com wrote:
> From: Eric Dumazet <edumazet at google.com>
>
> BugLink: http://bugs.launchpad.net/bugs/1526946
>
> David Wilder reported crashes caused by dst reuse.
>
> <quote David>
> I am seeing a crash on a distro V4.2.3 kernel caused by a double
> release of a dst_entry. In ipv4_dst_destroy() the call to
> list_empty() finds a poisoned next pointer, indicating the dst_entry
> has already been removed from the list and freed. The crash occurs
> 18 to 24 hours into a run of a network stress exerciser.
> </quote>
>
> Thanks to his detailed report and analysis, we were able to understand
> the core issue.
>
> IP early demux can associate a dst to skb, after a lookup in TCP/UDP
> sockets.
>
> When socket cache is not properly set, we want to store into
> sk->sk_dst_cache the dst for future IP early demux lookups,
> by acquiring a stable refcount on the dst.
>
> Problem is this acquisition is simply using an atomic_inc(),
> which works well, unless the dst was queued for destruction from
> dst_release() noticing dst refcount went to zero, if DST_NOCACHE
> was set on dst.
>
> We need to make sure current refcount is not zero before incrementing
> it, or risk double free as David reported.
>
> This patch, being a stable candidate, adds two new helpers, and use
> them only from IP early demux problematic paths.
>
> It might be possible to merge in net-next skb_dst_force() and
> skb_dst_force_safe(), but I prefer having the smallest patch for stable
> kernels : Maybe some skb_dst_force() callers do not expect skb->dst
> can suddenly be cleared.
>
> Can probably be backported back to linux-3.6 kernels
>
> Reported-by: David J. Wilder <dwilder at us.ibm.com>
> Tested-by: David J. Wilder <dwilder at us.ibm.com>
> Signed-off-by: Eric Dumazet <edumazet at google.com>
> Signed-off-by: David S. Miller <davem at davemloft.net>
> (cherry picked from linux-next commit 5037e9ef9454917b047f9f3a19b4dd179fbf7cd4)
> Signed-off-by: Tim Gardner <tim.gardner at canonical.com>
> ---
> include/net/dst.h | 33 +++++++++++++++++++++++++++++++++
> include/net/sock.h | 2 +-
> net/ipv4/tcp_ipv4.c | 5 ++---
> net/ipv6/tcp_ipv6.c | 3 +--
> 4 files changed, 37 insertions(+), 6 deletions(-)
>
> diff --git a/include/net/dst.h b/include/net/dst.h
> index 2bc73f8a..c34b277 100644
> --- a/include/net/dst.h
> +++ b/include/net/dst.h
> @@ -306,6 +306,39 @@ static inline void skb_dst_force(struct sk_buff *skb)
> }
> }
>
> +/**
> + * dst_hold_safe - Take a reference on a dst if possible
> + * @dst: pointer to dst entry
> + *
> + * This helper returns false if it could not safely
> + * take a reference on a dst.
> + */
> +static inline bool dst_hold_safe(struct dst_entry *dst)
> +{
> + if (dst->flags & DST_NOCACHE)
> + return atomic_inc_not_zero(&dst->__refcnt);
> + dst_hold(dst);
> + return true;
> +}
> +
> +/**
> + * skb_dst_force_safe - makes sure skb dst is refcounted
> + * @skb: buffer
> + *
> + * If dst is not yet refcounted and not destroyed, grab a ref on it.
> + */
> +static inline void skb_dst_force_safe(struct sk_buff *skb)
> +{
> + if (skb_dst_is_noref(skb)) {
> + struct dst_entry *dst = skb_dst(skb);
> +
> + if (!dst_hold_safe(dst))
> + dst = NULL;
> +
> + skb->_skb_refdst = (unsigned long)dst;
> + }
> +}
> +
>
> /**
> * __skb_tunnel_rx - prepare skb for rx reinsert
> diff --git a/include/net/sock.h b/include/net/sock.h
> index 4ca4c3f..952e780 100644
> --- a/include/net/sock.h
> +++ b/include/net/sock.h
> @@ -796,7 +796,7 @@ void sk_stream_write_space(struct sock *sk);
> static inline void __sk_add_backlog(struct sock *sk, struct sk_buff *skb)
> {
> /* dont let skb dst not refcounted, we are going to leave rcu lock */
> - skb_dst_force(skb);
> + skb_dst_force_safe(skb);
>
> if (!sk->sk_backlog.tail)
> sk->sk_backlog.head = skb;
> diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c
> index 0ea2e1c..4d37075 100644
> --- a/net/ipv4/tcp_ipv4.c
> +++ b/net/ipv4/tcp_ipv4.c
> @@ -1508,7 +1508,7 @@ bool tcp_prequeue(struct sock *sk, struct sk_buff *skb)
> if (likely(sk->sk_rx_dst))
> skb_dst_drop(skb);
> else
> - skb_dst_force(skb);
> + skb_dst_force_safe(skb);
>
> __skb_queue_tail(&tp->ucopy.prequeue, skb);
> tp->ucopy.memory += skb->truesize;
> @@ -1710,8 +1710,7 @@ void inet_sk_rx_dst_set(struct sock *sk, const struct sk_buff *skb)
> {
> struct dst_entry *dst = skb_dst(skb);
>
> - if (dst) {
> - dst_hold(dst);
> + if (dst && dst_hold_safe(dst)) {
> sk->sk_rx_dst = dst;
> inet_sk(sk)->rx_dst_ifindex = skb->skb_iif;
> }
> diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c
> index 7a6cea5..403cb24 100644
> --- a/net/ipv6/tcp_ipv6.c
> +++ b/net/ipv6/tcp_ipv6.c
> @@ -93,10 +93,9 @@ static void inet6_sk_rx_dst_set(struct sock *sk, const struct sk_buff *skb)
> {
> struct dst_entry *dst = skb_dst(skb);
>
> - if (dst) {
> + if (dst && dst_hold_safe(dst)) {
> const struct rt6_info *rt = (const struct rt6_info *)dst;
>
> - dst_hold(dst);
> sk->sk_rx_dst = dst;
> inet_sk(sk)->rx_dst_ifindex = skb->skb_iif;
> inet6_sk(sk)->rx_dst_cookie = rt6_get_cookie(rt);
Looks to do what is claimed. Clean cherrypick.
Acked-by: Andy Whitcroft <apw at canonical.com>
-apw
More information about the kernel-team
mailing list