| From a3ff1b7405ac4ab7caaf99689308c28fa6219539 Mon Sep 17 00:00:00 2001 |
| From: Eric Dumazet <edumazet@google.com> |
| Date: Wed, 26 Apr 2017 17:15:40 -0700 |
| Subject: [PATCH] tcp: do not underestimate skb->truesize in tcp_trim_head() |
| |
| commit 7162fb242cb8322beb558828fd26b33c3e9fc805 upstream. |
| |
| Andrey found a way to trigger the WARN_ON_ONCE(delta < len) in |
| skb_try_coalesce() using syzkaller and a filter attached to a TCP |
| socket over loopback interface. |
| |
| I believe one issue with looped skbs is that tcp_trim_head() can end up |
| producing skb with under estimated truesize. |
| |
| It hardly matters for normal conditions, since packets sent over |
| loopback are never truncated. |
| |
| Bytes trimmed from skb->head should not change skb truesize, since |
| skb->head is not reallocated. |
| |
| Signed-off-by: Eric Dumazet <edumazet@google.com> |
| Reported-by: Andrey Konovalov <andreyknvl@google.com> |
| Tested-by: Andrey Konovalov <andreyknvl@google.com> |
| Signed-off-by: David S. Miller <davem@davemloft.net> |
| Signed-off-by: Paul Gortmaker <paul.gortmaker@windriver.com> |
| |
| diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c |
| index 687c5e7a0287..2605b67c0ef3 100644 |
| --- a/net/ipv4/tcp_output.c |
| +++ b/net/ipv4/tcp_output.c |
| @@ -1235,7 +1235,7 @@ int tcp_fragment(struct sock *sk, struct sk_buff *skb, u32 len, |
| * eventually). The difference is that pulled data not copied, but |
| * immediately discarded. |
| */ |
| -static void __pskb_trim_head(struct sk_buff *skb, int len) |
| +static int __pskb_trim_head(struct sk_buff *skb, int len) |
| { |
| struct skb_shared_info *shinfo; |
| int i, k, eat; |
| @@ -1245,7 +1245,7 @@ static void __pskb_trim_head(struct sk_buff *skb, int len) |
| __skb_pull(skb, eat); |
| len -= eat; |
| if (!len) |
| - return; |
| + return 0; |
| } |
| eat = len; |
| k = 0; |
| @@ -1271,23 +1271,28 @@ static void __pskb_trim_head(struct sk_buff *skb, int len) |
| skb_reset_tail_pointer(skb); |
| skb->data_len -= len; |
| skb->len = skb->data_len; |
| + return len; |
| } |
| |
| /* Remove acked data from a packet in the transmit queue. */ |
| int tcp_trim_head(struct sock *sk, struct sk_buff *skb, u32 len) |
| { |
| + u32 delta_truesize; |
| + |
| if (skb_unclone(skb, GFP_ATOMIC)) |
| return -ENOMEM; |
| |
| - __pskb_trim_head(skb, len); |
| + delta_truesize = __pskb_trim_head(skb, len); |
| |
| TCP_SKB_CB(skb)->seq += len; |
| skb->ip_summed = CHECKSUM_PARTIAL; |
| |
| - skb->truesize -= len; |
| - sk->sk_wmem_queued -= len; |
| - sk_mem_uncharge(sk, len); |
| - sock_set_flag(sk, SOCK_QUEUE_SHRUNK); |
| + if (delta_truesize) { |
| + skb->truesize -= delta_truesize; |
| + sk->sk_wmem_queued -= delta_truesize; |
| + sk_mem_uncharge(sk, delta_truesize); |
| + sock_set_flag(sk, SOCK_QUEUE_SHRUNK); |
| + } |
| |
| /* Any change of skb->len requires recalculation of tso factor. */ |
| if (tcp_skb_pcount(skb) > 1) |
| -- |
| 2.12.0 |
| |