| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/10/10 08:45 | flow | patch-triage |
5mArgs:null Results: {
"EnableConfigs": [
"PAGE_POOL",
"NET_DEVMEM",
"DMA_SHARED_BUFFER",
"VETH",
"INET",
"IPV6",
"TLS",
"XFRM_IPTFS"
],
"FocusSymbols": [
"napi_pp_get_page",
"napi_pp_put_page",
"get_net_iov",
"put_net_iov",
"skb_zerocopy",
"skb_split",
"skb_shift",
"skb_segment",
"skb_try_coalesce",
"tcp_recvmsg_dmabuf",
"iptfs_skb_add_frags",
"veth_xdp_rcv_skb"
],
"KMSANReasoning": "This patch series refactors SKB (sk_buff) fragment reference counting and page_pool integration, introducing unified helpers (skb_netmem_ref, skb_netmem_unref, skb_frag_ref, skb_frag_unref) that correctly account for page_pool references (pp_ref_count) versus non-page_pool backing references across skb cloning, splitting, coalescing, shifting, and segmentation operations.\n\nThe potential hazards associated with these changes are reference count imbalances leading to use-after-free (UAF) conditions, premature page releases, or memory leaks. These bug classes are covered by KASAN and existing page/slab refcount sanity checks. The patch does not introduce or modify any kernel-to-user copy paths (no info-leak risks), does not allocate or read uninitialized memory buffers, and does not alter branching on uninitialized fields. Therefore, a dedicated KMSAN fuzzing session is not warranted, and standard KASAN fuzzing is sufficient.",
"NeedsKMSAN": false,
"Reasoning": "The patch modifies skb fragment reference counting and page_pool lifecycle handling across core networking (net/core/skbuff.c, include/linux/skbuff_ref.h, include/net/netmem.h) and protocols/drivers (veth, TCP, TLS, XFRM/IP-TFS, ESP). It introduces exported refcount functions (napi_pp_get_page, get_net_iov, put_net_iov), changes how skb-\u003epp_recycle is handled across skb operations (cloning, splitting, coalescing, segmentation, and zerocopy), and adds runtime assertions (DEBUG_NET_WARN_ON_ONCE, WARN_ON_ONCE). These paths are reachable via standard networking syscalls, socket operations, and virtual network devices.",
"WorthFuzzing": true
} |
| 1/1 | 2026/10/10 08:45 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 8bdbb9cbf2c5bd64c459f7ea69df80b6c1c38faf\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Sat Oct 10 08:45:49 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/drivers/net/ethernet/chelsio/inline_crypto/ch_ktls/chcr_ktls.c b/drivers/net/ethernet/chelsio/inline_crypto/ch_ktls/chcr_ktls.c\nindex dbec95b497773..4d645771596a8 100644\n--- a/drivers/net/ethernet/chelsio/inline_crypto/ch_ktls/chcr_ktls.c\n+++ b/drivers/net/ethernet/chelsio/inline_crypto/ch_ktls/chcr_ktls.c\n@@ -1660,7 +1660,7 @@ static void chcr_ktls_copy_record_in_skb(struct sk_buff *nskb,\n \tfor (i = 0; i \u003c record-\u003enum_frags; i++) {\n \t\tskb_shinfo(nskb)-\u003efrags[i] = record-\u003efrags[i];\n \t\t/* increase the frag ref count */\n-\t\t__skb_frag_ref(\u0026skb_shinfo(nskb)-\u003efrags[i]);\n+\t\tskb_frag_ref(nskb, i);\n \t}\n \n \tskb_shinfo(nskb)-\u003enr_frags = record-\u003enum_frags;\ndiff --git a/drivers/net/ethernet/marvell/sky2.c b/drivers/net/ethernet/marvell/sky2.c\nindex 30facdece3e29..1d42a82db8f2b 100644\n--- a/drivers/net/ethernet/marvell/sky2.c\n+++ b/drivers/net/ethernet/marvell/sky2.c\n@@ -2501,7 +2501,7 @@ static void skb_put_frags(struct sk_buff *skb, unsigned int hdr_space,\n \n \t\tif (length == 0) {\n \t\t\t/* don't need this page */\n-\t\t\t__skb_frag_unref(frag, false);\n+\t\t\tskb_frag_unref(skb, i);\n \t\t\t--skb_shinfo(skb)-\u003enr_frags;\n \t\t} else {\n \t\t\tsize = min(length, (unsigned) PAGE_SIZE);\ndiff --git a/drivers/net/ethernet/mellanox/mlx4/en_rx.c b/drivers/net/ethernet/mellanox/mlx4/en_rx.c\nindex 96f97fa0f5489..fe18606c2ab55 100644\n--- a/drivers/net/ethernet/mellanox/mlx4/en_rx.c\n+++ b/drivers/net/ethernet/mellanox/mlx4/en_rx.c\n@@ -497,7 +497,7 @@ static int mlx4_en_complete_rx_desc(struct mlx4_en_priv *priv,\n fail:\n \twhile (nr \u003e 0) {\n \t\tnr--;\n-\t\t__skb_frag_unref(skb_shinfo(skb)-\u003efrags + nr, false);\n+\t\tskb_frag_unref(skb, nr);\n \t}\n \treturn 0;\n }\ndiff --git a/drivers/net/ethernet/sun/cassini.c b/drivers/net/ethernet/sun/cassini.c\nindex 9a8b5cf2d6485..02158a1bb2f70 100644\n--- a/drivers/net/ethernet/sun/cassini.c\n+++ b/drivers/net/ethernet/sun/cassini.c\n@@ -1998,7 +1998,7 @@ static int cas_rx_process_pkt(struct cas *cp, struct cas_rx_comp *rxc,\n \t\tskb-\u003elen += hlen - swivel;\n \n \t\tskb_frag_fill_page_desc(frag, page-\u003ebuffer, off, hlen - swivel);\n-\t\t__skb_frag_ref(frag);\n+\t\tskb_frag_ref(skb, 0);\n \n \t\t/* any more data? */\n \t\tif ((words[0] \u0026 RX_COMP1_SPLIT_PKT) \u0026\u0026 ((dlen -= hlen) \u003e 0)) {\n@@ -2022,7 +2022,7 @@ static int cas_rx_process_pkt(struct cas *cp, struct cas_rx_comp *rxc,\n \t\t\tfrag++;\n \n \t\t\tskb_frag_fill_page_desc(frag, page-\u003ebuffer, 0, hlen);\n-\t\t\t__skb_frag_ref(frag);\n+\t\t\tskb_frag_ref(skb, 1);\n \t\t\tRX_USED_ADD(page, hlen + cp-\u003ecrc_size);\n \t\t}\n \ndiff --git a/drivers/net/veth.c b/drivers/net/veth.c\nindex 643b97dc52456..90a333ebab683 100644\n--- a/drivers/net/veth.c\n+++ b/drivers/net/veth.c\n@@ -746,7 +746,7 @@ static void veth_xdp_get(struct xdp_buff *xdp)\n \t\treturn;\n \n \tfor (i = 0; i \u003c sinfo-\u003enr_frags; i++)\n-\t\t__skb_frag_ref(\u0026sinfo-\u003efrags[i]);\n+\t\tget_netmem(skb_frag_netmem(\u0026sinfo-\u003efrags[i]));\n }\n \n static int veth_convert_skb_to_xdp_buff(struct veth_rq *rq,\ndiff --git a/include/linux/skbuff_ref.h b/include/linux/skbuff_ref.h\nindex 05c8486bafac8..f768a3fa1d606 100644\n--- a/include/linux/skbuff_ref.h\n+++ b/include/linux/skbuff_ref.h\n@@ -9,32 +9,53 @@\n \n #include \u003clinux/skbuff.h\u003e\n \n+bool napi_pp_get_page(netmem_ref netmem);\n+\n /**\n- * __skb_frag_ref - take an addition reference on a paged fragment.\n- * @frag: the paged fragment\n+ * skb_netmem_ref - acquire a fragment reference on a netmem\n+ * @netmem: netmem to reference\n+ * @recycle: whether page_pool recycling is enabled (e.g. skb-\u003epp_recycle)\n *\n- * Takes an additional reference on the paged fragment @frag.\n+ * Acquires pp_ref_count via napi_pp_get_page() if @recycle is true and\n+ * @netmem belongs to a page_pool; otherwise acquires a non-pp backing\n+ * reference via get_netmem(). Counterpart to skb_netmem_unref().\n */\n-static __always_inline void __skb_frag_ref(skb_frag_t *frag)\n+static __always_inline void skb_netmem_ref(netmem_ref netmem, bool recycle)\n {\n-\tget_netmem(skb_frag_netmem(frag));\n+#ifdef CONFIG_PAGE_POOL\n+\tif (recycle \u0026\u0026 napi_pp_get_page(netmem))\n+\t\treturn;\n+#endif\n+\tget_netmem(netmem);\n }\n \n /**\n- * skb_frag_ref - take an addition reference on a paged fragment of an skb.\n+ * skb_frag_ref - acquire a reference on a paged fragment of an skb\n * @skb: the buffer\n- * @f: the fragment offset.\n+ * @f: the fragment offset\n *\n- * Takes an additional reference on the @f'th paged fragment of @skb.\n+ * Acquires a reference on the @f'th fragment of @skb via skb_netmem_ref()\n+ * (pp_ref_count if skb-\u003epp_recycle and page_pool-backed, else non-pp\n+ * backing refcount). Counterpart to skb_frag_unref().\n */\n static __always_inline void skb_frag_ref(struct sk_buff *skb, int f)\n {\n-\t__skb_frag_ref(\u0026skb_shinfo(skb)-\u003efrags[f]);\n+\tskb_netmem_ref(skb_frag_netmem(\u0026skb_shinfo(skb)-\u003efrags[f]),\n+\t\t skb-\u003epp_recycle);\n }\n \n bool napi_pp_put_page(netmem_ref netmem);\n \n-static __always_inline void skb_page_unref(netmem_ref netmem, bool recycle)\n+/**\n+ * skb_netmem_unref - release a fragment reference on a netmem\n+ * @netmem: netmem to unreference\n+ * @recycle: whether page_pool recycling is enabled (e.g. skb-\u003epp_recycle)\n+ *\n+ * Drops pp_ref_count via napi_pp_put_page() if @recycle is true and @netmem\n+ * belongs to a page_pool; otherwise drops a non-pp backing reference via\n+ * put_netmem(). Counterpart to skb_netmem_ref().\n+ */\n+static __always_inline void skb_netmem_unref(netmem_ref netmem, bool recycle)\n {\n #ifdef CONFIG_PAGE_POOL\n \tif (recycle \u0026\u0026 napi_pp_put_page(netmem))\n@@ -44,31 +65,22 @@ static __always_inline void skb_page_unref(netmem_ref netmem, bool recycle)\n }\n \n /**\n- * __skb_frag_unref - release a reference on a paged fragment.\n- * @frag: the paged fragment\n- * @recycle: recycle the page if allocated via page_pool\n- *\n- * Releases a reference on the paged fragment @frag\n- * or recycles the page via the page_pool API.\n- */\n-static __always_inline void __skb_frag_unref(skb_frag_t *frag, bool recycle)\n-{\n-\tskb_page_unref(skb_frag_netmem(frag), recycle);\n-}\n-\n-/**\n- * skb_frag_unref - release a reference on a paged fragment of an skb.\n+ * skb_frag_unref - release a reference on a paged fragment of an skb\n * @skb: the buffer\n * @f: the fragment offset\n *\n- * Releases a reference on the @f'th paged fragment of @skb.\n+ * Drops a reference on the @f'th fragment of @skb via skb_netmem_unref()\n+ * (pp_ref_count if skb-\u003epp_recycle and page_pool-backed, else non-pp\n+ * backing refcount), skipping managed zerocopy fragments. Counterpart to\n+ * skb_frag_ref().\n */\n static __always_inline void skb_frag_unref(struct sk_buff *skb, int f)\n {\n \tstruct skb_shared_info *shinfo = skb_shinfo(skb);\n \n \tif (!skb_zcopy_managed(skb))\n-\t\t__skb_frag_unref(\u0026shinfo-\u003efrags[f], skb-\u003epp_recycle);\n+\t\tskb_netmem_unref(skb_frag_netmem(\u0026shinfo-\u003efrags[f]),\n+\t\t\t\t skb-\u003epp_recycle);\n }\n \n #endif\t/* _LINUX_SKBUFF_REF_H */\ndiff --git a/include/net/netmem.h b/include/net/netmem.h\nindex 0cc8572f62caf..697d50c51a5ee 100644\n--- a/include/net/netmem.h\n+++ b/include/net/netmem.h\n@@ -399,21 +399,37 @@ static inline bool net_is_devmem_iov(const struct net_iov *niov)\n }\n #endif\n \n-void __get_netmem(netmem_ref netmem);\n-void __put_netmem(netmem_ref netmem);\n+void get_net_iov(struct net_iov *niov);\n+void put_net_iov(struct net_iov *niov);\n \n+/**\n+ * get_netmem - acquire a non-page_pool reference on a netmem\n+ * @netmem: netmem to reference\n+ *\n+ * Acquires a non-pp backing reference (page._refcount via get_page() or\n+ * binding-\u003eref via get_net_iov()) regardless of page_pool membership.\n+ * Counterpart to put_netmem().\n+ */\n static __always_inline void get_netmem(netmem_ref netmem)\n {\n \tif (netmem_is_net_iov(netmem))\n-\t\t__get_netmem(netmem);\n+\t\tget_net_iov(netmem_to_net_iov(netmem));\n \telse\n \t\tget_page(netmem_to_page(netmem));\n }\n \n+/**\n+ * put_netmem - release a non-page_pool reference on a netmem\n+ * @netmem: netmem to unreference\n+ *\n+ * Drops a non-pp backing reference (page._refcount via put_page() or\n+ * binding-\u003eref via put_net_iov()) regardless of page_pool membership.\n+ * Counterpart to get_netmem().\n+ */\n static __always_inline void put_netmem(netmem_ref netmem)\n {\n \tif (netmem_is_net_iov(netmem))\n-\t\t__put_netmem(netmem);\n+\t\tput_net_iov(netmem_to_net_iov(netmem));\n \telse\n \t\tput_page(netmem_to_page(netmem));\n }\ndiff --git a/net/core/net_test.c b/net/core/net_test.c\nindex 9c3a590865d26..f9f50f9159902 100644\n--- a/net/core/net_test.c\n+++ b/net/core/net_test.c\n@@ -370,10 +370,757 @@ static void ip_tunnel_flags_test_run(struct kunit *test)\n \tKUNIT_ASSERT_TRUE(test, __ipt_flag_op(bitmap_equal, exp, out));\n }\n \n+/* Netmem \u0026 SKB fragment refcounting */\n+\n+#include \u003clinux/skbuff_ref.h\u003e\n+#include \u003cnet/netmem.h\u003e\n+#include \u003cnet/netdev_rx_queue.h\u003e\n+#include \u003cnet/page_pool/helpers.h\u003e\n+#include \u003cnet/page_pool/memory_provider.h\u003e\n+#include \"devmem.h\"\n+#include \"netmem_priv.h\"\n+\n+static void netmem_ref_test_page(struct kunit *test)\n+{\n+\tstruct sk_buff *skb;\n+\tnetmem_ref netmem;\n+\tstruct page *page;\n+\tint base_ref;\n+\n+\tpage = alloc_page(GFP_KERNEL);\n+\tKUNIT_ASSERT_NOT_NULL(test, page);\n+\tnetmem = page_to_netmem(page);\n+\tbase_ref = page_ref_count(page);\n+\n+\t/* Layer 1: get_netmem / put_netmem use page non-pp refcount */\n+\tget_netmem(netmem);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref + 1);\n+\tput_netmem(netmem);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);\n+\n+#ifdef CONFIG_PAGE_POOL\n+\t/* Layer 2: napi_pp_get/put_page return false on non-PP page */\n+\tKUNIT_EXPECT_FALSE(test, napi_pp_get_page(netmem));\n+\tKUNIT_EXPECT_FALSE(test, napi_pp_put_page(netmem));\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);\n+#endif\n+\n+\t/* Layer 3: skb_netmem_ref / unref fall back to non-pp on non-PP page */\n+\tskb_netmem_ref(netmem, false);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref + 1);\n+\tskb_netmem_unref(netmem, false);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);\n+\n+\tskb_netmem_ref(netmem, true);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref + 1);\n+\tskb_netmem_unref(netmem, true);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);\n+\n+\t/* Layer 4: skb_frag_ref / skb_frag_unref on non-PP page */\n+\tskb = alloc_skb(64, GFP_KERNEL);\n+\tKUNIT_ASSERT_NOT_NULL(test, skb);\n+\tskb_fill_netmem_desc(skb, 0, netmem, 0, 64);\n+\n+\tskb-\u003epp_recycle = 1;\n+\tskb_frag_ref(skb, 0);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref + 1);\n+\tskb_frag_unref(skb, 0);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);\n+\n+\tskb_shinfo(skb)-\u003enr_frags = 0;\n+\tconsume_skb(skb);\n+\t__free_page(page);\n+}\n+\n+#ifdef CONFIG_PAGE_POOL\n+static void netmem_ref_test_pp_page(struct kunit *test)\n+{\n+\tstruct page_pool_params pp_params = {\n+\t\t.order = 0,\n+\t\t.pool_size = 4,\n+\t\t.nid = NUMA_NO_NODE,\n+\t};\n+\tstruct page_pool *pool;\n+\tstruct sk_buff *skb, *copy, *clone, *split;\n+\tnetmem_ref netmem;\n+\tstruct page *page;\n+\tlong base_pp_ref;\n+\tint base_ref;\n+\n+\tpool = page_pool_create(\u0026pp_params);\n+\tKUNIT_ASSERT_NOT_ERR_OR_NULL(test, pool);\n+\n+\tpage = page_pool_dev_alloc_pages(pool);\n+\tKUNIT_ASSERT_NOT_NULL(test, page);\n+\tnetmem = page_to_netmem(page);\n+\tbase_ref = page_ref_count(page);\n+\tbase_pp_ref = atomic_long_read(\u0026page-\u003epp_ref_count);\n+\n+\t/* Layer 1: get_netmem / put_netmem always use non-pp refcount */\n+\tget_netmem(netmem);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref + 1);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref);\n+\tput_netmem(netmem);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);\n+\n+\t/* Layer 2: napi_pp_get/put_page use pp_ref_count */\n+\tKUNIT_EXPECT_TRUE(test, napi_pp_get_page(netmem));\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref + 1);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);\n+\tKUNIT_EXPECT_TRUE(test, napi_pp_put_page(netmem));\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref);\n+\n+\t/* Layer 3: skb_netmem_ref / unref obey recycle flag */\n+\tskb_netmem_ref(netmem, false);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref + 1);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref);\n+\tskb_netmem_unref(netmem, false);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);\n+\n+\tskb_netmem_ref(netmem, true);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref + 1);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);\n+\tskb_netmem_unref(netmem, true);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref);\n+\n+\t/* Layer 4: skb_frag_ref / skb_frag_unref match on pp_recycle=1 \u0026 0 */\n+\tskb = alloc_skb(64, GFP_KERNEL);\n+\tKUNIT_ASSERT_NOT_NULL(test, skb);\n+\tskb_fill_netmem_desc(skb, 0, netmem, 0, 64);\n+\tskb-\u003elen = 64;\n+\tskb-\u003edata_len = 64;\n+\n+\tskb-\u003epp_recycle = 1;\n+\tskb_frag_ref(skb, 0);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref + 1);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);\n+\tskb_frag_unref(skb, 0);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref);\n+\n+\t/* pskb_copy on pp_recycle=1 skb propagates pp_recycle \u0026 pp_ref_count */\n+\tcopy = pskb_copy(skb, GFP_KERNEL);\n+\tKUNIT_ASSERT_NOT_NULL(test, copy);\n+\tKUNIT_EXPECT_TRUE(test, copy-\u003epp_recycle);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref + 1);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);\n+\tconsume_skb(copy);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref);\n+\n+\t/* skb_zerocopy on pp_recycle=1 skb propagates pp_ref_count */\n+\tcopy = alloc_skb(0, GFP_KERNEL);\n+\tKUNIT_ASSERT_NOT_NULL(test, copy);\n+\tskb_put(copy, skb_tailroom(copy));\n+\tskb-\u003ehead_frag = 1;\n+\tKUNIT_ASSERT_EQ(test, skb_zerocopy(copy, skb, skb-\u003elen, 0), 0);\n+\tskb-\u003ehead_frag = 0;\n+\tKUNIT_EXPECT_TRUE(test, copy-\u003epp_recycle);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref + 1);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);\n+\tconsume_skb(copy);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref);\n+\n+\t/* skb_clone + pskb_expand_head preserves pp_recycle and pp_ref_count */\n+\tclone = skb_clone(skb, GFP_KERNEL);\n+\tKUNIT_ASSERT_NOT_NULL(test, clone);\n+\tKUNIT_ASSERT_EQ(test, pskb_expand_head(clone, 32, 0, GFP_KERNEL), 0);\n+\tKUNIT_EXPECT_TRUE(test, clone-\u003epp_recycle);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref + 1);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);\n+\tconsume_skb(clone);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref);\n+\n+\t/* skb_split on page_pool frag propagates pp_recycle \u0026 balances refs */\n+\tsplit = alloc_skb(0, GFP_KERNEL);\n+\tKUNIT_ASSERT_NOT_NULL(test, split);\n+\tskb_split(skb, split, 32);\n+\tKUNIT_EXPECT_TRUE(test, split-\u003epp_recycle);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref + 1);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);\n+\tconsume_skb(split);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref);\n+\n+\t/* skb_segment on pp_recycle=1 skb propagates pp_recycle to segments */\n+\tskb-\u003eprotocol = htons(ETH_P_IP);\n+\tskb_reset_network_header(skb);\n+\tskb_shinfo(skb)-\u003egso_size = 16;\n+\tsplit = skb_segment(skb, NETIF_F_SG | NETIF_F_HW_CSUM);\n+\tKUNIT_ASSERT_FALSE(test, IS_ERR_OR_NULL(split));\n+\tKUNIT_EXPECT_TRUE(test, split-\u003epp_recycle);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref + 2);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);\n+\tkfree_skb_list(split);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref);\n+\n+\t/* skb_shift balances pp_ref_count on split and merge */\n+\tskb_fill_netmem_desc(skb, 0, netmem, 0, 64);\n+\tskb-\u003elen = 64;\n+\tskb-\u003edata_len = 64;\n+\tsplit = alloc_skb(0, GFP_KERNEL);\n+\tKUNIT_ASSERT_NOT_NULL(test, split);\n+\tsplit-\u003epp_recycle = 1;\n+\tKUNIT_EXPECT_EQ(test, skb_shift(split, skb, 16), 16);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref + 1);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);\n+\tKUNIT_EXPECT_EQ(test, skb_shift(split, skb, 48), 48);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref);\n+\tskb_shinfo(split)-\u003enr_frags = 0;\n+\tconsume_skb(split);\n+\tskb_fill_netmem_desc(skb, 0, netmem, 0, 64);\n+\tskb-\u003elen = 64;\n+\tskb-\u003edata_len = 64;\n+\n+\tskb-\u003epp_recycle = 0;\n+\tskb_frag_ref(skb, 0);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref + 1);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref);\n+\tskb_frag_unref(skb, 0);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);\n+\n+\tskb_shinfo(skb)-\u003enr_frags = 0;\n+\tconsume_skb(skb);\n+\tpage_pool_put_full_page(pool, page, false);\n+\tpage_pool_destroy(pool);\n+}\n+#endif\n+\n+#if defined(CONFIG_PAGE_POOL) \u0026\u0026 defined(CONFIG_NET_DEVMEM)\n+static void dummy_percpu_ref_release(struct percpu_ref *ref)\n+{\n+}\n+\n+static void netmem_ref_test_net_iov(struct kunit *test)\n+{\n+\tstruct net_devmem_dmabuf_binding binding = {};\n+\tstruct page_pool_params pp_params = {\n+\t\t.order = 0,\n+\t\t.pool_size = 4,\n+\t\t.nid = NUMA_NO_NODE,\n+\t};\n+\tstruct sk_buff *skb, *clone, *copy;\n+\tstruct page_pool *pool;\n+\tstruct net_iov niov = {};\n+\tnetmem_ref netmem;\n+\tlong base_ref;\n+\tint err;\n+\n+\terr = percpu_ref_init(\u0026binding.ref, dummy_percpu_ref_release,\n+\t\t\t PERCPU_REF_INIT_ATOMIC, GFP_KERNEL);\n+\tKUNIT_ASSERT_EQ(test, err, 0);\n+\n+\tnet_iov_init(\u0026niov, \u0026binding.area, NET_IOV_DMABUF);\n+\tnetmem = net_iov_to_netmem(\u0026niov);\n+\tbase_ref = atomic_long_read(\u0026binding.ref.data-\u003ecount);\n+\n+\t/* Non-PP net_iov: get/put_net_iov \u0026 get/put_netmem */\n+\tget_net_iov(\u0026niov);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026binding.ref.data-\u003ecount),\n+\t\t\tbase_ref + 1);\n+\tput_net_iov(\u0026niov);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026binding.ref.data-\u003ecount),\n+\t\t\tbase_ref);\n+\n+\tKUNIT_EXPECT_FALSE(test, napi_pp_get_page(netmem));\n+\tKUNIT_EXPECT_FALSE(test, napi_pp_put_page(netmem));\n+\n+\tskb_netmem_ref(netmem, true);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026binding.ref.data-\u003ecount),\n+\t\t\tbase_ref + 1);\n+\tskb_netmem_unref(netmem, true);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026binding.ref.data-\u003ecount),\n+\t\t\tbase_ref);\n+\n+\t/* Attach net_iov to a page_pool to test PP net_iov behavior */\n+\tpool = page_pool_create(\u0026pp_params);\n+\tKUNIT_ASSERT_NOT_ERR_OR_NULL(test, pool);\n+\tnetmem_set_pp(netmem, pool);\n+\tnetmem_or_pp_magic(netmem, PP_SIGNATURE);\n+\tatomic_long_set(\u0026niov.desc.pp_ref_count, 2);\n+\n+\t/* Layer 0 \u0026 1: get_net_iov / get_netmem grab non-pp ref even on PP */\n+\tget_net_iov(\u0026niov);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026binding.ref.data-\u003ecount),\n+\t\t\tbase_ref + 1);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026niov.desc.pp_ref_count), 2L);\n+\tput_net_iov(\u0026niov);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026binding.ref.data-\u003ecount),\n+\t\t\tbase_ref);\n+\n+\t/* Layer 2: napi_pp_get/put_page use pp_ref_count */\n+\tKUNIT_EXPECT_TRUE(test, napi_pp_get_page(netmem));\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026niov.desc.pp_ref_count), 3L);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026binding.ref.data-\u003ecount),\n+\t\t\tbase_ref);\n+\tKUNIT_EXPECT_TRUE(test, napi_pp_put_page(netmem));\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026niov.desc.pp_ref_count), 2L);\n+\n+\t/* Layer 4: skb_frag_ref / skb_frag_unref on PP net_iov */\n+\tskb = alloc_skb(64, GFP_KERNEL);\n+\tKUNIT_ASSERT_NOT_NULL(test, skb);\n+\tskb_fill_netmem_desc(skb, 0, netmem, 0, 64);\n+\tskb-\u003elen = 64;\n+\tskb-\u003edata_len = 64;\n+\n+\tskb-\u003epp_recycle = 1;\n+\tskb_frag_ref(skb, 0);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026niov.desc.pp_ref_count), 3L);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026binding.ref.data-\u003ecount),\n+\t\t\tbase_ref);\n+\tskb_frag_unref(skb, 0);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026niov.desc.pp_ref_count), 2L);\n+\n+\t/* pskb_copy on pp_recycle=1 net_iov skb keeps pp_ref_count */\n+\tcopy = pskb_copy(skb, GFP_KERNEL);\n+\tKUNIT_ASSERT_NOT_NULL(test, copy);\n+\tKUNIT_EXPECT_TRUE(test, copy-\u003epp_recycle);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026niov.desc.pp_ref_count), 3L);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026binding.ref.data-\u003ecount),\n+\t\t\tbase_ref);\n+\tconsume_skb(copy);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026niov.desc.pp_ref_count), 2L);\n+\n+\t/* skb_zerocopy on pp_recycle=1 net_iov skb keeps pp_ref_count */\n+\tcopy = alloc_skb(0, GFP_KERNEL);\n+\tKUNIT_ASSERT_NOT_NULL(test, copy);\n+\tskb_put(copy, skb_tailroom(copy));\n+\tskb-\u003ehead_frag = 1;\n+\tskb-\u003eunreadable = 1;\n+\tKUNIT_ASSERT_EQ(test, skb_zerocopy(copy, skb, skb-\u003elen, 0), 0);\n+\tskb-\u003ehead_frag = 0;\n+\tKUNIT_EXPECT_TRUE(test, copy-\u003epp_recycle);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026niov.desc.pp_ref_count), 3L);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026binding.ref.data-\u003ecount),\n+\t\t\tbase_ref);\n+\tconsume_skb(copy);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026niov.desc.pp_ref_count), 2L);\n+\n+\t/* skb_shift balances pp_ref_count on split and merge */\n+\tcopy = alloc_skb(0, GFP_KERNEL);\n+\tKUNIT_ASSERT_NOT_NULL(test, copy);\n+\tcopy-\u003epp_recycle = 1;\n+\tKUNIT_EXPECT_EQ(test, skb_shift(copy, skb, 16), 16);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026niov.desc.pp_ref_count), 3L);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026binding.ref.data-\u003ecount),\n+\t\t\tbase_ref);\n+\tKUNIT_EXPECT_EQ(test, skb_shift(copy, skb, 48), 48);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026niov.desc.pp_ref_count), 2L);\n+\tskb_shinfo(copy)-\u003enr_frags = 0;\n+\tconsume_skb(copy);\n+\tskb_fill_netmem_desc(skb, 0, netmem, 0, 64);\n+\tskb-\u003elen = 64;\n+\tskb-\u003edata_len = 64;\n+\n+\t/* Uncloning a cloned pp_recycle=1 net_iov skb keeps pp_ref_count */\n+\tclone = skb_clone(skb, GFP_KERNEL);\n+\tKUNIT_ASSERT_NOT_NULL(test, clone);\n+\tKUNIT_ASSERT_EQ(test, pskb_expand_head(skb, 32, 0, GFP_KERNEL), 0);\n+\tKUNIT_EXPECT_TRUE(test, skb-\u003epp_recycle);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026niov.desc.pp_ref_count), 3L);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026binding.ref.data-\u003ecount),\n+\t\t\tbase_ref);\n+\tconsume_skb(clone);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026niov.desc.pp_ref_count), 2L);\n+\n+\tskb-\u003epp_recycle = 0;\n+\tskb_frag_ref(skb, 0);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026binding.ref.data-\u003ecount),\n+\t\t\tbase_ref + 1);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026niov.desc.pp_ref_count), 2L);\n+\tskb_frag_unref(skb, 0);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026binding.ref.data-\u003ecount),\n+\t\t\tbase_ref);\n+\n+\tskb_shinfo(skb)-\u003enr_frags = 0;\n+\tconsume_skb(skb);\n+\tnetmem_clear_pp_magic(netmem);\n+\tnetmem_set_pp(netmem, NULL);\n+\tpage_pool_destroy(pool);\n+\tpercpu_ref_exit(\u0026binding.ref);\n+}\n+\n+static void netmem_ref_test_release_data(struct kunit *test)\n+{\n+\tstruct page_pool_params pp_params = {\n+\t\t.order = 0,\n+\t\t.pool_size = 4,\n+\t\t.nid = NUMA_NO_NODE,\n+\t};\n+\tstruct sk_buff *skb, *clone1, *clone2, *ext;\n+\tstruct net_devmem_dmabuf_binding binding = {};\n+\tstruct net_iov niov = {};\n+\tstruct page_pool *pool;\n+\tstruct page *page;\n+\tnetmem_ref netmem;\n+\tlong base_pp_ref;\n+\tint base_ref;\n+\tint i;\n+\n+\tpool = page_pool_create(\u0026pp_params);\n+\tKUNIT_ASSERT_FALSE(test, IS_ERR(pool));\n+\tpage = page_pool_dev_alloc_pages(pool);\n+\tKUNIT_ASSERT_NOT_NULL(test, page);\n+\tnetmem = page_to_netmem(page);\n+\tpage_pool_ref_netmem(netmem);\n+\n+\t/* 3-way clone chain: only last clone drops pp_ref_count */\n+\tskb = alloc_skb(64, GFP_KERNEL);\n+\tKUNIT_ASSERT_NOT_NULL(test, skb);\n+\tskb_put(skb, 32);\n+\tskb-\u003epp_recycle = 1;\n+\tskb_fill_netmem_desc(skb, 0, netmem, 0, 64);\n+\tskb-\u003elen += 64;\n+\tskb-\u003edata_len = 64;\n+\tbase_ref = page_ref_count(page);\n+\tbase_pp_ref = atomic_long_read(\u0026page-\u003epp_ref_count);\n+\n+\tpage_pool_ref_netmem(netmem);\n+\tclone1 = skb_clone(skb, GFP_KERNEL);\n+\tclone2 = skb_clone(clone1, GFP_KERNEL);\n+\tKUNIT_ASSERT_NOT_NULL(test, clone1);\n+\tKUNIT_ASSERT_NOT_NULL(test, clone2);\n+\n+\t/* Freeing head skb first leaves clone1/clone2 pp_recycle=1 intact */\n+\tconsume_skb(skb);\n+\tKUNIT_EXPECT_TRUE(test, clone1-\u003epp_recycle);\n+\tKUNIT_EXPECT_TRUE(test, clone2-\u003epp_recycle);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref + 1);\n+\tconsume_skb(clone2);\n+\tKUNIT_EXPECT_TRUE(test, clone1-\u003epp_recycle);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref + 1);\n+\tconsume_skb(clone1);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref);\n+\n+\t/* All 4 combinations of (unclone skb vs clone) x (free order) */\n+\tfor (i = 0; i \u003c 4; i++) {\n+\t\tskb = alloc_skb(64, GFP_KERNEL);\n+\t\tKUNIT_ASSERT_NOT_NULL(test, skb);\n+\t\tskb_put(skb, 32);\n+\t\tskb-\u003epp_recycle = 1;\n+\t\tskb_fill_netmem_desc(skb, 0, netmem, 0, 64);\n+\t\tskb-\u003elen += 64;\n+\t\tskb-\u003edata_len = 64;\n+\t\tpage_pool_ref_netmem(netmem);\n+\n+\t\tclone1 = skb_clone(skb, GFP_KERNEL);\n+\t\tKUNIT_ASSERT_NOT_NULL(test, clone1);\n+\t\tKUNIT_ASSERT_EQ(test,\n+\t\t\t\tpskb_expand_head((i \u0026 1) ? clone1 : skb,\n+\t\t\t\t\t\t 32, 0, GFP_KERNEL), 0);\n+\t\tKUNIT_EXPECT_TRUE(test, skb-\u003epp_recycle);\n+\t\tKUNIT_EXPECT_TRUE(test, clone1-\u003epp_recycle);\n+\t\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\t\tbase_pp_ref + 2);\n+\t\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);\n+\n+\t\tif (i \u0026 2) {\n+\t\t\tconsume_skb(clone1);\n+\t\t\tKUNIT_EXPECT_EQ(test,\n+\t\t\t\t\tatomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\t\t\tbase_pp_ref + 1);\n+\t\t\tconsume_skb(skb);\n+\t\t} else {\n+\t\t\tconsume_skb(skb);\n+\t\t\tKUNIT_EXPECT_EQ(test,\n+\t\t\t\t\tatomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\t\t\tbase_pp_ref + 1);\n+\t\t\tconsume_skb(clone1);\n+\t\t}\n+\t\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\t\tbase_pp_ref);\n+\t\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);\n+\t}\n+\n+\t/* pskb_extract carving inside header \u0026 inside nonlinear frags */\n+\tskb = alloc_skb(64, GFP_KERNEL);\n+\tKUNIT_ASSERT_NOT_NULL(test, skb);\n+\tskb_put(skb, 32);\n+\tskb-\u003epp_recycle = 1;\n+\tskb_fill_netmem_desc(skb, 0, netmem, 0, 64);\n+\tskb-\u003elen += 64;\n+\tskb-\u003edata_len = 64;\n+\tpage_pool_ref_netmem(netmem);\n+\n+\t/* Carve inside header keeping frag -\u003e pp_ref_count +1 until free */\n+\text = pskb_extract(skb, 16, skb-\u003elen - 16, GFP_KERNEL);\n+\tKUNIT_ASSERT_NOT_NULL(test, ext);\n+\tKUNIT_EXPECT_TRUE(test, ext-\u003epp_recycle);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref + 2);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);\n+\tconsume_skb(ext);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref + 1);\n+\n+\t/* Carve + trim + skb_condense ref/unrefs pp_ref_count cleanly */\n+\text = pskb_extract(skb, 16, 48, GFP_KERNEL);\n+\tKUNIT_ASSERT_NOT_NULL(test, ext);\n+\tKUNIT_EXPECT_TRUE(test, ext-\u003epp_recycle);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref + 1);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);\n+\tconsume_skb(ext);\n+\n+\t/* Carve inside nonlinear keeping frag -\u003e pp_ref_count +1 until free */\n+\text = pskb_extract(skb, 48, skb-\u003elen - 48, GFP_KERNEL);\n+\tKUNIT_ASSERT_NOT_NULL(test, ext);\n+\tKUNIT_EXPECT_TRUE(test, ext-\u003epp_recycle);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref + 2);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);\n+\tconsume_skb(ext);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref + 1);\n+\n+\t/* skb_morph drops old skb data via skb_release_data */\n+\tpage_pool_ref_netmem(netmem);\n+\tclone1 = alloc_skb(64, GFP_KERNEL);\n+\tKUNIT_ASSERT_NOT_NULL(test, clone1);\n+\tclone1-\u003epp_recycle = 1;\n+\tskb_fill_netmem_desc(clone1, 0, netmem, 0, 64);\n+\tclone1-\u003elen = 64;\n+\tclone1-\u003edata_len = 64;\n+\tskb_morph(clone1, skb);\n+\tKUNIT_EXPECT_TRUE(test, clone1-\u003epp_recycle);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref + 1);\n+\tconsume_skb(clone1);\n+\tkfree_skb(skb);\n+\tpage_pool_put_full_page(pool, page, false);\n+\tpage_pool_put_full_page(pool, page, false);\n+\n+\t/* Also verify pskb_extract on page_pool net_iov */\n+\tKUNIT_ASSERT_EQ(test,\n+\t\t\tpercpu_ref_init(\u0026binding.ref, dummy_percpu_ref_release,\n+\t\t\t\t\tPERCPU_REF_INIT_ATOMIC, GFP_KERNEL),\n+\t\t\t0);\n+\tnet_iov_init(\u0026niov, \u0026binding.area, NET_IOV_DMABUF);\n+\tnetmem = net_iov_to_netmem(\u0026niov);\n+\tnetmem_set_pp(netmem, pool);\n+\tnetmem_or_pp_magic(netmem, PP_SIGNATURE);\n+\tatomic_long_set(\u0026niov.desc.pp_ref_count, 2);\n+\n+\tskb = alloc_skb(64, GFP_KERNEL);\n+\tKUNIT_ASSERT_NOT_NULL(test, skb);\n+\tskb_put(skb, 32);\n+\tskb-\u003epp_recycle = 1;\n+\tskb_fill_netmem_desc(skb, 0, netmem, 0, 64);\n+\tskb-\u003elen += 64;\n+\tskb-\u003edata_len = 64;\n+\n+\text = pskb_extract(skb, 48, skb-\u003elen - 48, GFP_KERNEL);\n+\tKUNIT_ASSERT_NOT_NULL(test, ext);\n+\tKUNIT_EXPECT_TRUE(test, ext-\u003epp_recycle);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026niov.desc.pp_ref_count), 3L);\n+\tconsume_skb(ext);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026niov.desc.pp_ref_count), 2L);\n+\n+\tskb_shinfo(skb)-\u003enr_frags = 0;\n+\tconsume_skb(skb);\n+\tnetmem_clear_pp_magic(netmem);\n+\tnetmem_set_pp(netmem, NULL);\n+\tpage_pool_destroy(pool);\n+\tpercpu_ref_exit(\u0026binding.ref);\n+}\n+\n+static void netmem_ref_test_skb_segment(struct kunit *test)\n+{\n+\tstruct page_pool_params pp_params = {\n+\t\t.order = 0,\n+\t\t.pool_size = 4,\n+\t\t.nid = NUMA_NO_NODE,\n+\t};\n+\tstruct sk_buff *head, *list, *segs, *seg;\n+\tstruct net_devmem_dmabuf_binding binding = {};\n+\tstruct net_iov niov = {};\n+\tstruct page *pp_page, *reg_page;\n+\tnetmem_ref pp_nm, niov_nm;\n+\tstruct page_pool *pool;\n+\tlong base_pp_ref, base_iov_ref;\n+\tint base_pp_page_ref, base_reg_ref;\n+\n+\tpool = page_pool_create(\u0026pp_params);\n+\tKUNIT_ASSERT_FALSE(test, IS_ERR(pool));\n+\tpp_page = page_pool_dev_alloc_pages(pool);\n+\tKUNIT_ASSERT_NOT_NULL(test, pp_page);\n+\tpp_nm = page_to_netmem(pp_page);\n+\tpage_pool_ref_netmem(pp_nm);\n+\treg_page = alloc_page(GFP_KERNEL);\n+\tKUNIT_ASSERT_NOT_NULL(test, reg_page);\n+\n+\tKUNIT_ASSERT_EQ(test,\n+\t\t\tpercpu_ref_init(\u0026binding.ref, dummy_percpu_ref_release,\n+\t\t\t\t\tPERCPU_REF_INIT_ATOMIC, GFP_KERNEL),\n+\t\t\t0);\n+\tnet_iov_init(\u0026niov, \u0026binding.area, NET_IOV_DMABUF);\n+\tniov_nm = net_iov_to_netmem(\u0026niov);\n+\tnetmem_set_pp(niov_nm, pool);\n+\tnetmem_or_pp_magic(niov_nm, PP_SIGNATURE);\n+\tatomic_long_set(\u0026niov.desc.pp_ref_count, 2);\n+\n+\tbase_pp_ref = atomic_long_read(\u0026pp_page-\u003epp_ref_count);\n+\tbase_pp_page_ref = page_ref_count(pp_page);\n+\tbase_reg_ref = page_ref_count(reg_page);\n+\tbase_iov_ref = atomic_long_read(\u0026binding.ref.data-\u003ecount);\n+\n+\t/* 1. Single GSO skb with page_pool net_iov frags */\n+\thead = alloc_skb(64, GFP_KERNEL);\n+\tKUNIT_ASSERT_NOT_NULL(test, head);\n+\thead-\u003epp_recycle = 1;\n+\thead-\u003eprotocol = htons(ETH_P_IP);\n+\tskb_reset_mac_header(head);\n+\tskb_reset_network_header(head);\n+\tskb_fill_netmem_desc(head, 0, niov_nm, 0, 64);\n+\thead-\u003elen = 64;\n+\thead-\u003edata_len = 64;\n+\tskb_shinfo(head)-\u003egso_size = 16;\n+\n+\tsegs = skb_segment(head, NETIF_F_SG | NETIF_F_HW_CSUM);\n+\tKUNIT_ASSERT_FALSE(test, IS_ERR_OR_NULL(segs));\n+\tfor (seg = segs; seg; seg = seg-\u003enext)\n+\t\tKUNIT_EXPECT_TRUE(test, seg-\u003epp_recycle);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026niov.desc.pp_ref_count), 6L);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026binding.ref.data-\u003ecount),\n+\t\t\tbase_iov_ref);\n+\tkfree_skb_list(segs);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026niov.desc.pp_ref_count), 2L);\n+\tskb_shinfo(head)-\u003enr_frags = 0;\n+\tconsume_skb(head);\n+\n+\t/* 2. GRO frag_list fast path (skb_clone of pp_recycle=1 list_skb) */\n+\thead = alloc_skb(64, GFP_KERNEL);\n+\tlist = alloc_skb(64, GFP_KERNEL);\n+\tKUNIT_ASSERT_NOT_NULL(test, head);\n+\tKUNIT_ASSERT_NOT_NULL(test, list);\n+\thead-\u003epp_recycle = 1;\n+\tlist-\u003epp_recycle = 1;\n+\thead-\u003eprotocol = htons(ETH_P_IP);\n+\tskb_reset_mac_header(head);\n+\tskb_reset_network_header(head);\n+\tskb_put(head, 16);\n+\tskb_fill_netmem_desc(head, 0, pp_nm, 0, 16);\n+\thead-\u003elen += 16;\n+\thead-\u003edata_len = 16;\n+\tpage_pool_ref_netmem(pp_nm);\n+\n+\tskb_put(list, 16);\n+\tskb_fill_netmem_desc(list, 0, pp_nm, 16, 16);\n+\tlist-\u003elen += 16;\n+\tlist-\u003edata_len = 16;\n+\tpage_pool_ref_netmem(pp_nm);\n+\n+\tskb_shinfo(head)-\u003efrag_list = list;\n+\thead-\u003elen += list-\u003elen;\n+\thead-\u003edata_len += list-\u003elen;\n+\tskb_shinfo(head)-\u003egso_size = 32;\n+\n+\tsegs = skb_segment(head, NETIF_F_SG | NETIF_F_HW_CSUM);\n+\tKUNIT_ASSERT_FALSE(test, IS_ERR_OR_NULL(segs));\n+\tfor (seg = segs; seg; seg = seg-\u003enext)\n+\t\tKUNIT_EXPECT_TRUE(test, seg-\u003epp_recycle);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026pp_page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref + 4);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(pp_page), base_pp_page_ref);\n+\tkfree_skb_list(segs);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026pp_page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref + 2);\n+\tconsume_skb(head);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026pp_page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref);\n+\n+\t/* 3. Mixed GRO frag_list: head pp_page + list reg_page */\n+\thead = alloc_skb(64, GFP_KERNEL);\n+\tlist = alloc_skb(0, GFP_KERNEL);\n+\tKUNIT_ASSERT_NOT_NULL(test, head);\n+\tKUNIT_ASSERT_NOT_NULL(test, list);\n+\thead-\u003epp_recycle = 1;\n+\tlist-\u003epp_recycle = 0;\n+\thead-\u003eprotocol = htons(ETH_P_IP);\n+\tskb_reset_mac_header(head);\n+\tskb_reset_network_header(head);\n+\tskb_fill_netmem_desc(head, 0, pp_nm, 0, 24);\n+\thead-\u003elen = 24;\n+\thead-\u003edata_len = 24;\n+\tpage_pool_ref_netmem(pp_nm);\n+\n+\tskb_fill_page_desc(list, 0, reg_page, 0, 24);\n+\tget_page(reg_page);\n+\tlist-\u003elen = 24;\n+\tlist-\u003edata_len = 24;\n+\n+\tskb_shinfo(head)-\u003efrag_list = list;\n+\thead-\u003elen += list-\u003elen;\n+\thead-\u003edata_len += list-\u003elen;\n+\tskb_shinfo(head)-\u003egso_size = 16;\n+\n+\t/* Segment 0: [pp_page:16], Segment 1: [pp_page:8 + reg_page:8],\n+\t * Segment 2: [reg_page:16]. Freeing all segments restores exact refs!\n+\t */\n+\tsegs = skb_segment(head, NETIF_F_SG | NETIF_F_HW_CSUM);\n+\tKUNIT_ASSERT_FALSE(test, IS_ERR_OR_NULL(segs));\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026pp_page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref + 3);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(pp_page), base_pp_page_ref);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(reg_page), base_reg_ref + 3);\n+\tkfree_skb_list(segs);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026pp_page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref + 1);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(pp_page), base_pp_page_ref);\n+\tKUNIT_EXPECT_EQ(test, page_ref_count(reg_page), base_reg_ref + 1);\n+\n+\tconsume_skb(head);\n+\tKUNIT_EXPECT_EQ(test, atomic_long_read(\u0026pp_page-\u003epp_ref_count),\n+\t\t\tbase_pp_ref);\n+\tpage_pool_put_full_page(pool, pp_page, false);\n+\tpage_pool_put_full_page(pool, pp_page, false);\n+\t__free_page(reg_page);\n+\tnetmem_clear_pp_magic(niov_nm);\n+\tnetmem_set_pp(niov_nm, NULL);\n+\tpage_pool_destroy(pool);\n+\tpercpu_ref_exit(\u0026binding.ref);\n+}\n+#endif\n+\n static struct kunit_case net_test_cases[] = {\n \tKUNIT_CASE_PARAM(gso_test_func, gso_test_gen_params),\n \tKUNIT_CASE_PARAM(ip_tunnel_flags_test_run,\n \t\t\t ip_tunnel_flags_test_gen_params),\n+\tKUNIT_CASE(netmem_ref_test_page),\n+#ifdef CONFIG_PAGE_POOL\n+\tKUNIT_CASE(netmem_ref_test_pp_page),\n+#endif\n+#if defined(CONFIG_PAGE_POOL) \u0026\u0026 defined(CONFIG_NET_DEVMEM)\n+\tKUNIT_CASE(netmem_ref_test_net_iov),\n+\tKUNIT_CASE(netmem_ref_test_release_data),\n+\tKUNIT_CASE(netmem_ref_test_skb_segment),\n+#endif\n \t{ },\n };\n \ndiff --git a/net/core/skbuff.c b/net/core/skbuff.c\nindex d56f4f0102f75..554af794bd7cd 100644\n--- a/net/core/skbuff.c\n+++ b/net/core/skbuff.c\n@@ -1026,6 +1026,15 @@ int skb_cow_data_for_xdp(struct page_pool *pool, struct sk_buff **pskb,\n EXPORT_SYMBOL(skb_cow_data_for_xdp);\n \n #if IS_ENABLED(CONFIG_PAGE_POOL)\n+/**\n+ * napi_pp_put_page - release a page_pool reference on a netmem\n+ * @netmem: netmem to unreference\n+ *\n+ * Drops a page_pool reference (pp_ref_count) on @netmem if it belongs to a\n+ * page_pool. Counterpart to napi_pp_get_page().\n+ *\n+ * Return: true if @netmem belongs to a page_pool, false otherwise.\n+ */\n bool napi_pp_put_page(netmem_ref netmem)\n {\n \tnetmem = netmem_compound_head(netmem);\n@@ -1038,44 +1047,35 @@ bool napi_pp_put_page(netmem_ref netmem)\n \treturn true;\n }\n EXPORT_SYMBOL(napi_pp_put_page);\n-#endif\n-\n-static bool skb_pp_recycle(struct sk_buff *skb, void *data)\n-{\n-\tif (!IS_ENABLED(CONFIG_PAGE_POOL) || !skb-\u003epp_recycle)\n-\t\treturn false;\n-\treturn napi_pp_put_page(page_to_netmem(virt_to_page(data)));\n-}\n \n /**\n- * skb_pp_frag_ref() - Increase fragment references of a page pool aware skb\n- * @skb:\tpage pool aware skb\n- *\n- * Increase the fragment reference count (pp_ref_count) of a skb. This is\n- * intended to gain fragment references only for page pool aware skbs,\n- * i.e. when skb-\u003epp_recycle is true, and not for fragments in a\n- * non-pp-recycling skb. It has a fallback to increase references on normal\n- * pages, as page pool aware skbs may also have normal page fragments.\n+ * napi_pp_get_page - acquire a page_pool reference on a netmem\n+ * @netmem: netmem to reference\n+ *\n+ * Acquires a page_pool reference (pp_ref_count) on @netmem if it belongs to a\n+ * page_pool. Counterpart to napi_pp_put_page().\n+ *\n+ * Return: true if @netmem belongs to a page_pool, false otherwise.\n */\n-static int skb_pp_frag_ref(struct sk_buff *skb)\n+bool napi_pp_get_page(netmem_ref netmem)\n {\n-\tstruct skb_shared_info *shinfo;\n-\tnetmem_ref head_netmem;\n-\tint i;\n+\tnetmem = netmem_compound_head(netmem);\n \n-\tif (!skb-\u003epp_recycle)\n-\t\treturn -EINVAL;\n+\tif (unlikely(!netmem_is_pp(netmem)))\n+\t\treturn false;\n \n-\tshinfo = skb_shinfo(skb);\n+\tpage_pool_ref_netmem(netmem);\n \n-\tfor (i = 0; i \u003c shinfo-\u003enr_frags; i++) {\n-\t\thead_netmem = netmem_compound_head(shinfo-\u003efrags[i].netmem);\n-\t\tif (likely(netmem_is_pp(head_netmem)))\n-\t\t\tpage_pool_ref_netmem(head_netmem);\n-\t\telse\n-\t\t\tpage_ref_inc(netmem_to_page(head_netmem));\n-\t}\n-\treturn 0;\n+\treturn true;\n+}\n+EXPORT_SYMBOL(napi_pp_get_page);\n+#endif\n+\n+static bool skb_pp_recycle(struct sk_buff *skb, void *data)\n+{\n+\tif (!IS_ENABLED(CONFIG_PAGE_POOL) || !skb-\u003epp_recycle)\n+\t\treturn false;\n+\treturn napi_pp_put_page(page_to_netmem(virt_to_page(data)));\n }\n \n static void skb_kfree_head(void *head)\n@@ -1102,7 +1102,7 @@ static void skb_release_data(struct sk_buff *skb, enum skb_drop_reason reason)\n \tint i;\n \n \tif (!skb_data_unref(skb, shinfo))\n-\t\tgoto exit;\n+\t\treturn;\n \n \tif (skb_zcopy(skb)) {\n \t\tbool skip_unref = shinfo-\u003eflags \u0026 SKBFL_MANAGED_FRAG_REFS;\n@@ -1113,24 +1113,14 @@ static void skb_release_data(struct sk_buff *skb, enum skb_drop_reason reason)\n \t}\n \n \tfor (i = 0; i \u003c shinfo-\u003enr_frags; i++)\n-\t\t__skb_frag_unref(\u0026shinfo-\u003efrags[i], skb-\u003epp_recycle);\n+\t\tskb_netmem_unref(skb_frag_netmem(\u0026shinfo-\u003efrags[i]),\n+\t\t\t\t skb-\u003epp_recycle);\n \n free_head:\n \tif (shinfo-\u003efrag_list)\n \t\tkfree_skb_list_reason(shinfo-\u003efrag_list, reason);\n \n \tskb_free_head(skb);\n-exit:\n-\t/* When we clone an SKB we copy the reycling bit. The pp_recycle\n-\t * bit is only set on the head though, so in order to avoid races\n-\t * while trying to recycle fragments on __skb_frag_unref() we need\n-\t * to make one SKB responsible for triggering the recycle path.\n-\t * So disable the recycling bit if an SKB is cloned and we have\n-\t * additional references to the fragmented part of the SKB.\n-\t * Eventually the last SKB will have the recycling bit set and it's\n-\t * dataref set to 0, which will trigger the recycling\n-\t */\n-\tskb-\u003epp_recycle = 0;\n }\n \n /*\n@@ -2255,9 +2245,10 @@ struct sk_buff *__pskb_copy_fclone(struct sk_buff *skb, int headroom,\n \t\t\tn = NULL;\n \t\t\tgoto out;\n \t\t}\n+\t\tn-\u003epp_recycle = skb-\u003epp_recycle;\n \t\tfor (i = 0; i \u003c skb_shinfo(skb)-\u003enr_frags; i++) {\n \t\t\tskb_shinfo(n)-\u003efrags[i] = skb_shinfo(skb)-\u003efrags[i];\n-\t\t\tskb_frag_ref(skb, i);\n+\t\t\tskb_frag_ref(n, i);\n \t\t}\n \t\tskb_shinfo(n)-\u003enr_frags = i;\n \t\tskb_shinfo(n)-\u003eflags |= skb_shinfo(skb)-\u003eflags \u0026 SKBFL_SHARED_FRAG;\n@@ -3895,6 +3886,7 @@ skb_zerocopy(struct sk_buff *to, struct sk_buff *from, int len, int hlen)\n \tif (len \u003c= skb_tailroom(to))\n \t\treturn skb_copy_bits(from, 0, skb_put(to, len), len);\n \n+\tto-\u003epp_recycle = from-\u003epp_recycle;\n \tif (hlen) {\n \t\tret = skb_copy_bits(from, 0, skb_put(to, hlen), hlen);\n \t\tif (unlikely(ret))\n@@ -3907,14 +3899,14 @@ skb_zerocopy(struct sk_buff *to, struct sk_buff *from, int len, int hlen)\n \t\t\toffset = from-\u003edata - (unsigned char *)page_address(page);\n \t\t\t__skb_fill_netmem_desc(to, 0, page_to_netmem(page),\n \t\t\t\t\t offset, plen);\n-\t\t\tget_page(page);\n+\t\t\tskb_frag_ref(to, 0);\n \t\t\tj = 1;\n \t\t\tlen -= plen;\n \t\t}\n \t}\n \n \tif (!skb_frags_readable(from) \u0026\u0026 j \u003e 0 \u0026\u0026 len) {\n-\t\tput_page(page);\n+\t\tskb_frag_unref(to, 0);\n \t\treturn -EFAULT;\n \t}\n \n@@ -3922,7 +3914,7 @@ skb_zerocopy(struct sk_buff *to, struct sk_buff *from, int len, int hlen)\n \n \tif (unlikely(skb_orphan_frags(from, GFP_ATOMIC))) {\n \t\tif (j \u003e 0)\n-\t\t\tput_page(page);\n+\t\t\tskb_frag_unref(to, 0);\n \t\treturn -ENOMEM;\n \t}\n \tskb_zerocopy_clone(to, from, GFP_ATOMIC);\n@@ -4223,7 +4215,7 @@ static inline void skb_split_no_header(struct sk_buff *skb,\n \t\t\t\t * where splitting is expensive.\n \t\t\t\t * 2. Split is accurately. We make this.\n \t\t\t\t */\n-\t\t\t\tskb_frag_ref(skb, i);\n+\t\t\t\tskb_frag_ref(skb1, k);\n \t\t\t\tskb_frag_off_add(\u0026skb_shinfo(skb1)-\u003efrags[0], len - pos);\n \t\t\t\tskb_frag_size_sub(\u0026skb_shinfo(skb1)-\u003efrags[0], len - pos);\n \t\t\t\tskb_frag_size_set(\u0026skb_shinfo(skb)-\u003efrags[i], len - pos);\n@@ -4254,6 +4246,7 @@ void skb_split(struct sk_buff *skb, struct sk_buff *skb1, const u32 len)\n \n \tskb_shinfo(skb1)-\u003eflags |= skb_shinfo(skb)-\u003eflags \u0026 zc_flags;\n \tskb_zerocopy_clone(skb1, skb, 0);\n+\tskb1-\u003epp_recycle = skb-\u003epp_recycle;\n \tif (len \u003c pos)\t/* Split line is inside header. */\n \t\tskb_split_inside_header(skb, skb1, len, pos);\n \telse\t\t/* Second chunk has no header, nothing to copy. */\n@@ -4311,8 +4304,8 @@ int skb_shift(struct sk_buff *tgt, struct sk_buff *skb, int shiftlen)\n \t/* Actual merge is delayed until the point when we know we can\n \t * commit all, so that we don't have to undo partial changes\n \t */\n-\tif (!skb_can_coalesce(tgt, to, skb_frag_page(fragfrom),\n-\t\t\t skb_frag_off(fragfrom))) {\n+\tif (!skb_can_coalesce_netmem(tgt, to, skb_frag_netmem(fragfrom),\n+\t\t\t\t skb_frag_off(fragfrom))) {\n \t\tmerge = -1;\n \t} else {\n \t\tmerge = to - 1;\n@@ -4359,8 +4352,8 @@ int skb_shift(struct sk_buff *tgt, struct sk_buff *skb, int shiftlen)\n \t\t\tto++;\n \n \t\t} else {\n-\t\t\t__skb_frag_ref(fragfrom);\n \t\t\tskb_frag_page_copy(fragto, fragfrom);\n+\t\t\tskb_frag_ref(tgt, to);\n \t\t\tskb_frag_off_copy(fragto, fragfrom);\n \t\t\tskb_frag_size_set(fragto, todo);\n \n@@ -4381,7 +4374,7 @@ int skb_shift(struct sk_buff *tgt, struct sk_buff *skb, int shiftlen)\n \t\tfragto = \u0026skb_shinfo(tgt)-\u003efrags[merge];\n \n \t\tskb_frag_size_add(fragto, skb_frag_size(fragfrom));\n-\t\t__skb_frag_unref(fragfrom, skb-\u003epp_recycle);\n+\t\tskb_frag_unref(skb, 0);\n \t}\n \n \t/* Reposition in the original skb */\n@@ -5055,8 +5048,10 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,\n \t\t\t\tgoto err;\n \t\t\t}\n \n+\t\t\tif (!skb_shinfo(nskb)-\u003enr_frags)\n+\t\t\t\tnskb-\u003epp_recycle = frag_skb-\u003epp_recycle;\n \t\t\t*nskb_frag = (i \u003c 0) ? skb_head_frag_to_page_desc(frag_skb) : *frag;\n-\t\t\t__skb_frag_ref(nskb_frag);\n+\t\t\tskb_frag_ref(nskb, skb_shinfo(nskb)-\u003enr_frags);\n \t\t\tsize = skb_frag_size(nskb_frag);\n \n \t\t\tif (pos \u003c offset) {\n@@ -6269,10 +6264,8 @@ bool skb_try_coalesce(struct sk_buff *to, struct sk_buff *from,\n \t/* if the skb is not cloned this does nothing\n \t * since we set nr_frags to 0.\n \t */\n-\tif (skb_pp_frag_ref(from)) {\n-\t\tfor (i = 0; i \u003c from_shinfo-\u003enr_frags; i++)\n-\t\t\t__skb_frag_ref(\u0026from_shinfo-\u003efrags[i]);\n-\t}\n+\tfor (i = 0; i \u003c from_shinfo-\u003enr_frags; i++)\n+\t\tskb_frag_ref(from, i);\n \n \tto-\u003etruesize += delta;\n \tto-\u003elen += len;\n@@ -7551,23 +7544,39 @@ bool csum_and_copy_from_iter_full(void *addr, size_t bytes,\n }\n EXPORT_SYMBOL(csum_and_copy_from_iter_full);\n \n-void __get_netmem(netmem_ref netmem)\n+/**\n+ * get_net_iov - acquire a non-page_pool reference on a net_iov\n+ * @niov: net_iov to reference\n+ *\n+ * Acquires a non-pp backing reference (binding-\u003eref) on @niov regardless of\n+ * page_pool membership, warning if @niov does not support non-pp references.\n+ * Counterpart to put_net_iov().\n+ */\n+void get_net_iov(struct net_iov *niov)\n {\n-\tstruct net_iov *niov = netmem_to_net_iov(netmem);\n-\n \tif (net_is_devmem_iov(niov))\n-\t\tnet_devmem_get_net_iov(netmem_to_net_iov(netmem));\n+\t\tnet_devmem_get_net_iov(niov);\n+\telse\n+\t\tDEBUG_NET_WARN_ON_ONCE(true);\n }\n-EXPORT_SYMBOL(__get_netmem);\n+EXPORT_SYMBOL(get_net_iov);\n \n-void __put_netmem(netmem_ref netmem)\n+/**\n+ * put_net_iov - release a non-page_pool reference on a net_iov\n+ * @niov: net_iov to unreference\n+ *\n+ * Drops a non-pp backing reference (binding-\u003eref) on @niov regardless of\n+ * page_pool membership, warning if @niov does not support non-pp references.\n+ * Counterpart to get_net_iov().\n+ */\n+void put_net_iov(struct net_iov *niov)\n {\n-\tstruct net_iov *niov = netmem_to_net_iov(netmem);\n-\n \tif (net_is_devmem_iov(niov))\n-\t\tnet_devmem_put_net_iov(netmem_to_net_iov(netmem));\n+\t\tnet_devmem_put_net_iov(niov);\n+\telse\n+\t\tDEBUG_NET_WARN_ON_ONCE(true);\n }\n-EXPORT_SYMBOL(__put_netmem);\n+EXPORT_SYMBOL(put_net_iov);\n \n struct vlan_type_depth __vlan_get_protocol_offset(const struct sk_buff *skb,\n \t\t\t\t\t\t __be16 type,\ndiff --git a/net/ipv4/esp4.c b/net/ipv4/esp4.c\nindex e76db5817e78e..ae701561d9f12 100644\n--- a/net/ipv4/esp4.c\n+++ b/net/ipv4/esp4.c\n@@ -117,8 +117,8 @@ static void esp_ssg_unref(struct xfrm_state *x, void *tmp, struct sk_buff *skb,\n \t\tstruct scatterlist *src = already_unref ? esp_req_sg(aead, req) : req-\u003esrc;\n \n \t\tfor (sg = sg_next(src); sg; sg = sg_next(sg))\n-\t\t\tskb_page_unref(page_to_netmem(sg_page(sg)),\n-\t\t\t\t skb-\u003epp_recycle);\n+\t\t\tskb_netmem_unref(page_to_netmem(sg_page(sg)),\n+\t\t\t\t\t skb-\u003epp_recycle);\n \t}\n }\n \ndiff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c\nindex f4c9101e5996a..99d7237a623d8 100644\n--- a/net/ipv4/tcp.c\n+++ b/net/ipv4/tcp.c\n@@ -254,6 +254,7 @@\n #include \u003clinux/init.h\u003e\n #include \u003clinux/fs.h\u003e\n #include \u003clinux/skbuff.h\u003e\n+#include \u003clinux/skbuff_ref.h\u003e\n #include \u003clinux/splice.h\u003e\n #include \u003clinux/net.h\u003e\n #include \u003clinux/socket.h\u003e\n@@ -2559,6 +2560,7 @@ static int tcp_recvmsg_dmabuf(struct sock *sk, const struct sk_buff *skb,\n \t\t */\n \t\tfor (i = 0; i \u003c skb_shinfo(skb)-\u003enr_frags; i++) {\n \t\t\tskb_frag_t *frag = \u0026skb_shinfo(skb)-\u003efrags[i];\n+\t\t\tnetmem_ref netmem = skb_frag_netmem(frag);\n \t\t\tstruct net_iov *niov;\n \t\t\tu64 frag_offset;\n \t\t\tint end;\n@@ -2612,8 +2614,8 @@ static int tcp_recvmsg_dmabuf(struct sock *sk, const struct sk_buff *skb,\n \t\t\t\tif (err)\n \t\t\t\t\tgoto out;\n \n-\t\t\t\tatomic_long_inc(\u0026niov-\u003edesc.pp_ref_count);\n-\t\t\t\ttcp_xa_pool.netmems[tcp_xa_pool.idx++] = skb_frag_netmem(frag);\n+\t\t\t\tWARN_ON_ONCE(!napi_pp_get_page(netmem));\n+\t\t\t\ttcp_xa_pool.netmems[tcp_xa_pool.idx++] = netmem;\n \n \t\t\t\tsent += copy;\n \ndiff --git a/net/ipv6/esp6.c b/net/ipv6/esp6.c\nindex b1c9b36f76dc4..d654f0f442388 100644\n--- a/net/ipv6/esp6.c\n+++ b/net/ipv6/esp6.c\n@@ -134,8 +134,8 @@ static void esp_ssg_unref(struct xfrm_state *x, void *tmp, struct sk_buff *skb,\n \t\tstruct scatterlist *src = already_unref ? esp_req_sg(aead, req) : req-\u003esrc;\n \n \t\tfor (sg = sg_next(src); sg; sg = sg_next(sg))\n-\t\t\tskb_page_unref(page_to_netmem(sg_page(sg)),\n-\t\t\t\t skb-\u003epp_recycle);\n+\t\t\tskb_netmem_unref(page_to_netmem(sg_page(sg)),\n+\t\t\t\t\t skb-\u003epp_recycle);\n \t}\n }\n \ndiff --git a/net/tls/tls_device.c b/net/tls/tls_device.c\nindex f11d0528fc431..b333a9a72cdae 100644\n--- a/net/tls/tls_device.c\n+++ b/net/tls/tls_device.c\n@@ -143,7 +143,7 @@ static void destroy_record(struct tls_record_info *record)\n \tint i;\n \n \tfor (i = 0; i \u003c record-\u003enum_frags; i++)\n-\t\t__skb_frag_unref(\u0026record-\u003efrags[i], false);\n+\t\tput_netmem(skb_frag_netmem(\u0026record-\u003efrags[i]));\n \tkfree(record);\n }\n \ndiff --git a/net/tls/tls_device_fallback.c b/net/tls/tls_device_fallback.c\nindex 3b7d0ab2bcf17..35cc510db4321 100644\n--- a/net/tls/tls_device_fallback.c\n+++ b/net/tls/tls_device_fallback.c\n@@ -256,7 +256,7 @@ static int fill_sg_in(struct scatterlist *sg_in,\n \tfor (i = 0; remaining \u003e 0; i++) {\n \t\tskb_frag_t *frag = \u0026record-\u003efrags[i];\n \n-\t\t__skb_frag_ref(frag);\n+\t\tget_netmem(skb_frag_netmem(frag));\n \t\tsg_set_page(sg_in + i, skb_frag_page(frag),\n \t\t\t skb_frag_size(frag), skb_frag_off(frag));\n \ndiff --git a/net/tls/tls_strp.c b/net/tls/tls_strp.c\nindex 6cc222008d95c..53d3256d0114a 100644\n--- a/net/tls/tls_strp.c\n+++ b/net/tls/tls_strp.c\n@@ -197,7 +197,7 @@ static void tls_strp_flush_anchor_copy(struct tls_strparser *strp)\n \tDEBUG_NET_WARN_ON_ONCE(atomic_read(\u0026shinfo-\u003edataref) != 1);\n \n \tfor (i = 0; i \u003c shinfo-\u003enr_frags; i++)\n-\t\t__skb_frag_unref(\u0026shinfo-\u003efrags[i], false);\n+\t\tskb_frag_unref(strp-\u003eanchor, i);\n \tshinfo-\u003enr_frags = 0;\n \tif (strp-\u003ecopy_mode) {\n \t\tkfree_skb_list(shinfo-\u003efrag_list);\ndiff --git a/net/xfrm/xfrm_iptfs.c b/net/xfrm/xfrm_iptfs.c\nindex 6920940a35b49..103cf7c5dbfb9 100644\n--- a/net/xfrm/xfrm_iptfs.c\n+++ b/net/xfrm/xfrm_iptfs.c\n@@ -486,8 +486,7 @@ static int iptfs_skb_add_frags(struct sk_buff *skb,\n \t\t\ttofrag-\u003elen -= offset;\n \t\t\toffset = 0;\n \t\t}\n-\t\t__skb_frag_ref(tofrag);\n-\t\tshinfo-\u003enr_frags++;\n+\t\tskb_frag_ref(skb, shinfo-\u003enr_frags++);\n \t\tshinfo-\u003eflags |= SKBFL_SHARED_FRAG;\n \n \t\t/* see if we are done */\n"
} |
| 2/1 | 2026/10/10 08:45 | agent | patch-evaluator |
4mModel:core Args: null Results: {
"EnableConfigs": [
"PAGE_POOL",
"NET_DEVMEM",
"DMA_SHARED_BUFFER",
"VETH",
"INET",
"IPV6",
"TLS",
"XFRM_IPTFS"
],
"FocusSymbols": [
"napi_pp_get_page",
"napi_pp_put_page",
"get_net_iov",
"put_net_iov",
"skb_zerocopy",
"skb_split",
"skb_shift",
"skb_segment",
"skb_try_coalesce",
"tcp_recvmsg_dmabuf",
"iptfs_skb_add_frags",
"veth_xdp_rcv_skb"
],
"Reasoning": "The patch modifies skb fragment reference counting and page_pool lifecycle handling across core networking (net/core/skbuff.c, include/linux/skbuff_ref.h, include/net/netmem.h) and protocols/drivers (veth, TCP, TLS, XFRM/IP-TFS, ESP). It introduces exported refcount functions (napi_pp_get_page, get_net_iov, put_net_iov), changes how skb-\u003epp_recycle is handled across skb operations (cloning, splitting, coalescing, segmentation, and zerocopy), and adds runtime assertions (DEBUG_NET_WARN_ON_ONCE, WARN_ON_ONCE). These paths are reachable via standard networking syscalls, socket operations, and virtual network devices.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.
IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.
================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.
- REACHABILITY IS THE PRIMARY GATE:
Fuzzing can only discover bugs in code that can actually execute in standard virtualized
environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
regardless of whether it adds assertions or complex logic.
- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
Patch authors routinely label changes as "cleanups", "refactorings", or state
"No functional change intended". Do NOT take these claims at face value.
Code refactorings that rearrange logic, introduce helper functions, or alter state management
in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
If reachable executable code is modified or refactored, it MUST be fuzzed.
- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
Even if the author believes the invariant always holds, fuzzing is essential to verify whether
an unusual sequence of operations can violate it.
================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:
- Non-kernel and non-executable changes:
* Modifications to Documentation/, comments, or spelling fixes.
* User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
that do not affect the compiled kernel image (vmlinux) or kernel modules.
* Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
that do not alter control flow or data structures.
* Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
* Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
* Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
physical PCI hot-unplug or manual sysfs driver unbinding.
* Code paths exclusive to architectures other than the target architecture.
================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
* Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
* Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
* Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
* Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).
================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:
- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
Do NOT list generic, ubiquitous functions called by almost every program in the corpus
(including, but not limited to: general memory allocators and deallocators, page fault
and trap handlers, or core synchronization primitives; this is not an exhaustive list).
Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
which severely dilutes fuzzing effort away from the actual changes.
- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
List functions that specifically implement the logic being added or altered, or direct API entrypoints
for the subsystem feature under review.
- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).
================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit 8bdbb9cbf2c5bd64c459f7ea69df80b6c1c38faf
Author: syz-cluster <triage@syzkaller.com>
Date: Sat Oct 10 08:45:49 2026 +0000
syz-cluster: applied patch under review
diff --git a/drivers/net/ethernet/chelsio/inline_crypto/ch_ktls/chcr_ktls.c b/drivers/net/ethernet/chelsio/inline_crypto/ch_ktls/chcr_ktls.c
index dbec95b497773..4d645771596a8 100644
--- a/drivers/net/ethernet/chelsio/inline_crypto/ch_ktls/chcr_ktls.c
+++ b/drivers/net/ethernet/chelsio/inline_crypto/ch_ktls/chcr_ktls.c
@@ -1660,7 +1660,7 @@ static void chcr_ktls_copy_record_in_skb(struct sk_buff *nskb,
for (i = 0; i < record->num_frags; i++) {
skb_shinfo(nskb)->frags[i] = record->frags[i];
/* increase the frag ref count */
- __skb_frag_ref(&skb_shinfo(nskb)->frags[i]);
+ skb_frag_ref(nskb, i);
}
skb_shinfo(nskb)->nr_frags = record->num_frags;
diff --git a/drivers/net/ethernet/marvell/sky2.c b/drivers/net/ethernet/marvell/sky2.c
index 30facdece3e29..1d42a82db8f2b 100644
--- a/drivers/net/ethernet/marvell/sky2.c
+++ b/drivers/net/ethernet/marvell/sky2.c
@@ -2501,7 +2501,7 @@ static void skb_put_frags(struct sk_buff *skb, unsigned int hdr_space,
if (length == 0) {
/* don't need this page */
- __skb_frag_unref(frag, false);
+ skb_frag_unref(skb, i);
--skb_shinfo(skb)->nr_frags;
} else {
size = min(length, (unsigned) PAGE_SIZE);
diff --git a/drivers/net/ethernet/mellanox/mlx4/en_rx.c b/drivers/net/ethernet/mellanox/mlx4/en_rx.c
index 96f97fa0f5489..fe18606c2ab55 100644
--- a/drivers/net/ethernet/mellanox/mlx4/en_rx.c
+++ b/drivers/net/ethernet/mellanox/mlx4/en_rx.c
@@ -497,7 +497,7 @@ static int mlx4_en_complete_rx_desc(struct mlx4_en_priv *priv,
fail:
while (nr > 0) {
nr--;
- __skb_frag_unref(skb_shinfo(skb)->frags + nr, false);
+ skb_frag_unref(skb, nr);
}
return 0;
}
diff --git a/drivers/net/ethernet/sun/cassini.c b/drivers/net/ethernet/sun/cassini.c
index 9a8b5cf2d6485..02158a1bb2f70 100644
--- a/drivers/net/ethernet/sun/cassini.c
+++ b/drivers/net/ethernet/sun/cassini.c
@@ -1998,7 +1998,7 @@ static int cas_rx_process_pkt(struct cas *cp, struct cas_rx_comp *rxc,
skb->len += hlen - swivel;
skb_frag_fill_page_desc(frag, page->buffer, off, hlen - swivel);
- __skb_frag_ref(frag);
+ skb_frag_ref(skb, 0);
/* any more data? */
if ((words[0] & RX_COMP1_SPLIT_PKT) && ((dlen -= hlen) > 0)) {
@@ -2022,7 +2022,7 @@ static int cas_rx_process_pkt(struct cas *cp, struct cas_rx_comp *rxc,
frag++;
skb_frag_fill_page_desc(frag, page->buffer, 0, hlen);
- __skb_frag_ref(frag);
+ skb_frag_ref(skb, 1);
RX_USED_ADD(page, hlen + cp->crc_size);
}
diff --git a/drivers/net/veth.c b/drivers/net/veth.c
index 643b97dc52456..90a333ebab683 100644
--- a/drivers/net/veth.c
+++ b/drivers/net/veth.c
@@ -746,7 +746,7 @@ static void veth_xdp_get(struct xdp_buff *xdp)
return;
for (i = 0; i < sinfo->nr_frags; i++)
- __skb_frag_ref(&sinfo->frags[i]);
+ get_netmem(skb_frag_netmem(&sinfo->frags[i]));
}
static int veth_convert_skb_to_xdp_buff(struct veth_rq *rq,
diff --git a/include/linux/skbuff_ref.h b/include/linux/skbuff_ref.h
index 05c8486bafac8..f768a3fa1d606 100644
--- a/include/linux/skbuff_ref.h
+++ b/include/linux/skbuff_ref.h
@@ -9,32 +9,53 @@
#include <linux/skbuff.h>
+bool napi_pp_get_page(netmem_ref netmem);
+
/**
- * __skb_frag_ref - take an addition reference on a paged fragment.
- * @frag: the paged fragment
+ * skb_netmem_ref - acquire a fragment reference on a netmem
+ * @netmem: netmem to reference
+ * @recycle: whether page_pool recycling is enabled (e.g. skb->pp_recycle)
*
- * Takes an additional reference on the paged fragment @frag.
+ * Acquires pp_ref_count via napi_pp_get_page() if @recycle is true and
+ * @netmem belongs to a page_pool; otherwise acquires a non-pp backing
+ * reference via get_netmem(). Counterpart to skb_netmem_unref().
*/
-static __always_inline void __skb_frag_ref(skb_frag_t *frag)
+static __always_inline void skb_netmem_ref(netmem_ref netmem, bool recycle)
{
- get_netmem(skb_frag_netmem(frag));
+#ifdef CONFIG_PAGE_POOL
+ if (recycle && napi_pp_get_page(netmem))
+ return;
+#endif
+ get_netmem(netmem);
}
/**
- * skb_frag_ref - take an addition reference on a paged fragment of an skb.
+ * skb_frag_ref - acquire a reference on a paged fragment of an skb
* @skb: the buffer
- * @f: the fragment offset.
+ * @f: the fragment offset
*
- * Takes an additional reference on the @f'th paged fragment of @skb.
+ * 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().
*/
static __always_inline void skb_frag_ref(struct sk_buff *skb, int f)
{
- __skb_frag_ref(&skb_shinfo(skb)->frags[f]);
+ skb_netmem_ref(skb_frag_netmem(&skb_shinfo(skb)->frags[f]),
+ skb->pp_recycle);
}
bool napi_pp_put_page(netmem_ref netmem);
-static __always_inline void skb_page_unref(netmem_ref netmem, bool recycle)
+/**
+ * skb_netmem_unref - release a fragment reference on a netmem
+ * @netmem: netmem to unreference
+ * @recycle: whether page_pool recycling is enabled (e.g. skb->pp_recycle)
+ *
+ * Drops pp_ref_count via napi_pp_put_page() if @recycle is true and @netmem
+ * belongs to a page_pool; otherwise drops a non-pp backing reference via
+ * put_netmem(). Counterpart to skb_netmem_ref().
+ */
+static __always_inline void skb_netmem_unref(netmem_ref netmem, bool recycle)
{
#ifdef CONFIG_PAGE_POOL
if (recycle && napi_pp_put_page(netmem))
@@ -44,31 +65,22 @@ static __always_inline void skb_page_unref(netmem_ref netmem, bool recycle)
}
/**
- * __skb_frag_unref - release a reference on a paged fragment.
- * @frag: the paged fragment
- * @recycle: recycle the page if allocated via page_pool
- *
- * Releases a reference on the paged fragment @frag
- * or recycles the page via the page_pool API.
- */
-static __always_inline void __skb_frag_unref(skb_frag_t *frag, bool recycle)
-{
- skb_page_unref(skb_frag_netmem(frag), recycle);
-}
-
-/**
- * skb_frag_unref - release a reference on a paged fragment of an skb.
+ * skb_frag_unref - release a reference on a paged fragment of an skb
* @skb: the buffer
* @f: the fragment offset
*
- * Releases a reference on the @f'th paged fragment of @skb.
+ * Drops a reference on the @f'th fragment of @skb via skb_netmem_unref()
+ * (pp_ref_count if skb->pp_recycle and page_pool-backed, else non-pp
+ * backing refcount), skipping managed zerocopy fragments. Counterpart to
+ * skb_frag_ref().
*/
static __always_inline void skb_frag_unref(struct sk_buff *skb, int f)
{
struct skb_shared_info *shinfo = skb_shinfo(skb);
if (!skb_zcopy_managed(skb))
- __skb_frag_unref(&shinfo->frags[f], skb->pp_recycle);
+ skb_netmem_unref(skb_frag_netmem(&shinfo->frags[f]),
+ skb->pp_recycle);
}
#endif /* _LINUX_SKBUFF_REF_H */
diff --git a/include/net/netmem.h b/include/net/netmem.h
index 0cc8572f62caf..697d50c51a5ee 100644
--- a/include/net/netmem.h
+++ b/include/net/netmem.h
@@ -399,21 +399,37 @@ static inline bool net_is_devmem_iov(const struct net_iov *niov)
}
#endif
-void __get_netmem(netmem_ref netmem);
-void __put_netmem(netmem_ref netmem);
+void get_net_iov(struct net_iov *niov);
+void put_net_iov(struct net_iov *niov);
+/**
+ * get_netmem - acquire a non-page_pool reference on a netmem
+ * @netmem: netmem to reference
+ *
+ * Acquires a non-pp backing reference (page._refcount via get_page() or
+ * binding->ref via get_net_iov()) regardless of page_pool membership.
+ * Counterpart to put_netmem().
+ */
static __always_inline void get_netmem(netmem_ref netmem)
{
if (netmem_is_net_iov(netmem))
- __get_netmem(netmem);
+ get_net_iov(netmem_to_net_iov(netmem));
else
get_page(netmem_to_page(netmem));
}
+/**
+ * put_netmem - release a non-page_pool reference on a netmem
+ * @netmem: netmem to unreference
+ *
+ * Drops a non-pp backing reference (page._refcount via put_page() or
+ * binding->ref via put_net_iov()) regardless of page_pool membership.
+ * Counterpart to get_netmem().
+ */
static __always_inline void put_netmem(netmem_ref netmem)
{
if (netmem_is_net_iov(netmem))
- __put_netmem(netmem);
+ put_net_iov(netmem_to_net_iov(netmem));
else
put_page(netmem_to_page(netmem));
}
diff --git a/net/core/net_test.c b/net/core/net_test.c
index 9c3a590865d26..f9f50f9159902 100644
--- a/net/core/net_test.c
+++ b/net/core/net_test.c
@@ -370,10 +370,757 @@ static void ip_tunnel_flags_test_run(struct kunit *test)
KUNIT_ASSERT_TRUE(test, __ipt_flag_op(bitmap_equal, exp, out));
}
+/* Netmem & SKB fragment refcounting */
+
+#include <linux/skbuff_ref.h>
+#include <net/netmem.h>
+#include <net/netdev_rx_queue.h>
+#include <net/page_pool/helpers.h>
+#include <net/page_pool/memory_provider.h>
+#include "devmem.h"
+#include "netmem_priv.h"
+
+static void netmem_ref_test_page(struct kunit *test)
+{
+ struct sk_buff *skb;
+ netmem_ref netmem;
+ struct page *page;
+ int base_ref;
+
+ page = alloc_page(GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, page);
+ netmem = page_to_netmem(page);
+ base_ref = page_ref_count(page);
+
+ /* Layer 1: get_netmem / put_netmem use page non-pp refcount */
+ get_netmem(netmem);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref + 1);
+ put_netmem(netmem);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+
+#ifdef CONFIG_PAGE_POOL
+ /* Layer 2: napi_pp_get/put_page return false on non-PP page */
+ KUNIT_EXPECT_FALSE(test, napi_pp_get_page(netmem));
+ KUNIT_EXPECT_FALSE(test, napi_pp_put_page(netmem));
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+#endif
+
+ /* Layer 3: skb_netmem_ref / unref fall back to non-pp on non-PP page */
+ skb_netmem_ref(netmem, false);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref + 1);
+ skb_netmem_unref(netmem, false);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+
+ skb_netmem_ref(netmem, true);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref + 1);
+ skb_netmem_unref(netmem, true);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+
+ /* Layer 4: skb_frag_ref / skb_frag_unref on non-PP page */
+ skb = alloc_skb(64, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, skb);
+ skb_fill_netmem_desc(skb, 0, netmem, 0, 64);
+
+ skb->pp_recycle = 1;
+ skb_frag_ref(skb, 0);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref + 1);
+ skb_frag_unref(skb, 0);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+
+ skb_shinfo(skb)->nr_frags = 0;
+ consume_skb(skb);
+ __free_page(page);
+}
+
+#ifdef CONFIG_PAGE_POOL
+static void netmem_ref_test_pp_page(struct kunit *test)
+{
+ struct page_pool_params pp_params = {
+ .order = 0,
+ .pool_size = 4,
+ .nid = NUMA_NO_NODE,
+ };
+ struct page_pool *pool;
+ struct sk_buff *skb, *copy, *clone, *split;
+ netmem_ref netmem;
+ struct page *page;
+ long base_pp_ref;
+ int base_ref;
+
+ pool = page_pool_create(&pp_params);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pool);
+
+ page = page_pool_dev_alloc_pages(pool);
+ KUNIT_ASSERT_NOT_NULL(test, page);
+ netmem = page_to_netmem(page);
+ base_ref = page_ref_count(page);
+ base_pp_ref = atomic_long_read(&page->pp_ref_count);
+
+ /* Layer 1: get_netmem / put_netmem always use non-pp refcount */
+ get_netmem(netmem);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref + 1);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref);
+ put_netmem(netmem);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+
+ /* Layer 2: napi_pp_get/put_page use pp_ref_count */
+ KUNIT_EXPECT_TRUE(test, napi_pp_get_page(netmem));
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ KUNIT_EXPECT_TRUE(test, napi_pp_put_page(netmem));
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref);
+
+ /* Layer 3: skb_netmem_ref / unref obey recycle flag */
+ skb_netmem_ref(netmem, false);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref + 1);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref);
+ skb_netmem_unref(netmem, false);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+
+ skb_netmem_ref(netmem, true);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ skb_netmem_unref(netmem, true);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref);
+
+ /* Layer 4: skb_frag_ref / skb_frag_unref match on pp_recycle=1 & 0 */
+ skb = alloc_skb(64, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, skb);
+ skb_fill_netmem_desc(skb, 0, netmem, 0, 64);
+ skb->len = 64;
+ skb->data_len = 64;
+
+ skb->pp_recycle = 1;
+ skb_frag_ref(skb, 0);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ skb_frag_unref(skb, 0);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref);
+
+ /* pskb_copy on pp_recycle=1 skb propagates pp_recycle & pp_ref_count */
+ copy = pskb_copy(skb, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, copy);
+ KUNIT_EXPECT_TRUE(test, copy->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ consume_skb(copy);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref);
+
+ /* skb_zerocopy on pp_recycle=1 skb propagates pp_ref_count */
+ copy = alloc_skb(0, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, copy);
+ skb_put(copy, skb_tailroom(copy));
+ skb->head_frag = 1;
+ KUNIT_ASSERT_EQ(test, skb_zerocopy(copy, skb, skb->len, 0), 0);
+ skb->head_frag = 0;
+ KUNIT_EXPECT_TRUE(test, copy->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ consume_skb(copy);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref);
+
+ /* skb_clone + pskb_expand_head preserves pp_recycle and pp_ref_count */
+ clone = skb_clone(skb, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, clone);
+ KUNIT_ASSERT_EQ(test, pskb_expand_head(clone, 32, 0, GFP_KERNEL), 0);
+ KUNIT_EXPECT_TRUE(test, clone->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ consume_skb(clone);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref);
+
+ /* skb_split on page_pool frag propagates pp_recycle & balances refs */
+ split = alloc_skb(0, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, split);
+ skb_split(skb, split, 32);
+ KUNIT_EXPECT_TRUE(test, split->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ consume_skb(split);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref);
+
+ /* skb_segment on pp_recycle=1 skb propagates pp_recycle to segments */
+ skb->protocol = htons(ETH_P_IP);
+ skb_reset_network_header(skb);
+ skb_shinfo(skb)->gso_size = 16;
+ split = skb_segment(skb, NETIF_F_SG | NETIF_F_HW_CSUM);
+ KUNIT_ASSERT_FALSE(test, IS_ERR_OR_NULL(split));
+ KUNIT_EXPECT_TRUE(test, split->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 2);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ kfree_skb_list(split);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref);
+
+ /* skb_shift balances pp_ref_count on split and merge */
+ skb_fill_netmem_desc(skb, 0, netmem, 0, 64);
+ skb->len = 64;
+ skb->data_len = 64;
+ split = alloc_skb(0, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, split);
+ split->pp_recycle = 1;
+ KUNIT_EXPECT_EQ(test, skb_shift(split, skb, 16), 16);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ KUNIT_EXPECT_EQ(test, skb_shift(split, skb, 48), 48);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref);
+ skb_shinfo(split)->nr_frags = 0;
+ consume_skb(split);
+ skb_fill_netmem_desc(skb, 0, netmem, 0, 64);
+ skb->len = 64;
+ skb->data_len = 64;
+
+ skb->pp_recycle = 0;
+ skb_frag_ref(skb, 0);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref + 1);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref);
+ skb_frag_unref(skb, 0);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+
+ skb_shinfo(skb)->nr_frags = 0;
+ consume_skb(skb);
+ page_pool_put_full_page(pool, page, false);
+ page_pool_destroy(pool);
+}
+#endif
+
+#if defined(CONFIG_PAGE_POOL) && defined(CONFIG_NET_DEVMEM)
+static void dummy_percpu_ref_release(struct percpu_ref *ref)
+{
+}
+
+static void netmem_ref_test_net_iov(struct kunit *test)
+{
+ struct net_devmem_dmabuf_binding binding = {};
+ struct page_pool_params pp_params = {
+ .order = 0,
+ .pool_size = 4,
+ .nid = NUMA_NO_NODE,
+ };
+ struct sk_buff *skb, *clone, *copy;
+ struct page_pool *pool;
+ struct net_iov niov = {};
+ netmem_ref netmem;
+ long base_ref;
+ int err;
+
+ err = percpu_ref_init(&binding.ref, dummy_percpu_ref_release,
+ PERCPU_REF_INIT_ATOMIC, GFP_KERNEL);
+ KUNIT_ASSERT_EQ(test, err, 0);
+
+ net_iov_init(&niov, &binding.area, NET_IOV_DMABUF);
+ netmem = net_iov_to_netmem(&niov);
+ base_ref = atomic_long_read(&binding.ref.data->count);
+
+ /* Non-PP net_iov: get/put_net_iov & get/put_netmem */
+ get_net_iov(&niov);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&binding.ref.data->count),
+ base_ref + 1);
+ put_net_iov(&niov);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&binding.ref.data->count),
+ base_ref);
+
+ KUNIT_EXPECT_FALSE(test, napi_pp_get_page(netmem));
+ KUNIT_EXPECT_FALSE(test, napi_pp_put_page(netmem));
+
+ skb_netmem_ref(netmem, true);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&binding.ref.data->count),
+ base_ref + 1);
+ skb_netmem_unref(netmem, true);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&binding.ref.data->count),
+ base_ref);
+
+ /* Attach net_iov to a page_pool to test PP net_iov behavior */
+ pool = page_pool_create(&pp_params);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pool);
+ netmem_set_pp(netmem, pool);
+ netmem_or_pp_magic(netmem, PP_SIGNATURE);
+ atomic_long_set(&niov.desc.pp_ref_count, 2);
+
+ /* Layer 0 & 1: get_net_iov / get_netmem grab non-pp ref even on PP */
+ get_net_iov(&niov);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&binding.ref.data->count),
+ base_ref + 1);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 2L);
+ put_net_iov(&niov);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&binding.ref.data->count),
+ base_ref);
+
+ /* Layer 2: napi_pp_get/put_page use pp_ref_count */
+ KUNIT_EXPECT_TRUE(test, napi_pp_get_page(netmem));
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 3L);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&binding.ref.data->count),
+ base_ref);
+ KUNIT_EXPECT_TRUE(test, napi_pp_put_page(netmem));
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 2L);
+
+ /* Layer 4: skb_frag_ref / skb_frag_unref on PP net_iov */
+ skb = alloc_skb(64, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, skb);
+ skb_fill_netmem_desc(skb, 0, netmem, 0, 64);
+ skb->len = 64;
+ skb->data_len = 64;
+
+ skb->pp_recycle = 1;
+ skb_frag_ref(skb, 0);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 3L);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&binding.ref.data->count),
+ base_ref);
+ skb_frag_unref(skb, 0);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 2L);
+
+ /* pskb_copy on pp_recycle=1 net_iov skb keeps pp_ref_count */
+ copy = pskb_copy(skb, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, copy);
+ KUNIT_EXPECT_TRUE(test, copy->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 3L);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&binding.ref.data->count),
+ base_ref);
+ consume_skb(copy);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 2L);
+
+ /* skb_zerocopy on pp_recycle=1 net_iov skb keeps pp_ref_count */
+ copy = alloc_skb(0, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, copy);
+ skb_put(copy, skb_tailroom(copy));
+ skb->head_frag = 1;
+ skb->unreadable = 1;
+ KUNIT_ASSERT_EQ(test, skb_zerocopy(copy, skb, skb->len, 0), 0);
+ skb->head_frag = 0;
+ KUNIT_EXPECT_TRUE(test, copy->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 3L);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&binding.ref.data->count),
+ base_ref);
+ consume_skb(copy);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 2L);
+
+ /* skb_shift balances pp_ref_count on split and merge */
+ copy = alloc_skb(0, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, copy);
+ copy->pp_recycle = 1;
+ KUNIT_EXPECT_EQ(test, skb_shift(copy, skb, 16), 16);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 3L);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&binding.ref.data->count),
+ base_ref);
+ KUNIT_EXPECT_EQ(test, skb_shift(copy, skb, 48), 48);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 2L);
+ skb_shinfo(copy)->nr_frags = 0;
+ consume_skb(copy);
+ skb_fill_netmem_desc(skb, 0, netmem, 0, 64);
+ skb->len = 64;
+ skb->data_len = 64;
+
+ /* Uncloning a cloned pp_recycle=1 net_iov skb keeps pp_ref_count */
+ clone = skb_clone(skb, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, clone);
+ KUNIT_ASSERT_EQ(test, pskb_expand_head(skb, 32, 0, GFP_KERNEL), 0);
+ KUNIT_EXPECT_TRUE(test, skb->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 3L);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&binding.ref.data->count),
+ base_ref);
+ consume_skb(clone);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 2L);
+
+ skb->pp_recycle = 0;
+ skb_frag_ref(skb, 0);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&binding.ref.data->count),
+ base_ref + 1);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 2L);
+ skb_frag_unref(skb, 0);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&binding.ref.data->count),
+ base_ref);
+
+ skb_shinfo(skb)->nr_frags = 0;
+ consume_skb(skb);
+ netmem_clear_pp_magic(netmem);
+ netmem_set_pp(netmem, NULL);
+ page_pool_destroy(pool);
+ percpu_ref_exit(&binding.ref);
+}
+
+static void netmem_ref_test_release_data(struct kunit *test)
+{
+ struct page_pool_params pp_params = {
+ .order = 0,
+ .pool_size = 4,
+ .nid = NUMA_NO_NODE,
+ };
+ struct sk_buff *skb, *clone1, *clone2, *ext;
+ struct net_devmem_dmabuf_binding binding = {};
+ struct net_iov niov = {};
+ struct page_pool *pool;
+ struct page *page;
+ netmem_ref netmem;
+ long base_pp_ref;
+ int base_ref;
+ int i;
+
+ pool = page_pool_create(&pp_params);
+ KUNIT_ASSERT_FALSE(test, IS_ERR(pool));
+ page = page_pool_dev_alloc_pages(pool);
+ KUNIT_ASSERT_NOT_NULL(test, page);
+ netmem = page_to_netmem(page);
+ page_pool_ref_netmem(netmem);
+
+ /* 3-way clone chain: only last clone drops pp_ref_count */
+ skb = alloc_skb(64, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, skb);
+ skb_put(skb, 32);
+ skb->pp_recycle = 1;
+ skb_fill_netmem_desc(skb, 0, netmem, 0, 64);
+ skb->len += 64;
+ skb->data_len = 64;
+ base_ref = page_ref_count(page);
+ base_pp_ref = atomic_long_read(&page->pp_ref_count);
+
+ page_pool_ref_netmem(netmem);
+ clone1 = skb_clone(skb, GFP_KERNEL);
+ clone2 = skb_clone(clone1, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, clone1);
+ KUNIT_ASSERT_NOT_NULL(test, clone2);
+
+ /* Freeing head skb first leaves clone1/clone2 pp_recycle=1 intact */
+ consume_skb(skb);
+ KUNIT_EXPECT_TRUE(test, clone1->pp_recycle);
+ KUNIT_EXPECT_TRUE(test, clone2->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+ consume_skb(clone2);
+ KUNIT_EXPECT_TRUE(test, clone1->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+ consume_skb(clone1);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref);
+
+ /* All 4 combinations of (unclone skb vs clone) x (free order) */
+ for (i = 0; i < 4; i++) {
+ skb = alloc_skb(64, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, skb);
+ skb_put(skb, 32);
+ skb->pp_recycle = 1;
+ skb_fill_netmem_desc(skb, 0, netmem, 0, 64);
+ skb->len += 64;
+ skb->data_len = 64;
+ page_pool_ref_netmem(netmem);
+
+ clone1 = skb_clone(skb, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, clone1);
+ KUNIT_ASSERT_EQ(test,
+ pskb_expand_head((i & 1) ? clone1 : skb,
+ 32, 0, GFP_KERNEL), 0);
+ KUNIT_EXPECT_TRUE(test, skb->pp_recycle);
+ KUNIT_EXPECT_TRUE(test, clone1->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 2);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+
+ if (i & 2) {
+ consume_skb(clone1);
+ KUNIT_EXPECT_EQ(test,
+ atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+ consume_skb(skb);
+ } else {
+ consume_skb(skb);
+ KUNIT_EXPECT_EQ(test,
+ atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+ consume_skb(clone1);
+ }
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ }
+
+ /* pskb_extract carving inside header & inside nonlinear frags */
+ skb = alloc_skb(64, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, skb);
+ skb_put(skb, 32);
+ skb->pp_recycle = 1;
+ skb_fill_netmem_desc(skb, 0, netmem, 0, 64);
+ skb->len += 64;
+ skb->data_len = 64;
+ page_pool_ref_netmem(netmem);
+
+ /* Carve inside header keeping frag -> pp_ref_count +1 until free */
+ ext = pskb_extract(skb, 16, skb->len - 16, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ext);
+ KUNIT_EXPECT_TRUE(test, ext->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 2);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ consume_skb(ext);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+
+ /* Carve + trim + skb_condense ref/unrefs pp_ref_count cleanly */
+ ext = pskb_extract(skb, 16, 48, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ext);
+ KUNIT_EXPECT_TRUE(test, ext->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ consume_skb(ext);
+
+ /* Carve inside nonlinear keeping frag -> pp_ref_count +1 until free */
+ ext = pskb_extract(skb, 48, skb->len - 48, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ext);
+ KUNIT_EXPECT_TRUE(test, ext->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 2);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ consume_skb(ext);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+
+ /* skb_morph drops old skb data via skb_release_data */
+ page_pool_ref_netmem(netmem);
+ clone1 = alloc_skb(64, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, clone1);
+ clone1->pp_recycle = 1;
+ skb_fill_netmem_desc(clone1, 0, netmem, 0, 64);
+ clone1->len = 64;
+ clone1->data_len = 64;
+ skb_morph(clone1, skb);
+ KUNIT_EXPECT_TRUE(test, clone1->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+ consume_skb(clone1);
+ kfree_skb(skb);
+ page_pool_put_full_page(pool, page, false);
+ page_pool_put_full_page(pool, page, false);
+
+ /* Also verify pskb_extract on page_pool net_iov */
+ KUNIT_ASSERT_EQ(test,
+ percpu_ref_init(&binding.ref, dummy_percpu_ref_release,
+ PERCPU_REF_INIT_ATOMIC, GFP_KERNEL),
+ 0);
+ net_iov_init(&niov, &binding.area, NET_IOV_DMABUF);
+ netmem = net_iov_to_netmem(&niov);
+ netmem_set_pp(netmem, pool);
+ netmem_or_pp_magic(netmem, PP_SIGNATURE);
+ atomic_long_set(&niov.desc.pp_ref_count, 2);
+
+ skb = alloc_skb(64, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, skb);
+ skb_put(skb, 32);
+ skb->pp_recycle = 1;
+ skb_fill_netmem_desc(skb, 0, netmem, 0, 64);
+ skb->len += 64;
+ skb->data_len = 64;
+
+ ext = pskb_extract(skb, 48, skb->len - 48, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ext);
+ KUNIT_EXPECT_TRUE(test, ext->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 3L);
+ consume_skb(ext);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 2L);
+
+ skb_shinfo(skb)->nr_frags = 0;
+ consume_skb(skb);
+ netmem_clear_pp_magic(netmem);
+ netmem_set_pp(netmem, NULL);
+ page_pool_destroy(pool);
+ percpu_ref_exit(&binding.ref);
+}
+
+static void netmem_ref_test_skb_segment(struct kunit *test)
+{
+ struct page_pool_params pp_params = {
+ .order = 0,
+ .pool_size = 4,
+ .nid = NUMA_NO_NODE,
+ };
+ struct sk_buff *head, *list, *segs, *seg;
+ struct net_devmem_dmabuf_binding binding = {};
+ struct net_iov niov = {};
+ struct page *pp_page, *reg_page;
+ netmem_ref pp_nm, niov_nm;
+ struct page_pool *pool;
+ long base_pp_ref, base_iov_ref;
+ int base_pp_page_ref, base_reg_ref;
+
+ pool = page_pool_create(&pp_params);
+ KUNIT_ASSERT_FALSE(test, IS_ERR(pool));
+ pp_page = page_pool_dev_alloc_pages(pool);
+ KUNIT_ASSERT_NOT_NULL(test, pp_page);
+ pp_nm = page_to_netmem(pp_page);
+ page_pool_ref_netmem(pp_nm);
+ reg_page = alloc_page(GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, reg_page);
+
+ KUNIT_ASSERT_EQ(test,
+ percpu_ref_init(&binding.ref, dummy_percpu_ref_release,
+ PERCPU_REF_INIT_ATOMIC, GFP_KERNEL),
+ 0);
+ net_iov_init(&niov, &binding.area, NET_IOV_DMABUF);
+ niov_nm = net_iov_to_netmem(&niov);
+ netmem_set_pp(niov_nm, pool);
+ netmem_or_pp_magic(niov_nm, PP_SIGNATURE);
+ atomic_long_set(&niov.desc.pp_ref_count, 2);
+
+ base_pp_ref = atomic_long_read(&pp_page->pp_ref_count);
+ base_pp_page_ref = page_ref_count(pp_page);
+ base_reg_ref = page_ref_count(reg_page);
+ base_iov_ref = atomic_long_read(&binding.ref.data->count);
+
+ /* 1. Single GSO skb with page_pool net_iov frags */
+ head = alloc_skb(64, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, head);
+ head->pp_recycle = 1;
+ head->protocol = htons(ETH_P_IP);
+ skb_reset_mac_header(head);
+ skb_reset_network_header(head);
+ skb_fill_netmem_desc(head, 0, niov_nm, 0, 64);
+ head->len = 64;
+ head->data_len = 64;
+ skb_shinfo(head)->gso_size = 16;
+
+ segs = skb_segment(head, NETIF_F_SG | NETIF_F_HW_CSUM);
+ KUNIT_ASSERT_FALSE(test, IS_ERR_OR_NULL(segs));
+ for (seg = segs; seg; seg = seg->next)
+ KUNIT_EXPECT_TRUE(test, seg->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 6L);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&binding.ref.data->count),
+ base_iov_ref);
+ kfree_skb_list(segs);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 2L);
+ skb_shinfo(head)->nr_frags = 0;
+ consume_skb(head);
+
+ /* 2. GRO frag_list fast path (skb_clone of pp_recycle=1 list_skb) */
+ head = alloc_skb(64, GFP_KERNEL);
+ list = alloc_skb(64, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, head);
+ KUNIT_ASSERT_NOT_NULL(test, list);
+ head->pp_recycle = 1;
+ list->pp_recycle = 1;
+ head->protocol = htons(ETH_P_IP);
+ skb_reset_mac_header(head);
+ skb_reset_network_header(head);
+ skb_put(head, 16);
+ skb_fill_netmem_desc(head, 0, pp_nm, 0, 16);
+ head->len += 16;
+ head->data_len = 16;
+ page_pool_ref_netmem(pp_nm);
+
+ skb_put(list, 16);
+ skb_fill_netmem_desc(list, 0, pp_nm, 16, 16);
+ list->len += 16;
+ list->data_len = 16;
+ page_pool_ref_netmem(pp_nm);
+
+ skb_shinfo(head)->frag_list = list;
+ head->len += list->len;
+ head->data_len += list->len;
+ skb_shinfo(head)->gso_size = 32;
+
+ segs = skb_segment(head, NETIF_F_SG | NETIF_F_HW_CSUM);
+ KUNIT_ASSERT_FALSE(test, IS_ERR_OR_NULL(segs));
+ for (seg = segs; seg; seg = seg->next)
+ KUNIT_EXPECT_TRUE(test, seg->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&pp_page->pp_ref_count),
+ base_pp_ref + 4);
+ KUNIT_EXPECT_EQ(test, page_ref_count(pp_page), base_pp_page_ref);
+ kfree_skb_list(segs);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&pp_page->pp_ref_count),
+ base_pp_ref + 2);
+ consume_skb(head);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&pp_page->pp_ref_count),
+ base_pp_ref);
+
+ /* 3. Mixed GRO frag_list: head pp_page + list reg_page */
+ head = alloc_skb(64, GFP_KERNEL);
+ list = alloc_skb(0, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, head);
+ KUNIT_ASSERT_NOT_NULL(test, list);
+ head->pp_recycle = 1;
+ list->pp_recycle = 0;
+ head->protocol = htons(ETH_P_IP);
+ skb_reset_mac_header(head);
+ skb_reset_network_header(head);
+ skb_fill_netmem_desc(head, 0, pp_nm, 0, 24);
+ head->len = 24;
+ head->data_len = 24;
+ page_pool_ref_netmem(pp_nm);
+
+ skb_fill_page_desc(list, 0, reg_page, 0, 24);
+ get_page(reg_page);
+ list->len = 24;
+ list->data_len = 24;
+
+ skb_shinfo(head)->frag_list = list;
+ head->len += list->len;
+ head->data_len += list->len;
+ skb_shinfo(head)->gso_size = 16;
+
+ /* Segment 0: [pp_page:16], Segment 1: [pp_page:8 + reg_page:8],
+ * Segment 2: [reg_page:16]. Freeing all segments restores exact refs!
+ */
+ segs = skb_segment(head, NETIF_F_SG | NETIF_F_HW_CSUM);
+ KUNIT_ASSERT_FALSE(test, IS_ERR_OR_NULL(segs));
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&pp_page->pp_ref_count),
+ base_pp_ref + 3);
+ KUNIT_EXPECT_EQ(test, page_ref_count(pp_page), base_pp_page_ref);
+ KUNIT_EXPECT_EQ(test, page_ref_count(reg_page), base_reg_ref + 3);
+ kfree_skb_list(segs);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&pp_page->pp_ref_count),
+ base_pp_ref + 1);
+ KUNIT_EXPECT_EQ(test, page_ref_count(pp_page), base_pp_page_ref);
+ KUNIT_EXPECT_EQ(test, page_ref_count(reg_page), base_reg_ref + 1);
+
+ consume_skb(head);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&pp_page->pp_ref_count),
+ base_pp_ref);
+ page_pool_put_full_page(pool, pp_page, false);
+ page_pool_put_full_page(pool, pp_page, false);
+ __free_page(reg_page);
+ netmem_clear_pp_magic(niov_nm);
+ netmem_set_pp(niov_nm, NULL);
+ page_pool_destroy(pool);
+ percpu_ref_exit(&binding.ref);
+}
+#endif
+
static struct kunit_case net_test_cases[] = {
KUNIT_CASE_PARAM(gso_test_func, gso_test_gen_params),
KUNIT_CASE_PARAM(ip_tunnel_flags_test_run,
ip_tunnel_flags_test_gen_params),
+ KUNIT_CASE(netmem_ref_test_page),
+#ifdef CONFIG_PAGE_POOL
+ KUNIT_CASE(netmem_ref_test_pp_page),
+#endif
+#if defined(CONFIG_PAGE_POOL) && defined(CONFIG_NET_DEVMEM)
+ KUNIT_CASE(netmem_ref_test_net_iov),
+ KUNIT_CASE(netmem_ref_test_release_data),
+ KUNIT_CASE(netmem_ref_test_skb_segment),
+#endif
{ },
};
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index d56f4f0102f75..554af794bd7cd 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -1026,6 +1026,15 @@ int skb_cow_data_for_xdp(struct page_pool *pool, struct sk_buff **pskb,
EXPORT_SYMBOL(skb_cow_data_for_xdp);
#if IS_ENABLED(CONFIG_PAGE_POOL)
+/**
+ * napi_pp_put_page - release a page_pool reference on a netmem
+ * @netmem: netmem to unreference
+ *
+ * Drops a page_pool reference (pp_ref_count) on @netmem if it belongs to a
+ * page_pool. Counterpart to napi_pp_get_page().
+ *
+ * Return: true if @netmem belongs to a page_pool, false otherwise.
+ */
bool napi_pp_put_page(netmem_ref netmem)
{
netmem = netmem_compound_head(netmem);
@@ -1038,44 +1047,35 @@ bool napi_pp_put_page(netmem_ref netmem)
return true;
}
EXPORT_SYMBOL(napi_pp_put_page);
-#endif
-
-static bool skb_pp_recycle(struct sk_buff *skb, void *data)
-{
- if (!IS_ENABLED(CONFIG_PAGE_POOL) || !skb->pp_recycle)
- return false;
- return napi_pp_put_page(page_to_netmem(virt_to_page(data)));
-}
/**
- * skb_pp_frag_ref() - Increase fragment references of a page pool aware skb
- * @skb: page pool aware skb
- *
- * Increase the fragment reference count (pp_ref_count) of a skb. This is
- * intended to gain fragment references only for page pool aware skbs,
- * i.e. when skb->pp_recycle is true, and not for fragments in a
- * non-pp-recycling skb. It has a fallback to increase references on normal
- * pages, as page pool aware skbs may also have normal page fragments.
+ * napi_pp_get_page - acquire a page_pool reference on a netmem
+ * @netmem: netmem to reference
+ *
+ * Acquires a page_pool reference (pp_ref_count) on @netmem if it belongs to a
+ * page_pool. Counterpart to napi_pp_put_page().
+ *
+ * Return: true if @netmem belongs to a page_pool, false otherwise.
*/
-static int skb_pp_frag_ref(struct sk_buff *skb)
+bool napi_pp_get_page(netmem_ref netmem)
{
- struct skb_shared_info *shinfo;
- netmem_ref head_netmem;
- int i;
+ netmem = netmem_compound_head(netmem);
- if (!skb->pp_recycle)
- return -EINVAL;
+ if (unlikely(!netmem_is_pp(netmem)))
+ return false;
- shinfo = skb_shinfo(skb);
+ page_pool_ref_netmem(netmem);
- for (i = 0; i < shinfo->nr_frags; i++) {
- head_netmem = netmem_compound_head(shinfo->frags[i].netmem);
- if (likely(netmem_is_pp(head_netmem)))
- page_pool_ref_netmem(head_netmem);
- else
- page_ref_inc(netmem_to_page(head_netmem));
- }
- return 0;
+ return true;
+}
+EXPORT_SYMBOL(napi_pp_get_page);
+#endif
+
+static bool skb_pp_recycle(struct sk_buff *skb, void *data)
+{
+ if (!IS_ENABLED(CONFIG_PAGE_POOL) || !skb->pp_recycle)
+ return false;
+ return napi_pp_put_page(page_to_netmem(virt_to_page(data)));
}
static void skb_kfree_head(void *head)
@@ -1102,7 +1102,7 @@ static void skb_release_data(struct sk_buff *skb, enum skb_drop_reason reason)
int i;
if (!skb_data_unref(skb, shinfo))
- goto exit;
+ return;
if (skb_zcopy(skb)) {
bool skip_unref = shinfo->flags & SKBFL_MANAGED_FRAG_REFS;
@@ -1113,24 +1113,14 @@ static void skb_release_data(struct sk_buff *skb, enum skb_drop_reason reason)
}
for (i = 0; i < shinfo->nr_frags; i++)
- __skb_frag_unref(&shinfo->frags[i], skb->pp_recycle);
+ skb_netmem_unref(skb_frag_netmem(&shinfo->frags[i]),
+ skb->pp_recycle);
free_head:
if (shinfo->frag_list)
kfree_skb_list_reason(shinfo->frag_list, reason);
skb_free_head(skb);
-exit:
- /* When we clone an SKB we copy the reycling bit. The pp_recycle
- * bit is only set on the head though, so in order to avoid races
- * while trying to recycle fragments on __skb_frag_unref() we need
- * to make one SKB responsible for triggering the recycle path.
- * So disable the recycling bit if an SKB is cloned and we have
- * additional references to the fragmented part of the SKB.
- * Eventually the last SKB will have the recycling bit set and it's
- * dataref set to 0, which will trigger the recycling
- */
- skb->pp_recycle = 0;
}
/*
@@ -2255,9 +2245,10 @@ struct sk_buff *__pskb_copy_fclone(struct sk_buff *skb, int headroom,
n = NULL;
goto out;
}
+ n->pp_recycle = skb->pp_recycle;
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
skb_shinfo(n)->frags[i] = skb_shinfo(skb)->frags[i];
- skb_frag_ref(skb, i);
+ skb_frag_ref(n, i);
}
skb_shinfo(n)->nr_frags = i;
skb_shinfo(n)->flags |= skb_shinfo(skb)->flags & SKBFL_SHARED_FRAG;
@@ -3895,6 +3886,7 @@ skb_zerocopy(struct sk_buff *to, struct sk_buff *from, int len, int hlen)
if (len <= skb_tailroom(to))
return skb_copy_bits(from, 0, skb_put(to, len), len);
+ to->pp_recycle = from->pp_recycle;
if (hlen) {
ret = skb_copy_bits(from, 0, skb_put(to, hlen), hlen);
if (unlikely(ret))
@@ -3907,14 +3899,14 @@ skb_zerocopy(struct sk_buff *to, struct sk_buff *from, int len, int hlen)
offset = from->data - (unsigned char *)page_address(page);
__skb_fill_netmem_desc(to, 0, page_to_netmem(page),
offset, plen);
- get_page(page);
+ skb_frag_ref(to, 0);
j = 1;
len -= plen;
}
}
if (!skb_frags_readable(from) && j > 0 && len) {
- put_page(page);
+ skb_frag_unref(to, 0);
return -EFAULT;
}
@@ -3922,7 +3914,7 @@ skb_zerocopy(struct sk_buff *to, struct sk_buff *from, int len, int hlen)
if (unlikely(skb_orphan_frags(from, GFP_ATOMIC))) {
if (j > 0)
- put_page(page);
+ skb_frag_unref(to, 0);
return -ENOMEM;
}
skb_zerocopy_clone(to, from, GFP_ATOMIC);
@@ -4223,7 +4215,7 @@ static inline void skb_split_no_header(struct sk_buff *skb,
* where splitting is expensive.
* 2. Split is accurately. We make this.
*/
- skb_frag_ref(skb, i);
+ skb_frag_ref(skb1, k);
skb_frag_off_add(&skb_shinfo(skb1)->frags[0], len - pos);
skb_frag_size_sub(&skb_shinfo(skb1)->frags[0], len - pos);
skb_frag_size_set(&skb_shinfo(skb)->frags[i], len - pos);
@@ -4254,6 +4246,7 @@ void skb_split(struct sk_buff *skb, struct sk_buff *skb1, const u32 len)
skb_shinfo(skb1)->flags |= skb_shinfo(skb)->flags & zc_flags;
skb_zerocopy_clone(skb1, skb, 0);
+ skb1->pp_recycle = skb->pp_recycle;
if (len < pos) /* Split line is inside header. */
skb_split_inside_header(skb, skb1, len, pos);
else /* Second chunk has no header, nothing to copy. */
@@ -4311,8 +4304,8 @@ int skb_shift(struct sk_buff *tgt, struct sk_buff *skb, int shiftlen)
/* Actual merge is delayed until the point when we know we can
* commit all, so that we don't have to undo partial changes
*/
- if (!skb_can_coalesce(tgt, to, skb_frag_page(fragfrom),
- skb_frag_off(fragfrom))) {
+ if (!skb_can_coalesce_netmem(tgt, to, skb_frag_netmem(fragfrom),
+ skb_frag_off(fragfrom))) {
merge = -1;
} else {
merge = to - 1;
@@ -4359,8 +4352,8 @@ int skb_shift(struct sk_buff *tgt, struct sk_buff *skb, int shiftlen)
to++;
} else {
- __skb_frag_ref(fragfrom);
skb_frag_page_copy(fragto, fragfrom);
+ skb_frag_ref(tgt, to);
skb_frag_off_copy(fragto, fragfrom);
skb_frag_size_set(fragto, todo);
@@ -4381,7 +4374,7 @@ int skb_shift(struct sk_buff *tgt, struct sk_buff *skb, int shiftlen)
fragto = &skb_shinfo(tgt)->frags[merge];
skb_frag_size_add(fragto, skb_frag_size(fragfrom));
- __skb_frag_unref(fragfrom, skb->pp_recycle);
+ skb_frag_unref(skb, 0);
}
/* Reposition in the original skb */
@@ -5055,8 +5048,10 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
goto err;
}
+ if (!skb_shinfo(nskb)->nr_frags)
+ nskb->pp_recycle = frag_skb->pp_recycle;
*nskb_frag = (i < 0) ? skb_head_frag_to_page_desc(frag_skb) : *frag;
- __skb_frag_ref(nskb_frag);
+ skb_frag_ref(nskb, skb_shinfo(nskb)->nr_frags);
size = skb_frag_size(nskb_frag);
if (pos < offset) {
@@ -6269,10 +6264,8 @@ bool skb_try_coalesce(struct sk_buff *to, struct sk_buff *from,
/* if the skb is not cloned this does nothing
* since we set nr_frags to 0.
*/
- if (skb_pp_frag_ref(from)) {
- for (i = 0; i < from_shinfo->nr_frags; i++)
- __skb_frag_ref(&from_shinfo->frags[i]);
- }
+ for (i = 0; i < from_shinfo->nr_frags; i++)
+ skb_frag_ref(from, i);
to->truesize += delta;
to->len += len;
@@ -7551,23 +7544,39 @@ bool csum_and_copy_from_iter_full(void *addr, size_t bytes,
}
EXPORT_SYMBOL(csum_and_copy_from_iter_full);
-void __get_netmem(netmem_ref netmem)
+/**
+ * get_net_iov - acquire a non-page_pool reference on a net_iov
+ * @niov: net_iov to reference
+ *
+ * Acquires a non-pp backing reference (binding->ref) on @niov regardless of
+ * page_pool membership, warning if @niov does not support non-pp references.
+ * Counterpart to put_net_iov().
+ */
+void get_net_iov(struct net_iov *niov)
{
- struct net_iov *niov = netmem_to_net_iov(netmem);
-
if (net_is_devmem_iov(niov))
- net_devmem_get_net_iov(netmem_to_net_iov(netmem));
+ net_devmem_get_net_iov(niov);
+ else
+ DEBUG_NET_WARN_ON_ONCE(true);
}
-EXPORT_SYMBOL(__get_netmem);
+EXPORT_SYMBOL(get_net_iov);
-void __put_netmem(netmem_ref netmem)
+/**
+ * put_net_iov - release a non-page_pool reference on a net_iov
+ * @niov: net_iov to unreference
+ *
+ * Drops a non-pp backing reference (binding->ref) on @niov regardless of
+ * page_pool membership, warning if @niov does not support non-pp references.
+ * Counterpart to get_net_iov().
+ */
+void put_net_iov(struct net_iov *niov)
{
- struct net_iov *niov = netmem_to_net_iov(netmem);
-
if (net_is_devmem_iov(niov))
- net_devmem_put_net_iov(netmem_to_net_iov(netmem));
+ net_devmem_put_net_iov(niov);
+ else
+ DEBUG_NET_WARN_ON_ONCE(true);
}
-EXPORT_SYMBOL(__put_netmem);
+EXPORT_SYMBOL(put_net_iov);
struct vlan_type_depth __vlan_get_protocol_offset(const struct sk_buff *skb,
__be16 type,
diff --git a/net/ipv4/esp4.c b/net/ipv4/esp4.c
index e76db5817e78e..ae701561d9f12 100644
--- a/net/ipv4/esp4.c
+++ b/net/ipv4/esp4.c
@@ -117,8 +117,8 @@ static void esp_ssg_unref(struct xfrm_state *x, void *tmp, struct sk_buff *skb,
struct scatterlist *src = already_unref ? esp_req_sg(aead, req) : req->src;
for (sg = sg_next(src); sg; sg = sg_next(sg))
- skb_page_unref(page_to_netmem(sg_page(sg)),
- skb->pp_recycle);
+ skb_netmem_unref(page_to_netmem(sg_page(sg)),
+ skb->pp_recycle);
}
}
diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c
index f4c9101e5996a..99d7237a623d8 100644
--- a/net/ipv4/tcp.c
+++ b/net/ipv4/tcp.c
@@ -254,6 +254,7 @@
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/skbuff.h>
+#include <linux/skbuff_ref.h>
#include <linux/splice.h>
#include <linux/net.h>
#include <linux/socket.h>
@@ -2559,6 +2560,7 @@ static int tcp_recvmsg_dmabuf(struct sock *sk, const struct sk_buff *skb,
*/
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
+ netmem_ref netmem = skb_frag_netmem(frag);
struct net_iov *niov;
u64 frag_offset;
int end;
@@ -2612,8 +2614,8 @@ static int tcp_recvmsg_dmabuf(struct sock *sk, const struct sk_buff *skb,
if (err)
goto out;
- atomic_long_inc(&niov->desc.pp_ref_count);
- tcp_xa_pool.netmems[tcp_xa_pool.idx++] = skb_frag_netmem(frag);
+ WARN_ON_ONCE(!napi_pp_get_page(netmem));
+ tcp_xa_pool.netmems[tcp_xa_pool.idx++] = netmem;
sent += copy;
diff --git a/net/ipv6/esp6.c b/net/ipv6/esp6.c
index b1c9b36f76dc4..d654f0f442388 100644
--- a/net/ipv6/esp6.c
+++ b/net/ipv6/esp6.c
@@ -134,8 +134,8 @@ static void esp_ssg_unref(struct xfrm_state *x, void *tmp, struct sk_buff *skb,
struct scatterlist *src = already_unref ? esp_req_sg(aead, req) : req->src;
for (sg = sg_next(src); sg; sg = sg_next(sg))
- skb_page_unref(page_to_netmem(sg_page(sg)),
- skb->pp_recycle);
+ skb_netmem_unref(page_to_netmem(sg_page(sg)),
+ skb->pp_recycle);
}
}
diff --git a/net/tls/tls_device.c b/net/tls/tls_device.c
index f11d0528fc431..b333a9a72cdae 100644
--- a/net/tls/tls_device.c
+++ b/net/tls/tls_device.c
@@ -143,7 +143,7 @@ static void destroy_record(struct tls_record_info *record)
int i;
for (i = 0; i < record->num_frags; i++)
- __skb_frag_unref(&record->frags[i], false);
+ put_netmem(skb_frag_netmem(&record->frags[i]));
kfree(record);
}
diff --git a/net/tls/tls_device_fallback.c b/net/tls/tls_device_fallback.c
index 3b7d0ab2bcf17..35cc510db4321 100644
--- a/net/tls/tls_device_fallback.c
+++ b/net/tls/tls_device_fallback.c
@@ -256,7 +256,7 @@ static int fill_sg_in(struct scatterlist *sg_in,
for (i = 0; remaining > 0; i++) {
skb_frag_t *frag = &record->frags[i];
- __skb_frag_ref(frag);
+ get_netmem(skb_frag_netmem(frag));
sg_set_page(sg_in + i, skb_frag_page(frag),
skb_frag_size(frag), skb_frag_off(frag));
diff --git a/net/tls/tls_strp.c b/net/tls/tls_strp.c
index 6cc222008d95c..53d3256d0114a 100644
--- a/net/tls/tls_strp.c
+++ b/net/tls/tls_strp.c
@@ -197,7 +197,7 @@ static void tls_strp_flush_anchor_copy(struct tls_strparser *strp)
DEBUG_NET_WARN_ON_ONCE(atomic_read(&shinfo->dataref) != 1);
for (i = 0; i < shinfo->nr_frags; i++)
- __skb_frag_unref(&shinfo->frags[i], false);
+ skb_frag_unref(strp->anchor, i);
shinfo->nr_frags = 0;
if (strp->copy_mode) {
kfree_skb_list(shinfo->frag_list);
diff --git a/net/xfrm/xfrm_iptfs.c b/net/xfrm/xfrm_iptfs.c
index 6920940a35b49..103cf7c5dbfb9 100644
--- a/net/xfrm/xfrm_iptfs.c
+++ b/net/xfrm/xfrm_iptfs.c
@@ -486,8 +486,7 @@ static int iptfs_skb_add_frags(struct sk_buff *skb,
tofrag->len -= offset;
offset = 0;
}
- __skb_frag_ref(tofrag);
- shinfo->nr_frags++;
+ skb_frag_ref(skb, shinfo->nr_frags++);
shinfo->flags |= SKBFL_SHARED_FRAG;
/* see if we are done */
|
| 3/2 | 2026/10/10 08:45 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 24538 output: 27 thoughts: 1115 Thoughts: **Analyzing a Major Skb Refcounting Refactor** Okay, let's dive into this patch. My initial impression is that this is a significant undertaking. The diff covers a wide range of core networking components, indicating a substantial refactoring effort. I'm seeing changes across numerous drivers and protocol implementations, hitting critical areas like skb management, TCP, IPsec, and TLS. This is definitely worth a closer look, given the potential for subtle but impactful changes. My focus is immediately drawn to the core networking subsystem files. The modifications to `net/core/skbuff.c`, `include/linux/skbuff_ref.h`, and `include/net/netmem.h` are the heart of this patch. I'm seeing a deep dive into how `skb` fragment refcounting is handled, particularly concerning the interaction between `skb->pp_recycle` and `page_pool` references versus non-page-pool references. This suggests a concerted effort to streamline or optimize memory management within the networking stack. Specifically, I'm observing new functions related to the `netmem` structure, such as `skb_netmem_ref` and `skb_netmem_unref`, that are linked to the `skb->pp_recycle` flag. I note the changes to functions like `skb_release_data`, `__pskb_copy_fclone`, `skb_zerocopy`, `skb_split`, `skb_shift`, `skb_segment`, and `skb_try_coalesce`. These are all crucial functions for manipulating skbs and their fragments, which makes this refactoring interesting. The `napi_pp_get_page` symbol being exported is particularly interesting in that it seems to be an addition to the public interface. I'm also seeing numerous `skb_frag_ref` and `skb_frag_unref` calls. The code modifications are not limited to just the core files. I spot changes in various drivers – `veth`, `chelsio`, `marvell`, `mellanox`, `sun/cassini` – along with protocol-specific code in TCP, IPsec (ESP), and TLS. The use of `WARN_ON_ONCE` and `DEBUG_NET_WARN_ON_ONCE` in various places is a great sign. It means the developers are proactively trying to catch potential issues during runtime by introducing assertions and checks. This level of diligence suggests that they anticipate subtle bugs and have designed the refactoring with robustness in mind. My analysis points to a systematic effort to refactor and fix issues related to `skb` fragment refcounting across multiple parts of the kernel's networking stack. This is a crucial area. Given how extensively these changes touch core networking components and drivers, the potential blast radius of these changes is high, therefore, Fuzzability = true! I intend to use git grep, read-file, and codesearch to verify configurations and symbol details. |
| 4/2 | 2026/10/10 08:46 | tool | grepper |
0mArgs:{
"Expression": "config PAGE_POOL",
"PathPrefixes": [
"net/"
]
}Results: {
"Output": "net/Kconfig=486=config NET_DEVLINK\n--\nnet/Kconfig-489-\nnet/Kconfig:490:config PAGE_POOL\nnet/Kconfig-491-\tbool\nnet/Kconfig-492-\nnet/Kconfig:493:config PAGE_POOL_STATS\nnet/Kconfig-494-\tdefault n\n"
} |
| 5/2 | 2026/10/10 08:46 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 26418 output: 18 thoughts: 11 Thoughts: Okay, let's dive into this. Here's my thought process on dissecting how `PAGE_POOL` is chosen: **Analyzing the `PAGE_POOL` Selection Process** Alright, so the task at hand is to understand the selection mechanism for `PAGE_POOL`. This isn't just a casual glance; I need a solid grasp of this. It's crucial for understanding memory management and potential performance bottlenecks. I'm going to carefully trace the code or relevant documentation to pinpoint the exact logic involved. Specifically, I'll be looking for any conditional statements, configuration parameters, or algorithms that dictate which `PAGE_POOL` gets used in a given scenario. Is it based on memory size, process ID, thread count, system configuration, or something else entirely? The nuances are what really matter. I'll need to identify any default settings, override mechanisms, and potential implications for different workloads. I'll probably start with the most obvious entry points and follow the breadcrumbs from there, keeping an eye out for any unexpected behaviors. Let's get to it! |
| 6/2 | 2026/10/10 08:46 | tool | grepper |
0mArgs:{
"Expression": "select PAGE_POOL"
}Results: {
"Output": "\nFull output is too long, showing 200 out of 263 lines.\nUse more precise expression or PathPrefixes if possible.\n\nNumber of matching lines per file (31 files in total):\ndrivers/net/Kconfig:5\ndrivers/net/ethernet/airoha/Kconfig:1\ndrivers/net/ethernet/alibaba/Kconfig:1\ndrivers/net/ethernet/broadcom/Kconfig:4\ndrivers/net/ethernet/cisco/enic/Kconfig:1\ndrivers/net/ethernet/engleder/Kconfig:1\ndrivers/net/ethernet/freescale/Kconfig:1\ndrivers/net/ethernet/google/Kconfig:1\ndrivers/net/ethernet/hisilicon/Kconfig:2\ndrivers/net/ethernet/huawei/hinic3/Kconfig:1\ndrivers/net/ethernet/intel/libeth/Kconfig:1\ndrivers/net/ethernet/marvell/Kconfig:3\ndrivers/net/ethernet/marvell/octeontx2/Kconfig:1\ndrivers/net/ethernet/mediatek/Kconfig:2\ndrivers/net/ethernet/mellanox/mlx4/Kconfig:1\ndrivers/net/ethernet/mellanox/mlx5/core/Kconfig:2\ndrivers/net/ethernet/mellanox/mlxsw/Kconfig:1\ndrivers/net/ethernet/meta/Kconfig:2\ndrivers/net/ethernet/microchip/lan966x/Kconfig:1\ndrivers/net/ethernet/microchip/sparx5/Kconfig:1\ndrivers/net/ethernet/microsoft/Kconfig:1\ndrivers/net/ethernet/pensando/Kconfig:1\ndrivers/net/ethernet/realtek/Kconfig:1\ndrivers/net/ethernet/renesas/Kconfig:1\ndrivers/net/ethernet/socionext/Kconfig:1\ndrivers/net/ethernet/stmicro/stmmac/Kconfig:1\ndrivers/net/ethernet/tehuti/Kconfig:1\ndrivers/net/ethernet/ti/Kconfig:4\ndrivers/net/ethernet/wangxun/Kconfig:1\ndrivers/net/wireless/mediatek/mt76/Kconfig:1\nkernel/bpf/Kconfig:1\n\ndrivers/net/Kconfig=435=config VETH\ndrivers/net/Kconfig-436-\ttristate \"Virtual ethernet pair device\"\ndrivers/net/Kconfig:437:\tselect PAGE_POOL\ndrivers/net/Kconfig-438-\thelp\n--\ndrivers/net/Kconfig=443=config VIRTIO_NET\n--\ndrivers/net/Kconfig-447-\tselect DIMLIB\ndrivers/net/Kconfig:448:\tselect PAGE_POOL\ndrivers/net/Kconfig-449-\thelp\n--\ndrivers/net/Kconfig=544=config XEN_NETDEV_FRONTEND\n--\ndrivers/net/Kconfig-547-\tselect XEN_XENBUS_FRONTEND\ndrivers/net/Kconfig:548:\tselect PAGE_POOL\ndrivers/net/Kconfig-549-\tdefault y\n--\ndrivers/net/Kconfig=584=config VMXNET3\n--\ndrivers/net/Kconfig-587-\tdepends on PAGE_SIZE_LESS_THAN_64KB\ndrivers/net/Kconfig:588:\tselect PAGE_POOL\ndrivers/net/Kconfig-589-\thelp\n--\ndrivers/net/Kconfig=604=config NETDEVSIM\n--\ndrivers/net/Kconfig-610-\tselect NET_DEVLINK\ndrivers/net/Kconfig:611:\tselect PAGE_POOL\ndrivers/net/Kconfig-612-\tselect NET_SHAPER\n--\ndrivers/net/ethernet/airoha/Kconfig=18=config NET_AIROHA\n--\ndrivers/net/ethernet/airoha/Kconfig-21-\tselect NET_AIROHA_NPU\ndrivers/net/ethernet/airoha/Kconfig:22:\tselect PAGE_POOL\ndrivers/net/ethernet/airoha/Kconfig-23-\thelp\n--\ndrivers/net/ethernet/alibaba/Kconfig=18=config ALIBABA_EEA\n--\ndrivers/net/ethernet/alibaba/Kconfig-21-\tdepends on 64BIT\ndrivers/net/ethernet/alibaba/Kconfig:22:\tselect PAGE_POOL\ndrivers/net/ethernet/alibaba/Kconfig-23-\thelp\n--\ndrivers/net/ethernet/broadcom/Kconfig=72=config BCMGENET\n--\ndrivers/net/ethernet/broadcom/Kconfig-80-\tselect DIMLIB\ndrivers/net/ethernet/broadcom/Kconfig:81:\tselect PAGE_POOL\ndrivers/net/ethernet/broadcom/Kconfig-82-\tselect BROADCOM_PHY if ARCH_BCM2835\n--\ndrivers/net/ethernet/broadcom/Kconfig=207=config BNXT\n--\ndrivers/net/ethernet/broadcom/Kconfig-213-\tselect NET_DEVLINK\ndrivers/net/ethernet/broadcom/Kconfig:214:\tselect PAGE_POOL\ndrivers/net/ethernet/broadcom/Kconfig-215-\tselect DIMLIB\n--\ndrivers/net/ethernet/broadcom/Kconfig=258=config BNGE\n--\ndrivers/net/ethernet/broadcom/Kconfig-261-\tselect NET_DEVLINK\ndrivers/net/ethernet/broadcom/Kconfig:262:\tselect PAGE_POOL\ndrivers/net/ethernet/broadcom/Kconfig-263-\tselect AUXILIARY_BUS\n--\ndrivers/net/ethernet/broadcom/Kconfig=269=config BCMASP\n--\ndrivers/net/ethernet/broadcom/Kconfig-275-\tselect MDIO_BCM_UNIMAC\ndrivers/net/ethernet/broadcom/Kconfig:276:\tselect PAGE_POOL\ndrivers/net/ethernet/broadcom/Kconfig-277-\thelp\n--\ndrivers/net/ethernet/cisco/enic/Kconfig=6=config ENIC\n--\ndrivers/net/ethernet/cisco/enic/Kconfig-8-\tdepends on PCI\ndrivers/net/ethernet/cisco/enic/Kconfig:9:\tselect PAGE_POOL\ndrivers/net/ethernet/cisco/enic/Kconfig-10-\thelp\n--\ndrivers/net/ethernet/engleder/Kconfig=19=config TSNEP\n--\ndrivers/net/ethernet/engleder/Kconfig-23-\tselect PHYLIB\ndrivers/net/ethernet/engleder/Kconfig:24:\tselect PAGE_POOL\ndrivers/net/ethernet/engleder/Kconfig-25-\thelp\n--\ndrivers/net/ethernet/freescale/Kconfig=23=config FEC\n--\ndrivers/net/ethernet/freescale/Kconfig-31-\tselect FIXED_PHY\ndrivers/net/ethernet/freescale/Kconfig:32:\tselect PAGE_POOL\ndrivers/net/ethernet/freescale/Kconfig-33-\timply PAGE_POOL_STATS\n--\ndrivers/net/ethernet/google/Kconfig=18=config GVE\n--\ndrivers/net/ethernet/google/Kconfig-21-\tdepends on PTP_1588_CLOCK_OPTIONAL\ndrivers/net/ethernet/google/Kconfig:22:\tselect PAGE_POOL\ndrivers/net/ethernet/google/Kconfig-23-\thelp\n--\ndrivers/net/ethernet/hisilicon/Kconfig=91=config HNS3\n--\ndrivers/net/ethernet/hisilicon/Kconfig-94-\tselect NET_DEVLINK\ndrivers/net/ethernet/hisilicon/Kconfig:95:\tselect PAGE_POOL\ndrivers/net/ethernet/hisilicon/Kconfig-96-\thelp\n--\ndrivers/net/ethernet/hisilicon/Kconfig=147=config HIBMCGE\n--\ndrivers/net/ethernet/hisilicon/Kconfig-153-\tselect REALTEK_PHY\ndrivers/net/ethernet/hisilicon/Kconfig:154:\tselect PAGE_POOL\ndrivers/net/ethernet/hisilicon/Kconfig-155-\thelp\n--\ndrivers/net/ethernet/huawei/hinic3/Kconfig=6=config HINIC3\n--\ndrivers/net/ethernet/huawei/hinic3/Kconfig-14-\tselect DIMLIB\ndrivers/net/ethernet/huawei/hinic3/Kconfig:15:\tselect PAGE_POOL\ndrivers/net/ethernet/huawei/hinic3/Kconfig-16-\thelp\n--\ndrivers/net/ethernet/intel/libeth/Kconfig=4=config LIBETH\ndrivers/net/ethernet/intel/libeth/Kconfig-5-\ttristate \"Common Ethernet library (libeth)\" if COMPILE_TEST\ndrivers/net/ethernet/intel/libeth/Kconfig:6:\tselect PAGE_POOL\ndrivers/net/ethernet/intel/libeth/Kconfig-7-\thelp\n--\ndrivers/net/ethernet/marvell/Kconfig=59=config MVNETA\n--\ndrivers/net/ethernet/marvell/Kconfig-63-\tselect PHYLINK\ndrivers/net/ethernet/marvell/Kconfig:64:\tselect PAGE_POOL\ndrivers/net/ethernet/marvell/Kconfig:65:\tselect PAGE_POOL_STATS\ndrivers/net/ethernet/marvell/Kconfig-66-\thelp\n--\ndrivers/net/ethernet/marvell/Kconfig=86=config MVPP2\n--\ndrivers/net/ethernet/marvell/Kconfig-90-\tselect PHYLINK\ndrivers/net/ethernet/marvell/Kconfig:91:\tselect PAGE_POOL\ndrivers/net/ethernet/marvell/Kconfig-92-\thelp\n--\ndrivers/net/ethernet/marvell/octeontx2/Kconfig=31=config OCTEONTX2_PF\n--\ndrivers/net/ethernet/marvell/octeontx2/Kconfig-34-\tselect NET_DEVLINK\ndrivers/net/ethernet/marvell/octeontx2/Kconfig:35:\tselect PAGE_POOL\ndrivers/net/ethernet/marvell/octeontx2/Kconfig-36-\tselect CRYPTO_LIB_AES if MACSEC\n--\ndrivers/net/ethernet/mediatek/Kconfig=14=config NET_MEDIATEK_SOC\n--\ndrivers/net/ethernet/mediatek/Kconfig-20-\tselect GENERIC_ALLOCATOR\ndrivers/net/ethernet/mediatek/Kconfig:21:\tselect PAGE_POOL\ndrivers/net/ethernet/mediatek/Kconfig:22:\tselect PAGE_POOL_STATS\ndrivers/net/ethernet/mediatek/Kconfig-23-\tselect PCS_MTK_LYNXI\n--\ndrivers/net/ethernet/mellanox/mlx4/Kconfig=6=config MLX4_EN\n--\ndrivers/net/ethernet/mellanox/mlx4/Kconfig-9-\tdepends on PTP_1588_CLOCK_OPTIONAL\ndrivers/net/ethernet/mellanox/mlx4/Kconfig:10:\tselect PAGE_POOL\ndrivers/net/ethernet/mellanox/mlx4/Kconfig-11-\tselect MLX4_CORE\n--\ndrivers/net/ethernet/mellanox/mlx5/core/Kconfig=29=config MLX5_CORE_EN\n--\ndrivers/net/ethernet/mellanox/mlx5/core/Kconfig-31-\tdepends on NETDEVICES \u0026\u0026 ETHERNET \u0026\u0026 INET \u0026\u0026 PCI \u0026\u0026 MLX5_CORE\ndrivers/net/ethernet/mellanox/mlx5/core/Kconfig:32:\tselect PAGE_POOL\ndrivers/net/ethernet/mellanox/mlx5/core/Kconfig:33:\tselect PAGE_POOL_STATS\ndrivers/net/ethernet/mellanox/mlx5/core/Kconfig-34-\tselect DIMLIB\n--\ndrivers/net/ethernet/mellanox/mlxsw/Kconfig=33=config MLXSW_PCI\n--\ndrivers/net/ethernet/mellanox/mlxsw/Kconfig-35-\tdepends on PCI \u0026\u0026 HAS_IOMEM \u0026\u0026 MLXSW_CORE\ndrivers/net/ethernet/mellanox/mlxsw/Kconfig:36:\tselect PAGE_POOL\ndrivers/net/ethernet/mellanox/mlxsw/Kconfig-37-\tdefault m\n--\ndrivers/net/ethernet/meta/Kconfig=20=config FBNIC\n--\ndrivers/net/ethernet/meta/Kconfig-27-\tselect NET_DEVLINK\ndrivers/net/ethernet/meta/Kconfig:28:\tselect PAGE_POOL\ndrivers/net/ethernet/meta/Kconfig-29-\tselect PCS_XPCS\n--\ndrivers/net/ethernet/meta/Kconfig=38=config MPNIC\n--\ndrivers/net/ethernet/meta/Kconfig-42-\tdepends on PCI_MSI\ndrivers/net/ethernet/meta/Kconfig:43:\tselect PAGE_POOL\ndrivers/net/ethernet/meta/Kconfig-44-\thelp\n--\ndrivers/net/ethernet/microchip/lan966x/Kconfig=1=config LAN966X_SWITCH\n--\ndrivers/net/ethernet/microchip/lan966x/Kconfig-8-\tselect PHYLINK\ndrivers/net/ethernet/microchip/lan966x/Kconfig:9:\tselect PAGE_POOL\ndrivers/net/ethernet/microchip/lan966x/Kconfig-10-\tselect VCAP\n--\ndrivers/net/ethernet/microchip/sparx5/Kconfig=28=config LAN969X_SWITCH\n--\ndrivers/net/ethernet/microchip/sparx5/Kconfig-30-\tdepends on SPARX5_SWITCH\ndrivers/net/ethernet/microchip/sparx5/Kconfig:31:\tselect PAGE_POOL\ndrivers/net/ethernet/microchip/sparx5/Kconfig-32-\thelp\n--\ndrivers/net/ethernet/microsoft/Kconfig=18=config MICROSOFT_GDMA_CORE\n--\ndrivers/net/ethernet/microsoft/Kconfig-21-\tselect DIMLIB\ndrivers/net/ethernet/microsoft/Kconfig:22:\tselect PAGE_POOL\ndrivers/net/ethernet/microsoft/Kconfig-23-\tselect NET_SHAPER\n--\ndrivers/net/ethernet/pensando/Kconfig=20=config IONIC\n--\ndrivers/net/ethernet/pensando/Kconfig-25-\tselect DIMLIB\ndrivers/net/ethernet/pensando/Kconfig:26:\tselect PAGE_POOL\ndrivers/net/ethernet/pensando/Kconfig-27-\tselect AUXILIARY_BUS\n--\ndrivers/net/ethernet/realtek/Kconfig=110=config RTASE\n--\ndrivers/net/ethernet/realtek/Kconfig-113-\tselect CRC32\ndrivers/net/ethernet/realtek/Kconfig:114:\tselect PAGE_POOL\ndrivers/net/ethernet/realtek/Kconfig-115-\thelp\n--\ndrivers/net/ethernet/renesas/Kconfig=29=config RAVB\n\n"
} |
| 7/2 | 2026/10/10 08:46 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 30479 output: 29 thoughts: 13 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience, presented in the first person: **BPF Configuration Review – Page Pool Context** Alright, before diving into the application logic, I need to verify something fundamental – the BPF configuration and how it's interfacing with the `PAGE_POOL`. This is crucial. I want to double-check that the BPF program, or specifically the section responsible for data handling and memory management, is correctly set up to leverage the page pool. There are several ways to do it and the method here must be understood before looking further. |
| 8/2 | 2026/10/10 08:46 | tool | grepper |
0mArgs:{
"Expression": "PAGE_POOL",
"PathPrefixes": [
"kernel/bpf/"
]
}Results: {
"Output": "kernel/bpf/Kconfig=27=config BPF_SYSCALL\n--\nkernel/bpf/Kconfig-35-\tselect NET_XGRESS if NET\nkernel/bpf/Kconfig:36:\tselect PAGE_POOL if NET\nkernel/bpf/Kconfig-37-\tdefault n\n"
} |
| 9/2 | 2026/10/10 08:46 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 30733 output: 20 thoughts: 69 Thoughts: Here's my summary, as though I'm thinking through this myself: **Network Memory Pool Observations** Okay, so the `BPF_SYSCALL` Kconfig option is specifically setting the `PAGE_POOL` option when dealing with the `NET` namespace. That's good to know – it's a clear dependency. And, aha, `VETH` also hooks into `PAGE_POOL`. Makes sense, considering its networking implications. Now, because of the interactions in the network space, it's worth double checking the `NET_DEVMEM` configuration. The `get_net_iov` and related devmem tests rely on `CONFIG_NET_DEVMEM` – might be some interplay there, so I'll need to check the current setting to ensure the page pool dependencies are behaving as expected in relation to those devmem tests. This seems likely because of the interaction of BPF, VETH, and overall network device memory handling. |
| 10/2 | 2026/10/10 08:46 | tool | grepper |
0mArgs:{
"Expression": "config NET_DEVMEM"
}Results: {
"Output": "net/Kconfig=66=config SKB_EXTENSIONS\n--\nnet/Kconfig-68-\nnet/Kconfig:69:config NET_DEVMEM\nnet/Kconfig-70-\tdef_bool y\n"
} |
| 11/2 | 2026/10/10 08:46 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 31094 output: 31 thoughts: 44 Thoughts: Here's my understanding of the situation: **Investigating Network Device Memory Configuration** Okay, so I'm digging into network device memory configuration within this kernel context. My initial clue is `NET_DEVMEM`. I remember seeing that defined as a `def_bool y` within the `net/Kconfig` file. Specifically, it's conditional, enabled only if `NET` is defined. To proceed, I'll need to go directly to `net/Kconfig` and take a look around line 69 – that's often a good starting point for finding the relevant definitions and dependencies. That will likely give me the surrounding context and related configuration options. I'm expecting this will lay out how device memory access is enabled or disabled at build time. |
| 12/2 | 2026/10/10 08:46 | tool | read-file |
0mArgs:{
"File": "net/Kconfig",
"FirstLine": 60,
"LineCount": 20
}Results: {
"Contents": " 60:\tconfig NET_REDIRECT\n 61:\t\tbool\n 62:\t\n 63:\tconfig SKB_DECRYPTED\n 64:\t\tbool\n 65:\t\n 66:\tconfig SKB_EXTENSIONS\n 67:\t\tbool\n 68:\t\n 69:\tconfig NET_DEVMEM\n 70:\t\tdef_bool y\n 71:\t\tdepends on DMA_SHARED_BUFFER\n 72:\t\tdepends on PAGE_POOL\n 73:\t\n 74:\tconfig NET_SHAPER\n 75:\t\tbool\n 76:\t\n 77:\tconfig NET_CRC32C\n 78:\t\tbool\n 79:\t\tselect CRC32\n"
} |
| 13/2 | 2026/10/10 08:46 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 31525 output: 20 thoughts: 34 Thoughts: Here's a summary of my thought process, as requested: **Dependency Chain Deep Dive: `NET_DEVMEM`** Okay, so I'm looking at `NET_DEVMEM` and immediately, I'm thinking about its dependencies. Right, it's pretty clear that it relies on both `DMA_SHARED_BUFFER` and `PAGE_POOL`. That's not a surprise; it's a pretty common pattern for network device memory management. Now, since I'm trying to fully grasp the implications here, I need to understand `DMA_SHARED_BUFFER` in detail. What are its configurations? What are the implications in memory models? What restrictions does it place on the devices? Let me dive into that dependency first. I'll need to confirm my assumptions and make sure there are no red flags on that front. |
| 14/2 | 2026/10/10 08:46 | tool | grepper |
0mArgs:{
"Expression": "config DMA_SHARED_BUFFER"
}Results: {
"Output": "drivers/base/Kconfig=213=source \"drivers/base/regmap/Kconfig\"\ndrivers/base/Kconfig-214-\ndrivers/base/Kconfig:215:config DMA_SHARED_BUFFER\ndrivers/base/Kconfig-216-\tbool\n"
} |
| 15/2 | 2026/10/10 08:46 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 31838 output: 31 thoughts: 1608 Thoughts: **Analysis of SKB Data Release and Fragment Recycling** Okay, let's break this down. My initial focus is on the changes to `skb_release_data` in `net/core/skbuff.c`. I see a significant comment block was removed. Previously, if `skb_data_unref(skb, shinfo)` returned false, indicating the SKB was cloned or had other references, the code would set `skb->pp_recycle = 0`. Now, that's gone; it simply returns. This implies a change in how the recycle mechanism is handled, particularly in scenarios involving clones and shared data. I need to understand what replaces the logic of `skb->pp_recycle = 0`. Looking at the cloning, splitting, and copying functions: `__pskb_copy_fclone`, `skb_zerocopy`, `skb_split`, `skb_shift`, `skb_segment` and `skb_try_coalesce` all have calls to the new function `skb_frag_ref` after the recycle bit is copied, implying the original recycling mechanism has been superseded by something more modern. It's clear there's a shift in how fragment references are managed. I also see some new exported symbols: `napi_pp_get_page`, `napi_pp_put_page`, `get_net_iov`, and `put_net_iov`. These are strong signals that this is related to NAPI (Network API) processing and, potentially, zero-copy operations. This suggests that the zero-copy and NAPI are now intimately involved in this refactoring. I'll need to research what "pp" might stand for; my guess is "page pool" or some similar abstraction for managing memory buffers. The modifications to `veth_xdp_get`, `tcp_recvmsg_dmabuf`, `tls_strp_flush_anchor_copy` and `iptfs_skb_add_frags` are a bit more specific. These examples suggest a broader impact across diverse subsystems. The `WARN_ON_ONCE` within `tcp_recvmsg_dmabuf` is particularly interesting. It's alerting me to a potential regression or unexpected usage pattern with `napi_pp_get_page`. Now, to refine my analysis, I've got to focus on the key function calls that provide the entry points for feature logic: `napi_pp_get_page`, `napi_pp_put_page`, `skb_zerocopy`, `skb_split`, `skb_shift`, `skb_segment`, `skb_try_coalesce`, `tcp_recvmsg_dmabuf`, `veth_xdp_get`, `iptfs_skb_add_frags`, `tls_strp_flush_anchor_copy`, and `get_net_iov`/`put_net_iov`. These are the focal points of change and where the new logic is introduced, and therefore, where I need to focus my fuzzing efforts. I need to check the calling context of `tcp_recvmsg_dmabuf` to see how it is invoked in the codebase. |
| 16/2 | 2026/10/10 08:47 | tool | grepper |
0mArgs:{
"Expression": "tcp_recvmsg_dmabuf",
"PathPrefixes": [
"net/"
]
}Results: {
"Output": "net/ipv4/tcp.c=2475=static int tcp_xa_pool_refill(struct sock *sk, struct tcp_xa_pool *p,\n--\nnet/ipv4/tcp.c-2503- */\nnet/ipv4/tcp.c:2504:static int tcp_recvmsg_dmabuf(struct sock *sk, const struct sk_buff *skb,\nnet/ipv4/tcp.c-2505-\t\t\t unsigned int offset, struct msghdr *msg,\n--\nnet/ipv4/tcp.c=2661=static int tcp_recvmsg_locked(struct sock *sk, struct msghdr *msg, size_t len,\n--\nnet/ipv4/tcp.c-2863-\nnet/ipv4/tcp.c:2864:\t\t\t\terr = tcp_recvmsg_dmabuf(sk, skb, offset, msg,\nnet/ipv4/tcp.c-2865-\t\t\t\t\t\t\t used);\n"
} |
| 17/2 | 2026/10/10 08:47 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 34430 output: 36 thoughts: 37 Thoughts: Okay, here's my take on those thoughts, framed as an expert's self-reflection: **Deep Dive into TCP Reception and Potential XDP Interactions** Alright, let's see, `tcp_recvmsg_locked` is the entry point for handling received TCP data, and it's calling `tcp_recvmsg_dmabuf`. That's standard practice, making sure to handle DMA buffers appropriately. But what about `veth_xdp_get`? Now, that's what's got me thinking. I need to trace this back. Where exactly does `veth_xdp_get` come into play in this entire sequence? Is it upstream, potentially influencing the data's path *before* it gets to `tcp_recvmsg_locked`? Is it related to the virtual Ethernet interface and eXpress Data Path (XDP) filtering, maybe? I need to understand if XDP is interacting with this standard TCP receive path, and if so, at what stage. Are we potentially bypassing some of the standard checks or optimizations? Or, is it downstream, and being called to utilize data received via normal TCP? I'm going to need to dig into the call chain to see the context of how this function is utilized. The details of how these functions interact is important. |
| 18/2 | 2026/10/10 08:47 | tool | grepper |
0mArgs:{
"Expression": "veth_xdp_get",
"PathPrefixes": [
"drivers/net/veth.c"
]
}Results: {
"Output": "drivers/net/veth.c=710=static void veth_xdp_rcv_bulk_skb(struct veth_rq *rq, void **frames,\n--\ndrivers/net/veth.c-738-\ndrivers/net/veth.c:739:static void veth_xdp_get(struct xdp_buff *xdp)\ndrivers/net/veth.c-740-{\n--\ndrivers/net/veth.c=791=static struct sk_buff *veth_xdp_rcv_skb(struct veth_rq *rq,\n--\ndrivers/net/veth.c-825-\tcase XDP_TX:\ndrivers/net/veth.c:826:\t\tveth_xdp_get(xdp);\ndrivers/net/veth.c-827-\t\tconsume_skb(skb);\n--\ndrivers/net/veth.c-837-\tcase XDP_REDIRECT:\ndrivers/net/veth.c:838:\t\tveth_xdp_get(xdp);\ndrivers/net/veth.c-839-\t\tconsume_skb(skb);\n"
} |
| 19/2 | 2026/10/10 08:47 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 35082 output: 36 thoughts: 43 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience, and written as if they were my own mental process: **Diving into veth_xdp_rcv_skb and iptfs_skb_add_frags** Alright, I'm thinking about `veth_xdp_rcv_skb`, that's the XDP receive path specifically on a veth interface. Now, I need to understand what's happening to the packets within that path, particularly in relation to fragment handling. So, I need to check `iptfs_skb_add_frags` – that's where the fragmentation logic comes into play. I'm going to want to see how the XDP path interacts with the fragment-related functions. Things to consider: how XDP is passing skbs with fragments to higher layers, whether or not the XDP program itself is manipulating the fragments, and if this interaction has any performance implications compared to the standard receive path. I'll need to look at how the buffer management is working to ensure that we're not running into issues with buffer reuse in the XDP context. |
| 20/2 | 2026/10/10 08:47 | tool | grepper |
0mArgs:{
"Expression": "iptfs_skb_add_frags",
"PathPrefixes": [
"net/xfrm/"
]
}Results: {
"Output": "net/xfrm/xfrm_iptfs.c=409=static bool iptfs_skb_can_add_frags(const struct sk_buff *skb,\n--\nnet/xfrm/xfrm_iptfs.c-421-\t * loop is otherwise unbounded and would index past walk-\u003efrags[].\nnet/xfrm/xfrm_iptfs.c:422:\t * This mirrors the guard already present in iptfs_skb_add_frags().\nnet/xfrm/xfrm_iptfs.c-423-\t */\n--\nnet/xfrm/xfrm_iptfs.c-452-/**\nnet/xfrm/xfrm_iptfs.c:453: * iptfs_skb_add_frags() - add a range of fragment references into an skb\nnet/xfrm/xfrm_iptfs.c-454- * @skb: skb to add references into\n--\nnet/xfrm/xfrm_iptfs.c-465- */\nnet/xfrm/xfrm_iptfs.c:466:static int iptfs_skb_add_frags(struct sk_buff *skb,\nnet/xfrm/xfrm_iptfs.c-467-\t\t\t struct iptfs_skb_frag_walk *walk, u32 offset,\n--\nnet/xfrm/xfrm_iptfs.c=540=iptfs_pskb_add_frags(struct sk_buff *tpl, struct iptfs_skb_frag_walk *walk,\n--\nnet/xfrm/xfrm_iptfs.c-563-\nnet/xfrm/xfrm_iptfs.c:564:\tiptfs_skb_add_frags(skb, walk, off + copy_len, len - copy_len);\nnet/xfrm/xfrm_iptfs.c-565-\treturn skb;\n--\nnet/xfrm/xfrm_iptfs.c=749=static u32 iptfs_reassem_cont(struct xfrm_iptfs_data *xtfs, u64 seq,\n--\nnet/xfrm/xfrm_iptfs.c-909-\t iptfs_skb_can_add_frags(newskb, fragwalk, data, copylen)) {\nnet/xfrm/xfrm_iptfs.c:910:\t\tiptfs_skb_add_frags(newskb, fragwalk, data, copylen);\nnet/xfrm/xfrm_iptfs.c-911-\t} else {\n"
} |
| 21/2 | 2026/10/10 08:47 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 36045 output: 35 thoughts: 47 Thoughts: Here's my summary, as though I'm thinking through this process: **Diving into the IP-TFS Code** Okay, so I'm digging into the IP-TFS code, specifically that XFRM IPTFS stuff. My initial focus is on `iptfs_skb_add_frags`. Now, where does that fit in the bigger picture? I need to check something related, and I'm pretty sure I remember `tls_strp_flush_anchor_copy` is relevant here. Let me pull that function definition up. I need to understand the connection between these two, if there even is one... Let's see if this rings a bell. |
| 22/2 | 2026/10/10 08:47 | tool | grepper |
0mArgs:{
"Expression": "tls_strp_flush_anchor_copy",
"PathPrefixes": [
"net/tls/"
]
}Results: {
"Output": "net/tls/tls_strp.c=145=int tls_strp_msg_hold(struct tls_strparser *strp, struct sk_buff_head *dst)\n--\nnet/tls/tls_strp.c-191-\nnet/tls/tls_strp.c:192:static void tls_strp_flush_anchor_copy(struct tls_strparser *strp)\nnet/tls/tls_strp.c-193-{\n--\nnet/tls/tls_strp.c=390=static int tls_strp_read_copy(struct tls_strparser *strp, bool qshort)\n--\nnet/tls/tls_strp.c-410-\t\tif (!page) {\nnet/tls/tls_strp.c:411:\t\t\ttls_strp_flush_anchor_copy(strp);\nnet/tls/tls_strp.c-412-\t\t\treturn -ENOMEM;\n--\nnet/tls/tls_strp.c=601=void tls_strp_msg_consume(struct tls_strparser *strp)\n--\nnet/tls/tls_strp.c-607-\telse\nnet/tls/tls_strp.c:608:\t\ttls_strp_flush_anchor_copy(strp);\nnet/tls/tls_strp.c-609-\n"
} |
| 23/2 | 2026/10/10 08:47 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 36626 output: 41 thoughts: 37 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience: **Diving into ESP Unreferencing** Alright, time to dig into the heart of the matter. I need to get a clear picture of how ESP traffic is unreferenced, and the usual suspects are `esp_ssg_unref` within the IPv4 and IPv6 implementations. So, I'm mentally checking `net/ipv4/esp4.c` and `net/ipv6/esp6.c` respectively. I'm looking for where this function is called, what its arguments are, and most importantly, how it interacts with reference counting and resource cleanup. I'm probably going to be specifically looking for potential double-frees or leaks. This needs to be a very careful inspection of the code. |
| 24/2 | 2026/10/10 08:47 | tool | grepper |
0mArgs:{
"Expression": "esp_ssg_unref",
"PathPrefixes": [
"net/ipv4/",
"net/ipv6/"
]
}Results: {
"Output": "net/ipv4/esp4.c=91=static inline struct scatterlist *esp_req_sg(struct crypto_aead *aead,\n--\nnet/ipv4/esp4.c-98-\nnet/ipv4/esp4.c:99:static void esp_ssg_unref(struct xfrm_state *x, void *tmp, struct sk_buff *skb, bool already_unref)\nnet/ipv4/esp4.c-100-{\n--\nnet/ipv4/esp4.c=210=static void esp_output_done(void *data, int err)\n--\nnet/ipv4/esp4.c-225-\ttmp = ESP_SKB_CB(skb)-\u003etmp;\nnet/ipv4/esp4.c:226:\tesp_ssg_unref(x, tmp, skb, false);\nnet/ipv4/esp4.c-227-\tkfree(tmp);\n--\nnet/ipv4/esp4.c=504=int esp_output_tail(struct xfrm_state *x, struct sk_buff *skb, struct esp_info *esp)\n--\nnet/ipv4/esp4.c-581-\t\tif (unlikely(err \u003c 0)) {\nnet/ipv4/esp4.c:582:\t\t\tesp_ssg_unref(x, tmp, skb, true);\nnet/ipv4/esp4.c-583-\t\t\tgoto error_free;\n--\nnet/ipv4/esp4.c-615-\tif (sg != dsg)\nnet/ipv4/esp4.c:616:\t\tesp_ssg_unref(x, tmp, skb, false);\nnet/ipv4/esp4.c-617-\n--\nnet/ipv6/esp6.c=108=static inline struct scatterlist *esp_req_sg(struct crypto_aead *aead,\n--\nnet/ipv6/esp6.c-115-\nnet/ipv6/esp6.c:116:static void esp_ssg_unref(struct xfrm_state *x, void *tmp, struct sk_buff *skb, bool already_unref)\nnet/ipv6/esp6.c-117-{\n--\nnet/ipv6/esp6.c=245=static void esp_output_done(void *data, int err)\n--\nnet/ipv6/esp6.c-260-\ttmp = ESP_SKB_CB(skb)-\u003etmp;\nnet/ipv6/esp6.c:261:\tesp_ssg_unref(x, tmp, skb, false);\nnet/ipv6/esp6.c-262-\tkfree(tmp);\n--\nnet/ipv6/esp6.c=536=int esp6_output_tail(struct xfrm_state *x, struct sk_buff *skb, struct esp_info *esp)\n--\nnet/ipv6/esp6.c-613-\t\tif (unlikely(err \u003c 0)) {\nnet/ipv6/esp6.c:614:\t\t\tesp_ssg_unref(x, tmp, skb, true);\nnet/ipv6/esp6.c-615-\t\t\tgoto error_free;\n--\nnet/ipv6/esp6.c-648-\tif (sg != dsg)\nnet/ipv6/esp6.c:649:\t\tesp_ssg_unref(x, tmp, skb, false);\nnet/ipv6/esp6.c-650-\n"
} |
| 25/2 | 2026/10/10 08:47 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 37766 output: 20 thoughts: 715 Thoughts: **Initial Assessment: WorthFuzzing Confirmed** Okay, let's break this down. First, I see a list of modified hardware drivers: Chelsio, Marvell, Mellanox, and Sun Ethernet NICs. Standard stuff. But then, I'm seeing changes in core networking code. `veth.c`, `skbuff.c`, `tcp.c`, IPsec, and TLS handling, along with some important include files. That combination is a green light, *definitely* worth fuzzing. It indicates broader impact than just NIC drivers. Now, let's figure out what configuration options we need. First up is `CONFIG_PAGE_POOL`. Hmm, no user-facing prompt in `net/Kconfig`, just a simple `bool`. That means a user can't explicitly set it to 'y'. So, who selects it? Ah, `VETH`, `BPF_SYSCALL`, `NETDEVSIM`, and `VIRTIO_NET` all `select PAGE_POOL`. Syzkaller's `.config` files or any other Kconfig tool can absolutely enable those! Wait... can we *directly* specify `PAGE_POOL` in the syzkaller config fragments? In Kconfig, without a prompt, it's typically dropped unless a selecting symbol is enabled... so no. We'll need to enable one of the selecting configs, or figure out how the build process handles the default values if none are explicitly enabled. Okay, let's explore this further. Looks like `NET_DEVMEM` is related, and that has a dependency on `DMA_SHARED_BUFFER`. What selects *that*? I need to check `DMA_SHARED_BUFFER` in `Kconfig` as well. This is the crucial step; we need to identify the complete set of required configurations. We're looking for dependencies, of dependencies... so we can build a minimally functional set of configs to test this code. This will be the key to triggering these code paths efficiently during fuzzing. |
| 26/2 | 2026/10/10 08:47 | tool | grepper |
0mArgs:{
"Expression": "select DMA_SHARED_BUFFER"
}Results: {
"Output": "Documentation/driver-api/dma-buf.rst=65=Any exporters or users of the dma-buf buffer sharing framework must have a\nDocumentation/driver-api/dma-buf.rst:66:'select DMA_SHARED_BUFFER' in their respective Kconfigs.\nDocumentation/driver-api/dma-buf.rst-67-\n--\ndrivers/accel/habanalabs/Kconfig=6=config DRM_ACCEL_HABANALABS\n--\ndrivers/accel/habanalabs/Kconfig-12-\tselect HWMON\ndrivers/accel/habanalabs/Kconfig:13:\tselect DMA_SHARED_BUFFER\ndrivers/accel/habanalabs/Kconfig-14-\tselect CRC32\n--\ndrivers/dma-buf/Kconfig=4=config SYNC_FILE\n--\ndrivers/dma-buf/Kconfig-6-\tdefault n\ndrivers/dma-buf/Kconfig:7:\tselect DMA_SHARED_BUFFER\ndrivers/dma-buf/Kconfig-8-\thelp\n--\ndrivers/dma-buf/Kconfig=52=config DMABUF_KUNIT_TEST\n--\ndrivers/dma-buf/Kconfig-54-\tdepends on KUNIT\ndrivers/dma-buf/Kconfig:55:\tselect DMA_SHARED_BUFFER\ndrivers/dma-buf/Kconfig-56-\tdefault KUNIT_ALL_TESTS\n--\ndrivers/dma-buf/Kconfig=60=menuconfig DMABUF_HEAPS\ndrivers/dma-buf/Kconfig-61-\tbool \"DMA-BUF Userland Memory Heaps\"\ndrivers/dma-buf/Kconfig:62:\tselect DMA_SHARED_BUFFER\ndrivers/dma-buf/Kconfig-63-\thelp\n--\ndrivers/gpu/drm/Kconfig=8=menuconfig DRM\n--\ndrivers/gpu/drm/Kconfig-13-\tselect I2C\ndrivers/gpu/drm/Kconfig:14:\tselect DMA_SHARED_BUFFER\ndrivers/gpu/drm/Kconfig-15-\tselect SYNC_FILE\n--\ndrivers/gpu/host1x/Kconfig=6=config TEGRA_HOST1X\n--\ndrivers/gpu/host1x/Kconfig-8-\tdepends on ARCH_TEGRA || COMPILE_TEST\ndrivers/gpu/host1x/Kconfig:9:\tselect DMA_SHARED_BUFFER\ndrivers/gpu/host1x/Kconfig-10-\tselect TEGRA_HOST1X_CONTEXT_BUS\n--\ndrivers/iio/Kconfig=15=config IIO_BUFFER\ndrivers/iio/Kconfig-16-\tbool \"Enable buffer support within IIO\"\ndrivers/iio/Kconfig:17:\tselect DMA_SHARED_BUFFER\ndrivers/iio/Kconfig-18-\thelp\n--\ndrivers/infiniband/Kconfig=2=menuconfig INFINIBAND\n--\ndrivers/infiniband/Kconfig-7-\tdepends on !ALPHA\ndrivers/infiniband/Kconfig:8:\tselect DMA_SHARED_BUFFER\ndrivers/infiniband/Kconfig-9-\tselect IRQ_POLL\n--\ndrivers/infiniband/Kconfig=44=config INFINIBAND_USER_MEM\n--\ndrivers/infiniband/Kconfig-47-\tdepends on MMU\ndrivers/infiniband/Kconfig:48:\tselect DMA_SHARED_BUFFER\ndrivers/infiniband/Kconfig-49-\tdefault y\n--\ndrivers/iommu/iommufd/Kconfig=51=config IOMMUFD_TEST\n--\ndrivers/iommu/iommufd/Kconfig-56-\tdepends on IOMMU_PT_AMDV1=y || IOMMUFD=IOMMU_PT_AMDV1\ndrivers/iommu/iommufd/Kconfig:57:\tselect DMA_SHARED_BUFFER\ndrivers/iommu/iommufd/Kconfig-58-\tselect IOMMUFD_DRIVER\n--\ndrivers/media/common/videobuf2/Kconfig=3=config VIDEOBUF2_CORE\ndrivers/media/common/videobuf2/Kconfig:4:\tselect DMA_SHARED_BUFFER\ndrivers/media/common/videobuf2/Kconfig-5-\ttristate\n--\ndrivers/media/common/videobuf2/Kconfig=13=config VIDEOBUF2_DMA_CONTIG\n--\ndrivers/media/common/videobuf2/Kconfig-16-\tselect VIDEOBUF2_MEMOPS\ndrivers/media/common/videobuf2/Kconfig:17:\tselect DMA_SHARED_BUFFER\ndrivers/media/common/videobuf2/Kconfig-18-\ndrivers/media/common/videobuf2/Kconfig=19=config VIDEOBUF2_VMALLOC\n--\ndrivers/media/common/videobuf2/Kconfig-22-\tselect VIDEOBUF2_MEMOPS\ndrivers/media/common/videobuf2/Kconfig:23:\tselect DMA_SHARED_BUFFER\ndrivers/media/common/videobuf2/Kconfig-24-\n--\ndrivers/media/platform/nvidia/tegra-vde/Kconfig=1=config VIDEO_TEGRA_VDE\n--\ndrivers/media/platform/nvidia/tegra-vde/Kconfig-6-\tdepends on VIDEO_DEV\ndrivers/media/platform/nvidia/tegra-vde/Kconfig:7:\tselect DMA_SHARED_BUFFER\ndrivers/media/platform/nvidia/tegra-vde/Kconfig-8-\tselect IOMMU_IOVA\n--\ndrivers/misc/Kconfig=266=config QCOM_FASTRPC\n--\ndrivers/misc/Kconfig-269-\tdepends on RPMSG\ndrivers/misc/Kconfig:270:\tselect DMA_SHARED_BUFFER\ndrivers/misc/Kconfig-271-\tselect QCOM_SCM\n--\ndrivers/tee/Kconfig=3=menuconfig TEE\n--\ndrivers/tee/Kconfig-6-\tselect CRYPTO_LIB_SHA1\ndrivers/tee/Kconfig:7:\tselect DMA_SHARED_BUFFER\ndrivers/tee/Kconfig-8-\tselect GENERIC_ALLOCATOR\n--\ndrivers/usb/gadget/Kconfig=192=config USB_F_FS\ndrivers/usb/gadget/Kconfig:193:\tselect DMA_SHARED_BUFFER\ndrivers/usb/gadget/Kconfig-194-\ttristate\n--\ndrivers/xen/Kconfig=145=config XEN_GNTDEV_DMABUF\n--\ndrivers/xen/Kconfig-147-\tdepends on XEN_GNTDEV \u0026\u0026 XEN_GRANT_DMA_ALLOC\ndrivers/xen/Kconfig:148:\tselect DMA_SHARED_BUFFER\ndrivers/xen/Kconfig-149-\thelp\n--\nsamples/Kconfig=230=config SAMPLE_VFIO_MDEV_MBOCHS\n--\nsamples/Kconfig-233-\tselect VFIO_MDEV\nsamples/Kconfig:234:\tselect DMA_SHARED_BUFFER\nsamples/Kconfig-235-\thelp\n--\nsound/soc/fsl/Kconfig=6=config SND_SOC_FSL_ASRC\n--\nsound/soc/fsl/Kconfig-8-\tdepends on HAS_DMA\nsound/soc/fsl/Kconfig:9:\tselect DMA_SHARED_BUFFER\nsound/soc/fsl/Kconfig-10-\tselect REGMAP_MMIO\n"
} |
| 27/2 | 2026/10/10 08:47 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 40737 output: 37 thoughts: 434 Thoughts: **My Configuration Hunting Expedition** Okay, so I need to figure out the essential kernel configurations required for this modified code to compile and run correctly. Let's start by methodically working through the suggested approach. First, the text mentions `DMABUF_HEAPS` or `SYNC_FILE`, specifically that `DMABUF_HEAPS` selects `DMA_SHARED_BUFFER`. That's a starting point, but I need to be more thorough than that. Wait, hold on a second… what about `PAGE_POOL`? Can that be set within `EnableConfigs`? I need to check the Kconfig files and conditionals. The instructions are clear: *no assumptions*. Inspect Kconfig files, check those `#ifdef` guards, and look at "depends on" lines for any parent subsystem configs. And remember to remove the "CONFIG_" prefix. Got it. Looking at `include/linux/skbuff_ref.h` and `net/core/skbuff.c`, it's clear `CONFIG_PAGE_POOL` is directly used in `#ifdef` and `#if IS_ENABLED` constructs. That's a good find! That config is definitely needed for enabling the code. Next up is `CONFIG_NET_DEVMEM`. Checking `include/net/netmem.h` reveals that `get_net_iov` and `put_net_iov` call `net_is_devmem_iov`, which is under `CONFIG_NET_DEVMEM`. And in `net/ipv4/tcp.c`, `tcp_recvmsg_dmabuf` is mentioned; it seems to be related to devmem and TCP dmabuf usage. So I'll need to check where `tcp_recvmsg_dmabuf` is defined and ifdef'd. Time to dig deeper. |
| 28/2 | 2026/10/10 08:48 | tool | grepper |
0mArgs:{
"Expression": "tcp_recvmsg_dmabuf",
"PathPrefixes": [
"net/ipv4/tcp.c"
]
}Results: {
"Output": "net/ipv4/tcp.c=2475=static int tcp_xa_pool_refill(struct sock *sk, struct tcp_xa_pool *p,\n--\nnet/ipv4/tcp.c-2503- */\nnet/ipv4/tcp.c:2504:static int tcp_recvmsg_dmabuf(struct sock *sk, const struct sk_buff *skb,\nnet/ipv4/tcp.c-2505-\t\t\t unsigned int offset, struct msghdr *msg,\n--\nnet/ipv4/tcp.c=2661=static int tcp_recvmsg_locked(struct sock *sk, struct msghdr *msg, size_t len,\n--\nnet/ipv4/tcp.c-2863-\nnet/ipv4/tcp.c:2864:\t\t\t\terr = tcp_recvmsg_dmabuf(sk, skb, offset, msg,\nnet/ipv4/tcp.c-2865-\t\t\t\t\t\t\t used);\n"
} |
| 29/2 | 2026/10/10 08:48 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 41848 output: 37 thoughts: 26 Thoughts: Okay, here's my attempt at summarizing the thought process, as requested, targeting an expert user and starting with a bolded header: **Diving into the TCP Implementation** Alright, time to get my hands dirty. I've got a hunch about something, and to confirm it, I need to go straight to the source. Let's start by zeroing in on a very specific section of the Linux kernel's TCP implementation. I'm thinking lines 2500 through 2520 in `net/ipv4/tcp.c`. This area is likely involved in the part of TCP's operation that's the focus of this investigation. Hopefully, the code there will provide the details I need to confirm or refute my suspicion. Let's see what we've got. |
| 30/2 | 2026/10/10 08:48 | tool | read-file |
0mArgs:{
"File": "net/ipv4/tcp.c",
"FirstLine": 2500,
"LineCount": 25
}Results: {
"Contents": "2500:\t\n2501:\t/* On error, returns the -errno. On success, returns number of bytes sent to the\n2502:\t * user. May not consume all of @remaining_len.\n2503:\t */\n2504:\tstatic int tcp_recvmsg_dmabuf(struct sock *sk, const struct sk_buff *skb,\n2505:\t\t\t\t unsigned int offset, struct msghdr *msg,\n2506:\t\t\t\t int remaining_len)\n2507:\t{\n2508:\t\tstruct dmabuf_cmsg dmabuf_cmsg = { 0 };\n2509:\t\tstruct tcp_xa_pool tcp_xa_pool;\n2510:\t\tunsigned int start;\n2511:\t\tint i, copy, n;\n2512:\t\tint sent = 0;\n2513:\t\tint err = 0;\n2514:\t\n2515:\t\ttcp_xa_pool.max = 0;\n2516:\t\ttcp_xa_pool.idx = 0;\n2517:\t\tdo {\n2518:\t\t\tstart = skb_headlen(skb);\n2519:\t\n2520:\t\t\tif (skb_frags_readable(skb)) {\n2521:\t\t\t\terr = -ENODEV;\n2522:\t\t\t\tgoto out;\n2523:\t\t\t}\n2524:\t\n"
} |
| 31/2 | 2026/10/10 08:48 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 42459 output: 37 thoughts: 35 Thoughts: Okay, let's dive into this. Here's my summary, framed as my thought process: **Diving into `tcp_recvmsg_dmabuf`'s Security** Alright, I need to understand the safeguards around `tcp_recvmsg_dmabuf` within the `net/ipv4/tcp.c` code, specifically around the 2860-line region. That function handles the reception of data over TCP, and the `dmabuf` part suggests it's dealing with Direct Memory Access buffers. This suggests we're looking at optimizations or specialized handling, which are good candidates to evaluate for security concerns. My initial questions are: what checks are performed on the data itself? Are there limits on data size, and are those bounds enforced strictly to prevent buffer overflows or other memory-related issues? What about the origin of the data? Is the source properly authenticated and verified? Are there any race conditions that might permit a malicious actor to manipulate this function's behaviour, and can they bypass the checks? I expect to see carefully crafted data structures that help control the data transfer, but I'll need to confirm that they are properly handled. The interaction with `dmabuf` also makes me want to look into how the data physically resides in memory, and whether any vulnerabilities could stem from that arrangement. Time to dig in and see what's what. |
| 32/2 | 2026/10/10 08:48 | tool | read-file |
0mArgs:{
"File": "net/ipv4/tcp.c",
"FirstLine": 2855,
"LineCount": 20
}Results: {
"Contents": "2855:\t\t\t\t\t\t/* dmabuf skbs can only be received\n2856:\t\t\t\t\t\t * with the MSG_SOCK_DEVMEM flag.\n2857:\t\t\t\t\t\t */\n2858:\t\t\t\t\t\tif (!copied)\n2859:\t\t\t\t\t\t\tcopied = -EFAULT;\n2860:\t\n2861:\t\t\t\t\t\tbreak;\n2862:\t\t\t\t\t}\n2863:\t\n2864:\t\t\t\t\terr = tcp_recvmsg_dmabuf(sk, skb, offset, msg,\n2865:\t\t\t\t\t\t\t\t used);\n2866:\t\t\t\t\tif (err \u003c 0) {\n2867:\t\t\t\t\t\tif (!copied)\n2868:\t\t\t\t\t\t\tcopied = err;\n2869:\t\n2870:\t\t\t\t\t\tbreak;\n2871:\t\t\t\t\t}\n2872:\t\t\t\t\tused = err;\n2873:\t\t\t\t}\n2874:\t\t\t}\n"
} |
| 33/2 | 2026/10/10 08:48 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 43057 output: 37 thoughts: 25 Thoughts: Here's my take on the process, considering I'm an expert and this is what's running through my head: **Diving into `tcp.c` Above Line 2855** Okay, so I need to understand what's going on above line 2855 in `net/ipv4/tcp.c`. Right away, my brain starts to chunk. That specific area likely relates to [insert specific area of expertise based on context, e.g., "congestion control," "the SYN processing path," or "TCP connection state transitions"]. Given where that line likely sits within the overall file, I'm probably looking at [infer file location details, e.g., "some sort of receive logic," "the handling of RST packets," or "a section on retransmission timers."]. Let me quickly recall the overall structure of `tcp.c`. The file's organized pretty logically, so I can probably anticipate the broader context. Line 2855 usually lands in the area that handles [insert inferred function of the code], but I need a quick visual check. I can visualize the likely surrounding code, and I'll jump straight to the relevant part. I might need to cross-reference with related files like `tcp_output.c`, `tcp_input.c`, and potentially relevant headers like `tcp.h` or `inet_connection_sock.h` to fully grasp the interactions. This is going to be about understanding the data flow and how the kernel handles the TCP state machine, or maybe some specific corner case. Let's get to it. |
| 34/2 | 2026/10/10 08:48 | tool | read-file |
0mArgs:{
"File": "net/ipv4/tcp.c",
"FirstLine": 2835,
"LineCount": 25
}Results: {
"Contents": "2835:\t\t\t\t\t\tused = urg_offset;\n2836:\t\t\t\t}\n2837:\t\t\t}\n2838:\t\n2839:\t\t\tif (!(flags \u0026 MSG_TRUNC)) {\n2840:\t\t\t\tif (last_copied_dmabuf != -1 \u0026\u0026\n2841:\t\t\t\t last_copied_dmabuf != !skb_frags_readable(skb))\n2842:\t\t\t\t\tbreak;\n2843:\t\n2844:\t\t\t\tif (skb_frags_readable(skb)) {\n2845:\t\t\t\t\terr = skb_copy_datagram_msg(skb, offset, msg,\n2846:\t\t\t\t\t\t\t\t used);\n2847:\t\t\t\t\tif (err) {\n2848:\t\t\t\t\t\t/* Exception. Bailout! */\n2849:\t\t\t\t\t\tif (!copied)\n2850:\t\t\t\t\t\t\tcopied = -EFAULT;\n2851:\t\t\t\t\t\tbreak;\n2852:\t\t\t\t\t}\n2853:\t\t\t\t} else {\n2854:\t\t\t\t\tif (!(flags \u0026 MSG_SOCK_DEVMEM)) {\n2855:\t\t\t\t\t\t/* dmabuf skbs can only be received\n2856:\t\t\t\t\t\t * with the MSG_SOCK_DEVMEM flag.\n2857:\t\t\t\t\t\t */\n2858:\t\t\t\t\t\tif (!copied)\n2859:\t\t\t\t\t\t\tcopied = -EFAULT;\n"
} |
| 35/2 | 2026/10/10 08:48 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 43809 output: 37 thoughts: 82 Thoughts: Okay, let's break this down. **Investigating a Data Copy Pathway** Alright, so I'm trying to trace the data flow here. The function or code block I'm interested in is clearly protected by a few conditions: first, `!skb_frags_readable(skb)`. This suggests something related to Scatter-Gather I/O with network buffers. Then there's the `MSG_SOCK_DEVMEM` flag. That's a strong hint – we're dealing with device memory mapping in the socket receive path. My primary question is whether this potential data copying (`tcp_recvmsg_dmabuf`, specifically) is conditionally compiled. I need to know if there are any preprocessor directives (`#ifdef`) that might control whether this is even *used*. If it *is* used, it tells us a lot about the configuration of this system. This implies that there is configuration that could make this code path active. Now, where do I find this function? The codebase is probably the kernel. I should look in `net/ipv4/tcp.c`. Based on my experience, I'd wager it's around line 2470. I should be able to get a pretty good idea of what's going on by looking at the surrounding context there. Let's see what's in that area. |
| 36/2 | 2026/10/10 08:48 | tool | read-file |
0mArgs:{
"File": "net/ipv4/tcp.c",
"FirstLine": 2460,
"LineCount": 20
}Results: {
"Contents": "2460:\t\tp-\u003eidx = 0;\n2461:\t}\n2462:\t\n2463:\tstatic void tcp_xa_pool_commit(struct sock *sk, struct tcp_xa_pool *p)\n2464:\t{\n2465:\t\tif (!p-\u003emax)\n2466:\t\t\treturn;\n2467:\t\n2468:\t\txa_lock_bh(\u0026sk-\u003esk_user_frags);\n2469:\t\n2470:\t\ttcp_xa_pool_commit_locked(sk, p);\n2471:\t\n2472:\t\txa_unlock_bh(\u0026sk-\u003esk_user_frags);\n2473:\t}\n2474:\t\n2475:\tstatic int tcp_xa_pool_refill(struct sock *sk, struct tcp_xa_pool *p,\n2476:\t\t\t\t unsigned int max_frags)\n2477:\t{\n2478:\t\tint err, k;\n2479:\t\n"
} |
| 37/2 | 2026/10/10 08:48 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 44496 output: 31 thoughts: 45 Thoughts: Here's my take on the thought process: **Focusing on Network Configuration** Okay, so I'm observing that this code is *always* built with TCP. No conditional compilation around the `tcp_recvmsg_dmabuf` function, which is interesting. Now, my next step is to dive into the `net/xfrm/xfrm_iptfs.c` configuration. This suggests that the issue might be related to the interaction between the TCP implementation and the XFRM (IPsec) subsystem, specifically the IP traffic filtering and security policy mechanisms that XFRM provides. It's a classic place where connection establishment, data flow, and security policies can interact in unexpected ways. Time to see what I can uncover there. |
| 38/2 | 2026/10/10 08:48 | tool | grepper |
0mArgs:{
"Expression": "xfrm_iptfs",
"PathPrefixes": [
"net/xfrm/"
]
}Results: {
"Output": "\nFull output is too long, showing 200 out of 239 lines.\nUse more precise expression or PathPrefixes if possible.\n\nNumber of matching lines per file (3 files in total):\nnet/xfrm/Makefile:1\nnet/xfrm/trace_iptfs.h:9\nnet/xfrm/xfrm_iptfs.c:46\n\nnet/xfrm/Makefile=23=obj-$(CONFIG_XFRM_INTERFACE) += xfrm_interface.o\nnet/xfrm/Makefile:24:obj-$(CONFIG_XFRM_IPTFS) += xfrm_iptfs.o\nnet/xfrm/Makefile-25-obj-$(CONFIG_XFRM_ESPINTCP) += espintcp.o\n--\nnet/xfrm/trace_iptfs.h-19-\nnet/xfrm/trace_iptfs.h:20:struct xfrm_iptfs_data;\nnet/xfrm/trace_iptfs.h-21-\nnet/xfrm/trace_iptfs.h=22=TRACE_EVENT(iptfs_egress_recv,\nnet/xfrm/trace_iptfs.h:23:\t TP_PROTO(struct sk_buff *skb, struct xfrm_iptfs_data *xtfs, u16 blkoff),\nnet/xfrm/trace_iptfs.h-24-\t TP_ARGS(skb, xtfs, blkoff),\n--\nnet/xfrm/trace_iptfs.h=68=DECLARE_EVENT_CLASS(iptfs_ingress_preq_event,\nnet/xfrm/trace_iptfs.h:69:\t\t TP_PROTO(struct sk_buff *skb, struct xfrm_iptfs_data *xtfs,\nnet/xfrm/trace_iptfs.h-70-\t\t\t u32 pmtu, u8 was_gso),\n--\nnet/xfrm/trace_iptfs.h=96=DEFINE_EVENT(iptfs_ingress_preq_event, iptfs_enqueue,\nnet/xfrm/trace_iptfs.h:97:\t TP_PROTO(struct sk_buff *skb, struct xfrm_iptfs_data *xtfs, u32 pmtu, u8 was_gso),\nnet/xfrm/trace_iptfs.h-98-\t TP_ARGS(skb, xtfs, pmtu, was_gso));\n--\nnet/xfrm/trace_iptfs.h=100=DEFINE_EVENT(iptfs_ingress_preq_event, iptfs_no_queue_space,\nnet/xfrm/trace_iptfs.h:101:\t TP_PROTO(struct sk_buff *skb, struct xfrm_iptfs_data *xtfs, u32 pmtu, u8 was_gso),\nnet/xfrm/trace_iptfs.h-102-\t TP_ARGS(skb, xtfs, pmtu, was_gso));\n--\nnet/xfrm/trace_iptfs.h=104=DEFINE_EVENT(iptfs_ingress_preq_event, iptfs_too_big,\nnet/xfrm/trace_iptfs.h:105:\t TP_PROTO(struct sk_buff *skb, struct xfrm_iptfs_data *xtfs, u32 pmtu, u8 was_gso),\nnet/xfrm/trace_iptfs.h-106-\t TP_ARGS(skb, xtfs, pmtu, was_gso));\n--\nnet/xfrm/trace_iptfs.h=192=DECLARE_EVENT_CLASS(iptfs_timer_event,\nnet/xfrm/trace_iptfs.h:193:\t\t TP_PROTO(struct xfrm_iptfs_data *xtfs, u64 time_val),\nnet/xfrm/trace_iptfs.h-194-\t\t TP_ARGS(xtfs, time_val),\n--\nnet/xfrm/trace_iptfs.h=203=DEFINE_EVENT(iptfs_timer_event, iptfs_timer_start,\nnet/xfrm/trace_iptfs.h:204:\t TP_PROTO(struct xfrm_iptfs_data *xtfs, u64 time_val),\nnet/xfrm/trace_iptfs.h-205-\t TP_ARGS(xtfs, time_val));\n--\nnet/xfrm/trace_iptfs.h=207=DEFINE_EVENT(iptfs_timer_event, iptfs_timer_expire,\nnet/xfrm/trace_iptfs.h:208:\t TP_PROTO(struct xfrm_iptfs_data *xtfs, u64 time_val),\nnet/xfrm/trace_iptfs.h-209-\t TP_ARGS(xtfs, time_val));\n--\nnet/xfrm/xfrm_iptfs.c-1-// SPDX-License-Identifier: GPL-2.0\nnet/xfrm/xfrm_iptfs.c:2:/* xfrm_iptfs: IPTFS encapsulation support\nnet/xfrm/xfrm_iptfs.c-3- *\n--\nnet/xfrm/xfrm_iptfs.c-103-/**\nnet/xfrm/xfrm_iptfs.c:104: * struct xfrm_iptfs_config - configuration for the IPTFS tunnel.\nnet/xfrm/xfrm_iptfs.c-105- * @pkt_size: size of the outer IP packet. 0 to use interface and MTU discovery,\n--\nnet/xfrm/xfrm_iptfs.c-113- */\nnet/xfrm/xfrm_iptfs.c:114:struct xfrm_iptfs_config {\nnet/xfrm/xfrm_iptfs.c-115-\tu32 pkt_size;\t /* outer_packet_size or 0 */\n--\nnet/xfrm/xfrm_iptfs.c=121=struct skb_wseq {\n--\nnet/xfrm/xfrm_iptfs.c-126-/**\nnet/xfrm/xfrm_iptfs.c:127: * struct xfrm_iptfs_data - mode specific xfrm state.\nnet/xfrm/xfrm_iptfs.c-128- * @cfg: IPTFS tunnel config.\n--\nnet/xfrm/xfrm_iptfs.c-148- */\nnet/xfrm/xfrm_iptfs.c:149:struct xfrm_iptfs_data {\nnet/xfrm/xfrm_iptfs.c:150:\tstruct xfrm_iptfs_config cfg;\nnet/xfrm/xfrm_iptfs.c-151-\n--\nnet/xfrm/xfrm_iptfs.c=585=iptfs_pskb_extract_seq(u32 skblen, struct skb_seq_state *st, u32 off, int len)\n--\nnet/xfrm/xfrm_iptfs.c-608- */\nnet/xfrm/xfrm_iptfs.c:609:static void iptfs_input_save_runt(struct xfrm_iptfs_data *xtfs, u64 seq,\nnet/xfrm/xfrm_iptfs.c-610-\t\t\t\t u8 *buf, int len)\n--\nnet/xfrm/xfrm_iptfs.c=666=static void iptfs_complete_inner_skb(struct xfrm_state *x, struct sk_buff *skb)\n--\nnet/xfrm/xfrm_iptfs.c-704-\nnet/xfrm/xfrm_iptfs.c:705:static void __iptfs_reassem_done(struct xfrm_iptfs_data *xtfs, bool free)\nnet/xfrm/xfrm_iptfs.c-706-{\n--\nnet/xfrm/xfrm_iptfs.c-719- */\nnet/xfrm/xfrm_iptfs.c:720:static void iptfs_reassem_abort(struct xfrm_iptfs_data *xtfs)\nnet/xfrm/xfrm_iptfs.c-721-{\n--\nnet/xfrm/xfrm_iptfs.c-728- */\nnet/xfrm/xfrm_iptfs.c:729:static void iptfs_reassem_done(struct xfrm_iptfs_data *xtfs)\nnet/xfrm/xfrm_iptfs.c-730-{\n--\nnet/xfrm/xfrm_iptfs.c-748- */\nnet/xfrm/xfrm_iptfs.c:749:static u32 iptfs_reassem_cont(struct xfrm_iptfs_data *xtfs, u64 seq,\nnet/xfrm/xfrm_iptfs.c-750-\t\t\t struct skb_seq_state *st, struct sk_buff *skb,\n--\nnet/xfrm/xfrm_iptfs.c=950=static bool __input_process_payload(struct xfrm_state *x, u32 data,\n--\nnet/xfrm/xfrm_iptfs.c-958-\tconst unsigned char *old_mac;\nnet/xfrm/xfrm_iptfs.c:959:\tstruct xfrm_iptfs_data *xtfs;\nnet/xfrm/xfrm_iptfs.c-960-\tstruct iphdr *iph;\n--\nnet/xfrm/xfrm_iptfs.c=1225=static void iptfs_input_ordered(struct xfrm_state *x, struct sk_buff *skb)\n--\nnet/xfrm/xfrm_iptfs.c-1229-\tstruct list_head sublist; /* rename this it's just a list */\nnet/xfrm/xfrm_iptfs.c:1230:\tstruct xfrm_iptfs_data *xtfs;\nnet/xfrm/xfrm_iptfs.c-1231-\tstruct ip_iptfs_hdr *ipth;\n--\nnet/xfrm/xfrm_iptfs.c-1307-\nnet/xfrm/xfrm_iptfs.c:1308:static void __vec_shift(struct xfrm_iptfs_data *xtfs, u32 shift)\nnet/xfrm/xfrm_iptfs.c-1309-{\n--\nnet/xfrm/xfrm_iptfs.c-1321-\nnet/xfrm/xfrm_iptfs.c:1322:static void __reorder_past(struct xfrm_iptfs_data *xtfs, struct sk_buff *inskb,\nnet/xfrm/xfrm_iptfs.c-1323-\t\t\t struct list_head *freelist)\n--\nnet/xfrm/xfrm_iptfs.c-1327-\nnet/xfrm/xfrm_iptfs.c:1328:static u32 __reorder_drop(struct xfrm_iptfs_data *xtfs, struct list_head *list)\nnet/xfrm/xfrm_iptfs.c-1329-\n--\nnet/xfrm/xfrm_iptfs.c-1374-\nnet/xfrm/xfrm_iptfs.c:1375:static void __reorder_this(struct xfrm_iptfs_data *xtfs, struct sk_buff *inskb,\nnet/xfrm/xfrm_iptfs.c-1376-\t\t\t struct list_head *list)\n--\nnet/xfrm/xfrm_iptfs.c-1403- */\nnet/xfrm/xfrm_iptfs.c:1404:static void iptfs_set_window_drop_times(struct xfrm_iptfs_data *xtfs, int index)\nnet/xfrm/xfrm_iptfs.c-1405-{\n--\nnet/xfrm/xfrm_iptfs.c-1430-\nnet/xfrm/xfrm_iptfs.c:1431:static void __reorder_future_fits(struct xfrm_iptfs_data *xtfs,\nnet/xfrm/xfrm_iptfs.c-1432-\t\t\t\t struct sk_buff *inskb,\n--\nnet/xfrm/xfrm_iptfs.c-1480-\nnet/xfrm/xfrm_iptfs.c:1481:static void __reorder_future_shifts(struct xfrm_iptfs_data *xtfs,\nnet/xfrm/xfrm_iptfs.c-1482-\t\t\t\t struct sk_buff *inskb,\n--\nnet/xfrm/xfrm_iptfs.c-1626- */\nnet/xfrm/xfrm_iptfs.c:1627:static void iptfs_input_reorder(struct xfrm_iptfs_data *xtfs,\nnet/xfrm/xfrm_iptfs.c-1628-\t\t\t\tstruct sk_buff *inskb, struct list_head *list,\n--\nnet/xfrm/xfrm_iptfs.c=1675=static enum hrtimer_restart iptfs_drop_timer(struct hrtimer *me)\n--\nnet/xfrm/xfrm_iptfs.c-1678-\tstruct list_head list;\nnet/xfrm/xfrm_iptfs.c:1679:\tstruct xfrm_iptfs_data *xtfs;\nnet/xfrm/xfrm_iptfs.c-1680-\tstruct xfrm_state *x;\n--\nnet/xfrm/xfrm_iptfs.c=1724=static int iptfs_input(struct xfrm_state *x, struct sk_buff *skb)\n--\nnet/xfrm/xfrm_iptfs.c-1726-\tstruct list_head freelist, list;\nnet/xfrm/xfrm_iptfs.c:1727:\tstruct xfrm_iptfs_data *xtfs = x-\u003emode_data;\nnet/xfrm/xfrm_iptfs.c-1728-\tstruct sk_buff *next;\n--\nnet/xfrm/xfrm_iptfs.c-1777- */\nnet/xfrm/xfrm_iptfs.c:1778:static bool iptfs_enqueue(struct xfrm_iptfs_data *xtfs, struct sk_buff *skb)\nnet/xfrm/xfrm_iptfs.c-1779-{\n--\nnet/xfrm/xfrm_iptfs.c-1801-\nnet/xfrm/xfrm_iptfs.c:1802:static int iptfs_get_cur_pmtu(struct xfrm_state *x, struct xfrm_iptfs_data *xtfs,\nnet/xfrm/xfrm_iptfs.c-1803-\t\t\t struct sk_buff *skb)\n--\nnet/xfrm/xfrm_iptfs.c=1840=static int iptfs_output_collect(struct net *net, struct sock *sk, struct sk_buff *skb)\n--\nnet/xfrm/xfrm_iptfs.c-1843-\tstruct xfrm_state *x = dst-\u003exfrm;\nnet/xfrm/xfrm_iptfs.c:1844:\tstruct xfrm_iptfs_data *xtfs = x-\u003emode_data;\nnet/xfrm/xfrm_iptfs.c-1845-\tstruct sk_buff *segs, *nskb;\n--\nnet/xfrm/xfrm_iptfs.c=1970=static struct sk_buff *iptfs_copy_create_frag(struct skb_seq_state *st, u32 offset, u32 copy_len)\n--\nnet/xfrm/xfrm_iptfs.c-2003- */\nnet/xfrm/xfrm_iptfs.c:2004:static int iptfs_copy_create_frags(struct sk_buff **skbp, struct xfrm_iptfs_data *xtfs, u32 mtu)\nnet/xfrm/xfrm_iptfs.c-2005-{\n--\nnet/xfrm/xfrm_iptfs.c-2100- */\nnet/xfrm/xfrm_iptfs.c:2101:static int iptfs_first_skb(struct sk_buff **skbp, struct xfrm_iptfs_data *xtfs, u32 mtu)\nnet/xfrm/xfrm_iptfs.c-2102-{\n--\nnet/xfrm/xfrm_iptfs.c=2193=static void iptfs_output_queued(struct xfrm_state *x, struct sk_buff_head *list)\nnet/xfrm/xfrm_iptfs.c-2194-{\nnet/xfrm/xfrm_iptfs.c:2195:\tstruct xfrm_iptfs_data *xtfs = x-\u003emode_data;\nnet/xfrm/xfrm_iptfs.c-2196-\tstruct sk_buff *skb, *skb2, **nextp;\n--\nnet/xfrm/xfrm_iptfs.c=2328=static enum hrtimer_restart iptfs_delay_timer(struct hrtimer *me)\n--\nnet/xfrm/xfrm_iptfs.c-2330-\tstruct sk_buff_head list;\nnet/xfrm/xfrm_iptfs.c:2331:\tstruct xfrm_iptfs_data *xtfs;\nnet/xfrm/xfrm_iptfs.c-2332-\tstruct xfrm_state *x;\n--\nnet/xfrm/xfrm_iptfs.c=2512=static u32 iptfs_get_inner_mtu(struct xfrm_state *x, int outer_mtu)\nnet/xfrm/xfrm_iptfs.c-2513-{\nnet/xfrm/xfrm_iptfs.c:2514:\tstruct xfrm_iptfs_data *xtfs = x-\u003emode_data;\nnet/xfrm/xfrm_iptfs.c-2515-\n--\nnet/xfrm/xfrm_iptfs.c=2531=static int iptfs_user_init(struct net *net, struct xfrm_state *x,\n--\nnet/xfrm/xfrm_iptfs.c-2534-{\nnet/xfrm/xfrm_iptfs.c:2535:\tstruct xfrm_iptfs_data *xtfs = x-\u003emode_data;\nnet/xfrm/xfrm_iptfs.c:2536:\tstruct xfrm_iptfs_config *xc;\nnet/xfrm/xfrm_iptfs.c-2537-\tu64 q;\n--\nnet/xfrm/xfrm_iptfs.c=2588=static unsigned int iptfs_sa_len(const struct xfrm_state *x)\nnet/xfrm/xfrm_iptfs.c-2589-{\nnet/xfrm/xfrm_iptfs.c:2590:\tstruct xfrm_iptfs_data *xtfs = x-\u003emode_data;\nnet/xfrm/xfrm_iptfs.c:2591:\tstruct xfrm_iptfs_config *xc = \u0026xtfs-\u003ecfg;\nnet/xfrm/xfrm_iptfs.c-2592-\tunsigned int l = 0;\n--\nnet/xfrm/xfrm_iptfs.c=2608=static int iptfs_copy_to_user(struct xfrm_state *x, struct sk_buff *skb)\nnet/xfrm/xfrm_iptfs.c-2609-{\nnet/xfrm/xfrm_iptfs.c:2610:\tstruct xfrm_iptfs_data *xtfs = x-\u003emode_data;\nnet/xfrm/xfrm_iptfs.c:2611:\tstruct xfrm_iptfs_config *xc = \u0026xtfs-\u003ecfg;\nnet/xfrm/xfrm_iptfs.c-2612-\tint ret = 0;\n\n"
} |
| 39/2 | 2026/10/10 08:48 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 49673 output: 33 thoughts: 14 Thoughts: Here's my summary, as though I'm thinking these thoughts: **XFRM & iptables Configuration Check** Alright, let's get down to brass tacks. I need to make sure this system is configured correctly for XFRM, specifically that `CONFIG_XFRM_IPTFS` is enabled. Without that, I'm going to have some serious problems further down the line with the intended setup. It's a fundamental piece, essentially the glue that lets me do what I need to do with IPsec and iptables integration. Time to verify that this critical component is enabled, before going any further. |
| 40/2 | 2026/10/10 08:48 | tool | grepper |
0mArgs:{
"Expression": "config XFRM_IPTFS",
"PathPrefixes": [
"net/xfrm/"
]
}Results: {
"Output": "net/xfrm/Kconfig=125=config NET_KEY_MIGRATE\n--\nnet/xfrm/Kconfig-140-\nnet/xfrm/Kconfig:141:config XFRM_IPTFS\nnet/xfrm/Kconfig-142-\ttristate \"IPsec IP-TFS/AGGFRAG (RFC 9347) encapsulation support\"\n"
} |
| 41/2 | 2026/10/10 08:48 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 49960 output: 35 thoughts: 25 Thoughts: Okay, here's my attempt at summarizing that thought process, tailored for an expert audience: **Investigating `XFRM_IPTFS` Dependencies in the XFRM Kconfig** Alright, let's dive into this. I need to understand the configuration dependencies for `XFRM_IPTFS` within the `net/xfrm/Kconfig` file. Specifically, I want to clarify its prerequisites. Is it a modular feature? Does it depend on specific cryptographic modules, XFRM core components, or particular networking protocols being enabled? Are there any hidden assumptions or build flags I need to be aware of to ensure it compiles and functions correctly with my desired configuration? This is important because understanding those dependencies is critical to designing a performant and reliable kernel module configuration. |
| 42/2 | 2026/10/10 08:48 | tool | read-file |
0mArgs:{
"File": "net/xfrm/Kconfig",
"FirstLine": 140,
"LineCount": 15
}Results: {
"Contents": " 140:\t\n 141:\tconfig XFRM_IPTFS\n 142:\t\ttristate \"IPsec IP-TFS/AGGFRAG (RFC 9347) encapsulation support\"\n 143:\t\tdepends on XFRM\n 144:\t\thelp\n 145:\t\t Information on the IP-TFS/AGGFRAG encapsulation can be found\n 146:\t\t in RFC 9347. This feature supports demand driven (i.e.,\n 147:\t\t non-constant send rate) IP-TFS to take advantage of the\n 148:\t\t AGGFRAG ESP payload encapsulation. This payload type\n 149:\t\t supports aggregation and fragmentation of the inner IP\n 150:\t\t packet stream which in turn yields higher small-packet\n 151:\t\t bandwidth as well as reducing MTU/PMTU issues. Congestion\n 152:\t\t control is unimplementated as the send rate is demand driven\n 153:\t\t rather than constant.\n 154:\t\n"
} |
| 43/2 | 2026/10/10 08:48 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 50453 output: 29 thoughts: 84 Thoughts: Okay, let's dive into this. Here's how I'm thinking about it: **Investigating TLS Configuration Options** Alright, I need to get a handle on how this TLS stuff is configured. Specifically, I'm going to look at these files within the `net/tls` directory: `tls_device.c`, `tls_device_fallback.c`, and `tls_strp.c`. They're probably related to the core TLS functionality, given their location. My initial assumption is confirmed as they are part of the TLS implementation, thanks to the mentions of `CONFIG_TLS` and `CONFIG_TLS_DEVICE`. Now, I'm specifically interested in `CONFIG_TLS_DEVICE`, so I'll go check out `net/tls/Kconfig` to see how it's defined and what the dependencies or implications are. That's where I'll get the real meat of the configuration details. That Kconfig file will reveal the conditions under which `CONFIG_TLS_DEVICE` is enabled, and that will give me a better understanding of the overall picture. |
| 44/2 | 2026/10/10 08:48 | tool | grepper |
0mArgs:{
"Expression": "config TLS_DEVICE",
"PathPrefixes": [
"net/tls/"
]
}Results: {
"Output": "net/tls/Kconfig=5=config TLS\n--\nnet/tls/Kconfig-18-\nnet/tls/Kconfig:19:config TLS_DEVICE\nnet/tls/Kconfig-20-\tbool \"Transport Layer Security HW offload\"\n"
} |
| 45/2 | 2026/10/10 08:49 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 50872 output: 29 thoughts: 31 Thoughts: Here's my thought process, as if I were considering this code: **Diving into TLS Implementation** Okay, so I need to understand the dependencies within this TLS implementation. My initial focus is on `net/tls/tls_strp.c`. My immediate question is: does this file have a dependency on either `TLS_DEVICE` or the more general `TLS` configuration option? Understanding this relationship is critical to figuring out how the code is structured and how different parts interface. That will really dictate how this module is enabled. I need to quickly determine which of these flags, or any others, govern its compilation or execution path. |
| 46/2 | 2026/10/10 08:49 | tool | grepper |
0mArgs:{
"Expression": "tls_strp",
"PathPrefixes": [
"net/tls/"
]
}Results: {
"Output": "\nFull output is too long, showing 200 out of 447 lines.\nUse more precise expression or PathPrefixes if possible.\n\nNumber of matching lines per file (6 files in total):\nnet/tls/Makefile:1\nnet/tls/tls.h:19\nnet/tls/tls_device.c:5\nnet/tls/tls_main.c:5\nnet/tls/tls_strp.c:71\nnet/tls/tls_sw.c:28\n\nnet/tls/Makefile=8=obj-$(CONFIG_TLS) += tls.o\nnet/tls/Makefile-9-\nnet/tls/Makefile:10:tls-y := tls_main.o tls_sw.o tls_proc.o trace.o tls_strp.o\nnet/tls/Makefile-11-\n--\nnet/tls/tls.h=144=void tls_err_abort(struct sock *sk, int err);\nnet/tls/tls.h:145:void tls_strp_abort_strp(struct tls_strparser *strp, int err);\nnet/tls/tls.h-146-\n--\nnet/tls/tls.h=183=int tls_sw_fallback_init(struct sock *sk,\n--\nnet/tls/tls.h-186-\nnet/tls/tls.h:187:int tls_strp_dev_init(void);\nnet/tls/tls.h:188:void tls_strp_dev_exit(void);\nnet/tls/tls.h-189-\nnet/tls/tls.h:190:void tls_strp_done(struct tls_strparser *strp);\nnet/tls/tls.h:191:void __tls_strp_done(struct tls_strparser *strp);\nnet/tls/tls.h:192:void tls_strp_stop(struct tls_strparser *strp);\nnet/tls/tls.h:193:int tls_strp_init(struct tls_strparser *strp, struct sock *sk);\nnet/tls/tls.h:194:void tls_strp_data_ready(struct tls_strparser *strp);\nnet/tls/tls.h-195-\nnet/tls/tls.h:196:void tls_strp_check_rcv(struct tls_strparser *strp, bool announce);\nnet/tls/tls.h:197:void tls_strp_msg_consume(struct tls_strparser *strp);\nnet/tls/tls.h-198-\nnet/tls/tls.h:199:int tls_rx_msg_size(struct tls_strparser *strp, struct sk_buff *skb);\nnet/tls/tls.h:200:void tls_rx_msg_maybe_announce(struct tls_strparser *strp);\nnet/tls/tls.h-201-\nnet/tls/tls.h:202:bool tls_strp_msg_load(struct tls_strparser *strp, bool force_refresh);\nnet/tls/tls.h:203:int tls_strp_msg_cow(struct tls_sw_context_rx *ctx);\nnet/tls/tls.h:204:struct sk_buff *tls_strp_msg_detach(struct tls_sw_context_rx *ctx);\nnet/tls/tls.h:205:int tls_strp_msg_hold(struct tls_strparser *strp, struct sk_buff_head *dst);\nnet/tls/tls.h-206-\nnet/tls/tls.h=207=static inline struct tls_msg *tls_msg(struct sk_buff *skb)\n--\nnet/tls/tls.h-213-\nnet/tls/tls.h:214:static inline struct sk_buff *tls_strp_msg(struct tls_sw_context_rx *ctx)\nnet/tls/tls.h-215-{\n--\nnet/tls/tls.h-219-\nnet/tls/tls.h:220:static inline bool tls_strp_msg_ready(struct tls_sw_context_rx *ctx)\nnet/tls/tls.h-221-{\n--\nnet/tls/tls.h-224-\nnet/tls/tls.h:225:static inline bool tls_strp_msg_mixed_decrypted(struct tls_sw_context_rx *ctx)\nnet/tls/tls.h-226-{\n--\nnet/tls/tls_device.c=886=tls_device_reencrypt(struct sock *sk, struct tls_context *tls_ctx)\n--\nnet/tls/tls_device.c-898-\nnet/tls/tls_device.c:899:\trxm = strp_msg(tls_strp_msg(sw_ctx));\nnet/tls/tls_device.c-900-\torig_buf = kmalloc(rxm-\u003efull_len + TLS_HEADER_SIZE + cipher_desc-\u003eiv,\n--\nnet/tls/tls_device.c-905-\nnet/tls/tls_device.c:906:\terr = tls_strp_msg_cow(sw_ctx);\nnet/tls/tls_device.c-907-\tif (unlikely(err))\n--\nnet/tls/tls_device.c-909-\nnet/tls/tls_device.c:910:\tskb = tls_strp_msg(sw_ctx);\nnet/tls/tls_device.c-911-\trxm = strp_msg(skb);\n--\nnet/tls/tls_device.c=977=int tls_device_decrypted(struct sock *sk, struct tls_context *tls_ctx)\n--\nnet/tls/tls_device.c-980-\tstruct tls_sw_context_rx *sw_ctx = tls_sw_ctx_rx(tls_ctx);\nnet/tls/tls_device.c:981:\tstruct sk_buff *skb = tls_strp_msg(sw_ctx);\nnet/tls/tls_device.c-982-\tstruct strp_msg *rxm = strp_msg(skb);\n--\nnet/tls/tls_device.c-984-\nnet/tls/tls_device.c:985:\tif (!tls_strp_msg_mixed_decrypted(sw_ctx)) {\nnet/tls/tls_device.c-986-\t\tis_decrypted = skb-\u003edecrypted;\n--\nnet/tls/tls_main.c=397=static __poll_t tls_sk_poll(struct file *file, struct socket *sock,\n--\nnet/tls/tls_main.c-417-\tif ((skb_queue_empty_lockless(\u0026ctx-\u003erx_list) \u0026\u0026\nnet/tls/tls_main.c:418:\t !tls_strp_msg_ready(ctx)) ||\nnet/tls/tls_main.c-419-\t READ_ONCE(ctx-\u003ekey_update_pending))\n--\nnet/tls/tls_main.c=621=static int do_tls_setsockopt_conf(struct sock *sk, sockptr_t optval,\n--\nnet/tls/tls_main.c-768-\nnet/tls/tls_main.c:769:\t\ttls_strp_check_rcv(\u0026rx_ctx-\u003estrp, true);\nnet/tls/tls_main.c-770-\t}\n--\nnet/tls/tls_main.c=1221=static int __init tls_register(void)\n--\nnet/tls/tls_main.c-1228-\nnet/tls/tls_main.c:1229:\terr = tls_strp_dev_init();\nnet/tls/tls_main.c-1230-\tif (err)\n--\nnet/tls/tls_main.c-1240-err_strp:\nnet/tls/tls_main.c:1241:\ttls_strp_dev_exit();\nnet/tls/tls_main.c-1242-err_pernet:\n--\nnet/tls/tls_main.c=1247=static void __exit tls_unregister(void)\n--\nnet/tls/tls_main.c-1249-\ttcp_unregister_ulp(\u0026tcp_tls_ulp_ops);\nnet/tls/tls_main.c:1250:\ttls_strp_dev_exit();\nnet/tls/tls_main.c-1251-\ttls_device_cleanup();\n--\nnet/tls/tls_strp.c-13-\nnet/tls/tls_strp.c:14:static struct workqueue_struct *tls_strp_wq;\nnet/tls/tls_strp.c-15-\nnet/tls/tls_strp.c:16:void tls_strp_abort_strp(struct tls_strparser *strp, int err)\nnet/tls/tls_strp.c-17-{\n--\nnet/tls/tls_strp.c-29-\nnet/tls/tls_strp.c:30:static void tls_strp_anchor_free(struct tls_strparser *strp)\nnet/tls/tls_strp.c-31-{\n--\nnet/tls/tls_strp.c=41=static struct sk_buff *\nnet/tls/tls_strp.c:42:tls_strp_skb_copy(struct tls_strparser *strp, struct sk_buff *in_skb,\nnet/tls/tls_strp.c-43-\t\t int offset, int len)\n--\nnet/tls/tls_strp.c-68-/* Create a new skb with the contents of input copied to its page frags */\nnet/tls/tls_strp.c:69:static struct sk_buff *tls_strp_msg_make_copy(struct tls_strparser *strp)\nnet/tls/tls_strp.c-70-{\n--\nnet/tls/tls_strp.c-73-\nnet/tls/tls_strp.c:74:\tskb = tls_strp_skb_copy(strp, strp-\u003eanchor, strp-\u003estm.offset,\nnet/tls/tls_strp.c-75-\t\t\t\tstrp-\u003estm.full_len);\n--\nnet/tls/tls_strp.c-84-/* Steal the input skb, input msg is invalid after calling this function */\nnet/tls/tls_strp.c:85:struct sk_buff *tls_strp_msg_detach(struct tls_sw_context_rx *ctx)\nnet/tls/tls_strp.c-86-{\nnet/tls/tls_strp.c:87:\tstruct tls_strparser *strp = \u0026ctx-\u003estrp;\nnet/tls/tls_strp.c-88-\n--\nnet/tls/tls_strp.c-112-\nnet/tls/tls_strp.c:113:\treturn tls_strp_msg_make_copy(strp);\nnet/tls/tls_strp.c-114-}\n--\nnet/tls/tls_strp.c-119- */\nnet/tls/tls_strp.c:120:int tls_strp_msg_cow(struct tls_sw_context_rx *ctx)\nnet/tls/tls_strp.c-121-{\nnet/tls/tls_strp.c:122:\tstruct tls_strparser *strp = \u0026ctx-\u003estrp;\nnet/tls/tls_strp.c-123-\tstruct sk_buff *skb;\n--\nnet/tls/tls_strp.c-127-\nnet/tls/tls_strp.c:128:\tskb = tls_strp_msg_make_copy(strp);\nnet/tls/tls_strp.c-129-\tif (!skb)\n--\nnet/tls/tls_strp.c-131-\nnet/tls/tls_strp.c:132:\ttls_strp_anchor_free(strp);\nnet/tls/tls_strp.c-133-\tstrp-\u003eanchor = skb;\n--\nnet/tls/tls_strp.c-144- */\nnet/tls/tls_strp.c:145:int tls_strp_msg_hold(struct tls_strparser *strp, struct sk_buff_head *dst)\nnet/tls/tls_strp.c-146-{\n--\nnet/tls/tls_strp.c-191-\nnet/tls/tls_strp.c:192:static void tls_strp_flush_anchor_copy(struct tls_strparser *strp)\nnet/tls/tls_strp.c-193-{\n--\nnet/tls/tls_strp.c-209-\nnet/tls/tls_strp.c:210:static int tls_strp_copyin_frag(struct tls_strparser *strp, struct sk_buff *skb,\nnet/tls/tls_strp.c-211-\t\t\t\tstruct sk_buff *in_skb, unsigned int offset,\n--\nnet/tls/tls_strp.c-285-\nnet/tls/tls_strp.c:286:static int tls_strp_copyin_skb(struct tls_strparser *strp, struct sk_buff *skb,\nnet/tls/tls_strp.c-287-\t\t\t struct sk_buff *in_skb, unsigned int offset,\n--\nnet/tls/tls_strp.c-300-\nnet/tls/tls_strp.c:301:\tnskb = tls_strp_skb_copy(strp, in_skb, offset, chunk);\nnet/tls/tls_strp.c-302-\tif (!nskb)\n--\nnet/tls/tls_strp.c-341-\nnet/tls/tls_strp.c:342:static int tls_strp_copyin(read_descriptor_t *desc, struct sk_buff *in_skb,\nnet/tls/tls_strp.c-343-\t\t\t unsigned int offset, size_t in_len)\nnet/tls/tls_strp.c-344-{\nnet/tls/tls_strp.c:345:\tstruct tls_strparser *strp = (struct tls_strparser *)desc-\u003earg.data;\nnet/tls/tls_strp.c-346-\tstruct sk_buff *skb;\n--\nnet/tls/tls_strp.c-358-\tif (IS_ENABLED(CONFIG_TLS_DEVICE) \u0026\u0026 strp-\u003emixed_decrypted)\nnet/tls/tls_strp.c:359:\t\tret = tls_strp_copyin_skb(strp, skb, in_skb, offset, in_len);\nnet/tls/tls_strp.c-360-\telse\nnet/tls/tls_strp.c:361:\t\tret = tls_strp_copyin_frag(strp, skb, in_skb, offset, in_len);\nnet/tls/tls_strp.c-362-\tif (ret \u003c 0) {\n--\nnet/tls/tls_strp.c-375-\nnet/tls/tls_strp.c:376:static int tls_strp_read_copyin(struct tls_strparser *strp)\nnet/tls/tls_strp.c-377-{\n--\nnet/tls/tls_strp.c-384-\t/* sk should be locked here, so okay to do read_sock */\nnet/tls/tls_strp.c:385:\ttcp_read_sock(strp-\u003esk, \u0026desc, tls_strp_copyin);\nnet/tls/tls_strp.c-386-\n--\nnet/tls/tls_strp.c-389-\nnet/tls/tls_strp.c:390:static int tls_strp_read_copy(struct tls_strparser *strp, bool qshort)\nnet/tls/tls_strp.c-391-{\n--\nnet/tls/tls_strp.c-410-\t\tif (!page) {\nnet/tls/tls_strp.c:411:\t\t\ttls_strp_flush_anchor_copy(strp);\nnet/tls/tls_strp.c-412-\t\t\treturn -ENOMEM;\n--\nnet/tls/tls_strp.c-427-\nnet/tls/tls_strp.c:428:\ttls_strp_read_copyin(strp);\nnet/tls/tls_strp.c-429-\n--\nnet/tls/tls_strp.c-432-\nnet/tls/tls_strp.c:433:static bool tls_strp_check_queue_ok(struct tls_strparser *strp,\nnet/tls/tls_strp.c-434-\t\t\t\t unsigned int len)\n\n"
} |
| 47/2 | 2026/10/10 08:49 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 55360 output: 40 thoughts: 1559 Thoughts: **Focusing on Targeted Network Functionality** Okay, so I'm diving into this code, keeping in mind the need to avoid listing ubiquitous functions that'll just clutter the fuzzing effort. We want to pinpoint the *specific* changes and entrypoints tied to this patch. The fact that `tls_strp.c` is compiled under `CONFIG_TLS` and `CONFIG_INET` is basic networking is important context. First, I'm noting the static inline functions that have been touched in `include/linux/skbuff_ref.h`: `skb_netmem_ref`, `skb_frag_ref`, `skb_netmem_unref`, and `skb_frag_unref`. Also, from `include/net/netmem.h`, the related static inlines `get_netmem` and `put_netmem` are touched. Since static inlines can't be targeted directly, I'm making sure to identify their non-static callers. Now, let's see which *exported* functions have been modified or call these inlines. In `net/core/skbuff.c`, I've got: * `napi_pp_get_page` and `napi_pp_put_page` (both EXPORT_SYMBOL, so good targets). * `get_net_iov` and `put_net_iov` (also EXPORT_SYMBOL, replacing some older functions, and noting those `DEBUG_NET_WARN_ON_ONCE` instances). * `skb_zerocopy`, `skb_split`, `skb_shift`, `skb_segment`, `skb_try_coalesce`, and `__pskb_copy_fclone` (all EXPORT_SYMBOL) – these are all modified to use the ref/unref functions. In `net/ipv4/tcp.c`, `tcp_recvmsg_dmabuf` calls `napi_pp_get_page`, which I’m marking. In `drivers/net/veth.c`, `veth_xdp_rcv_skb` is a caller of static `veth_xdp_get`, which is worth keeping in mind. In `net/xfrm/xfrm_iptfs.c`, `iptfs_pskb_add_frags` / `iptfs_skb_add_frags` are updated. Now, looking at `net/tls/tls_strp.c`, `tls_strp_flush_anchor_copy` is a static helper, called by `tls_strp_read_copy` and `tls_strp_msg_consume`, therefore, these are potential targets. Finally, in `net/ipv4/esp4.c` / `net/ipv6/esp6.c` I see that `esp_output_done` / `esp_output_tail` / `esp6_output_done` / `esp6_output_tail` all call the static `esp_ssg_unref`, which in turn, calls `skb_netmem_unref`. Now, I'm checking if all the functions I've identified are indeed exported, and I can confirm that `napi_pp_get_page`, `napi_pp_put_page`, `get_net_iov`, `put_net_iov`, `skb_zerocopy`, `skb_split`, `skb_shift`, `skb_segment`, `skb_try_coalesce`, and `__pskb_copy_fclone` are all properly EXPORT_SYMBOL'd and thus targetable. I also need to make sure to target `tls_strp_msg_consume` and `tls_strp_read_copy`, and `veth_xdp_rcv_skb`. |
| 48/2 | 2026/10/10 08:49 | tool | grepper |
0mArgs:{
"Expression": "veth_xdp_rcv_skb",
"PathPrefixes": [
"drivers/net/veth.c"
]
}Results: {
"Output": "drivers/net/veth.c=752=static int veth_convert_skb_to_xdp_buff(struct veth_rq *rq,\n--\ndrivers/net/veth.c-790-\ndrivers/net/veth.c:791:static struct sk_buff *veth_xdp_rcv_skb(struct veth_rq *rq,\ndrivers/net/veth.c-792-\t\t\t\t\tstruct sk_buff *skb,\n--\ndrivers/net/veth.c=908=static int veth_xdp_rcv(struct veth_rq *rq, int budget,\n--\ndrivers/net/veth.c-940-\t\t\tstats-\u003exdp_bytes += skb-\u003elen;\ndrivers/net/veth.c:941:\t\t\tskb = veth_xdp_rcv_skb(rq, skb, bq, stats);\ndrivers/net/veth.c-942-\t\t\tif (skb) {\n"
} |
| 49/2 | 2026/10/10 08:49 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 58070 output: 33 thoughts: 29 Thoughts: Okay, here's my take on those thoughts, framed for an expert audience: **Diving into the veth.c Transmit Path** Right, time to get my hands dirty. I'm thinking I need to zero in on the core data transfer process within the veth driver. Given my familiarity with the usual suspects in network driver performance bottlenecks, my initial gut feeling tells me the `veth_xmit` function or perhaps the polling function `veth_poll` are going to be key. They're both prime locations where the kernel interacts directly with the virtual ethernet device. `veth_xmit` is where the packets would be submitted by the virtual device to the kernel, and `veth_poll` is where the kernel would retrieve them. So, let's start with `drivers/net/veth.c`, and see what's what in those functions. Likely I'll find how the ring buffer and DMA are handled. I'll need to confirm if there are any obvious memory copies, spinlocks, or other contention points. Perhaps, if this doesn't lead to anything, I will have to look at the other functions within this driver and consider what interactions may lead to performance overhead. This is going to be interesting. |
| 50/2 | 2026/10/10 08:49 | tool | grepper |
0mArgs:{
"Expression": "veth_poll",
"PathPrefixes": [
"drivers/net/veth.c"
]
}Results: {
"Output": "drivers/net/veth.c=908=static int veth_xdp_rcv(struct veth_rq *rq, int budget,\n--\ndrivers/net/veth.c-965-\ndrivers/net/veth.c:966:static int veth_poll(struct napi_struct *napi, int budget)\ndrivers/net/veth.c-967-{\n--\ndrivers/net/veth.c=1120=static int veth_enable_xdp_range(struct net_device *dev, int start, int end,\n--\ndrivers/net/veth.c-1129-\t\tif (!napi_already_on)\ndrivers/net/veth.c:1130:\t\t\tnetif_napi_add(dev, \u0026rq-\u003exdp_napi, veth_poll);\ndrivers/net/veth.c-1131-\t\terr = xdp_rxq_info_reg(\u0026rq-\u003exdp_rxq, dev, i, rq-\u003exdp_napi.napi_id);\n--\ndrivers/net/veth.c=1221=static int veth_napi_enable_range(struct net_device *dev, int start, int end)\n--\ndrivers/net/veth.c-1228-\ndrivers/net/veth.c:1229:\t\tnetif_napi_add(dev, \u0026rq-\u003exdp_napi, veth_poll);\ndrivers/net/veth.c-1230-\t}\n--\ndrivers/net/veth.c=1476=static void veth_dev_free(struct net_device *dev)\n--\ndrivers/net/veth.c-1481-#ifdef CONFIG_NET_POLL_CONTROLLER\ndrivers/net/veth.c:1482:static void veth_poll_controller(struct net_device *dev)\ndrivers/net/veth.c-1483-{\n--\ndrivers/net/veth.c=1713=static const struct net_device_ops veth_netdev_ops = {\n--\ndrivers/net/veth.c-1721-#ifdef CONFIG_NET_POLL_CONTROLLER\ndrivers/net/veth.c:1722:\t.ndo_poll_controller\t= veth_poll_controller,\ndrivers/net/veth.c-1723-#endif\n"
} |
| 51/2 | 2026/10/10 08:49 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 58999 output: 34 thoughts: 68 Thoughts: Here's my analysis of that snippet: **Dissecting the veth_xdp_rcv_skb Path** Okay, so I need to trace the path to `veth_xdp_rcv_skb`, and I'm focusing on how it’s reached, with and without NAPI. The goal is to understand the packet flow in the veth virtual Ethernet interface, and how XDP is integrated. My initial hunch is that `veth_xmit` in `drivers/net/veth.c` is the key. Given that `veth_xmit` handles packet *sending* on the veth interface, it probably acts as the entry point from which we can follow the call chain to `veth_xdp_rcv_skb` based on its context. Let's dig into `veth_xmit` and see how the packets traverse through the other side of the veth pair. |
| 52/2 | 2026/10/10 08:49 | tool | grepper |
0mArgs:{
"Expression": "veth_xmit",
"PathPrefixes": [
"drivers/net/veth.c"
]
}Results: {
"Output": "drivers/net/veth.c=339=static bool veth_skb_is_eligible_for_gro(const struct net_device *dev,\n--\ndrivers/net/veth.c-347-\ndrivers/net/veth.c:348:static netdev_tx_t veth_xmit(struct sk_buff *skb, struct net_device *dev)\ndrivers/net/veth.c-349-{\n--\ndrivers/net/veth.c=1713=static const struct net_device_ops veth_netdev_ops = {\n--\ndrivers/net/veth.c-1716-\t.ndo_stop = veth_close,\ndrivers/net/veth.c:1717:\t.ndo_start_xmit = veth_xmit,\ndrivers/net/veth.c-1718-\t.ndo_get_stats64 = veth_get_stats64,\n"
} |
| 53/2 | 2026/10/10 08:49 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 59555 output: 36 thoughts: 14 Thoughts: Okay, let's dive into this. Here's my understanding, presented as a personal thought process, given my expertise: **Diving into `veth_xmit`** Alright, I need to understand what `veth_xmit` is up to. I'm assuming, given the name, it's involved in transmitting data, likely within a virtual Ethernet context, probably related to a veth pair. I immediately want to grasp the specifics of its functionality. What are its core responsibilities? How does it interact with the network stack, and with other components like the `veth` driver itself? What data structures are involved and how are they manipulated? Is it called from the upper layers, or is this a lower-level function? I'll probably be most interested in how it handles the interaction with the underlying physical NIC or the virtual counterpart on the other end of the `veth` pair, depending on which side I'm looking at. I'll need to think about potential locking issues, concurrency concerns, and error handling as well, especially considering it's probably handling packet transmission. This seems like a critical piece to analyze for the bigger picture. |
| 54/2 | 2026/10/10 08:49 | tool | read-file |
0mArgs:{
"File": "drivers/net/veth.c",
"FirstLine": 348,
"LineCount": 40
}Results: {
"Contents": " 348:\tstatic netdev_tx_t veth_xmit(struct sk_buff *skb, struct net_device *dev)\n 349:\t{\n 350:\t\tstruct veth_priv *rcv_priv, *priv = netdev_priv(dev);\n 351:\t\tstruct veth_rq *rq = NULL;\n 352:\t\tstruct netdev_queue *txq;\n 353:\t\tstruct net_device *rcv;\n 354:\t\tint length = skb-\u003elen;\n 355:\t\tbool use_napi = false;\n 356:\t\tint ret, rxq;\n 357:\t\n 358:\t\trcu_read_lock();\n 359:\t\trcv = rcu_dereference(priv-\u003epeer);\n 360:\t\tif (unlikely(!rcv) || !pskb_may_pull(skb, ETH_HLEN)) {\n 361:\t\t\tkfree_skb(skb);\n 362:\t\t\tgoto drop;\n 363:\t\t}\n 364:\t\n 365:\t\trcv_priv = netdev_priv(rcv);\n 366:\t\trxq = skb_get_queue_mapping(skb);\n 367:\t\tif (rxq \u003c rcv-\u003ereal_num_rx_queues) {\n 368:\t\t\trq = \u0026rcv_priv-\u003erq[rxq];\n 369:\t\n 370:\t\t\t/* The napi pointer is available when an XDP program is\n 371:\t\t\t * attached or when GRO is enabled\n 372:\t\t\t * Don't bother with napi/GRO if the skb can't be aggregated\n 373:\t\t\t */\n 374:\t\t\tuse_napi = rcu_access_pointer(rq-\u003enapi) \u0026\u0026\n 375:\t\t\t\t veth_skb_is_eligible_for_gro(dev, rcv, skb);\n 376:\t\t}\n 377:\t\n 378:\t\tskb_tx_timestamp(skb);\n 379:\t\n 380:\t\tret = veth_forward_skb(rcv, skb, rq, use_napi);\n 381:\t\tswitch (ret) {\n 382:\t\tcase NET_RX_SUCCESS: /* same as NETDEV_TX_OK */\n 383:\t\t\tif (!use_napi)\n 384:\t\t\t\tdev_sw_netstats_tx_add(dev, 1, length);\n 385:\t\t\telse\n 386:\t\t\t\t__veth_xdp_flush(rq);\n 387:\t\t\tbreak;\n"
} |
| 55/2 | 2026/10/10 08:49 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 60535 output: 28 thoughts: 1431 Thoughts: **My Analysis of Network Configuration Dependencies** Okay, let's break this down. I'm focusing on network performance and trying to understand which configurations are crucial. First, I noticed `veth_xdp_rcv_skb`, a static function, is a good starting point and I know that static functions, unless inlined, are usually exported symbols. I'm building a list of key symbols, and the initial set includes several SKB-related functions (`skb_zerocopy`, `skb_split`, `skb_shift`, `skb_segment`, `skb_try_coalesce`), page pool functions (`napi_pp_get_page`, `napi_pp_put_page`), net_iov functions (`get_net_iov`, `put_net_iov`), and some others like `__pskb_copy_fclone`, `tcp_recvmsg_dmabuf`, `iptfs_skb_add_frags`, and `tls_strp_flush_anchor_copy`. None of these are *universally* used, but they are all critical in particular scenarios, such as zero-copy operations, SKB manipulation, GSO, page pool refcounting, and devmem. `veth_xdp_rcv_skb` is interesting because it's a veth-specific receive function. Now, I'm checking `EnableConfigs`. The `PAGE_POOL` option immediately jumps out. It's the primary feature being refactored! Without `CONFIG_PAGE_POOL`, the page pool operations are effectively disabled. I can see that `NETDEVSIM`, `VETH`, `BPF_SYSCALL`, and `VIRTIO_NET` all select `PAGE_POOL`. Including `PAGE_POOL` along with `NETDEVSIM` or `VETH` seems like a pretty standard setup. Next, I'm digging into `NET_DEVMEM`. I see it's used in `include/net/netmem.h` and `net/core/skbuff.c`. `get_net_iov` and `put_net_iov` call `net_is_devmem_iov`, which, as a general rule, relates to `net_devmem_get_net_iov` / `net_devmem_put_net_iov`. `CONFIG_NET_DEVMEM` depends on `DMA_SHARED_BUFFER` and `PAGE_POOL`. Now, `DMA_SHARED_BUFFER` is interesting because it's selected by `DMABUF_HEAPS`, `DRM`, `SYNC_FILE`, and `IIO_BUFFER`. `DMA_SHARED_BUFFER` is a boolean without a prompt. It can't be enabled directly without a prompt, but `DMABUF_HEAPS` or `DRM` can, so `DMABUF_HEAPS` indirectly selects `DMA_SHARED_BUFFER`. I'm making a point of this relationship to `NET_DEVMEM`: with `PAGE_POOL` and `DMA_SHARED_BUFFER` (which is often indirectly selected by `DMABUF_HEAPS` or the like) enabled, `NET_DEVMEM` is automatically enabled. This is good to know. The relationship between `NET_DEVMEM` and the mentioned KUnit tests is a good point of reference. Going back to `VETH`: As expected, `CONFIG_VETH=y` also selects `CONFIG_PAGE_POOL`. That's important for the context of this discussion. I'll also keep in mind `XFRM_IPTFS`, which will enable `xfrm_iptfs.c`, and depends on `XFRM`. `TLS` and `TLS_DEVICE` will enable `tls_strp.c` and `tls_device.c` respectively. And of course, the ever present `INET` and `NET` which are standard. Finally, I'm looking at the KUnit test cases added in `net/core/net_test.c`. These tests cover a lot of the aspects I'm interested in, including: `netmem_ref_test_page` `netmem_ref_test_pp_page` `netmem_ref_test_net_iov` `netmem_ref_test_release_data` `netmem_ref_test_skb_segment` It's clear the testing is comprehensive. I'll need to check the `net/core/Makefile` for the build configuration. |
| 56/2 | 2026/10/10 08:50 | tool | grepper |
0mArgs:{
"Expression": "net_test",
"PathPrefixes": [
"net/core/"
]
}Results: {
"Output": "net/core/Makefile=50=obj-$(CONFIG_OF)\t+= of_net.o\nnet/core/Makefile:51:obj-$(CONFIG_NET_TEST) += net_test.o\nnet/core/Makefile-52-obj-$(CONFIG_NET_DEVMEM) += devmem.o\n--\nnet/core/net_test.c=953=static void netmem_ref_test_skb_segment(struct kunit *test)\n--\nnet/core/net_test.c-1110-\nnet/core/net_test.c:1111:static struct kunit_case net_test_cases[] = {\nnet/core/net_test.c-1112-\tKUNIT_CASE_PARAM(gso_test_func, gso_test_gen_params),\n--\nnet/core/net_test.c-1126-\nnet/core/net_test.c:1127:static struct kunit_suite net_test_suite = {\nnet/core/net_test.c-1128-\t.name\t\t= \"net_core\",\nnet/core/net_test.c:1129:\t.test_cases\t= net_test_cases,\nnet/core/net_test.c-1130-};\nnet/core/net_test.c:1131:kunit_test_suite(net_test_suite);\nnet/core/net_test.c-1132-\n--\nnet/core/selftests.c-16-\nnet/core/selftests.c:17:static u8 net_test_next_id;\nnet/core/selftests.c-18-\nnet/core/selftests.c:19:struct sk_buff *net_test_get_skb(struct net_device *ndev, u8 id,\nnet/core/selftests.c-20-\t\t\t\t struct net_packet_attrs *attr)\n--\nnet/core/selftests.c-156-}\nnet/core/selftests.c:157:EXPORT_SYMBOL_GPL(net_test_get_skb);\nnet/core/selftests.c-158-\nnet/core/selftests.c:159:static int net_test_loopback_validate(struct sk_buff *skb,\nnet/core/selftests.c-160-\t\t\t\t struct net_device *ndev,\n--\nnet/core/selftests.c-163-{\nnet/core/selftests.c:164:\tstruct net_test_priv *tpriv = pt-\u003eaf_packet_priv;\nnet/core/selftests.c-165-\tconst unsigned char *src = tpriv-\u003epacket-\u003esrc;\n--\nnet/core/selftests.c-232-\nnet/core/selftests.c:233:static int __net_test_loopback(struct net_device *ndev,\nnet/core/selftests.c-234-\t\t\t struct net_packet_attrs *attr)\nnet/core/selftests.c-235-{\nnet/core/selftests.c:236:\tstruct net_test_priv *tpriv;\nnet/core/selftests.c-237-\tstruct sk_buff *skb = NULL;\n--\nnet/core/selftests.c-247-\ttpriv-\u003ept.type = htons(ETH_P_IP);\nnet/core/selftests.c:248:\ttpriv-\u003ept.func = net_test_loopback_validate;\nnet/core/selftests.c-249-\ttpriv-\u003ept.dev = ndev;\n--\nnet/core/selftests.c-253-\nnet/core/selftests.c:254:\tskb = net_test_get_skb(ndev, net_test_next_id, attr);\nnet/core/selftests.c-255-\tif (!skb) {\n--\nnet/core/selftests.c-259-\nnet/core/selftests.c:260:\tnet_test_next_id++;\nnet/core/selftests.c-261-\tret = dev_direct_xmit(skb, attr-\u003equeue_mapping);\n--\nnet/core/selftests.c-285-\nnet/core/selftests.c:286:static int net_test_netif_carrier(struct net_device *ndev)\nnet/core/selftests.c-287-{\n--\nnet/core/selftests.c-290-\nnet/core/selftests.c:291:static int net_test_phy_phydev(struct net_device *ndev)\nnet/core/selftests.c-292-{\n--\nnet/core/selftests.c-295-\nnet/core/selftests.c:296:static int net_test_phy_loopback_enable(struct net_device *ndev)\nnet/core/selftests.c-297-{\n--\nnet/core/selftests.c-303-\nnet/core/selftests.c:304:static int net_test_phy_loopback_disable(struct net_device *ndev)\nnet/core/selftests.c-305-{\n--\nnet/core/selftests.c-311-\nnet/core/selftests.c:312:static int net_test_phy_loopback_udp(struct net_device *ndev)\nnet/core/selftests.c-313-{\n--\nnet/core/selftests.c-316-\tattr.dst = ndev-\u003edev_addr;\nnet/core/selftests.c:317:\treturn __net_test_loopback(ndev, \u0026attr);\nnet/core/selftests.c-318-}\nnet/core/selftests.c-319-\nnet/core/selftests.c:320:static int net_test_phy_loopback_udp_mtu(struct net_device *ndev)\nnet/core/selftests.c-321-{\n--\nnet/core/selftests.c-325-\tattr.max_size = ndev-\u003emtu;\nnet/core/selftests.c:326:\treturn __net_test_loopback(ndev, \u0026attr);\nnet/core/selftests.c-327-}\nnet/core/selftests.c-328-\nnet/core/selftests.c:329:static int net_test_phy_loopback_tcp(struct net_device *ndev)\nnet/core/selftests.c-330-{\n--\nnet/core/selftests.c-334-\tattr.tcp = true;\nnet/core/selftests.c:335:\treturn __net_test_loopback(ndev, \u0026attr);\nnet/core/selftests.c-336-}\n--\nnet/core/selftests.c-338-/**\nnet/core/selftests.c:339: * net_test_phy_loopback_tcp_bad_csum - PHY loopback test with a deliberately\nnet/core/selftests.c-340- *\t\t\t\t\tcorrupted TCP checksum\n--\nnet/core/selftests.c-343- * Builds the same minimal Ethernet/IPv4/TCP frame as\nnet/core/selftests.c:344: * net_test_phy_loopback_tcp(), then flips the least-significant bit of the TCP\nnet/core/selftests.c-345- * checksum so the resulting value is provably invalid (neither 0 nor 0xFFFF).\n--\nnet/core/selftests.c-363- */\nnet/core/selftests.c:364:static int net_test_phy_loopback_tcp_bad_csum(struct net_device *ndev)\nnet/core/selftests.c-365-{\n--\nnet/core/selftests.c-370-\tattr.bad_csum = true;\nnet/core/selftests.c:371:\treturn __net_test_loopback(ndev, \u0026attr);\nnet/core/selftests.c-372-}\nnet/core/selftests.c-373-\nnet/core/selftests.c:374:static const struct net_test {\nnet/core/selftests.c-375-\tchar name[ETH_GSTRING_LEN];\n--\nnet/core/selftests.c-379-\t\t.name = \"Carrier \",\nnet/core/selftests.c:380:\t\t.fn = net_test_netif_carrier,\nnet/core/selftests.c-381-\t}, {\nnet/core/selftests.c-382-\t\t.name = \"PHY dev is present \",\nnet/core/selftests.c:383:\t\t.fn = net_test_phy_phydev,\nnet/core/selftests.c-384-\t}, {\n--\nnet/core/selftests.c-386-\t\t.name = \"PHY internal loopback, enable \",\nnet/core/selftests.c:387:\t\t.fn = net_test_phy_loopback_enable,\nnet/core/selftests.c-388-\t}, {\nnet/core/selftests.c-389-\t\t.name = \"PHY internal loopback, UDP \",\nnet/core/selftests.c:390:\t\t.fn = net_test_phy_loopback_udp,\nnet/core/selftests.c-391-\t}, {\nnet/core/selftests.c-392-\t\t.name = \"PHY internal loopback, MTU \",\nnet/core/selftests.c:393:\t\t.fn = net_test_phy_loopback_udp_mtu,\nnet/core/selftests.c-394-\t}, {\nnet/core/selftests.c-395-\t\t.name = \"PHY internal loopback, TCP \",\nnet/core/selftests.c:396:\t\t.fn = net_test_phy_loopback_tcp,\nnet/core/selftests.c-397-\t}, {\nnet/core/selftests.c-398-\t\t.name = \"PHY loopback, bad TCP csum \",\nnet/core/selftests.c:399:\t\t.fn = net_test_phy_loopback_tcp_bad_csum,\nnet/core/selftests.c-400-\t}, {\n--\nnet/core/selftests.c-402-\t\t.name = \"PHY internal loopback, disable\",\nnet/core/selftests.c:403:\t\t.fn = net_test_phy_loopback_disable,\nnet/core/selftests.c-404-\t},\n--\nnet/core/selftests.c=407=void net_selftest(struct net_device *ndev, struct ethtool_test *etest, u64 *buf)\n--\nnet/core/selftests.c-412-\tmemset(buf, 0, sizeof(*buf) * count);\nnet/core/selftests.c:413:\tnet_test_next_id = 0;\nnet/core/selftests.c-414-\n--\nnet/core/sock.c=769=bool sk_mc_loop(const struct sock *sk)\n--\nnet/core/sock.c-777-\tcase AF_INET:\nnet/core/sock.c:778:\t\treturn inet_test_bit(MC_LOOP, sk);\nnet/core/sock.c-779-#if IS_ENABLED(CONFIG_IPV6)\n"
} |
| 57/2 | 2026/10/10 08:50 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 65974 output: 27 thoughts: 15 Thoughts: Okay, let's see... **Checking the `CONFIG_NET_TEST` Kconfig Option** Alright, before I dive too deep into this networking issue, I need to take a quick step back and consider the configuration options. The first thing I'm going to look at is `CONFIG_NET_TEST` in Kconfig. This is a common starting point when debugging anything related to networking test suites. It's often a good sanity check to see if the testing infrastructure itself is enabled and configured correctly. I need to confirm that this basic building block for network testing is set, before I start looking at more complex settings or diving into the code itself. Essentially, I'm verifying the pre-requisite. |
| 58/2 | 2026/10/10 08:50 | tool | grepper |
0mArgs:{
"Expression": "config NET_TEST",
"PathPrefixes": [
"net/"
]
}Results: {
"Output": "net/Kconfig=528=config NETDEV_ADDR_LIST_TEST\n--\nnet/Kconfig-532-\nnet/Kconfig:533:config NET_TEST\nnet/Kconfig-534-\ttristate \"KUnit tests for networking\" if !KUNIT_ALL_TESTS\n"
} |
| 59/2 | 2026/10/10 08:50 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 66253 output: 365 thoughts: 1849 Thoughts: **Analyzing the Target for Fuzzing: A Deep Dive into SKB Reference Counting and Page Pool Refactoring** Alright, let's break this down. We're looking at changes to SKB reference counting and page pool usage in the kernel, and the goal is to define a good fuzzing target. The `KUNIT` tests are unit tests, not what we're after, since `syzkaller` targets the kernel's syscalls and reachable code in a running environment. This patch is touching core networking components. The key files affected are, as I see it: `include/linux/skbuff_ref.h`, `include/net/netmem.h`, `net/core/skbuff.c`, `net/ipv4/tcp.c`, `net/ipv4/esp4.c`, `net/ipv6/esp6.c`, `net/tls/tls_strp.c`, `drivers/net/veth.c`, and `net/xfrm/xfrm_iptfs.c`. This covers quite a broad area, from core structures to various networking protocols and device drivers. Now, for the configurations: we need to enable the core features: `PAGE_POOL` is the fundamental aspect that's being refactored, and it's used extensively. Next, `NET_DEVMEM` is relevant for devmem and dmabuf support, which brings in `DMA_SHARED_BUFFER` as a dependency. `VETH` is essential, since it exercises `veth_xdp_get` and also interacts with `PAGE_POOL`. Since it involves IP traffic, `INET` and `IPV6` are necessary, and they cover `tcp_recvmsg_dmabuf` and IPsec respectively. Kernel TLS is involved too, through `TLS`, while `XFRM_IPTFS` is IPsec too, thus also the need to activate it. `XFRM_IPTFS` needs `XFRM`, `TLS` depends on `INET`, and `VETH` has `PAGE_POOL` selected, so these relationships are important when putting together the fuzzing config. The really interesting bit is identifying the `FocusSymbols`. Let's scrutinize the modified and relevant non-static functions in the codebase. From `net/core/skbuff.c`, we have: `napi_pp_get_page`, `napi_pp_put_page`, `get_net_iov`, `put_net_iov`, `skb_zerocopy`, `skb_split`, `skb_shift`, `skb_segment`, `skb_try_coalesce`, and `__pskb_copy_fclone`. Then from `net/ipv4/tcp.c`, there's `tcp_recvmsg_dmabuf`. In `drivers/net/veth.c`, we have `veth_xdp_rcv_skb`, and finally from `net/xfrm/xfrm_iptfs.c`, we have `iptfs_skb_add_frags`. We also have `tls_strp_read_copy`. Now, the question is: are any of these functions ubiquitous? Specifically, are functions like `skb_segment`, `skb_shift`, `skb_split`, `skb_zerocopy`, `skb_try_coalesce` called a lot? `skb_segment` is tied to GSO and isn't everywhere. `skb_shift` and `skb_split` only come up in specific packet manipulation scenarios. `skb_zerocopy` is just for zerocopy and the same is true for `skb_try_coalesce`. The `napi_pp_*` and `get/put_net_iov` are key, though. The `tcp_recvmsg_dmabuf` is specific to the TCP devmem path, and `iptfs_skb_add_frags` handles IP-TFS fragments. We need to avoid hot-paths like memory deallocators and functions that are core to the system. `skb_release_data` is modified, but it's called on almost every SKB freeing path through `kfree_skb`. So according to the rule, these are definitely hot-paths and should be ignored, or we will dilute the fuzzing. Thus, we prioritize the following: - `napi_pp_get_page` - `napi_pp_put_page` - `get_net_iov` - `put_net_iov` - `skb_zerocopy` - `skb_split` - ` |
| 60/2 | 2026/10/10 08:50 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"PAGE_POOL",
"NET_DEVMEM",
"DMA_SHARED_BUFFER",
"VETH",
"INET",
"IPV6",
"TLS",
"XFRM_IPTFS"
],
"FocusSymbols": [
"napi_pp_get_page",
"napi_pp_put_page",
"get_net_iov",
"put_net_iov",
"skb_zerocopy",
"skb_split",
"skb_shift",
"skb_segment",
"skb_try_coalesce",
"tcp_recvmsg_dmabuf",
"iptfs_skb_add_frags",
"veth_xdp_rcv_skb"
],
"Reasoning": "The patch modifies skb fragment reference counting and page_pool lifecycle handling across core networking (net/core/skbuff.c, include/linux/skbuff_ref.h, include/net/netmem.h) and protocols/drivers (veth, TCP, TLS, XFRM/IP-TFS, ESP). It introduces exported refcount functions (napi_pp_get_page, get_net_iov, put_net_iov), changes how skb-\u003epp_recycle is handled across skb operations (cloning, splitting, coalescing, segmentation, and zerocopy), and adds runtime assertions (DEBUG_NET_WARN_ON_ONCE, WARN_ON_ONCE). These paths are reachable via standard networking syscalls, socket operations, and virtual network devices.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"PAGE_POOL",
"NET_DEVMEM",
"DMA_SHARED_BUFFER",
"VETH",
"INET",
"IPV6",
"TLS",
"XFRM_IPTFS"
],
"FocusSymbols": [
"napi_pp_get_page",
"napi_pp_put_page",
"get_net_iov",
"put_net_iov",
"skb_zerocopy",
"skb_split",
"skb_shift",
"skb_segment",
"skb_try_coalesce",
"tcp_recvmsg_dmabuf",
"iptfs_skb_add_frags",
"veth_xdp_rcv_skb"
],
"Reasoning": "The patch modifies skb fragment reference counting and page_pool lifecycle handling across core networking (net/core/skbuff.c, include/linux/skbuff_ref.h, include/net/netmem.h) and protocols/drivers (veth, TCP, TLS, XFRM/IP-TFS, ESP). It introduces exported refcount functions (napi_pp_get_page, get_net_iov, put_net_iov), changes how skb-\u003epp_recycle is handled across skb operations (cloning, splitting, coalescing, segmentation, and zerocopy), and adds runtime assertions (DEBUG_NET_WARN_ON_ONCE, WARN_ON_ONCE). These paths are reachable via standard networking syscalls, socket operations, and virtual network devices.",
"WorthFuzzing": true
} |
| 61/1 | 2026/10/10 08:50 | action | If |
0mArgs:{
"WorthFuzzing": true
}Results: null |
| 62/2 | 2026/10/10 08:50 | agent | kmsan-evaluator |
0mModel:core Args: null Results: {
"KMSANReasoning": "This patch series refactors SKB (sk_buff) fragment reference counting and page_pool integration, introducing unified helpers (skb_netmem_ref, skb_netmem_unref, skb_frag_ref, skb_frag_unref) that correctly account for page_pool references (pp_ref_count) versus non-page_pool backing references across skb cloning, splitting, coalescing, shifting, and segmentation operations.\n\nThe potential hazards associated with these changes are reference count imbalances leading to use-after-free (UAF) conditions, premature page releases, or memory leaks. These bug classes are covered by KASAN and existing page/slab refcount sanity checks. The patch does not introduce or modify any kernel-to-user copy paths (no info-leak risks), does not allocate or read uninitialized memory buffers, and does not alter branching on uninitialized fields. Therefore, a dedicated KMSAN fuzzing session is not warranted, and standard KASAN fuzzing is sufficient.",
"NeedsKMSAN": false
}Instruction: You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.
CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
a comprehensive suite of debugging tools and sanitizers, including KASAN
(out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
(locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
or page allocations) and kernel-to-user memory info-leaks.
Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.
Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.
Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
attributes, ioctl output arguments, socket options, or BPF buffers) where fields
or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
uninitialized bytes of existing buffers.
Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
dereferences, locking deadlocks, or use-after-free bugs (these are already caught
by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.
Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit 8bdbb9cbf2c5bd64c459f7ea69df80b6c1c38faf
Author: syz-cluster <triage@syzkaller.com>
Date: Sat Oct 10 08:45:49 2026 +0000
syz-cluster: applied patch under review
diff --git a/drivers/net/ethernet/chelsio/inline_crypto/ch_ktls/chcr_ktls.c b/drivers/net/ethernet/chelsio/inline_crypto/ch_ktls/chcr_ktls.c
index dbec95b497773..4d645771596a8 100644
--- a/drivers/net/ethernet/chelsio/inline_crypto/ch_ktls/chcr_ktls.c
+++ b/drivers/net/ethernet/chelsio/inline_crypto/ch_ktls/chcr_ktls.c
@@ -1660,7 +1660,7 @@ static void chcr_ktls_copy_record_in_skb(struct sk_buff *nskb,
for (i = 0; i < record->num_frags; i++) {
skb_shinfo(nskb)->frags[i] = record->frags[i];
/* increase the frag ref count */
- __skb_frag_ref(&skb_shinfo(nskb)->frags[i]);
+ skb_frag_ref(nskb, i);
}
skb_shinfo(nskb)->nr_frags = record->num_frags;
diff --git a/drivers/net/ethernet/marvell/sky2.c b/drivers/net/ethernet/marvell/sky2.c
index 30facdece3e29..1d42a82db8f2b 100644
--- a/drivers/net/ethernet/marvell/sky2.c
+++ b/drivers/net/ethernet/marvell/sky2.c
@@ -2501,7 +2501,7 @@ static void skb_put_frags(struct sk_buff *skb, unsigned int hdr_space,
if (length == 0) {
/* don't need this page */
- __skb_frag_unref(frag, false);
+ skb_frag_unref(skb, i);
--skb_shinfo(skb)->nr_frags;
} else {
size = min(length, (unsigned) PAGE_SIZE);
diff --git a/drivers/net/ethernet/mellanox/mlx4/en_rx.c b/drivers/net/ethernet/mellanox/mlx4/en_rx.c
index 96f97fa0f5489..fe18606c2ab55 100644
--- a/drivers/net/ethernet/mellanox/mlx4/en_rx.c
+++ b/drivers/net/ethernet/mellanox/mlx4/en_rx.c
@@ -497,7 +497,7 @@ static int mlx4_en_complete_rx_desc(struct mlx4_en_priv *priv,
fail:
while (nr > 0) {
nr--;
- __skb_frag_unref(skb_shinfo(skb)->frags + nr, false);
+ skb_frag_unref(skb, nr);
}
return 0;
}
diff --git a/drivers/net/ethernet/sun/cassini.c b/drivers/net/ethernet/sun/cassini.c
index 9a8b5cf2d6485..02158a1bb2f70 100644
--- a/drivers/net/ethernet/sun/cassini.c
+++ b/drivers/net/ethernet/sun/cassini.c
@@ -1998,7 +1998,7 @@ static int cas_rx_process_pkt(struct cas *cp, struct cas_rx_comp *rxc,
skb->len += hlen - swivel;
skb_frag_fill_page_desc(frag, page->buffer, off, hlen - swivel);
- __skb_frag_ref(frag);
+ skb_frag_ref(skb, 0);
/* any more data? */
if ((words[0] & RX_COMP1_SPLIT_PKT) && ((dlen -= hlen) > 0)) {
@@ -2022,7 +2022,7 @@ static int cas_rx_process_pkt(struct cas *cp, struct cas_rx_comp *rxc,
frag++;
skb_frag_fill_page_desc(frag, page->buffer, 0, hlen);
- __skb_frag_ref(frag);
+ skb_frag_ref(skb, 1);
RX_USED_ADD(page, hlen + cp->crc_size);
}
diff --git a/drivers/net/veth.c b/drivers/net/veth.c
index 643b97dc52456..90a333ebab683 100644
--- a/drivers/net/veth.c
+++ b/drivers/net/veth.c
@@ -746,7 +746,7 @@ static void veth_xdp_get(struct xdp_buff *xdp)
return;
for (i = 0; i < sinfo->nr_frags; i++)
- __skb_frag_ref(&sinfo->frags[i]);
+ get_netmem(skb_frag_netmem(&sinfo->frags[i]));
}
static int veth_convert_skb_to_xdp_buff(struct veth_rq *rq,
diff --git a/include/linux/skbuff_ref.h b/include/linux/skbuff_ref.h
index 05c8486bafac8..f768a3fa1d606 100644
--- a/include/linux/skbuff_ref.h
+++ b/include/linux/skbuff_ref.h
@@ -9,32 +9,53 @@
#include <linux/skbuff.h>
+bool napi_pp_get_page(netmem_ref netmem);
+
/**
- * __skb_frag_ref - take an addition reference on a paged fragment.
- * @frag: the paged fragment
+ * skb_netmem_ref - acquire a fragment reference on a netmem
+ * @netmem: netmem to reference
+ * @recycle: whether page_pool recycling is enabled (e.g. skb->pp_recycle)
*
- * Takes an additional reference on the paged fragment @frag.
+ * Acquires pp_ref_count via napi_pp_get_page() if @recycle is true and
+ * @netmem belongs to a page_pool; otherwise acquires a non-pp backing
+ * reference via get_netmem(). Counterpart to skb_netmem_unref().
*/
-static __always_inline void __skb_frag_ref(skb_frag_t *frag)
+static __always_inline void skb_netmem_ref(netmem_ref netmem, bool recycle)
{
- get_netmem(skb_frag_netmem(frag));
+#ifdef CONFIG_PAGE_POOL
+ if (recycle && napi_pp_get_page(netmem))
+ return;
+#endif
+ get_netmem(netmem);
}
/**
- * skb_frag_ref - take an addition reference on a paged fragment of an skb.
+ * skb_frag_ref - acquire a reference on a paged fragment of an skb
* @skb: the buffer
- * @f: the fragment offset.
+ * @f: the fragment offset
*
- * Takes an additional reference on the @f'th paged fragment of @skb.
+ * 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().
*/
static __always_inline void skb_frag_ref(struct sk_buff *skb, int f)
{
- __skb_frag_ref(&skb_shinfo(skb)->frags[f]);
+ skb_netmem_ref(skb_frag_netmem(&skb_shinfo(skb)->frags[f]),
+ skb->pp_recycle);
}
bool napi_pp_put_page(netmem_ref netmem);
-static __always_inline void skb_page_unref(netmem_ref netmem, bool recycle)
+/**
+ * skb_netmem_unref - release a fragment reference on a netmem
+ * @netmem: netmem to unreference
+ * @recycle: whether page_pool recycling is enabled (e.g. skb->pp_recycle)
+ *
+ * Drops pp_ref_count via napi_pp_put_page() if @recycle is true and @netmem
+ * belongs to a page_pool; otherwise drops a non-pp backing reference via
+ * put_netmem(). Counterpart to skb_netmem_ref().
+ */
+static __always_inline void skb_netmem_unref(netmem_ref netmem, bool recycle)
{
#ifdef CONFIG_PAGE_POOL
if (recycle && napi_pp_put_page(netmem))
@@ -44,31 +65,22 @@ static __always_inline void skb_page_unref(netmem_ref netmem, bool recycle)
}
/**
- * __skb_frag_unref - release a reference on a paged fragment.
- * @frag: the paged fragment
- * @recycle: recycle the page if allocated via page_pool
- *
- * Releases a reference on the paged fragment @frag
- * or recycles the page via the page_pool API.
- */
-static __always_inline void __skb_frag_unref(skb_frag_t *frag, bool recycle)
-{
- skb_page_unref(skb_frag_netmem(frag), recycle);
-}
-
-/**
- * skb_frag_unref - release a reference on a paged fragment of an skb.
+ * skb_frag_unref - release a reference on a paged fragment of an skb
* @skb: the buffer
* @f: the fragment offset
*
- * Releases a reference on the @f'th paged fragment of @skb.
+ * Drops a reference on the @f'th fragment of @skb via skb_netmem_unref()
+ * (pp_ref_count if skb->pp_recycle and page_pool-backed, else non-pp
+ * backing refcount), skipping managed zerocopy fragments. Counterpart to
+ * skb_frag_ref().
*/
static __always_inline void skb_frag_unref(struct sk_buff *skb, int f)
{
struct skb_shared_info *shinfo = skb_shinfo(skb);
if (!skb_zcopy_managed(skb))
- __skb_frag_unref(&shinfo->frags[f], skb->pp_recycle);
+ skb_netmem_unref(skb_frag_netmem(&shinfo->frags[f]),
+ skb->pp_recycle);
}
#endif /* _LINUX_SKBUFF_REF_H */
diff --git a/include/net/netmem.h b/include/net/netmem.h
index 0cc8572f62caf..697d50c51a5ee 100644
--- a/include/net/netmem.h
+++ b/include/net/netmem.h
@@ -399,21 +399,37 @@ static inline bool net_is_devmem_iov(const struct net_iov *niov)
}
#endif
-void __get_netmem(netmem_ref netmem);
-void __put_netmem(netmem_ref netmem);
+void get_net_iov(struct net_iov *niov);
+void put_net_iov(struct net_iov *niov);
+/**
+ * get_netmem - acquire a non-page_pool reference on a netmem
+ * @netmem: netmem to reference
+ *
+ * Acquires a non-pp backing reference (page._refcount via get_page() or
+ * binding->ref via get_net_iov()) regardless of page_pool membership.
+ * Counterpart to put_netmem().
+ */
static __always_inline void get_netmem(netmem_ref netmem)
{
if (netmem_is_net_iov(netmem))
- __get_netmem(netmem);
+ get_net_iov(netmem_to_net_iov(netmem));
else
get_page(netmem_to_page(netmem));
}
+/**
+ * put_netmem - release a non-page_pool reference on a netmem
+ * @netmem: netmem to unreference
+ *
+ * Drops a non-pp backing reference (page._refcount via put_page() or
+ * binding->ref via put_net_iov()) regardless of page_pool membership.
+ * Counterpart to get_netmem().
+ */
static __always_inline void put_netmem(netmem_ref netmem)
{
if (netmem_is_net_iov(netmem))
- __put_netmem(netmem);
+ put_net_iov(netmem_to_net_iov(netmem));
else
put_page(netmem_to_page(netmem));
}
diff --git a/net/core/net_test.c b/net/core/net_test.c
index 9c3a590865d26..f9f50f9159902 100644
--- a/net/core/net_test.c
+++ b/net/core/net_test.c
@@ -370,10 +370,757 @@ static void ip_tunnel_flags_test_run(struct kunit *test)
KUNIT_ASSERT_TRUE(test, __ipt_flag_op(bitmap_equal, exp, out));
}
+/* Netmem & SKB fragment refcounting */
+
+#include <linux/skbuff_ref.h>
+#include <net/netmem.h>
+#include <net/netdev_rx_queue.h>
+#include <net/page_pool/helpers.h>
+#include <net/page_pool/memory_provider.h>
+#include "devmem.h"
+#include "netmem_priv.h"
+
+static void netmem_ref_test_page(struct kunit *test)
+{
+ struct sk_buff *skb;
+ netmem_ref netmem;
+ struct page *page;
+ int base_ref;
+
+ page = alloc_page(GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, page);
+ netmem = page_to_netmem(page);
+ base_ref = page_ref_count(page);
+
+ /* Layer 1: get_netmem / put_netmem use page non-pp refcount */
+ get_netmem(netmem);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref + 1);
+ put_netmem(netmem);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+
+#ifdef CONFIG_PAGE_POOL
+ /* Layer 2: napi_pp_get/put_page return false on non-PP page */
+ KUNIT_EXPECT_FALSE(test, napi_pp_get_page(netmem));
+ KUNIT_EXPECT_FALSE(test, napi_pp_put_page(netmem));
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+#endif
+
+ /* Layer 3: skb_netmem_ref / unref fall back to non-pp on non-PP page */
+ skb_netmem_ref(netmem, false);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref + 1);
+ skb_netmem_unref(netmem, false);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+
+ skb_netmem_ref(netmem, true);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref + 1);
+ skb_netmem_unref(netmem, true);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+
+ /* Layer 4: skb_frag_ref / skb_frag_unref on non-PP page */
+ skb = alloc_skb(64, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, skb);
+ skb_fill_netmem_desc(skb, 0, netmem, 0, 64);
+
+ skb->pp_recycle = 1;
+ skb_frag_ref(skb, 0);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref + 1);
+ skb_frag_unref(skb, 0);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+
+ skb_shinfo(skb)->nr_frags = 0;
+ consume_skb(skb);
+ __free_page(page);
+}
+
+#ifdef CONFIG_PAGE_POOL
+static void netmem_ref_test_pp_page(struct kunit *test)
+{
+ struct page_pool_params pp_params = {
+ .order = 0,
+ .pool_size = 4,
+ .nid = NUMA_NO_NODE,
+ };
+ struct page_pool *pool;
+ struct sk_buff *skb, *copy, *clone, *split;
+ netmem_ref netmem;
+ struct page *page;
+ long base_pp_ref;
+ int base_ref;
+
+ pool = page_pool_create(&pp_params);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pool);
+
+ page = page_pool_dev_alloc_pages(pool);
+ KUNIT_ASSERT_NOT_NULL(test, page);
+ netmem = page_to_netmem(page);
+ base_ref = page_ref_count(page);
+ base_pp_ref = atomic_long_read(&page->pp_ref_count);
+
+ /* Layer 1: get_netmem / put_netmem always use non-pp refcount */
+ get_netmem(netmem);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref + 1);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref);
+ put_netmem(netmem);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+
+ /* Layer 2: napi_pp_get/put_page use pp_ref_count */
+ KUNIT_EXPECT_TRUE(test, napi_pp_get_page(netmem));
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ KUNIT_EXPECT_TRUE(test, napi_pp_put_page(netmem));
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref);
+
+ /* Layer 3: skb_netmem_ref / unref obey recycle flag */
+ skb_netmem_ref(netmem, false);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref + 1);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref);
+ skb_netmem_unref(netmem, false);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+
+ skb_netmem_ref(netmem, true);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ skb_netmem_unref(netmem, true);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref);
+
+ /* Layer 4: skb_frag_ref / skb_frag_unref match on pp_recycle=1 & 0 */
+ skb = alloc_skb(64, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, skb);
+ skb_fill_netmem_desc(skb, 0, netmem, 0, 64);
+ skb->len = 64;
+ skb->data_len = 64;
+
+ skb->pp_recycle = 1;
+ skb_frag_ref(skb, 0);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ skb_frag_unref(skb, 0);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref);
+
+ /* pskb_copy on pp_recycle=1 skb propagates pp_recycle & pp_ref_count */
+ copy = pskb_copy(skb, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, copy);
+ KUNIT_EXPECT_TRUE(test, copy->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ consume_skb(copy);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref);
+
+ /* skb_zerocopy on pp_recycle=1 skb propagates pp_ref_count */
+ copy = alloc_skb(0, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, copy);
+ skb_put(copy, skb_tailroom(copy));
+ skb->head_frag = 1;
+ KUNIT_ASSERT_EQ(test, skb_zerocopy(copy, skb, skb->len, 0), 0);
+ skb->head_frag = 0;
+ KUNIT_EXPECT_TRUE(test, copy->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ consume_skb(copy);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref);
+
+ /* skb_clone + pskb_expand_head preserves pp_recycle and pp_ref_count */
+ clone = skb_clone(skb, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, clone);
+ KUNIT_ASSERT_EQ(test, pskb_expand_head(clone, 32, 0, GFP_KERNEL), 0);
+ KUNIT_EXPECT_TRUE(test, clone->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ consume_skb(clone);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref);
+
+ /* skb_split on page_pool frag propagates pp_recycle & balances refs */
+ split = alloc_skb(0, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, split);
+ skb_split(skb, split, 32);
+ KUNIT_EXPECT_TRUE(test, split->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ consume_skb(split);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref);
+
+ /* skb_segment on pp_recycle=1 skb propagates pp_recycle to segments */
+ skb->protocol = htons(ETH_P_IP);
+ skb_reset_network_header(skb);
+ skb_shinfo(skb)->gso_size = 16;
+ split = skb_segment(skb, NETIF_F_SG | NETIF_F_HW_CSUM);
+ KUNIT_ASSERT_FALSE(test, IS_ERR_OR_NULL(split));
+ KUNIT_EXPECT_TRUE(test, split->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 2);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ kfree_skb_list(split);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref);
+
+ /* skb_shift balances pp_ref_count on split and merge */
+ skb_fill_netmem_desc(skb, 0, netmem, 0, 64);
+ skb->len = 64;
+ skb->data_len = 64;
+ split = alloc_skb(0, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, split);
+ split->pp_recycle = 1;
+ KUNIT_EXPECT_EQ(test, skb_shift(split, skb, 16), 16);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ KUNIT_EXPECT_EQ(test, skb_shift(split, skb, 48), 48);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref);
+ skb_shinfo(split)->nr_frags = 0;
+ consume_skb(split);
+ skb_fill_netmem_desc(skb, 0, netmem, 0, 64);
+ skb->len = 64;
+ skb->data_len = 64;
+
+ skb->pp_recycle = 0;
+ skb_frag_ref(skb, 0);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref + 1);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref);
+ skb_frag_unref(skb, 0);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+
+ skb_shinfo(skb)->nr_frags = 0;
+ consume_skb(skb);
+ page_pool_put_full_page(pool, page, false);
+ page_pool_destroy(pool);
+}
+#endif
+
+#if defined(CONFIG_PAGE_POOL) && defined(CONFIG_NET_DEVMEM)
+static void dummy_percpu_ref_release(struct percpu_ref *ref)
+{
+}
+
+static void netmem_ref_test_net_iov(struct kunit *test)
+{
+ struct net_devmem_dmabuf_binding binding = {};
+ struct page_pool_params pp_params = {
+ .order = 0,
+ .pool_size = 4,
+ .nid = NUMA_NO_NODE,
+ };
+ struct sk_buff *skb, *clone, *copy;
+ struct page_pool *pool;
+ struct net_iov niov = {};
+ netmem_ref netmem;
+ long base_ref;
+ int err;
+
+ err = percpu_ref_init(&binding.ref, dummy_percpu_ref_release,
+ PERCPU_REF_INIT_ATOMIC, GFP_KERNEL);
+ KUNIT_ASSERT_EQ(test, err, 0);
+
+ net_iov_init(&niov, &binding.area, NET_IOV_DMABUF);
+ netmem = net_iov_to_netmem(&niov);
+ base_ref = atomic_long_read(&binding.ref.data->count);
+
+ /* Non-PP net_iov: get/put_net_iov & get/put_netmem */
+ get_net_iov(&niov);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&binding.ref.data->count),
+ base_ref + 1);
+ put_net_iov(&niov);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&binding.ref.data->count),
+ base_ref);
+
+ KUNIT_EXPECT_FALSE(test, napi_pp_get_page(netmem));
+ KUNIT_EXPECT_FALSE(test, napi_pp_put_page(netmem));
+
+ skb_netmem_ref(netmem, true);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&binding.ref.data->count),
+ base_ref + 1);
+ skb_netmem_unref(netmem, true);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&binding.ref.data->count),
+ base_ref);
+
+ /* Attach net_iov to a page_pool to test PP net_iov behavior */
+ pool = page_pool_create(&pp_params);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pool);
+ netmem_set_pp(netmem, pool);
+ netmem_or_pp_magic(netmem, PP_SIGNATURE);
+ atomic_long_set(&niov.desc.pp_ref_count, 2);
+
+ /* Layer 0 & 1: get_net_iov / get_netmem grab non-pp ref even on PP */
+ get_net_iov(&niov);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&binding.ref.data->count),
+ base_ref + 1);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 2L);
+ put_net_iov(&niov);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&binding.ref.data->count),
+ base_ref);
+
+ /* Layer 2: napi_pp_get/put_page use pp_ref_count */
+ KUNIT_EXPECT_TRUE(test, napi_pp_get_page(netmem));
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 3L);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&binding.ref.data->count),
+ base_ref);
+ KUNIT_EXPECT_TRUE(test, napi_pp_put_page(netmem));
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 2L);
+
+ /* Layer 4: skb_frag_ref / skb_frag_unref on PP net_iov */
+ skb = alloc_skb(64, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, skb);
+ skb_fill_netmem_desc(skb, 0, netmem, 0, 64);
+ skb->len = 64;
+ skb->data_len = 64;
+
+ skb->pp_recycle = 1;
+ skb_frag_ref(skb, 0);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 3L);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&binding.ref.data->count),
+ base_ref);
+ skb_frag_unref(skb, 0);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 2L);
+
+ /* pskb_copy on pp_recycle=1 net_iov skb keeps pp_ref_count */
+ copy = pskb_copy(skb, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, copy);
+ KUNIT_EXPECT_TRUE(test, copy->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 3L);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&binding.ref.data->count),
+ base_ref);
+ consume_skb(copy);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 2L);
+
+ /* skb_zerocopy on pp_recycle=1 net_iov skb keeps pp_ref_count */
+ copy = alloc_skb(0, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, copy);
+ skb_put(copy, skb_tailroom(copy));
+ skb->head_frag = 1;
+ skb->unreadable = 1;
+ KUNIT_ASSERT_EQ(test, skb_zerocopy(copy, skb, skb->len, 0), 0);
+ skb->head_frag = 0;
+ KUNIT_EXPECT_TRUE(test, copy->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 3L);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&binding.ref.data->count),
+ base_ref);
+ consume_skb(copy);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 2L);
+
+ /* skb_shift balances pp_ref_count on split and merge */
+ copy = alloc_skb(0, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, copy);
+ copy->pp_recycle = 1;
+ KUNIT_EXPECT_EQ(test, skb_shift(copy, skb, 16), 16);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 3L);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&binding.ref.data->count),
+ base_ref);
+ KUNIT_EXPECT_EQ(test, skb_shift(copy, skb, 48), 48);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 2L);
+ skb_shinfo(copy)->nr_frags = 0;
+ consume_skb(copy);
+ skb_fill_netmem_desc(skb, 0, netmem, 0, 64);
+ skb->len = 64;
+ skb->data_len = 64;
+
+ /* Uncloning a cloned pp_recycle=1 net_iov skb keeps pp_ref_count */
+ clone = skb_clone(skb, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, clone);
+ KUNIT_ASSERT_EQ(test, pskb_expand_head(skb, 32, 0, GFP_KERNEL), 0);
+ KUNIT_EXPECT_TRUE(test, skb->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 3L);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&binding.ref.data->count),
+ base_ref);
+ consume_skb(clone);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 2L);
+
+ skb->pp_recycle = 0;
+ skb_frag_ref(skb, 0);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&binding.ref.data->count),
+ base_ref + 1);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 2L);
+ skb_frag_unref(skb, 0);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&binding.ref.data->count),
+ base_ref);
+
+ skb_shinfo(skb)->nr_frags = 0;
+ consume_skb(skb);
+ netmem_clear_pp_magic(netmem);
+ netmem_set_pp(netmem, NULL);
+ page_pool_destroy(pool);
+ percpu_ref_exit(&binding.ref);
+}
+
+static void netmem_ref_test_release_data(struct kunit *test)
+{
+ struct page_pool_params pp_params = {
+ .order = 0,
+ .pool_size = 4,
+ .nid = NUMA_NO_NODE,
+ };
+ struct sk_buff *skb, *clone1, *clone2, *ext;
+ struct net_devmem_dmabuf_binding binding = {};
+ struct net_iov niov = {};
+ struct page_pool *pool;
+ struct page *page;
+ netmem_ref netmem;
+ long base_pp_ref;
+ int base_ref;
+ int i;
+
+ pool = page_pool_create(&pp_params);
+ KUNIT_ASSERT_FALSE(test, IS_ERR(pool));
+ page = page_pool_dev_alloc_pages(pool);
+ KUNIT_ASSERT_NOT_NULL(test, page);
+ netmem = page_to_netmem(page);
+ page_pool_ref_netmem(netmem);
+
+ /* 3-way clone chain: only last clone drops pp_ref_count */
+ skb = alloc_skb(64, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, skb);
+ skb_put(skb, 32);
+ skb->pp_recycle = 1;
+ skb_fill_netmem_desc(skb, 0, netmem, 0, 64);
+ skb->len += 64;
+ skb->data_len = 64;
+ base_ref = page_ref_count(page);
+ base_pp_ref = atomic_long_read(&page->pp_ref_count);
+
+ page_pool_ref_netmem(netmem);
+ clone1 = skb_clone(skb, GFP_KERNEL);
+ clone2 = skb_clone(clone1, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, clone1);
+ KUNIT_ASSERT_NOT_NULL(test, clone2);
+
+ /* Freeing head skb first leaves clone1/clone2 pp_recycle=1 intact */
+ consume_skb(skb);
+ KUNIT_EXPECT_TRUE(test, clone1->pp_recycle);
+ KUNIT_EXPECT_TRUE(test, clone2->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+ consume_skb(clone2);
+ KUNIT_EXPECT_TRUE(test, clone1->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+ consume_skb(clone1);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref);
+
+ /* All 4 combinations of (unclone skb vs clone) x (free order) */
+ for (i = 0; i < 4; i++) {
+ skb = alloc_skb(64, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, skb);
+ skb_put(skb, 32);
+ skb->pp_recycle = 1;
+ skb_fill_netmem_desc(skb, 0, netmem, 0, 64);
+ skb->len += 64;
+ skb->data_len = 64;
+ page_pool_ref_netmem(netmem);
+
+ clone1 = skb_clone(skb, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, clone1);
+ KUNIT_ASSERT_EQ(test,
+ pskb_expand_head((i & 1) ? clone1 : skb,
+ 32, 0, GFP_KERNEL), 0);
+ KUNIT_EXPECT_TRUE(test, skb->pp_recycle);
+ KUNIT_EXPECT_TRUE(test, clone1->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 2);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+
+ if (i & 2) {
+ consume_skb(clone1);
+ KUNIT_EXPECT_EQ(test,
+ atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+ consume_skb(skb);
+ } else {
+ consume_skb(skb);
+ KUNIT_EXPECT_EQ(test,
+ atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+ consume_skb(clone1);
+ }
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ }
+
+ /* pskb_extract carving inside header & inside nonlinear frags */
+ skb = alloc_skb(64, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, skb);
+ skb_put(skb, 32);
+ skb->pp_recycle = 1;
+ skb_fill_netmem_desc(skb, 0, netmem, 0, 64);
+ skb->len += 64;
+ skb->data_len = 64;
+ page_pool_ref_netmem(netmem);
+
+ /* Carve inside header keeping frag -> pp_ref_count +1 until free */
+ ext = pskb_extract(skb, 16, skb->len - 16, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ext);
+ KUNIT_EXPECT_TRUE(test, ext->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 2);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ consume_skb(ext);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+
+ /* Carve + trim + skb_condense ref/unrefs pp_ref_count cleanly */
+ ext = pskb_extract(skb, 16, 48, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ext);
+ KUNIT_EXPECT_TRUE(test, ext->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ consume_skb(ext);
+
+ /* Carve inside nonlinear keeping frag -> pp_ref_count +1 until free */
+ ext = pskb_extract(skb, 48, skb->len - 48, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ext);
+ KUNIT_EXPECT_TRUE(test, ext->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 2);
+ KUNIT_EXPECT_EQ(test, page_ref_count(page), base_ref);
+ consume_skb(ext);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+
+ /* skb_morph drops old skb data via skb_release_data */
+ page_pool_ref_netmem(netmem);
+ clone1 = alloc_skb(64, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, clone1);
+ clone1->pp_recycle = 1;
+ skb_fill_netmem_desc(clone1, 0, netmem, 0, 64);
+ clone1->len = 64;
+ clone1->data_len = 64;
+ skb_morph(clone1, skb);
+ KUNIT_EXPECT_TRUE(test, clone1->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&page->pp_ref_count),
+ base_pp_ref + 1);
+ consume_skb(clone1);
+ kfree_skb(skb);
+ page_pool_put_full_page(pool, page, false);
+ page_pool_put_full_page(pool, page, false);
+
+ /* Also verify pskb_extract on page_pool net_iov */
+ KUNIT_ASSERT_EQ(test,
+ percpu_ref_init(&binding.ref, dummy_percpu_ref_release,
+ PERCPU_REF_INIT_ATOMIC, GFP_KERNEL),
+ 0);
+ net_iov_init(&niov, &binding.area, NET_IOV_DMABUF);
+ netmem = net_iov_to_netmem(&niov);
+ netmem_set_pp(netmem, pool);
+ netmem_or_pp_magic(netmem, PP_SIGNATURE);
+ atomic_long_set(&niov.desc.pp_ref_count, 2);
+
+ skb = alloc_skb(64, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, skb);
+ skb_put(skb, 32);
+ skb->pp_recycle = 1;
+ skb_fill_netmem_desc(skb, 0, netmem, 0, 64);
+ skb->len += 64;
+ skb->data_len = 64;
+
+ ext = pskb_extract(skb, 48, skb->len - 48, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, ext);
+ KUNIT_EXPECT_TRUE(test, ext->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 3L);
+ consume_skb(ext);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 2L);
+
+ skb_shinfo(skb)->nr_frags = 0;
+ consume_skb(skb);
+ netmem_clear_pp_magic(netmem);
+ netmem_set_pp(netmem, NULL);
+ page_pool_destroy(pool);
+ percpu_ref_exit(&binding.ref);
+}
+
+static void netmem_ref_test_skb_segment(struct kunit *test)
+{
+ struct page_pool_params pp_params = {
+ .order = 0,
+ .pool_size = 4,
+ .nid = NUMA_NO_NODE,
+ };
+ struct sk_buff *head, *list, *segs, *seg;
+ struct net_devmem_dmabuf_binding binding = {};
+ struct net_iov niov = {};
+ struct page *pp_page, *reg_page;
+ netmem_ref pp_nm, niov_nm;
+ struct page_pool *pool;
+ long base_pp_ref, base_iov_ref;
+ int base_pp_page_ref, base_reg_ref;
+
+ pool = page_pool_create(&pp_params);
+ KUNIT_ASSERT_FALSE(test, IS_ERR(pool));
+ pp_page = page_pool_dev_alloc_pages(pool);
+ KUNIT_ASSERT_NOT_NULL(test, pp_page);
+ pp_nm = page_to_netmem(pp_page);
+ page_pool_ref_netmem(pp_nm);
+ reg_page = alloc_page(GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, reg_page);
+
+ KUNIT_ASSERT_EQ(test,
+ percpu_ref_init(&binding.ref, dummy_percpu_ref_release,
+ PERCPU_REF_INIT_ATOMIC, GFP_KERNEL),
+ 0);
+ net_iov_init(&niov, &binding.area, NET_IOV_DMABUF);
+ niov_nm = net_iov_to_netmem(&niov);
+ netmem_set_pp(niov_nm, pool);
+ netmem_or_pp_magic(niov_nm, PP_SIGNATURE);
+ atomic_long_set(&niov.desc.pp_ref_count, 2);
+
+ base_pp_ref = atomic_long_read(&pp_page->pp_ref_count);
+ base_pp_page_ref = page_ref_count(pp_page);
+ base_reg_ref = page_ref_count(reg_page);
+ base_iov_ref = atomic_long_read(&binding.ref.data->count);
+
+ /* 1. Single GSO skb with page_pool net_iov frags */
+ head = alloc_skb(64, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, head);
+ head->pp_recycle = 1;
+ head->protocol = htons(ETH_P_IP);
+ skb_reset_mac_header(head);
+ skb_reset_network_header(head);
+ skb_fill_netmem_desc(head, 0, niov_nm, 0, 64);
+ head->len = 64;
+ head->data_len = 64;
+ skb_shinfo(head)->gso_size = 16;
+
+ segs = skb_segment(head, NETIF_F_SG | NETIF_F_HW_CSUM);
+ KUNIT_ASSERT_FALSE(test, IS_ERR_OR_NULL(segs));
+ for (seg = segs; seg; seg = seg->next)
+ KUNIT_EXPECT_TRUE(test, seg->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 6L);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&binding.ref.data->count),
+ base_iov_ref);
+ kfree_skb_list(segs);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&niov.desc.pp_ref_count), 2L);
+ skb_shinfo(head)->nr_frags = 0;
+ consume_skb(head);
+
+ /* 2. GRO frag_list fast path (skb_clone of pp_recycle=1 list_skb) */
+ head = alloc_skb(64, GFP_KERNEL);
+ list = alloc_skb(64, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, head);
+ KUNIT_ASSERT_NOT_NULL(test, list);
+ head->pp_recycle = 1;
+ list->pp_recycle = 1;
+ head->protocol = htons(ETH_P_IP);
+ skb_reset_mac_header(head);
+ skb_reset_network_header(head);
+ skb_put(head, 16);
+ skb_fill_netmem_desc(head, 0, pp_nm, 0, 16);
+ head->len += 16;
+ head->data_len = 16;
+ page_pool_ref_netmem(pp_nm);
+
+ skb_put(list, 16);
+ skb_fill_netmem_desc(list, 0, pp_nm, 16, 16);
+ list->len += 16;
+ list->data_len = 16;
+ page_pool_ref_netmem(pp_nm);
+
+ skb_shinfo(head)->frag_list = list;
+ head->len += list->len;
+ head->data_len += list->len;
+ skb_shinfo(head)->gso_size = 32;
+
+ segs = skb_segment(head, NETIF_F_SG | NETIF_F_HW_CSUM);
+ KUNIT_ASSERT_FALSE(test, IS_ERR_OR_NULL(segs));
+ for (seg = segs; seg; seg = seg->next)
+ KUNIT_EXPECT_TRUE(test, seg->pp_recycle);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&pp_page->pp_ref_count),
+ base_pp_ref + 4);
+ KUNIT_EXPECT_EQ(test, page_ref_count(pp_page), base_pp_page_ref);
+ kfree_skb_list(segs);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&pp_page->pp_ref_count),
+ base_pp_ref + 2);
+ consume_skb(head);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&pp_page->pp_ref_count),
+ base_pp_ref);
+
+ /* 3. Mixed GRO frag_list: head pp_page + list reg_page */
+ head = alloc_skb(64, GFP_KERNEL);
+ list = alloc_skb(0, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_NULL(test, head);
+ KUNIT_ASSERT_NOT_NULL(test, list);
+ head->pp_recycle = 1;
+ list->pp_recycle = 0;
+ head->protocol = htons(ETH_P_IP);
+ skb_reset_mac_header(head);
+ skb_reset_network_header(head);
+ skb_fill_netmem_desc(head, 0, pp_nm, 0, 24);
+ head->len = 24;
+ head->data_len = 24;
+ page_pool_ref_netmem(pp_nm);
+
+ skb_fill_page_desc(list, 0, reg_page, 0, 24);
+ get_page(reg_page);
+ list->len = 24;
+ list->data_len = 24;
+
+ skb_shinfo(head)->frag_list = list;
+ head->len += list->len;
+ head->data_len += list->len;
+ skb_shinfo(head)->gso_size = 16;
+
+ /* Segment 0: [pp_page:16], Segment 1: [pp_page:8 + reg_page:8],
+ * Segment 2: [reg_page:16]. Freeing all segments restores exact refs!
+ */
+ segs = skb_segment(head, NETIF_F_SG | NETIF_F_HW_CSUM);
+ KUNIT_ASSERT_FALSE(test, IS_ERR_OR_NULL(segs));
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&pp_page->pp_ref_count),
+ base_pp_ref + 3);
+ KUNIT_EXPECT_EQ(test, page_ref_count(pp_page), base_pp_page_ref);
+ KUNIT_EXPECT_EQ(test, page_ref_count(reg_page), base_reg_ref + 3);
+ kfree_skb_list(segs);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&pp_page->pp_ref_count),
+ base_pp_ref + 1);
+ KUNIT_EXPECT_EQ(test, page_ref_count(pp_page), base_pp_page_ref);
+ KUNIT_EXPECT_EQ(test, page_ref_count(reg_page), base_reg_ref + 1);
+
+ consume_skb(head);
+ KUNIT_EXPECT_EQ(test, atomic_long_read(&pp_page->pp_ref_count),
+ base_pp_ref);
+ page_pool_put_full_page(pool, pp_page, false);
+ page_pool_put_full_page(pool, pp_page, false);
+ __free_page(reg_page);
+ netmem_clear_pp_magic(niov_nm);
+ netmem_set_pp(niov_nm, NULL);
+ page_pool_destroy(pool);
+ percpu_ref_exit(&binding.ref);
+}
+#endif
+
static struct kunit_case net_test_cases[] = {
KUNIT_CASE_PARAM(gso_test_func, gso_test_gen_params),
KUNIT_CASE_PARAM(ip_tunnel_flags_test_run,
ip_tunnel_flags_test_gen_params),
+ KUNIT_CASE(netmem_ref_test_page),
+#ifdef CONFIG_PAGE_POOL
+ KUNIT_CASE(netmem_ref_test_pp_page),
+#endif
+#if defined(CONFIG_PAGE_POOL) && defined(CONFIG_NET_DEVMEM)
+ KUNIT_CASE(netmem_ref_test_net_iov),
+ KUNIT_CASE(netmem_ref_test_release_data),
+ KUNIT_CASE(netmem_ref_test_skb_segment),
+#endif
{ },
};
diff --git a/net/core/skbuff.c b/net/core/skbuff.c
index d56f4f0102f75..554af794bd7cd 100644
--- a/net/core/skbuff.c
+++ b/net/core/skbuff.c
@@ -1026,6 +1026,15 @@ int skb_cow_data_for_xdp(struct page_pool *pool, struct sk_buff **pskb,
EXPORT_SYMBOL(skb_cow_data_for_xdp);
#if IS_ENABLED(CONFIG_PAGE_POOL)
+/**
+ * napi_pp_put_page - release a page_pool reference on a netmem
+ * @netmem: netmem to unreference
+ *
+ * Drops a page_pool reference (pp_ref_count) on @netmem if it belongs to a
+ * page_pool. Counterpart to napi_pp_get_page().
+ *
+ * Return: true if @netmem belongs to a page_pool, false otherwise.
+ */
bool napi_pp_put_page(netmem_ref netmem)
{
netmem = netmem_compound_head(netmem);
@@ -1038,44 +1047,35 @@ bool napi_pp_put_page(netmem_ref netmem)
return true;
}
EXPORT_SYMBOL(napi_pp_put_page);
-#endif
-
-static bool skb_pp_recycle(struct sk_buff *skb, void *data)
-{
- if (!IS_ENABLED(CONFIG_PAGE_POOL) || !skb->pp_recycle)
- return false;
- return napi_pp_put_page(page_to_netmem(virt_to_page(data)));
-}
/**
- * skb_pp_frag_ref() - Increase fragment references of a page pool aware skb
- * @skb: page pool aware skb
- *
- * Increase the fragment reference count (pp_ref_count) of a skb. This is
- * intended to gain fragment references only for page pool aware skbs,
- * i.e. when skb->pp_recycle is true, and not for fragments in a
- * non-pp-recycling skb. It has a fallback to increase references on normal
- * pages, as page pool aware skbs may also have normal page fragments.
+ * napi_pp_get_page - acquire a page_pool reference on a netmem
+ * @netmem: netmem to reference
+ *
+ * Acquires a page_pool reference (pp_ref_count) on @netmem if it belongs to a
+ * page_pool. Counterpart to napi_pp_put_page().
+ *
+ * Return: true if @netmem belongs to a page_pool, false otherwise.
*/
-static int skb_pp_frag_ref(struct sk_buff *skb)
+bool napi_pp_get_page(netmem_ref netmem)
{
- struct skb_shared_info *shinfo;
- netmem_ref head_netmem;
- int i;
+ netmem = netmem_compound_head(netmem);
- if (!skb->pp_recycle)
- return -EINVAL;
+ if (unlikely(!netmem_is_pp(netmem)))
+ return false;
- shinfo = skb_shinfo(skb);
+ page_pool_ref_netmem(netmem);
- for (i = 0; i < shinfo->nr_frags; i++) {
- head_netmem = netmem_compound_head(shinfo->frags[i].netmem);
- if (likely(netmem_is_pp(head_netmem)))
- page_pool_ref_netmem(head_netmem);
- else
- page_ref_inc(netmem_to_page(head_netmem));
- }
- return 0;
+ return true;
+}
+EXPORT_SYMBOL(napi_pp_get_page);
+#endif
+
+static bool skb_pp_recycle(struct sk_buff *skb, void *data)
+{
+ if (!IS_ENABLED(CONFIG_PAGE_POOL) || !skb->pp_recycle)
+ return false;
+ return napi_pp_put_page(page_to_netmem(virt_to_page(data)));
}
static void skb_kfree_head(void *head)
@@ -1102,7 +1102,7 @@ static void skb_release_data(struct sk_buff *skb, enum skb_drop_reason reason)
int i;
if (!skb_data_unref(skb, shinfo))
- goto exit;
+ return;
if (skb_zcopy(skb)) {
bool skip_unref = shinfo->flags & SKBFL_MANAGED_FRAG_REFS;
@@ -1113,24 +1113,14 @@ static void skb_release_data(struct sk_buff *skb, enum skb_drop_reason reason)
}
for (i = 0; i < shinfo->nr_frags; i++)
- __skb_frag_unref(&shinfo->frags[i], skb->pp_recycle);
+ skb_netmem_unref(skb_frag_netmem(&shinfo->frags[i]),
+ skb->pp_recycle);
free_head:
if (shinfo->frag_list)
kfree_skb_list_reason(shinfo->frag_list, reason);
skb_free_head(skb);
-exit:
- /* When we clone an SKB we copy the reycling bit. The pp_recycle
- * bit is only set on the head though, so in order to avoid races
- * while trying to recycle fragments on __skb_frag_unref() we need
- * to make one SKB responsible for triggering the recycle path.
- * So disable the recycling bit if an SKB is cloned and we have
- * additional references to the fragmented part of the SKB.
- * Eventually the last SKB will have the recycling bit set and it's
- * dataref set to 0, which will trigger the recycling
- */
- skb->pp_recycle = 0;
}
/*
@@ -2255,9 +2245,10 @@ struct sk_buff *__pskb_copy_fclone(struct sk_buff *skb, int headroom,
n = NULL;
goto out;
}
+ n->pp_recycle = skb->pp_recycle;
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
skb_shinfo(n)->frags[i] = skb_shinfo(skb)->frags[i];
- skb_frag_ref(skb, i);
+ skb_frag_ref(n, i);
}
skb_shinfo(n)->nr_frags = i;
skb_shinfo(n)->flags |= skb_shinfo(skb)->flags & SKBFL_SHARED_FRAG;
@@ -3895,6 +3886,7 @@ skb_zerocopy(struct sk_buff *to, struct sk_buff *from, int len, int hlen)
if (len <= skb_tailroom(to))
return skb_copy_bits(from, 0, skb_put(to, len), len);
+ to->pp_recycle = from->pp_recycle;
if (hlen) {
ret = skb_copy_bits(from, 0, skb_put(to, hlen), hlen);
if (unlikely(ret))
@@ -3907,14 +3899,14 @@ skb_zerocopy(struct sk_buff *to, struct sk_buff *from, int len, int hlen)
offset = from->data - (unsigned char *)page_address(page);
__skb_fill_netmem_desc(to, 0, page_to_netmem(page),
offset, plen);
- get_page(page);
+ skb_frag_ref(to, 0);
j = 1;
len -= plen;
}
}
if (!skb_frags_readable(from) && j > 0 && len) {
- put_page(page);
+ skb_frag_unref(to, 0);
return -EFAULT;
}
@@ -3922,7 +3914,7 @@ skb_zerocopy(struct sk_buff *to, struct sk_buff *from, int len, int hlen)
if (unlikely(skb_orphan_frags(from, GFP_ATOMIC))) {
if (j > 0)
- put_page(page);
+ skb_frag_unref(to, 0);
return -ENOMEM;
}
skb_zerocopy_clone(to, from, GFP_ATOMIC);
@@ -4223,7 +4215,7 @@ static inline void skb_split_no_header(struct sk_buff *skb,
* where splitting is expensive.
* 2. Split is accurately. We make this.
*/
- skb_frag_ref(skb, i);
+ skb_frag_ref(skb1, k);
skb_frag_off_add(&skb_shinfo(skb1)->frags[0], len - pos);
skb_frag_size_sub(&skb_shinfo(skb1)->frags[0], len - pos);
skb_frag_size_set(&skb_shinfo(skb)->frags[i], len - pos);
@@ -4254,6 +4246,7 @@ void skb_split(struct sk_buff *skb, struct sk_buff *skb1, const u32 len)
skb_shinfo(skb1)->flags |= skb_shinfo(skb)->flags & zc_flags;
skb_zerocopy_clone(skb1, skb, 0);
+ skb1->pp_recycle = skb->pp_recycle;
if (len < pos) /* Split line is inside header. */
skb_split_inside_header(skb, skb1, len, pos);
else /* Second chunk has no header, nothing to copy. */
@@ -4311,8 +4304,8 @@ int skb_shift(struct sk_buff *tgt, struct sk_buff *skb, int shiftlen)
/* Actual merge is delayed until the point when we know we can
* commit all, so that we don't have to undo partial changes
*/
- if (!skb_can_coalesce(tgt, to, skb_frag_page(fragfrom),
- skb_frag_off(fragfrom))) {
+ if (!skb_can_coalesce_netmem(tgt, to, skb_frag_netmem(fragfrom),
+ skb_frag_off(fragfrom))) {
merge = -1;
} else {
merge = to - 1;
@@ -4359,8 +4352,8 @@ int skb_shift(struct sk_buff *tgt, struct sk_buff *skb, int shiftlen)
to++;
} else {
- __skb_frag_ref(fragfrom);
skb_frag_page_copy(fragto, fragfrom);
+ skb_frag_ref(tgt, to);
skb_frag_off_copy(fragto, fragfrom);
skb_frag_size_set(fragto, todo);
@@ -4381,7 +4374,7 @@ int skb_shift(struct sk_buff *tgt, struct sk_buff *skb, int shiftlen)
fragto = &skb_shinfo(tgt)->frags[merge];
skb_frag_size_add(fragto, skb_frag_size(fragfrom));
- __skb_frag_unref(fragfrom, skb->pp_recycle);
+ skb_frag_unref(skb, 0);
}
/* Reposition in the original skb */
@@ -5055,8 +5048,10 @@ struct sk_buff *skb_segment(struct sk_buff *head_skb,
goto err;
}
+ if (!skb_shinfo(nskb)->nr_frags)
+ nskb->pp_recycle = frag_skb->pp_recycle;
*nskb_frag = (i < 0) ? skb_head_frag_to_page_desc(frag_skb) : *frag;
- __skb_frag_ref(nskb_frag);
+ skb_frag_ref(nskb, skb_shinfo(nskb)->nr_frags);
size = skb_frag_size(nskb_frag);
if (pos < offset) {
@@ -6269,10 +6264,8 @@ bool skb_try_coalesce(struct sk_buff *to, struct sk_buff *from,
/* if the skb is not cloned this does nothing
* since we set nr_frags to 0.
*/
- if (skb_pp_frag_ref(from)) {
- for (i = 0; i < from_shinfo->nr_frags; i++)
- __skb_frag_ref(&from_shinfo->frags[i]);
- }
+ for (i = 0; i < from_shinfo->nr_frags; i++)
+ skb_frag_ref(from, i);
to->truesize += delta;
to->len += len;
@@ -7551,23 +7544,39 @@ bool csum_and_copy_from_iter_full(void *addr, size_t bytes,
}
EXPORT_SYMBOL(csum_and_copy_from_iter_full);
-void __get_netmem(netmem_ref netmem)
+/**
+ * get_net_iov - acquire a non-page_pool reference on a net_iov
+ * @niov: net_iov to reference
+ *
+ * Acquires a non-pp backing reference (binding->ref) on @niov regardless of
+ * page_pool membership, warning if @niov does not support non-pp references.
+ * Counterpart to put_net_iov().
+ */
+void get_net_iov(struct net_iov *niov)
{
- struct net_iov *niov = netmem_to_net_iov(netmem);
-
if (net_is_devmem_iov(niov))
- net_devmem_get_net_iov(netmem_to_net_iov(netmem));
+ net_devmem_get_net_iov(niov);
+ else
+ DEBUG_NET_WARN_ON_ONCE(true);
}
-EXPORT_SYMBOL(__get_netmem);
+EXPORT_SYMBOL(get_net_iov);
-void __put_netmem(netmem_ref netmem)
+/**
+ * put_net_iov - release a non-page_pool reference on a net_iov
+ * @niov: net_iov to unreference
+ *
+ * Drops a non-pp backing reference (binding->ref) on @niov regardless of
+ * page_pool membership, warning if @niov does not support non-pp references.
+ * Counterpart to get_net_iov().
+ */
+void put_net_iov(struct net_iov *niov)
{
- struct net_iov *niov = netmem_to_net_iov(netmem);
-
if (net_is_devmem_iov(niov))
- net_devmem_put_net_iov(netmem_to_net_iov(netmem));
+ net_devmem_put_net_iov(niov);
+ else
+ DEBUG_NET_WARN_ON_ONCE(true);
}
-EXPORT_SYMBOL(__put_netmem);
+EXPORT_SYMBOL(put_net_iov);
struct vlan_type_depth __vlan_get_protocol_offset(const struct sk_buff *skb,
__be16 type,
diff --git a/net/ipv4/esp4.c b/net/ipv4/esp4.c
index e76db5817e78e..ae701561d9f12 100644
--- a/net/ipv4/esp4.c
+++ b/net/ipv4/esp4.c
@@ -117,8 +117,8 @@ static void esp_ssg_unref(struct xfrm_state *x, void *tmp, struct sk_buff *skb,
struct scatterlist *src = already_unref ? esp_req_sg(aead, req) : req->src;
for (sg = sg_next(src); sg; sg = sg_next(sg))
- skb_page_unref(page_to_netmem(sg_page(sg)),
- skb->pp_recycle);
+ skb_netmem_unref(page_to_netmem(sg_page(sg)),
+ skb->pp_recycle);
}
}
diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c
index f4c9101e5996a..99d7237a623d8 100644
--- a/net/ipv4/tcp.c
+++ b/net/ipv4/tcp.c
@@ -254,6 +254,7 @@
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/skbuff.h>
+#include <linux/skbuff_ref.h>
#include <linux/splice.h>
#include <linux/net.h>
#include <linux/socket.h>
@@ -2559,6 +2560,7 @@ static int tcp_recvmsg_dmabuf(struct sock *sk, const struct sk_buff *skb,
*/
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
+ netmem_ref netmem = skb_frag_netmem(frag);
struct net_iov *niov;
u64 frag_offset;
int end;
@@ -2612,8 +2614,8 @@ static int tcp_recvmsg_dmabuf(struct sock *sk, const struct sk_buff *skb,
if (err)
goto out;
- atomic_long_inc(&niov->desc.pp_ref_count);
- tcp_xa_pool.netmems[tcp_xa_pool.idx++] = skb_frag_netmem(frag);
+ WARN_ON_ONCE(!napi_pp_get_page(netmem));
+ tcp_xa_pool.netmems[tcp_xa_pool.idx++] = netmem;
sent += copy;
diff --git a/net/ipv6/esp6.c b/net/ipv6/esp6.c
index b1c9b36f76dc4..d654f0f442388 100644
--- a/net/ipv6/esp6.c
+++ b/net/ipv6/esp6.c
@@ -134,8 +134,8 @@ static void esp_ssg_unref(struct xfrm_state *x, void *tmp, struct sk_buff *skb,
struct scatterlist *src = already_unref ? esp_req_sg(aead, req) : req->src;
for (sg = sg_next(src); sg; sg = sg_next(sg))
- skb_page_unref(page_to_netmem(sg_page(sg)),
- skb->pp_recycle);
+ skb_netmem_unref(page_to_netmem(sg_page(sg)),
+ skb->pp_recycle);
}
}
diff --git a/net/tls/tls_device.c b/net/tls/tls_device.c
index f11d0528fc431..b333a9a72cdae 100644
--- a/net/tls/tls_device.c
+++ b/net/tls/tls_device.c
@@ -143,7 +143,7 @@ static void destroy_record(struct tls_record_info *record)
int i;
for (i = 0; i < record->num_frags; i++)
- __skb_frag_unref(&record->frags[i], false);
+ put_netmem(skb_frag_netmem(&record->frags[i]));
kfree(record);
}
diff --git a/net/tls/tls_device_fallback.c b/net/tls/tls_device_fallback.c
index 3b7d0ab2bcf17..35cc510db4321 100644
--- a/net/tls/tls_device_fallback.c
+++ b/net/tls/tls_device_fallback.c
@@ -256,7 +256,7 @@ static int fill_sg_in(struct scatterlist *sg_in,
for (i = 0; remaining > 0; i++) {
skb_frag_t *frag = &record->frags[i];
- __skb_frag_ref(frag);
+ get_netmem(skb_frag_netmem(frag));
sg_set_page(sg_in + i, skb_frag_page(frag),
skb_frag_size(frag), skb_frag_off(frag));
diff --git a/net/tls/tls_strp.c b/net/tls/tls_strp.c
index 6cc222008d95c..53d3256d0114a 100644
--- a/net/tls/tls_strp.c
+++ b/net/tls/tls_strp.c
@@ -197,7 +197,7 @@ static void tls_strp_flush_anchor_copy(struct tls_strparser *strp)
DEBUG_NET_WARN_ON_ONCE(atomic_read(&shinfo->dataref) != 1);
for (i = 0; i < shinfo->nr_frags; i++)
- __skb_frag_unref(&shinfo->frags[i], false);
+ skb_frag_unref(strp->anchor, i);
shinfo->nr_frags = 0;
if (strp->copy_mode) {
kfree_skb_list(shinfo->frag_list);
diff --git a/net/xfrm/xfrm_iptfs.c b/net/xfrm/xfrm_iptfs.c
index 6920940a35b49..103cf7c5dbfb9 100644
--- a/net/xfrm/xfrm_iptfs.c
+++ b/net/xfrm/xfrm_iptfs.c
@@ -486,8 +486,7 @@ static int iptfs_skb_add_frags(struct sk_buff *skb,
tofrag->len -= offset;
offset = 0;
}
- __skb_frag_ref(tofrag);
- shinfo->nr_frags++;
+ skb_frag_ref(skb, shinfo->nr_frags++);
shinfo->flags |= SKBFL_SHARED_FRAG;
/* see if we are done */
|
| 63/3 | 2026/10/10 08:50 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 23750 output: 239 thoughts: 3444 Thoughts: **KMSAN Assessment: SKB Fragment Refcounting Refactor** Okay, let's break this down. My primary concern here is determining if this patch series warrants a dedicated KMSAN fuzzing session. The guiding principle is clear: if standard bug detectors (KASAN, LOCKDEP, etc.) can catch the potential issues, then KMSAN is overkill – it's an expensive tool. So, I need to methodically analyze this refactor of SKB fragment refcounting through the lens of uninitialized memory usage. First, I'm reviewing the instructions. KASAN is already enabled, so that's my baseline. KMSAN's strength lies in detecting reads of *uninitialized* memory, especially in stack, heap, and page allocations, and also kernel-to-user memory leaks. The instructions highlight four key scenarios where KMSAN becomes crucial. This is my checklist: are kernel structures sent to userspace with potentially uninitialized fields? Does the patch introduce any conditional logic based on uninitialized values? Does it involve allocations where uninitialized fields could be read later? Does it alter bounds checks in a way that introduces uninitialized byte access? If the answer is "no" to all, KMSAN is unnecessary. Now, let's dissect the patch itself. It's essentially unifying the handling of page pool refcounting, especially as it relates to SKB fragments. Previously, it looks like there were inconsistencies in how references were managed across various SKB operations. This patch aims to fix that. The core of the patch involves replacing calls to `__skb_frag_ref` and `__skb_frag_unref` with `skb_frag_ref` and `skb_frag_unref`, respectively, and also dealing with `skb->pp_recycle` correctly. These calls, in turn, leverage `napi_pp_get_page` and `napi_pp_put_page`, or `get_netmem` and `put_netmem`. What potential bugs *could* this refactor introduce? Refcount imbalances (underflow or overflow), use-after-free, memory leaks, and page pool recycling problems are the obvious ones. But, I need to determine if any of these are related to uninitialized memory. Use-after-free bugs are *definitely* within KASAN's domain. Memory leaks are usually caught by `kmemleak`. Refcount bugs are covered by the refcount and/or atomic warnings as well as KASAN. Let's carefully go through the code with the KMSAN criteria in mind. 1. **Does it introduce or modify kernel structures sent to user space?** The `tcp_recvmsg_dmabuf` change is internal and does not transmit any uninitialized state to the userspace. 2. **Does it introduce conditional logic based on uninitialized variables?** I see no new branches or conditionals that are driven by uninitialized data. The variables used, like `skb->pp_recycle`, are already initialized as parts of existing SKB or netmem structs. 3. **Does it involve new allocations of structures where uninitialized fields could be read later?** No, there are no new allocations, just refcounting. The KUnit tests initialize these structures. 4. **Does it alter bounds checks in a way that introduces uninitialized byte access?** The index is correct with `shinfo->nr_frags++`, this does not allow for surrounding code to access uninitialized memory. This looks safe. I'm double-checking key call sites and the surrounding logic. The refactoring consistently ensures that the `frags[i]` elements are fully initialized *before* `skb_frag_ref` is called. No, the refactoring does not use, nor expose any uninitialized memory to user space. It deals exclusively with existing, and initialized data. The tests in `net_test.c` are also confirming this. So, in conclusion: This patch refactors SKB fragment reference counting and page pool interaction. The potential bugs are primarily related to reference counting and access errors, not uninitialized memory. KASAN, and related tools are more appropriate here. There are no new uninitialized memory reads, or info leaks. Therefore, `NeedsKMSAN` is `false`. The reasoning is a clear and concise explanation contrasting KASAN's coverage versus KMSAN's applicability in this specific patch series. That should be sufficient for the `KMSANReasoning` field. |
| 64/3 | 2026/10/10 08:51 | tool | set-results |
0mArgs:{
"KMSANReasoning": "This patch series refactors SKB (sk_buff) fragment reference counting and page_pool integration, introducing unified helpers (skb_netmem_ref, skb_netmem_unref, skb_frag_ref, skb_frag_unref) that correctly account for page_pool references (pp_ref_count) versus non-page_pool backing references across skb cloning, splitting, coalescing, shifting, and segmentation operations.\n\nThe potential hazards associated with these changes are reference count imbalances leading to use-after-free (UAF) conditions, premature page releases, or memory leaks. These bug classes are covered by KASAN and existing page/slab refcount sanity checks. The patch does not introduce or modify any kernel-to-user copy paths (no info-leak risks), does not allocate or read uninitialized memory buffers, and does not alter branching on uninitialized fields. Therefore, a dedicated KMSAN fuzzing session is not warranted, and standard KASAN fuzzing is sufficient.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "This patch series refactors SKB (sk_buff) fragment reference counting and page_pool integration, introducing unified helpers (skb_netmem_ref, skb_netmem_unref, skb_frag_ref, skb_frag_unref) that correctly account for page_pool references (pp_ref_count) versus non-page_pool backing references across skb cloning, splitting, coalescing, shifting, and segmentation operations.\n\nThe potential hazards associated with these changes are reference count imbalances leading to use-after-free (UAF) conditions, premature page releases, or memory leaks. These bug classes are covered by KASAN and existing page/slab refcount sanity checks. The patch does not introduce or modify any kernel-to-user copy paths (no info-leak risks), does not allocate or read uninitialized memory buffers, and does not alter branching on uninitialized fields. Therefore, a dedicated KMSAN fuzzing session is not warranted, and standard KASAN fuzzing is sufficient.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|