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


Reply via email to