diff options
author | Eric Dumazet <edumazet@google.com> | 2017-02-05 12:25:24 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2017-02-07 11:19:00 -0500 |
commit | 69629464e0b587f3711739b3aa2bcdaf2e075276 (patch) | |
tree | a0fc1812afaf992b01042e2bf081c455addc16ab | |
parent | 6a413e269b170d6d3bd32a71de4d5dcf987d6843 (diff) |
udp: properly cope with csum errors
Dmitry reported that UDP sockets being destroyed would trigger the
WARN_ON(atomic_read(&sk->sk_rmem_alloc)); in inet_sock_destruct()
It turns out we do not properly destroy skb(s) that have wrong UDP
checksum.
Thanks again to syzkaller team.
Fixes : 7c13f97ffde6 ("udp: do fwd memory scheduling on dequeue")
Reported-by: Dmitry Vyukov <dvyukov@google.com>
Signed-off-by: Eric Dumazet <edumazet@google.com>
Cc: Paolo Abeni <pabeni@redhat.com>
Cc: Hannes Frederic Sowa <hannes@stressinduktion.org>
Acked-by: Paolo Abeni <pabeni@redhat.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | include/net/sock.h | 4 | ||||
-rw-r--r-- | net/core/datagram.c | 8 | ||||
-rw-r--r-- | net/ipv4/udp.c | 2 | ||||
-rw-r--r-- | net/ipv6/udp.c | 2 |
4 files changed, 11 insertions, 5 deletions
diff --git a/include/net/sock.h b/include/net/sock.h index f0e867f58722..c4f5e6fca17c 100644 --- a/include/net/sock.h +++ b/include/net/sock.h | |||
@@ -2006,7 +2006,9 @@ void sk_reset_timer(struct sock *sk, struct timer_list *timer, | |||
2006 | void sk_stop_timer(struct sock *sk, struct timer_list *timer); | 2006 | void sk_stop_timer(struct sock *sk, struct timer_list *timer); |
2007 | 2007 | ||
2008 | int __sk_queue_drop_skb(struct sock *sk, struct sk_buff *skb, | 2008 | int __sk_queue_drop_skb(struct sock *sk, struct sk_buff *skb, |
2009 | unsigned int flags); | 2009 | unsigned int flags, |
2010 | void (*destructor)(struct sock *sk, | ||
2011 | struct sk_buff *skb)); | ||
2010 | int __sock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb); | 2012 | int __sock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb); |
2011 | int sock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb); | 2013 | int sock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb); |
2012 | 2014 | ||
diff --git a/net/core/datagram.c b/net/core/datagram.c index 662bea587165..ea633342ab0d 100644 --- a/net/core/datagram.c +++ b/net/core/datagram.c | |||
@@ -332,7 +332,9 @@ void __skb_free_datagram_locked(struct sock *sk, struct sk_buff *skb, int len) | |||
332 | EXPORT_SYMBOL(__skb_free_datagram_locked); | 332 | EXPORT_SYMBOL(__skb_free_datagram_locked); |
333 | 333 | ||
334 | int __sk_queue_drop_skb(struct sock *sk, struct sk_buff *skb, | 334 | int __sk_queue_drop_skb(struct sock *sk, struct sk_buff *skb, |
335 | unsigned int flags) | 335 | unsigned int flags, |
336 | void (*destructor)(struct sock *sk, | ||
337 | struct sk_buff *skb)) | ||
336 | { | 338 | { |
337 | int err = 0; | 339 | int err = 0; |
338 | 340 | ||
@@ -342,6 +344,8 @@ int __sk_queue_drop_skb(struct sock *sk, struct sk_buff *skb, | |||
342 | if (skb == skb_peek(&sk->sk_receive_queue)) { | 344 | if (skb == skb_peek(&sk->sk_receive_queue)) { |
343 | __skb_unlink(skb, &sk->sk_receive_queue); | 345 | __skb_unlink(skb, &sk->sk_receive_queue); |
344 | atomic_dec(&skb->users); | 346 | atomic_dec(&skb->users); |
347 | if (destructor) | ||
348 | destructor(sk, skb); | ||
345 | err = 0; | 349 | err = 0; |
346 | } | 350 | } |
347 | spin_unlock_bh(&sk->sk_receive_queue.lock); | 351 | spin_unlock_bh(&sk->sk_receive_queue.lock); |
@@ -375,7 +379,7 @@ EXPORT_SYMBOL(__sk_queue_drop_skb); | |||
375 | 379 | ||
376 | int skb_kill_datagram(struct sock *sk, struct sk_buff *skb, unsigned int flags) | 380 | int skb_kill_datagram(struct sock *sk, struct sk_buff *skb, unsigned int flags) |
377 | { | 381 | { |
378 | int err = __sk_queue_drop_skb(sk, skb, flags); | 382 | int err = __sk_queue_drop_skb(sk, skb, flags, NULL); |
379 | 383 | ||
380 | kfree_skb(skb); | 384 | kfree_skb(skb); |
381 | sk_mem_reclaim_partial(sk); | 385 | sk_mem_reclaim_partial(sk); |
diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c index 1307a7c2e544..8aab7d78d25b 100644 --- a/net/ipv4/udp.c +++ b/net/ipv4/udp.c | |||
@@ -1501,7 +1501,7 @@ try_again: | |||
1501 | return err; | 1501 | return err; |
1502 | 1502 | ||
1503 | csum_copy_err: | 1503 | csum_copy_err: |
1504 | if (!__sk_queue_drop_skb(sk, skb, flags)) { | 1504 | if (!__sk_queue_drop_skb(sk, skb, flags, udp_skb_destructor)) { |
1505 | UDP_INC_STATS(sock_net(sk), UDP_MIB_CSUMERRORS, is_udplite); | 1505 | UDP_INC_STATS(sock_net(sk), UDP_MIB_CSUMERRORS, is_udplite); |
1506 | UDP_INC_STATS(sock_net(sk), UDP_MIB_INERRORS, is_udplite); | 1506 | UDP_INC_STATS(sock_net(sk), UDP_MIB_INERRORS, is_udplite); |
1507 | } | 1507 | } |
diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c index 4d5c4eee4b3f..8990856f5101 100644 --- a/net/ipv6/udp.c +++ b/net/ipv6/udp.c | |||
@@ -441,7 +441,7 @@ try_again: | |||
441 | return err; | 441 | return err; |
442 | 442 | ||
443 | csum_copy_err: | 443 | csum_copy_err: |
444 | if (!__sk_queue_drop_skb(sk, skb, flags)) { | 444 | if (!__sk_queue_drop_skb(sk, skb, flags, udp_skb_destructor)) { |
445 | if (is_udp4) { | 445 | if (is_udp4) { |
446 | UDP_INC_STATS(sock_net(sk), | 446 | UDP_INC_STATS(sock_net(sk), |
447 | UDP_MIB_CSUMERRORS, is_udplite); | 447 | UDP_MIB_CSUMERRORS, is_udplite); |