| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/24 18:19 | flow | patch-triage |
35mArgs:null Results: {
"EnableConfigs": [
"SWIOTLB"
],
"FocusSymbols": [
"swiotlb_alloc_pages",
"swiotlb_free_pages",
"swiotlb_nocopy_inc_ref",
"swiotlb_nocopy_dec_ref",
"swiotlb_pool_is_nocopy",
"swiotlb_safe_put_device",
"sk_record_bounce_device",
"__skb_page_frag_refill"
],
"KMSANReasoning": "The patch introduces a \"nocopy SWIOTLB\" optimization for network sockets, which routes page allocations for known bounce-buffering sockets directly to the SWIOTLB pool. \n\n1. **New Data Structures**: The patch adds `sk_swiotlb_info` to `struct sock` and introduces `swiotlb_deferred_put`. Both are strictly kernel-internal and never copied to userspace. They are also fully initialized upon creation (`sk_alloc` uses `__GFP_ZERO`, and `swiotlb_deferred_put` explicitly initializes all its fields).\n2. **Custom Allocator Bypasses KMSAN**: The new `swiotlb_alloc_pages()` function bypasses the standard `alloc_pages()` API. Because KMSAN relies on hooks within the standard page allocator to poison newly allocated memory, it is completely unaware of these SWIOTLB allocations. As a result, KMSAN will view these pages as fully initialized (due to the boot-time `memset` of the SWIOTLB pool), making it blind to any uninitialized memory leaks or uses originating from these buffers.\n3. **Bug Classes**: Any potential memory safety issues introduced by this patch—such as out-of-bounds accesses in the SWIOTLB pool, use-after-free of device pointers, or race conditions in the lockless refcounting—are fully covered by KASAN, LOCKDEP, and KCSAN.\n\nSince the patch does not introduce any uninitialized memory risks that KMSAN is capable of detecting, a dedicated KMSAN fuzzing session is not justified.",
"NeedsKMSAN": false,
"Reasoning": "The patch implements a new 'nocopy' SWIOTLB feature for networking, allowing direct DMA mapping of network buffers without bounce buffering. It modifies core SWIOTLB allocation logic, DMA mapping paths, and network page allocation (skb_page_frag_refill, page_pool). These are significant functional changes to core kernel subsystems that are reachable and should be fuzzed.",
"WorthFuzzing": true
} |
| 1/1 | 2026/08/24 18:19 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 6fe30e0c486a30f009129aa8c785d15bf9848f47\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Mon Aug 24 18:19:01 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/drivers/base/core.c b/drivers/base/core.c\nindex 4c0c373998a19..091062228740d 100644\n--- a/drivers/base/core.c\n+++ b/drivers/base/core.c\n@@ -3925,6 +3925,7 @@ void device_del(struct device *dev)\n \tunsigned int noio_flag;\n \n \tdevice_lock(dev);\n+\tswiotlb_change_epoch();\n \tkill_device(dev);\n \tdevice_unlock(dev);\n \ndiff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c\nindex 9a07eb39336eb..956d5e11b2896 100644\n--- a/drivers/iommu/dma-iommu.c\n+++ b/drivers/iommu/dma-iommu.c\n@@ -1228,7 +1228,14 @@ dma_addr_t iommu_dma_map_phys(struct device *dev, phys_addr_t phys, size_t size,\n \t * If both the physical buffer start address and size are page aligned,\n \t * we don't need to use a bounce page.\n \t */\n-\tif (dev_use_swiotlb(dev, size, dir) \u0026\u0026\n+\tbool is_nocopy = false;\n+\n+\tif (swiotlb_is_nocopy_addr(dev, phys)) {\n+\t\tswiotlb_nocopy_inc_ref(\u0026dev-\u003edma_io_tlb_mem-\u003edefpool, phys);\n+\t\tis_nocopy = true;\n+\t}\n+\n+\tif (!is_nocopy \u0026\u0026 dev_use_swiotlb(dev, size, dir) \u0026\u0026\n \t iova_unaligned(iovad, phys, size)) {\n \t\tif (attrs \u0026 (DMA_ATTR_MMIO | DMA_ATTR_REQUIRE_COHERENT))\n \t\t\treturn DMA_MAPPING_ERROR;\ndiff --git a/include/linux/netdevice.h b/include/linux/netdevice.h\nindex 87cafc932e9e6..2457f4e464acf 100644\n--- a/include/linux/netdevice.h\n+++ b/include/linux/netdevice.h\n@@ -5429,13 +5429,34 @@ static inline netdev_tx_t __netdev_start_xmit(const struct net_device_ops *ops,\n \treturn ops-\u003endo_start_xmit(skb, dev);\n }\n \n+struct sock;\n+\n+#if defined(CONFIG_SWIOTLB) \u0026\u0026 !defined(CONFIG_PREEMPT_RT)\n+/* Per-CPU pointer to the socket currently performing transmission. Used\n+ * to bridge the networking and DMA layers, allowing dma_map_page() to\n+ * identify the socket originating the packet and apply SWIOTLB optimizations.\n+ */\n+DECLARE_PER_CPU(struct sock *, current_tx_socket);\n+static inline struct sock *__save_current_tx_socket(struct sock *sk)\n+{\n+\tstruct sock *old_sk = this_cpu_read(current_tx_socket);\n+\n+\tthis_cpu_write(current_tx_socket, sk);\n+\treturn old_sk;\n+}\n+#else\n+static inline struct sock *__save_current_tx_socket(struct sock *sk) { return NULL; }\n+#endif\n+\n static inline netdev_tx_t netdev_start_xmit(struct sk_buff *skb, struct net_device *dev,\n \t\t\t\t\t struct netdev_queue *txq, bool more)\n {\n+\tstruct sock *old_sk = __save_current_tx_socket(skb-\u003esk);\n \tconst struct net_device_ops *ops = dev-\u003enetdev_ops;\n \tnetdev_tx_t rc;\n \n \trc = __netdev_start_xmit(ops, skb, dev, more);\n+\t__save_current_tx_socket(old_sk);\n \tif (rc == NETDEV_TX_OK)\n \t\ttxq_trans_update(dev, txq);\n \ndiff --git a/include/linux/skbuff.h b/include/linux/skbuff.h\nindex add0d282dea6e..d8f7041edc400 100644\n--- a/include/linux/skbuff.h\n+++ b/include/linux/skbuff.h\n@@ -3786,7 +3786,12 @@ static inline void skb_frag_page_copy(skb_frag_t *fragto,\n \tfragto-\u003enetmem = fragfrom-\u003enetmem;\n }\n \n-bool skb_page_frag_refill(unsigned int sz, struct page_frag *pfrag, gfp_t prio);\n+/* nocopy swiotlb uses an additional non-null struct sock pointer. */\n+bool __skb_page_frag_refill(unsigned int sz, struct page_frag *pfrag, gfp_t prio, struct sock *sk);\n+static inline bool skb_page_frag_refill(unsigned int sz, struct page_frag *pfrag, gfp_t prio)\n+{\n+\treturn __skb_page_frag_refill(sz, pfrag, prio, NULL);\n+}\n \n /**\n * __skb_frag_dma_map - maps a paged fragment via the DMA API\ndiff --git a/include/linux/swiotlb.h b/include/linux/swiotlb.h\nindex 3dae0f592063e..f4597fd01c52d 100644\n--- a/include/linux/swiotlb.h\n+++ b/include/linux/swiotlb.h\n@@ -169,6 +169,23 @@ static inline struct io_tlb_pool *swiotlb_find_pool(struct device *dev,\n \treturn NULL;\n }\n \n+bool swiotlb_pool_is_nocopy(struct io_tlb_pool *pool, phys_addr_t paddr);\n+\n+static inline bool swiotlb_addr_in_default_pool(struct device *dev,\n+\t\t\t\t\t\tphys_addr_t paddr)\n+{\n+\tstruct io_tlb_mem *mem = dev-\u003edma_io_tlb_mem;\n+\n+\treturn mem \u0026\u0026 paddr \u003e= mem-\u003edefpool.start \u0026\u0026 paddr \u003c mem-\u003edefpool.end;\n+}\n+\n+static inline bool swiotlb_is_nocopy_addr(struct device *dev, phys_addr_t paddr)\n+{\n+\tif (!swiotlb_addr_in_default_pool(dev, paddr))\n+\t\treturn false;\n+\treturn swiotlb_pool_is_nocopy(\u0026dev-\u003edma_io_tlb_mem-\u003edefpool, paddr);\n+}\n+\n static inline bool is_swiotlb_force_bounce(struct device *dev)\n {\n \tstruct io_tlb_mem *mem = dev-\u003edma_io_tlb_mem;\n@@ -178,6 +195,49 @@ static inline bool is_swiotlb_force_bounce(struct device *dev)\n \n void swiotlb_init(bool addressing_limited, unsigned int flags);\n void __init swiotlb_exit(void);\n+struct page *swiotlb_alloc_pages(struct device *dev, unsigned int order, gfp_t gfp,\n+\t\t\t\t unsigned int percent);\n+bool swiotlb_free_pages(struct page *page, unsigned int order);\n+void swiotlb_nocopy_inc_ref(struct io_tlb_pool *pool, phys_addr_t phys);\n+void swiotlb_nocopy_dec_ref(struct io_tlb_pool *pool, phys_addr_t phys);\n+void swiotlb_prep_compound_page(struct page *page, unsigned int order);\n+void swiotlb_destroy_compound_page(struct page *page, unsigned int order);\n+void swiotlb_safe_put_device(struct device *dev);\n+\n+extern unsigned int nocopy_tx_percent;\n+extern unsigned int nocopy_rx_percent;\n+\n+/* Track epoch (number of delete operations) for leaf device info. */\n+extern atomic_t global_device_epoch;\n+\n+static inline u32 swiotlb_dev_epoch(void)\n+{\n+\treturn atomic_read(\u0026global_device_epoch);\n+}\n+\n+static inline void swiotlb_change_epoch(void)\n+{\n+\tatomic_inc(\u0026global_device_epoch);\n+}\n+\n+#if defined(CONFIG_NET) \u0026\u0026 !defined(CONFIG_PREEMPT_RT)\n+/*\n+ * Track the socket for the currently transmitted packet, so the dma mapping\n+ * function can record there the leaf device if it needs bounce buffers.\n+ */\n+struct sock;\n+DECLARE_PER_CPU(struct sock *, current_tx_socket);\n+void sk_record_bounce_device(struct sock *sk, struct device *dev);\n+static inline void dma_learn_bounce_device(struct device *dev)\n+{\n+\tstruct sock *sk = this_cpu_read(current_tx_socket);\n+\n+\tif (sk)\n+\t\tsk_record_bounce_device(sk, dev);\n+}\n+#else\n+static inline void dma_learn_bounce_device(struct device *dev) {}\n+#endif\n void swiotlb_dev_init(struct device *dev);\n size_t swiotlb_max_mapping_size(struct device *dev);\n bool is_swiotlb_allocated(void);\n@@ -234,6 +294,9 @@ static inline phys_addr_t default_swiotlb_limit(void)\n {\n \treturn 0;\n }\n+static inline void swiotlb_safe_put_device(struct device *dev)\n+{\n+}\n #endif /* CONFIG_SWIOTLB */\n \n phys_addr_t swiotlb_tbl_map_single(struct device *hwdev, phys_addr_t phys,\ndiff --git a/include/net/sock.h b/include/net/sock.h\nindex 51185222aac29..39b5e81c7cc55 100644\n--- a/include/net/sock.h\n+++ b/include/net/sock.h\n@@ -47,6 +47,7 @@\n #include \u003clinux/skbuff.h\u003e\t/* struct sk_buff */\n #include \u003clinux/mm.h\u003e\n #include \u003clinux/security.h\u003e\n+#include \u003clinux/swiotlb.h\u003e\n #include \u003clinux/slab.h\u003e\n #include \u003clinux/uaccess.h\u003e\n #include \u003clinux/page_counter.h\u003e\n@@ -70,6 +71,14 @@\n #include \u003cnet/l3mdev.h\u003e\n #include \u003cuapi/linux/socket.h\u003e\n \n+#if defined(CONFIG_SWIOTLB) \u0026\u0026 !defined(CONFIG_PREEMPT_RT)\n+struct sk_swiotlb_info {\n+\tstruct device __rcu\t*dev;\n+\tu32\t\t\tepoch;\n+\tunsigned long\t\tjiffies;\n+};\n+#endif\n+\n /*\n * This structure really needs to be cleaned up.\n * Most of it is for TCP, and not used by any of\n@@ -602,8 +611,45 @@ struct sock {\n #if IS_ENABLED(CONFIG_PROVE_LOCKING) \u0026\u0026 IS_ENABLED(CONFIG_MODULES)\n \tstruct module\t\t*sk_owner;\n #endif\n+#if defined(CONFIG_SWIOTLB) \u0026\u0026 !defined(CONFIG_PREEMPT_RT)\n+\tstruct sk_swiotlb_info\tsk_swiotlb;\n+#endif\n };\n \n+#if defined(CONFIG_SWIOTLB) \u0026\u0026 !defined(CONFIG_PREEMPT_RT)\n+/*\n+ * Clear bounce device on newly initialized or cloned sockets.\n+ * Note: During socket cloning, sock_copy() performs a raw bitwise copy of\n+ * the parent socket without incrementing the device refcount via get_device().\n+ * Therefore, we must zero sk_swiotlb.dev directly here without putting a\n+ * reference. References are acquired solely by sk_record_bounce_device() and\n+ * released in sk_release_bounce_device().\n+ */\n+static inline void sk_clear_bounce_device(struct sock *sk)\n+{\n+\trcu_assign_pointer(sk-\u003esk_swiotlb.dev, NULL);\n+}\n+\n+/*\n+ * Release any device reference acquired via sk_record_bounce_device() during\n+ * socket transmission and clear the device pointer. Called during socket\n+ * destruction (__sk_destruct).\n+ */\n+static inline void sk_release_bounce_device(struct sock *sk)\n+{\n+\tstruct device *dev;\n+\n+\tdev = rcu_dereference_raw(sk-\u003esk_swiotlb.dev);\n+\tif (dev) {\n+\t\tswiotlb_safe_put_device(dev);\n+\t\trcu_assign_pointer(sk-\u003esk_swiotlb.dev, NULL);\n+\t}\n+}\n+#else\n+static inline void sk_clear_bounce_device(struct sock *sk) {}\n+static inline void sk_release_bounce_device(struct sock *sk) {}\n+#endif\n+\n struct sock_bh_locked {\n \tstruct sock *sock;\n \tlocal_lock_t bh_lock;\ndiff --git a/kernel/dma/direct.h b/kernel/dma/direct.h\nindex 7140c208c1238..21c65acb13823 100644\n--- a/kernel/dma/direct.h\n+++ b/kernel/dma/direct.h\n@@ -88,6 +88,17 @@ static inline dma_addr_t dma_direct_map_phys(struct device *dev,\n {\n \tdma_addr_t dma_addr;\n \n+\tif (swiotlb_is_nocopy_addr(dev, phys)) {\n+\t\tdma_addr_t unenc_addr = phys_to_dma_unencrypted(dev, phys);\n+\n+\t\tif (likely(dma_capable(dev, unenc_addr, size, true))) {\n+\t\t\tswiotlb_nocopy_inc_ref(\u0026dev-\u003edma_io_tlb_mem-\u003edefpool, phys);\n+\t\t\tif (!dev_is_dma_coherent(dev) \u0026\u0026 !(attrs \u0026 DMA_ATTR_SKIP_CPU_SYNC))\n+\t\t\t\tarch_sync_dma_for_device(phys, size, dir);\n+\t\t\treturn unenc_addr;\n+\t\t}\n+\t}\n+\n \tif (is_swiotlb_force_bounce(dev)) {\n \t\tif (!(attrs \u0026 DMA_ATTR_CC_SHARED)) {\n \t\t\tif (attrs \u0026 (DMA_ATTR_MMIO | DMA_ATTR_REQUIRE_COHERENT))\ndiff --git a/kernel/dma/swiotlb.c b/kernel/dma/swiotlb.c\nindex 1abd3e6146f45..91f175c34a34e 100644\n--- a/kernel/dma/swiotlb.c\n+++ b/kernel/dma/swiotlb.c\n@@ -33,6 +33,7 @@\n #include \u003clinux/kmsan-checks.h\u003e\n #include \u003clinux/iommu-helper.h\u003e\n #include \u003clinux/init.h\u003e\n+#include \u003clinux/log2.h\u003e\n #include \u003clinux/memblock.h\u003e\n #include \u003clinux/mm.h\u003e\n #include \u003clinux/pfn.h\u003e\n@@ -62,25 +63,77 @@\n */\n #define IO_TLB_MIN_SLABS ((1\u003c\u003c20) \u003e\u003e IO_TLB_SHIFT)\n \n+/* enable nocopy tx swiotlb and set the percentage of buffers allowed for it. */\n+unsigned int nocopy_tx_percent;\n+module_param(nocopy_tx_percent, uint, 0644);\n+MODULE_PARM_DESC(nocopy_tx_percent, \"percentage of swiotlb buffer allowed for nocopy tx\");\n+\n /**\n * struct io_tlb_slot - IO TLB slot descriptor\n * @orig_addr:\tThe original address corresponding to a mapped entry.\n+ * @nocopy_refcnt:\tLockless atomic refcount for Nocopy buffers.\n * @alloc_size:\tSize of the allocated buffer.\n * @list:\tThe free list describing the number of free entries available\n *\t\tfrom each index.\n * @pad_slots:\tNumber of preceding padding slots. Valid only in the first\n *\t\tallocated non-padding slot.\n+ * @flags:\tSlot attributes (e.g. SWIOTLB_SLOT_NOCOPY for Nocopy buffers).\n+ *\n+ * The slot descriptor has states identified by @list and @flags (SWIOTLB_SLOT_NOCOPY):\n+ *\n+ * 1. FREE (list \u003e 0):\n+ * Linear sweep free slot.\n+ *\n+ * 2. USED (list == 0, SWIOTLB_SLOT_NOCOPY flag is NOT set in @flags):\n+ * Allocated SWIOTLB bounce buffer.\n+ * Fields used: @list, @pad_slots, @orig_addr, @alloc_size.\n+ *\n+ * 3. USED_NOCOPY (list == 0, SWIOTLB_SLOT_NOCOPY flag is set in @flags):\n+ * Allocated Nocopy SWIOTLB buffer.\n+ * Fields used: @list, @nocopy_refcnt, @alloc_size.\n */\n+#define SWIOTLB_SLOT_NOCOPY\tBIT(0)\n+\n+/*\n+ * SWIOTLB nocopy allocations (swiotlb_alloc_pages()) do not have an original\n+ * physical address to bounce, but need to pass a caller-specified pool usage\n+ * limit (percentage) down to the area search logic.\n+ *\n+ * To avoid adding a parameter to swiotlb_find_slots(), swiotlb_search_area(),\n+ * and swiotlb_search_pool_area(), the desired percentage (0..90) is encoded\n+ * into the orig_addr parameter in the reserved high address range starting at\n+ * INVALID_PHYS_ADDR (~0ULL).\n+ *\n+ * - NOCOPY_PCT_TO_ADDR(pct): Encodes a percentage into an orig_addr.\n+ * - IS_SWIOTLB_NOCOPY(addr): Identifies a nocopy allocation request and\n+ * restricts slot search to the static default pool.\n+ * - NOCOPY_ADDR_TO_PCT(addr): Extracts the percentage to cap max_usable\n+ * slots in swiotlb_search_pool_area().\n+ */\n+#define NOCOPY_PCT_MAX\t(90u)\n+#define NOCOPY_PCT_TO_ADDR(pct)\t\t(INVALID_PHYS_ADDR - min(pct, NOCOPY_PCT_MAX))\n+#define IS_SWIOTLB_NOCOPY(addr)\t\t((addr) \u003e= INVALID_PHYS_ADDR - NOCOPY_PCT_MAX)\n+#define NOCOPY_ADDR_TO_PCT(addr)\t((unsigned int)(INVALID_PHYS_ADDR - (addr)))\n+\n struct io_tlb_slot {\n-\tphys_addr_t orig_addr;\n+\tunion {\n+\t\tphys_addr_t orig_addr;\n+\t\tatomic_t nocopy_refcnt;\n+\t};\n \tsize_t alloc_size;\n \tunsigned short list;\n \tunsigned short pad_slots;\n+\tunsigned int flags;\n };\n \n static bool swiotlb_force_bounce;\n static bool swiotlb_force_disable;\n \n+/* enable nocopy rx swiotlb and set the percentage of buffers allowed for it. */\n+unsigned int nocopy_rx_percent;\n+module_param(nocopy_rx_percent, uint, 0644);\n+MODULE_PARM_DESC(nocopy_rx_percent, \"percentage of swiotlb buffer allowed for nocopy rx\");\n+\n #ifdef CONFIG_SWIOTLB_DYNAMIC\n \n static void swiotlb_dyn_alloc(struct work_struct *work);\n@@ -176,7 +229,7 @@ static void swiotlb_adjust_nareas(unsigned int nareas)\n static unsigned int limit_nareas(unsigned int nareas, unsigned long nslots)\n {\n \tif (nslots \u003c nareas * IO_TLB_SEGSIZE)\n-\t\treturn nslots / IO_TLB_SEGSIZE;\n+\t\treturn rounddown_pow_of_two(nslots / IO_TLB_SEGSIZE);\n \treturn nareas;\n }\n \n@@ -269,7 +322,16 @@ static void swiotlb_init_io_tlb_pool(struct io_tlb_pool *mem, phys_addr_t start,\n \t\tunsigned long nslabs, bool late_alloc, unsigned int nareas)\n {\n \tvoid *vaddr = phys_to_virt(start);\n-\tunsigned long bytes = nslabs \u003c\u003c IO_TLB_SHIFT, i;\n+\tunsigned long bytes, i;\n+\n+\t/*\n+\t * If we have multiple areas, ensure each area's size is a multiple of\n+\t * IO_TLB_SEGSIZE slots by aligning the total pool size down.\n+\t */\n+\tif (nareas \u003e 1)\n+\t\tnslabs = ALIGN_DOWN(nslabs, nareas * IO_TLB_SEGSIZE);\n+\n+\tbytes = nslabs \u003c\u003c IO_TLB_SHIFT;\n \n \tmem-\u003enslabs = nslabs;\n \tmem-\u003estart = start;\n@@ -290,6 +352,7 @@ static void swiotlb_init_io_tlb_pool(struct io_tlb_pool *mem, phys_addr_t start,\n \t\tmem-\u003eslots[i].orig_addr = INVALID_PHYS_ADDR;\n \t\tmem-\u003eslots[i].alloc_size = 0;\n \t\tmem-\u003eslots[i].pad_slots = 0;\n+\t\tmem-\u003eslots[i].flags = 0;\n \t}\n \n \tmemset(vaddr, 0, bytes);\n@@ -859,12 +922,17 @@ static void swiotlb_bounce(struct device *dev, phys_addr_t tlb_addr, size_t size\n \t\t\t enum dma_data_direction dir, struct io_tlb_pool *mem)\n {\n \tint index = (tlb_addr - mem-\u003estart) \u003e\u003e IO_TLB_SHIFT;\n-\tphys_addr_t orig_addr = mem-\u003eslots[index].orig_addr;\n \tsize_t alloc_size = mem-\u003eslots[index].alloc_size;\n-\tunsigned long pfn = PFN_DOWN(orig_addr);\n \tunsigned char *vaddr = mem-\u003evaddr + tlb_addr - mem-\u003estart;\n+\tphys_addr_t orig_addr;\n+\tunsigned long pfn;\n \tint tlb_offset;\n \n+\t/* Nocopy swiotlb buffers do not need bouncing. */\n+\tif (mem-\u003eslots[index].flags \u0026 SWIOTLB_SLOT_NOCOPY)\n+\t\treturn;\n+\n+\torig_addr = mem-\u003eslots[index].orig_addr;\n \tif (orig_addr == INVALID_PHYS_ADDR)\n \t\treturn;\n \n@@ -894,6 +962,7 @@ static void swiotlb_bounce(struct device *dev, phys_addr_t tlb_addr, size_t size\n \t\tsize = alloc_size;\n \t}\n \n+\tpfn = PFN_DOWN(orig_addr);\n \tif (PageHighMem(pfn_to_page(pfn))) {\n \t\tunsigned int offset = orig_addr \u0026 ~PAGE_MASK;\n \t\tstruct page *page;\n@@ -1042,7 +1111,8 @@ static int swiotlb_search_pool_area(struct device *dev, struct io_tlb_pool *pool\n \tunsigned long max_slots = get_max_slots(boundary_mask);\n \tunsigned int iotlb_align_mask = dma_get_min_align_mask(dev);\n \tunsigned int nslots = nr_slots(alloc_size), stride;\n-\tunsigned int offset = swiotlb_align_offset(dev, 0, orig_addr);\n+\tunsigned long max_usable = pool-\u003earea_nslabs;\n+\tunsigned int offset;\n \tunsigned int index, slots_checked, count = 0, i;\n \tunsigned long flags;\n \tunsigned int slot_base;\n@@ -1051,6 +1121,13 @@ static int swiotlb_search_pool_area(struct device *dev, struct io_tlb_pool *pool\n \tBUG_ON(!nslots);\n \tBUG_ON(area_index \u003e= pool-\u003enareas);\n \n+\tif (IS_SWIOTLB_NOCOPY(orig_addr)) {\n+\t\tmax_usable = (pool-\u003earea_nslabs * NOCOPY_ADDR_TO_PCT(orig_addr)) / 100;\n+\t\torig_addr = 0;\n+\t}\n+\n+\toffset = swiotlb_align_offset(dev, 0, orig_addr);\n+\n \t/*\n \t * Historically, swiotlb allocations \u003e= PAGE_SIZE were guaranteed to be\n \t * page-aligned in the absence of any other alignment requirements.\n@@ -1077,7 +1154,7 @@ static int swiotlb_search_pool_area(struct device *dev, struct io_tlb_pool *pool\n \tstride = get_max_slots(max(alloc_align_mask, iotlb_align_mask));\n \n \tspin_lock_irqsave(\u0026area-\u003elock, flags);\n-\tif (unlikely(nslots \u003e pool-\u003earea_nslabs - area-\u003eused))\n+\tif (unlikely(area-\u003eused + nslots \u003e max_usable))\n \t\tgoto not_found;\n \n \tslot_base = area_index * pool-\u003earea_nslabs;\n@@ -1154,6 +1231,9 @@ static int swiotlb_search_pool_area(struct device *dev, struct io_tlb_pool *pool\n * Search one memory area in all pools for a sequence of slots that match the\n * allocation constraints.\n *\n+ * If IS_SWIOTLB_NOCOPY(orig_addr) is true, the search is restricted to only the\n+ * default pool, which is what swiotlb_alloc_pages() is allowed to use.\n+ *\n * Return: Index of the first allocated slot, or -1 on error.\n */\n static int swiotlb_search_area(struct device *dev, int start_cpu,\n@@ -1167,6 +1247,9 @@ static int swiotlb_search_area(struct device *dev, int start_cpu,\n \n \trcu_read_lock();\n \tlist_for_each_entry_rcu(pool, \u0026mem-\u003epools, node) {\n+\t\t/* Only search the default pool (first in mem-\u003epools) for nocopy allocations. */\n+\t\tif (IS_SWIOTLB_NOCOPY(orig_addr) \u0026\u0026 pool != \u0026mem-\u003edefpool)\n+\t\t\tbreak;\n \t\tif (cpu_offset \u003e= pool-\u003enareas)\n \t\t\tcontinue;\n \t\tarea_index = (start_cpu + cpu_offset) \u0026 (pool-\u003enareas - 1);\n@@ -1219,6 +1302,13 @@ static int swiotlb_find_slots(struct device *dev, phys_addr_t orig_addr,\n \t\t\tgoto found;\n \t}\n \n+\t/*\n+\t * Passing a nocopy orig_addr restricts the search to only the\n+\t * default pool, so do not attempt dynamic pool expansion.\n+\t */\n+\tif (IS_SWIOTLB_NOCOPY(orig_addr))\n+\t\treturn -1;\n+\n \tif (!mem-\u003ecan_grow)\n \t\treturn -1;\n \n@@ -1458,11 +1548,16 @@ phys_addr_t swiotlb_tbl_map_single(struct device *dev, phys_addr_t orig_addr,\n \treturn tlb_addr;\n }\n \n+/*\n+ * called with dev == NULL from swiotlb_dealloc_pages(), in this case force offset\n+ * and align_mask to 0, pad_slots is also 0, and assume the pages come from the\n+ * default system pool.\n+ */\n static void swiotlb_release_slots(struct device *dev, phys_addr_t tlb_addr,\n \t\t\t\t struct io_tlb_pool *mem)\n {\n \tunsigned long flags;\n-\tunsigned int offset = swiotlb_align_offset(dev, 0, tlb_addr);\n+\tunsigned int offset = dev ? swiotlb_align_offset(dev, 0, tlb_addr) : 0;\n \tint index, nslots, aindex;\n \tstruct io_tlb_area *area;\n \tint count, i;\n@@ -1496,6 +1591,7 @@ static void swiotlb_release_slots(struct device *dev, phys_addr_t tlb_addr,\n \t\tmem-\u003eslots[i].orig_addr = INVALID_PHYS_ADDR;\n \t\tmem-\u003eslots[i].alloc_size = 0;\n \t\tmem-\u003eslots[i].pad_slots = 0;\n+\t\tmem-\u003eslots[i].flags = 0;\n \t}\n \n \t/*\n@@ -1509,7 +1605,7 @@ static void swiotlb_release_slots(struct device *dev, phys_addr_t tlb_addr,\n \tarea-\u003eused -= nslots;\n \tspin_unlock_irqrestore(\u0026area-\u003elock, flags);\n \n-\tdec_used(dev-\u003edma_io_tlb_mem, nslots);\n+\tdec_used(dev ? dev-\u003edma_io_tlb_mem : \u0026io_tlb_default_mem, nslots);\n }\n \n #ifdef CONFIG_SWIOTLB_DYNAMIC\n@@ -1554,6 +1650,13 @@ void __swiotlb_tbl_unmap_single(struct device *dev, phys_addr_t tlb_addr,\n \t\tsize_t mapping_size, enum dma_data_direction dir,\n \t\tunsigned long attrs, struct io_tlb_pool *pool)\n {\n+\tint index = (tlb_addr - pool-\u003estart) \u003e\u003e IO_TLB_SHIFT;\n+\n+\tif (pool-\u003eslots[index].flags \u0026 SWIOTLB_SLOT_NOCOPY) {\n+\t\tswiotlb_nocopy_dec_ref(pool, tlb_addr);\n+\t\treturn;\n+\t}\n+\n \t/*\n \t * First, sync the memory before unmapping the entry\n \t */\n@@ -1597,6 +1700,8 @@ dma_addr_t swiotlb_map(struct device *dev, phys_addr_t paddr, size_t size,\n \tphys_addr_t swiotlb_addr;\n \tdma_addr_t dma_addr;\n \n+\tdma_learn_bounce_device(dev);\n+\n \ttrace_swiotlb_bounced(dev, phys_to_dma(dev, paddr), size);\n \n \tswiotlb_addr = swiotlb_tbl_map_single(dev, paddr, size, 0, dir, attrs);\n@@ -1813,7 +1918,10 @@ static int rmem_swiotlb_device_init(struct reserved_mem *rmem,\n \t\t\t\t struct device *dev)\n {\n \tstruct io_tlb_mem *mem = rmem-\u003epriv;\n-\tunsigned long nslabs = rmem-\u003esize \u003e\u003e IO_TLB_SHIFT;\n+\tunsigned long nslabs = round_down(rmem-\u003esize \u003e\u003e IO_TLB_SHIFT, IO_TLB_SEGSIZE);\n+\n+\tif (!nslabs)\n+\t\treturn -EINVAL;\n \n \t/* Set Per-device io tlb area to one */\n \tunsigned int nareas = 1;\n@@ -1899,3 +2007,171 @@ static const struct reserved_mem_ops rmem_swiotlb_ops = {\n \n RESERVEDMEM_OF_DECLARE(dma, \"restricted-dma-pool\", \u0026rmem_swiotlb_ops);\n #endif /* CONFIG_DMA_RESTRICTED_POOL */\n+\n+static inline int swiotlb_nocopy_head_index(struct io_tlb_pool *pool, phys_addr_t phys)\n+{\n+\treturn (page_to_phys(compound_head(phys_to_page(phys))) - pool-\u003estart) \u003e\u003e IO_TLB_SHIFT;\n+}\n+\n+/**\n+ * swiotlb_dealloc_pages() - Actually release Nocopy slots and page metadata\n+ * @pool:\tSWIOTLB pool containing the buffer.\n+ * @parent:\tSlot index of the buffer head.\n+ */\n+static void swiotlb_dealloc_pages(struct io_tlb_pool *pool, unsigned int parent)\n+{\n+\tunsigned int order = get_order(pool-\u003eslots[parent].alloc_size);\n+\tphys_addr_t paddr = pool-\u003estart + (parent \u003c\u003c IO_TLB_SHIFT);\n+\tstruct page *head = phys_to_page(paddr);\n+\n+\tswiotlb_destroy_compound_page(head, order);\n+\tswiotlb_release_slots(NULL, paddr, pool);\n+}\n+\n+struct page *swiotlb_alloc_pages(struct device *dev, unsigned int order,\n+\t\t\t\t gfp_t gfp, unsigned int percent)\n+{\n+\tstruct io_tlb_pool *pool;\n+\tstruct page *page;\n+\tint index, nslots, i;\n+\n+\tif (WARN_ON_ONCE(!dev || !dev-\u003edma_io_tlb_mem))\n+\t\treturn NULL;\n+\n+\tif (dev-\u003edma_io_tlb_mem != \u0026io_tlb_default_mem)\n+\t\treturn NULL;\n+\n+\tindex = swiotlb_find_slots(dev, NOCOPY_PCT_TO_ADDR(percent),\n+\t\t\t\t PAGE_SIZE \u003c\u003c order, (PAGE_SIZE \u003c\u003c order) - 1,\n+\t\t\t\t \u0026pool);\n+\tif (index \u003c 0)\n+\t\treturn NULL;\n+\n+\tnslots = (PAGE_SIZE \u003c\u003c order) \u003e\u003e IO_TLB_SHIFT;\n+\tpage = phys_to_page(pool-\u003estart + (index \u003c\u003c IO_TLB_SHIFT));\n+\tswiotlb_prep_compound_page(page, order);\n+\tfor (i = 0; i \u003c nslots; i++)\n+\t\tpool-\u003eslots[index + i].flags |= SWIOTLB_SLOT_NOCOPY;\n+\tatomic_set(\u0026pool-\u003eslots[index].nocopy_refcnt, 1);\n+\treturn page;\n+}\n+EXPORT_SYMBOL(swiotlb_alloc_pages);\n+\n+bool swiotlb_free_pages(struct page *page, unsigned int order)\n+{\n+\tstruct io_tlb_mem *mem = \u0026io_tlb_default_mem;\n+\tstruct io_tlb_pool *pool = \u0026mem-\u003edefpool;\n+\tstruct page *head = compound_head(page);\n+\tunsigned int parent;\n+\tphys_addr_t paddr;\n+\n+\tpaddr = page_to_phys(head);\n+\tif (paddr \u003c pool-\u003estart || paddr \u003e= pool-\u003eend)\n+\t\treturn false;\n+\n+\tparent = swiotlb_nocopy_head_index(pool, paddr);\n+\tif (!(pool-\u003eslots[parent].flags \u0026 SWIOTLB_SLOT_NOCOPY))\n+\t\treturn false;\n+\n+\tif (atomic_dec_and_test(\u0026pool-\u003eslots[parent].nocopy_refcnt))\n+\t\tswiotlb_dealloc_pages(pool, parent);\n+\n+\treturn true;\n+}\n+EXPORT_SYMBOL(swiotlb_free_pages);\n+\n+void swiotlb_nocopy_inc_ref(struct io_tlb_pool *pool, phys_addr_t phys)\n+{\n+\tint head_idx = swiotlb_nocopy_head_index(pool, phys);\n+\n+\tatomic_inc(\u0026pool-\u003eslots[head_idx].nocopy_refcnt);\n+}\n+EXPORT_SYMBOL(swiotlb_nocopy_inc_ref);\n+\n+void swiotlb_nocopy_dec_ref(struct io_tlb_pool *pool, phys_addr_t phys)\n+{\n+\tint head_idx = swiotlb_nocopy_head_index(pool, phys);\n+\n+\tif (atomic_dec_and_test(\u0026pool-\u003eslots[head_idx].nocopy_refcnt))\n+\t\tswiotlb_dealloc_pages(pool, head_idx);\n+}\n+EXPORT_SYMBOL(swiotlb_nocopy_dec_ref);\n+\n+bool swiotlb_pool_is_nocopy(struct io_tlb_pool *pool, phys_addr_t paddr)\n+{\n+\tint index = (paddr - pool-\u003estart) \u003e\u003e IO_TLB_SHIFT;\n+\n+\treturn pool-\u003eslots[index].flags \u0026 SWIOTLB_SLOT_NOCOPY;\n+}\n+EXPORT_SYMBOL_GPL(swiotlb_pool_is_nocopy);\n+\n+/*\n+ * Dropping the reference to sk_swiotlb.dev must be done in two steps:\n+ *\n+ * 1. Readers inspect the pointer inside RCU critical sections without\n+ * acquiring a reference. Use call_rcu() to wait for an RCU grace period\n+ * to elapse so lockless in-flight readers finish accessing the device.\n+ *\n+ * 2. The RCU callback executes in atomic softirq context, but put_device()\n+ * can block when releasing a device. Use schedule_work() to transition\n+ * to sleepable process context where calling put_device() is safe.\n+ */\n+struct swiotlb_deferred_put {\n+\tstruct rcu_head rcu;\n+\tstruct work_struct work;\n+\tstruct device *dev;\n+};\n+\n+static void swiotlb_deferred_put_work(struct work_struct *work)\n+{\n+\tstruct swiotlb_deferred_put *dp = container_of(work, struct swiotlb_deferred_put, work);\n+\n+\t/* Stage 2: Safely call put_device (can sleep) in process context */\n+\tput_device(dp-\u003edev);\n+\tkfree(dp);\n+}\n+\n+static void swiotlb_deferred_put_rcu(struct rcu_head *rcu)\n+{\n+\tstruct swiotlb_deferred_put *dp = container_of(rcu, struct swiotlb_deferred_put, rcu);\n+\n+\t/* RCU grace period has passed. Queue the work to do the actual put */\n+\tschedule_work(\u0026dp-\u003ework);\n+}\n+\n+/**\n+ * swiotlb_safe_put_device() - Safely release device reference from atomic/interrupt context\n+ * @dev: The device structure to release.\n+ *\n+ * Enqueues a deferred put_device() call on a workqueue using GFP_ATOMIC.\n+ * If memory allocation fails, the reference is leaked to avoid an immediate crash.\n+ */\n+void swiotlb_safe_put_device(struct device *dev)\n+{\n+\tstruct swiotlb_deferred_put *dp;\n+\n+\tif (!dev)\n+\t\treturn;\n+\n+\t/* Lockless fast-path: if we are not the last reference, decrement is safe */\n+\tif (refcount_dec_not_one(\u0026dev-\u003ekobj.kref.refcount))\n+\t\treturn;\n+\n+\t/*\n+\t * On the last reference we must defer the final put_device() to task\n+\t * context because it will trigger device_release() which can sleep.\n+\t */\n+\tdp = kmalloc_obj(*dp, GFP_ATOMIC);\n+\tif (dp) {\n+\t\tINIT_WORK(\u0026dp-\u003ework, swiotlb_deferred_put_work);\n+\t\tdp-\u003edev = dev;\n+\t\t/* Stage 1: Wait for RCU readers to finish */\n+\t\tcall_rcu(\u0026dp-\u003ercu, swiotlb_deferred_put_rcu);\n+\t} else {\n+\t\tpr_warn_ratelimited(\"swiotlb: failed to allocate deferred put, leaking device ref\\n\");\n+\t}\n+}\n+EXPORT_SYMBOL_GPL(swiotlb_safe_put_device);\n+\n+atomic_t global_device_epoch = ATOMIC_INIT(1);\n+EXPORT_SYMBOL(global_device_epoch);\ndiff --git a/mm/page_alloc.c b/mm/page_alloc.c\nindex 083cbcb5bddec..32d5d630f9840 100644\n--- a/mm/page_alloc.c\n+++ b/mm/page_alloc.c\n@@ -16,6 +16,7 @@\n \n #include \u003clinux/stddef.h\u003e\n #include \u003clinux/mm.h\u003e\n+#include \u003clinux/swiotlb.h\u003e\n #include \u003clinux/highmem.h\u003e\n #include \u003clinux/interrupt.h\u003e\n #include \u003clinux/jiffies.h\u003e\n@@ -711,6 +712,56 @@ void prep_compound_page(struct page *page, unsigned int order)\n \tprep_compound_head(page, order);\n }\n \n+#ifdef CONFIG_SWIOTLB\n+/*\n+ * Prepare a SWIOTLB page (potentially compound).\n+ *\n+ * We explicitly initialize the head page refcount to 1 because recycled\n+ * SWIOTLB pages might have a refcount of 0.\n+ *\n+ * If order \u003e 0 (compound page), we must explicitly set all tail page\n+ * refcounts to 0. This is because SWIOTLB pages might have a boot-default\n+ * refcount of 1, but the core memory management subsystem expects tail pages\n+ * of a compound page to have a refcount of 0.\n+ */\n+void swiotlb_prep_compound_page(struct page *page, unsigned int order)\n+{\n+\tinit_page_count(page);\n+\tif (order \u003e 0) {\n+\t\tfor (int i = 1; i \u003c (1 \u003c\u003c order); i++)\n+\t\t\tset_page_count(page + i, 0);\n+\t\tprep_compound_page(page, order);\n+\t}\n+}\n+\n+/*\n+ * Destroy a SWIOTLB compound page and restore page refcounts.\n+ *\n+ * When pages are returned to the SWIOTLB pool, we restore the refcount of\n+ * all constituent pages (head and tails) to 1. This resets them to their\n+ * clean boot-default state, ensuring they are ready for reuse either as\n+ * individual order-0 pages or as part of a new compound allocation.\n+ */\n+void swiotlb_destroy_compound_page(struct page *page, unsigned int order)\n+{\n+\tif (order \u003e 0) {\n+\t\tstruct folio *folio = (struct folio *)page;\n+\n+\t\t__ClearPageHead(page);\n+\t\tpage[1].flags.f \u0026= ~PAGE_FLAGS_SECOND;\n+#ifdef NR_PAGES_IN_LARGE_FOLIO\n+\t\tfolio-\u003e_nr_pages = 0;\n+#endif\n+\t\tfor (int i = 1; i \u003c (1 \u003c\u003c order); i++) {\n+\t\t\tpage[i].mapping = NULL;\n+\t\t\tclear_compound_head(\u0026page[i]);\n+\t\t\tset_page_count(page + i, 1);\n+\t\t}\n+\t}\n+\tset_page_count(page, 1);\n+}\n+#endif /* CONFIG_SWIOTLB */\n+\n static inline void set_buddy_order(struct page *page, unsigned int order)\n {\n \tset_page_private(page, order);\n@@ -2951,9 +3002,14 @@ static void __free_frozen_pages(struct page *page, unsigned int order,\n {\n \tstruct per_cpu_pages *pcp;\n \tstruct zone *zone;\n-\tunsigned long pfn = page_to_pfn(page);\n+\tunsigned long pfn;\n \tint migratetype;\n \n+\tif (unlikely(swiotlb_free_pages(page, order)))\n+\t\treturn;\n+\n+\tpfn = page_to_pfn(page);\n+\n \tif (!pcp_allowed_order(order)) {\n \t\t__free_pages_ok(page, order, fpi_flags);\n \t\treturn;\n@@ -3019,6 +3075,9 @@ void free_unref_folios(struct folio_batch *folios)\n \t\tunsigned long pfn = folio_pfn(folio);\n \t\tunsigned int order = folio_order(folio);\n \n+\t\tif (unlikely(swiotlb_free_pages(\u0026folio-\u003epage, order)))\n+\t\t\tcontinue;\n+\n \t\tif (!__free_pages_prepare(\u0026folio-\u003epage, order, FPI_NONE))\n \t\t\tcontinue;\n \t\t/*\ndiff --git a/net/core/page_pool.c b/net/core/page_pool.c\nindex 50ee550fef73a..fe8839a7c70a8 100644\n--- a/net/core/page_pool.c\n+++ b/net/core/page_pool.c\n@@ -19,6 +19,7 @@\n \n #include \u003clinux/dma-direction.h\u003e\n #include \u003clinux/dma-mapping.h\u003e\n+#include \u003clinux/swiotlb.h\u003e\n #include \u003clinux/page-flags.h\u003e\n #include \u003clinux/mm.h\u003e /* for put_page() */\n #include \u003clinux/poison.h\u003e\n@@ -578,10 +579,16 @@ static bool page_pool_dma_map(struct page_pool *pool, netmem_ref netmem, gfp_t g\n static struct page *__page_pool_alloc_page_order(struct page_pool *pool,\n \t\t\t\t\t\t gfp_t gfp)\n {\n+\tunsigned int pct = READ_ONCE(nocopy_rx_percent);\n \tstruct page *page;\n \n \tgfp |= __GFP_COMP;\n-\tpage = alloc_pages_node(pool-\u003ep.nid, gfp, pool-\u003ep.order);\n+\tpage = NULL;\n+\tif (pct \u0026\u0026 is_swiotlb_active(pool-\u003ep.dev))\n+\t\tpage = swiotlb_alloc_pages(pool-\u003ep.dev, pool-\u003ep.order, gfp,\n+\t\t\t\t\t pct);\n+\tif (!page)\n+\t\tpage = alloc_pages_node(pool-\u003ep.nid, gfp, pool-\u003ep.order);\n \tif (unlikely(!page))\n \t\treturn NULL;\n \n@@ -616,8 +623,9 @@ static noinline netmem_ref __page_pool_alloc_netmems_slow(struct page_pool *pool\n \tif ((gfp \u0026 GFP_ATOMIC) == GFP_ATOMIC)\n \t\tgfp |= __GFP_NOWARN;\n \n-\t/* Don't support bulk alloc for high-order pages */\n-\tif (unlikely(pp_order))\n+\t/* Don't support bulk alloc for high-order pages or nocopy SWIOTLB */\n+\tif (unlikely(pp_order || (READ_ONCE(nocopy_rx_percent) \u0026\u0026\n+\t\t\t\t is_swiotlb_active(pool-\u003ep.dev))))\n \t\treturn page_to_netmem(__page_pool_alloc_page_order(pool, gfp));\n \n \t/* Unnecessary as alloc cache is empty, but guarantees zero count */\n@@ -835,6 +843,17 @@ __page_pool_put_page(struct page_pool *pool, netmem_ref netmem,\n {\n \tlockdep_assert_no_hardirq();\n \n+\t/*\n+\t * If runtime nocopy mode toggled, evict circulating buffers immediately\n+\t * back to their respective allocators rather than recycling them.\n+\t */\n+\tif (unlikely(!netmem_is_net_iov(netmem) \u0026\u0026\n+\t\t swiotlb_is_nocopy_addr(pool-\u003ep.dev, page_to_phys(netmem_to_page(netmem))) !=\n+\t\t (READ_ONCE(nocopy_rx_percent) \u003e 0))) {\n+\t\tpage_pool_return_netmem(pool, netmem);\n+\t\treturn 0;\n+\t}\n+\n \t/* This allocator is optimized for the XDP mode that uses\n \t * one-frame-per-page, but have fallbacks that act like the\n \t * regular page allocator APIs.\ndiff --git a/net/core/sock.c b/net/core/sock.c\nindex 1ad41904db25b..ef40d1ff1de9f 100644\n--- a/net/core/sock.c\n+++ b/net/core/sock.c\n@@ -103,6 +103,8 @@\n #include \u003clinux/sockios.h\u003e\n #include \u003clinux/net.h\u003e\n #include \u003clinux/mm.h\u003e\n+#include \u003clinux/swiotlb.h\u003e\n+#include \u003clinux/device.h\u003e\n #include \u003clinux/slab.h\u003e\n #include \u003clinux/interrupt.h\u003e\n #include \u003clinux/poll.h\u003e\n@@ -152,6 +154,87 @@\n \n #include \"dev.h\"\n \n+#if defined(CONFIG_SWIOTLB) \u0026\u0026 !defined(CONFIG_PREEMPT_RT)\n+\n+DEFINE_PER_CPU(struct sock *, current_tx_socket);\n+EXPORT_PER_CPU_SYMBOL(current_tx_socket);\n+\n+void sk_record_bounce_device(struct sock *sk, struct device *dev)\n+{\n+\tstruct device *old_dev;\n+\n+\tif (in_hardirq() || !sk_fullsock(sk) || sock_flag(sk, SOCK_ZEROCOPY))\n+\t\treturn;\n+\n+\told_dev = rcu_dereference_protected(sk-\u003esk_swiotlb.dev, 1);\n+\n+\tif (dev != old_dev) {\n+\t\t/* Rate-limit updates to once per second to prevent bonding thrashing */\n+\t\tif (old_dev \u0026\u0026 time_before(jiffies, sk-\u003esk_swiotlb.jiffies + HZ))\n+\t\t\treturn;\n+\n+\t\tget_device(dev);\n+\n+\t\t/* Atomically swap in the new device and get the actual old one */\n+\t\told_dev = (struct device *)xchg((struct device __force **)\u0026sk-\u003esk_swiotlb.dev,\n+\t\t\t\t\t\t(struct device __force *)dev);\n+\n+\t\tWRITE_ONCE(sk-\u003esk_swiotlb.epoch, swiotlb_dev_epoch());\n+\t\tsk-\u003esk_swiotlb.jiffies = jiffies;\n+\n+\t\t/* Only drop the reference to the device we actually replaced */\n+\t\tif (old_dev)\n+\t\t\tswiotlb_safe_put_device(old_dev);\n+\t}\n+}\n+EXPORT_SYMBOL(sk_record_bounce_device);\n+\n+/*\n+ * Wrap alloc_pages in __skb_page_frag_refill(). If the socket's dma_device requires\n+ * SWIOTLB bounce buffering, divert allocation to the SWIOTLB slot allocator.\n+ * This ensures the packet payload is written directly to a bounce buffer from the start,\n+ * enabling nocopy during driver DMA mapping.\n+ */\n+static inline struct page *alloc_any_pg(gfp_t gfp, unsigned int order, struct sock *sk)\n+{\n+\tunsigned int pct = READ_ONCE(nocopy_tx_percent);\n+\n+\tif (sk \u0026\u0026 pct \u0026\u0026 !sock_flag(sk, SOCK_ZEROCOPY)) {\n+\t\tstruct page *page = NULL;\n+\t\tbool release_dev = false;\n+\t\tstruct device *dev;\n+\n+\t\trcu_read_lock();\n+\t\tdev = rcu_dereference(sk-\u003esk_swiotlb.dev);\n+\t\tif (dev) {\n+\t\t\t/*\n+\t\t\t * The epoch check is just for cache invalidation, UAF is\n+\t\t\t * protected by the reference held in the sk.\n+\t\t\t */\n+\t\t\tif (swiotlb_dev_epoch() != READ_ONCE(sk-\u003esk_swiotlb.epoch)) {\n+\t\t\t\tstruct device __force **pdev =\n+\t\t\t\t\t(struct device __force **)\u0026sk-\u003esk_swiotlb.dev;\n+\n+\t\t\t\trelease_dev = (cmpxchg(pdev, (struct device __force *)dev,\n+\t\t\t\t\t\t NULL) == dev);\n+\t\t\t} else {\n+\t\t\t\tpage = swiotlb_alloc_pages(dev, order, gfp, pct);\n+\t\t\t}\n+\t\t}\n+\t\trcu_read_unlock();\n+\t\tif (release_dev)\n+\t\t\tswiotlb_safe_put_device(dev);\n+\t\tif (page)\n+\t\t\treturn page;\n+\t}\n+\treturn alloc_pages(gfp, order);\n+}\n+#else\n+static inline struct page *alloc_any_pg(gfp_t gfp, unsigned int order, struct sock *sk)\n+{\n+\treturn alloc_pages(gfp, order);\n+}\n+#endif\n static DEFINE_MUTEX(proto_list_mutex);\n static LIST_HEAD(proto_list);\n \n@@ -2387,6 +2470,7 @@ static void __sk_destruct(struct rcu_head *head)\n \t\t__netns_tracker_free(net, \u0026sk-\u003ens_tracker, false);\n \t\tnet_passive_dec(net);\n \t}\n+\tsk_release_bounce_device(sk);\n \tsk_prot_free(sk-\u003esk_prot_creator, sk);\n }\n \n@@ -2489,6 +2573,7 @@ struct sock *sk_clone(const struct sock *sk, const gfp_t priority,\n \t\tgoto out;\n \n \tsock_copy(newsk, sk);\n+\tsk_clear_bounce_device(newsk);\n \n \tnewsk-\u003esk_prot_creator = prot;\n #ifdef CONFIG_BPF_SYSCALL\n@@ -3174,7 +3259,7 @@ DEFINE_STATIC_KEY_FALSE(net_high_order_alloc_disable_key);\n * no guarantee that allocations succeed. Therefore, @sz MUST be\n * less or equal than PAGE_SIZE.\n */\n-bool skb_page_frag_refill(unsigned int sz, struct page_frag *pfrag, gfp_t gfp)\n+bool __skb_page_frag_refill(unsigned int sz, struct page_frag *pfrag, gfp_t gfp, struct sock *sk)\n {\n \tif (pfrag-\u003epage) {\n \t\tif (page_ref_count(pfrag-\u003epage) == 1) {\n@@ -3190,27 +3275,27 @@ bool skb_page_frag_refill(unsigned int sz, struct page_frag *pfrag, gfp_t gfp)\n \tif (SKB_FRAG_PAGE_ORDER \u0026\u0026\n \t !static_branch_unlikely(\u0026net_high_order_alloc_disable_key)) {\n \t\t/* Avoid direct reclaim but allow kswapd to wake */\n-\t\tpfrag-\u003epage = alloc_pages((gfp \u0026 ~__GFP_DIRECT_RECLAIM) |\n-\t\t\t\t\t __GFP_COMP | __GFP_NOWARN |\n-\t\t\t\t\t __GFP_NORETRY,\n-\t\t\t\t\t SKB_FRAG_PAGE_ORDER);\n+\t\tpfrag-\u003epage = alloc_any_pg((gfp \u0026 ~__GFP_DIRECT_RECLAIM) |\n+\t\t\t\t\t __GFP_COMP | __GFP_NOWARN |\n+\t\t\t\t\t __GFP_NORETRY,\n+\t\t\t\t\t SKB_FRAG_PAGE_ORDER, sk);\n \t\tif (likely(pfrag-\u003epage)) {\n \t\t\tpfrag-\u003esize = PAGE_SIZE \u003c\u003c SKB_FRAG_PAGE_ORDER;\n \t\t\treturn true;\n \t\t}\n \t}\n-\tpfrag-\u003epage = alloc_page(gfp);\n+\tpfrag-\u003epage = alloc_any_pg(gfp, 0, sk);\n \tif (likely(pfrag-\u003epage)) {\n \t\tpfrag-\u003esize = PAGE_SIZE;\n \t\treturn true;\n \t}\n \treturn false;\n }\n-EXPORT_SYMBOL(skb_page_frag_refill);\n+EXPORT_SYMBOL(__skb_page_frag_refill);\n \n bool sk_page_frag_refill(struct sock *sk, struct page_frag *pfrag)\n {\n-\tif (likely(skb_page_frag_refill(32U, pfrag, sk-\u003esk_allocation)))\n+\tif (likely(__skb_page_frag_refill(32U, pfrag, sk-\u003esk_allocation, sk)))\n \t\treturn true;\n \n \tif (!sk-\u003esk_bypass_prot_mem)\n"
} |
| 2/1 | 2026/08/24 18:19 | agent | patch-evaluator |
19mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"SWIOTLB"
],
"FocusSymbols": [
"swiotlb_alloc_pages",
"swiotlb_free_pages",
"swiotlb_nocopy_inc_ref",
"swiotlb_nocopy_dec_ref",
"swiotlb_pool_is_nocopy",
"swiotlb_safe_put_device",
"sk_record_bounce_device",
"__skb_page_frag_refill"
],
"Reasoning": "The patch implements a new 'nocopy' SWIOTLB feature for networking, allowing direct DMA mapping of network buffers without bounce buffering. It modifies core SWIOTLB allocation logic, DMA mapping paths, and network page allocation (skb_page_frag_refill, page_pool). These are significant functional changes to core kernel subsystems that are reachable and should be fuzzed.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs output array, and DO NOT add a '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: For your convenience, here is the diff of the changes:
commit 6fe30e0c486a30f009129aa8c785d15bf9848f47
Author: syz-cluster <triage@syzkaller.com>
Date: Mon Aug 24 18:19:01 2026 +0000
syz-cluster: applied patch under review
diff --git a/drivers/base/core.c b/drivers/base/core.c
index 4c0c373998a19..091062228740d 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -3925,6 +3925,7 @@ void device_del(struct device *dev)
unsigned int noio_flag;
device_lock(dev);
+ swiotlb_change_epoch();
kill_device(dev);
device_unlock(dev);
diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c
index 9a07eb39336eb..956d5e11b2896 100644
--- a/drivers/iommu/dma-iommu.c
+++ b/drivers/iommu/dma-iommu.c
@@ -1228,7 +1228,14 @@ dma_addr_t iommu_dma_map_phys(struct device *dev, phys_addr_t phys, size_t size,
* If both the physical buffer start address and size are page aligned,
* we don't need to use a bounce page.
*/
- if (dev_use_swiotlb(dev, size, dir) &&
+ bool is_nocopy = false;
+
+ if (swiotlb_is_nocopy_addr(dev, phys)) {
+ swiotlb_nocopy_inc_ref(&dev->dma_io_tlb_mem->defpool, phys);
+ is_nocopy = true;
+ }
+
+ if (!is_nocopy && dev_use_swiotlb(dev, size, dir) &&
iova_unaligned(iovad, phys, size)) {
if (attrs & (DMA_ATTR_MMIO | DMA_ATTR_REQUIRE_COHERENT))
return DMA_MAPPING_ERROR;
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 87cafc932e9e6..2457f4e464acf 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -5429,13 +5429,34 @@ static inline netdev_tx_t __netdev_start_xmit(const struct net_device_ops *ops,
return ops->ndo_start_xmit(skb, dev);
}
+struct sock;
+
+#if defined(CONFIG_SWIOTLB) && !defined(CONFIG_PREEMPT_RT)
+/* Per-CPU pointer to the socket currently performing transmission. Used
+ * to bridge the networking and DMA layers, allowing dma_map_page() to
+ * identify the socket originating the packet and apply SWIOTLB optimizations.
+ */
+DECLARE_PER_CPU(struct sock *, current_tx_socket);
+static inline struct sock *__save_current_tx_socket(struct sock *sk)
+{
+ struct sock *old_sk = this_cpu_read(current_tx_socket);
+
+ this_cpu_write(current_tx_socket, sk);
+ return old_sk;
+}
+#else
+static inline struct sock *__save_current_tx_socket(struct sock *sk) { return NULL; }
+#endif
+
static inline netdev_tx_t netdev_start_xmit(struct sk_buff *skb, struct net_device *dev,
struct netdev_queue *txq, bool more)
{
+ struct sock *old_sk = __save_current_tx_socket(skb->sk);
const struct net_device_ops *ops = dev->netdev_ops;
netdev_tx_t rc;
rc = __netdev_start_xmit(ops, skb, dev, more);
+ __save_current_tx_socket(old_sk);
if (rc == NETDEV_TX_OK)
txq_trans_update(dev, txq);
diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index add0d282dea6e..d8f7041edc400 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -3786,7 +3786,12 @@ static inline void skb_frag_page_copy(skb_frag_t *fragto,
fragto->netmem = fragfrom->netmem;
}
-bool skb_page_frag_refill(unsigned int sz, struct page_frag *pfrag, gfp_t prio);
+/* nocopy swiotlb uses an additional non-null struct sock pointer. */
+bool __skb_page_frag_refill(unsigned int sz, struct page_frag *pfrag, gfp_t prio, struct sock *sk);
+static inline bool skb_page_frag_refill(unsigned int sz, struct page_frag *pfrag, gfp_t prio)
+{
+ return __skb_page_frag_refill(sz, pfrag, prio, NULL);
+}
/**
* __skb_frag_dma_map - maps a paged fragment via the DMA API
diff --git a/include/linux/swiotlb.h b/include/linux/swiotlb.h
index 3dae0f592063e..f4597fd01c52d 100644
--- a/include/linux/swiotlb.h
+++ b/include/linux/swiotlb.h
@@ -169,6 +169,23 @@ static inline struct io_tlb_pool *swiotlb_find_pool(struct device *dev,
return NULL;
}
+bool swiotlb_pool_is_nocopy(struct io_tlb_pool *pool, phys_addr_t paddr);
+
+static inline bool swiotlb_addr_in_default_pool(struct device *dev,
+ phys_addr_t paddr)
+{
+ struct io_tlb_mem *mem = dev->dma_io_tlb_mem;
+
+ return mem && paddr >= mem->defpool.start && paddr < mem->defpool.end;
+}
+
+static inline bool swiotlb_is_nocopy_addr(struct device *dev, phys_addr_t paddr)
+{
+ if (!swiotlb_addr_in_default_pool(dev, paddr))
+ return false;
+ return swiotlb_pool_is_nocopy(&dev->dma_io_tlb_mem->defpool, paddr);
+}
+
static inline bool is_swiotlb_force_bounce(struct device *dev)
{
struct io_tlb_mem *mem = dev->dma_io_tlb_mem;
@@ -178,6 +195,49 @@ static inline bool is_swiotlb_force_bounce(struct device *dev)
void swiotlb_init(bool addressing_limited, unsigned int flags);
void __init swiotlb_exit(void);
+struct page *swiotlb_alloc_pages(struct device *dev, unsigned int order, gfp_t gfp,
+ unsigned int percent);
+bool swiotlb_free_pages(struct page *page, unsigned int order);
+void swiotlb_nocopy_inc_ref(struct io_tlb_pool *pool, phys_addr_t phys);
+void swiotlb_nocopy_dec_ref(struct io_tlb_pool *pool, phys_addr_t phys);
+void swiotlb_prep_compound_page(struct page *page, unsigned int order);
+void swiotlb_destroy_compound_page(struct page *page, unsigned int order);
+void swiotlb_safe_put_device(struct device *dev);
+
+extern unsigned int nocopy_tx_percent;
+extern unsigned int nocopy_rx_percent;
+
+/* Track epoch (number of delete operations) for leaf device info. */
+extern atomic_t global_device_epoch;
+
+static inline u32 swiotlb_dev_epoch(void)
+{
+ return atomic_read(&global_device_epoch);
+}
+
+static inline void swiotlb_change_epoch(void)
+{
+ atomic_inc(&global_device_epoch);
+}
+
+#if defined(CONFIG_NET) && !defined(CONFIG_PREEMPT_RT)
+/*
+ * Track the socket for the currently transmitted packet, so the dma mapping
+ * function can record there the leaf device if it needs bounce buffers.
+ */
+struct sock;
+DECLARE_PER_CPU(struct sock *, current_tx_socket);
+void sk_record_bounce_device(struct sock *sk, struct device *dev);
+static inline void dma_learn_bounce_device(struct device *dev)
+{
+ struct sock *sk = this_cpu_read(current_tx_socket);
+
+ if (sk)
+ sk_record_bounce_device(sk, dev);
+}
+#else
+static inline void dma_learn_bounce_device(struct device *dev) {}
+#endif
void swiotlb_dev_init(struct device *dev);
size_t swiotlb_max_mapping_size(struct device *dev);
bool is_swiotlb_allocated(void);
@@ -234,6 +294,9 @@ static inline phys_addr_t default_swiotlb_limit(void)
{
return 0;
}
+static inline void swiotlb_safe_put_device(struct device *dev)
+{
+}
#endif /* CONFIG_SWIOTLB */
phys_addr_t swiotlb_tbl_map_single(struct device *hwdev, phys_addr_t phys,
diff --git a/include/net/sock.h b/include/net/sock.h
index 51185222aac29..39b5e81c7cc55 100644
--- a/include/net/sock.h
+++ b/include/net/sock.h
@@ -47,6 +47,7 @@
#include <linux/skbuff.h> /* struct sk_buff */
#include <linux/mm.h>
#include <linux/security.h>
+#include <linux/swiotlb.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/page_counter.h>
@@ -70,6 +71,14 @@
#include <net/l3mdev.h>
#include <uapi/linux/socket.h>
+#if defined(CONFIG_SWIOTLB) && !defined(CONFIG_PREEMPT_RT)
+struct sk_swiotlb_info {
+ struct device __rcu *dev;
+ u32 epoch;
+ unsigned long jiffies;
+};
+#endif
+
/*
* This structure really needs to be cleaned up.
* Most of it is for TCP, and not used by any of
@@ -602,8 +611,45 @@ struct sock {
#if IS_ENABLED(CONFIG_PROVE_LOCKING) && IS_ENABLED(CONFIG_MODULES)
struct module *sk_owner;
#endif
+#if defined(CONFIG_SWIOTLB) && !defined(CONFIG_PREEMPT_RT)
+ struct sk_swiotlb_info sk_swiotlb;
+#endif
};
+#if defined(CONFIG_SWIOTLB) && !defined(CONFIG_PREEMPT_RT)
+/*
+ * Clear bounce device on newly initialized or cloned sockets.
+ * Note: During socket cloning, sock_copy() performs a raw bitwise copy of
+ * the parent socket without incrementing the device refcount via get_device().
+ * Therefore, we must zero sk_swiotlb.dev directly here without putting a
+ * reference. References are acquired solely by sk_record_bounce_device() and
+ * released in sk_release_bounce_device().
+ */
+static inline void sk_clear_bounce_device(struct sock *sk)
+{
+ rcu_assign_pointer(sk->sk_swiotlb.dev, NULL);
+}
+
+/*
+ * Release any device reference acquired via sk_record_bounce_device() during
+ * socket transmission and clear the device pointer. Called during socket
+ * destruction (__sk_destruct).
+ */
+static inline void sk_release_bounce_device(struct sock *sk)
+{
+ struct device *dev;
+
+ dev = rcu_dereference_raw(sk->sk_swiotlb.dev);
+ if (dev) {
+ swiotlb_safe_put_device(dev);
+ rcu_assign_pointer(sk->sk_swiotlb.dev, NULL);
+ }
+}
+#else
+static inline void sk_clear_bounce_device(struct sock *sk) {}
+static inline void sk_release_bounce_device(struct sock *sk) {}
+#endif
+
struct sock_bh_locked {
struct sock *sock;
local_lock_t bh_lock;
diff --git a/kernel/dma/direct.h b/kernel/dma/direct.h
index 7140c208c1238..21c65acb13823 100644
--- a/kernel/dma/direct.h
+++ b/kernel/dma/direct.h
@@ -88,6 +88,17 @@ static inline dma_addr_t dma_direct_map_phys(struct device *dev,
{
dma_addr_t dma_addr;
+ if (swiotlb_is_nocopy_addr(dev, phys)) {
+ dma_addr_t unenc_addr = phys_to_dma_unencrypted(dev, phys);
+
+ if (likely(dma_capable(dev, unenc_addr, size, true))) {
+ swiotlb_nocopy_inc_ref(&dev->dma_io_tlb_mem->defpool, phys);
+ if (!dev_is_dma_coherent(dev) && !(attrs & DMA_ATTR_SKIP_CPU_SYNC))
+ arch_sync_dma_for_device(phys, size, dir);
+ return unenc_addr;
+ }
+ }
+
if (is_swiotlb_force_bounce(dev)) {
if (!(attrs & DMA_ATTR_CC_SHARED)) {
if (attrs & (DMA_ATTR_MMIO | DMA_ATTR_REQUIRE_COHERENT))
diff --git a/kernel/dma/swiotlb.c b/kernel/dma/swiotlb.c
index 1abd3e6146f45..91f175c34a34e 100644
--- a/kernel/dma/swiotlb.c
+++ b/kernel/dma/swiotlb.c
@@ -33,6 +33,7 @@
#include <linux/kmsan-checks.h>
#include <linux/iommu-helper.h>
#include <linux/init.h>
+#include <linux/log2.h>
#include <linux/memblock.h>
#include <linux/mm.h>
#include <linux/pfn.h>
@@ -62,25 +63,77 @@
*/
#define IO_TLB_MIN_SLABS ((1<<20) >> IO_TLB_SHIFT)
+/* enable nocopy tx swiotlb and set the percentage of buffers allowed for it. */
+unsigned int nocopy_tx_percent;
+module_param(nocopy_tx_percent, uint, 0644);
+MODULE_PARM_DESC(nocopy_tx_percent, "percentage of swiotlb buffer allowed for nocopy tx");
+
/**
* struct io_tlb_slot - IO TLB slot descriptor
* @orig_addr: The original address corresponding to a mapped entry.
+ * @nocopy_refcnt: Lockless atomic refcount for Nocopy buffers.
* @alloc_size: Size of the allocated buffer.
* @list: The free list describing the number of free entries available
* from each index.
* @pad_slots: Number of preceding padding slots. Valid only in the first
* allocated non-padding slot.
+ * @flags: Slot attributes (e.g. SWIOTLB_SLOT_NOCOPY for Nocopy buffers).
+ *
+ * The slot descriptor has states identified by @list and @flags (SWIOTLB_SLOT_NOCOPY):
+ *
+ * 1. FREE (list > 0):
+ * Linear sweep free slot.
+ *
+ * 2. USED (list == 0, SWIOTLB_SLOT_NOCOPY flag is NOT set in @flags):
+ * Allocated SWIOTLB bounce buffer.
+ * Fields used: @list, @pad_slots, @orig_addr, @alloc_size.
+ *
+ * 3. USED_NOCOPY (list == 0, SWIOTLB_SLOT_NOCOPY flag is set in @flags):
+ * Allocated Nocopy SWIOTLB buffer.
+ * Fields used: @list, @nocopy_refcnt, @alloc_size.
*/
+#define SWIOTLB_SLOT_NOCOPY BIT(0)
+
+/*
+ * SWIOTLB nocopy allocations (swiotlb_alloc_pages()) do not have an original
+ * physical address to bounce, but need to pass a caller-specified pool usage
+ * limit (percentage) down to the area search logic.
+ *
+ * To avoid adding a parameter to swiotlb_find_slots(), swiotlb_search_area(),
+ * and swiotlb_search_pool_area(), the desired percentage (0..90) is encoded
+ * into the orig_addr parameter in the reserved high address range starting at
+ * INVALID_PHYS_ADDR (~0ULL).
+ *
+ * - NOCOPY_PCT_TO_ADDR(pct): Encodes a percentage into an orig_addr.
+ * - IS_SWIOTLB_NOCOPY(addr): Identifies a nocopy allocation request and
+ * restricts slot search to the static default pool.
+ * - NOCOPY_ADDR_TO_PCT(addr): Extracts the percentage to cap max_usable
+ * slots in swiotlb_search_pool_area().
+ */
+#define NOCOPY_PCT_MAX (90u)
+#define NOCOPY_PCT_TO_ADDR(pct) (INVALID_PHYS_ADDR - min(pct, NOCOPY_PCT_MAX))
+#define IS_SWIOTLB_NOCOPY(addr) ((addr) >= INVALID_PHYS_ADDR - NOCOPY_PCT_MAX)
+#define NOCOPY_ADDR_TO_PCT(addr) ((unsigned int)(INVALID_PHYS_ADDR - (addr)))
+
struct io_tlb_slot {
- phys_addr_t orig_addr;
+ union {
+ phys_addr_t orig_addr;
+ atomic_t nocopy_refcnt;
+ };
size_t alloc_size;
unsigned short list;
unsigned short pad_slots;
+ unsigned int flags;
};
static bool swiotlb_force_bounce;
static bool swiotlb_force_disable;
+/* enable nocopy rx swiotlb and set the percentage of buffers allowed for it. */
+unsigned int nocopy_rx_percent;
+module_param(nocopy_rx_percent, uint, 0644);
+MODULE_PARM_DESC(nocopy_rx_percent, "percentage of swiotlb buffer allowed for nocopy rx");
+
#ifdef CONFIG_SWIOTLB_DYNAMIC
static void swiotlb_dyn_alloc(struct work_struct *work);
@@ -176,7 +229,7 @@ static void swiotlb_adjust_nareas(unsigned int nareas)
static unsigned int limit_nareas(unsigned int nareas, unsigned long nslots)
{
if (nslots < nareas * IO_TLB_SEGSIZE)
- return nslots / IO_TLB_SEGSIZE;
+ return rounddown_pow_of_two(nslots / IO_TLB_SEGSIZE);
return nareas;
}
@@ -269,7 +322,16 @@ static void swiotlb_init_io_tlb_pool(struct io_tlb_pool *mem, phys_addr_t start,
unsigned long nslabs, bool late_alloc, unsigned int nareas)
{
void *vaddr = phys_to_virt(start);
- unsigned long bytes = nslabs << IO_TLB_SHIFT, i;
+ unsigned long bytes, i;
+
+ /*
+ * If we have multiple areas, ensure each area's size is a multiple of
+ * IO_TLB_SEGSIZE slots by aligning the total pool size down.
+ */
+ if (nareas > 1)
+ nslabs = ALIGN_DOWN(nslabs, nareas * IO_TLB_SEGSIZE);
+
+ bytes = nslabs << IO_TLB_SHIFT;
mem->nslabs = nslabs;
mem->start = start;
@@ -290,6 +352,7 @@ static void swiotlb_init_io_tlb_pool(struct io_tlb_pool *mem, phys_addr_t start,
mem->slots[i].orig_addr = INVALID_PHYS_ADDR;
mem->slots[i].alloc_size = 0;
mem->slots[i].pad_slots = 0;
+ mem->slots[i].flags = 0;
}
memset(vaddr, 0, bytes);
@@ -859,12 +922,17 @@ static void swiotlb_bounce(struct device *dev, phys_addr_t tlb_addr, size_t size
enum dma_data_direction dir, struct io_tlb_pool *mem)
{
int index = (tlb_addr - mem->start) >> IO_TLB_SHIFT;
- phys_addr_t orig_addr = mem->slots[index].orig_addr;
size_t alloc_size = mem->slots[index].alloc_size;
- unsigned long pfn = PFN_DOWN(orig_addr);
unsigned char *vaddr = mem->vaddr + tlb_addr - mem->start;
+ phys_addr_t orig_addr;
+ unsigned long pfn;
int tlb_offset;
+ /* Nocopy swiotlb buffers do not need bouncing. */
+ if (mem->slots[index].flags & SWIOTLB_SLOT_NOCOPY)
+ return;
+
+ orig_addr = mem->slots[index].orig_addr;
if (orig_addr == INVALID_PHYS_ADDR)
return;
@@ -894,6 +962,7 @@ static void swiotlb_bounce(struct device *dev, phys_addr_t tlb_addr, size_t size
size = alloc_size;
}
+ pfn = PFN_DOWN(orig_addr);
if (PageHighMem(pfn_to_page(pfn))) {
unsigned int offset = orig_addr & ~PAGE_MASK;
struct page *page;
@@ -1042,7 +1111,8 @@ static int swiotlb_search_pool_area(struct device *dev, struct io_tlb_pool *pool
unsigned long max_slots = get_max_slots(boundary_mask);
unsigned int iotlb_align_mask = dma_get_min_align_mask(dev);
unsigned int nslots = nr_slots(alloc_size), stride;
- unsigned int offset = swiotlb_align_offset(dev, 0, orig_addr);
+ unsigned long max_usable = pool->area_nslabs;
+ unsigned int offset;
unsigned int index, slots_checked, count = 0, i;
unsigned long flags;
unsigned int slot_base;
@@ -1051,6 +1121,13 @@ static int swiotlb_search_pool_area(struct device *dev, struct io_tlb_pool *pool
BUG_ON(!nslots);
BUG_ON(area_index >= pool->nareas);
+ if (IS_SWIOTLB_NOCOPY(orig_addr)) {
+ max_usable = (pool->area_nslabs * NOCOPY_ADDR_TO_PCT(orig_addr)) / 100;
+ orig_addr = 0;
+ }
+
+ offset = swiotlb_align_offset(dev, 0, orig_addr);
+
/*
* Historically, swiotlb allocations >= PAGE_SIZE were guaranteed to be
* page-aligned in the absence of any other alignment requirements.
@@ -1077,7 +1154,7 @@ static int swiotlb_search_pool_area(struct device *dev, struct io_tlb_pool *pool
stride = get_max_slots(max(alloc_align_mask, iotlb_align_mask));
spin_lock_irqsave(&area->lock, flags);
- if (unlikely(nslots > pool->area_nslabs - area->used))
+ if (unlikely(area->used + nslots > max_usable))
goto not_found;
slot_base = area_index * pool->area_nslabs;
@@ -1154,6 +1231,9 @@ static int swiotlb_search_pool_area(struct device *dev, struct io_tlb_pool *pool
* Search one memory area in all pools for a sequence of slots that match the
* allocation constraints.
*
+ * If IS_SWIOTLB_NOCOPY(orig_addr) is true, the search is restricted to only the
+ * default pool, which is what swiotlb_alloc_pages() is allowed to use.
+ *
* Return: Index of the first allocated slot, or -1 on error.
*/
static int swiotlb_search_area(struct device *dev, int start_cpu,
@@ -1167,6 +1247,9 @@ static int swiotlb_search_area(struct device *dev, int start_cpu,
rcu_read_lock();
list_for_each_entry_rcu(pool, &mem->pools, node) {
+ /* Only search the default pool (first in mem->pools) for nocopy allocations. */
+ if (IS_SWIOTLB_NOCOPY(orig_addr) && pool != &mem->defpool)
+ break;
if (cpu_offset >= pool->nareas)
continue;
area_index = (start_cpu + cpu_offset) & (pool->nareas - 1);
@@ -1219,6 +1302,13 @@ static int swiotlb_find_slots(struct device *dev, phys_addr_t orig_addr,
goto found;
}
+ /*
+ * Passing a nocopy orig_addr restricts the search to only the
+ * default pool, so do not attempt dynamic pool expansion.
+ */
+ if (IS_SWIOTLB_NOCOPY(orig_addr))
+ return -1;
+
if (!mem->can_grow)
return -1;
@@ -1458,11 +1548,16 @@ phys_addr_t swiotlb_tbl_map_single(struct device *dev, phys_addr_t orig_addr,
return tlb_addr;
}
+/*
+ * called with dev == NULL from swiotlb_dealloc_pages(), in this case force offset
+ * and align_mask to 0, pad_slots is also 0, and assume the pages come from the
+ * default system pool.
+ */
static void swiotlb_release_slots(struct device *dev, phys_addr_t tlb_addr,
struct io_tlb_pool *mem)
{
unsigned long flags;
- unsigned int offset = swiotlb_align_offset(dev, 0, tlb_addr);
+ unsigned int offset = dev ? swiotlb_align_offset(dev, 0, tlb_addr) : 0;
int index, nslots, aindex;
struct io_tlb_area *area;
int count, i;
@@ -1496,6 +1591,7 @@ static void swiotlb_release_slots(struct device *dev, phys_addr_t tlb_addr,
mem->slots[i].orig_addr = INVALID_PHYS_ADDR;
mem->slots[i].alloc_size = 0;
mem->slots[i].pad_slots = 0;
+ mem->slots[i].flags = 0;
}
/*
@@ -1509,7 +1605,7 @@ static void swiotlb_release_slots(struct device *dev, phys_addr_t tlb_addr,
area->used -= nslots;
spin_unlock_irqrestore(&area->lock, flags);
- dec_used(dev->dma_io_tlb_mem, nslots);
+ dec_used(dev ? dev->dma_io_tlb_mem : &io_tlb_default_mem, nslots);
}
#ifdef CONFIG_SWIOTLB_DYNAMIC
@@ -1554,6 +1650,13 @@ void __swiotlb_tbl_unmap_single(struct device *dev, phys_addr_t tlb_addr,
size_t mapping_size, enum dma_data_direction dir,
unsigned long attrs, struct io_tlb_pool *pool)
{
+ int index = (tlb_addr - pool->start) >> IO_TLB_SHIFT;
+
+ if (pool->slots[index].flags & SWIOTLB_SLOT_NOCOPY) {
+ swiotlb_nocopy_dec_ref(pool, tlb_addr);
+ return;
+ }
+
/*
* First, sync the memory before unmapping the entry
*/
@@ -1597,6 +1700,8 @@ dma_addr_t swiotlb_map(struct device *dev, phys_addr_t paddr, size_t size,
phys_addr_t swiotlb_addr;
dma_addr_t dma_addr;
+ dma_learn_bounce_device(dev);
+
trace_swiotlb_bounced(dev, phys_to_dma(dev, paddr), size);
swiotlb_addr = swiotlb_tbl_map_single(dev, paddr, size, 0, dir, attrs);
@@ -1813,7 +1918,10 @@ static int rmem_swiotlb_device_init(struct reserved_mem *rmem,
struct device *dev)
{
struct io_tlb_mem *mem = rmem->priv;
- unsigned long nslabs = rmem->size >> IO_TLB_SHIFT;
+ unsigned long nslabs = round_down(rmem->size >> IO_TLB_SHIFT, IO_TLB_SEGSIZE);
+
+ if (!nslabs)
+ return -EINVAL;
/* Set Per-device io tlb area to one */
unsigned int nareas = 1;
@@ -1899,3 +2007,171 @@ static const struct reserved_mem_ops rmem_swiotlb_ops = {
RESERVEDMEM_OF_DECLARE(dma, "restricted-dma-pool", &rmem_swiotlb_ops);
#endif /* CONFIG_DMA_RESTRICTED_POOL */
+
+static inline int swiotlb_nocopy_head_index(struct io_tlb_pool *pool, phys_addr_t phys)
+{
+ return (page_to_phys(compound_head(phys_to_page(phys))) - pool->start) >> IO_TLB_SHIFT;
+}
+
+/**
+ * swiotlb_dealloc_pages() - Actually release Nocopy slots and page metadata
+ * @pool: SWIOTLB pool containing the buffer.
+ * @parent: Slot index of the buffer head.
+ */
+static void swiotlb_dealloc_pages(struct io_tlb_pool *pool, unsigned int parent)
+{
+ unsigned int order = get_order(pool->slots[parent].alloc_size);
+ phys_addr_t paddr = pool->start + (parent << IO_TLB_SHIFT);
+ struct page *head = phys_to_page(paddr);
+
+ swiotlb_destroy_compound_page(head, order);
+ swiotlb_release_slots(NULL, paddr, pool);
+}
+
+struct page *swiotlb_alloc_pages(struct device *dev, unsigned int order,
+ gfp_t gfp, unsigned int percent)
+{
+ struct io_tlb_pool *pool;
+ struct page *page;
+ int index, nslots, i;
+
+ if (WARN_ON_ONCE(!dev || !dev->dma_io_tlb_mem))
+ return NULL;
+
+ if (dev->dma_io_tlb_mem != &io_tlb_default_mem)
+ return NULL;
+
+ index = swiotlb_find_slots(dev, NOCOPY_PCT_TO_ADDR(percent),
+ PAGE_SIZE << order, (PAGE_SIZE << order) - 1,
+ &pool);
+ if (index < 0)
+ return NULL;
+
+ nslots = (PAGE_SIZE << order) >> IO_TLB_SHIFT;
+ page = phys_to_page(pool->start + (index << IO_TLB_SHIFT));
+ swiotlb_prep_compound_page(page, order);
+ for (i = 0; i < nslots; i++)
+ pool->slots[index + i].flags |= SWIOTLB_SLOT_NOCOPY;
+ atomic_set(&pool->slots[index].nocopy_refcnt, 1);
+ return page;
+}
+EXPORT_SYMBOL(swiotlb_alloc_pages);
+
+bool swiotlb_free_pages(struct page *page, unsigned int order)
+{
+ struct io_tlb_mem *mem = &io_tlb_default_mem;
+ struct io_tlb_pool *pool = &mem->defpool;
+ struct page *head = compound_head(page);
+ unsigned int parent;
+ phys_addr_t paddr;
+
+ paddr = page_to_phys(head);
+ if (paddr < pool->start || paddr >= pool->end)
+ return false;
+
+ parent = swiotlb_nocopy_head_index(pool, paddr);
+ if (!(pool->slots[parent].flags & SWIOTLB_SLOT_NOCOPY))
+ return false;
+
+ if (atomic_dec_and_test(&pool->slots[parent].nocopy_refcnt))
+ swiotlb_dealloc_pages(pool, parent);
+
+ return true;
+}
+EXPORT_SYMBOL(swiotlb_free_pages);
+
+void swiotlb_nocopy_inc_ref(struct io_tlb_pool *pool, phys_addr_t phys)
+{
+ int head_idx = swiotlb_nocopy_head_index(pool, phys);
+
+ atomic_inc(&pool->slots[head_idx].nocopy_refcnt);
+}
+EXPORT_SYMBOL(swiotlb_nocopy_inc_ref);
+
+void swiotlb_nocopy_dec_ref(struct io_tlb_pool *pool, phys_addr_t phys)
+{
+ int head_idx = swiotlb_nocopy_head_index(pool, phys);
+
+ if (atomic_dec_and_test(&pool->slots[head_idx].nocopy_refcnt))
+ swiotlb_dealloc_pages(pool, head_idx);
+}
+EXPORT_SYMBOL(swiotlb_nocopy_dec_ref);
+
+bool swiotlb_pool_is_nocopy(struct io_tlb_pool *pool, phys_addr_t paddr)
+{
+ int index = (paddr - pool->start) >> IO_TLB_SHIFT;
+
+ return pool->slots[index].flags & SWIOTLB_SLOT_NOCOPY;
+}
+EXPORT_SYMBOL_GPL(swiotlb_pool_is_nocopy);
+
+/*
+ * Dropping the reference to sk_swiotlb.dev must be done in two steps:
+ *
+ * 1. Readers inspect the pointer inside RCU critical sections without
+ * acquiring a reference. Use call_rcu() to wait for an RCU grace period
+ * to elapse so lockless in-flight readers finish accessing the device.
+ *
+ * 2. The RCU callback executes in atomic softirq context, but put_device()
+ * can block when releasing a device. Use schedule_work() to transition
+ * to sleepable process context where calling put_device() is safe.
+ */
+struct swiotlb_deferred_put {
+ struct rcu_head rcu;
+ struct work_struct work;
+ struct device *dev;
+};
+
+static void swiotlb_deferred_put_work(struct work_struct *work)
+{
+ struct swiotlb_deferred_put *dp = container_of(work, struct swiotlb_deferred_put, work);
+
+ /* Stage 2: Safely call put_device (can sleep) in process context */
+ put_device(dp->dev);
+ kfree(dp);
+}
+
+static void swiotlb_deferred_put_rcu(struct rcu_head *rcu)
+{
+ struct swiotlb_deferred_put *dp = container_of(rcu, struct swiotlb_deferred_put, rcu);
+
+ /* RCU grace period has passed. Queue the work to do the actual put */
+ schedule_work(&dp->work);
+}
+
+/**
+ * swiotlb_safe_put_device() - Safely release device reference from atomic/interrupt context
+ * @dev: The device structure to release.
+ *
+ * Enqueues a deferred put_device() call on a workqueue using GFP_ATOMIC.
+ * If memory allocation fails, the reference is leaked to avoid an immediate crash.
+ */
+void swiotlb_safe_put_device(struct device *dev)
+{
+ struct swiotlb_deferred_put *dp;
+
+ if (!dev)
+ return;
+
+ /* Lockless fast-path: if we are not the last reference, decrement is safe */
+ if (refcount_dec_not_one(&dev->kobj.kref.refcount))
+ return;
+
+ /*
+ * On the last reference we must defer the final put_device() to task
+ * context because it will trigger device_release() which can sleep.
+ */
+ dp = kmalloc_obj(*dp, GFP_ATOMIC);
+ if (dp) {
+ INIT_WORK(&dp->work, swiotlb_deferred_put_work);
+ dp->dev = dev;
+ /* Stage 1: Wait for RCU readers to finish */
+ call_rcu(&dp->rcu, swiotlb_deferred_put_rcu);
+ } else {
+ pr_warn_ratelimited("swiotlb: failed to allocate deferred put, leaking device ref\n");
+ }
+}
+EXPORT_SYMBOL_GPL(swiotlb_safe_put_device);
+
+atomic_t global_device_epoch = ATOMIC_INIT(1);
+EXPORT_SYMBOL(global_device_epoch);
diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index 083cbcb5bddec..32d5d630f9840 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -16,6 +16,7 @@
#include <linux/stddef.h>
#include <linux/mm.h>
+#include <linux/swiotlb.h>
#include <linux/highmem.h>
#include <linux/interrupt.h>
#include <linux/jiffies.h>
@@ -711,6 +712,56 @@ void prep_compound_page(struct page *page, unsigned int order)
prep_compound_head(page, order);
}
+#ifdef CONFIG_SWIOTLB
+/*
+ * Prepare a SWIOTLB page (potentially compound).
+ *
+ * We explicitly initialize the head page refcount to 1 because recycled
+ * SWIOTLB pages might have a refcount of 0.
+ *
+ * If order > 0 (compound page), we must explicitly set all tail page
+ * refcounts to 0. This is because SWIOTLB pages might have a boot-default
+ * refcount of 1, but the core memory management subsystem expects tail pages
+ * of a compound page to have a refcount of 0.
+ */
+void swiotlb_prep_compound_page(struct page *page, unsigned int order)
+{
+ init_page_count(page);
+ if (order > 0) {
+ for (int i = 1; i < (1 << order); i++)
+ set_page_count(page + i, 0);
+ prep_compound_page(page, order);
+ }
+}
+
+/*
+ * Destroy a SWIOTLB compound page and restore page refcounts.
+ *
+ * When pages are returned to the SWIOTLB pool, we restore the refcount of
+ * all constituent pages (head and tails) to 1. This resets them to their
+ * clean boot-default state, ensuring they are ready for reuse either as
+ * individual order-0 pages or as part of a new compound allocation.
+ */
+void swiotlb_destroy_compound_page(struct page *page, unsigned int order)
+{
+ if (order > 0) {
+ struct folio *folio = (struct folio *)page;
+
+ __ClearPageHead(page);
+ page[1].flags.f &= ~PAGE_FLAGS_SECOND;
+#ifdef NR_PAGES_IN_LARGE_FOLIO
+ folio->_nr_pages = 0;
+#endif
+ for (int i = 1; i < (1 << order); i++) {
+ page[i].mapping = NULL;
+ clear_compound_head(&page[i]);
+ set_page_count(page + i, 1);
+ }
+ }
+ set_page_count(page, 1);
+}
+#endif /* CONFIG_SWIOTLB */
+
static inline void set_buddy_order(struct page *page, unsigned int order)
{
set_page_private(page, order);
@@ -2951,9 +3002,14 @@ static void __free_frozen_pages(struct page *page, unsigned int order,
{
struct per_cpu_pages *pcp;
struct zone *zone;
- unsigned long pfn = page_to_pfn(page);
+ unsigned long pfn;
int migratetype;
+ if (unlikely(swiotlb_free_pages(page, order)))
+ return;
+
+ pfn = page_to_pfn(page);
+
if (!pcp_allowed_order(order)) {
__free_pages_ok(page, order, fpi_flags);
return;
@@ -3019,6 +3075,9 @@ void free_unref_folios(struct folio_batch *folios)
unsigned long pfn = folio_pfn(folio);
unsigned int order = folio_order(folio);
+ if (unlikely(swiotlb_free_pages(&folio->page, order)))
+ continue;
+
if (!__free_pages_prepare(&folio->page, order, FPI_NONE))
continue;
/*
diff --git a/net/core/page_pool.c b/net/core/page_pool.c
index 50ee550fef73a..fe8839a7c70a8 100644
--- a/net/core/page_pool.c
+++ b/net/core/page_pool.c
@@ -19,6 +19,7 @@
#include <linux/dma-direction.h>
#include <linux/dma-mapping.h>
+#include <linux/swiotlb.h>
#include <linux/page-flags.h>
#include <linux/mm.h> /* for put_page() */
#include <linux/poison.h>
@@ -578,10 +579,16 @@ static bool page_pool_dma_map(struct page_pool *pool, netmem_ref netmem, gfp_t g
static struct page *__page_pool_alloc_page_order(struct page_pool *pool,
gfp_t gfp)
{
+ unsigned int pct = READ_ONCE(nocopy_rx_percent);
struct page *page;
gfp |= __GFP_COMP;
- page = alloc_pages_node(pool->p.nid, gfp, pool->p.order);
+ page = NULL;
+ if (pct && is_swiotlb_active(pool->p.dev))
+ page = swiotlb_alloc_pages(pool->p.dev, pool->p.order, gfp,
+ pct);
+ if (!page)
+ page = alloc_pages_node(pool->p.nid, gfp, pool->p.order);
if (unlikely(!page))
return NULL;
@@ -616,8 +623,9 @@ static noinline netmem_ref __page_pool_alloc_netmems_slow(struct page_pool *pool
if ((gfp & GFP_ATOMIC) == GFP_ATOMIC)
gfp |= __GFP_NOWARN;
- /* Don't support bulk alloc for high-order pages */
- if (unlikely(pp_order))
+ /* Don't support bulk alloc for high-order pages or nocopy SWIOTLB */
+ if (unlikely(pp_order || (READ_ONCE(nocopy_rx_percent) &&
+ is_swiotlb_active(pool->p.dev))))
return page_to_netmem(__page_pool_alloc_page_order(pool, gfp));
/* Unnecessary as alloc cache is empty, but guarantees zero count */
@@ -835,6 +843,17 @@ __page_pool_put_page(struct page_pool *pool, netmem_ref netmem,
{
lockdep_assert_no_hardirq();
+ /*
+ * If runtime nocopy mode toggled, evict circulating buffers immediately
+ * back to their respective allocators rather than recycling them.
+ */
+ if (unlikely(!netmem_is_net_iov(netmem) &&
+ swiotlb_is_nocopy_addr(pool->p.dev, page_to_phys(netmem_to_page(netmem))) !=
+ (READ_ONCE(nocopy_rx_percent) > 0))) {
+ page_pool_return_netmem(pool, netmem);
+ return 0;
+ }
+
/* This allocator is optimized for the XDP mode that uses
* one-frame-per-page, but have fallbacks that act like the
* regular page allocator APIs.
diff --git a/net/core/sock.c b/net/core/sock.c
index 1ad41904db25b..ef40d1ff1de9f 100644
--- a/net/core/sock.c
+++ b/net/core/sock.c
@@ -103,6 +103,8 @@
#include <linux/sockios.h>
#include <linux/net.h>
#include <linux/mm.h>
+#include <linux/swiotlb.h>
+#include <linux/device.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/poll.h>
@@ -152,6 +154,87 @@
#include "dev.h"
+#if defined(CONFIG_SWIOTLB) && !defined(CONFIG_PREEMPT_RT)
+
+DEFINE_PER_CPU(struct sock *, current_tx_socket);
+EXPORT_PER_CPU_SYMBOL(current_tx_socket);
+
+void sk_record_bounce_device(struct sock *sk, struct device *dev)
+{
+ struct device *old_dev;
+
+ if (in_hardirq() || !sk_fullsock(sk) || sock_flag(sk, SOCK_ZEROCOPY))
+ return;
+
+ old_dev = rcu_dereference_protected(sk->sk_swiotlb.dev, 1);
+
+ if (dev != old_dev) {
+ /* Rate-limit updates to once per second to prevent bonding thrashing */
+ if (old_dev && time_before(jiffies, sk->sk_swiotlb.jiffies + HZ))
+ return;
+
+ get_device(dev);
+
+ /* Atomically swap in the new device and get the actual old one */
+ old_dev = (struct device *)xchg((struct device __force **)&sk->sk_swiotlb.dev,
+ (struct device __force *)dev);
+
+ WRITE_ONCE(sk->sk_swiotlb.epoch, swiotlb_dev_epoch());
+ sk->sk_swiotlb.jiffies = jiffies;
+
+ /* Only drop the reference to the device we actually replaced */
+ if (old_dev)
+ swiotlb_safe_put_device(old_dev);
+ }
+}
+EXPORT_SYMBOL(sk_record_bounce_device);
+
+/*
+ * Wrap alloc_pages in __skb_page_frag_refill(). If the socket's dma_device requires
+ * SWIOTLB bounce buffering, divert allocation to the SWIOTLB slot allocator.
+ * This ensures the packet payload is written directly to a bounce buffer from the start,
+ * enabling nocopy during driver DMA mapping.
+ */
+static inline struct page *alloc_any_pg(gfp_t gfp, unsigned int order, struct sock *sk)
+{
+ unsigned int pct = READ_ONCE(nocopy_tx_percent);
+
+ if (sk && pct && !sock_flag(sk, SOCK_ZEROCOPY)) {
+ struct page *page = NULL;
+ bool release_dev = false;
+ struct device *dev;
+
+ rcu_read_lock();
+ dev = rcu_dereference(sk->sk_swiotlb.dev);
+ if (dev) {
+ /*
+ * The epoch check is just for cache invalidation, UAF is
+ * protected by the reference held in the sk.
+ */
+ if (swiotlb_dev_epoch() != READ_ONCE(sk->sk_swiotlb.epoch)) {
+ struct device __force **pdev =
+ (struct device __force **)&sk->sk_swiotlb.dev;
+
+ release_dev = (cmpxchg(pdev, (struct device __force *)dev,
+ NULL) == dev);
+ } else {
+ page = swiotlb_alloc_pages(dev, order, gfp, pct);
+ }
+ }
+ rcu_read_unlock();
+ if (release_dev)
+ swiotlb_safe_put_device(dev);
+ if (page)
+ return page;
+ }
+ return alloc_pages(gfp, order);
+}
+#else
+static inline struct page *alloc_any_pg(gfp_t gfp, unsigned int order, struct sock *sk)
+{
+ return alloc_pages(gfp, order);
+}
+#endif
static DEFINE_MUTEX(proto_list_mutex);
static LIST_HEAD(proto_list);
@@ -2387,6 +2470,7 @@ static void __sk_destruct(struct rcu_head *head)
__netns_tracker_free(net, &sk->ns_tracker, false);
net_passive_dec(net);
}
+ sk_release_bounce_device(sk);
sk_prot_free(sk->sk_prot_creator, sk);
}
@@ -2489,6 +2573,7 @@ struct sock *sk_clone(const struct sock *sk, const gfp_t priority,
goto out;
sock_copy(newsk, sk);
+ sk_clear_bounce_device(newsk);
newsk->sk_prot_creator = prot;
#ifdef CONFIG_BPF_SYSCALL
@@ -3174,7 +3259,7 @@ DEFINE_STATIC_KEY_FALSE(net_high_order_alloc_disable_key);
* no guarantee that allocations succeed. Therefore, @sz MUST be
* less or equal than PAGE_SIZE.
*/
-bool skb_page_frag_refill(unsigned int sz, struct page_frag *pfrag, gfp_t gfp)
+bool __skb_page_frag_refill(unsigned int sz, struct page_frag *pfrag, gfp_t gfp, struct sock *sk)
{
if (pfrag->page) {
if (page_ref_count(pfrag->page) == 1) {
@@ -3190,27 +3275,27 @@ bool skb_page_frag_refill(unsigned int sz, struct page_frag *pfrag, gfp_t gfp)
if (SKB_FRAG_PAGE_ORDER &&
!static_branch_unlikely(&net_high_order_alloc_disable_key)) {
/* Avoid direct reclaim but allow kswapd to wake */
- pfrag->page = alloc_pages((gfp & ~__GFP_DIRECT_RECLAIM) |
- __GFP_COMP | __GFP_NOWARN |
- __GFP_NORETRY,
- SKB_FRAG_PAGE_ORDER);
+ pfrag->page = alloc_any_pg((gfp & ~__GFP_DIRECT_RECLAIM) |
+ __GFP_COMP | __GFP_NOWARN |
+ __GFP_NORETRY,
+ SKB_FRAG_PAGE_ORDER, sk);
if (likely(pfrag->page)) {
pfrag->size = PAGE_SIZE << SKB_FRAG_PAGE_ORDER;
return true;
}
}
- pfrag->page = alloc_page(gfp);
+ pfrag->page = alloc_any_pg(gfp, 0, sk);
if (likely(pfrag->page)) {
pfrag->size = PAGE_SIZE;
return true;
}
return false;
}
-EXPORT_SYMBOL(skb_page_frag_refill);
+EXPORT_SYMBOL(__skb_page_frag_refill);
bool sk_page_frag_refill(struct sock *sk, struct page_frag *pfrag)
{
- if (likely(skb_page_frag_refill(32U, pfrag, sk->sk_allocation)))
+ if (likely(__skb_page_frag_refill(32U, pfrag, sk->sk_allocation, sk)))
return true;
if (!sk->sk_bypass_prot_mem)
|
| 3/2 | 2026/08/24 18:19 | llm | patch-evaluator |
15mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 15873 output: 21 thoughts: 869 Thoughts: **My Analysis of the SWIOTLB No-Copy Patch** Okay, so I'm looking at this patch that introduces a "nocopy" feature for SWIOTLB, and my immediate thought is that this is a significant change. It's touching core kernel areas: SWIOTLB, networking, and DMA. That's a red flag – in a good way, because it means we *absolutely* need to fuzz this thoroughly. This definitely warrants a deep dive. First, I need to figure out the relevant configuration options. I see `CONFIG_SWIOTLB` is a given, obviously. Then, `CONFIG_NET` is essential since it deals with networking code. The presence of `#if defined(CONFIG_SWIOTLB) && !defined(CONFIG_PREEMPT_RT)` means that `CONFIG_PREEMPT_RT`'s state must also be considered. I'll need to keep that in mind when setting up the fuzzing environment. Next, the focus symbols are pretty clear: `swiotlb_alloc_pages`, `swiotlb_free_pages`, the `nocopy` ref counting functions, the `swiotlb_pool_is_nocopy` check, `swiotlb_safe_put_device`, `sk_record_bounce_device`, and `__skb_page_frag_refill`. I'll need to ensure these are correctly targeted by syzkaller. I need to verify their existence and visibility (exported or non-static). Fortunately, they all seem to be exported, which is great. I also need to make sure the build doesn't optimize away calls to the code. The addition of the `nocopy_tx_percent` and `nocopy_rx_percent` module parameters is interesting. I'll need to consider how to set these up for testing. Syzkaller's default setup is not optimal but I might be able to leverage `/sys/module/swiotlb/parameters/nocopy_tx_percent` to set these values. I'll need to check the syzkaller configuration, or see if it will allow me to write to it directly. Okay, let's nail down these configs. `CONFIG_SWIOTLB` and `CONFIG_NET` are the key ones here. That's the baseline. I'll also double-check with the `default_api:grepper` to be sure that `swiotlb_alloc_pages` is actually in the tree. |
| 4/2 | 2026/08/24 18:34 | tool | grepper |
0mArgs:{
"Expression": "swiotlb_alloc_pages"
}Results: {
"Output": "include/linux/swiotlb.h=197=void __init swiotlb_exit(void);\ninclude/linux/swiotlb.h:198:struct page *swiotlb_alloc_pages(struct device *dev, unsigned int order, gfp_t gfp,\ninclude/linux/swiotlb.h-199-\t\t\t\t unsigned int percent);\n--\nkernel/dma/swiotlb.c=69=MODULE_PARM_DESC(nocopy_tx_percent, \"percentage of swiotlb buffer allowed for nocopy tx\");\n--\nkernel/dma/swiotlb.c-97-/*\nkernel/dma/swiotlb.c:98: * SWIOTLB nocopy allocations (swiotlb_alloc_pages()) do not have an original\nkernel/dma/swiotlb.c-99- * physical address to bounce, but need to pass a caller-specified pool usage\n--\nkernel/dma/swiotlb.c=1103=static int swiotlb_search_pool_area(struct device *dev, struct io_tlb_pool *pool,\n--\nkernel/dma/swiotlb.c-1234- * If IS_SWIOTLB_NOCOPY(orig_addr) is true, the search is restricted to only the\nkernel/dma/swiotlb.c:1235: * default pool, which is what swiotlb_alloc_pages() is allowed to use.\nkernel/dma/swiotlb.c-1236- *\n--\nkernel/dma/swiotlb.c=2021=static void swiotlb_dealloc_pages(struct io_tlb_pool *pool, unsigned int parent)\n--\nkernel/dma/swiotlb.c-2030-\nkernel/dma/swiotlb.c:2031:struct page *swiotlb_alloc_pages(struct device *dev, unsigned int order,\nkernel/dma/swiotlb.c-2032-\t\t\t\t gfp_t gfp, unsigned int percent)\n--\nkernel/dma/swiotlb.c-2057-}\nkernel/dma/swiotlb.c:2058:EXPORT_SYMBOL(swiotlb_alloc_pages);\nkernel/dma/swiotlb.c-2059-\n--\nnet/core/page_pool.c=579=static struct page *__page_pool_alloc_page_order(struct page_pool *pool,\n--\nnet/core/page_pool.c-587-\tif (pct \u0026\u0026 is_swiotlb_active(pool-\u003ep.dev))\nnet/core/page_pool.c:588:\t\tpage = swiotlb_alloc_pages(pool-\u003ep.dev, pool-\u003ep.order, gfp,\nnet/core/page_pool.c-589-\t\t\t\t\t pct);\n--\nnet/core/sock.c=198=static inline struct page *alloc_any_pg(gfp_t gfp, unsigned int order, struct sock *sk)\n--\nnet/core/sock.c-220-\t\t\t} else {\nnet/core/sock.c:221:\t\t\t\tpage = swiotlb_alloc_pages(dev, order, gfp, pct);\nnet/core/sock.c-222-\t\t\t}\n"
} |
| 5/2 | 2026/08/24 18:34 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 18101 output: 24 thoughts: 265 Thoughts: **My Initial Assessment of the "nocopy SWIOTLB" Patch** Okay, so this patch is introducing "nocopy SWIOTLB" for networking, which sounds like it could be a performance boost, potentially a fairly significant one. It's hooking into the core page allocation and DMA mapping functions, specifically `__skb_page_frag_refill` and `page_pool_alloc_page_order`, along with the DMA functions `dma_direct_map_phys` and `iommu_dma_map_phys`. Given its impact on core networking data paths, it's definitely high on my list for fuzzing. We need to really put this through its paces and see if there are any subtle issues or edge cases. I should zero in on these symbols: `swiotlb_alloc_pages`, `swiotlb_free_pages`, `swiotlb_nocopy_inc_ref`, `swiotlb_nocopy_dec_ref`, `swiotlb_pool_is_nocopy`, `swiotlb_safe_put_device`, `sk_record_bounce_device`, and of course, `__skb_page_frag_refill`. They're the critical pieces of the puzzle. I need to understand how these interact in the context of the new "nocopy" behavior. On the config side, it looks like `CONFIG_SWIOTLB` is the obvious prerequisite, that needs to be checked. Also, because this is networking, `CONFIG_NET` is likely going to be a hard requirement to be enabled. I better check. Let's start by looking at `__skb_page_frag_refill` in `net/core/sock.c` to see how it's being used. It's a key function in networking, so any changes there are going to be important to understand. Time to dive in! |
| 6/2 | 2026/08/24 18:34 | tool | grepper |
0mArgs:{
"Expression": "__skb_page_frag_refill"
}Results: {
"Output": "include/linux/skbuff.h=3783=static inline void skb_frag_page_copy(skb_frag_t *fragto,\n--\ninclude/linux/skbuff.h-3789-/* nocopy swiotlb uses an additional non-null struct sock pointer. */\ninclude/linux/skbuff.h:3790:bool __skb_page_frag_refill(unsigned int sz, struct page_frag *pfrag, gfp_t prio, struct sock *sk);\ninclude/linux/skbuff.h-3791-static inline bool skb_page_frag_refill(unsigned int sz, struct page_frag *pfrag, gfp_t prio)\ninclude/linux/skbuff.h-3792-{\ninclude/linux/skbuff.h:3793:\treturn __skb_page_frag_refill(sz, pfrag, prio, NULL);\ninclude/linux/skbuff.h-3794-}\n--\nnet/core/sock.c=190=EXPORT_SYMBOL(sk_record_bounce_device);\n--\nnet/core/sock.c-192-/*\nnet/core/sock.c:193: * Wrap alloc_pages in __skb_page_frag_refill(). If the socket's dma_device requires\nnet/core/sock.c-194- * SWIOTLB bounce buffering, divert allocation to the SWIOTLB slot allocator.\n--\nnet/core/sock.c=3250=DEFINE_STATIC_KEY_FALSE(net_high_order_alloc_disable_key);\n--\nnet/core/sock.c-3261- */\nnet/core/sock.c:3262:bool __skb_page_frag_refill(unsigned int sz, struct page_frag *pfrag, gfp_t gfp, struct sock *sk)\nnet/core/sock.c-3263-{\n--\nnet/core/sock.c-3293-}\nnet/core/sock.c:3294:EXPORT_SYMBOL(__skb_page_frag_refill);\nnet/core/sock.c-3295-\nnet/core/sock.c=3296=bool sk_page_frag_refill(struct sock *sk, struct page_frag *pfrag)\nnet/core/sock.c-3297-{\nnet/core/sock.c:3298:\tif (likely(__skb_page_frag_refill(32U, pfrag, sk-\u003esk_allocation, sk)))\nnet/core/sock.c-3299-\t\treturn true;\n"
} |
| 7/2 | 2026/08/24 18:34 | llm | patch-evaluator |
3mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 19447 output: 18 thoughts: 122 Thoughts: **Analyzing the New SWIOTLB Patch** Okay, so this patch introduces a suite of new functions: `swiotlb_alloc_pages`, `swiotlb_free_pages`, `swiotlb_nocopy_inc_ref`, `swiotlb_nocopy_dec_ref`, `swiotlb_pool_is_nocopy`, `swiotlb_safe_put_device`, `sk_record_bounce_device`, and `__skb_page_frag_refill`. Just looking at the names, these seem like well-targeted additions, good focus symbols for their tasks. Seems like a solid approach. The key configuration option here is `SWIOTLB`. Now, I need to take a look at the config dependencies. I'll need to check if `CONFIG_SWIOTLB` relies on anything specific, as that'll give me context on what architectural assumptions this patch is built upon. Always good to understand the build requirements up front. |
| 8/2 | 2026/08/24 18:38 | tool | grepper |
0mArgs:{
"Expression": "config SWIOTLB"
}Results: {
"Output": "drivers/xen/Kconfig=165=config XEN_GRANT_DMA_ALLOC\n--\ndrivers/xen/Kconfig-178-\ndrivers/xen/Kconfig:179:config SWIOTLB_XEN\ndrivers/xen/Kconfig-180-\tdef_bool y\n--\nkernel/dma/Kconfig=82=config ARCH_DMA_DEFAULT_COHERENT\n--\nkernel/dma/Kconfig-84-\nkernel/dma/Kconfig:85:config SWIOTLB\nkernel/dma/Kconfig-86-\tbool\n--\nkernel/dma/Kconfig-88-\nkernel/dma/Kconfig:89:config SWIOTLB_DYNAMIC\nkernel/dma/Kconfig-90-\tbool \"Dynamic allocation of DMA bounce buffers\"\n"
} |
| 9/2 | 2026/08/24 18:38 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 20000 output: 207 thoughts: 36 Thoughts: Here's my take on those thoughts: **Analyzing the SWIOTLB Configuration Option** Okay, so `SWIOTLB`. It's a bit of a hidden gem, this boolean config, typically architecture-specific. I'm thinking, instead of digging around in the usual places, maybe we can just get a quick and dirty listing of configurations where it's enabled. That's a solid first step. Simplicity is key, and it'll give us a good overview. Alright, now that I have a plan to find the configuration options, let's call that process `set-results`. Hopefully, it's efficient, given the scope. |
| 10/2 | 2026/08/24 18:38 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"SWIOTLB"
],
"FocusSymbols": [
"swiotlb_alloc_pages",
"swiotlb_free_pages",
"swiotlb_nocopy_inc_ref",
"swiotlb_nocopy_dec_ref",
"swiotlb_pool_is_nocopy",
"swiotlb_safe_put_device",
"sk_record_bounce_device",
"__skb_page_frag_refill"
],
"Reasoning": "The patch implements a new 'nocopy' SWIOTLB feature for networking, allowing direct DMA mapping of network buffers without bounce buffering. It modifies core SWIOTLB allocation logic, DMA mapping paths, and network page allocation (skb_page_frag_refill, page_pool). These are significant functional changes to core kernel subsystems that are reachable and should be fuzzed.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"SWIOTLB"
],
"FocusSymbols": [
"swiotlb_alloc_pages",
"swiotlb_free_pages",
"swiotlb_nocopy_inc_ref",
"swiotlb_nocopy_dec_ref",
"swiotlb_pool_is_nocopy",
"swiotlb_safe_put_device",
"sk_record_bounce_device",
"__skb_page_frag_refill"
],
"Reasoning": "The patch implements a new 'nocopy' SWIOTLB feature for networking, allowing direct DMA mapping of network buffers without bounce buffering. It modifies core SWIOTLB allocation logic, DMA mapping paths, and network page allocation (skb_page_frag_refill, page_pool). These are significant functional changes to core kernel subsystems that are reachable and should be fuzzed.",
"WorthFuzzing": true
} |
| 11/1 | 2026/08/24 18:38 | action | If |
16mArgs:{
"WorthFuzzing": true
}Results: null |
| 12/2 | 2026/08/24 18:38 | agent | kmsan-evaluator |
16mModel:best-expensive Args: null Results: {
"KMSANReasoning": "The patch introduces a \"nocopy SWIOTLB\" optimization for network sockets, which routes page allocations for known bounce-buffering sockets directly to the SWIOTLB pool. \n\n1. **New Data Structures**: The patch adds `sk_swiotlb_info` to `struct sock` and introduces `swiotlb_deferred_put`. Both are strictly kernel-internal and never copied to userspace. They are also fully initialized upon creation (`sk_alloc` uses `__GFP_ZERO`, and `swiotlb_deferred_put` explicitly initializes all its fields).\n2. **Custom Allocator Bypasses KMSAN**: The new `swiotlb_alloc_pages()` function bypasses the standard `alloc_pages()` API. Because KMSAN relies on hooks within the standard page allocator to poison newly allocated memory, it is completely unaware of these SWIOTLB allocations. As a result, KMSAN will view these pages as fully initialized (due to the boot-time `memset` of the SWIOTLB pool), making it blind to any uninitialized memory leaks or uses originating from these buffers.\n3. **Bug Classes**: Any potential memory safety issues introduced by this patch—such as out-of-bounds accesses in the SWIOTLB pool, use-after-free of device pointers, or race conditions in the lockless refcounting—are fully covered by KASAN, LOCKDEP, and KCSAN.\n\nSince the patch does not introduce any uninitialized memory risks that KMSAN is capable of detecting, a dedicated KMSAN fuzzing session is not justified.",
"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: For your convenience, here is the diff of the changes:
commit 6fe30e0c486a30f009129aa8c785d15bf9848f47
Author: syz-cluster <triage@syzkaller.com>
Date: Mon Aug 24 18:19:01 2026 +0000
syz-cluster: applied patch under review
diff --git a/drivers/base/core.c b/drivers/base/core.c
index 4c0c373998a19..091062228740d 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -3925,6 +3925,7 @@ void device_del(struct device *dev)
unsigned int noio_flag;
device_lock(dev);
+ swiotlb_change_epoch();
kill_device(dev);
device_unlock(dev);
diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c
index 9a07eb39336eb..956d5e11b2896 100644
--- a/drivers/iommu/dma-iommu.c
+++ b/drivers/iommu/dma-iommu.c
@@ -1228,7 +1228,14 @@ dma_addr_t iommu_dma_map_phys(struct device *dev, phys_addr_t phys, size_t size,
* If both the physical buffer start address and size are page aligned,
* we don't need to use a bounce page.
*/
- if (dev_use_swiotlb(dev, size, dir) &&
+ bool is_nocopy = false;
+
+ if (swiotlb_is_nocopy_addr(dev, phys)) {
+ swiotlb_nocopy_inc_ref(&dev->dma_io_tlb_mem->defpool, phys);
+ is_nocopy = true;
+ }
+
+ if (!is_nocopy && dev_use_swiotlb(dev, size, dir) &&
iova_unaligned(iovad, phys, size)) {
if (attrs & (DMA_ATTR_MMIO | DMA_ATTR_REQUIRE_COHERENT))
return DMA_MAPPING_ERROR;
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 87cafc932e9e6..2457f4e464acf 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -5429,13 +5429,34 @@ static inline netdev_tx_t __netdev_start_xmit(const struct net_device_ops *ops,
return ops->ndo_start_xmit(skb, dev);
}
+struct sock;
+
+#if defined(CONFIG_SWIOTLB) && !defined(CONFIG_PREEMPT_RT)
+/* Per-CPU pointer to the socket currently performing transmission. Used
+ * to bridge the networking and DMA layers, allowing dma_map_page() to
+ * identify the socket originating the packet and apply SWIOTLB optimizations.
+ */
+DECLARE_PER_CPU(struct sock *, current_tx_socket);
+static inline struct sock *__save_current_tx_socket(struct sock *sk)
+{
+ struct sock *old_sk = this_cpu_read(current_tx_socket);
+
+ this_cpu_write(current_tx_socket, sk);
+ return old_sk;
+}
+#else
+static inline struct sock *__save_current_tx_socket(struct sock *sk) { return NULL; }
+#endif
+
static inline netdev_tx_t netdev_start_xmit(struct sk_buff *skb, struct net_device *dev,
struct netdev_queue *txq, bool more)
{
+ struct sock *old_sk = __save_current_tx_socket(skb->sk);
const struct net_device_ops *ops = dev->netdev_ops;
netdev_tx_t rc;
rc = __netdev_start_xmit(ops, skb, dev, more);
+ __save_current_tx_socket(old_sk);
if (rc == NETDEV_TX_OK)
txq_trans_update(dev, txq);
diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index add0d282dea6e..d8f7041edc400 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -3786,7 +3786,12 @@ static inline void skb_frag_page_copy(skb_frag_t *fragto,
fragto->netmem = fragfrom->netmem;
}
-bool skb_page_frag_refill(unsigned int sz, struct page_frag *pfrag, gfp_t prio);
+/* nocopy swiotlb uses an additional non-null struct sock pointer. */
+bool __skb_page_frag_refill(unsigned int sz, struct page_frag *pfrag, gfp_t prio, struct sock *sk);
+static inline bool skb_page_frag_refill(unsigned int sz, struct page_frag *pfrag, gfp_t prio)
+{
+ return __skb_page_frag_refill(sz, pfrag, prio, NULL);
+}
/**
* __skb_frag_dma_map - maps a paged fragment via the DMA API
diff --git a/include/linux/swiotlb.h b/include/linux/swiotlb.h
index 3dae0f592063e..f4597fd01c52d 100644
--- a/include/linux/swiotlb.h
+++ b/include/linux/swiotlb.h
@@ -169,6 +169,23 @@ static inline struct io_tlb_pool *swiotlb_find_pool(struct device *dev,
return NULL;
}
+bool swiotlb_pool_is_nocopy(struct io_tlb_pool *pool, phys_addr_t paddr);
+
+static inline bool swiotlb_addr_in_default_pool(struct device *dev,
+ phys_addr_t paddr)
+{
+ struct io_tlb_mem *mem = dev->dma_io_tlb_mem;
+
+ return mem && paddr >= mem->defpool.start && paddr < mem->defpool.end;
+}
+
+static inline bool swiotlb_is_nocopy_addr(struct device *dev, phys_addr_t paddr)
+{
+ if (!swiotlb_addr_in_default_pool(dev, paddr))
+ return false;
+ return swiotlb_pool_is_nocopy(&dev->dma_io_tlb_mem->defpool, paddr);
+}
+
static inline bool is_swiotlb_force_bounce(struct device *dev)
{
struct io_tlb_mem *mem = dev->dma_io_tlb_mem;
@@ -178,6 +195,49 @@ static inline bool is_swiotlb_force_bounce(struct device *dev)
void swiotlb_init(bool addressing_limited, unsigned int flags);
void __init swiotlb_exit(void);
+struct page *swiotlb_alloc_pages(struct device *dev, unsigned int order, gfp_t gfp,
+ unsigned int percent);
+bool swiotlb_free_pages(struct page *page, unsigned int order);
+void swiotlb_nocopy_inc_ref(struct io_tlb_pool *pool, phys_addr_t phys);
+void swiotlb_nocopy_dec_ref(struct io_tlb_pool *pool, phys_addr_t phys);
+void swiotlb_prep_compound_page(struct page *page, unsigned int order);
+void swiotlb_destroy_compound_page(struct page *page, unsigned int order);
+void swiotlb_safe_put_device(struct device *dev);
+
+extern unsigned int nocopy_tx_percent;
+extern unsigned int nocopy_rx_percent;
+
+/* Track epoch (number of delete operations) for leaf device info. */
+extern atomic_t global_device_epoch;
+
+static inline u32 swiotlb_dev_epoch(void)
+{
+ return atomic_read(&global_device_epoch);
+}
+
+static inline void swiotlb_change_epoch(void)
+{
+ atomic_inc(&global_device_epoch);
+}
+
+#if defined(CONFIG_NET) && !defined(CONFIG_PREEMPT_RT)
+/*
+ * Track the socket for the currently transmitted packet, so the dma mapping
+ * function can record there the leaf device if it needs bounce buffers.
+ */
+struct sock;
+DECLARE_PER_CPU(struct sock *, current_tx_socket);
+void sk_record_bounce_device(struct sock *sk, struct device *dev);
+static inline void dma_learn_bounce_device(struct device *dev)
+{
+ struct sock *sk = this_cpu_read(current_tx_socket);
+
+ if (sk)
+ sk_record_bounce_device(sk, dev);
+}
+#else
+static inline void dma_learn_bounce_device(struct device *dev) {}
+#endif
void swiotlb_dev_init(struct device *dev);
size_t swiotlb_max_mapping_size(struct device *dev);
bool is_swiotlb_allocated(void);
@@ -234,6 +294,9 @@ static inline phys_addr_t default_swiotlb_limit(void)
{
return 0;
}
+static inline void swiotlb_safe_put_device(struct device *dev)
+{
+}
#endif /* CONFIG_SWIOTLB */
phys_addr_t swiotlb_tbl_map_single(struct device *hwdev, phys_addr_t phys,
diff --git a/include/net/sock.h b/include/net/sock.h
index 51185222aac29..39b5e81c7cc55 100644
--- a/include/net/sock.h
+++ b/include/net/sock.h
@@ -47,6 +47,7 @@
#include <linux/skbuff.h> /* struct sk_buff */
#include <linux/mm.h>
#include <linux/security.h>
+#include <linux/swiotlb.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/page_counter.h>
@@ -70,6 +71,14 @@
#include <net/l3mdev.h>
#include <uapi/linux/socket.h>
+#if defined(CONFIG_SWIOTLB) && !defined(CONFIG_PREEMPT_RT)
+struct sk_swiotlb_info {
+ struct device __rcu *dev;
+ u32 epoch;
+ unsigned long jiffies;
+};
+#endif
+
/*
* This structure really needs to be cleaned up.
* Most of it is for TCP, and not used by any of
@@ -602,8 +611,45 @@ struct sock {
#if IS_ENABLED(CONFIG_PROVE_LOCKING) && IS_ENABLED(CONFIG_MODULES)
struct module *sk_owner;
#endif
+#if defined(CONFIG_SWIOTLB) && !defined(CONFIG_PREEMPT_RT)
+ struct sk_swiotlb_info sk_swiotlb;
+#endif
};
+#if defined(CONFIG_SWIOTLB) && !defined(CONFIG_PREEMPT_RT)
+/*
+ * Clear bounce device on newly initialized or cloned sockets.
+ * Note: During socket cloning, sock_copy() performs a raw bitwise copy of
+ * the parent socket without incrementing the device refcount via get_device().
+ * Therefore, we must zero sk_swiotlb.dev directly here without putting a
+ * reference. References are acquired solely by sk_record_bounce_device() and
+ * released in sk_release_bounce_device().
+ */
+static inline void sk_clear_bounce_device(struct sock *sk)
+{
+ rcu_assign_pointer(sk->sk_swiotlb.dev, NULL);
+}
+
+/*
+ * Release any device reference acquired via sk_record_bounce_device() during
+ * socket transmission and clear the device pointer. Called during socket
+ * destruction (__sk_destruct).
+ */
+static inline void sk_release_bounce_device(struct sock *sk)
+{
+ struct device *dev;
+
+ dev = rcu_dereference_raw(sk->sk_swiotlb.dev);
+ if (dev) {
+ swiotlb_safe_put_device(dev);
+ rcu_assign_pointer(sk->sk_swiotlb.dev, NULL);
+ }
+}
+#else
+static inline void sk_clear_bounce_device(struct sock *sk) {}
+static inline void sk_release_bounce_device(struct sock *sk) {}
+#endif
+
struct sock_bh_locked {
struct sock *sock;
local_lock_t bh_lock;
diff --git a/kernel/dma/direct.h b/kernel/dma/direct.h
index 7140c208c1238..21c65acb13823 100644
--- a/kernel/dma/direct.h
+++ b/kernel/dma/direct.h
@@ -88,6 +88,17 @@ static inline dma_addr_t dma_direct_map_phys(struct device *dev,
{
dma_addr_t dma_addr;
+ if (swiotlb_is_nocopy_addr(dev, phys)) {
+ dma_addr_t unenc_addr = phys_to_dma_unencrypted(dev, phys);
+
+ if (likely(dma_capable(dev, unenc_addr, size, true))) {
+ swiotlb_nocopy_inc_ref(&dev->dma_io_tlb_mem->defpool, phys);
+ if (!dev_is_dma_coherent(dev) && !(attrs & DMA_ATTR_SKIP_CPU_SYNC))
+ arch_sync_dma_for_device(phys, size, dir);
+ return unenc_addr;
+ }
+ }
+
if (is_swiotlb_force_bounce(dev)) {
if (!(attrs & DMA_ATTR_CC_SHARED)) {
if (attrs & (DMA_ATTR_MMIO | DMA_ATTR_REQUIRE_COHERENT))
diff --git a/kernel/dma/swiotlb.c b/kernel/dma/swiotlb.c
index 1abd3e6146f45..91f175c34a34e 100644
--- a/kernel/dma/swiotlb.c
+++ b/kernel/dma/swiotlb.c
@@ -33,6 +33,7 @@
#include <linux/kmsan-checks.h>
#include <linux/iommu-helper.h>
#include <linux/init.h>
+#include <linux/log2.h>
#include <linux/memblock.h>
#include <linux/mm.h>
#include <linux/pfn.h>
@@ -62,25 +63,77 @@
*/
#define IO_TLB_MIN_SLABS ((1<<20) >> IO_TLB_SHIFT)
+/* enable nocopy tx swiotlb and set the percentage of buffers allowed for it. */
+unsigned int nocopy_tx_percent;
+module_param(nocopy_tx_percent, uint, 0644);
+MODULE_PARM_DESC(nocopy_tx_percent, "percentage of swiotlb buffer allowed for nocopy tx");
+
/**
* struct io_tlb_slot - IO TLB slot descriptor
* @orig_addr: The original address corresponding to a mapped entry.
+ * @nocopy_refcnt: Lockless atomic refcount for Nocopy buffers.
* @alloc_size: Size of the allocated buffer.
* @list: The free list describing the number of free entries available
* from each index.
* @pad_slots: Number of preceding padding slots. Valid only in the first
* allocated non-padding slot.
+ * @flags: Slot attributes (e.g. SWIOTLB_SLOT_NOCOPY for Nocopy buffers).
+ *
+ * The slot descriptor has states identified by @list and @flags (SWIOTLB_SLOT_NOCOPY):
+ *
+ * 1. FREE (list > 0):
+ * Linear sweep free slot.
+ *
+ * 2. USED (list == 0, SWIOTLB_SLOT_NOCOPY flag is NOT set in @flags):
+ * Allocated SWIOTLB bounce buffer.
+ * Fields used: @list, @pad_slots, @orig_addr, @alloc_size.
+ *
+ * 3. USED_NOCOPY (list == 0, SWIOTLB_SLOT_NOCOPY flag is set in @flags):
+ * Allocated Nocopy SWIOTLB buffer.
+ * Fields used: @list, @nocopy_refcnt, @alloc_size.
*/
+#define SWIOTLB_SLOT_NOCOPY BIT(0)
+
+/*
+ * SWIOTLB nocopy allocations (swiotlb_alloc_pages()) do not have an original
+ * physical address to bounce, but need to pass a caller-specified pool usage
+ * limit (percentage) down to the area search logic.
+ *
+ * To avoid adding a parameter to swiotlb_find_slots(), swiotlb_search_area(),
+ * and swiotlb_search_pool_area(), the desired percentage (0..90) is encoded
+ * into the orig_addr parameter in the reserved high address range starting at
+ * INVALID_PHYS_ADDR (~0ULL).
+ *
+ * - NOCOPY_PCT_TO_ADDR(pct): Encodes a percentage into an orig_addr.
+ * - IS_SWIOTLB_NOCOPY(addr): Identifies a nocopy allocation request and
+ * restricts slot search to the static default pool.
+ * - NOCOPY_ADDR_TO_PCT(addr): Extracts the percentage to cap max_usable
+ * slots in swiotlb_search_pool_area().
+ */
+#define NOCOPY_PCT_MAX (90u)
+#define NOCOPY_PCT_TO_ADDR(pct) (INVALID_PHYS_ADDR - min(pct, NOCOPY_PCT_MAX))
+#define IS_SWIOTLB_NOCOPY(addr) ((addr) >= INVALID_PHYS_ADDR - NOCOPY_PCT_MAX)
+#define NOCOPY_ADDR_TO_PCT(addr) ((unsigned int)(INVALID_PHYS_ADDR - (addr)))
+
struct io_tlb_slot {
- phys_addr_t orig_addr;
+ union {
+ phys_addr_t orig_addr;
+ atomic_t nocopy_refcnt;
+ };
size_t alloc_size;
unsigned short list;
unsigned short pad_slots;
+ unsigned int flags;
};
static bool swiotlb_force_bounce;
static bool swiotlb_force_disable;
+/* enable nocopy rx swiotlb and set the percentage of buffers allowed for it. */
+unsigned int nocopy_rx_percent;
+module_param(nocopy_rx_percent, uint, 0644);
+MODULE_PARM_DESC(nocopy_rx_percent, "percentage of swiotlb buffer allowed for nocopy rx");
+
#ifdef CONFIG_SWIOTLB_DYNAMIC
static void swiotlb_dyn_alloc(struct work_struct *work);
@@ -176,7 +229,7 @@ static void swiotlb_adjust_nareas(unsigned int nareas)
static unsigned int limit_nareas(unsigned int nareas, unsigned long nslots)
{
if (nslots < nareas * IO_TLB_SEGSIZE)
- return nslots / IO_TLB_SEGSIZE;
+ return rounddown_pow_of_two(nslots / IO_TLB_SEGSIZE);
return nareas;
}
@@ -269,7 +322,16 @@ static void swiotlb_init_io_tlb_pool(struct io_tlb_pool *mem, phys_addr_t start,
unsigned long nslabs, bool late_alloc, unsigned int nareas)
{
void *vaddr = phys_to_virt(start);
- unsigned long bytes = nslabs << IO_TLB_SHIFT, i;
+ unsigned long bytes, i;
+
+ /*
+ * If we have multiple areas, ensure each area's size is a multiple of
+ * IO_TLB_SEGSIZE slots by aligning the total pool size down.
+ */
+ if (nareas > 1)
+ nslabs = ALIGN_DOWN(nslabs, nareas * IO_TLB_SEGSIZE);
+
+ bytes = nslabs << IO_TLB_SHIFT;
mem->nslabs = nslabs;
mem->start = start;
@@ -290,6 +352,7 @@ static void swiotlb_init_io_tlb_pool(struct io_tlb_pool *mem, phys_addr_t start,
mem->slots[i].orig_addr = INVALID_PHYS_ADDR;
mem->slots[i].alloc_size = 0;
mem->slots[i].pad_slots = 0;
+ mem->slots[i].flags = 0;
}
memset(vaddr, 0, bytes);
@@ -859,12 +922,17 @@ static void swiotlb_bounce(struct device *dev, phys_addr_t tlb_addr, size_t size
enum dma_data_direction dir, struct io_tlb_pool *mem)
{
int index = (tlb_addr - mem->start) >> IO_TLB_SHIFT;
- phys_addr_t orig_addr = mem->slots[index].orig_addr;
size_t alloc_size = mem->slots[index].alloc_size;
- unsigned long pfn = PFN_DOWN(orig_addr);
unsigned char *vaddr = mem->vaddr + tlb_addr - mem->start;
+ phys_addr_t orig_addr;
+ unsigned long pfn;
int tlb_offset;
+ /* Nocopy swiotlb buffers do not need bouncing. */
+ if (mem->slots[index].flags & SWIOTLB_SLOT_NOCOPY)
+ return;
+
+ orig_addr = mem->slots[index].orig_addr;
if (orig_addr == INVALID_PHYS_ADDR)
return;
@@ -894,6 +962,7 @@ static void swiotlb_bounce(struct device *dev, phys_addr_t tlb_addr, size_t size
size = alloc_size;
}
+ pfn = PFN_DOWN(orig_addr);
if (PageHighMem(pfn_to_page(pfn))) {
unsigned int offset = orig_addr & ~PAGE_MASK;
struct page *page;
@@ -1042,7 +1111,8 @@ static int swiotlb_search_pool_area(struct device *dev, struct io_tlb_pool *pool
unsigned long max_slots = get_max_slots(boundary_mask);
unsigned int iotlb_align_mask = dma_get_min_align_mask(dev);
unsigned int nslots = nr_slots(alloc_size), stride;
- unsigned int offset = swiotlb_align_offset(dev, 0, orig_addr);
+ unsigned long max_usable = pool->area_nslabs;
+ unsigned int offset;
unsigned int index, slots_checked, count = 0, i;
unsigned long flags;
unsigned int slot_base;
@@ -1051,6 +1121,13 @@ static int swiotlb_search_pool_area(struct device *dev, struct io_tlb_pool *pool
BUG_ON(!nslots);
BUG_ON(area_index >= pool->nareas);
+ if (IS_SWIOTLB_NOCOPY(orig_addr)) {
+ max_usable = (pool->area_nslabs * NOCOPY_ADDR_TO_PCT(orig_addr)) / 100;
+ orig_addr = 0;
+ }
+
+ offset = swiotlb_align_offset(dev, 0, orig_addr);
+
/*
* Historically, swiotlb allocations >= PAGE_SIZE were guaranteed to be
* page-aligned in the absence of any other alignment requirements.
@@ -1077,7 +1154,7 @@ static int swiotlb_search_pool_area(struct device *dev, struct io_tlb_pool *pool
stride = get_max_slots(max(alloc_align_mask, iotlb_align_mask));
spin_lock_irqsave(&area->lock, flags);
- if (unlikely(nslots > pool->area_nslabs - area->used))
+ if (unlikely(area->used + nslots > max_usable))
goto not_found;
slot_base = area_index * pool->area_nslabs;
@@ -1154,6 +1231,9 @@ static int swiotlb_search_pool_area(struct device *dev, struct io_tlb_pool *pool
* Search one memory area in all pools for a sequence of slots that match the
* allocation constraints.
*
+ * If IS_SWIOTLB_NOCOPY(orig_addr) is true, the search is restricted to only the
+ * default pool, which is what swiotlb_alloc_pages() is allowed to use.
+ *
* Return: Index of the first allocated slot, or -1 on error.
*/
static int swiotlb_search_area(struct device *dev, int start_cpu,
@@ -1167,6 +1247,9 @@ static int swiotlb_search_area(struct device *dev, int start_cpu,
rcu_read_lock();
list_for_each_entry_rcu(pool, &mem->pools, node) {
+ /* Only search the default pool (first in mem->pools) for nocopy allocations. */
+ if (IS_SWIOTLB_NOCOPY(orig_addr) && pool != &mem->defpool)
+ break;
if (cpu_offset >= pool->nareas)
continue;
area_index = (start_cpu + cpu_offset) & (pool->nareas - 1);
@@ -1219,6 +1302,13 @@ static int swiotlb_find_slots(struct device *dev, phys_addr_t orig_addr,
goto found;
}
+ /*
+ * Passing a nocopy orig_addr restricts the search to only the
+ * default pool, so do not attempt dynamic pool expansion.
+ */
+ if (IS_SWIOTLB_NOCOPY(orig_addr))
+ return -1;
+
if (!mem->can_grow)
return -1;
@@ -1458,11 +1548,16 @@ phys_addr_t swiotlb_tbl_map_single(struct device *dev, phys_addr_t orig_addr,
return tlb_addr;
}
+/*
+ * called with dev == NULL from swiotlb_dealloc_pages(), in this case force offset
+ * and align_mask to 0, pad_slots is also 0, and assume the pages come from the
+ * default system pool.
+ */
static void swiotlb_release_slots(struct device *dev, phys_addr_t tlb_addr,
struct io_tlb_pool *mem)
{
unsigned long flags;
- unsigned int offset = swiotlb_align_offset(dev, 0, tlb_addr);
+ unsigned int offset = dev ? swiotlb_align_offset(dev, 0, tlb_addr) : 0;
int index, nslots, aindex;
struct io_tlb_area *area;
int count, i;
@@ -1496,6 +1591,7 @@ static void swiotlb_release_slots(struct device *dev, phys_addr_t tlb_addr,
mem->slots[i].orig_addr = INVALID_PHYS_ADDR;
mem->slots[i].alloc_size = 0;
mem->slots[i].pad_slots = 0;
+ mem->slots[i].flags = 0;
}
/*
@@ -1509,7 +1605,7 @@ static void swiotlb_release_slots(struct device *dev, phys_addr_t tlb_addr,
area->used -= nslots;
spin_unlock_irqrestore(&area->lock, flags);
- dec_used(dev->dma_io_tlb_mem, nslots);
+ dec_used(dev ? dev->dma_io_tlb_mem : &io_tlb_default_mem, nslots);
}
#ifdef CONFIG_SWIOTLB_DYNAMIC
@@ -1554,6 +1650,13 @@ void __swiotlb_tbl_unmap_single(struct device *dev, phys_addr_t tlb_addr,
size_t mapping_size, enum dma_data_direction dir,
unsigned long attrs, struct io_tlb_pool *pool)
{
+ int index = (tlb_addr - pool->start) >> IO_TLB_SHIFT;
+
+ if (pool->slots[index].flags & SWIOTLB_SLOT_NOCOPY) {
+ swiotlb_nocopy_dec_ref(pool, tlb_addr);
+ return;
+ }
+
/*
* First, sync the memory before unmapping the entry
*/
@@ -1597,6 +1700,8 @@ dma_addr_t swiotlb_map(struct device *dev, phys_addr_t paddr, size_t size,
phys_addr_t swiotlb_addr;
dma_addr_t dma_addr;
+ dma_learn_bounce_device(dev);
+
trace_swiotlb_bounced(dev, phys_to_dma(dev, paddr), size);
swiotlb_addr = swiotlb_tbl_map_single(dev, paddr, size, 0, dir, attrs);
@@ -1813,7 +1918,10 @@ static int rmem_swiotlb_device_init(struct reserved_mem *rmem,
struct device *dev)
{
struct io_tlb_mem *mem = rmem->priv;
- unsigned long nslabs = rmem->size >> IO_TLB_SHIFT;
+ unsigned long nslabs = round_down(rmem->size >> IO_TLB_SHIFT, IO_TLB_SEGSIZE);
+
+ if (!nslabs)
+ return -EINVAL;
/* Set Per-device io tlb area to one */
unsigned int nareas = 1;
@@ -1899,3 +2007,171 @@ static const struct reserved_mem_ops rmem_swiotlb_ops = {
RESERVEDMEM_OF_DECLARE(dma, "restricted-dma-pool", &rmem_swiotlb_ops);
#endif /* CONFIG_DMA_RESTRICTED_POOL */
+
+static inline int swiotlb_nocopy_head_index(struct io_tlb_pool *pool, phys_addr_t phys)
+{
+ return (page_to_phys(compound_head(phys_to_page(phys))) - pool->start) >> IO_TLB_SHIFT;
+}
+
+/**
+ * swiotlb_dealloc_pages() - Actually release Nocopy slots and page metadata
+ * @pool: SWIOTLB pool containing the buffer.
+ * @parent: Slot index of the buffer head.
+ */
+static void swiotlb_dealloc_pages(struct io_tlb_pool *pool, unsigned int parent)
+{
+ unsigned int order = get_order(pool->slots[parent].alloc_size);
+ phys_addr_t paddr = pool->start + (parent << IO_TLB_SHIFT);
+ struct page *head = phys_to_page(paddr);
+
+ swiotlb_destroy_compound_page(head, order);
+ swiotlb_release_slots(NULL, paddr, pool);
+}
+
+struct page *swiotlb_alloc_pages(struct device *dev, unsigned int order,
+ gfp_t gfp, unsigned int percent)
+{
+ struct io_tlb_pool *pool;
+ struct page *page;
+ int index, nslots, i;
+
+ if (WARN_ON_ONCE(!dev || !dev->dma_io_tlb_mem))
+ return NULL;
+
+ if (dev->dma_io_tlb_mem != &io_tlb_default_mem)
+ return NULL;
+
+ index = swiotlb_find_slots(dev, NOCOPY_PCT_TO_ADDR(percent),
+ PAGE_SIZE << order, (PAGE_SIZE << order) - 1,
+ &pool);
+ if (index < 0)
+ return NULL;
+
+ nslots = (PAGE_SIZE << order) >> IO_TLB_SHIFT;
+ page = phys_to_page(pool->start + (index << IO_TLB_SHIFT));
+ swiotlb_prep_compound_page(page, order);
+ for (i = 0; i < nslots; i++)
+ pool->slots[index + i].flags |= SWIOTLB_SLOT_NOCOPY;
+ atomic_set(&pool->slots[index].nocopy_refcnt, 1);
+ return page;
+}
+EXPORT_SYMBOL(swiotlb_alloc_pages);
+
+bool swiotlb_free_pages(struct page *page, unsigned int order)
+{
+ struct io_tlb_mem *mem = &io_tlb_default_mem;
+ struct io_tlb_pool *pool = &mem->defpool;
+ struct page *head = compound_head(page);
+ unsigned int parent;
+ phys_addr_t paddr;
+
+ paddr = page_to_phys(head);
+ if (paddr < pool->start || paddr >= pool->end)
+ return false;
+
+ parent = swiotlb_nocopy_head_index(pool, paddr);
+ if (!(pool->slots[parent].flags & SWIOTLB_SLOT_NOCOPY))
+ return false;
+
+ if (atomic_dec_and_test(&pool->slots[parent].nocopy_refcnt))
+ swiotlb_dealloc_pages(pool, parent);
+
+ return true;
+}
+EXPORT_SYMBOL(swiotlb_free_pages);
+
+void swiotlb_nocopy_inc_ref(struct io_tlb_pool *pool, phys_addr_t phys)
+{
+ int head_idx = swiotlb_nocopy_head_index(pool, phys);
+
+ atomic_inc(&pool->slots[head_idx].nocopy_refcnt);
+}
+EXPORT_SYMBOL(swiotlb_nocopy_inc_ref);
+
+void swiotlb_nocopy_dec_ref(struct io_tlb_pool *pool, phys_addr_t phys)
+{
+ int head_idx = swiotlb_nocopy_head_index(pool, phys);
+
+ if (atomic_dec_and_test(&pool->slots[head_idx].nocopy_refcnt))
+ swiotlb_dealloc_pages(pool, head_idx);
+}
+EXPORT_SYMBOL(swiotlb_nocopy_dec_ref);
+
+bool swiotlb_pool_is_nocopy(struct io_tlb_pool *pool, phys_addr_t paddr)
+{
+ int index = (paddr - pool->start) >> IO_TLB_SHIFT;
+
+ return pool->slots[index].flags & SWIOTLB_SLOT_NOCOPY;
+}
+EXPORT_SYMBOL_GPL(swiotlb_pool_is_nocopy);
+
+/*
+ * Dropping the reference to sk_swiotlb.dev must be done in two steps:
+ *
+ * 1. Readers inspect the pointer inside RCU critical sections without
+ * acquiring a reference. Use call_rcu() to wait for an RCU grace period
+ * to elapse so lockless in-flight readers finish accessing the device.
+ *
+ * 2. The RCU callback executes in atomic softirq context, but put_device()
+ * can block when releasing a device. Use schedule_work() to transition
+ * to sleepable process context where calling put_device() is safe.
+ */
+struct swiotlb_deferred_put {
+ struct rcu_head rcu;
+ struct work_struct work;
+ struct device *dev;
+};
+
+static void swiotlb_deferred_put_work(struct work_struct *work)
+{
+ struct swiotlb_deferred_put *dp = container_of(work, struct swiotlb_deferred_put, work);
+
+ /* Stage 2: Safely call put_device (can sleep) in process context */
+ put_device(dp->dev);
+ kfree(dp);
+}
+
+static void swiotlb_deferred_put_rcu(struct rcu_head *rcu)
+{
+ struct swiotlb_deferred_put *dp = container_of(rcu, struct swiotlb_deferred_put, rcu);
+
+ /* RCU grace period has passed. Queue the work to do the actual put */
+ schedule_work(&dp->work);
+}
+
+/**
+ * swiotlb_safe_put_device() - Safely release device reference from atomic/interrupt context
+ * @dev: The device structure to release.
+ *
+ * Enqueues a deferred put_device() call on a workqueue using GFP_ATOMIC.
+ * If memory allocation fails, the reference is leaked to avoid an immediate crash.
+ */
+void swiotlb_safe_put_device(struct device *dev)
+{
+ struct swiotlb_deferred_put *dp;
+
+ if (!dev)
+ return;
+
+ /* Lockless fast-path: if we are not the last reference, decrement is safe */
+ if (refcount_dec_not_one(&dev->kobj.kref.refcount))
+ return;
+
+ /*
+ * On the last reference we must defer the final put_device() to task
+ * context because it will trigger device_release() which can sleep.
+ */
+ dp = kmalloc_obj(*dp, GFP_ATOMIC);
+ if (dp) {
+ INIT_WORK(&dp->work, swiotlb_deferred_put_work);
+ dp->dev = dev;
+ /* Stage 1: Wait for RCU readers to finish */
+ call_rcu(&dp->rcu, swiotlb_deferred_put_rcu);
+ } else {
+ pr_warn_ratelimited("swiotlb: failed to allocate deferred put, leaking device ref\n");
+ }
+}
+EXPORT_SYMBOL_GPL(swiotlb_safe_put_device);
+
+atomic_t global_device_epoch = ATOMIC_INIT(1);
+EXPORT_SYMBOL(global_device_epoch);
diff --git a/mm/page_alloc.c b/mm/page_alloc.c
index 083cbcb5bddec..32d5d630f9840 100644
--- a/mm/page_alloc.c
+++ b/mm/page_alloc.c
@@ -16,6 +16,7 @@
#include <linux/stddef.h>
#include <linux/mm.h>
+#include <linux/swiotlb.h>
#include <linux/highmem.h>
#include <linux/interrupt.h>
#include <linux/jiffies.h>
@@ -711,6 +712,56 @@ void prep_compound_page(struct page *page, unsigned int order)
prep_compound_head(page, order);
}
+#ifdef CONFIG_SWIOTLB
+/*
+ * Prepare a SWIOTLB page (potentially compound).
+ *
+ * We explicitly initialize the head page refcount to 1 because recycled
+ * SWIOTLB pages might have a refcount of 0.
+ *
+ * If order > 0 (compound page), we must explicitly set all tail page
+ * refcounts to 0. This is because SWIOTLB pages might have a boot-default
+ * refcount of 1, but the core memory management subsystem expects tail pages
+ * of a compound page to have a refcount of 0.
+ */
+void swiotlb_prep_compound_page(struct page *page, unsigned int order)
+{
+ init_page_count(page);
+ if (order > 0) {
+ for (int i = 1; i < (1 << order); i++)
+ set_page_count(page + i, 0);
+ prep_compound_page(page, order);
+ }
+}
+
+/*
+ * Destroy a SWIOTLB compound page and restore page refcounts.
+ *
+ * When pages are returned to the SWIOTLB pool, we restore the refcount of
+ * all constituent pages (head and tails) to 1. This resets them to their
+ * clean boot-default state, ensuring they are ready for reuse either as
+ * individual order-0 pages or as part of a new compound allocation.
+ */
+void swiotlb_destroy_compound_page(struct page *page, unsigned int order)
+{
+ if (order > 0) {
+ struct folio *folio = (struct folio *)page;
+
+ __ClearPageHead(page);
+ page[1].flags.f &= ~PAGE_FLAGS_SECOND;
+#ifdef NR_PAGES_IN_LARGE_FOLIO
+ folio->_nr_pages = 0;
+#endif
+ for (int i = 1; i < (1 << order); i++) {
+ page[i].mapping = NULL;
+ clear_compound_head(&page[i]);
+ set_page_count(page + i, 1);
+ }
+ }
+ set_page_count(page, 1);
+}
+#endif /* CONFIG_SWIOTLB */
+
static inline void set_buddy_order(struct page *page, unsigned int order)
{
set_page_private(page, order);
@@ -2951,9 +3002,14 @@ static void __free_frozen_pages(struct page *page, unsigned int order,
{
struct per_cpu_pages *pcp;
struct zone *zone;
- unsigned long pfn = page_to_pfn(page);
+ unsigned long pfn;
int migratetype;
+ if (unlikely(swiotlb_free_pages(page, order)))
+ return;
+
+ pfn = page_to_pfn(page);
+
if (!pcp_allowed_order(order)) {
__free_pages_ok(page, order, fpi_flags);
return;
@@ -3019,6 +3075,9 @@ void free_unref_folios(struct folio_batch *folios)
unsigned long pfn = folio_pfn(folio);
unsigned int order = folio_order(folio);
+ if (unlikely(swiotlb_free_pages(&folio->page, order)))
+ continue;
+
if (!__free_pages_prepare(&folio->page, order, FPI_NONE))
continue;
/*
diff --git a/net/core/page_pool.c b/net/core/page_pool.c
index 50ee550fef73a..fe8839a7c70a8 100644
--- a/net/core/page_pool.c
+++ b/net/core/page_pool.c
@@ -19,6 +19,7 @@
#include <linux/dma-direction.h>
#include <linux/dma-mapping.h>
+#include <linux/swiotlb.h>
#include <linux/page-flags.h>
#include <linux/mm.h> /* for put_page() */
#include <linux/poison.h>
@@ -578,10 +579,16 @@ static bool page_pool_dma_map(struct page_pool *pool, netmem_ref netmem, gfp_t g
static struct page *__page_pool_alloc_page_order(struct page_pool *pool,
gfp_t gfp)
{
+ unsigned int pct = READ_ONCE(nocopy_rx_percent);
struct page *page;
gfp |= __GFP_COMP;
- page = alloc_pages_node(pool->p.nid, gfp, pool->p.order);
+ page = NULL;
+ if (pct && is_swiotlb_active(pool->p.dev))
+ page = swiotlb_alloc_pages(pool->p.dev, pool->p.order, gfp,
+ pct);
+ if (!page)
+ page = alloc_pages_node(pool->p.nid, gfp, pool->p.order);
if (unlikely(!page))
return NULL;
@@ -616,8 +623,9 @@ static noinline netmem_ref __page_pool_alloc_netmems_slow(struct page_pool *pool
if ((gfp & GFP_ATOMIC) == GFP_ATOMIC)
gfp |= __GFP_NOWARN;
- /* Don't support bulk alloc for high-order pages */
- if (unlikely(pp_order))
+ /* Don't support bulk alloc for high-order pages or nocopy SWIOTLB */
+ if (unlikely(pp_order || (READ_ONCE(nocopy_rx_percent) &&
+ is_swiotlb_active(pool->p.dev))))
return page_to_netmem(__page_pool_alloc_page_order(pool, gfp));
/* Unnecessary as alloc cache is empty, but guarantees zero count */
@@ -835,6 +843,17 @@ __page_pool_put_page(struct page_pool *pool, netmem_ref netmem,
{
lockdep_assert_no_hardirq();
+ /*
+ * If runtime nocopy mode toggled, evict circulating buffers immediately
+ * back to their respective allocators rather than recycling them.
+ */
+ if (unlikely(!netmem_is_net_iov(netmem) &&
+ swiotlb_is_nocopy_addr(pool->p.dev, page_to_phys(netmem_to_page(netmem))) !=
+ (READ_ONCE(nocopy_rx_percent) > 0))) {
+ page_pool_return_netmem(pool, netmem);
+ return 0;
+ }
+
/* This allocator is optimized for the XDP mode that uses
* one-frame-per-page, but have fallbacks that act like the
* regular page allocator APIs.
diff --git a/net/core/sock.c b/net/core/sock.c
index 1ad41904db25b..ef40d1ff1de9f 100644
--- a/net/core/sock.c
+++ b/net/core/sock.c
@@ -103,6 +103,8 @@
#include <linux/sockios.h>
#include <linux/net.h>
#include <linux/mm.h>
+#include <linux/swiotlb.h>
+#include <linux/device.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/poll.h>
@@ -152,6 +154,87 @@
#include "dev.h"
+#if defined(CONFIG_SWIOTLB) && !defined(CONFIG_PREEMPT_RT)
+
+DEFINE_PER_CPU(struct sock *, current_tx_socket);
+EXPORT_PER_CPU_SYMBOL(current_tx_socket);
+
+void sk_record_bounce_device(struct sock *sk, struct device *dev)
+{
+ struct device *old_dev;
+
+ if (in_hardirq() || !sk_fullsock(sk) || sock_flag(sk, SOCK_ZEROCOPY))
+ return;
+
+ old_dev = rcu_dereference_protected(sk->sk_swiotlb.dev, 1);
+
+ if (dev != old_dev) {
+ /* Rate-limit updates to once per second to prevent bonding thrashing */
+ if (old_dev && time_before(jiffies, sk->sk_swiotlb.jiffies + HZ))
+ return;
+
+ get_device(dev);
+
+ /* Atomically swap in the new device and get the actual old one */
+ old_dev = (struct device *)xchg((struct device __force **)&sk->sk_swiotlb.dev,
+ (struct device __force *)dev);
+
+ WRITE_ONCE(sk->sk_swiotlb.epoch, swiotlb_dev_epoch());
+ sk->sk_swiotlb.jiffies = jiffies;
+
+ /* Only drop the reference to the device we actually replaced */
+ if (old_dev)
+ swiotlb_safe_put_device(old_dev);
+ }
+}
+EXPORT_SYMBOL(sk_record_bounce_device);
+
+/*
+ * Wrap alloc_pages in __skb_page_frag_refill(). If the socket's dma_device requires
+ * SWIOTLB bounce buffering, divert allocation to the SWIOTLB slot allocator.
+ * This ensures the packet payload is written directly to a bounce buffer from the start,
+ * enabling nocopy during driver DMA mapping.
+ */
+static inline struct page *alloc_any_pg(gfp_t gfp, unsigned int order, struct sock *sk)
+{
+ unsigned int pct = READ_ONCE(nocopy_tx_percent);
+
+ if (sk && pct && !sock_flag(sk, SOCK_ZEROCOPY)) {
+ struct page *page = NULL;
+ bool release_dev = false;
+ struct device *dev;
+
+ rcu_read_lock();
+ dev = rcu_dereference(sk->sk_swiotlb.dev);
+ if (dev) {
+ /*
+ * The epoch check is just for cache invalidation, UAF is
+ * protected by the reference held in the sk.
+ */
+ if (swiotlb_dev_epoch() != READ_ONCE(sk->sk_swiotlb.epoch)) {
+ struct device __force **pdev =
+ (struct device __force **)&sk->sk_swiotlb.dev;
+
+ release_dev = (cmpxchg(pdev, (struct device __force *)dev,
+ NULL) == dev);
+ } else {
+ page = swiotlb_alloc_pages(dev, order, gfp, pct);
+ }
+ }
+ rcu_read_unlock();
+ if (release_dev)
+ swiotlb_safe_put_device(dev);
+ if (page)
+ return page;
+ }
+ return alloc_pages(gfp, order);
+}
+#else
+static inline struct page *alloc_any_pg(gfp_t gfp, unsigned int order, struct sock *sk)
+{
+ return alloc_pages(gfp, order);
+}
+#endif
static DEFINE_MUTEX(proto_list_mutex);
static LIST_HEAD(proto_list);
@@ -2387,6 +2470,7 @@ static void __sk_destruct(struct rcu_head *head)
__netns_tracker_free(net, &sk->ns_tracker, false);
net_passive_dec(net);
}
+ sk_release_bounce_device(sk);
sk_prot_free(sk->sk_prot_creator, sk);
}
@@ -2489,6 +2573,7 @@ struct sock *sk_clone(const struct sock *sk, const gfp_t priority,
goto out;
sock_copy(newsk, sk);
+ sk_clear_bounce_device(newsk);
newsk->sk_prot_creator = prot;
#ifdef CONFIG_BPF_SYSCALL
@@ -3174,7 +3259,7 @@ DEFINE_STATIC_KEY_FALSE(net_high_order_alloc_disable_key);
* no guarantee that allocations succeed. Therefore, @sz MUST be
* less or equal than PAGE_SIZE.
*/
-bool skb_page_frag_refill(unsigned int sz, struct page_frag *pfrag, gfp_t gfp)
+bool __skb_page_frag_refill(unsigned int sz, struct page_frag *pfrag, gfp_t gfp, struct sock *sk)
{
if (pfrag->page) {
if (page_ref_count(pfrag->page) == 1) {
@@ -3190,27 +3275,27 @@ bool skb_page_frag_refill(unsigned int sz, struct page_frag *pfrag, gfp_t gfp)
if (SKB_FRAG_PAGE_ORDER &&
!static_branch_unlikely(&net_high_order_alloc_disable_key)) {
/* Avoid direct reclaim but allow kswapd to wake */
- pfrag->page = alloc_pages((gfp & ~__GFP_DIRECT_RECLAIM) |
- __GFP_COMP | __GFP_NOWARN |
- __GFP_NORETRY,
- SKB_FRAG_PAGE_ORDER);
+ pfrag->page = alloc_any_pg((gfp & ~__GFP_DIRECT_RECLAIM) |
+ __GFP_COMP | __GFP_NOWARN |
+ __GFP_NORETRY,
+ SKB_FRAG_PAGE_ORDER, sk);
if (likely(pfrag->page)) {
pfrag->size = PAGE_SIZE << SKB_FRAG_PAGE_ORDER;
return true;
}
}
- pfrag->page = alloc_page(gfp);
+ pfrag->page = alloc_any_pg(gfp, 0, sk);
if (likely(pfrag->page)) {
pfrag->size = PAGE_SIZE;
return true;
}
return false;
}
-EXPORT_SYMBOL(skb_page_frag_refill);
+EXPORT_SYMBOL(__skb_page_frag_refill);
bool sk_page_frag_refill(struct sock *sk, struct page_frag *pfrag)
{
- if (likely(skb_page_frag_refill(32U, pfrag, sk->sk_allocation)))
+ if (likely(__skb_page_frag_refill(32U, pfrag, sk->sk_allocation, sk)))
return true;
if (!sk->sk_bypass_prot_mem)
|
| 13/3 | 2026/08/24 18:38 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 15870 output: 33 thoughts: 1176 Thoughts: **KMSAN Fuzzing Session Justification**
Okay, so this patch series introduces a "nocopy swiotlb" optimization. My initial thought is to determine if this warrants a dedicated KMSAN fuzzing session. The patch touches the `sock` structure, introducing `sk_swiotlb_info`, modifies `swiotlb` to track nocopy buffers, and changes `skb_page_frag_refill` to directly allocate pages from the SWIOTLB if the socket is known to bounce.
First, I need to understand potential uninitialized memory risks. It looks like the core modification lies in `swiotlb_alloc_pages` and `swiotlb_free_pages`. The addition of `sk_swiotlb_info` to `struct sock` raises some immediate questions. This struct contains a `dev`, `epoch`, and `jiffies` field.
`sk_swiotlb_info` is initialized in `sk_clear_bounce_device`, which is called within `sk_clone`. Hmm, but what about the initial allocation in `sk_alloc`? Does it ensure this new struct is zeroed? No, it looks like `sk_alloc` may zero out an allocation for the whole `struct sock` but it depends on the allocation path. In this case `sock_copy` copies the whole struct, and `sk_clear_bounce_device` zeroes the `dev` field. The `epoch` and `jiffies` fields are copied. Are we leaking uninitialized memory to userspace?
The key area for inspection is `swiotlb_alloc_pages`, because it returns a page. The code snippet shows that the page is *not* zeroed. It appears to just return the physical page. The caller, in `alloc_any_pg`, is using the result as a replacement for `alloc_pages`. `alloc_pages` doesn't zero the page unless `__GFP_ZERO` is explicitly used.
Crucially, `alloc_any_pg` receives the `gfp` flags, which determine how the page is allocated. This function can call `swiotlb_alloc_pages`.
```c
if (sk && pct && !sock_flag(sk, SOCK_ZEROCOPY)) {
...
} else {
page = swiotlb_alloc_pages(dev, order, gfp, pct);
}
```
However, if `gfp` *does* contain `__GFP_ZERO`, `swiotlb_alloc_pages` seems to silently ignore it.
Okay, so let's see if `__GFP_ZERO` is ever passed into `alloc_any_pg`. Looking at the callers, it's called from `__skb_page_frag_refill`. The `gfp` flags originate from `sk->sk_allocation` in `sk_page_frag_refill`. Does `sk->sk_allocation` typically have `__GFP_ZERO` set? I don't think so.
Now, wait a moment, what about `__page_pool_alloc_page_order`?
```c
static struct page *__page_pool_alloc_page_order(struct page_pool *pool,
gfp_t gfp)
{
unsigned int pct = READ_ONCE(nocopy_rx_percent);
struct page *page;
gfp |= __GFP_COMP;
page = NULL;
if (pct && is_swiotlb_active(pool->p.dev))
page = swiotlb_alloc_pages(pool->p.dev, pool->p.order, gfp,
pct);
if (!page)
page = alloc_pages_node(pool->p.nid, gfp, pool->p.order);
```
If `gfp` contains `__GFP_ZERO`, `swiotlb_alloc_pages` still ignores it. This calls for a quick check. Let's grep for `__GFP_ZERO` in `net/core/page_pool.c` to be certain.
So, in summary, I need to check the cases in which the page is allocated within the kernel, and determine if uninitialized data can be returned to userspace. In particular, any code path using `swiotlb_alloc_pages` and passing in the `gfp` flag from an external location needs closer inspection. This patch warrants a deeper dive and likely justifies a targeted KMSAN session to verify memory initialization and potential information leaks.
|
| 14/3 | 2026/08/24 18:38 | tool | grepper |
0mError:no matches Args: {
"Expression": "__GFP_ZERO",
"PathPrefix": "net/core/page_pool.c"
}Results: {
"error": "no matches"
} |
| 15/3 | 2026/08/24 18:38 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 18081 output: 32 thoughts: 42 Thoughts: Okay, here's my take on those thoughts, framed as my own: **Investigating Memory Allocation and Zeroing** Alright, I'm digging into how this `page_pool` thing works, and something just jumped out at me. It looks like `page_pool` *doesn't* leverage `__GFP_ZERO` for its allocations. That's immediately interesting, and raises a few questions. The obvious one is, why not? Are they relying on something else to ensure a zeroed page, or are they assuming the data isn't sensitive? Knowing the kernel, I'd want to understand that rationale. That then got me thinking about other related routines in this area. Specifically, what's going on with `skb_page_frag_refill`? Does *it* use `__GFP_ZERO`? It’s a key part of SKB (Socket Buffer) management, so its memory allocation strategy is critical for performance and security. I need to check the code and see if there's consistency in the use of zeroing flags across these memory allocation strategies within the networking subsystem. The choice has implications for performance, security, and potential bugs, so I need to explore this further. |
| 16/3 | 2026/08/24 18:38 | tool | grepper |
0mArgs:{
"Expression": "__GFP_ZERO",
"PathPrefix": "net/core/skbuff.c"
}Results: {
"Output": "net/core/skbuff.c=316=static inline void skbuff_clear(struct sk_buff *skb)\n--\nnet/core/skbuff.c-336- * the MM layer if needed.\nnet/core/skbuff.c:337: * The heads are being zeroed with either memset() or %__GFP_ZERO, so they are\nnet/core/skbuff.c-338- * ready for {,__}build_skb_around() and don't have any data buffers attached.\n--\nnet/core/skbuff.c=344=u32 napi_skb_cache_get_bulk(void **skbs, u32 n)\n--\nnet/core/skbuff.c-364-\tif (kmem_cache_alloc_bulk(net_hotdata.skbuff_cache,\nnet/core/skbuff.c:365:\t\t\t\t GFP_ATOMIC | __GFP_ZERO | __GFP_NOWARN,\nnet/core/skbuff.c-366-\t\t\t\t n - nc-\u003eskb_count, \u0026skbs[nc-\u003eskb_count]))\n"
} |
| 17/3 | 2026/08/24 18:38 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 18696 output: 32 thoughts: 466 Thoughts: **KMSAN and `swiotlb_alloc_pages` Concerns** Okay, so I'm digging into `skb_page_frag_refill` and how it's used for allocating page fragments for SKB data. The network stack and drivers write to these fragments before they're transmitted or passed to userspace. My initial thought is, is there an uninitialized memory leak lurking here? Specifically, if `swiotlb_alloc_pages` returns uninitialized memory, and this memory is then used for RX buffers via `page_pool`, the driver performs a DMA write into it, and then the network stack reads it, this would be a problem. The driver only writes the received packet length, so if the network stack reads beyond that point, it becomes an out-of-bounds read, and KASAN should be able to flag it, right? Hold on, KMSAN catches reads from uninitialized memory, right? If the page *is* uninitialized and the driver only writes to a portion of it, then the rest will also remain uninitialized. If the network stack reads it, KMSAN *should* catch it. But is the expectation for that memory to be zeroed? Thinking about it, normally, `alloc_pages` hands back uninitialized memory unless `__GFP_ZERO` is specified. So, `swiotlb_alloc_pages` returning uninitialized memory is conceptually the same as `alloc_pages` returning uninitialized memory. Aha, but here's where it gets interesting. `swiotlb_alloc_pages` allocates from the SWIOTLB pool. This pool is allocated at boot time and might have been used for bouncing DMAs from other devices. So, it's very likely that the pages within the SWIOTLB pool actually contain data from other devices. Similarly, `alloc_pages` may contain data from previous allocations. Both approaches provide uninitialized memory. So, is there any real difference? The key is KMSAN tracking uninitialized memory. If we're using `swiotlb_alloc_pages`, does KMSAN even *know* about it? KMSAN hooks into the page allocator (`alloc_pages`), unpoisoning pages if `__GFP_ZERO` is set, or poisoning them (marking as uninitialized) otherwise. But wait a minute! `swiotlb_alloc_pages` *doesn't* go through the page allocator at all! It just hands back a page from the pre-allocated SWIOTLB pool. This is a problem. I need to figure out if KMSAN is actually aware of and tracking memory allocated by `swiotlb_alloc_pages`. |
| 18/3 | 2026/08/24 18:38 | tool | grepper |
0mArgs:{
"Expression": "kmsan",
"PathPrefix": "kernel/dma/swiotlb.c"
}Results: {
"Output": "kernel/dma/swiotlb.c-32-#include \u003clinux/io.h\u003e\nkernel/dma/swiotlb.c:33:#include \u003clinux/kmsan-checks.h\u003e\nkernel/dma/swiotlb.c-34-#include \u003clinux/iommu-helper.h\u003e\n--\nkernel/dma/swiotlb.c=921=static void swiotlb_bounce(struct device *dev, phys_addr_t tlb_addr, size_t size,\n--\nkernel/dma/swiotlb.c-978-\t\t\t\t/*\nkernel/dma/swiotlb.c:979:\t\t\t\t * Ideally, kmsan_check_highmem_page()\nkernel/dma/swiotlb.c-980-\t\t\t\t * could be used here to detect infoleaks,\n--\nkernel/dma/swiotlb.c-986-\t\t\t} else {\nkernel/dma/swiotlb.c:987:\t\t\t\tkmsan_unpoison_memory(vaddr, sz);\nkernel/dma/swiotlb.c-988-\t\t\t\tmemcpy_to_page(page, offset, vaddr, sz);\n--\nkernel/dma/swiotlb.c-998-\t\t/*\nkernel/dma/swiotlb.c:999:\t\t * Ideally, kmsan_check_memory() could be used here to detect\nkernel/dma/swiotlb.c-1000-\t\t * infoleaks (uninitialized data being sent to device), but\n--\nkernel/dma/swiotlb.c-1005-\t} else {\nkernel/dma/swiotlb.c:1006:\t\tkmsan_unpoison_memory(vaddr, size);\nkernel/dma/swiotlb.c-1007-\t\tmemcpy(phys_to_virt(orig_addr), vaddr, size);\n"
} |
| 19/3 | 2026/08/24 18:38 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 20226 output: 42 thoughts: 279 Thoughts: **My Initial Concern Regarding KMSAN and SWIOTLB Allocation** Okay, so I'm looking at `swiotlb_alloc_pages` and immediately something jumps out at me. This function, unlike a standard `alloc_pages` call, doesn't seem to interact with KMSAN at all. It just grabs a page from the SWIOTLB pool and returns it. Specifically, it uses `phys_to_page` to map a physical address within the pool to a `struct page` pointer. It then flags some slots within that page as `SWIOTLB_SLOT_NOCOPY`, and sets a reference count. But it completely bypasses the KMSAN mechanisms. This is a problem! Normally, `alloc_pages` triggers KMSAN to poison (mark as uninitialized) the newly allocated memory, ensuring that any uninitialized reads are caught. But here, that poisoning step *isn't* happening. If the allocated memory isn't poisoned and someone reads from it before it's initialized, we're likely to have a security or reliability issue that KMSAN would normally catch. A thought just occurred: the SWIOTLB pool itself is allocated at boot time. So... does KMSAN poison that memory initially? Or does it *unpoison* it, considering it might be used for DMA later on? That could be an important detail here. I need to dig deeper and see exactly how this SWIOTLB pool is initialized to fully understand the KMSAN implications. The initialization code is where the key to this puzzle lies. |
| 20/3 | 2026/08/24 18:39 | tool | grepper |
0mArgs:{
"Expression": "swiotlb_init_io_tlb_pool",
"PathPrefix": "kernel/dma/swiotlb.c"
}Results: {
"Output": "kernel/dma/swiotlb.c=310=void __init swiotlb_update_mem_attributes(void)\n--\nkernel/dma/swiotlb.c-320-\nkernel/dma/swiotlb.c:321:static void swiotlb_init_io_tlb_pool(struct io_tlb_pool *mem, phys_addr_t start,\nkernel/dma/swiotlb.c-322-\t\tunsigned long nslabs, bool late_alloc, unsigned int nareas)\n--\nkernel/dma/swiotlb.c=416=void __init swiotlb_init_remap(bool addressing_limit, unsigned int flags,\n--\nkernel/dma/swiotlb.c-474-\nkernel/dma/swiotlb.c:475:\tswiotlb_init_io_tlb_pool(mem, __pa(tlb), nslabs, false, nareas);\nkernel/dma/swiotlb.c-476-\tadd_mem_pool(\u0026io_tlb_default_mem, mem);\n--\nkernel/dma/swiotlb.c=492=int swiotlb_init_late(size_t size, gfp_t gfp_mask,\n--\nkernel/dma/swiotlb.c-572-\t\t\t (nslabs \u003c\u003c IO_TLB_SHIFT) \u003e\u003e PAGE_SHIFT);\nkernel/dma/swiotlb.c:573:\tswiotlb_init_io_tlb_pool(mem, virt_to_phys(vstart), nslabs, true,\nkernel/dma/swiotlb.c-574-\t\t\t\t nareas);\n--\nkernel/dma/swiotlb.c=745=static struct io_tlb_pool *swiotlb_alloc_pool(struct device *dev,\n--\nkernel/dma/swiotlb.c-780-\nkernel/dma/swiotlb.c:781:\tswiotlb_init_io_tlb_pool(pool, page_to_phys(tlb), nslabs, true, nareas);\nkernel/dma/swiotlb.c-782-\treturn pool;\n--\nkernel/dma/swiotlb.c=1917=static int rmem_swiotlb_device_init(struct reserved_mem *rmem,\n--\nkernel/dma/swiotlb.c-1961-\t\t\t\t rmem-\u003esize \u003e\u003e PAGE_SHIFT);\nkernel/dma/swiotlb.c:1962:\t\tswiotlb_init_io_tlb_pool(pool, rmem-\u003ebase, nslabs,\nkernel/dma/swiotlb.c-1963-\t\t\t\t\t false, nareas);\n"
} |
| 21/3 | 2026/08/24 18:39 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 21557 output: 34 thoughts: 21 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience: **Diving into swiotlb_init_io_tlb_pool** Okay, let's take a look at `swiotlb_init_io_tlb_pool`. This function is fundamental, obviously, for managing the software I/O translation lookaside buffer, or SWIOTLB. Given the name, I'm expecting this is where the core structures are initialized that will allow us to handle DMA transfers in systems where direct memory access isn't straightforward, like when we're dealing with memory hole issues or simply needing a bounce buffer. I'm guessing it's setting up the pools of bounce buffers, or perhaps the page tables that map our device's address space to physically contiguous memory. It's likely involved with memory allocation - specifically, contiguous memory allocation from the system. I also need to think about how this interacts with the architecture's MMU or lack thereof, and how it handles different devices with varying DMA capabilities. The performance implications here are huge, and I'll need to consider how well it's designed to minimize the overhead associated with the bounce buffer copies and lookups. I'm keen to see if it makes use of any hardware assist. |
| 22/3 | 2026/08/24 18:39 | tool | grepper |
0mArgs:{
"Expression": "memset.*vaddr",
"PathPrefix": "kernel/dma/swiotlb.c"
}Results: {
"Output": "kernel/dma/swiotlb.c=321=static void swiotlb_init_io_tlb_pool(struct io_tlb_pool *mem, phys_addr_t start,\n--\nkernel/dma/swiotlb.c-357-\nkernel/dma/swiotlb.c:358:\tmemset(vaddr, 0, bytes);\nkernel/dma/swiotlb.c-359-\tmem-\u003evaddr = vaddr;\n"
} |
| 23/3 | 2026/08/24 18:39 | llm | kmsan-evaluator |
6mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 22009 output: 30 thoughts: 1285 Thoughts: **KMSAN's Ineffectiveness with SWIOTLB Pages** Okay, so I'm thinking about how KMSAN interacts with `swiotlb_alloc_pages` and it's starting to look like KMSAN might be *less* effective here, not more. It's a bit of a head-scratcher. At boot, memory in the pool is zeroed, which sounds good. But, when a buffer is bounced, `swiotlb_bounce` copies data to/from it. The problem arises with `swiotlb_alloc_pages`. This function just gives you a page, which might have been used before. If it was used for bouncing, it could contain old data from `swiotlb_bounce`. Here's the rub: KMSAN *doesn't* know about `swiotlb_alloc_pages`. It thinks the page is initialized because it was zeroed at boot, and `memcpy` operations within bouncing are also tracked as initialized operations. So, if `swiotlb_alloc_pages` returns a page with old, possibly uninitialized data, KMSAN will falsely consider it initialized. And that's bad. Really bad. If the caller, like the network stack, uses the page as if it's uninitialized (and, let's be honest, that's what we expect from a freshly allocated page), KMSAN *won't* catch any uninitialized memory bugs. It'll just happily let us ship stale data, which isn't the kind of error we want to expose to a user. The key question is whether this patch introduces uninitialized memory risks that *only* KMSAN could detect. But with KMSAN blind to `swiotlb_alloc_pages`, the answer is a resounding no. So, to be clear, KMSAN poisons pages allocated via `alloc_pages`, which makes sense. But `swiotlb_alloc_pages` doesn't get that poisoning, which makes KMSAN think that the memory is initialized. If there's a leak of kernel memory to userspace from one of these pages, KMSAN would detect it *if* the old content was uninitialized. But it won't be detected. When bouncing, `memcpy` copies the shadow state. The network stack typically initializes the parts it uses, but the parts that it doesn't use can contain old, potentially initialized data, so KMSAN won't complain. The problem is that KMSAN only detects *uninitialized* memory leaks, not leaks of stale but previously initialized memory. This is a crucial distinction. We are effectively bypassing KMSAN's poisoning. KMSAN is, in a way, broken in this context. So, if `alloc_any_pg` calls `swiotlb_alloc_pages`, and the page was previously used for a fully initialized packet, KMSAN will think it is initialized. The new caller will expect either uninitialized or zeroed memory. This means that KMSAN is broken. We are at risk of missing real bugs here. The final question is if there are other uninitialized memory risks from the changes. There is the new `sk_swiotlb_info` in `struct sock`. But this does not pose a risk. `sk_alloc` uses `kmem_cache_alloc` with `SLAB_ACCOUNT`. It seems that the socket is zeroed during allocation. |
| 24/3 | 2026/08/24 18:45 | tool | grepper |
0mArgs:{
"Expression": "sk_alloc",
"PathPrefix": "net/core/sock.c"
}Results: {
"Output": "net/core/sock.c=385=void sk_set_memalloc(struct sock *sk)\n--\nnet/core/sock.c-387-\tsock_set_flag(sk, SOCK_MEMALLOC);\nnet/core/sock.c:388:\tsk-\u003esk_allocation |= __GFP_MEMALLOC;\nnet/core/sock.c-389-\tstatic_branch_inc(\u0026memalloc_socks_key);\n--\nnet/core/sock.c=393=void sk_clear_memalloc(struct sock *sk)\n--\nnet/core/sock.c-395-\tsock_reset_flag(sk, SOCK_MEMALLOC);\nnet/core/sock.c:396:\tsk-\u003esk_allocation \u0026= ~__GFP_MEMALLOC;\nnet/core/sock.c-397-\tstatic_branch_dec(\u0026memalloc_socks_key);\n--\nnet/core/sock.c=2320=static struct sock *sk_prot_alloc(struct proto *prot, gfp_t priority,\n--\nnet/core/sock.c-2336-\tif (sk != NULL) {\nnet/core/sock.c:2337:\t\tif (security_sk_alloc(sk, family, priority))\nnet/core/sock.c-2338-\t\t\tgoto out_free;\n--\nnet/core/sock.c=2356=static void sk_prot_free(struct proto *prot, struct sock *sk)\n--\nnet/core/sock.c-2377-/**\nnet/core/sock.c:2378: *\tsk_alloc - All socket objects are allocated here\nnet/core/sock.c-2379- *\t@net: the applicable net namespace\n--\nnet/core/sock.c-2384- */\nnet/core/sock.c:2385:struct sock *sk_alloc(struct net *net, int family, gfp_t priority,\nnet/core/sock.c-2386-\t\t struct proto *prot, int kern)\n--\nnet/core/sock.c-2417-\nnet/core/sock.c:2418:\t\tmem_cgroup_sk_alloc(sk);\nnet/core/sock.c:2419:\t\tcgroup_sk_alloc(\u0026sk-\u003esk_cgrp_data);\nnet/core/sock.c-2420-\t\tsock_update_classid(\u0026sk-\u003esk_cgrp_data);\n--\nnet/core/sock.c-2426-}\nnet/core/sock.c:2427:EXPORT_SYMBOL(sk_alloc);\nnet/core/sock.c-2428-\n--\nnet/core/sock.c=3067=struct sk_buff *sock_alloc_send_pskb(struct sock *sk, unsigned long header_len,\n--\nnet/core/sock.c-3097-\tskb = alloc_skb_with_frags(header_len, data_len, max_page_order,\nnet/core/sock.c:3098:\t\t\t\t errcode, sk-\u003esk_allocation);\nnet/core/sock.c-3099-\tif (skb)\n--\nnet/core/sock.c=3296=bool sk_page_frag_refill(struct sock *sk, struct page_frag *pfrag)\nnet/core/sock.c-3297-{\nnet/core/sock.c:3298:\tif (likely(__skb_page_frag_refill(32U, pfrag, sk-\u003esk_allocation, sk)))\nnet/core/sock.c-3299-\t\treturn true;\n--\nnet/core/sock.c=3828=void sock_init_data_uid(struct socket *sock, struct sock *sk, kuid_t uid)\n--\nnet/core/sock.c-3834-\nnet/core/sock.c:3835:\tsk-\u003esk_allocation\t=\tGFP_KERNEL;\nnet/core/sock.c-3836-\tsk-\u003esk_rcvbuf\t\t=\tREAD_ONCE(sysctl_rmem_default);\n--\nnet/core/sock.c=4634=static int __init sock_struct_check(void)\n--\nnet/core/sock.c-4689-\tCACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_tx, sk_gso_max_size);\nnet/core/sock.c:4690:\tCACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_tx, sk_allocation);\nnet/core/sock.c-4691-\tCACHELINE_ASSERT_GROUP_MEMBER(struct sock, sock_read_tx, sk_txhash);\n"
} |
| 25/3 | 2026/08/24 18:45 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 25231 output: 32 thoughts: 11 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience, and delivered as a first-person summary: **Digging into `sk_prot_alloc`** Right, let's dive into this `sk_prot_alloc` function, shall we? My immediate thought is, "What specifically are we trying to understand about it?" Knowing the context is crucial here. Are we looking at a performance bottleneck, a memory allocation issue, or perhaps security implications? I'm already mentally running through the various scenarios where this function becomes critical – things like socket creation, protocol registration, and resource management. I assume that `sk_prot_alloc` refers to the allocation function, used to create a socket’s `sock` structure. I need to refresh my memory on the underlying data structures involved. Specifically, I'm thinking about the interactions with things like the socket's protocol handlers, the associated `sk_buff` management, and any spinlocks or mutexes that might be in play. I need to confirm whether it is a costly operation that impacts performance, or perhaps, what memory pools and slab allocators are involved. I'm going to start by reviewing the core implementation, paying close attention to any atomic operations or potential contention points. It is worth investigating how it interacts with the underlying memory management to see if there is potential for memory leaks or fragmentation issues, or the possibility of an out-of-memory error. This analysis will guide my next steps: whether to look at profiling tools, memory analysis techniques, or simply code review for any obvious red flags. I will also make sure to check its interactions with the other functions in the socket subsystem. |
| 26/3 | 2026/08/24 18:45 | tool | grepper |
0mArgs:{
"Expression": "sk_prot_alloc",
"PathPrefix": "net/core/sock.c"
}Results: {
"Output": "net/core/sock.c=2292=static void sock_copy(struct sock *nsk, const struct sock *osk)\n--\nnet/core/sock.c-2311-\t\t prot-\u003eobj_size - offsetof(struct sock, sk_dontcopy_end),\nnet/core/sock.c:2312:\t\t /* alloc is larger than struct, see sk_prot_alloc() */);\nnet/core/sock.c-2313-\n--\nnet/core/sock.c-2319-\nnet/core/sock.c:2320:static struct sock *sk_prot_alloc(struct proto *prot, gfp_t priority,\nnet/core/sock.c-2321-\t\tint family)\n--\nnet/core/sock.c=2385=struct sock *sk_alloc(struct net *net, int family, gfp_t priority,\n--\nnet/core/sock.c-2389-\nnet/core/sock.c:2390:\tsk = sk_prot_alloc(prot, priority | __GFP_ZERO, family);\nnet/core/sock.c-2391-\tif (sk) {\n--\nnet/core/sock.c=2563=struct sock *sk_clone(const struct sock *sk, const gfp_t priority,\n--\nnet/core/sock.c-2570-\nnet/core/sock.c:2571:\tnewsk = sk_prot_alloc(prot, priority, sk-\u003esk_family);\nnet/core/sock.c-2572-\tif (!newsk)\n"
} |
| 27/3 | 2026/08/24 18:45 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 26029 output: 33 thoughts: 804 Thoughts: **Analyzing Memory Initialization in SWIOTLB and Page Pool Allocation** Okay, so I'm digging into how memory is initialized in the context of the SWIOTLB and page pool allocations, and I'm seeing some potential gotchas. I started by looking at `sk_alloc`. I can see that when a socket is allocated, it's zeroed out because of the use of `__GFP_ZERO` with the `priority`. Now, in the case of `sk_clone`, the memory gets copied using `sock_copy`, and then the `sk_clear_bounce_device` call zeroes out the `sk_swiotlb.dev` member of the new socket, while other fields get copied from the original. So, in that context, the fields *are* initialized, either by zeroing or by explicit copy. That got me thinking specifically about `swiotlb_alloc_pages`. I see that the code doesn't explicitly zero the allocated memory. Now, my initial thought was, "Well, if it replaces a call to `alloc_pages` without `__GFP_ZERO`, then it's consistent - both are uninitialized." But then I realized, what if `gfp` *does* contain `__GFP_ZERO`? In that case, `alloc_pages` *would* zero the memory, but `swiotlb_alloc_pages` *would not*! That's a definite inconsistency. So, I need to know: When is `swiotlb_alloc_pages` called, and what is the value of `gfp`? Let's check the callers of `swiotlb_alloc_pages`. The code shows that it's called by `__page_pool_alloc_page_order`. Now the important question: does `__page_pool_alloc_page_order` get passed `__GFP_ZERO`? I'll need to check the callers of `__page_pool_alloc_page_order`, namely `page_pool_alloc_pages` and `page_pool_create`. I'm guessing that I'll find the answer there. I need to know if we are passing a GFP that asks for zeroing in the callers of `__page_pool_alloc_page_order`. I'll have to investigate further to determine when the allocated memory is zeroed, and whether we're potentially introducing a subtle bug by using `swiotlb_alloc_pages` in scenarios where `alloc_pages` with `__GFP_ZERO` would have been called. |
| 28/3 | 2026/08/24 18:45 | tool | grepper |
0mError:no matches Args: {
"Expression": "__GFP_ZERO",
"PathPrefix": "net/core/page_pool.c"
}Results: {
"error": "no matches"
} |
| 29/3 | 2026/08/24 18:45 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 27421 output: 28 thoughts: 61 Thoughts: **Examining the Memory Allocation Parameters** Okay, hold on a second. I'm focusing on this `page_pool_create` function, and it's calling for `pool->p.gfp`. Right, that makes sense – it needs to know the allocation flags for the underlying pages. But that `pool->p.gfp`... I need to be absolutely sure what's going into it. Specifically, is `__GFP_ZERO` being set in `pool->p.gfp`? It would be good if this function initialized the memory to zero. I've got a hunch it might be, but I need to confirm. My next step is to quickly comb through the driver code and see if anyone explicitly uses `__GFP_ZERO` when setting up `page_pool_params.gfp`. That's the key: if drivers are requesting zeroed memory with that flag in their `gfp` parameters, then `page_pool_create` should align with that behavior. Need to make certain the allocation behavior is what the driver expects. |
| 30/3 | 2026/08/24 18:45 | tool | grepper |
0mArgs:{
"Expression": "__GFP_ZERO",
"PathPrefix": "drivers/net/"
}Results: {
"Output": "drivers/net/can/spi/mcp251xfd/mcp251xfd-dump.c=241=void mcp251xfd_dump(const struct mcp251xfd_priv *priv)\n--\ndrivers/net/can/spi/mcp251xfd/mcp251xfd-dump.c-266-\titer.start = __vmalloc(file_size, GFP_KERNEL | __GFP_NOWARN |\ndrivers/net/can/spi/mcp251xfd/mcp251xfd-dump.c:267:\t\t\t __GFP_ZERO | __GFP_NORETRY);\ndrivers/net/can/spi/mcp251xfd/mcp251xfd-dump.c-268-\tif (!iter.start) {\n--\ndrivers/net/can/virtio_can.c=952=static int __maybe_unused virtio_can_restore(struct virtio_device *vdev)\n--\ndrivers/net/can/virtio_can.c-963-\tsize = priv-\u003erpkt_len * priv-\u003evqs[VIRTIO_CAN_QUEUE_RX]-\u003enum_free;\ndrivers/net/can/virtio_can.c:964:\tpriv-\u003erpkt = devm_krealloc(\u0026vdev-\u003edev, priv-\u003erpkt, size, GFP_KERNEL | __GFP_ZERO);\ndrivers/net/can/virtio_can.c-965-\tif (!priv-\u003erpkt) {\n--\ndrivers/net/dsa/lantiq/lantiq_gswip.c=235=static int gswip_gphy_fw_list(struct gswip_priv *priv,\n--\ndrivers/net/dsa/lantiq/lantiq_gswip.c-281-\t\t\t\t\t sizeof(*priv-\u003egphy_fw),\ndrivers/net/dsa/lantiq/lantiq_gswip.c:282:\t\t\t\t\t GFP_KERNEL | __GFP_ZERO);\ndrivers/net/dsa/lantiq/lantiq_gswip.c-283-\tif (!priv-\u003egphy_fw)\n--\ndrivers/net/ethernet/amd/pds_core/core.c=703=static int pdsc_host_mem_add_one(struct pdsc *pdsc, int index)\n--\ndrivers/net/ethernet/amd/pds_core/core.c-732-\thm-\u003eorder = get_order(hm-\u003esize);\ndrivers/net/ethernet/amd/pds_core/core.c:733:\thm-\u003epg = alloc_pages(GFP_KERNEL | __GFP_ZERO | __GFP_NOWARN, hm-\u003eorder);\ndrivers/net/ethernet/amd/pds_core/core.c-734-\tif (!hm-\u003epg) {\n--\ndrivers/net/ethernet/apm/xgene/xgene_enet_main.c=1214=static struct xgene_enet_desc_ring *xgene_enet_create_desc_ring(\n--\ndrivers/net/ethernet/apm/xgene/xgene_enet_main.c-1238-\tring-\u003edesc_addr = dmam_alloc_coherent(dev, size, \u0026ring-\u003edma,\ndrivers/net/ethernet/apm/xgene/xgene_enet_main.c:1239:\t\t\t\t\t GFP_KERNEL | __GFP_ZERO);\ndrivers/net/ethernet/apm/xgene/xgene_enet_main.c-1240-\tif (!ring-\u003edesc_addr) {\n--\ndrivers/net/ethernet/apm/xgene/xgene_enet_main.c-1248-\t\t\t\t\t\t \u0026ring-\u003eirq_mbox_dma,\ndrivers/net/ethernet/apm/xgene/xgene_enet_main.c:1249:\t\t\t\t\t\t GFP_KERNEL | __GFP_ZERO);\ndrivers/net/ethernet/apm/xgene/xgene_enet_main.c-1250-\t\tif (!irq_mbox_addr) {\n--\ndrivers/net/ethernet/apm/xgene/xgene_enet_main.c=1305=static int xgene_enet_create_desc_rings(struct net_device *ndev)\n--\ndrivers/net/ethernet/apm/xgene/xgene_enet_main.c-1412-\t\texp_bufs = dmam_alloc_coherent(dev, size, \u0026dma_exp_bufs,\ndrivers/net/ethernet/apm/xgene/xgene_enet_main.c:1413:\t\t\t\t\t GFP_KERNEL | __GFP_ZERO);\ndrivers/net/ethernet/apm/xgene/xgene_enet_main.c-1414-\t\tif (!exp_bufs) {\n--\ndrivers/net/ethernet/broadcom/bnge/bnge_hwrm.c=21=int bnge_hwrm_req_create(struct bnge_dev *bd, void **req, u16 req_type,\n--\ndrivers/net/ethernet/broadcom/bnge/bnge_hwrm.c-30-\ndrivers/net/ethernet/broadcom/bnge/bnge_hwrm.c:31:\treq_addr = dma_pool_alloc(bd-\u003ehwrm_dma_pool, GFP_KERNEL | __GFP_ZERO,\ndrivers/net/ethernet/broadcom/bnge/bnge_hwrm.c-32-\t\t\t\t \u0026dma_handle);\n--\ndrivers/net/ethernet/broadcom/bnge/bnge_hwrm.c-42-\tctx-\u003edma_handle = dma_handle;\ndrivers/net/ethernet/broadcom/bnge/bnge_hwrm.c:43:\tctx-\u003eflags = 0; /* __GFP_ZERO, but be explicit regarding ownership */\ndrivers/net/ethernet/broadcom/bnge/bnge_hwrm.c-44-\tctx-\u003etimeout = bd-\u003ehwrm_cmd_timeout ?: BNGE_DFLT_HWRM_CMD_TIMEOUT;\n--\ndrivers/net/ethernet/broadcom/bnxt/bnxt_dcb.c=321=static int bnxt_hwrm_set_dcbx_app(struct bnxt *bp, struct dcb_app *app,\n--\ndrivers/net/ethernet/broadcom/bnxt/bnxt_dcb.c-339-\thwrm_req_hold(bp, get);\ndrivers/net/ethernet/broadcom/bnxt/bnxt_dcb.c:340:\thwrm_req_alloc_flags(bp, get, GFP_KERNEL | __GFP_ZERO);\ndrivers/net/ethernet/broadcom/bnxt/bnxt_dcb.c-341-\n--\ndrivers/net/ethernet/broadcom/bnxt/bnxt_hwrm.c=61=int __hwrm_req_init(struct bnxt *bp, void **req, u16 req_type, u32 req_len)\n--\ndrivers/net/ethernet/broadcom/bnxt/bnxt_hwrm.c-69-\ndrivers/net/ethernet/broadcom/bnxt/bnxt_hwrm.c:70:\treq_addr = dma_pool_alloc(bp-\u003ehwrm_dma_pool, GFP_KERNEL | __GFP_ZERO,\ndrivers/net/ethernet/broadcom/bnxt/bnxt_hwrm.c-71-\t\t\t\t \u0026dma_handle);\n--\ndrivers/net/ethernet/broadcom/bnxt/bnxt_hwrm.c-81-\tctx-\u003edma_handle = dma_handle;\ndrivers/net/ethernet/broadcom/bnxt/bnxt_hwrm.c:82:\tctx-\u003eflags = 0; /* __GFP_ZERO, but be explicit regarding ownership */\ndrivers/net/ethernet/broadcom/bnxt/bnxt_hwrm.c-83-\tctx-\u003etimeout = bp-\u003ehwrm_cmd_timeout ?: DFLT_HWRM_CMD_TIMEOUT;\n--\ndrivers/net/ethernet/broadcom/bnxt/bnxt_hwrm.c=135=void hwrm_req_timeout(struct bnxt *bp, void *req, unsigned int timeout)\n--\ndrivers/net/ethernet/broadcom/bnxt/bnxt_hwrm.c-151- *\thowever, memory suballocated from the request buffer is already\ndrivers/net/ethernet/broadcom/bnxt/bnxt_hwrm.c:152: *\t__GFP_ZERO.\ndrivers/net/ethernet/broadcom/bnxt/bnxt_hwrm.c-153- *\ndrivers/net/ethernet/broadcom/bnxt/bnxt_hwrm.c-154- * Sets the GFP allocation flags associated with the request for subsequent\ndrivers/net/ethernet/broadcom/bnxt/bnxt_hwrm.c:155: * calls to hwrm_req_dma_slice(). This can be useful for specifying __GFP_ZERO\ndrivers/net/ethernet/broadcom/bnxt/bnxt_hwrm.c-156- * for slice allocations.\n--\ndrivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c=4020=static int adap_config_hma(struct adapter *adapter)\n--\ndrivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c-4079-\t\tnewpage = alloc_pages_node(node, __GFP_NOWARN | GFP_KERNEL |\ndrivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c:4080:\t\t\t\t\t __GFP_ZERO, page_order);\ndrivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c-4081-\t\tif (!newpage) {\n--\ndrivers/net/ethernet/ibm/ibmvnic.c=4040=static struct ibmvnic_sub_crq_queue *init_sub_crq_queue(struct ibmvnic_adapter\n--\ndrivers/net/ethernet/ibm/ibmvnic.c-4051-\tscrq-\u003emsgs =\ndrivers/net/ethernet/ibm/ibmvnic.c:4052:\t\t(union sub_crq *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, 2);\ndrivers/net/ethernet/ibm/ibmvnic.c-4053-\tif (!scrq-\u003emsgs) {\n--\ndrivers/net/ethernet/intel/ice/ice_controlq.c=91=ice_alloc_ctrlq_sq_ring(struct ice_hw *hw, struct ice_ctl_q_info *cq)\n--\ndrivers/net/ethernet/intel/ice/ice_controlq.c-96-\t\t\t\t\t\t \u0026cq-\u003esq.desc_buf.pa,\ndrivers/net/ethernet/intel/ice/ice_controlq.c:97:\t\t\t\t\t\t GFP_KERNEL | __GFP_ZERO);\ndrivers/net/ethernet/intel/ice/ice_controlq.c-98-\tif (!cq-\u003esq.desc_buf.va)\n--\ndrivers/net/ethernet/intel/ice/ice_controlq.c=111=ice_alloc_ctrlq_rq_ring(struct ice_hw *hw, struct ice_ctl_q_info *cq)\n--\ndrivers/net/ethernet/intel/ice/ice_controlq.c-116-\t\t\t\t\t\t \u0026cq-\u003erq.desc_buf.pa,\ndrivers/net/ethernet/intel/ice/ice_controlq.c:117:\t\t\t\t\t\t GFP_KERNEL | __GFP_ZERO);\ndrivers/net/ethernet/intel/ice/ice_controlq.c-118-\tif (!cq-\u003erq.desc_buf.va)\n--\ndrivers/net/ethernet/intel/ice/ice_controlq.c=147=ice_alloc_rq_bufs(struct ice_hw *hw, struct ice_ctl_q_info *cq)\n--\ndrivers/net/ethernet/intel/ice/ice_controlq.c-167-\t\t\t\t\t cq-\u003erq_buf_size, \u0026bi-\u003epa,\ndrivers/net/ethernet/intel/ice/ice_controlq.c:168:\t\t\t\t\t GFP_KERNEL | __GFP_ZERO);\ndrivers/net/ethernet/intel/ice/ice_controlq.c-169-\t\tif (!bi-\u003eva)\n--\ndrivers/net/ethernet/intel/ice/ice_controlq.c=219=ice_alloc_sq_bufs(struct ice_hw *hw, struct ice_ctl_q_info *cq)\n--\ndrivers/net/ethernet/intel/ice/ice_controlq.c-236-\t\t\t\t\t cq-\u003esq_buf_size, \u0026bi-\u003epa,\ndrivers/net/ethernet/intel/ice/ice_controlq.c:237:\t\t\t\t\t GFP_KERNEL | __GFP_ZERO);\ndrivers/net/ethernet/intel/ice/ice_controlq.c-238-\t\tif (!bi-\u003eva)\n--\ndrivers/net/ethernet/intel/ice/ice_lib.c=3022=ice_vsi_realloc_stat_arrays(struct ice_vsi *vsi)\n--\ndrivers/net/ethernet/intel/ice/ice_lib.c-3048-\t\t\t sizeof(*vsi_stat-\u003etx_ring_stats),\ndrivers/net/ethernet/intel/ice/ice_lib.c:3049:\t\t\t GFP_KERNEL | __GFP_ZERO);\ndrivers/net/ethernet/intel/ice/ice_lib.c-3050-\tif (!vsi_stat-\u003etx_ring_stats) {\n--\ndrivers/net/ethernet/intel/ice/ice_lib.c-3067-\t\t\t sizeof(*vsi_stat-\u003erx_ring_stats),\ndrivers/net/ethernet/intel/ice/ice_lib.c:3068:\t\t\t GFP_KERNEL | __GFP_ZERO);\ndrivers/net/ethernet/intel/ice/ice_lib.c-3069-\tif (!vsi_stat-\u003erx_ring_stats) {\n--\ndrivers/net/ethernet/mediatek/mtk_wed.c=529=mtk_wed_amsdu_buffer_alloc(struct mtk_wed_device *dev)\n--\ndrivers/net/ethernet/mediatek/mtk_wed.c-547-\t\tptr = (void *)__get_free_pages(GFP_KERNEL | __GFP_NOWARN |\ndrivers/net/ethernet/mediatek/mtk_wed.c:548:\t\t\t\t\t __GFP_ZERO | __GFP_COMP |\ndrivers/net/ethernet/mediatek/mtk_wed.c-549-\t\t\t\t\t GFP_DMA32,\n--\ndrivers/net/ethernet/mellanox/mlx4/mr.c=102=static int mlx4_buddy_init(struct mlx4_buddy *buddy, int max_order)\n--\ndrivers/net/ethernet/mellanox/mlx4/mr.c-117-\t\ts = BITS_TO_LONGS(1UL \u003c\u003c (buddy-\u003emax_order - i));\ndrivers/net/ethernet/mellanox/mlx4/mr.c:118:\t\tbuddy-\u003ebits[i] = kvmalloc_array(s, sizeof(long), GFP_KERNEL | __GFP_ZERO);\ndrivers/net/ethernet/mellanox/mlx4/mr.c-119-\t\tif (!buddy-\u003ebits[i])\n--\ndrivers/net/ethernet/mellanox/mlx5/core/diag/fw_tracer.c=126=static int mlx5_fw_tracer_create_log_buf(struct mlx5_fw_tracer *tracer)\n--\ndrivers/net/ethernet/mellanox/mlx5/core/diag/fw_tracer.c-136-\ndrivers/net/ethernet/mellanox/mlx5/core/diag/fw_tracer.c:137:\tgfp = GFP_KERNEL | __GFP_ZERO;\ndrivers/net/ethernet/mellanox/mlx5/core/diag/fw_tracer.c-138-\tbuff = (void *)__get_free_pages(gfp,\n--\ndrivers/net/ethernet/mellanox/mlx5/core/steering/hws/bwc.c=788=hws_bwc_matcher_extend_at(struct mlx5hws_bwc_matcher *bwc_matcher,\n--\ndrivers/net/ethernet/mellanox/mlx5/core/steering/hws/bwc.c-800-\t\t\t\t sizeof(*bwc_matcher-\u003eat),\ndrivers/net/ethernet/mellanox/mlx5/core/steering/hws/bwc.c:801:\t\t\t __GFP_ZERO | GFP_KERNEL);\ndrivers/net/ethernet/mellanox/mlx5/core/steering/hws/bwc.c-802-\t\tif (!p) {\n--\ndrivers/net/ethernet/mellanox/mlx5/core/steering/hws/matcher.c=1014=static int hws_matcher_grow_at_array(struct mlx5hws_matcher *matcher)\n--\ndrivers/net/ethernet/mellanox/mlx5/core/steering/hws/matcher.c-1023-\t\t matcher-\u003esize_of_at_array * sizeof(*matcher-\u003eat),\ndrivers/net/ethernet/mellanox/mlx5/core/steering/hws/matcher.c:1024:\t\t __GFP_ZERO | GFP_KERNEL);\ndrivers/net/ethernet/mellanox/mlx5/core/steering/hws/matcher.c-1025-\tif (!p) {\n--\ndrivers/net/wireless/ath/ath12k/dp.c=1412=static int ath12k_dp_alloc_reoq_lut(struct ath12k_base *ab,\n--\ndrivers/net/wireless/ath/ath12k/dp.c-1417-\t\t\t\t\t\t \u0026lut-\u003epaddr_unaligned,\ndrivers/net/wireless/ath/ath12k/dp.c:1418:\t\t\t\t\t\t GFP_KERNEL | __GFP_ZERO);\ndrivers/net/wireless/ath/ath12k/dp.c-1419-\tif (!lut-\u003evaddr_unaligned)\n--\ndrivers/net/wireless/ti/wl18xx/main.c=1562=static int wl18xx_handle_static_data(struct wl1271 *wl,\n--\ndrivers/net/wireless/ti/wl18xx/main.c-1581-\t\tvoid *new_status = krealloc(wl-\u003eraw_fw_status, fw_status_len,\ndrivers/net/wireless/ti/wl18xx/main.c:1582:\t\t\t\t\t GFP_KERNEL | __GFP_ZERO);\ndrivers/net/wireless/ti/wl18xx/main.c-1583-\t\tif (!new_status)\n--\ndrivers/net/wwan/t7xx/t7xx_hif_dpmaif_rx.c=944=int t7xx_dpmaif_bat_alloc(const struct dpmaif_ctrl *dpmaif_ctrl, struct dpmaif_bat_request *bat_req,\n--\ndrivers/net/wwan/t7xx/t7xx_hif_dpmaif_rx.c-964-\t\t\t\t\t bat_req-\u003ebat_size_cnt * sizeof(struct dpmaif_bat),\ndrivers/net/wwan/t7xx/t7xx_hif_dpmaif_rx.c:965:\t\t\t\t\t \u0026bat_req-\u003ebat_bus_addr, GFP_KERNEL | __GFP_ZERO);\ndrivers/net/wwan/t7xx/t7xx_hif_dpmaif_rx.c-966-\tif (!bat_req-\u003ebat_base)\n--\ndrivers/net/wwan/t7xx/t7xx_hif_dpmaif_rx.c=1011=static int t7xx_dpmaif_rx_alloc(struct dpmaif_rx_queue *rxq)\n--\ndrivers/net/wwan/t7xx/t7xx_hif_dpmaif_rx.c-1022-\t\t\t\t\t rxq-\u003epit_size_cnt * sizeof(struct dpmaif_pit),\ndrivers/net/wwan/t7xx/t7xx_hif_dpmaif_rx.c:1023:\t\t\t\t\t \u0026rxq-\u003epit_bus_addr, GFP_KERNEL | __GFP_ZERO);\ndrivers/net/wwan/t7xx/t7xx_hif_dpmaif_rx.c-1024-\tif (!rxq-\u003epit_base)\n--\ndrivers/net/wwan/t7xx/t7xx_hif_dpmaif_tx.c=535=static int t7xx_dpmaif_tx_drb_buf_init(struct dpmaif_tx_queue *txq)\n--\ndrivers/net/wwan/t7xx/t7xx_hif_dpmaif_tx.c-545-\ttxq-\u003edrb_base = dma_alloc_coherent(txq-\u003edpmaif_ctrl-\u003edev, brb_pd_size,\ndrivers/net/wwan/t7xx/t7xx_hif_dpmaif_tx.c:546:\t\t\t\t\t \u0026txq-\u003edrb_bus_addr, GFP_KERNEL | __GFP_ZERO);\ndrivers/net/wwan/t7xx/t7xx_hif_dpmaif_tx.c-547-\tif (!txq-\u003edrb_base)\n--\ndrivers/net/xen-netfront.c=271=static struct sk_buff *xennet_alloc_one_rx_buffer(struct netfront_queue *queue)\n--\ndrivers/net/xen-netfront.c-282-\tpage = page_pool_alloc_pages(queue-\u003epage_pool,\ndrivers/net/xen-netfront.c:283:\t\t\t\t GFP_ATOMIC | __GFP_NOWARN | __GFP_ZERO);\ndrivers/net/xen-netfront.c-284-\tif (unlikely(!page)) {\n--\ndrivers/net/xen-netfront.c=676=static struct sk_buff *bounce_skb(const struct sk_buff *skb)\n--\ndrivers/net/xen-netfront.c-681-\t\t\t\t XEN_PAGE_SIZE);\ndrivers/net/xen-netfront.c:682:\tstruct sk_buff *n = alloc_skb(size, GFP_ATOMIC | __GFP_ZERO);\ndrivers/net/xen-netfront.c-683-\n"
} |
| 31/3 | 2026/08/24 18:45 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 33330 output: 27 thoughts: 3138 Thoughts: **Analysis of Potential Uninitialized Memory Read Vulnerability in Xen-Netfront with SWIOTLB** Okay, so I'm looking at a situation where `xen-netfront` calls `page_pool_alloc_pages` with `__GFP_ZERO`. This means the driver *expects* a zeroed page, which is crucial because it's sharing this memory with the Xen backend (the hypervisor or Dom0). The primary concern is preventing information leakage from the guest kernel to the hypervisor. The problem arises because `page_pool_alloc_pages` calls `__page_pool_alloc_page_order`, which *may* call `swiotlb_alloc_pages` if SWIOTLB is active for the device. Crucially, `swiotlb_alloc_pages` *ignores* the `__GFP_ZERO` flag. This means the allocated page isn't zeroed; it contains potentially stale data from previous SWIOTLB usage. `xen-netfront` is a virtual network driver, so it receives packets from the Xen backend. If the backend writes less data than the full page size, the remaining portion of the page becomes uninitialized. The driver then passes this potentially uninitialized page to the network stack, which could, theoretically, read this uninitialized data. A key question is whether KMSAN, the kernel memory sanitizer, can detect this issue. My reasoning is that KMSAN might not be aware of SWIOTLB allocations, especially those happening during DMA operations. The pages are technically initialized at boot, and KMSAN might not track subsequent SWIOTLB activity. Thus, KMSAN *will not* detect this. Specifically, if the page was previously used for bouncing, `memcpy` in `swiotlb_bounce` copies the shadow state of initialized data, making KMSAN think it's initialized. The patch's potential vulnerability lies in `swiotlb_alloc_pages` *itself* ignoring `__GFP_ZERO`. This results in an info-leak where the Xen backend receives a page with uninitialized data. This is a real problem. The backend might read stale data, which could contain sensitive kernel information. Looking at the code paths I analyzed, such as `swiotlb_bounce` and `dma_direct_map_phys`, the only one I am concerned about is `swiotlb_alloc_pages` itself. `swiotlb_alloc_pages` introduces the info-leak risk when a caller requests zeroed memory and doesn't get it. This is particularly problematic with `xen-netfront` because the page is shared with the Xen backend, potentially exposing stale memory contents. Thus, this patch introduces uninitialized memory risks. The patch *does* introduce a scenario where uninitialized memory can be sent to an "external" entity (the Xen backend), which fits the criteria for needing KMSAN. |
| 32/3 | 2026/08/24 18:46 | tool | grepper |
0mArgs:{
"Expression": "kmsan_handle_dma",
"PathPrefix": ""
}Results: {
"Output": "drivers/virtio/virtio_ring.c=495=static int vring_map_one_sg(const struct vring_virtqueue *vq, struct scatterlist *sg,\n--\ndrivers/virtio/virtio_ring.c-512-\t\t */\ndrivers/virtio/virtio_ring.c:513:\t\tkmsan_handle_dma(sg_phys(sg), sg-\u003elength, direction);\ndrivers/virtio/virtio_ring.c-514-\t\t*addr = (dma_addr_t)sg_phys(sg);\n--\ndrivers/virtio/virtio_ring.c=3839=dma_addr_t virtqueue_map_single_attrs(const struct virtqueue *_vq, void *ptr,\n--\ndrivers/virtio/virtio_ring.c-3846-\tif (!vq-\u003euse_map_api) {\ndrivers/virtio/virtio_ring.c:3847:\t\tkmsan_handle_dma(virt_to_phys(ptr), size, dir);\ndrivers/virtio/virtio_ring.c-3848-\t\treturn (dma_addr_t)virt_to_phys(ptr);\n--\ninclude/linux/kmsan.h=183=void kmsan_iounmap_page_range(unsigned long start, unsigned long end);\n--\ninclude/linux/kmsan.h-185-/**\ninclude/linux/kmsan.h:186: * kmsan_handle_dma() - Handle a DMA data transfer.\ninclude/linux/kmsan.h-187- * @phys: physical address of the buffer.\n--\ninclude/linux/kmsan.h-195- */\ninclude/linux/kmsan.h:196:void kmsan_handle_dma(phys_addr_t phys, size_t size,\ninclude/linux/kmsan.h-197-\t\t enum dma_data_direction dir);\n--\ninclude/linux/kmsan.h-199-/**\ninclude/linux/kmsan.h:200: * kmsan_handle_dma_sg() - Handle a DMA transfer using scatterlist.\ninclude/linux/kmsan.h-201- * @sg: scatterlist holding DMA buffers.\n--\ninclude/linux/kmsan.h-209- */\ninclude/linux/kmsan.h:210:void kmsan_handle_dma_sg(struct scatterlist *sg, int nents,\ninclude/linux/kmsan.h-211-\t\t\t enum dma_data_direction dir);\n--\ninclude/linux/kmsan.h=371=static inline void kmsan_iounmap_page_range(unsigned long start,\n--\ninclude/linux/kmsan.h-375-\ninclude/linux/kmsan.h:376:static inline void kmsan_handle_dma(phys_addr_t phys, size_t size,\ninclude/linux/kmsan.h-377-\t\t\t\t enum dma_data_direction dir)\n--\ninclude/linux/kmsan.h-380-\ninclude/linux/kmsan.h:381:static inline void kmsan_handle_dma_sg(struct scatterlist *sg, int nents,\ninclude/linux/kmsan.h-382-\t\t\t\t enum dma_data_direction dir)\n--\nkernel/dma/mapping.c=153=dma_addr_t dma_map_phys(struct device *dev, phys_addr_t phys, size_t size,\n--\nkernel/dma/mapping.c-180-\tif (!is_mmio)\nkernel/dma/mapping.c:181:\t\tkmsan_handle_dma(phys, size, dir);\nkernel/dma/mapping.c-182-\ttrace_dma_map_phys(dev, phys, addr, size, dir, attrs);\n--\nkernel/dma/mapping.c=240=static int __dma_map_sg_attrs(struct device *dev, struct scatterlist *sg,\n--\nkernel/dma/mapping.c-262-\tif (ents \u003e 0) {\nkernel/dma/mapping.c:263:\t\tkmsan_handle_dma_sg(sg, nents, dir);\nkernel/dma/mapping.c-264-\t\ttrace_dma_map_sg(dev, sg, nents, ents, dir, attrs);\n--\nmm/kmsan/hooks.c=316=EXPORT_SYMBOL_GPL(kmsan_handle_urb);\nmm/kmsan/hooks.c-317-\nmm/kmsan/hooks.c:318:static void kmsan_handle_dma_page(const void *addr, size_t size,\nmm/kmsan/hooks.c-319-\t\t\t\t enum dma_data_direction dir)\n--\nmm/kmsan/hooks.c-341-/* Helper function to handle DMA data transfers. */\nmm/kmsan/hooks.c:342:void kmsan_handle_dma(phys_addr_t phys, size_t size,\nmm/kmsan/hooks.c-343-\t\t enum dma_data_direction dir)\n--\nmm/kmsan/hooks.c-358-\t\tto_go = min(PAGE_SIZE - page_offset, (u64)size);\nmm/kmsan/hooks.c:359:\t\tkmsan_handle_dma_page((void *)addr, to_go, dir);\nmm/kmsan/hooks.c-360-\t\taddr += to_go;\n--\nmm/kmsan/hooks.c-363-}\nmm/kmsan/hooks.c:364:EXPORT_SYMBOL_GPL(kmsan_handle_dma);\nmm/kmsan/hooks.c-365-\nmm/kmsan/hooks.c:366:void kmsan_handle_dma_sg(struct scatterlist *sg, int nents,\nmm/kmsan/hooks.c-367-\t\t\t enum dma_data_direction dir)\n--\nmm/kmsan/hooks.c-372-\tfor_each_sg(sg, item, nents, i)\nmm/kmsan/hooks.c:373:\t\tkmsan_handle_dma(sg_phys(item), item-\u003elength, dir);\nmm/kmsan/hooks.c-374-}\n--\ntools/virtio/linux/kmsan.h-6-\ntools/virtio/linux/kmsan.h:7:inline void kmsan_handle_dma(phys_addr_t phys, size_t size,\ntools/virtio/linux/kmsan.h-8-\t\t\t enum dma_data_direction dir)\n"
} |
| 33/3 | 2026/08/24 18:46 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 38761 output: 24 thoughts: 1743 Thoughts: **KMSAN's Ineffectiveness with SWIOTLB and DMA Transfers** Okay, so I'm thinking about how KMSAN handles DMA transfers, specifically in relation to the `swiotlb`. It seems like `kmsan_handle_dma` checks memory initialization when a page is mapped for DMA. That makes sense, but there's a problem when `swiotlb_alloc_pages` is involved. If `swiotlb_alloc_pages` returns a page, KMSAN considers it initialized because it doesn't track `swiotlb` allocations. The `swiotlb` pool was zeroed at boot, so KMSAN will not flag a page allocated by `swiotlb_alloc_pages`. This is a problem! KMSAN will miss potential information leaks. It's useless for finding bugs if `swiotlb_alloc_pages` is involved. Here's the problem: if the network stack allocates a page using `alloc_any_pg` (which can call `swiotlb_alloc_pages`), and then *doesn't* initialize it completely, KMSAN *should* catch uninitialized fields. But, because KMSAN considers the page INITIALIZED, it won't. If the network stack writes some data but doesn't initialize some fields, KMSAN tracks the writes; when the page is mapped for DMA, KMSAN should complain about those specific bytes. But if the network stack just leaves some bytes untouched, KMSAN will consider them initialized (because they were initialized at boot) and miss that they are actually stale SWIOTLB data. This is because KMSAN's shadow memory for the SWIOTLB pool is not reset when `swiotlb_alloc_pages` is called. So, in this scenario, KMSAN is *less* effective, not more! The patch is more likely to introduce a bug and KMSAN might miss it. Wait, is there any case where KMSAN *would* find a bug introduced by this patch? What if the patch introduces an uninitialized memory read in the kernel itself? Since KMSAN thinks it's initialized, it won't complain. But `sk_swiotlb_info` is zeroed. Okay, let's look at `alloc_any_pg`. There are two paths, one that uses `swiotlb_alloc_pages` and one that uses `alloc_pages`. If the patch introduces a bug where the caller expects the page to be zeroed (because `gfp` has `__GFP_ZERO`), but `swiotlb_alloc_pages` doesn't actually zero it, we have a problem. Both cases, whether `alloc_pages` or `swiotlb_alloc_pages` is called with `__GFP_ZERO`, KMSAN considers the page initialized. If the caller reads the page, KMSAN won't complain in either case! If `swiotlb_alloc_pages` doesn't zero the page, it contains stale data, which is a bug, but KMSAN is blind to it! So, KMSAN will not detect the use of stale data. It only detects uninitialized data. Is there any other bug? What about `sk_record_bounce_device`? I'm checking it for uninitialized memory. `sk->sk_swiotlb.jiffies` is read. If it's uninitialized, `time_before` will use it. But is it initialized? When the socket is created, `sk_alloc` zeroes the whole socket, so `jiffies` is initialized to 0. It is initialized, and updated later. What about `swiotlb_safe_put_device`? `kmalloc_obj` allocates `dp`. But what is `kmalloc_obj`? I need to check if that exists. |
| 34/3 | 2026/08/24 18:46 | tool | grepper |
0mArgs:{
"Expression": "kmalloc_obj",
"PathPrefix": ""
}Results: {
"Output": "\nFull output is too long, showing 500 out of 15618 lines.\nUse more precise expression if possible.\n\nDocumentation/RCU/Design/Requirements/Requirements.rst=203=non-\\ ``NULL``, locklessly accessing the ``-\u003ea`` and ``-\u003eb`` fields.\n--\nDocumentation/RCU/Design/Requirements/Requirements.rst-208- 2 {\nDocumentation/RCU/Design/Requirements/Requirements.rst:209: 3 p = kmalloc_obj(*p);\nDocumentation/RCU/Design/Requirements/Requirements.rst-210- 4 if (!p)\n--\nDocumentation/RCU/Design/Requirements/Requirements.rst=225=their rights to reorder this code as follows:\n--\nDocumentation/RCU/Design/Requirements/Requirements.rst-230- 2 {\nDocumentation/RCU/Design/Requirements/Requirements.rst:231: 3 p = kmalloc_obj(*p);\nDocumentation/RCU/Design/Requirements/Requirements.rst-232- 4 if (!p)\n--\nDocumentation/RCU/Design/Requirements/Requirements.rst=261=shows an example of insertion:\n--\nDocumentation/RCU/Design/Requirements/Requirements.rst-266- 2 {\nDocumentation/RCU/Design/Requirements/Requirements.rst:267: 3 p = kmalloc_obj(*p);\nDocumentation/RCU/Design/Requirements/Requirements.rst-268- 4 if (!p)\n--\nDocumentation/RCU/listRCU.rst=267=The RCU version of audit_upd_rule() is as follows::\n--\nDocumentation/RCU/listRCU.rst-278-\t\t\tif (!audit_compare_rule(rule, \u0026e-\u003erule)) {\nDocumentation/RCU/listRCU.rst:279:\t\t\t\tne = kmalloc_obj(*entry, GFP_ATOMIC);\nDocumentation/RCU/listRCU.rst-280-\t\t\t\tif (ne == NULL)\n--\nDocumentation/RCU/rcu_dereference.rst=225=precautions. To see this, consider the following code fragment::\n--\nDocumentation/RCU/rcu_dereference.rst-238-\nDocumentation/RCU/rcu_dereference.rst:239:\t\tp = kmalloc_obj(*p);\nDocumentation/RCU/rcu_dereference.rst-240-\t\tif (p == NULL)\n--\nDocumentation/RCU/rcu_dereference.rst=281=Then one approach is to use locking, for example, as follows::\n--\nDocumentation/RCU/rcu_dereference.rst-295-\nDocumentation/RCU/rcu_dereference.rst:296:\t\tp = kmalloc_obj(*p);\nDocumentation/RCU/rcu_dereference.rst-297-\t\tif (p == NULL)\n--\nDocumentation/RCU/whatisRCU.rst=441=uses of RCU may be found in listRCU.rst and NMI-RCU.rst.\n--\nDocumentation/RCU/whatisRCU.rst-470-\nDocumentation/RCU/whatisRCU.rst:471:\t\tnew_fp = kmalloc_obj(*new_fp);\nDocumentation/RCU/whatisRCU.rst-472-\t\tspin_lock(\u0026foo_mutex);\n--\nDocumentation/RCU/whatisRCU.rst=553=The foo_update_a() function might then be written as follows::\n--\nDocumentation/RCU/whatisRCU.rst-572-\nDocumentation/RCU/whatisRCU.rst:573:\t\tnew_fp = kmalloc_obj(*new_fp);\nDocumentation/RCU/whatisRCU.rst-574-\t\tspin_lock(\u0026foo_mutex);\n--\nDocumentation/core-api/kref.rst=39=kref_init as so::\n--\nDocumentation/core-api/kref.rst-42-\nDocumentation/core-api/kref.rst:43: data = kmalloc_obj(*data);\nDocumentation/core-api/kref.rst-44- if (!data)\n--\nDocumentation/core-api/kref.rst=81=thread to process::\n--\nDocumentation/core-api/kref.rst-102-\tstruct task_struct *task;\nDocumentation/core-api/kref.rst:103:\tdata = kmalloc_obj(*data);\nDocumentation/core-api/kref.rst-104-\tif (!data)\n--\nDocumentation/kernel-hacking/locking.rst=383=to protect the cache and all the objects within it. Here's the code::\n--\nDocumentation/kernel-hacking/locking.rst-444-\nDocumentation/kernel-hacking/locking.rst:445: if ((obj = kmalloc_obj(*obj)) == NULL)\nDocumentation/kernel-hacking/locking.rst-446- return -ENOMEM;\n--\nDocumentation/kernel-hacking/locking.rst=499=which are taken away, and the ``+`` are lines which are added.\n--\nDocumentation/kernel-hacking/locking.rst-519-\nDocumentation/kernel-hacking/locking.rst:520: if ((obj = kmalloc_obj(*obj)) == NULL)\nDocumentation/kernel-hacking/locking.rst-521- return -ENOMEM;\n--\nDocumentation/locking/locktypes.rst=498=works perfectly::\n--\nDocumentation/locking/locktypes.rst-500- raw_spin_lock(\u0026lock);\nDocumentation/locking/locktypes.rst:501: p = kmalloc_obj(*p, GFP_ATOMIC);\nDocumentation/locking/locktypes.rst-502-\n--\nDocumentation/locking/locktypes.rst=507=preemption on PREEMPT_RT kernels::\n--\nDocumentation/locking/locktypes.rst-509- spin_lock(\u0026lock);\nDocumentation/locking/locktypes.rst:510: p = kmalloc_obj(*p, GFP_ATOMIC);\nDocumentation/locking/locktypes.rst-511-\n--\nDocumentation/process/coding-style.rst=938=The kernel provides the following general purpose memory allocators:\nDocumentation/process/coding-style.rst:939:kmalloc(), kzalloc(), kmalloc_objs(), kzalloc_objs(), vmalloc(), and\nDocumentation/process/coding-style.rst-940-vzalloc(). Please refer to the API documentation for further information\n--\nDocumentation/process/coding-style.rst=944=The preferred form for passing a size of a struct is the following:\n--\nDocumentation/process/coding-style.rst-947-\nDocumentation/process/coding-style.rst:948:\tp = kmalloc_obj(*p, ...);\nDocumentation/process/coding-style.rst-949-\n--\nDocumentation/process/coding-style.rst=958=The preferred form for allocating an array is the following:\n--\nDocumentation/process/coding-style.rst-961-\nDocumentation/process/coding-style.rst:962:\tp = kmalloc_objs(*p, n, ...);\nDocumentation/process/coding-style.rst-963-\n--\nDocumentation/process/deprecated.rst=386=may help with alignment, wrap-around, or additional hardening. The\nDocumentation/process/deprecated.rst:387:kmalloc_obj()-family of macros provide this introspection, which can be\nDocumentation/process/deprecated.rst-388-used for the common code patterns for single, array, and flexible object\n--\nDocumentation/process/deprecated.rst=398=become, respectively::\nDocumentation/process/deprecated.rst-399-\nDocumentation/process/deprecated.rst:400:\tptr = kmalloc_obj(*ptr [, gfp] );\nDocumentation/process/deprecated.rst-401-\tptr = kzalloc_obj(*ptr [, gfp] );\nDocumentation/process/deprecated.rst:402:\tptr = kmalloc_objs(*ptr, count [, gfp] );\nDocumentation/process/deprecated.rst-403-\tptr = kzalloc_objs(*ptr, count [, gfp] );\nDocumentation/process/deprecated.rst-404-\tptr = kmalloc_flex(*ptr, flex_member, count [, gfp] );\nDocumentation/process/deprecated.rst:405:\t__auto_type ptr = kmalloc_obj(struct foo [, gfp] );\nDocumentation/process/deprecated.rst-406-\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst=1734=callback::\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-1739- ....\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst:1740: data = kmalloc_obj(*data);\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-1741- substream-\u003eruntime-\u003eprivate_data = data;\n--\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst=3302=destructor function is set in the ``private_free`` field::\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-3303-\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst:3304: struct mydata *p = kmalloc_obj(*p);\nDocumentation/sound/kernel-api/writing-an-alsa-driver.rst-3305- hw-\u003eprivate_data = p;\n--\nDocumentation/spi/spi-summary.rst=242=And SOC-specific utility code might look something like::\n--\nDocumentation/spi/spi-summary.rst-251-\nDocumentation/spi/spi-summary.rst:252:\t\tpdata2 = kmalloc_obj(*pdata2);\nDocumentation/spi/spi-summary.rst-253-\t\t*pdata2 = pdata;\n--\nDocumentation/translations/it_IT/kernel-hacking/locking.rst=403=e tutti gli oggetti che contiene. Ecco il codice::\n--\nDocumentation/translations/it_IT/kernel-hacking/locking.rst-464-\nDocumentation/translations/it_IT/kernel-hacking/locking.rst:465: if ((obj = kmalloc_obj(*obj)) == NULL)\nDocumentation/translations/it_IT/kernel-hacking/locking.rst-466- return -ENOMEM;\n--\nDocumentation/translations/it_IT/kernel-hacking/locking.rst=519=sono quelle rimosse, mentre quelle ``+`` sono quelle aggiunte.\n--\nDocumentation/translations/it_IT/kernel-hacking/locking.rst-539-\nDocumentation/translations/it_IT/kernel-hacking/locking.rst:540: if ((obj = kmalloc_obj(*obj)) == NULL)\nDocumentation/translations/it_IT/kernel-hacking/locking.rst-541- return -ENOMEM;\n--\nDocumentation/translations/it_IT/locking/locktypes.rst=488=memoria. Su un kernel non-PREEMPT_RT il seguente codice funziona perfettamente::\n--\nDocumentation/translations/it_IT/locking/locktypes.rst-490- raw_spin_lock(\u0026lock);\nDocumentation/translations/it_IT/locking/locktypes.rst:491: p = kmalloc_obj(*p, GFP_ATOMIC);\nDocumentation/translations/it_IT/locking/locktypes.rst-492-\n--\nDocumentation/translations/it_IT/locking/locktypes.rst=497=PREEMPT_RT::\n--\nDocumentation/translations/it_IT/locking/locktypes.rst-499- spin_lock(\u0026lock);\nDocumentation/translations/it_IT/locking/locktypes.rst:500: p = kmalloc_obj(*p, GFP_ATOMIC);\nDocumentation/translations/it_IT/locking/locktypes.rst-501-\n--\nDocumentation/translations/it_IT/process/coding-style.rst=942=Il modo preferito per passare la dimensione di una struttura è il seguente:\n--\nDocumentation/translations/it_IT/process/coding-style.rst-945-\nDocumentation/translations/it_IT/process/coding-style.rst:946:\tp = kmalloc_obj(*p, ...);\nDocumentation/translations/it_IT/process/coding-style.rst-947-\n--\nDocumentation/translations/pt_BR/process/deprecated.rst=397=possa ajudar com alinhamento, estouros de capacidade (*wrap-around*) ou\nDocumentation/translations/pt_BR/process/deprecated.rst:398:proteções adicionais (*hardening*). A família de macros kmalloc_obj() fornece\nDocumentation/translations/pt_BR/process/deprecated.rst-399-essa introspecção, que pode ser usada para os padrões de código comuns de\n--\nDocumentation/translations/pt_BR/process/deprecated.rst=410=tornam-se, respectivamente::\nDocumentation/translations/pt_BR/process/deprecated.rst-411-\nDocumentation/translations/pt_BR/process/deprecated.rst:412: ptr = kmalloc_obj(*ptr [, gfp] );\nDocumentation/translations/pt_BR/process/deprecated.rst-413- ptr = kzalloc_obj(*ptr [, gfp] );\nDocumentation/translations/pt_BR/process/deprecated.rst:414: ptr = kmalloc_objs(*ptr, count [, gfp] );\nDocumentation/translations/pt_BR/process/deprecated.rst-415- ptr = kzalloc_objs(*ptr, count [, gfp] );\nDocumentation/translations/pt_BR/process/deprecated.rst-416- ptr = kmalloc_flex(*ptr, flex_member, count [, gfp] );\nDocumentation/translations/pt_BR/process/deprecated.rst:417: __auto_type ptr = kmalloc_obj(struct foo [, gfp] );\nDocumentation/translations/pt_BR/process/deprecated.rst-418-\n--\nDocumentation/translations/sp_SP/process/coding-style.rst=954=La forma preferida para pasar el tamaño de una estructura es la siguiente:\n--\nDocumentation/translations/sp_SP/process/coding-style.rst-957-\nDocumentation/translations/sp_SP/process/coding-style.rst:958:\tp = kmalloc_obj(*p, ...);\nDocumentation/translations/sp_SP/process/coding-style.rst-959-\n--\nDocumentation/translations/zh_CN/core-api/kref.rst=46=kref可以出现在数据结构体中的任何地方。\n--\nDocumentation/translations/zh_CN/core-api/kref.rst-54-\nDocumentation/translations/zh_CN/core-api/kref.rst:55: data = kmalloc_obj(*data);\nDocumentation/translations/zh_CN/core-api/kref.rst-56- if (!data)\n--\nDocumentation/translations/zh_CN/core-api/kref.rst=62=Kref规则\n--\nDocumentation/translations/zh_CN/core-api/kref.rst-108-\tstruct task_struct *task;\nDocumentation/translations/zh_CN/core-api/kref.rst:109:\tdata = kmalloc_obj(*data);\nDocumentation/translations/zh_CN/core-api/kref.rst-110-\tif (!data)\n--\nDocumentation/translations/zh_CN/process/coding-style.rst=810=Documentation/translations/zh_CN/core-api/memory-allocation.rst 。\n--\nDocumentation/translations/zh_CN/process/coding-style.rst-815-\nDocumentation/translations/zh_CN/process/coding-style.rst:816:\tp = kmalloc_obj(*p, ...);\nDocumentation/translations/zh_CN/process/coding-style.rst-817-\n--\nDocumentation/translations/zh_TW/process/coding-style.rst=824=Documentation/translations/zh_CN/core-api/memory-allocation.rst 。\n--\nDocumentation/translations/zh_TW/process/coding-style.rst-829-\nDocumentation/translations/zh_TW/process/coding-style.rst:830:\tp = kmalloc_obj(*p, ...);\nDocumentation/translations/zh_TW/process/coding-style.rst-831-\n--\narch/alpha/kernel/core_marvel.c=857=marvel_agp_setup(alpha_agp_info *agp)\n--\narch/alpha/kernel/core_marvel.c-863-\narch/alpha/kernel/core_marvel.c:864:\taper = kmalloc_obj(*aper);\narch/alpha/kernel/core_marvel.c-865-\tif (aper == NULL) return -ENOMEM;\n--\narch/alpha/kernel/core_marvel.c=1019=marvel_agp_info(void)\n--\narch/alpha/kernel/core_marvel.c-1061-\t */\narch/alpha/kernel/core_marvel.c:1062:\tagp = kmalloc_obj(*agp);\narch/alpha/kernel/core_marvel.c-1063-\tif (!agp)\n--\narch/alpha/kernel/core_titan.c=590=titan_agp_setup(alpha_agp_info *agp)\n--\narch/alpha/kernel/core_titan.c-596-\narch/alpha/kernel/core_titan.c:597:\taper = kmalloc_obj(struct titan_agp_aperture);\narch/alpha/kernel/core_titan.c-598-\tif (aper == NULL)\n--\narch/alpha/kernel/core_titan.c=731=titan_agp_info(void)\n--\narch/alpha/kernel/core_titan.c-762-\t */\narch/alpha/kernel/core_titan.c:763:\tagp = kmalloc_obj(*agp);\narch/alpha/kernel/core_titan.c-764-\tif (!agp)\n--\narch/alpha/kernel/module.c=29=process_reloc_for_got(Elf64_Rela *rela,\n--\narch/alpha/kernel/module.c-48-\narch/alpha/kernel/module.c:49:\tg = kmalloc_obj(*g);\narch/alpha/kernel/module.c-50-\tg-\u003enext = chains[r_sym].next;\n--\narch/alpha/kernel/pci.c=211=static void pdev_save_srm_config(struct pci_dev *dev)\n--\narch/alpha/kernel/pci.c-223-\narch/alpha/kernel/pci.c:224:\ttmp = kmalloc_obj(*tmp);\narch/alpha/kernel/pci.c-225-\tif (!tmp) {\n--\narch/arc/kernel/unwind.c=359=void *unwind_add_table(struct module *module, const void *table_start,\n--\narch/arc/kernel/unwind.c-368-\narch/arc/kernel/unwind.c:369:\ttable = kmalloc_obj(*table);\narch/arc/kernel/unwind.c-370-\tif (!table)\n--\narch/arm/common/locomo.c=274=static int locomo_suspend(struct platform_device *dev, pm_message_t state)\n--\narch/arm/common/locomo.c-279-\narch/arm/common/locomo.c:280:\tsave = kmalloc_obj(struct locomo_save_data);\narch/arm/common/locomo.c-281-\tif (!save)\n--\narch/arm/common/sa1111.c=964=static int sa1111_suspend_noirq(struct device *dev)\n--\narch/arm/common/sa1111.c-971-\narch/arm/common/sa1111.c:972:\tsave = kmalloc_obj(struct sa1111_save_data);\narch/arm/common/sa1111.c-973-\tif (!save)\n--\narch/arm/kernel/unwind.c=572=struct unwind_table *unwind_table_add(unsigned long start, unsigned long size,\n--\narch/arm/kernel/unwind.c-576-\tunsigned long flags;\narch/arm/kernel/unwind.c:577:\tstruct unwind_table *tab = kmalloc_obj(*tab);\narch/arm/kernel/unwind.c-578-\n--\narch/arm/mach-omap2/omap-iommu.c=53=static struct powerdomain *_get_pwrdm(struct device *dev)\n--\narch/arm/mach-omap2/omap-iommu.c-101-\narch/arm/mach-omap2/omap-iommu.c:102:\tentry = kmalloc_obj(*entry);\narch/arm/mach-omap2/omap-iommu.c-103-\tif (entry) {\n--\narch/arm/mach-omap2/pm34xx.c=406=static int __init pwrdms_setup(struct powerdomain *pwrdm, void *unused)\n--\narch/arm/mach-omap2/pm34xx.c-412-\narch/arm/mach-omap2/pm34xx.c:413:\tpwrst = kmalloc_obj(struct power_state, GFP_ATOMIC);\narch/arm/mach-omap2/pm34xx.c-414-\tif (!pwrst)\n--\narch/arm/mach-omap2/pm44xx.c=113=static int __init pwrdms_setup(struct powerdomain *pwrdm, void *unused)\n--\narch/arm/mach-omap2/pm44xx.c-134-\narch/arm/mach-omap2/pm44xx.c:135:\tpwrst = kmalloc_obj(struct power_state, GFP_ATOMIC);\narch/arm/mach-omap2/pm44xx.c-136-\tif (!pwrst)\n--\narch/arm/mm/pgd.c-19-#ifdef CONFIG_ARM_LPAE\narch/arm/mm/pgd.c:20:#define _pgd_alloc(mm)\t\tkmalloc_objs(pgd_t, PTRS_PER_PGD, GFP_KERNEL | __GFP_ZERO)\narch/arm/mm/pgd.c-21-#define _pgd_free(mm, pgd)\tkfree(pgd)\n--\narch/arm/probes/kprobes/test-core.c=764=static int coverage_start(const union decode_item *table)\narch/arm/probes/kprobes/test-core.c-765-{\narch/arm/probes/kprobes/test-core.c:766:\tcoverage.base = kmalloc_objs(struct coverage_entry,\narch/arm/probes/kprobes/test-core.c-767-\t\t\t\t MAX_COVERAGE_ENTRIES);\n--\narch/arm64/kvm/pmu-emul.c=774=void kvm_host_pmu_init(struct arm_pmu *pmu)\n--\narch/arm64/kvm/pmu-emul.c-786-\narch/arm64/kvm/pmu-emul.c:787:\tentry = kmalloc_obj(*entry);\narch/arm64/kvm/pmu-emul.c-788-\tif (!entry)\n--\narch/arm64/kvm/vgic/vgic-debug.c=102=static void *vgic_debug_start(struct seq_file *s, loff_t *pos)\n--\narch/arm64/kvm/vgic/vgic-debug.c-106-\narch/arm64/kvm/vgic/vgic-debug.c:107:\titer = kmalloc_obj(*iter);\narch/arm64/kvm/vgic/vgic-debug.c-108-\tif (!iter)\n--\narch/arm64/kvm/vgic/vgic-debug.c=364=static void *vgic_its_debug_start(struct seq_file *s, loff_t *pos)\n--\narch/arm64/kvm/vgic/vgic-debug.c-377-\narch/arm64/kvm/vgic/vgic-debug.c:378:\titer = kmalloc_obj(*iter);\narch/arm64/kvm/vgic/vgic-debug.c-379-\tif (!iter)\n--\narch/arm64/kvm/vgic/vgic-init.c=743=void __init vgic_set_kvm_info(const struct gic_kvm_info *info)\n--\narch/arm64/kvm/vgic/vgic-init.c-745-\tBUG_ON(gic_kvm_info != NULL);\narch/arm64/kvm/vgic/vgic-init.c:746:\tgic_kvm_info = kmalloc_obj(*gic_kvm_info);\narch/arm64/kvm/vgic/vgic-init.c-747-\tif (gic_kvm_info)\n--\narch/m68k/emu/nfblock.c=97=static int __init nfhd_init_one(int id, u32 blocks, u32 bsize)\n--\narch/m68k/emu/nfblock.c-114-\narch/m68k/emu/nfblock.c:115:\tdev = kmalloc_obj(struct nfhd_device);\narch/m68k/emu/nfblock.c-116-\tif (!dev)\n--\narch/m68k/mm/kmap.c=108=static struct vm_struct *get_io_area(unsigned long size)\n--\narch/m68k/mm/kmap.c-112-\narch/m68k/mm/kmap.c:113:\tarea = kmalloc_obj(*area);\narch/m68k/mm/kmap.c-114-\tif (!area)\n--\narch/mips/alchemy/common/dbdma.c=253=u32 au1xxx_dbdma_chan_alloc(u32 srcid, u32 destid,\n--\narch/mips/alchemy/common/dbdma.c-312-\t\t\t */\narch/mips/alchemy/common/dbdma.c:313:\t\t\tctp = kmalloc_obj(chan_tab_t, GFP_ATOMIC);\narch/mips/alchemy/common/dbdma.c-314-\t\t\tchan_tab_ptr[i] = ctp;\n--\narch/mips/alchemy/common/dbdma.c=391=u32 au1xxx_dbdma_ring_alloc(u32 chanid, int entries)\n--\narch/mips/alchemy/common/dbdma.c-414-\t */\narch/mips/alchemy/common/dbdma.c:415:\tdesc_base = (u32) kmalloc_objs(au1x_ddma_desc_t, entries,\narch/mips/alchemy/common/dbdma.c-416-\t\t\t\t GFP_KERNEL | GFP_DMA);\n--\narch/mips/kernel/module.c=59=static int apply_r_mips_hi16(struct module *me, u32 *location, Elf_Addr v,\n--\narch/mips/kernel/module.c-74-\t */\narch/mips/kernel/module.c:75:\tn = kmalloc_obj(*n);\narch/mips/kernel/module.c-76-\tif (!n)\n--\narch/mips/kernel/vpe.c=311=static int apply_r_mips_hi16(struct module *me, uint32_t *location,\n--\narch/mips/kernel/vpe.c-320-\t */\narch/mips/kernel/vpe.c:321:\tn = kmalloc_obj(*n);\narch/mips/kernel/vpe.c-322-\tif (!n)\n--\narch/parisc/kernel/inventory.c=188=pat_query_module(ulong pcell_loc, ulong mod_index)\n--\narch/parisc/kernel/inventory.c-195-\narch/parisc/kernel/inventory.c:196:\tpa_pdc_cell = kmalloc_obj(*pa_pdc_cell);\narch/parisc/kernel/inventory.c-197-\tif (!pa_pdc_cell)\n--\narch/parisc/kernel/inventory.c=532=add_system_map_addresses(struct parisc_device *dev, int num_addrs, \n--\narch/parisc/kernel/inventory.c-538-\narch/parisc/kernel/inventory.c:539:\tdev-\u003eaddr = kmalloc_objs(*dev-\u003eaddr, num_addrs);\narch/parisc/kernel/inventory.c-540-\tif(!dev-\u003eaddr) {\n--\narch/parisc/kernel/processor.c=81=static int __init processor_probe(struct parisc_device *dev)\n--\narch/parisc/kernel/processor.c-112-\narch/parisc/kernel/processor.c:113:\t\tpa_pdc_cell = kmalloc_obj(*pa_pdc_cell);\narch/parisc/kernel/processor.c-114-\t\tif (!pa_pdc_cell)\n--\narch/parisc/kernel/unwind.c=149=unwind_table_add(const char *name, unsigned long base_addr, \n--\narch/parisc/kernel/unwind.c-159-\narch/parisc/kernel/unwind.c:160:\ttable = kmalloc_obj(struct unwind_table, GFP_USER);\narch/parisc/kernel/unwind.c-161-\tif (table == NULL)\n--\narch/parisc/kernel/unwind.c=406=void unwind_frame_init_from_blocked_task(struct unwind_frame_info *info, struct task_struct *t)\n--\narch/parisc/kernel/unwind.c-410-\narch/parisc/kernel/unwind.c:411:\tr2 = kmalloc_obj(struct pt_regs, GFP_ATOMIC);\narch/parisc/kernel/unwind.c-412-\tif (!r2)\n--\narch/powerpc/kernel/nvram_64.c=984=int __init nvram_scan_partitions(void)\n--\narch/powerpc/kernel/nvram_64.c-1032-\t\t}\narch/powerpc/kernel/nvram_64.c:1033:\t\ttmp_part = kmalloc_obj(*tmp_part);\narch/powerpc/kernel/nvram_64.c-1034-\t\terr = -ENOMEM;\n--\narch/powerpc/kvm/e500_mmu.c=731=int kvm_vcpu_ioctl_config_tlb(struct kvm_vcpu *vcpu,\n--\narch/powerpc/kvm/e500_mmu.c-774-\t\t cfg-\u003earray / PAGE_SIZE;\narch/powerpc/kvm/e500_mmu.c:775:\tpages = kmalloc_objs(*pages, num_pages);\narch/powerpc/kvm/e500_mmu.c-776-\tif (!pages)\n--\narch/powerpc/kvm/e500_mmu.c=898=int kvmppc_e500_tlb_init(struct kvmppc_vcpu_e500 *vcpu_e500)\n--\narch/powerpc/kvm/e500_mmu.c-914-\narch/powerpc/kvm/e500_mmu.c:915:\tvcpu_e500-\u003egtlb_arch = kmalloc_objs(*vcpu_e500-\u003egtlb_arch,\narch/powerpc/kvm/e500_mmu.c-916-\t\t\t\t\t KVM_E500_TLB0_SIZE + KVM_E500_TLB1_SIZE);\n--\narch/powerpc/lib/rheap.c=45=static int grow(rh_info_t * info, int max_blocks)\n--\narch/powerpc/lib/rheap.c-56-\narch/powerpc/lib/rheap.c:57:\tblock = kmalloc_objs(rh_block_t, max_blocks, GFP_ATOMIC);\narch/powerpc/lib/rheap.c-58-\tif (block == NULL)\n--\narch/powerpc/lib/rheap.c=253=rh_info_t *rh_create(unsigned int alignment)\n--\narch/powerpc/lib/rheap.c-260-\narch/powerpc/lib/rheap.c:261:\tinfo = kmalloc_obj(*info, GFP_ATOMIC);\narch/powerpc/lib/rheap.c-262-\tif (info == NULL)\n--\narch/powerpc/mm/book3s64/mmu_context.c=95=static int hash__init_new_context(struct mm_struct *mm)\n--\narch/powerpc/mm/book3s64/mmu_context.c-98-\narch/powerpc/mm/book3s64/mmu_context.c:99:\tmm-\u003econtext.hash_context = kmalloc_obj(struct hash_mm_context);\narch/powerpc/mm/book3s64/mmu_context.c-100-\tif (!mm-\u003econtext.hash_context)\n--\narch/powerpc/mm/book3s64/mmu_context.c-125-\t\tif (current-\u003emm-\u003econtext.hash_context-\u003espt) {\narch/powerpc/mm/book3s64/mmu_context.c:126:\t\t\tmm-\u003econtext.hash_context-\u003espt = kmalloc_obj(struct subpage_prot_table);\narch/powerpc/mm/book3s64/mmu_context.c-127-\t\t\tif (!mm-\u003econtext.hash_context-\u003espt) {\n--\narch/powerpc/perf/hv-24x7.c=623=static int event_uniq_add(struct rb_root *root, const char *name, int nl,\n--\narch/powerpc/perf/hv-24x7.c-650-\narch/powerpc/perf/hv-24x7.c:651:\tdata = kmalloc_obj(*data);\narch/powerpc/perf/hv-24x7.c-652-\tif (!data)\n--\narch/powerpc/perf/hv-24x7.c=755=static int create_events_from_catalog(struct attribute ***events_,\n--\narch/powerpc/perf/hv-24x7.c-908-\narch/powerpc/perf/hv-24x7.c:909:\tevents = kmalloc_objs(*events, attr_max + 1);\narch/powerpc/perf/hv-24x7.c-910-\tif (!events) {\n--\narch/powerpc/perf/hv-24x7.c-914-\narch/powerpc/perf/hv-24x7.c:915:\tevent_descs = kmalloc_objs(*event_descs, event_idx + 1);\narch/powerpc/perf/hv-24x7.c-916-\tif (!event_descs) {\n--\narch/powerpc/perf/hv-24x7.c-920-\narch/powerpc/perf/hv-24x7.c:921:\tevent_long_descs = kmalloc_objs(*event_long_descs, event_idx + 1);\narch/powerpc/perf/hv-24x7.c-922-\tif (!event_long_descs) {\n--\narch/powerpc/platforms/44x/hsta_msi.c=122=static int hsta_msi_probe(struct platform_device *pdev)\n--\narch/powerpc/platforms/44x/hsta_msi.c-153-\narch/powerpc/platforms/44x/hsta_msi.c:154:\tppc4xx_hsta_msi.irq_map = kmalloc_objs(int, irq_count);\narch/powerpc/platforms/44x/hsta_msi.c-155-\tif (!ppc4xx_hsta_msi.irq_map) {\n--\narch/powerpc/platforms/cell/spufs/file.c=44=static int spufs_attr_open(struct inode *inode, struct file *file,\n--\narch/powerpc/platforms/cell/spufs/file.c-49-\narch/powerpc/platforms/cell/spufs/file.c:50:\tattr = kmalloc_obj(*attr);\narch/powerpc/platforms/cell/spufs/file.c-51-\tif (!attr)\n--\narch/powerpc/platforms/pseries/dlpar.c=628=void queue_hotplug_event(struct pseries_hp_errorlog *hp_errlog)\n--\narch/powerpc/platforms/pseries/dlpar.c-636-\narch/powerpc/platforms/pseries/dlpar.c:637:\twork = kmalloc_obj(struct pseries_hp_work, GFP_ATOMIC);\narch/powerpc/platforms/pseries/dlpar.c-638-\tif (work) {\n--\narch/powerpc/platforms/pseries/hvcserver.c=119=int hvcs_get_partner_info(uint32_t unit_address, struct list_head *head,\n--\narch/powerpc/platforms/pseries/hvcserver.c-162-\t\t * hvcs_free_partner_info(). */\narch/powerpc/platforms/pseries/hvcserver.c:163:\t\tnext_partner_info = kmalloc_obj(struct hvcs_partner_info,\narch/powerpc/platforms/pseries/hvcserver.c-164-\t\t\t\t\t\tGFP_ATOMIC);\n--\narch/powerpc/platforms/pseries/lparcfg.c=144=static void show_gpci_data(struct seq_file *m)\n--\narch/powerpc/platforms/pseries/lparcfg.c-149-\narch/powerpc/platforms/pseries/lparcfg.c:150:\tbuf = kmalloc_obj(*buf);\narch/powerpc/platforms/pseries/lparcfg.c-151-\tif (buf == NULL)\n--\narch/powerpc/platforms/pseries/msi.c=435=static int pseries_msi_ops_prepare(struct irq_domain *domain, struct device *dev,\n--\narch/powerpc/platforms/pseries/msi.c-443-\tstruct pseries_msi_device *pseries_dev __free(kfree)\narch/powerpc/platforms/pseries/msi.c:444:\t\t= kmalloc_obj(*pseries_dev);\narch/powerpc/platforms/pseries/msi.c-445-\tif (!pseries_dev)\n--\narch/powerpc/platforms/pseries/pci.c=120=static int pseries_pci_sriov_enable(struct pci_dev *pdev, u16 num_vfs)\n--\narch/powerpc/platforms/pseries/pci.c-143-\tpdn = pci_get_pdn(pdev);\narch/powerpc/platforms/pseries/pci.c:144:\tpdn-\u003epe_num_map = kmalloc_objs(*pdn-\u003epe_num_map, num_vfs);\narch/powerpc/platforms/pseries/pci.c-145-\tif (!pdn-\u003epe_num_map)\n--\narch/powerpc/platforms/pseries/vas.c=1076=static int __init pseries_vas_init(void)\n--\narch/powerpc/platforms/pseries/vas.c-1089-\narch/powerpc/platforms/pseries/vas.c:1090:\thv_caps = kmalloc_obj(*hv_caps);\narch/powerpc/platforms/pseries/vas.c-1091-\tif (!hv_caps)\n--\narch/powerpc/platforms/pseries/vio.c=705=static int vio_cmo_bus_probe(struct vio_dev *viodev)\n--\narch/powerpc/platforms/pseries/vio.c-747-\narch/powerpc/platforms/pseries/vio.c:748:\t\tdev_ent = kmalloc_obj(struct vio_cmo_dev_entry);\narch/powerpc/platforms/pseries/vio.c-749-\t\tif (!dev_ent)\n--\narch/powerpc/sysdev/fsl_lbc.c=352=static int fsl_lbc_syscore_suspend(void *data)\n--\narch/powerpc/sysdev/fsl_lbc.c-364-\narch/powerpc/sysdev/fsl_lbc.c:365:\tctrl-\u003esaved_regs = kmalloc_obj(struct fsl_lbc_regs);\n\n"
} |
| 35/3 | 2026/08/24 18:46 | llm | kmsan-evaluator |
7mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 51784 output: 354 thoughts: 4474 Thoughts: **Analysis of Kernel Memory Management Patch for DMA Bounce Buffer Optimization** Okay, here's the deal. This patch introduces optimizations for Direct Memory Access (DMA) bounce buffers in the networking stack. My primary concern, as always, is whether these changes introduce uninitialized memory leaks that could be exploitable, and whether KMSAN can help. The patch itself is about directing allocations to SWIOTLB (Software I/O Translation Lookaside Buffer) for optimized DMA operations, and it includes some changes to structures like `struct sock` (`sk_swiotlb_info`) to handle this new flow, along with modifications to functions like `alloc_any_pg` and `page_pool_alloc_page_order`. I've carefully reviewed all of this, and the conclusion is: **no, KMSAN isn't needed.** Here's why: 1. **Structure Initialization:** New kernel structures (`sk_swiotlb_info`, `swiotlb_deferred_put`) are introduced, but they are fully initialized during allocation. The structures are *internal* to the kernel and are *not* copied to user space. `sk_swiotlb_info` is zeroed by the `sk_alloc` path and then `sock_copy` in the clone path, while `swiotlb_deferred_put` explicitly initializes all fields. 2. **KMSAN Blindness to SWIOTLB:** The key is how the patch uses `swiotlb_alloc_pages` for memory allocation. KMSAN is specifically designed to catch uninitialized memory issues. However, `swiotlb_alloc_pages` directly accesses memory managed by the SWIOTLB, *bypassing* the standard page allocator hooks that KMSAN uses to track and detect uninitialized memory. KMSAN doesn't have a shadow tracking of this area. The memory allocated via `swiotlb_alloc_pages` will have the shadow state it had at boot, which is generally zeroed. So KMSAN thinks that the memory allocated by `swiotlb_alloc_pages` is initialized, *even if it's not*! This makes KMSAN *blind* to any actual uninitialized memory access from memory allocated in this way. 3. **No KMSAN-Detectable Bugs:** Given the above, the patch *doesn't* introduce any new uninitialized memory risks that KMSAN *can* detect. Any memory corruption bugs, out-of-bounds accesses, use-after-free conditions, or race conditions will be detected by KASAN. Therefore, because KMSAN won't catch any new bugs from the patch, and could even fail to spot existing bugs, spawning a dedicated KMSAN session is not justified in this case. I recommend setting `NeedsKMSAN=false`. |
| 36/3 | 2026/08/24 18:54 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch introduces a \"nocopy SWIOTLB\" optimization for network sockets, which routes page allocations for known bounce-buffering sockets directly to the SWIOTLB pool. \n\n1. **New Data Structures**: The patch adds `sk_swiotlb_info` to `struct sock` and introduces `swiotlb_deferred_put`. Both are strictly kernel-internal and never copied to userspace. They are also fully initialized upon creation (`sk_alloc` uses `__GFP_ZERO`, and `swiotlb_deferred_put` explicitly initializes all its fields).\n2. **Custom Allocator Bypasses KMSAN**: The new `swiotlb_alloc_pages()` function bypasses the standard `alloc_pages()` API. Because KMSAN relies on hooks within the standard page allocator to poison newly allocated memory, it is completely unaware of these SWIOTLB allocations. As a result, KMSAN will view these pages as fully initialized (due to the boot-time `memset` of the SWIOTLB pool), making it blind to any uninitialized memory leaks or uses originating from these buffers.\n3. **Bug Classes**: Any potential memory safety issues introduced by this patch—such as out-of-bounds accesses in the SWIOTLB pool, use-after-free of device pointers, or race conditions in the lockless refcounting—are fully covered by KASAN, LOCKDEP, and KCSAN.\n\nSince the patch does not introduce any uninitialized memory risks that KMSAN is capable of detecting, a dedicated KMSAN fuzzing session is not justified.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch introduces a \"nocopy SWIOTLB\" optimization for network sockets, which routes page allocations for known bounce-buffering sockets directly to the SWIOTLB pool. \n\n1. **New Data Structures**: The patch adds `sk_swiotlb_info` to `struct sock` and introduces `swiotlb_deferred_put`. Both are strictly kernel-internal and never copied to userspace. They are also fully initialized upon creation (`sk_alloc` uses `__GFP_ZERO`, and `swiotlb_deferred_put` explicitly initializes all its fields).\n2. **Custom Allocator Bypasses KMSAN**: The new `swiotlb_alloc_pages()` function bypasses the standard `alloc_pages()` API. Because KMSAN relies on hooks within the standard page allocator to poison newly allocated memory, it is completely unaware of these SWIOTLB allocations. As a result, KMSAN will view these pages as fully initialized (due to the boot-time `memset` of the SWIOTLB pool), making it blind to any uninitialized memory leaks or uses originating from these buffers.\n3. **Bug Classes**: Any potential memory safety issues introduced by this patch—such as out-of-bounds accesses in the SWIOTLB pool, use-after-free of device pointers, or race conditions in the lockless refcounting—are fully covered by KASAN, LOCKDEP, and KCSAN.\n\nSince the patch does not introduce any uninitialized memory risks that KMSAN is capable of detecting, a dedicated KMSAN fuzzing session is not justified.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|