| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/08/18 19:49 | flow | patch-triage |
4mArgs:null Results: {
"EnableConfigs": [
"SWAP",
"ZSWAP",
"ZSWAP_DEFAULT_ON"
],
"FocusSymbols": [
"swap_writeback_dropbehind_folio",
"remove_mapping",
"__swap_cache_alloc_folio"
],
"KMSANReasoning": "The patch series modifies the internal memory management subsystem, specifically focusing on swap cache allocation, writeback, and LRU management (introducing a \"dropbehind\" mechanism for zswap writeback). The changes involve page lifecycle management, workqueues for freeing folios, and modifying function signatures like `remove_mapping` and `swap_cache_alloc_folio`.\n\nThese modifications do not introduce new data structures that are copied to user space, nor do they alter bounds checking or initialization of memory buffers. Any potential bugs introduced by these changes would manifest as use-after-free, double-free, memory leaks, or NULL pointer dereferences. These types of memory corruption and lifecycle bugs are effectively detected by standard KASAN, KMEMLEAK, and LOCKDEP. There is no risk of exposing uninitialized memory to user space or using uninitialized memory in control flow, which are the specific classes of bugs KMSAN is designed to catch. Therefore, a dedicated KMSAN fuzzing session is not necessary.",
"NeedsKMSAN": false,
"Reasoning": "The patch introduces a 'dropbehind' mechanism for swap cache folios during zswap writeback to avoid putting them on the LRU list. It modifies core memory management functions like `remove_mapping`, `folio_end_writeback`, and adds `swap_writeback_dropbehind_folio` which uses a workqueue to drop folios from the swap cache. This is a functional change in the core MM subsystem that should be fuzzed.",
"WorthFuzzing": true
} |
| 1/1 | 2026/08/18 19:49 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 862c847d54f3ccb96cb4797e3279ad4836fbeb17\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Tue Aug 18 19:49:50 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/fs/splice.c b/fs/splice.c\nindex 9d8f63e2fd1ab..79424bbee1b0f 100644\n--- a/fs/splice.c\n+++ b/fs/splice.c\n@@ -90,7 +90,7 @@ static bool page_cache_pipe_buf_try_steal(struct pipe_inode_info *pipe,\n \t\t * If we succeeded in removing the mapping, set LRU flag\n \t\t * and return good.\n \t\t */\n-\t\tif (remove_mapping(mapping, folio)) {\n+\t\tif (remove_mapping(mapping, folio, false, NULL)) {\n \t\t\tbuf-\u003eflags |= PIPE_BUF_FLAG_LRU;\n \t\t\treturn true;\n \t\t}\ndiff --git a/include/linux/swap.h b/include/linux/swap.h\nindex 8f0f68e245baa..bdaffc0d1ef31 100644\n--- a/include/linux/swap.h\n+++ b/include/linux/swap.h\n@@ -373,7 +373,8 @@ extern unsigned long mem_cgroup_shrink_node(struct mem_cgroup *mem,\n \t\t\t\t\t\tunsigned long *nr_scanned);\n extern unsigned long shrink_all_memory(unsigned long nr_pages);\n extern int vm_swappiness;\n-long remove_mapping(struct address_space *mapping, struct folio *folio);\n+long remove_mapping(struct address_space *mapping, struct folio *folio,\n+\t\t bool reclaimed, struct mem_cgroup *target_memcg);\n \n #if defined(CONFIG_SYSFS) \u0026\u0026 defined(CONFIG_NUMA)\n extern int reclaim_register_node(struct node *node);\n@@ -465,6 +466,8 @@ void swap_put_entries_direct(swp_entry_t entry, int nr);\n */\n bool folio_free_swap(struct folio *folio);\n \n+void swap_writeback_dropbehind_folio(struct folio *folio);\n+\n /* Allocate / free (hibernation) exclusive entries */\n swp_entry_t swap_alloc_hibernation_slot(int type);\n void swap_free_hibernation_slot(swp_entry_t entry);\n@@ -475,6 +478,7 @@ static inline void put_swap_device(struct swap_info_struct *si)\n }\n \n #else /* CONFIG_SWAP */\n+static inline void swap_writeback_dropbehind_folio(struct folio *folio) {}\n static inline struct swap_info_struct *get_swap_device(swp_entry_t entry)\n {\n \treturn NULL;\ndiff --git a/mm/filemap.c b/mm/filemap.c\nindex d721986d5f465..1647961c5ba20 100644\n--- a/mm/filemap.c\n+++ b/mm/filemap.c\n@@ -1686,6 +1686,8 @@ EXPORT_SYMBOL_GPL(folio_end_writeback_no_dropbehind);\n */\n void folio_end_writeback(struct folio *folio)\n {\n+\tbool swap_dropbehind;\n+\n \tVM_BUG_ON_FOLIO(!folio_test_writeback(folio), folio);\n \n \t/*\n@@ -1695,7 +1697,24 @@ void folio_end_writeback(struct folio *folio)\n \t * reused before the folio_wake_bit().\n \t */\n \tfolio_get(folio);\n+\n+\t/*\n+\t * Dropbehind swap cache folios are off-LRU, so we must prevent a racing\n+\t * swapin from removing the folio from the swap cache and keeping it\n+\t * off-LRU: the writeback flag allows that. Afterwards a swapin may win\n+\t * the race, but the folio is already queued and the worker puts it back\n+\t * on the LRU in that case.\n+\t */\n+\tswap_dropbehind = folio_test_swapcache(folio) \u0026\u0026\n+\t\t\t folio_test_dropbehind(folio);\n+\n \tfolio_end_writeback_no_dropbehind(folio);\n+\n+\tif (swap_dropbehind) {\n+\t\tswap_writeback_dropbehind_folio(folio);\n+\t\treturn;\n+\t}\n+\n \tfolio_end_dropbehind(folio);\n \tfolio_put(folio);\n }\ndiff --git a/mm/swap.h b/mm/swap.h\nindex 77d2d14eda421..fc44daae1de1f 100644\n--- a/mm/swap.h\n+++ b/mm/swap.h\n@@ -304,9 +304,9 @@ bool swap_cache_has_folio(swp_entry_t entry);\n struct folio *swap_cache_get_folio(swp_entry_t entry);\n void *swap_cache_get_shadow(swp_entry_t entry);\n void swap_cache_del_folio(struct folio *folio);\n-struct folio *swap_cache_alloc_folio(swp_entry_t target_entry, gfp_t gfp_mask,\n-\t\t\t\t unsigned long orders, struct vm_fault *vmf,\n-\t\t\t\t struct mempolicy *mpol, pgoff_t ilx);\n+struct folio *__swap_cache_alloc_folio(swp_entry_t target_entry, gfp_t gfp_mask,\n+\t\t\t\t unsigned long orders, struct vm_fault *vmf,\n+\t\t\t\t struct mempolicy *mpol, pgoff_t ilx);\n /* Below helpers require the caller to lock and pass in the swap cluster. */\n void __swap_cache_add_folio(struct swap_cluster_info *ci,\n \t\t\t struct folio *folio, swp_entry_t entry);\ndiff --git a/mm/swap_state.c b/mm/swap_state.c\nindex 9c3a5cf997786..b93dd607d24c6 100644\n--- a/mm/swap_state.c\n+++ b/mm/swap_state.c\n@@ -16,6 +16,8 @@\n #include \u003clinux/init.h\u003e\n #include \u003clinux/pagemap.h\u003e\n #include \u003clinux/folio_batch.h\u003e\n+#include \u003clinux/llist.h\u003e\n+#include \u003clinux/workqueue.h\u003e\n #include \u003clinux/backing-dev.h\u003e\n #include \u003clinux/blkdev.h\u003e\n #include \u003clinux/migrate.h\u003e\n@@ -483,13 +485,11 @@ static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,\n \tnode_stat_mod_folio(folio, NR_FILE_PAGES, nr_pages);\n \tlruvec_stat_mod_folio(folio, NR_SWAPCACHE, nr_pages);\n \n-\t/* Caller will initiate read into locked new_folio */\n-\tfolio_add_lru(folio);\n \treturn folio;\n }\n \n /**\n- * swap_cache_alloc_folio - Allocate folio for swapped out slot in swap cache.\n+ * __swap_cache_alloc_folio - Allocate folio for swapped out slot in swap cache.\n * @targ_entry: swap entry indicating the target slot\n * @gfp: memory allocation flags\n * @orders: allocation orders, must be non zero\n@@ -501,13 +501,17 @@ static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,\n * doing IO (e.g. swap in or zswap writeback). The swap slot indicated by\n * @targ_entry must have a non-zero swap count (swapped out).\n *\n+ * The returned folio is locked and is NOT on the LRU. The caller must either\n+ * add it to the LRU with folio_add_lru() so page reclaim can find it, or free\n+ * it directly once done; a folio left off the LRU is unreclaimable and leaks.\n+ *\n * Context: Caller must protect the swap device with reference count or locks.\n * Return: Returns the folio if allocation succeeded and folio is in the swap\n * cache. Returns error code if failed due to race, OOM or invalid arguments.\n */\n-struct folio *swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,\n-\t\t\t\t unsigned long orders, struct vm_fault *vmf,\n-\t\t\t\t struct mempolicy *mpol, pgoff_t ilx)\n+struct folio *__swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,\n+\t\t\t\t unsigned long orders, struct vm_fault *vmf,\n+\t\t\t\t struct mempolicy *mpol, pgoff_t ilx)\n {\n \tint order, err;\n \tstruct folio *ret;\n@@ -535,6 +539,98 @@ struct folio *swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,\n \treturn ret;\n }\n \n+static DEFINE_PER_CPU(struct llist_head, swap_dropbehind_llist);\n+\n+static bool swap_dropbehind_drop_folio(struct folio *folio)\n+{\n+\tstruct mem_cgroup *memcg;\n+\tbool dropped = false;\n+\n+\tfolio_lock(folio);\n+\n+\t/* The folio was allocated off the LRU and nothing re-adds it here. */\n+\tVM_WARN_ON_ONCE_FOLIO(folio_test_lru(folio), folio);\n+\n+\trcu_read_lock();\n+\tmemcg = folio_memcg(folio);\n+\tif (!mem_cgroup_tryget(memcg))\n+\t\tmemcg = NULL;\n+\trcu_read_unlock();\n+\n+\t/*\n+\t * Gate remove_mapping() on folio_test_swapcache(): a racing swapin may\n+\t * have freed the swap slot (folio_free_swap()) and dropped the folio from\n+\t * the cache, and remove_mapping() must not run on a non-swapcache folio\n+\t * (it would trip __remove_mapping()'s mapping == folio_mapping() check).\n+\t */\n+\tif (folio_test_swapcache(folio) \u0026\u0026 !folio_test_writeback(folio) \u0026\u0026\n+\t remove_mapping(swap_address_space(folio-\u003eswap), folio, true, memcg)) {\n+\t\tdropped = true;\n+\t} else {\n+\t\t/* Raced: the folio is now owned by the swapin; put it back. */\n+\t\tfolio_clear_dropbehind(folio);\n+\t\tfolio_add_lru(folio);\n+\t}\n+\n+\tmem_cgroup_put(memcg);\n+\n+\tfolio_unlock(folio);\n+\tif (!dropped)\n+\t\tfolio_put(folio);\n+\treturn dropped;\n+}\n+\n+/**\n+ * swap_dropbehind_free_batch_folio - free a dropbehind swap cache folio into a batch\n+ * @folio: the off-LRU folio whose writeback has completed\n+ * @fbatch: batch of folios to free, flushed when full\n+ */\n+static void swap_dropbehind_free_batch_folio(struct folio *folio,\n+\t\t\t\t\t struct folio_batch *fbatch)\n+{\n+\tif (swap_dropbehind_drop_folio(folio) \u0026\u0026 !folio_batch_add(fbatch, folio))\n+\t\tfolios_put(fbatch);\n+}\n+\n+static void swap_dropbehind_workfn(struct work_struct *work)\n+{\n+\tstruct folio_batch fbatch;\n+\tstruct llist_node *pos, *next;\n+\tint cpu;\n+\n+\tfolio_batch_init(\u0026fbatch);\n+\tfor_each_possible_cpu(cpu) {\n+\t\tpos = llist_del_all(per_cpu_ptr(\u0026swap_dropbehind_llist, cpu));\n+\t\tllist_for_each_safe(pos, next, pos) {\n+\t\t\tstruct folio *folio = container_of((struct list_head *)pos,\n+\t\t\t\t\t\t\t struct folio, lru);\n+\t\t\tswap_dropbehind_free_batch_folio(folio, \u0026fbatch);\n+\t\t}\n+\t}\n+\tif (fbatch.nr)\n+\t\tfolios_put(\u0026fbatch);\n+}\n+\n+static DECLARE_WORK(swap_dropbehind_work, swap_dropbehind_workfn);\n+static struct workqueue_struct *swap_dropbehind_wq;\n+\n+void swap_writeback_dropbehind_folio(struct folio *folio)\n+{\n+\tllist_add((struct llist_node *)\u0026folio-\u003elru,\n+\t\t raw_cpu_ptr(\u0026swap_dropbehind_llist));\n+\tqueue_work(swap_dropbehind_wq, \u0026swap_dropbehind_work);\n+}\n+\n+static int __init swap_dropbehind_init(void)\n+{\n+\tswap_dropbehind_wq = alloc_workqueue(\"swap_dropbehind\",\n+\t\t\t\t\t WQ_MEM_RECLAIM | WQ_PERCPU, 0);\n+\tif (!swap_dropbehind_wq)\n+\t\treturn -ENOMEM;\n+\treturn 0;\n+}\n+core_initcall(swap_dropbehind_init);\n+\n /*\n * If we are the only user, then try to free up the swap cache.\n *\n@@ -643,12 +739,13 @@ static struct folio *swap_cache_read_folio(swp_entry_t entry, gfp_t gfp,\n \t\tfolio = swap_cache_get_folio(entry);\n \t\tif (folio)\n \t\t\treturn folio;\n-\t\tfolio = swap_cache_alloc_folio(entry, gfp, BIT(0), NULL, mpol, ilx);\n+\t\tfolio = __swap_cache_alloc_folio(entry, gfp, BIT(0), NULL, mpol, ilx);\n \t} while (PTR_ERR(folio) == -EEXIST);\n \n \tif (IS_ERR_OR_NULL(folio))\n \t\treturn NULL;\n \n+\tfolio_add_lru(folio);\n \tswap_read_folio(folio, plug);\n \tif (readahead) {\n \t\tfolio_set_readahead(folio);\n@@ -683,12 +780,13 @@ struct folio *swapin_sync(swp_entry_t entry, gfp_t gfp, unsigned long orders,\n \t\tfolio = swap_cache_get_folio(entry);\n \t\tif (folio)\n \t\t\treturn folio;\n-\t\tfolio = swap_cache_alloc_folio(entry, gfp, orders, vmf, mpol, ilx);\n+\t\tfolio = __swap_cache_alloc_folio(entry, gfp, orders, vmf, mpol, ilx);\n \t} while (PTR_ERR(folio) == -EEXIST);\n \n \tif (IS_ERR(folio))\n \t\treturn folio;\n \n+\tfolio_add_lru(folio);\n \tswap_read_folio(folio, NULL);\n \treturn folio;\n }\ndiff --git a/mm/truncate.c b/mm/truncate.c\nindex b58ba940be474..37f7d8278c1cd 100644\n--- a/mm/truncate.c\n+++ b/mm/truncate.c\n@@ -336,7 +336,7 @@ long mapping_evict_folio(struct address_space *mapping, struct folio *folio)\n \tif (!filemap_release_folio(folio, 0))\n \t\treturn 0;\n \n-\treturn remove_mapping(mapping, folio);\n+\treturn remove_mapping(mapping, folio, false, NULL);\n }\n \n /**\ndiff --git a/mm/vmscan.c b/mm/vmscan.c\nindex 56708d1d2dfd5..c40513e0b50b2 100644\n--- a/mm/vmscan.c\n+++ b/mm/vmscan.c\n@@ -784,17 +784,24 @@ static int __remove_mapping(struct address_space *mapping, struct folio *folio,\n * remove_mapping() - Attempt to remove a folio from its mapping.\n * @mapping: The address space.\n * @folio: The folio to remove.\n+ * @reclaimed: Whether the folio is being reclaimed (record a shadow).\n+ * @target_memcg: The memcg to charge the eviction shadow to when @reclaimed;\n+ * the caller must keep it alive across the call. Ignored (may\n+ * be NULL) when @reclaimed is false.\n *\n * If the folio is dirty, under writeback or if someone else has a ref\n- * on it, removal will fail.\n+ * on it, removal will fail. When @reclaimed is true, a workingset eviction\n+ * shadow is stored (like page reclaim does) so that a later refault can be\n+ * detected and the folio re-activated.\n * Return: The number of pages removed from the mapping. 0 if the folio\n * could not be removed.\n * Context: The caller should have a single refcount on the folio and\n * hold its lock.\n */\n-long remove_mapping(struct address_space *mapping, struct folio *folio)\n+long remove_mapping(struct address_space *mapping, struct folio *folio,\n+\t\t bool reclaimed, struct mem_cgroup *target_memcg)\n {\n-\tif (__remove_mapping(mapping, folio, false, NULL)) {\n+\tif (__remove_mapping(mapping, folio, reclaimed, target_memcg)) {\n \t\t/*\n \t\t * Unfreezing the refcount with 1 effectively\n \t\t * drops the pagecache ref for us without requiring another\ndiff --git a/mm/zswap.c b/mm/zswap.c\nindex 761cd699e0a3e..a35671e837f89 100644\n--- a/mm/zswap.c\n+++ b/mm/zswap.c\n@@ -1000,8 +1000,8 @@ static int zswap_writeback_entry(struct zswap_entry *entry,\n \t\treturn -EEXIST;\n \n \tmpol = get_task_policy(current);\n-\tfolio = swap_cache_alloc_folio(swpentry, GFP_KERNEL, BIT(0), NULL, mpol,\n-\t\t\t\t NO_INTERLEAVE_INDEX);\n+\tfolio = __swap_cache_alloc_folio(swpentry, GFP_KERNEL, BIT(0), NULL, mpol,\n+\t\t\t\t\t NO_INTERLEAVE_INDEX);\n \tput_swap_device(si);\n \n \t/*\n@@ -1045,8 +1045,7 @@ static int zswap_writeback_entry(struct zswap_entry *entry,\n \t/* folio is up to date */\n \tfolio_mark_uptodate(folio);\n \n-\t/* move it to the tail of the inactive list after end_writeback */\n-\tfolio_set_reclaim(folio);\n+\tfolio_set_dropbehind(folio);\n \n \t/* start writeback */\n \t__swap_writepage(folio, NULL);\n"
} |
| 2/1 | 2026/08/18 19:49 | agent | patch-evaluator |
4mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"SWAP",
"ZSWAP",
"ZSWAP_DEFAULT_ON"
],
"FocusSymbols": [
"swap_writeback_dropbehind_folio",
"remove_mapping",
"__swap_cache_alloc_folio"
],
"Reasoning": "The patch introduces a 'dropbehind' mechanism for swap cache folios during zswap writeback to avoid putting them on the LRU list. It modifies core memory management functions like `remove_mapping`, `folio_end_writeback`, and adds `swap_writeback_dropbehind_folio` which uses a workqueue to drop folios from the swap cache. This is a functional change in the core MM subsystem that 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 862c847d54f3ccb96cb4797e3279ad4836fbeb17
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Aug 18 19:49:50 2026 +0000
syz-cluster: applied patch under review
diff --git a/fs/splice.c b/fs/splice.c
index 9d8f63e2fd1ab..79424bbee1b0f 100644
--- a/fs/splice.c
+++ b/fs/splice.c
@@ -90,7 +90,7 @@ static bool page_cache_pipe_buf_try_steal(struct pipe_inode_info *pipe,
* If we succeeded in removing the mapping, set LRU flag
* and return good.
*/
- if (remove_mapping(mapping, folio)) {
+ if (remove_mapping(mapping, folio, false, NULL)) {
buf->flags |= PIPE_BUF_FLAG_LRU;
return true;
}
diff --git a/include/linux/swap.h b/include/linux/swap.h
index 8f0f68e245baa..bdaffc0d1ef31 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -373,7 +373,8 @@ extern unsigned long mem_cgroup_shrink_node(struct mem_cgroup *mem,
unsigned long *nr_scanned);
extern unsigned long shrink_all_memory(unsigned long nr_pages);
extern int vm_swappiness;
-long remove_mapping(struct address_space *mapping, struct folio *folio);
+long remove_mapping(struct address_space *mapping, struct folio *folio,
+ bool reclaimed, struct mem_cgroup *target_memcg);
#if defined(CONFIG_SYSFS) && defined(CONFIG_NUMA)
extern int reclaim_register_node(struct node *node);
@@ -465,6 +466,8 @@ void swap_put_entries_direct(swp_entry_t entry, int nr);
*/
bool folio_free_swap(struct folio *folio);
+void swap_writeback_dropbehind_folio(struct folio *folio);
+
/* Allocate / free (hibernation) exclusive entries */
swp_entry_t swap_alloc_hibernation_slot(int type);
void swap_free_hibernation_slot(swp_entry_t entry);
@@ -475,6 +478,7 @@ static inline void put_swap_device(struct swap_info_struct *si)
}
#else /* CONFIG_SWAP */
+static inline void swap_writeback_dropbehind_folio(struct folio *folio) {}
static inline struct swap_info_struct *get_swap_device(swp_entry_t entry)
{
return NULL;
diff --git a/mm/filemap.c b/mm/filemap.c
index d721986d5f465..1647961c5ba20 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -1686,6 +1686,8 @@ EXPORT_SYMBOL_GPL(folio_end_writeback_no_dropbehind);
*/
void folio_end_writeback(struct folio *folio)
{
+ bool swap_dropbehind;
+
VM_BUG_ON_FOLIO(!folio_test_writeback(folio), folio);
/*
@@ -1695,7 +1697,24 @@ void folio_end_writeback(struct folio *folio)
* reused before the folio_wake_bit().
*/
folio_get(folio);
+
+ /*
+ * Dropbehind swap cache folios are off-LRU, so we must prevent a racing
+ * swapin from removing the folio from the swap cache and keeping it
+ * off-LRU: the writeback flag allows that. Afterwards a swapin may win
+ * the race, but the folio is already queued and the worker puts it back
+ * on the LRU in that case.
+ */
+ swap_dropbehind = folio_test_swapcache(folio) &&
+ folio_test_dropbehind(folio);
+
folio_end_writeback_no_dropbehind(folio);
+
+ if (swap_dropbehind) {
+ swap_writeback_dropbehind_folio(folio);
+ return;
+ }
+
folio_end_dropbehind(folio);
folio_put(folio);
}
diff --git a/mm/swap.h b/mm/swap.h
index 77d2d14eda421..fc44daae1de1f 100644
--- a/mm/swap.h
+++ b/mm/swap.h
@@ -304,9 +304,9 @@ bool swap_cache_has_folio(swp_entry_t entry);
struct folio *swap_cache_get_folio(swp_entry_t entry);
void *swap_cache_get_shadow(swp_entry_t entry);
void swap_cache_del_folio(struct folio *folio);
-struct folio *swap_cache_alloc_folio(swp_entry_t target_entry, gfp_t gfp_mask,
- unsigned long orders, struct vm_fault *vmf,
- struct mempolicy *mpol, pgoff_t ilx);
+struct folio *__swap_cache_alloc_folio(swp_entry_t target_entry, gfp_t gfp_mask,
+ unsigned long orders, struct vm_fault *vmf,
+ struct mempolicy *mpol, pgoff_t ilx);
/* Below helpers require the caller to lock and pass in the swap cluster. */
void __swap_cache_add_folio(struct swap_cluster_info *ci,
struct folio *folio, swp_entry_t entry);
diff --git a/mm/swap_state.c b/mm/swap_state.c
index 9c3a5cf997786..b93dd607d24c6 100644
--- a/mm/swap_state.c
+++ b/mm/swap_state.c
@@ -16,6 +16,8 @@
#include <linux/init.h>
#include <linux/pagemap.h>
#include <linux/folio_batch.h>
+#include <linux/llist.h>
+#include <linux/workqueue.h>
#include <linux/backing-dev.h>
#include <linux/blkdev.h>
#include <linux/migrate.h>
@@ -483,13 +485,11 @@ static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,
node_stat_mod_folio(folio, NR_FILE_PAGES, nr_pages);
lruvec_stat_mod_folio(folio, NR_SWAPCACHE, nr_pages);
- /* Caller will initiate read into locked new_folio */
- folio_add_lru(folio);
return folio;
}
/**
- * swap_cache_alloc_folio - Allocate folio for swapped out slot in swap cache.
+ * __swap_cache_alloc_folio - Allocate folio for swapped out slot in swap cache.
* @targ_entry: swap entry indicating the target slot
* @gfp: memory allocation flags
* @orders: allocation orders, must be non zero
@@ -501,13 +501,17 @@ static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,
* doing IO (e.g. swap in or zswap writeback). The swap slot indicated by
* @targ_entry must have a non-zero swap count (swapped out).
*
+ * The returned folio is locked and is NOT on the LRU. The caller must either
+ * add it to the LRU with folio_add_lru() so page reclaim can find it, or free
+ * it directly once done; a folio left off the LRU is unreclaimable and leaks.
+ *
* Context: Caller must protect the swap device with reference count or locks.
* Return: Returns the folio if allocation succeeded and folio is in the swap
* cache. Returns error code if failed due to race, OOM or invalid arguments.
*/
-struct folio *swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,
- unsigned long orders, struct vm_fault *vmf,
- struct mempolicy *mpol, pgoff_t ilx)
+struct folio *__swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,
+ unsigned long orders, struct vm_fault *vmf,
+ struct mempolicy *mpol, pgoff_t ilx)
{
int order, err;
struct folio *ret;
@@ -535,6 +539,98 @@ struct folio *swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,
return ret;
}
+static DEFINE_PER_CPU(struct llist_head, swap_dropbehind_llist);
+
+static bool swap_dropbehind_drop_folio(struct folio *folio)
+{
+ struct mem_cgroup *memcg;
+ bool dropped = false;
+
+ folio_lock(folio);
+
+ /* The folio was allocated off the LRU and nothing re-adds it here. */
+ VM_WARN_ON_ONCE_FOLIO(folio_test_lru(folio), folio);
+
+ rcu_read_lock();
+ memcg = folio_memcg(folio);
+ if (!mem_cgroup_tryget(memcg))
+ memcg = NULL;
+ rcu_read_unlock();
+
+ /*
+ * Gate remove_mapping() on folio_test_swapcache(): a racing swapin may
+ * have freed the swap slot (folio_free_swap()) and dropped the folio from
+ * the cache, and remove_mapping() must not run on a non-swapcache folio
+ * (it would trip __remove_mapping()'s mapping == folio_mapping() check).
+ */
+ if (folio_test_swapcache(folio) && !folio_test_writeback(folio) &&
+ remove_mapping(swap_address_space(folio->swap), folio, true, memcg)) {
+ dropped = true;
+ } else {
+ /* Raced: the folio is now owned by the swapin; put it back. */
+ folio_clear_dropbehind(folio);
+ folio_add_lru(folio);
+ }
+
+ mem_cgroup_put(memcg);
+
+ folio_unlock(folio);
+ if (!dropped)
+ folio_put(folio);
+ return dropped;
+}
+
+/**
+ * swap_dropbehind_free_batch_folio - free a dropbehind swap cache folio into a batch
+ * @folio: the off-LRU folio whose writeback has completed
+ * @fbatch: batch of folios to free, flushed when full
+ */
+static void swap_dropbehind_free_batch_folio(struct folio *folio,
+ struct folio_batch *fbatch)
+{
+ if (swap_dropbehind_drop_folio(folio) && !folio_batch_add(fbatch, folio))
+ folios_put(fbatch);
+}
+
+static void swap_dropbehind_workfn(struct work_struct *work)
+{
+ struct folio_batch fbatch;
+ struct llist_node *pos, *next;
+ int cpu;
+
+ folio_batch_init(&fbatch);
+ for_each_possible_cpu(cpu) {
+ pos = llist_del_all(per_cpu_ptr(&swap_dropbehind_llist, cpu));
+ llist_for_each_safe(pos, next, pos) {
+ struct folio *folio = container_of((struct list_head *)pos,
+ struct folio, lru);
+ swap_dropbehind_free_batch_folio(folio, &fbatch);
+ }
+ }
+ if (fbatch.nr)
+ folios_put(&fbatch);
+}
+
+static DECLARE_WORK(swap_dropbehind_work, swap_dropbehind_workfn);
+static struct workqueue_struct *swap_dropbehind_wq;
+
+void swap_writeback_dropbehind_folio(struct folio *folio)
+{
+ llist_add((struct llist_node *)&folio->lru,
+ raw_cpu_ptr(&swap_dropbehind_llist));
+ queue_work(swap_dropbehind_wq, &swap_dropbehind_work);
+}
+
+static int __init swap_dropbehind_init(void)
+{
+ swap_dropbehind_wq = alloc_workqueue("swap_dropbehind",
+ WQ_MEM_RECLAIM | WQ_PERCPU, 0);
+ if (!swap_dropbehind_wq)
+ return -ENOMEM;
+ return 0;
+}
+core_initcall(swap_dropbehind_init);
+
/*
* If we are the only user, then try to free up the swap cache.
*
@@ -643,12 +739,13 @@ static struct folio *swap_cache_read_folio(swp_entry_t entry, gfp_t gfp,
folio = swap_cache_get_folio(entry);
if (folio)
return folio;
- folio = swap_cache_alloc_folio(entry, gfp, BIT(0), NULL, mpol, ilx);
+ folio = __swap_cache_alloc_folio(entry, gfp, BIT(0), NULL, mpol, ilx);
} while (PTR_ERR(folio) == -EEXIST);
if (IS_ERR_OR_NULL(folio))
return NULL;
+ folio_add_lru(folio);
swap_read_folio(folio, plug);
if (readahead) {
folio_set_readahead(folio);
@@ -683,12 +780,13 @@ struct folio *swapin_sync(swp_entry_t entry, gfp_t gfp, unsigned long orders,
folio = swap_cache_get_folio(entry);
if (folio)
return folio;
- folio = swap_cache_alloc_folio(entry, gfp, orders, vmf, mpol, ilx);
+ folio = __swap_cache_alloc_folio(entry, gfp, orders, vmf, mpol, ilx);
} while (PTR_ERR(folio) == -EEXIST);
if (IS_ERR(folio))
return folio;
+ folio_add_lru(folio);
swap_read_folio(folio, NULL);
return folio;
}
diff --git a/mm/truncate.c b/mm/truncate.c
index b58ba940be474..37f7d8278c1cd 100644
--- a/mm/truncate.c
+++ b/mm/truncate.c
@@ -336,7 +336,7 @@ long mapping_evict_folio(struct address_space *mapping, struct folio *folio)
if (!filemap_release_folio(folio, 0))
return 0;
- return remove_mapping(mapping, folio);
+ return remove_mapping(mapping, folio, false, NULL);
}
/**
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 56708d1d2dfd5..c40513e0b50b2 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -784,17 +784,24 @@ static int __remove_mapping(struct address_space *mapping, struct folio *folio,
* remove_mapping() - Attempt to remove a folio from its mapping.
* @mapping: The address space.
* @folio: The folio to remove.
+ * @reclaimed: Whether the folio is being reclaimed (record a shadow).
+ * @target_memcg: The memcg to charge the eviction shadow to when @reclaimed;
+ * the caller must keep it alive across the call. Ignored (may
+ * be NULL) when @reclaimed is false.
*
* If the folio is dirty, under writeback or if someone else has a ref
- * on it, removal will fail.
+ * on it, removal will fail. When @reclaimed is true, a workingset eviction
+ * shadow is stored (like page reclaim does) so that a later refault can be
+ * detected and the folio re-activated.
* Return: The number of pages removed from the mapping. 0 if the folio
* could not be removed.
* Context: The caller should have a single refcount on the folio and
* hold its lock.
*/
-long remove_mapping(struct address_space *mapping, struct folio *folio)
+long remove_mapping(struct address_space *mapping, struct folio *folio,
+ bool reclaimed, struct mem_cgroup *target_memcg)
{
- if (__remove_mapping(mapping, folio, false, NULL)) {
+ if (__remove_mapping(mapping, folio, reclaimed, target_memcg)) {
/*
* Unfreezing the refcount with 1 effectively
* drops the pagecache ref for us without requiring another
diff --git a/mm/zswap.c b/mm/zswap.c
index 761cd699e0a3e..a35671e837f89 100644
--- a/mm/zswap.c
+++ b/mm/zswap.c
@@ -1000,8 +1000,8 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
return -EEXIST;
mpol = get_task_policy(current);
- folio = swap_cache_alloc_folio(swpentry, GFP_KERNEL, BIT(0), NULL, mpol,
- NO_INTERLEAVE_INDEX);
+ folio = __swap_cache_alloc_folio(swpentry, GFP_KERNEL, BIT(0), NULL, mpol,
+ NO_INTERLEAVE_INDEX);
put_swap_device(si);
/*
@@ -1045,8 +1045,7 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
/* folio is up to date */
folio_mark_uptodate(folio);
- /* move it to the tail of the inactive list after end_writeback */
- folio_set_reclaim(folio);
+ folio_set_dropbehind(folio);
/* start writeback */
__swap_writepage(folio, NULL);
|
| 3/2 | 2026/08/18 19:49 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 6503 output: 33 thoughts: 524 Thoughts: **Analyzing a Kernel Memory Management Patch** Okay, so I've got this patch here, and it's messing with how swap cache folios are handled, particularly in the context of zswap writeback. My initial thought is, this is definitely something that needs careful attention. The patch touches critical bits of the kernel: `fs/splice.c`, `include/linux/swap.h`, `mm/filemap.c`, `mm/swap.h`, `mm/swap_state.c`, `mm/truncate.c`, `mm/vmscan.c`, and, crucially, `mm/zswap.c`. This isn't some minor tweak; it's right in the core memory management subsystem. Specifically, it looks like a "dropbehind" mechanism is being introduced for zswap writeback. This involves a new function, `swap_writeback_dropbehind_folio`, which will use a workqueue, `swap_dropbehind_wq`, to process folios that have been written back from zswap and should be immediately dropped from the swap cache, rather than being put back on the LRU list. Smart. It also alters `remove_mapping` to accept `reclaimed` and `target_memcg` arguments, and modifies `zswap_writeback_entry` and `folio_end_writeback` to use the new "dropbehind" logic. This is a functional change, a significant one. My first action? This needs fuzzing. And probably some heavy-duty testing on a variety of workloads. Let's break down the key symbols: `swap_writeback_dropbehind_folio`, `swap_dropbehind_workfn`, `zswap_writeback_entry`, `remove_mapping`, and `__swap_cache_alloc_folio`. I'll need to know these intimately. Config-wise, it's clear that `CONFIG_ZSWAP` and `CONFIG_SWAP` are required. Now, I'm going to check if `zswap_writeback_entry` is static or not because it's always good to understand the scope of those functions. Let's see... using `grepper`... Ah, yes, it's static within `mm/zswap.c`. This means it's internal to that file, which makes sense. I see now that `swap_writeback_dropbehind_folio` is *not* static, so it's visible outside of its file. I'm also going to check where `zswap_writeback_entry` is called from. I want to see if it's called from a non-static function, which could be important for understanding how this change integrates with other parts of the kernel. |
| 4/2 | 2026/08/18 19:50 | tool | grepper |
0mArgs:{
"Expression": "zswap_writeback_entry",
"PathPrefix": "mm/zswap.c"
}Results: {
"Output": "mm/zswap.c=924=static bool zswap_decompress(struct zswap_entry *entry, struct folio *folio)\n--\nmm/zswap.c-986- */\nmm/zswap.c:987:static int zswap_writeback_entry(struct zswap_entry *entry,\nmm/zswap.c-988-\t\t\t\t swp_entry_t swpentry)\n--\nmm/zswap.c=1087=static enum lru_status shrink_memcg_cb(struct list_head *item, struct list_lru_one *l,\n--\nmm/zswap.c-1110-\t * 1. We extract the swp_entry_t to the stack, allowing\nmm/zswap.c:1111:\t * zswap_writeback_entry() to pin the swap entry and\nmm/zswap.c-1112-\t * then validate the zswap entry against that swap entry's\n--\nmm/zswap.c-1145-\nmm/zswap.c:1146:\twriteback_result = zswap_writeback_entry(entry, swpentry);\nmm/zswap.c-1147-\n"
} |
| 5/2 | 2026/08/18 19:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 7955 output: 32 thoughts: 42 Thoughts: Okay, let me think this through. **Analyzing the Memory Controller Callback** Right, so `shrink_memcg_cb` is the function in question. I need to understand its behavior, specifically how it interacts with the zswap layer. The first clue is that `shrink_memcg_cb` calls `zswap_writeback_entry`. That's important. It suggests that this callback is somehow involved in triggering or managing the writing back of pages to zswap. Makes sense; `shrink` implies we're trying to free up memory. The fact that `shrink_memcg_cb` is declared as `static` is also a key piece of information. That limits its scope to the compilation unit it's defined in. So, I need to figure out where that is. The question then becomes: what exactly is calling this `shrink_memcg_cb` function? Understanding its callers will illuminate the purpose and context of this callback function within the memory management framework. That's where I need to start my investigation. This would likely be found in the core memory controller code. |
| 6/2 | 2026/08/18 19:50 | tool | grepper |
0mArgs:{
"Expression": "shrink_memcg_cb",
"PathPrefix": "mm/zswap.c"
}Results: {
"Output": "mm/zswap.c=987=static int zswap_writeback_entry(struct zswap_entry *entry,\n--\nmm/zswap.c-1086- */\nmm/zswap.c:1087:static enum lru_status shrink_memcg_cb(struct list_head *item, struct list_lru_one *l,\nmm/zswap.c-1088-\t\t\t\t void *arg)\n--\nmm/zswap.c=1168=static unsigned long zswap_shrinker_scan(struct shrinker *shrinker,\n--\nmm/zswap.c-1179-\nmm/zswap.c:1180:\tshrink_ret = list_lru_shrink_walk(\u0026zswap_list_lru, sc, \u0026shrink_memcg_cb,\nmm/zswap.c-1181-\t\t\u0026encountered_page_in_swapcache);\n--\nmm/zswap.c=1277=static int shrink_memcg(struct mem_cgroup *memcg)\n--\nmm/zswap.c-1294-\t\tshrunk += list_lru_walk_one(\u0026zswap_list_lru, nid, memcg,\nmm/zswap.c:1295:\t\t\t\t\t \u0026shrink_memcg_cb, NULL, \u0026nr_to_walk);\nmm/zswap.c-1296-\t\tscanned += 1 - nr_to_walk;\n"
} |
| 7/2 | 2026/08/18 19:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 8626 output: 30 thoughts: 52 Thoughts: Here's my understanding, as if I'm thinking through this: **Analyzing the Callers of `shrink_memcg`** Okay, so I'm looking at how memory is being managed here. Specifically, I'm trying to figure out the chain of calls that leads to `shrink_memcg`. The code shows that both `zswap_shrinker_scan` and `shrink_memcg` are static functions. This suggests that they are only visible within their respective source files, which helps with modularity, but also makes tracing the calling path a bit more important. The key thing I see is that `zswap_shrinker_scan` explicitly calls `shrink_memcg`. Now, I need to know what else calls `shrink_memcg`. Figuring out the upstream call sites for `shrink_memcg` is my next focus. |
| 8/2 | 2026/08/18 19:50 | tool | grepper |
0mArgs:{
"Expression": "shrink_memcg",
"PathPrefix": "mm/zswap.c"
}Results: {
"Output": "mm/zswap.c=987=static int zswap_writeback_entry(struct zswap_entry *entry,\n--\nmm/zswap.c-1086- */\nmm/zswap.c:1087:static enum lru_status shrink_memcg_cb(struct list_head *item, struct list_lru_one *l,\nmm/zswap.c-1088-\t\t\t\t void *arg)\n--\nmm/zswap.c=1168=static unsigned long zswap_shrinker_scan(struct shrinker *shrinker,\n--\nmm/zswap.c-1179-\nmm/zswap.c:1180:\tshrink_ret = list_lru_shrink_walk(\u0026zswap_list_lru, sc, \u0026shrink_memcg_cb,\nmm/zswap.c-1181-\t\t\u0026encountered_page_in_swapcache);\n--\nmm/zswap.c=1261=static struct shrinker *zswap_alloc_shrinker(void)\n--\nmm/zswap.c-1276-\nmm/zswap.c:1277:static int shrink_memcg(struct mem_cgroup *memcg)\nmm/zswap.c-1278-{\n--\nmm/zswap.c-1294-\t\tshrunk += list_lru_walk_one(\u0026zswap_list_lru, nid, memcg,\nmm/zswap.c:1295:\t\t\t\t\t \u0026shrink_memcg_cb, NULL, \u0026nr_to_walk);\nmm/zswap.c-1296-\t\tscanned += 1 - nr_to_walk;\n--\nmm/zswap.c=1305=static void shrink_worker(struct work_struct *w)\n--\nmm/zswap.c-1369-\nmm/zswap.c:1370:\t\tret = shrink_memcg(memcg);\nmm/zswap.c-1371-\t\t/* drop the extra reference */\n--\nmm/zswap.c=1475=bool zswap_store(struct folio *folio)\n--\nmm/zswap.c-1493-\t\tmemcg = get_mem_cgroup_from_objcg(objcg);\nmm/zswap.c:1494:\t\tif (shrink_memcg(memcg)) {\nmm/zswap.c-1495-\t\t\tmem_cgroup_put(memcg);\n"
} |
| 9/2 | 2026/08/18 19:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 9509 output: 35 thoughts: 140 Thoughts: **My Initial Thoughts on Zswap and Swap Cache Interactions** Okay, so `zswap_store` is calling `shrink_memcg`. That makes sense, part of managing memory pressure. And it's exported, so it's clearly intended to be used by other parts of the kernel. Good. Now, let's see what `remove_mapping` is doing in this context. It's also non-static, so it's accessible. This likely plays a role in freeing up resources. Hmm, let's look at `__swap_cache_alloc_folio`. Wait a minute, is that *also* non-static? Let me double-check... Yes, it's in `mm/swap_state.c` but it's exported. This seems important. I need to dig into `mm/swap_state.c` and understand the implications here. This seems like a core element. |
| 10/2 | 2026/08/18 19:50 | tool | grepper |
0mArgs:{
"Expression": "__swap_cache_alloc_folio",
"PathPrefix": "mm/swap_state.c"
}Results: {
"Output": "mm/swap_state.c=405=static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,\n--\nmm/swap_state.c-491-/**\nmm/swap_state.c:492: * __swap_cache_alloc_folio - Allocate folio for swapped out slot in swap cache.\nmm/swap_state.c-493- * @targ_entry: swap entry indicating the target slot\n--\nmm/swap_state.c-511- */\nmm/swap_state.c:512:struct folio *__swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,\nmm/swap_state.c-513-\t\t\t\t unsigned long orders, struct vm_fault *vmf,\n--\nmm/swap_state.c=732=static struct folio *swap_cache_read_folio(swp_entry_t entry, gfp_t gfp,\n--\nmm/swap_state.c-741-\t\t\treturn folio;\nmm/swap_state.c:742:\t\tfolio = __swap_cache_alloc_folio(entry, gfp, BIT(0), NULL, mpol, ilx);\nmm/swap_state.c-743-\t} while (PTR_ERR(folio) == -EEXIST);\n--\nmm/swap_state.c=774=struct folio *swapin_sync(swp_entry_t entry, gfp_t gfp, unsigned long orders,\n--\nmm/swap_state.c-782-\t\t\treturn folio;\nmm/swap_state.c:783:\t\tfolio = __swap_cache_alloc_folio(entry, gfp, orders, vmf, mpol, ilx);\nmm/swap_state.c-784-\t} while (PTR_ERR(folio) == -EEXIST);\n"
} |
| 11/2 | 2026/08/18 19:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 10347 output: 36 thoughts: 102 Thoughts: **My Initial Code Inspection: The Non-Static Conundrum** Okay, so I'm digging into this code, and a few things are immediately jumping out at me. I see that `__swap_cache_alloc_folio` isn't declared static. That's unusual, but not necessarily a red flag in itself. However, it's not exported either, which is a bit odd. Then I see that `swap_writeback_dropbehind_folio` is also non-static. Interesting. Then `swapin_sync`, and `zswap_store`. Finally, `remove_mapping`. These all share the same attribute. Given this pattern, I need to understand the intent of these functions and whether they should or shouldn't be static. I need to understand why they're not. They all seem to relate to the swap subsystem in some way. Let's see... I'll start by taking a closer look at `swap_writeback_dropbehind_folio`. Let's check the code for it in `mm/swap_state.c`. |
| 12/2 | 2026/08/18 19:50 | tool | grepper |
0mArgs:{
"Expression": "swap_writeback_dropbehind_folio",
"PathPrefix": "mm/swap_state.c"
}Results: {
"Output": "mm/swap_state.c=615=static struct workqueue_struct *swap_dropbehind_wq;\nmm/swap_state.c-616-\nmm/swap_state.c:617:void swap_writeback_dropbehind_folio(struct folio *folio)\nmm/swap_state.c-618-{\n"
} |
| 13/2 | 2026/08/18 19:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 10808 output: 35 thoughts: 14 Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue: **Checking Kernel Internals** Right, let me see... I'm curious about the `swap_writeback_dropbehind_folio` function. Is it actually exported? That's a critical piece of information. Knowing if it's exported dictates so much about how I can interact with it, if at all. Is it available for external modules to call, or is it intended solely for internal kernel use? That detail impacts so much of my ability to, for example, build a custom memory management system or explore how folio writeback behavior works. I'm especially interested in understanding the interaction of `dropbehind` with `writeback`. If it's *not* exported, I'll have to investigate alternatives or consider whether it's possible to indirectly achieve the desired behavior through other, officially supported APIs and structures. The export status immediately signals the boundaries of my possible experimentation. I'll need to figure out how to determine its export status quickly. |
| 14/2 | 2026/08/18 19:51 | tool | grepper |
0mError:no matches Args: {
"Expression": "EXPORT_SYMBOL.*swap_writeback_dropbehind_folio",
"PathPrefix": "mm/"
}Results: {
"error": "no matches"
} |
| 15/2 | 2026/08/18 19:51 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 11091 output: 29 thoughts: 19 Thoughts: Okay, here's my summary, taking on the persona you requested: **Assessing the Landscape: A Quick Diagnostic** Hmm, the situation isn't *static*, that much is clear. My initial assessment is a firm negative, but the context indicates a dynamic element. This means things are potentially changing or mutable. My immediate next step is to examine a specific piece of the puzzle: the `remove_mapping` function or method. It is a logical next point of investigation. This function is likely the key to understanding the non-static behavior I'm observing and is the logical next piece to examine. |
| 16/2 | 2026/08/18 19:51 | tool | grepper |
0mError:no matches Args: {
"Expression": "EXPORT_SYMBOL.*remove_mapping",
"PathPrefix": "mm/"
}Results: {
"error": "no matches"
} |
| 17/2 | 2026/08/18 19:51 | llm | patch-evaluator |
1mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 11280 output: 30 thoughts: 13 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience: **Investigating `zswap_store`** No, that's not it. Okay, let's step back and take a look at `zswap_store`. I need to figure out the root cause and I'm guessing that the problem lies elsewhere. However, since I was thinking in this general area, checking `zswap_store` could give me a clue of why the results were negative and point me in the right direction. It's a quick check to see if there's anything obvious there before I dive into more complex areas. |
| 18/2 | 2026/08/18 19:52 | tool | grepper |
0mError:no matches Args: {
"Expression": "EXPORT_SYMBOL.*zswap_store",
"PathPrefix": "mm/"
}Results: {
"error": "no matches"
} |
| 19/2 | 2026/08/18 19:52 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 11472 output: 26 thoughts: 24 Thoughts: Okay, here's my summary, in the first person, as requested: **Navigating the Swap Subsystem** No. Just… no. That first line, that's what I'm thinking. This doesn't feel right. Wait, hold on a second. Let me back up and think this through. Is `zswap_store` actually called from something like `add_to_swap`, or a similar function in the swap subsystem? I need to clarify that dependency. That's the key question right now. Understanding the call stack is crucial. |
| 20/2 | 2026/08/18 19:53 | tool | grepper |
0mArgs:{
"Expression": "zswap_store",
"PathPrefix": "mm/"
}Results: {
"Output": "mm/page_io.c=251=int swap_writeout(struct folio *folio, struct swap_iocb **swap_plug)\n--\nmm/page_io.c-284-\nmm/page_io.c:285:\tif (zswap_store(folio)) {\nmm/page_io.c-286-\t\tcount_mthp_stat(folio_order(folio), MTHP_STAT_ZSWPOUT);\n--\nmm/zswap.c-46-/* The number of pages currently stored in zswap */\nmm/zswap.c:47:atomic_long_t zswap_stored_pages = ATOMIC_LONG_INIT(0);\nmm/zswap.c-48-/* The number of incompressible pages currently stored in zswap */\nmm/zswap.c:49:static atomic_long_t zswap_stored_incompressible_pages = ATOMIC_LONG_INIT(0);\nmm/zswap.c-50-\n--\nmm/zswap.c=765=static void zswap_entry_free(struct zswap_entry *entry)\n--\nmm/zswap.c-774-\tif (entry-\u003elength == PAGE_SIZE)\nmm/zswap.c:775:\t\tatomic_long_dec(\u0026zswap_stored_incompressible_pages);\nmm/zswap.c-776-\tzswap_entry_cache_free(entry);\nmm/zswap.c:777:\tatomic_long_dec(\u0026zswap_stored_pages);\nmm/zswap.c-778-}\n--\nmm/zswap.c=924=static bool zswap_decompress(struct zswap_entry *entry, struct folio *folio)\n--\nmm/zswap.c-981- * to the swap device. We are basically resuming the same swap\nmm/zswap.c:982: * writeback path that was intercepted with the zswap_store()\nmm/zswap.c-983- * in the first place. After the folio has been decompressed into\n--\nmm/zswap.c=1189=static unsigned long zswap_shrinker_count(struct shrinker *shrinker,\n--\nmm/zswap.c-1223-\t\tnr_backing = zswap_total_pages();\nmm/zswap.c:1224:\t\tnr_stored = atomic_long_read(\u0026zswap_stored_pages);\nmm/zswap.c-1225-\t}\n--\nmm/zswap.c=1305=static void shrink_worker(struct work_struct *w)\n--\nmm/zswap.c-1394-\nmm/zswap.c:1395:static bool zswap_store_page(struct page *page,\nmm/zswap.c-1396-\t\t\t struct obj_cgroup *objcg,\n--\nmm/zswap.c-1433-\t * no further possibility of failure. Grab refs to the pool and objcg,\nmm/zswap.c:1434:\t * charge zswap memory, and increment zswap_stored_pages.\nmm/zswap.c-1435-\t * The opposite actions will be performed by zswap_entry_free()\n--\nmm/zswap.c-1442-\t}\nmm/zswap.c:1443:\tatomic_long_inc(\u0026zswap_stored_pages);\nmm/zswap.c-1444-\tif (entry-\u003elength == PAGE_SIZE)\nmm/zswap.c:1445:\t\tatomic_long_inc(\u0026zswap_stored_incompressible_pages);\nmm/zswap.c-1446-\n--\nmm/zswap.c-1474-\nmm/zswap.c:1475:bool zswap_store(struct folio *folio)\nmm/zswap.c-1476-{\n--\nmm/zswap.c-1519-\nmm/zswap.c:1520:\t\tif (!zswap_store_page(page, objcg, pool))\nmm/zswap.c-1521-\t\t\tgoto put_pool;\n--\nmm/zswap.c=1700=static int debugfs_get_stored_pages(void *data, u64 *val)\nmm/zswap.c-1701-{\nmm/zswap.c:1702:\t*val = atomic_long_read(\u0026zswap_stored_pages);\nmm/zswap.c-1703-\treturn 0;\n--\nmm/zswap.c=1707=static int debugfs_get_stored_incompressible_pages(void *data, u64 *val)\nmm/zswap.c-1708-{\nmm/zswap.c:1709:\t*val = atomic_long_read(\u0026zswap_stored_incompressible_pages);\nmm/zswap.c-1710-\treturn 0;\n"
} |
| 21/2 | 2026/08/18 19:53 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 12873 output: 27 thoughts: 739 Thoughts: **Analyzing Potential Focus Symbols in the Kernel Code** Okay, so I'm diving into this code and trying to get a handle on the changes. My initial thought process is to identify the key functions that are being modified and, more importantly, how they relate to each other within the kernel. I'm focusing on non-static functions because those are the entry points that are likely to be interacting with other parts of the system. I've already noted that `zswap_store` is non-static and linked to `swap_writeout`, which is a good starting point. Likewise, `swap_writeback_dropbehind_folio` is non-static and called from `folio_end_writeback`. Then, there's `remove_mapping`, non-static and called from `page_cache_pipe_buf_try_steal`, `mapping_evict_folio`, and `swap_dropbehind_drop_folio`. Now, I'm going through the code to examine these functions closely. I've taken a look at `swap_writeback_dropbehind_folio` in `mm/swap_state.c`. It looks like it's adding a folio to a linked list and queuing a work item. That's definitely interesting; it's a good candidate for deeper investigation. Then, I'm checking `remove_mapping` in `mm/vmscan.c`. It's pretty clear that this is another critical function to understand, as it's dealing with freeing up memory. It takes a mapping, a folio, a reclaimed flag, and a target memcg. Next, I've looked at `__swap_cache_alloc_folio` in `mm/swap_state.c`. It seems like a core function for allocating folio structures within the swap cache. I've also quickly dismissed `swap_dropbehind_workfn` and `zswap_writeback_entry`, because they are static and less likely to be the primary focus. Therefore, my short list of top focus symbols are now: - `swap_writeback_dropbehind_folio` - `remove_mapping` - `__swap_cache_alloc_folio` Finally, I'm checking to see if there are other, possibly less obvious, changes. `folio_end_writeback` is modified, but I know that's a heavily used path, so I need to be cautious about that one. `page_cache_pipe_buf_try_steal` is static. `mapping_evict_folio` is non-static, but it just calls `remove_mapping`, so it's probably not that important. To finish, I'll review the configuration options that impact the modified files. I already know that `CONFIG_ZSWAP` is needed for `mm/zswap.c` and `CONFIG_SWAP` for `mm/swap_state.c`. I will then go on to check `mm/Makefile` to make sure there are no other dependent config options. This should give me a complete view of what's going on. |
| 22/2 | 2026/08/18 19:53 | tool | grepper |
0mArgs:{
"Expression": "zswap.o",
"PathPrefix": "mm/Makefile"
}Results: {
"Output": "mm/Makefile=78=obj-$(CONFIG_SWAP)\t+= page_io.o swap_state.o swapfile.o\nmm/Makefile:79:obj-$(CONFIG_ZSWAP)\t+= zswap.o\nmm/Makefile-80-obj-$(CONFIG_HAS_DMA)\t+= dmapool.o\n"
} |
| 23/2 | 2026/08/18 19:53 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 14389 output: 28 thoughts: 36 Thoughts: Okay, here's my take on those thoughts, framed as my own: **Kernel Configuration Considerations for zswap** Alright, so the first thing I'm noting is that if I want to use `zswap`, I'm going to need both `CONFIG_ZSWAP` and, obviously, the basic `CONFIG_SWAP` option enabled in the kernel. Seems pretty straightforward, that's what I'd expect. Now, before I go any further, I want to make sure I understand the dependencies. I need to quickly verify if `CONFIG_ZSWAP` has any other dependencies that might not be immediately obvious. Gotta make sure I'm not missing any other required pieces. Digging into the configuration options is always the right approach at this stage! |
| 24/2 | 2026/08/18 19:53 | tool | grepper |
0mArgs:{
"Expression": "config ZSWAP",
"PathPrefix": "mm/Kconfig"
}Results: {
"Output": "mm/Kconfig=12=menuconfig SWAP\n--\nmm/Kconfig-21-\nmm/Kconfig:22:config ZSWAP\nmm/Kconfig-23-\tbool \"Compressed cache for swap pages\"\n--\nmm/Kconfig-34-\nmm/Kconfig:35:config ZSWAP_DEFAULT_ON\nmm/Kconfig-36-\tbool \"Enable the compressed cache for swap pages by default\"\n--\nmm/Kconfig-44-\nmm/Kconfig:45:config ZSWAP_SHRINKER_DEFAULT_ON\nmm/Kconfig-46-\tbool \"Shrink the zswap pool on memory pressure\"\n--\nmm/Kconfig=59=choice\n--\nmm/Kconfig-76-\nmm/Kconfig:77:config ZSWAP_COMPRESSOR_DEFAULT_DEFLATE\nmm/Kconfig-78-\tbool \"Deflate\"\n--\nmm/Kconfig-82-\nmm/Kconfig:83:config ZSWAP_COMPRESSOR_DEFAULT_LZO\nmm/Kconfig-84-\tbool \"LZO\"\n--\nmm/Kconfig-88-\nmm/Kconfig:89:config ZSWAP_COMPRESSOR_DEFAULT_842\nmm/Kconfig-90-\tbool \"842\"\n--\nmm/Kconfig-94-\nmm/Kconfig:95:config ZSWAP_COMPRESSOR_DEFAULT_LZ4\nmm/Kconfig-96-\tbool \"LZ4\"\n--\nmm/Kconfig-100-\nmm/Kconfig:101:config ZSWAP_COMPRESSOR_DEFAULT_LZ4HC\nmm/Kconfig-102-\tbool \"LZ4HC\"\n--\nmm/Kconfig-106-\nmm/Kconfig:107:config ZSWAP_COMPRESSOR_DEFAULT_ZSTD\nmm/Kconfig-108-\tbool \"zstd\"\n--\nmm/Kconfig=112=endchoice\nmm/Kconfig-113-\nmm/Kconfig:114:config ZSWAP_COMPRESSOR_DEFAULT\nmm/Kconfig-115- string\n"
} |
| 25/2 | 2026/08/18 19:53 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 15155 output: 31 thoughts: 12 Thoughts: Here's my take on the dependencies of `ZSWAP`: **Delving into ZSWAP's Dependencies** Alright, let's get down to brass tacks. I need to understand `ZSWAP` inside and out, which means a deep dive into its dependencies is the first order of business. I'm thinking, "what external libraries, services, or kernel modules is this thing leaning on?" Are we talking core libraries, potentially another project of equal stature within the system, or just something small to handle a specific process? Knowing the answer to that will determine so much. Does it require a specific version of the Linux kernel? Does it pull in something from another library that could break it with an update? Is there a hidden dependency on a piece of the infrastructure? Those are the crucial questions I'm asking myself. Gotta identify any potential weak points in the architectural foundation of `ZSWAP` right now and get those potential conflicts squared away. This is about solidifying my understanding, mapping out potential risks, and preparing to troubleshoot. Let's see what we uncover. |
| 26/2 | 2026/08/18 19:53 | tool | read-file |
0mArgs:{
"File": "mm/Kconfig",
"FirstLine": 22,
"LineCount": 10
}Results: {
"Contents": " 22:\tconfig ZSWAP\n 23:\t\tbool \"Compressed cache for swap pages\"\n 24:\t\tdepends on SWAP\n 25:\t\tselect CRYPTO\n 26:\t\tselect ZSMALLOC\n 27:\t\thelp\n 28:\t\t A lightweight compressed cache for swap pages. It takes\n 29:\t\t pages that are in the process of being swapped out and attempts to\n 30:\t\t compress them into a dynamically allocated RAM-based memory pool.\n 31:\t\t This can result in a significant I/O reduction on swap device and,\n"
} |
| 27/2 | 2026/08/18 19:53 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 15578 output: 31 thoughts: 50 Thoughts: Okay, here's my take on those thoughts, framed as my own mental processing: **ZSWAP, SWAP, and Default Activation** Right, so I'm thinking about this ZSWAP thing, and it's clear: it's built on the foundation of regular SWAP. That's a fundamental dependency I need to keep in mind. We're going to need both. My instinct, based on the potential performance benefits, is to enable *both* ZSWAP and the underlying SWAP functionality. Seems like the logical approach to take advantage of the compression. And that leads me to another thought, the `ZSWAP_DEFAULT_ON` option. It might be prudent to explore that further. If we want this to be used effectively, we need to ensure it's actually *running* and being leveraged without requiring manual intervention from the end-user. Ensuring it's on by default could be the key to widespread adoption and maximizing the advantages of ZSWAP. Needs a little more research, but it's certainly something to strongly consider. |
| 28/2 | 2026/08/18 19:53 | tool | read-file |
0mArgs:{
"File": "mm/Kconfig",
"FirstLine": 35,
"LineCount": 10
}Results: {
"Contents": " 35:\tconfig ZSWAP_DEFAULT_ON\n 36:\t\tbool \"Enable the compressed cache for swap pages by default\"\n 37:\t\tdepends on ZSWAP\n 38:\t\thelp\n 39:\t\t If selected, the compressed cache for swap pages will be enabled\n 40:\t\t at boot, otherwise it will be disabled.\n 41:\t\n 42:\t\t The selection made here can be overridden by using the kernel\n 43:\t\t command line 'zswap.enabled=' option.\n 44:\t\n"
} |
| 29/2 | 2026/08/18 19:53 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 16030 output: 172 thoughts: 587 Thoughts: **Fuzzing Strategy for Zswap Writeback Dropbehind Mechanism** Okay, so I've been digging into this patch. The key thing here is this new "dropbehind" mechanism for swap cache folios during zswap writeback. The goal is to avoid sticking these folios back on the LRU list, which could cause unnecessary thrashing. This sounds like a potentially significant performance optimization, but also a definite candidate for fuzzing. My initial thoughts are that enabling `ZSWAP`, `SWAP`, and `ZSWAP_DEFAULT_ON` are a good starting point for the fuzzing configuration. Functionally, I see modifications across several key areas: `mm/vmscan.c`, `mm/truncate.c`, `fs/splice.c`, `mm/swap_state.c`, and `mm/zswap.c`, indicating this is not a trivial change. I've taken a look at some of the modified and new functions. Specifically, `remove_mapping` in `mm/vmscan.c` (and its uses in `mm/truncate.c`, `fs/splice.c`, and `mm/swap_state.c`) is definitely modified as part of this change. Then, `__swap_cache_alloc_folio` in `mm/swap_state.c` and `mm/zswap.c` is also changed. And lastly, a brand-new function, `swap_writeback_dropbehind_folio`, which is introduced in `mm/swap_state.c` but called from `mm/filemap.c`. That function seems central to the core "dropbehind" functionality. Given the potential impact on core MM behavior, I've identified the following as focus symbols to target with the fuzzer: `swap_writeback_dropbehind_folio`, `remove_mapping`, and `__swap_cache_alloc_folio`. I've checked and these functions, while not exported, are non-static, which is perfect for fuzzing. This should be interesting. Let's call `set-results`. |
| 30/2 | 2026/08/18 19:54 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"SWAP",
"ZSWAP",
"ZSWAP_DEFAULT_ON"
],
"FocusSymbols": [
"swap_writeback_dropbehind_folio",
"remove_mapping",
"__swap_cache_alloc_folio"
],
"Reasoning": "The patch introduces a 'dropbehind' mechanism for swap cache folios during zswap writeback to avoid putting them on the LRU list. It modifies core memory management functions like `remove_mapping`, `folio_end_writeback`, and adds `swap_writeback_dropbehind_folio` which uses a workqueue to drop folios from the swap cache. This is a functional change in the core MM subsystem that should be fuzzed.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"SWAP",
"ZSWAP",
"ZSWAP_DEFAULT_ON"
],
"FocusSymbols": [
"swap_writeback_dropbehind_folio",
"remove_mapping",
"__swap_cache_alloc_folio"
],
"Reasoning": "The patch introduces a 'dropbehind' mechanism for swap cache folios during zswap writeback to avoid putting them on the LRU list. It modifies core memory management functions like `remove_mapping`, `folio_end_writeback`, and adds `swap_writeback_dropbehind_folio` which uses a workqueue to drop folios from the swap cache. This is a functional change in the core MM subsystem that should be fuzzed.",
"WorthFuzzing": true
} |
| 31/1 | 2026/08/18 19:54 | action | If |
0mArgs:{
"WorthFuzzing": true
}Results: null |
| 32/2 | 2026/08/18 19:54 | agent | kmsan-evaluator |
0mModel:best-expensive Args: null Results: {
"KMSANReasoning": "The patch series modifies the internal memory management subsystem, specifically focusing on swap cache allocation, writeback, and LRU management (introducing a \"dropbehind\" mechanism for zswap writeback). The changes involve page lifecycle management, workqueues for freeing folios, and modifying function signatures like `remove_mapping` and `swap_cache_alloc_folio`.\n\nThese modifications do not introduce new data structures that are copied to user space, nor do they alter bounds checking or initialization of memory buffers. Any potential bugs introduced by these changes would manifest as use-after-free, double-free, memory leaks, or NULL pointer dereferences. These types of memory corruption and lifecycle bugs are effectively detected by standard KASAN, KMEMLEAK, and LOCKDEP. There is no risk of exposing uninitialized memory to user space or using uninitialized memory in control flow, which are the specific classes of bugs KMSAN is designed to catch. Therefore, a dedicated KMSAN fuzzing session is not necessary.",
"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 862c847d54f3ccb96cb4797e3279ad4836fbeb17
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Aug 18 19:49:50 2026 +0000
syz-cluster: applied patch under review
diff --git a/fs/splice.c b/fs/splice.c
index 9d8f63e2fd1ab..79424bbee1b0f 100644
--- a/fs/splice.c
+++ b/fs/splice.c
@@ -90,7 +90,7 @@ static bool page_cache_pipe_buf_try_steal(struct pipe_inode_info *pipe,
* If we succeeded in removing the mapping, set LRU flag
* and return good.
*/
- if (remove_mapping(mapping, folio)) {
+ if (remove_mapping(mapping, folio, false, NULL)) {
buf->flags |= PIPE_BUF_FLAG_LRU;
return true;
}
diff --git a/include/linux/swap.h b/include/linux/swap.h
index 8f0f68e245baa..bdaffc0d1ef31 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -373,7 +373,8 @@ extern unsigned long mem_cgroup_shrink_node(struct mem_cgroup *mem,
unsigned long *nr_scanned);
extern unsigned long shrink_all_memory(unsigned long nr_pages);
extern int vm_swappiness;
-long remove_mapping(struct address_space *mapping, struct folio *folio);
+long remove_mapping(struct address_space *mapping, struct folio *folio,
+ bool reclaimed, struct mem_cgroup *target_memcg);
#if defined(CONFIG_SYSFS) && defined(CONFIG_NUMA)
extern int reclaim_register_node(struct node *node);
@@ -465,6 +466,8 @@ void swap_put_entries_direct(swp_entry_t entry, int nr);
*/
bool folio_free_swap(struct folio *folio);
+void swap_writeback_dropbehind_folio(struct folio *folio);
+
/* Allocate / free (hibernation) exclusive entries */
swp_entry_t swap_alloc_hibernation_slot(int type);
void swap_free_hibernation_slot(swp_entry_t entry);
@@ -475,6 +478,7 @@ static inline void put_swap_device(struct swap_info_struct *si)
}
#else /* CONFIG_SWAP */
+static inline void swap_writeback_dropbehind_folio(struct folio *folio) {}
static inline struct swap_info_struct *get_swap_device(swp_entry_t entry)
{
return NULL;
diff --git a/mm/filemap.c b/mm/filemap.c
index d721986d5f465..1647961c5ba20 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -1686,6 +1686,8 @@ EXPORT_SYMBOL_GPL(folio_end_writeback_no_dropbehind);
*/
void folio_end_writeback(struct folio *folio)
{
+ bool swap_dropbehind;
+
VM_BUG_ON_FOLIO(!folio_test_writeback(folio), folio);
/*
@@ -1695,7 +1697,24 @@ void folio_end_writeback(struct folio *folio)
* reused before the folio_wake_bit().
*/
folio_get(folio);
+
+ /*
+ * Dropbehind swap cache folios are off-LRU, so we must prevent a racing
+ * swapin from removing the folio from the swap cache and keeping it
+ * off-LRU: the writeback flag allows that. Afterwards a swapin may win
+ * the race, but the folio is already queued and the worker puts it back
+ * on the LRU in that case.
+ */
+ swap_dropbehind = folio_test_swapcache(folio) &&
+ folio_test_dropbehind(folio);
+
folio_end_writeback_no_dropbehind(folio);
+
+ if (swap_dropbehind) {
+ swap_writeback_dropbehind_folio(folio);
+ return;
+ }
+
folio_end_dropbehind(folio);
folio_put(folio);
}
diff --git a/mm/swap.h b/mm/swap.h
index 77d2d14eda421..fc44daae1de1f 100644
--- a/mm/swap.h
+++ b/mm/swap.h
@@ -304,9 +304,9 @@ bool swap_cache_has_folio(swp_entry_t entry);
struct folio *swap_cache_get_folio(swp_entry_t entry);
void *swap_cache_get_shadow(swp_entry_t entry);
void swap_cache_del_folio(struct folio *folio);
-struct folio *swap_cache_alloc_folio(swp_entry_t target_entry, gfp_t gfp_mask,
- unsigned long orders, struct vm_fault *vmf,
- struct mempolicy *mpol, pgoff_t ilx);
+struct folio *__swap_cache_alloc_folio(swp_entry_t target_entry, gfp_t gfp_mask,
+ unsigned long orders, struct vm_fault *vmf,
+ struct mempolicy *mpol, pgoff_t ilx);
/* Below helpers require the caller to lock and pass in the swap cluster. */
void __swap_cache_add_folio(struct swap_cluster_info *ci,
struct folio *folio, swp_entry_t entry);
diff --git a/mm/swap_state.c b/mm/swap_state.c
index 9c3a5cf997786..b93dd607d24c6 100644
--- a/mm/swap_state.c
+++ b/mm/swap_state.c
@@ -16,6 +16,8 @@
#include <linux/init.h>
#include <linux/pagemap.h>
#include <linux/folio_batch.h>
+#include <linux/llist.h>
+#include <linux/workqueue.h>
#include <linux/backing-dev.h>
#include <linux/blkdev.h>
#include <linux/migrate.h>
@@ -483,13 +485,11 @@ static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,
node_stat_mod_folio(folio, NR_FILE_PAGES, nr_pages);
lruvec_stat_mod_folio(folio, NR_SWAPCACHE, nr_pages);
- /* Caller will initiate read into locked new_folio */
- folio_add_lru(folio);
return folio;
}
/**
- * swap_cache_alloc_folio - Allocate folio for swapped out slot in swap cache.
+ * __swap_cache_alloc_folio - Allocate folio for swapped out slot in swap cache.
* @targ_entry: swap entry indicating the target slot
* @gfp: memory allocation flags
* @orders: allocation orders, must be non zero
@@ -501,13 +501,17 @@ static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,
* doing IO (e.g. swap in or zswap writeback). The swap slot indicated by
* @targ_entry must have a non-zero swap count (swapped out).
*
+ * The returned folio is locked and is NOT on the LRU. The caller must either
+ * add it to the LRU with folio_add_lru() so page reclaim can find it, or free
+ * it directly once done; a folio left off the LRU is unreclaimable and leaks.
+ *
* Context: Caller must protect the swap device with reference count or locks.
* Return: Returns the folio if allocation succeeded and folio is in the swap
* cache. Returns error code if failed due to race, OOM or invalid arguments.
*/
-struct folio *swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,
- unsigned long orders, struct vm_fault *vmf,
- struct mempolicy *mpol, pgoff_t ilx)
+struct folio *__swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,
+ unsigned long orders, struct vm_fault *vmf,
+ struct mempolicy *mpol, pgoff_t ilx)
{
int order, err;
struct folio *ret;
@@ -535,6 +539,98 @@ struct folio *swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,
return ret;
}
+static DEFINE_PER_CPU(struct llist_head, swap_dropbehind_llist);
+
+static bool swap_dropbehind_drop_folio(struct folio *folio)
+{
+ struct mem_cgroup *memcg;
+ bool dropped = false;
+
+ folio_lock(folio);
+
+ /* The folio was allocated off the LRU and nothing re-adds it here. */
+ VM_WARN_ON_ONCE_FOLIO(folio_test_lru(folio), folio);
+
+ rcu_read_lock();
+ memcg = folio_memcg(folio);
+ if (!mem_cgroup_tryget(memcg))
+ memcg = NULL;
+ rcu_read_unlock();
+
+ /*
+ * Gate remove_mapping() on folio_test_swapcache(): a racing swapin may
+ * have freed the swap slot (folio_free_swap()) and dropped the folio from
+ * the cache, and remove_mapping() must not run on a non-swapcache folio
+ * (it would trip __remove_mapping()'s mapping == folio_mapping() check).
+ */
+ if (folio_test_swapcache(folio) && !folio_test_writeback(folio) &&
+ remove_mapping(swap_address_space(folio->swap), folio, true, memcg)) {
+ dropped = true;
+ } else {
+ /* Raced: the folio is now owned by the swapin; put it back. */
+ folio_clear_dropbehind(folio);
+ folio_add_lru(folio);
+ }
+
+ mem_cgroup_put(memcg);
+
+ folio_unlock(folio);
+ if (!dropped)
+ folio_put(folio);
+ return dropped;
+}
+
+/**
+ * swap_dropbehind_free_batch_folio - free a dropbehind swap cache folio into a batch
+ * @folio: the off-LRU folio whose writeback has completed
+ * @fbatch: batch of folios to free, flushed when full
+ */
+static void swap_dropbehind_free_batch_folio(struct folio *folio,
+ struct folio_batch *fbatch)
+{
+ if (swap_dropbehind_drop_folio(folio) && !folio_batch_add(fbatch, folio))
+ folios_put(fbatch);
+}
+
+static void swap_dropbehind_workfn(struct work_struct *work)
+{
+ struct folio_batch fbatch;
+ struct llist_node *pos, *next;
+ int cpu;
+
+ folio_batch_init(&fbatch);
+ for_each_possible_cpu(cpu) {
+ pos = llist_del_all(per_cpu_ptr(&swap_dropbehind_llist, cpu));
+ llist_for_each_safe(pos, next, pos) {
+ struct folio *folio = container_of((struct list_head *)pos,
+ struct folio, lru);
+ swap_dropbehind_free_batch_folio(folio, &fbatch);
+ }
+ }
+ if (fbatch.nr)
+ folios_put(&fbatch);
+}
+
+static DECLARE_WORK(swap_dropbehind_work, swap_dropbehind_workfn);
+static struct workqueue_struct *swap_dropbehind_wq;
+
+void swap_writeback_dropbehind_folio(struct folio *folio)
+{
+ llist_add((struct llist_node *)&folio->lru,
+ raw_cpu_ptr(&swap_dropbehind_llist));
+ queue_work(swap_dropbehind_wq, &swap_dropbehind_work);
+}
+
+static int __init swap_dropbehind_init(void)
+{
+ swap_dropbehind_wq = alloc_workqueue("swap_dropbehind",
+ WQ_MEM_RECLAIM | WQ_PERCPU, 0);
+ if (!swap_dropbehind_wq)
+ return -ENOMEM;
+ return 0;
+}
+core_initcall(swap_dropbehind_init);
+
/*
* If we are the only user, then try to free up the swap cache.
*
@@ -643,12 +739,13 @@ static struct folio *swap_cache_read_folio(swp_entry_t entry, gfp_t gfp,
folio = swap_cache_get_folio(entry);
if (folio)
return folio;
- folio = swap_cache_alloc_folio(entry, gfp, BIT(0), NULL, mpol, ilx);
+ folio = __swap_cache_alloc_folio(entry, gfp, BIT(0), NULL, mpol, ilx);
} while (PTR_ERR(folio) == -EEXIST);
if (IS_ERR_OR_NULL(folio))
return NULL;
+ folio_add_lru(folio);
swap_read_folio(folio, plug);
if (readahead) {
folio_set_readahead(folio);
@@ -683,12 +780,13 @@ struct folio *swapin_sync(swp_entry_t entry, gfp_t gfp, unsigned long orders,
folio = swap_cache_get_folio(entry);
if (folio)
return folio;
- folio = swap_cache_alloc_folio(entry, gfp, orders, vmf, mpol, ilx);
+ folio = __swap_cache_alloc_folio(entry, gfp, orders, vmf, mpol, ilx);
} while (PTR_ERR(folio) == -EEXIST);
if (IS_ERR(folio))
return folio;
+ folio_add_lru(folio);
swap_read_folio(folio, NULL);
return folio;
}
diff --git a/mm/truncate.c b/mm/truncate.c
index b58ba940be474..37f7d8278c1cd 100644
--- a/mm/truncate.c
+++ b/mm/truncate.c
@@ -336,7 +336,7 @@ long mapping_evict_folio(struct address_space *mapping, struct folio *folio)
if (!filemap_release_folio(folio, 0))
return 0;
- return remove_mapping(mapping, folio);
+ return remove_mapping(mapping, folio, false, NULL);
}
/**
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 56708d1d2dfd5..c40513e0b50b2 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -784,17 +784,24 @@ static int __remove_mapping(struct address_space *mapping, struct folio *folio,
* remove_mapping() - Attempt to remove a folio from its mapping.
* @mapping: The address space.
* @folio: The folio to remove.
+ * @reclaimed: Whether the folio is being reclaimed (record a shadow).
+ * @target_memcg: The memcg to charge the eviction shadow to when @reclaimed;
+ * the caller must keep it alive across the call. Ignored (may
+ * be NULL) when @reclaimed is false.
*
* If the folio is dirty, under writeback or if someone else has a ref
- * on it, removal will fail.
+ * on it, removal will fail. When @reclaimed is true, a workingset eviction
+ * shadow is stored (like page reclaim does) so that a later refault can be
+ * detected and the folio re-activated.
* Return: The number of pages removed from the mapping. 0 if the folio
* could not be removed.
* Context: The caller should have a single refcount on the folio and
* hold its lock.
*/
-long remove_mapping(struct address_space *mapping, struct folio *folio)
+long remove_mapping(struct address_space *mapping, struct folio *folio,
+ bool reclaimed, struct mem_cgroup *target_memcg)
{
- if (__remove_mapping(mapping, folio, false, NULL)) {
+ if (__remove_mapping(mapping, folio, reclaimed, target_memcg)) {
/*
* Unfreezing the refcount with 1 effectively
* drops the pagecache ref for us without requiring another
diff --git a/mm/zswap.c b/mm/zswap.c
index 761cd699e0a3e..a35671e837f89 100644
--- a/mm/zswap.c
+++ b/mm/zswap.c
@@ -1000,8 +1000,8 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
return -EEXIST;
mpol = get_task_policy(current);
- folio = swap_cache_alloc_folio(swpentry, GFP_KERNEL, BIT(0), NULL, mpol,
- NO_INTERLEAVE_INDEX);
+ folio = __swap_cache_alloc_folio(swpentry, GFP_KERNEL, BIT(0), NULL, mpol,
+ NO_INTERLEAVE_INDEX);
put_swap_device(si);
/*
@@ -1045,8 +1045,7 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
/* folio is up to date */
folio_mark_uptodate(folio);
- /* move it to the tail of the inactive list after end_writeback */
- folio_set_reclaim(folio);
+ folio_set_dropbehind(folio);
/* start writeback */
__swap_writepage(folio, NULL);
|
| 33/3 | 2026/08/18 19:54 | llm | kmsan-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 6500 output: 230 thoughts: 576 Thoughts: **KMSAN Assessment: This Patch is Safe** Okay, so I've taken a look at this patch series, and it's pretty clear where it's focusing: the swap cache, writeback mechanisms, and LRU behavior. Specifically, we're seeing changes like the introduction of a "dropbehind" mechanism for swap cache folios, modifications to `remove_mapping`, a name change (`swap_cache_alloc_folio` to `__swap_cache_alloc_folio`), and new functions like `swap_writeback_dropbehind_folio` and a change to zswap's folio setting. Essentially, this is all internal memory management within the kernel. It's handling page lifecycles, LRU list maintenance, and interactions with the swap cache. Nothing here is touching data structures that are then sent to user space or implementing conditional logic depending on uninitialized variables, which, obviously, KMSAN loves to dig up. There aren't any complex new data structures being allocated and passed up to user space either. The potential issues I can envision here are all pretty textbook memory management bugs: use-after-free scenarios, NULL pointer dereferences, or maybe memory leaks. Things like this are bread and butter for KASAN, KMEMLEAK, and generally good kernel testing. These are pretty common and well-understood classes of errors. KMSAN really shines when you're dealing with initialization issues and exposed uninitialized memory or control flow that could be corrupted by uninitialized memory. That just isn't here. I went through the code and specifically checked `folio_set_dropbehind` and `swap_dropbehind_workfn` for potential uninitialized memory exposure, but they're just managing the folio's position in the swap cache and LRU, and the folio allocation itself is standard page allocation. No problems there. Therefore, my assessment is: **NeedsKMSAN is false.** This patch is safe from the particular vulnerabilities KMSAN is designed to detect. The standard memory safety tools will be sufficient. |
| 34/3 | 2026/08/18 19:54 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch series modifies the internal memory management subsystem, specifically focusing on swap cache allocation, writeback, and LRU management (introducing a \"dropbehind\" mechanism for zswap writeback). The changes involve page lifecycle management, workqueues for freeing folios, and modifying function signatures like `remove_mapping` and `swap_cache_alloc_folio`.\n\nThese modifications do not introduce new data structures that are copied to user space, nor do they alter bounds checking or initialization of memory buffers. Any potential bugs introduced by these changes would manifest as use-after-free, double-free, memory leaks, or NULL pointer dereferences. These types of memory corruption and lifecycle bugs are effectively detected by standard KASAN, KMEMLEAK, and LOCKDEP. There is no risk of exposing uninitialized memory to user space or using uninitialized memory in control flow, which are the specific classes of bugs KMSAN is designed to catch. Therefore, a dedicated KMSAN fuzzing session is not necessary.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch series modifies the internal memory management subsystem, specifically focusing on swap cache allocation, writeback, and LRU management (introducing a \"dropbehind\" mechanism for zswap writeback). The changes involve page lifecycle management, workqueues for freeing folios, and modifying function signatures like `remove_mapping` and `swap_cache_alloc_folio`.\n\nThese modifications do not introduce new data structures that are copied to user space, nor do they alter bounds checking or initialization of memory buffers. Any potential bugs introduced by these changes would manifest as use-after-free, double-free, memory leaks, or NULL pointer dereferences. These types of memory corruption and lifecycle bugs are effectively detected by standard KASAN, KMEMLEAK, and LOCKDEP. There is no risk of exposing uninitialized memory to user space or using uninitialized memory in control flow, which are the specific classes of bugs KMSAN is designed to catch. Therefore, a dedicated KMSAN fuzzing session is not necessary.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|