skb_expand_head() adjust skb->truesize incorrectly
Christoph Paasch reports [1] about incorrect skb->truesize after skb_expand_head() call in ip6_xmit. This may happen because of two reasons: - skb_set_owner_w() for newly cloned skb is called too early, before pskb_expand_head() where truesize is adjusted for (!skb-sk) case. - pskb_expand_head() does not adjust truesize in (skb->sk) case. In this case sk->sk_wmem_alloc should be adjusted too. [1] https://lkml.org/lkml/2021/8/20/1082 Fixes:f1260ff15a
("skbuff: introduce skb_expand_head()") Fixes:2d85a1b31d
("ipv6: ip6_finish_output2: set sk into newly allocated nskb") Reported-by: Christoph Paasch <christoph.paasch@gmail.com> Signed-off-by: Vasily Averin <vvs@virtuozzo.com> Reviewed-by: Eric Dumazet <edumazet@google.com> Link: https://lore.kernel.org/r/644330dd-477e-0462-83bf-9f514c41edd1@virtuozzo.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
parent
7fcb1c950e
commit
7f678def99
@ -80,6 +80,7 @@
|
|||||||
#include <linux/indirect_call_wrapper.h>
|
#include <linux/indirect_call_wrapper.h>
|
||||||
|
|
||||||
#include "datagram.h"
|
#include "datagram.h"
|
||||||
|
#include "sock_destructor.h"
|
||||||
|
|
||||||
struct kmem_cache *skbuff_head_cache __ro_after_init;
|
struct kmem_cache *skbuff_head_cache __ro_after_init;
|
||||||
static struct kmem_cache *skbuff_fclone_cache __ro_after_init;
|
static struct kmem_cache *skbuff_fclone_cache __ro_after_init;
|
||||||
@ -1804,30 +1805,39 @@ EXPORT_SYMBOL(skb_realloc_headroom);
|
|||||||
struct sk_buff *skb_expand_head(struct sk_buff *skb, unsigned int headroom)
|
struct sk_buff *skb_expand_head(struct sk_buff *skb, unsigned int headroom)
|
||||||
{
|
{
|
||||||
int delta = headroom - skb_headroom(skb);
|
int delta = headroom - skb_headroom(skb);
|
||||||
|
int osize = skb_end_offset(skb);
|
||||||
|
struct sock *sk = skb->sk;
|
||||||
|
|
||||||
if (WARN_ONCE(delta <= 0,
|
if (WARN_ONCE(delta <= 0,
|
||||||
"%s is expecting an increase in the headroom", __func__))
|
"%s is expecting an increase in the headroom", __func__))
|
||||||
return skb;
|
return skb;
|
||||||
|
|
||||||
/* pskb_expand_head() might crash, if skb is shared */
|
delta = SKB_DATA_ALIGN(delta);
|
||||||
if (skb_shared(skb)) {
|
/* pskb_expand_head() might crash, if skb is shared. */
|
||||||
|
if (skb_shared(skb) || !is_skb_wmem(skb)) {
|
||||||
struct sk_buff *nskb = skb_clone(skb, GFP_ATOMIC);
|
struct sk_buff *nskb = skb_clone(skb, GFP_ATOMIC);
|
||||||
|
|
||||||
if (likely(nskb)) {
|
if (unlikely(!nskb))
|
||||||
if (skb->sk)
|
goto fail;
|
||||||
skb_set_owner_w(nskb, skb->sk);
|
|
||||||
consume_skb(skb);
|
if (sk)
|
||||||
} else {
|
skb_set_owner_w(nskb, sk);
|
||||||
kfree_skb(skb);
|
consume_skb(skb);
|
||||||
}
|
|
||||||
skb = nskb;
|
skb = nskb;
|
||||||
}
|
}
|
||||||
if (skb &&
|
if (pskb_expand_head(skb, delta, 0, GFP_ATOMIC))
|
||||||
pskb_expand_head(skb, SKB_DATA_ALIGN(delta), 0, GFP_ATOMIC)) {
|
goto fail;
|
||||||
kfree_skb(skb);
|
|
||||||
skb = NULL;
|
if (sk && is_skb_wmem(skb)) {
|
||||||
|
delta = skb_end_offset(skb) - osize;
|
||||||
|
refcount_add(delta, &sk->sk_wmem_alloc);
|
||||||
|
skb->truesize += delta;
|
||||||
}
|
}
|
||||||
return skb;
|
return skb;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
kfree_skb(skb);
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(skb_expand_head);
|
EXPORT_SYMBOL(skb_expand_head);
|
||||||
|
|
||||||
|
12
net/core/sock_destructor.h
Normal file
12
net/core/sock_destructor.h
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||||
|
#ifndef _NET_CORE_SOCK_DESTRUCTOR_H
|
||||||
|
#define _NET_CORE_SOCK_DESTRUCTOR_H
|
||||||
|
#include <net/tcp.h>
|
||||||
|
|
||||||
|
static inline bool is_skb_wmem(const struct sk_buff *skb)
|
||||||
|
{
|
||||||
|
return skb->destructor == sock_wfree ||
|
||||||
|
skb->destructor == __sock_wfree ||
|
||||||
|
(IS_ENABLED(CONFIG_INET) && skb->destructor == tcp_wfree);
|
||||||
|
}
|
||||||
|
#endif
|
Loading…
Reference in New Issue
Block a user