[PATCH net-next v1 1/5] net: skbuff: skip managed zerocopy frags in skb_frag_ref()
From: Mina Almasry
Date: Tue Nov 03 2026 - 16:16:00 EST
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 <almasrymina@xxxxxxxxxx>
---
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