tcp: do not underestimate skb->truesize in tcp_trim_head()
[ Upstream commit 7162fb242cb8322beb558828fd26b33c3e9fc805 ] 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: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
097994b3d1
commit
9150b10401
1 changed files with 12 additions and 7 deletions
|
@ -1221,7 +1221,7 @@ int tcp_fragment(struct sock *sk, struct sk_buff *skb, u32 len,
|
||||||
* eventually). The difference is that pulled data not copied, but
|
* eventually). The difference is that pulled data not copied, but
|
||||||
* immediately discarded.
|
* 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;
|
struct skb_shared_info *shinfo;
|
||||||
int i, k, eat;
|
int i, k, eat;
|
||||||
|
@ -1231,7 +1231,7 @@ static void __pskb_trim_head(struct sk_buff *skb, int len)
|
||||||
__skb_pull(skb, eat);
|
__skb_pull(skb, eat);
|
||||||
len -= eat;
|
len -= eat;
|
||||||
if (!len)
|
if (!len)
|
||||||
return;
|
return 0;
|
||||||
}
|
}
|
||||||
eat = len;
|
eat = len;
|
||||||
k = 0;
|
k = 0;
|
||||||
|
@ -1257,23 +1257,28 @@ static void __pskb_trim_head(struct sk_buff *skb, int len)
|
||||||
skb_reset_tail_pointer(skb);
|
skb_reset_tail_pointer(skb);
|
||||||
skb->data_len -= len;
|
skb->data_len -= len;
|
||||||
skb->len = skb->data_len;
|
skb->len = skb->data_len;
|
||||||
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Remove acked data from a packet in the transmit queue. */
|
/* Remove acked data from a packet in the transmit queue. */
|
||||||
int tcp_trim_head(struct sock *sk, struct sk_buff *skb, u32 len)
|
int tcp_trim_head(struct sock *sk, struct sk_buff *skb, u32 len)
|
||||||
{
|
{
|
||||||
|
u32 delta_truesize;
|
||||||
|
|
||||||
if (skb_unclone(skb, GFP_ATOMIC))
|
if (skb_unclone(skb, GFP_ATOMIC))
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
__pskb_trim_head(skb, len);
|
delta_truesize = __pskb_trim_head(skb, len);
|
||||||
|
|
||||||
TCP_SKB_CB(skb)->seq += len;
|
TCP_SKB_CB(skb)->seq += len;
|
||||||
skb->ip_summed = CHECKSUM_PARTIAL;
|
skb->ip_summed = CHECKSUM_PARTIAL;
|
||||||
|
|
||||||
skb->truesize -= len;
|
if (delta_truesize) {
|
||||||
sk->sk_wmem_queued -= len;
|
skb->truesize -= delta_truesize;
|
||||||
sk_mem_uncharge(sk, len);
|
sk->sk_wmem_queued -= delta_truesize;
|
||||||
sock_set_flag(sk, SOCK_QUEUE_SHRUNK);
|
sk_mem_uncharge(sk, delta_truesize);
|
||||||
|
sock_set_flag(sk, SOCK_QUEUE_SHRUNK);
|
||||||
|
}
|
||||||
|
|
||||||
/* Any change of skb->len requires recalculation of tso factor. */
|
/* Any change of skb->len requires recalculation of tso factor. */
|
||||||
if (tcp_skb_pcount(skb) > 1)
|
if (tcp_skb_pcount(skb) > 1)
|
||||||
|
|
Loading…
Add table
Reference in a new issue