skb_frag_unref() and skb_release_data() skip releasing references on managed zerocopy fragments (SKBFL_MANAGED_FRAG_REFS), whereas skb_frag_ref() acquires a reference unconditionally. If skb_frag_ref() is called on a managed zerocopy SKB, the acquired reference is never released by skb_frag_unref() or skb_release_data().
Skip acquiring a reference in skb_frag_ref() when skb_zcopy_managed(skb) is true so skb_frag_ref() matches skb_frag_unref() and skb_release_data(), and disable share_ok in iptfs_output_queued() when skb_zcopy_managed(skb) is true so iptfs_consume_frags() does not append unreferenced fragments into a managed zerocopy outer SKB. Signed-off-by: Mina Almasry <[email protected]> --- include/linux/skbuff_ref.h | 10 +++++++--- net/core/net_test.c | 10 ++++++++++ net/xfrm/xfrm_iptfs.c | 3 ++- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/include/linux/skbuff_ref.h b/include/linux/skbuff_ref.h index b6f6da556ee90..e94c599ce83d2 100644 --- a/include/linux/skbuff_ref.h +++ b/include/linux/skbuff_ref.h @@ -36,12 +36,16 @@ static __always_inline void skb_netmem_ref(netmem_ref netmem, bool recycle) * * Acquires a reference on the @f'th fragment of @skb via skb_netmem_ref() * (pp_ref_count if skb->pp_recycle and page_pool-backed, else non-pp - * backing refcount). Counterpart to skb_frag_unref(). + * backing refcount), skipping managed zerocopy fragments. Counterpart to + * skb_frag_unref(). */ static __always_inline void skb_frag_ref(struct sk_buff *skb, int f) { - skb_netmem_ref(skb_frag_netmem(&skb_shinfo(skb)->frags[f]), - skb->pp_recycle); + struct skb_shared_info *shinfo = skb_shinfo(skb); + + if (!skb_zcopy_managed(skb)) + skb_netmem_ref(skb_frag_netmem(&shinfo->frags[f]), + skb->pp_recycle); } bool napi_pp_put_page(netmem_ref netmem); diff --git a/net/core/net_test.c b/net/core/net_test.c index 2b5080c226636..e20059a00003c 100644 --- a/net/core/net_test.c +++ b/net/core/net_test.c @@ -571,6 +571,16 @@ static void netmem_ref_test_page(struct kunit *test) skb_frag_unref(skb, 0); KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref); + /* SKBFL_MANAGED_FRAG_REFS skips both skb_frag_ref and skb_frag_unref */ + skb_shinfo(skb)->flags |= SKBFL_ZEROCOPY_ENABLE | + SKBFL_MANAGED_FRAG_REFS; + skb_frag_ref(skb, 0); + KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref); + skb_frag_unref(skb, 0); + KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref); + skb_shinfo(skb)->flags &= ~(SKBFL_ZEROCOPY_ENABLE | + SKBFL_MANAGED_FRAG_REFS); + skb_shinfo(skb)->nr_frags = 0; consume_skb(skb); __free_page(page); diff --git a/net/xfrm/xfrm_iptfs.c b/net/xfrm/xfrm_iptfs.c index 103cf7c5dbfb9..04ef833074fc8 100644 --- a/net/xfrm/xfrm_iptfs.c +++ b/net/xfrm/xfrm_iptfs.c @@ -2258,7 +2258,8 @@ static void iptfs_output_queued(struct xfrm_state *x, struct sk_buff_head *list) nextp = &(*nextp)->next; } - if (shi->frag_list || skb_cloned(skb) || skb_shared(skb)) + if (shi->frag_list || skb_cloned(skb) || skb_shared(skb) || + skb_zcopy_managed(skb)) share_ok = false; /* See if we have enough space to simply append. -- 2.56.0.385.gac671c819a-goog
