| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/09/11 11:18 | flow | patch-triage |
1mArgs:null Results: {
"EnableConfigs": [
"SWAP",
"ZSWAP"
],
"FocusSymbols": [
"zswap_folio_is_writeback_buffer",
"zswap_lookup_and_clear_shadows",
"zswap_writeback_entry",
"zswap_store",
"workingset_refault_lru_managed",
"__swap_cache_alloc_folio"
],
"KMSANReasoning": "The patch series modifies core memory management code in mm/ (swap cache, zswap writeback, and workingset refault tracking for LRU-managed folios). It adjusts how shadow entries are parked in the zswap tree during writeback and restored/refaulted during swapin.\n\n1. No kernel-to-user info-leak risk: No data is exposed or copied to user space (no copy_to_user, netlink, ioctls, or BPF buffers).\n2. No uninitialized memory usage: All newly introduced or modified local variables (such as `shadow`, `shadow_parked`, and loop indices) are explicitly initialized before use or assigned by functions that return pointers/values.\n3. Bug applicability: Potential risks introduced by these changes involve page locking, race conditions in swapcache/LRU manipulation, refcounting, or invalid xarray operations. These are detectable by standard debugging tools (KASAN, LOCKDEP, VM_WARN/VM_BUG assertions), not KMSAN.\n\nTherefore, a dedicated KMSAN fuzzing session is not warranted.",
"NeedsKMSAN": false,
"Reasoning": "The patch modifies core memory management and zswap subsystem logic, specifically refault tracking and shadow entry preservation across zswap writeback and swap-in paths. It introduces new helpers (zswap_folio_is_writeback_buffer, zswap_lookup_and_clear_shadows, workingset_refault_lru_managed) and modifies swap cache allocation and writeback handling. These changes are reachable and warrant fuzzing to detect regressions, memory leaks, or race conditions.",
"WorthFuzzing": true
} |
| 1/1 | 2026/09/11 11:18 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 605ec6b889c9da09a9bd88724ff9c3217d185b8e\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Fri Sep 11 11:18:34 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/include/linux/zswap.h b/include/linux/zswap.h\nindex 30c193a1207e1..dfb7153236f5c 100644\n--- a/include/linux/zswap.h\n+++ b/include/linux/zswap.h\n@@ -35,6 +35,8 @@ void zswap_lruvec_state_init(struct lruvec *lruvec);\n void zswap_folio_swapin(struct folio *folio);\n bool zswap_is_enabled(void);\n bool zswap_never_enabled(void);\n+bool zswap_folio_is_writeback_buffer(struct folio *folio);\n+void *zswap_lookup_and_clear_shadows(struct folio *folio);\n #else\n \n struct zswap_lruvec_state {};\n@@ -69,6 +71,16 @@ static inline bool zswap_never_enabled(void)\n \treturn true;\n }\n \n+static inline bool zswap_folio_is_writeback_buffer(struct folio *folio)\n+{\n+\treturn false;\n+}\n+\n+static inline void *zswap_lookup_and_clear_shadows(struct folio *folio)\n+{\n+\treturn NULL;\n+}\n+\n #endif\n \n #endif /* _LINUX_ZSWAP_H */\ndiff --git a/mm/internal.h b/mm/internal.h\nindex 8be3d28d9c3d8..d19947a3c3385 100644\n--- a/mm/internal.h\n+++ b/mm/internal.h\n@@ -31,6 +31,7 @@ void workingset_age_nonresident(struct lruvec *lruvec, unsigned long nr_pages);\n void *workingset_eviction(struct folio *folio,\n \t\t\t struct mem_cgroup *target_memcg);\n void workingset_refault(struct folio *folio, void *shadow);\n+void workingset_refault_lru_managed(struct folio *folio, void *shadow);\n void workingset_activation(struct folio *folio);\n \n /* mm/folio.c */\ndiff --git a/mm/memory.c b/mm/memory.c\nindex a2a63ae0967c1..0043a9237a8dd 100644\n--- a/mm/memory.c\n+++ b/mm/memory.c\n@@ -4857,6 +4857,7 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)\n \tunsigned long page_idx;\n \tunsigned long address;\n \tpte_t *ptep;\n+\tvoid *shadow;\n \n \tif (!pte_unmap_same(vmf))\n \t\tgoto out;\n@@ -4984,6 +4985,10 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)\n \t\tgoto out_page;\n \t}\n \n+\tshadow = zswap_lookup_and_clear_shadows(folio);\n+\tif (shadow)\n+\t\tworkingset_refault_lru_managed(folio, shadow);\n+\n \t/*\n \t * KSM sometimes has to copy on read faults, for example, if\n \t * folio-\u003eindex of non-ksm folios would be nonlinear inside the\ndiff --git a/mm/shmem.c b/mm/shmem.c\nindex c3212fdece9b4..53f57ad194a39 100644\n--- a/mm/shmem.c\n+++ b/mm/shmem.c\n@@ -2465,6 +2465,7 @@ static int shmem_swapin_folio(struct inode *inode, pgoff_t index,\n \tstruct folio *folio = NULL;\n \tint error, nr_pages, order;\n \tpgoff_t offset;\n+\tvoid *shadow;\n \n \tVM_BUG_ON(!*foliop || !xa_is_value(*foliop));\n \tindex_entry = radix_to_swp_entry(*foliop);\n@@ -2576,6 +2577,10 @@ static int shmem_swapin_folio(struct inode *inode, pgoff_t index,\n \t */\n \tarch_swap_restore(folio_swap(swap, folio), folio);\n \n+\tshadow = zswap_lookup_and_clear_shadows(folio);\n+\tif (shadow)\n+\t\tworkingset_refault_lru_managed(folio, shadow);\n+\n \tif (shmem_should_replace_folio(folio, gfp)) {\n \t\terror = shmem_replace_folio(\u0026folio, gfp, info, index, vma);\n \t\tif (error)\ndiff --git a/mm/swap.h b/mm/swap.h\nindex 0b5d507739bcb..ddd108b41d33a 100644\n--- a/mm/swap.h\n+++ b/mm/swap.h\n@@ -312,9 +312,10 @@ 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+\t\t\t\t void **shadowp);\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 305877e1f4d7b..e80a694dddb47 100644\n--- a/mm/swap_state.c\n+++ b/mm/swap_state.c\n@@ -24,6 +24,7 @@\n #include \u003clinux/shmem_fs.h\u003e\n #include \u003clinux/sysctl.h\u003e\n #include \u003clinux/swap_ops.h\u003e\n+#include \u003clinux/zswap.h\u003e\n #include \"internal.h\"\n #include \"swap_table.h\"\n #include \"swap.h\"\n@@ -263,12 +264,21 @@ static void __swap_cache_do_del_folio(struct swap_cluster_info *ci,\n \tunsigned int ci_start, ci_off, ci_end;\n \tbool folio_swapped = false, need_free = false;\n \tunsigned long nr_pages = folio_nr_pages(folio);\n+\tvoid *shadow_parked;\n \n \tVM_WARN_ON_ONCE(__swap_entry_to_cluster(entry) != ci);\n \tVM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);\n \tVM_WARN_ON_ONCE_FOLIO(!folio_test_swapcache(folio), folio);\n \tVM_WARN_ON_ONCE_FOLIO(folio_test_writeback(folio), folio);\n \n+\t/*\n+\t * A zswap writeback buffer parked the slot's original shadow in the\n+\t * zswap tree: restore it into the slot for later swap-in.\n+\t */\n+\tshadow_parked = zswap_lookup_and_clear_shadows(folio);\n+\tif (shadow_parked)\n+\t\tshadow = shadow_parked;\n+\n \tsi = __swap_entry_to_info(entry);\n \tci_start = swp_cluster_offset(entry);\n \tci_end = ci_start + nr_pages;\n@@ -409,7 +419,8 @@ void __swap_cache_replace_folio(struct swap_cluster_info *ci,\n static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,\n \t\t\t\t\tswp_entry_t targ_entry, gfp_t gfp,\n \t\t\t\t\tunsigned int order, struct vm_fault *vmf,\n-\t\t\t\t\tstruct mempolicy *mpol, pgoff_t ilx)\n+\t\t\t\t\tstruct mempolicy *mpol, pgoff_t ilx,\n+\t\t\t\t\tvoid **shadowp)\n {\n \tint err;\n \tswp_entry_t entry;\n@@ -483,37 +494,42 @@ static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,\n \n \t/* memsw uncharges swap when folio is added to swap cache */\n \tmemcg1_swapin(folio);\n-\tif (shadow)\n-\t\tworkingset_refault(folio, shadow);\n \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+\tif (shadowp)\n+\t\t*shadowp = shadow;\n+\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 * @vmf: fault information\n * @mpol: NUMA memory allocation policy to be applied\n * @ilx: NUMA interleave index, for use only when MPOL_INTERLEAVE\n+ * @shadowp: Returns the shadow the allocation displaced, NULL to ignore\n *\n * Allocate a folio in the swap cache for one swap slot, typically before\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+\t\t\t\t void **shadowp)\n {\n \tint order, err;\n \tstruct folio *ret;\n@@ -528,7 +544,7 @@ struct folio *swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,\n \n \tdo {\n \t\tret = __swap_cache_alloc(ci, targ_entry, gfp, order,\n-\t\t\t\t\t vmf, mpol, ilx);\n+\t\t\t\t\t vmf, mpol, ilx, shadowp);\n \t\tif (!IS_ERR(ret))\n \t\t\tbreak;\n \t\terr = PTR_ERR(ret);\n@@ -644,17 +660,23 @@ static struct folio *swap_cache_read_folio(struct swap_io_ctx *ctx,\n \t\tpgoff_t ilx, bool readahead)\n {\n \tstruct folio *folio;\n+\tvoid *shadow = NULL;\n \n \tdo {\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,\n+\t\t\t\t\t\t ilx, \u0026shadow);\n \t} while (PTR_ERR(folio) == -EEXIST);\n \n \tif (IS_ERR_OR_NULL(folio))\n \t\treturn NULL;\n \n+\tif (shadow)\n+\t\tworkingset_refault(folio, shadow);\n+\n+\tfolio_add_lru(folio);\n \tswap_read_folio(ctx, folio);\n \tif (readahead) {\n \t\tfolio_set_readahead(folio);\n@@ -685,17 +707,23 @@ struct folio *swapin_sync(swp_entry_t entry, gfp_t gfp, unsigned long orders,\n {\n \tstruct swap_io_ctx ctx = {};\n \tstruct folio *folio;\n+\tvoid *shadow = NULL;\n \n \tdo {\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,\n+\t\t\t\t\t\t ilx, \u0026shadow);\n \t} while (PTR_ERR(folio) == -EEXIST);\n \n \tif (IS_ERR(folio))\n \t\treturn folio;\n \n+\tif (shadow)\n+\t\tworkingset_refault(folio, shadow);\n+\n+\tfolio_add_lru(folio);\n \tswap_read_folio(\u0026ctx, folio);\n \tswap_read_submit(\u0026ctx);\n \treturn folio;\ndiff --git a/mm/swapfile.c b/mm/swapfile.c\nindex 48d3cd40defdb..6101f90e91d2a 100644\n--- a/mm/swapfile.c\n+++ b/mm/swapfile.c\n@@ -1885,7 +1885,7 @@ void folio_put_swap(struct folio *folio, struct page *page)\n * CPU1\t\t\t\tCPU2\n * do_swap_page()\n * ...\t\t\t\tswapoff+swapon\n- * swap_cache_alloc_folio()\n+ * __swap_cache_alloc_folio()\n * // check swap_map\n * // verify PTE not changed\n *\ndiff --git a/mm/vmscan.c b/mm/vmscan.c\nindex 40d3f1b48a74c..1ca6489e50122 100644\n--- a/mm/vmscan.c\n+++ b/mm/vmscan.c\n@@ -48,6 +48,7 @@\n #include \u003clinux/prefetch.h\u003e\n #include \u003clinux/printk.h\u003e\n #include \u003clinux/dax.h\u003e\n+#include \u003clinux/zswap.h\u003e\n #include \u003clinux/psi.h\u003e\n #include \u003clinux/pagewalk.h\u003e\n #include \u003clinux/shmem_fs.h\u003e\n@@ -753,7 +754,8 @@ static int __remove_mapping(struct address_space *mapping, struct folio *folio,\n \tif (folio_test_swapcache(folio)) {\n \t\tswp_entry_t swap = folio-\u003eswap;\n \n-\t\tif (reclaimed \u0026\u0026 !mapping_exiting(mapping))\n+\t\tif (reclaimed \u0026\u0026 !mapping_exiting(mapping) \u0026\u0026\n+\t\t !zswap_folio_is_writeback_buffer(folio))\n \t\t\tshadow = workingset_eviction(folio, target_memcg);\n \t\t__memcg1_swapout(folio, ci);\n \t\t__swap_cache_del_folio(ci, folio, swap, shadow);\ndiff --git a/mm/workingset.c b/mm/workingset.c\nindex 8412f4840ae35..8e45b0baeb36d 100644\n--- a/mm/workingset.c\n+++ b/mm/workingset.c\n@@ -536,16 +536,8 @@ bool workingset_test_recent(void *shadow, bool file, bool *workingset,\n \treturn refault_distance \u003c= workingset_size;\n }\n \n-/**\n- * workingset_refault - Evaluate the refault of a previously evicted folio.\n- * @folio: The freshly allocated replacement folio.\n- * @shadow: Shadow entry of the evicted folio.\n- *\n- * Calculates and evaluates the refault distance of the previously\n- * evicted folio in the context of the node and the memcg whose memory\n- * pressure caused the eviction.\n- */\n-void workingset_refault(struct folio *folio, void *shadow)\n+static void __workingset_refault(struct folio *folio, void *shadow,\n+\t\t\t\t bool lru_managed)\n {\n \tbool file = folio_is_file_lru(folio);\n \tstruct mem_cgroup *memcg;\n@@ -577,7 +569,16 @@ void workingset_refault(struct folio *folio, void *shadow)\n \tif (!workingset_test_recent(shadow, file, \u0026workingset, true))\n \t\tgoto out;\n \n-\tfolio_set_active(folio);\n+\t/*\n+\t * An LRU-managed folio may sit in a per-CPU batch, which cannot be\n+\t * determined here: setting the flag would race the drain and leave it\n+\t * disagreeing with the list. folio_activate() is safe, but misses the\n+\t * activation for such a folio.\n+\t */\n+\tif (lru_managed)\n+\t\tfolio_activate(folio);\n+\telse\n+\t\tfolio_set_active(folio);\n \tworkingset_age_nonresident(lruvec, nr);\n \tmod_lruvec_state(lruvec, WORKINGSET_ACTIVATE_BASE + file, nr);\n \n@@ -590,6 +591,43 @@ void workingset_refault(struct folio *folio, void *shadow)\n \tmem_cgroup_put(memcg);\n }\n \n+/**\n+ * workingset_refault - Evaluate the refault of a previously evicted folio.\n+ * @folio: The freshly allocated replacement folio.\n+ * @shadow: Shadow entry of the evicted folio.\n+ *\n+ * Calculates and evaluates the refault distance of the previously\n+ * evicted folio in the context of the node and the memcg whose memory\n+ * pressure caused the eviction.\n+ *\n+ * Context: @folio must be locked and not on the LRU yet, the caller adds\n+ * it with folio_add_lru() afterwards.\n+ */\n+void workingset_refault(struct folio *folio, void *shadow)\n+{\n+\t__workingset_refault(folio, shadow, false);\n+}\n+\n+/**\n+ * workingset_refault_lru_managed - Evaluate the refault of a previously\n+ * evicted folio that is already on the LRU.\n+ * @folio: The folio the eviction is refaulted into.\n+ * @shadow: Shadow entry of the evicted folio.\n+ *\n+ * Like workingset_refault(), but for a folio the caller has already added\n+ * to the LRU, which has to be activated by moving it between the lists\n+ * rather than by setting PG_active.\n+ *\n+ * The activation is best effort: a folio still sitting in a per-CPU LRU\n+ * batch cannot be moved yet and is left inactive.\n+ *\n+ * Context: @folio must be locked and already added to the LRU.\n+ */\n+void workingset_refault_lru_managed(struct folio *folio, void *shadow)\n+{\n+\t__workingset_refault(folio, shadow, true);\n+}\n+\n /**\n * workingset_activation - note a page activation\n * @folio: Folio that is being activated.\ndiff --git a/mm/zswap.c b/mm/zswap.c\nindex c27e22d067e3e..904821db401f3 100644\n--- a/mm/zswap.c\n+++ b/mm/zswap.c\n@@ -961,6 +961,79 @@ static bool zswap_decompress(struct zswap_entry *entry, struct folio *folio)\n /*********************************\n * writeback code\n **********************************/\n+\n+#define ZSWAP_WRITEBACK_NO_SHADOW xa_mk_value(0)\n+\n+/**\n+ * zswap_folio_is_writeback_buffer - is @folio a zswap writeback buffer?\n+ * @folio: the folio being examined (typically a swap cache folio under reclaim)\n+ *\n+ * A folio is a zswap writeback buffer when every one of its swap offsets holds\n+ * a parked writeback shadow (a real shadow or the ZSWAP_WRITEBACK_NO_SHADOW\n+ * sentinel) in the zswap tree rather than a live zswap entry.\n+ *\n+ * Return: true if @folio is a writeback buffer, in which case the reclaim path\n+ * must not mint a fresh workingset shadow for it.\n+ */\n+bool zswap_folio_is_writeback_buffer(struct folio *folio)\n+{\n+\tswp_entry_t swp = folio-\u003eswap;\n+\tunsigned long nr_pages = folio_nr_pages(folio);\n+\tpgoff_t offset = swp_offset(swp);\n+\tunsigned long i;\n+\n+\tif (zswap_never_enabled())\n+\t\treturn false;\n+\n+\tfor (i = 0; i \u003c nr_pages; i++) {\n+\t\tswp_entry_t e = swp_entry(swp_type(swp), offset + i);\n+\n+\t\tif (!xa_is_value(xa_load(swap_zswap_tree(e), offset + i)))\n+\t\t\treturn false;\n+\t}\n+\n+\treturn true;\n+}\n+\n+/**\n+ * zswap_lookup_and_clear_shadows - retrieve and clear @folio's parked shadow(s)\n+ * @folio: the writeback buffer folio (or the swapin folio that consumed it)\n+ *\n+ * Remove any parked writeback shadows for @folio's swap offset(s) from the\n+ * zswap tree.\n+ *\n+ * Return: the preserved workingset shadow, or NULL if the slot(s) had no shadow\n+ * (sentinel only) or nothing parked. The caller either restores the returned\n+ * shadow into the swap slot (buffer dropped) or feeds it to\n+ * workingset_refault_lru_managed() (buffer consumed by a swapin); clearing\n+ * here ensures the two paths never double-count.\n+ */\n+void *zswap_lookup_and_clear_shadows(struct folio *folio)\n+{\n+\tswp_entry_t swp = folio-\u003eswap;\n+\tunsigned long nr_pages = folio_nr_pages(folio);\n+\tpgoff_t offset = swp_offset(swp);\n+\tvoid *shadow = NULL;\n+\tunsigned long i;\n+\n+\tif (zswap_never_enabled())\n+\t\treturn NULL;\n+\n+\tfor (i = 0; i \u003c nr_pages; i++) {\n+\t\tswp_entry_t e = swp_entry(swp_type(swp), offset + i);\n+\t\tstruct xarray *tree = swap_zswap_tree(e);\n+\t\tvoid *parked = xa_load(tree, offset + i);\n+\n+\t\tif (!xa_is_value(parked))\n+\t\t\tcontinue;\n+\t\txa_erase(tree, offset + i);\n+\t\tif (parked != ZSWAP_WRITEBACK_NO_SHADOW)\n+\t\t\tshadow = parked;\n+\t}\n+\n+\treturn shadow;\n+}\n+\n /*\n * Attempts to free an entry by adding a folio to the swap cache,\n * decompressing the entry data into the folio, and issuing a\n@@ -976,12 +1049,14 @@ static bool zswap_decompress(struct zswap_entry *entry, struct folio *folio)\n static int zswap_writeback_entry(struct zswap_entry *entry,\n \t\t\t\t swp_entry_t swpentry)\n {\n+\tstruct swap_cluster_info *ci;\n \tstruct xarray *tree;\n \tpgoff_t offset = swp_offset(swpentry);\n \tstruct folio *folio;\n \tstruct mempolicy *mpol;\n \tstruct swap_info_struct *si;\n \tstruct swap_io_ctx ctx = {};\n+\tvoid *shadow;\n \tint ret = 0;\n \n \t/* try to allocate swap cache folio */\n@@ -990,8 +1065,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, \u0026shadow);\n \tput_swap_device(si);\n \n \t/*\n@@ -1003,6 +1078,7 @@ static int zswap_writeback_entry(struct zswap_entry *entry,\n \t */\n \tif (IS_ERR(folio))\n \t\treturn PTR_ERR(folio);\n+\tfolio_add_lru(folio);\n \n \t/*\n \t * folio is locked, and the swapcache is now secured against\n@@ -1024,7 +1100,12 @@ static int zswap_writeback_entry(struct zswap_entry *entry,\n \t\tgoto out;\n \t}\n \n-\txa_erase(tree, offset);\n+\t/*\n+\t * A slot with no shadow already reads back as ZSWAP_WRITEBACK_NO_SHADOW:\n+\t * the swap table marks a swapped out slot with xa_mk_value(0) and holds\n+\t * the shadow, when there is one, in that same value.\n+\t */\n+\txa_store(tree, offset, shadow, GFP_KERNEL);\n \n \tcount_vm_event(ZSWPWB);\n \tif (entry-\u003eobjcg)\n@@ -1044,7 +1125,15 @@ static int zswap_writeback_entry(struct zswap_entry *entry,\n \n out:\n \tif (ret) {\n-\t\tswap_cache_del_folio(folio);\n+\t\t/*\n+\t\t * The allocation above overwrote the slot's shadow and nothing\n+\t\t * has been parked for it yet, so restore it as the folio leaves\n+\t\t * the swap cache, or the eviction it records is lost.\n+\t\t */\n+\t\tci = swap_cluster_lock(__swap_entry_to_info(swpentry), offset);\n+\t\t__swap_cache_del_folio(ci, folio, swpentry, shadow);\n+\t\tswap_cluster_unlock(ci);\n+\t\tfolio_ref_sub(folio, folio_nr_pages(folio));\n \t\tfolio_unlock(folio);\n \t}\n \tfolio_put(folio);\n@@ -1487,6 +1576,17 @@ bool zswap_store(struct folio *folio)\n \tVM_WARN_ON_ONCE(!folio_test_locked(folio));\n \tVM_WARN_ON_ONCE(!folio_test_swapcache(folio));\n \n+\t/*\n+\t * A writeback buffer whose IO failed is redirtied and left in the\n+\t * swap cache, so reclaim writes it out again. Writeback already\n+\t * decided this data belongs on disk, so send it there instead of\n+\t * compressing it back into zswap. This also leaves the shadow\n+\t * parked for the buffer in place, so it is still restored into\n+\t * the slot once the folio leaves the swap cache.\n+\t */\n+\tif (zswap_folio_is_writeback_buffer(folio))\n+\t\treturn false;\n+\n \tif (!zswap_enabled)\n \t\tgoto check_old;\n \n"
} |
| 2/1 | 2026/09/11 11:18 | agent | patch-evaluator |
0mModel:core Args: null Results: {
"EnableConfigs": [
"SWAP",
"ZSWAP"
],
"FocusSymbols": [
"zswap_folio_is_writeback_buffer",
"zswap_lookup_and_clear_shadows",
"zswap_writeback_entry",
"zswap_store",
"workingset_refault_lru_managed",
"__swap_cache_alloc_folio"
],
"Reasoning": "The patch modifies core memory management and zswap subsystem logic, specifically refault tracking and shadow entry preservation across zswap writeback and swap-in paths. It introduces new helpers (zswap_folio_is_writeback_buffer, zswap_lookup_and_clear_shadows, workingset_refault_lru_managed) and modifies swap cache allocation and writeback handling. These changes are reachable and warrant fuzzing to detect regressions, memory leaks, or race conditions.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.
IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.
================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.
- REACHABILITY IS THE PRIMARY GATE:
Fuzzing can only discover bugs in code that can actually execute in standard virtualized
environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
regardless of whether it adds assertions or complex logic.
- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
Patch authors routinely label changes as "cleanups", "refactorings", or state
"No functional change intended". Do NOT take these claims at face value.
Code refactorings that rearrange logic, introduce helper functions, or alter state management
in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
If reachable executable code is modified or refactored, it MUST be fuzzed.
- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
Even if the author believes the invariant always holds, fuzzing is essential to verify whether
an unusual sequence of operations can violate it.
================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:
- Non-kernel and non-executable changes:
* Modifications to Documentation/, comments, or spelling fixes.
* User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
that do not affect the compiled kernel image (vmlinux) or kernel modules.
* Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
that do not alter control flow or data structures.
* Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
* Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
* Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
physical PCI hot-unplug or manual sysfs driver unbinding.
* Code paths exclusive to architectures other than the target architecture.
================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
* Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
* Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
* Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
* Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).
================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:
- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
Do NOT list generic, ubiquitous functions called by almost every program in the corpus
(including, but not limited to: general memory allocators and deallocators, page fault
and trap handlers, or core synchronization primitives; this is not an exhaustive list).
Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
which severely dilutes fuzzing effort away from the actual changes.
- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
List functions that specifically implement the logic being added or altered, or direct API entrypoints
for the subsystem feature under review.
- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).
================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit 605ec6b889c9da09a9bd88724ff9c3217d185b8e
Author: syz-cluster <triage@syzkaller.com>
Date: Fri Sep 11 11:18:34 2026 +0000
syz-cluster: applied patch under review
diff --git a/include/linux/zswap.h b/include/linux/zswap.h
index 30c193a1207e1..dfb7153236f5c 100644
--- a/include/linux/zswap.h
+++ b/include/linux/zswap.h
@@ -35,6 +35,8 @@ void zswap_lruvec_state_init(struct lruvec *lruvec);
void zswap_folio_swapin(struct folio *folio);
bool zswap_is_enabled(void);
bool zswap_never_enabled(void);
+bool zswap_folio_is_writeback_buffer(struct folio *folio);
+void *zswap_lookup_and_clear_shadows(struct folio *folio);
#else
struct zswap_lruvec_state {};
@@ -69,6 +71,16 @@ static inline bool zswap_never_enabled(void)
return true;
}
+static inline bool zswap_folio_is_writeback_buffer(struct folio *folio)
+{
+ return false;
+}
+
+static inline void *zswap_lookup_and_clear_shadows(struct folio *folio)
+{
+ return NULL;
+}
+
#endif
#endif /* _LINUX_ZSWAP_H */
diff --git a/mm/internal.h b/mm/internal.h
index 8be3d28d9c3d8..d19947a3c3385 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -31,6 +31,7 @@ void workingset_age_nonresident(struct lruvec *lruvec, unsigned long nr_pages);
void *workingset_eviction(struct folio *folio,
struct mem_cgroup *target_memcg);
void workingset_refault(struct folio *folio, void *shadow);
+void workingset_refault_lru_managed(struct folio *folio, void *shadow);
void workingset_activation(struct folio *folio);
/* mm/folio.c */
diff --git a/mm/memory.c b/mm/memory.c
index a2a63ae0967c1..0043a9237a8dd 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -4857,6 +4857,7 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
unsigned long page_idx;
unsigned long address;
pte_t *ptep;
+ void *shadow;
if (!pte_unmap_same(vmf))
goto out;
@@ -4984,6 +4985,10 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
goto out_page;
}
+ shadow = zswap_lookup_and_clear_shadows(folio);
+ if (shadow)
+ workingset_refault_lru_managed(folio, shadow);
+
/*
* KSM sometimes has to copy on read faults, for example, if
* folio->index of non-ksm folios would be nonlinear inside the
diff --git a/mm/shmem.c b/mm/shmem.c
index c3212fdece9b4..53f57ad194a39 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -2465,6 +2465,7 @@ static int shmem_swapin_folio(struct inode *inode, pgoff_t index,
struct folio *folio = NULL;
int error, nr_pages, order;
pgoff_t offset;
+ void *shadow;
VM_BUG_ON(!*foliop || !xa_is_value(*foliop));
index_entry = radix_to_swp_entry(*foliop);
@@ -2576,6 +2577,10 @@ static int shmem_swapin_folio(struct inode *inode, pgoff_t index,
*/
arch_swap_restore(folio_swap(swap, folio), folio);
+ shadow = zswap_lookup_and_clear_shadows(folio);
+ if (shadow)
+ workingset_refault_lru_managed(folio, shadow);
+
if (shmem_should_replace_folio(folio, gfp)) {
error = shmem_replace_folio(&folio, gfp, info, index, vma);
if (error)
diff --git a/mm/swap.h b/mm/swap.h
index 0b5d507739bcb..ddd108b41d33a 100644
--- a/mm/swap.h
+++ b/mm/swap.h
@@ -312,9 +312,10 @@ 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,
+ void **shadowp);
/* 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 305877e1f4d7b..e80a694dddb47 100644
--- a/mm/swap_state.c
+++ b/mm/swap_state.c
@@ -24,6 +24,7 @@
#include <linux/shmem_fs.h>
#include <linux/sysctl.h>
#include <linux/swap_ops.h>
+#include <linux/zswap.h>
#include "internal.h"
#include "swap_table.h"
#include "swap.h"
@@ -263,12 +264,21 @@ static void __swap_cache_do_del_folio(struct swap_cluster_info *ci,
unsigned int ci_start, ci_off, ci_end;
bool folio_swapped = false, need_free = false;
unsigned long nr_pages = folio_nr_pages(folio);
+ void *shadow_parked;
VM_WARN_ON_ONCE(__swap_entry_to_cluster(entry) != ci);
VM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);
VM_WARN_ON_ONCE_FOLIO(!folio_test_swapcache(folio), folio);
VM_WARN_ON_ONCE_FOLIO(folio_test_writeback(folio), folio);
+ /*
+ * A zswap writeback buffer parked the slot's original shadow in the
+ * zswap tree: restore it into the slot for later swap-in.
+ */
+ shadow_parked = zswap_lookup_and_clear_shadows(folio);
+ if (shadow_parked)
+ shadow = shadow_parked;
+
si = __swap_entry_to_info(entry);
ci_start = swp_cluster_offset(entry);
ci_end = ci_start + nr_pages;
@@ -409,7 +419,8 @@ void __swap_cache_replace_folio(struct swap_cluster_info *ci,
static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,
swp_entry_t targ_entry, gfp_t gfp,
unsigned int order, struct vm_fault *vmf,
- struct mempolicy *mpol, pgoff_t ilx)
+ struct mempolicy *mpol, pgoff_t ilx,
+ void **shadowp)
{
int err;
swp_entry_t entry;
@@ -483,37 +494,42 @@ static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,
/* memsw uncharges swap when folio is added to swap cache */
memcg1_swapin(folio);
- if (shadow)
- workingset_refault(folio, shadow);
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);
+ if (shadowp)
+ *shadowp = shadow;
+
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
* @vmf: fault information
* @mpol: NUMA memory allocation policy to be applied
* @ilx: NUMA interleave index, for use only when MPOL_INTERLEAVE
+ * @shadowp: Returns the shadow the allocation displaced, NULL to ignore
*
* Allocate a folio in the swap cache for one swap slot, typically before
* 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,
+ void **shadowp)
{
int order, err;
struct folio *ret;
@@ -528,7 +544,7 @@ struct folio *swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,
do {
ret = __swap_cache_alloc(ci, targ_entry, gfp, order,
- vmf, mpol, ilx);
+ vmf, mpol, ilx, shadowp);
if (!IS_ERR(ret))
break;
err = PTR_ERR(ret);
@@ -644,17 +660,23 @@ static struct folio *swap_cache_read_folio(struct swap_io_ctx *ctx,
pgoff_t ilx, bool readahead)
{
struct folio *folio;
+ void *shadow = NULL;
do {
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, &shadow);
} while (PTR_ERR(folio) == -EEXIST);
if (IS_ERR_OR_NULL(folio))
return NULL;
+ if (shadow)
+ workingset_refault(folio, shadow);
+
+ folio_add_lru(folio);
swap_read_folio(ctx, folio);
if (readahead) {
folio_set_readahead(folio);
@@ -685,17 +707,23 @@ struct folio *swapin_sync(swp_entry_t entry, gfp_t gfp, unsigned long orders,
{
struct swap_io_ctx ctx = {};
struct folio *folio;
+ void *shadow = NULL;
do {
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, &shadow);
} while (PTR_ERR(folio) == -EEXIST);
if (IS_ERR(folio))
return folio;
+ if (shadow)
+ workingset_refault(folio, shadow);
+
+ folio_add_lru(folio);
swap_read_folio(&ctx, folio);
swap_read_submit(&ctx);
return folio;
diff --git a/mm/swapfile.c b/mm/swapfile.c
index 48d3cd40defdb..6101f90e91d2a 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -1885,7 +1885,7 @@ void folio_put_swap(struct folio *folio, struct page *page)
* CPU1 CPU2
* do_swap_page()
* ... swapoff+swapon
- * swap_cache_alloc_folio()
+ * __swap_cache_alloc_folio()
* // check swap_map
* // verify PTE not changed
*
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 40d3f1b48a74c..1ca6489e50122 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -48,6 +48,7 @@
#include <linux/prefetch.h>
#include <linux/printk.h>
#include <linux/dax.h>
+#include <linux/zswap.h>
#include <linux/psi.h>
#include <linux/pagewalk.h>
#include <linux/shmem_fs.h>
@@ -753,7 +754,8 @@ static int __remove_mapping(struct address_space *mapping, struct folio *folio,
if (folio_test_swapcache(folio)) {
swp_entry_t swap = folio->swap;
- if (reclaimed && !mapping_exiting(mapping))
+ if (reclaimed && !mapping_exiting(mapping) &&
+ !zswap_folio_is_writeback_buffer(folio))
shadow = workingset_eviction(folio, target_memcg);
__memcg1_swapout(folio, ci);
__swap_cache_del_folio(ci, folio, swap, shadow);
diff --git a/mm/workingset.c b/mm/workingset.c
index 8412f4840ae35..8e45b0baeb36d 100644
--- a/mm/workingset.c
+++ b/mm/workingset.c
@@ -536,16 +536,8 @@ bool workingset_test_recent(void *shadow, bool file, bool *workingset,
return refault_distance <= workingset_size;
}
-/**
- * workingset_refault - Evaluate the refault of a previously evicted folio.
- * @folio: The freshly allocated replacement folio.
- * @shadow: Shadow entry of the evicted folio.
- *
- * Calculates and evaluates the refault distance of the previously
- * evicted folio in the context of the node and the memcg whose memory
- * pressure caused the eviction.
- */
-void workingset_refault(struct folio *folio, void *shadow)
+static void __workingset_refault(struct folio *folio, void *shadow,
+ bool lru_managed)
{
bool file = folio_is_file_lru(folio);
struct mem_cgroup *memcg;
@@ -577,7 +569,16 @@ void workingset_refault(struct folio *folio, void *shadow)
if (!workingset_test_recent(shadow, file, &workingset, true))
goto out;
- folio_set_active(folio);
+ /*
+ * An LRU-managed folio may sit in a per-CPU batch, which cannot be
+ * determined here: setting the flag would race the drain and leave it
+ * disagreeing with the list. folio_activate() is safe, but misses the
+ * activation for such a folio.
+ */
+ if (lru_managed)
+ folio_activate(folio);
+ else
+ folio_set_active(folio);
workingset_age_nonresident(lruvec, nr);
mod_lruvec_state(lruvec, WORKINGSET_ACTIVATE_BASE + file, nr);
@@ -590,6 +591,43 @@ void workingset_refault(struct folio *folio, void *shadow)
mem_cgroup_put(memcg);
}
+/**
+ * workingset_refault - Evaluate the refault of a previously evicted folio.
+ * @folio: The freshly allocated replacement folio.
+ * @shadow: Shadow entry of the evicted folio.
+ *
+ * Calculates and evaluates the refault distance of the previously
+ * evicted folio in the context of the node and the memcg whose memory
+ * pressure caused the eviction.
+ *
+ * Context: @folio must be locked and not on the LRU yet, the caller adds
+ * it with folio_add_lru() afterwards.
+ */
+void workingset_refault(struct folio *folio, void *shadow)
+{
+ __workingset_refault(folio, shadow, false);
+}
+
+/**
+ * workingset_refault_lru_managed - Evaluate the refault of a previously
+ * evicted folio that is already on the LRU.
+ * @folio: The folio the eviction is refaulted into.
+ * @shadow: Shadow entry of the evicted folio.
+ *
+ * Like workingset_refault(), but for a folio the caller has already added
+ * to the LRU, which has to be activated by moving it between the lists
+ * rather than by setting PG_active.
+ *
+ * The activation is best effort: a folio still sitting in a per-CPU LRU
+ * batch cannot be moved yet and is left inactive.
+ *
+ * Context: @folio must be locked and already added to the LRU.
+ */
+void workingset_refault_lru_managed(struct folio *folio, void *shadow)
+{
+ __workingset_refault(folio, shadow, true);
+}
+
/**
* workingset_activation - note a page activation
* @folio: Folio that is being activated.
diff --git a/mm/zswap.c b/mm/zswap.c
index c27e22d067e3e..904821db401f3 100644
--- a/mm/zswap.c
+++ b/mm/zswap.c
@@ -961,6 +961,79 @@ static bool zswap_decompress(struct zswap_entry *entry, struct folio *folio)
/*********************************
* writeback code
**********************************/
+
+#define ZSWAP_WRITEBACK_NO_SHADOW xa_mk_value(0)
+
+/**
+ * zswap_folio_is_writeback_buffer - is @folio a zswap writeback buffer?
+ * @folio: the folio being examined (typically a swap cache folio under reclaim)
+ *
+ * A folio is a zswap writeback buffer when every one of its swap offsets holds
+ * a parked writeback shadow (a real shadow or the ZSWAP_WRITEBACK_NO_SHADOW
+ * sentinel) in the zswap tree rather than a live zswap entry.
+ *
+ * Return: true if @folio is a writeback buffer, in which case the reclaim path
+ * must not mint a fresh workingset shadow for it.
+ */
+bool zswap_folio_is_writeback_buffer(struct folio *folio)
+{
+ swp_entry_t swp = folio->swap;
+ unsigned long nr_pages = folio_nr_pages(folio);
+ pgoff_t offset = swp_offset(swp);
+ unsigned long i;
+
+ if (zswap_never_enabled())
+ return false;
+
+ for (i = 0; i < nr_pages; i++) {
+ swp_entry_t e = swp_entry(swp_type(swp), offset + i);
+
+ if (!xa_is_value(xa_load(swap_zswap_tree(e), offset + i)))
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * zswap_lookup_and_clear_shadows - retrieve and clear @folio's parked shadow(s)
+ * @folio: the writeback buffer folio (or the swapin folio that consumed it)
+ *
+ * Remove any parked writeback shadows for @folio's swap offset(s) from the
+ * zswap tree.
+ *
+ * Return: the preserved workingset shadow, or NULL if the slot(s) had no shadow
+ * (sentinel only) or nothing parked. The caller either restores the returned
+ * shadow into the swap slot (buffer dropped) or feeds it to
+ * workingset_refault_lru_managed() (buffer consumed by a swapin); clearing
+ * here ensures the two paths never double-count.
+ */
+void *zswap_lookup_and_clear_shadows(struct folio *folio)
+{
+ swp_entry_t swp = folio->swap;
+ unsigned long nr_pages = folio_nr_pages(folio);
+ pgoff_t offset = swp_offset(swp);
+ void *shadow = NULL;
+ unsigned long i;
+
+ if (zswap_never_enabled())
+ return NULL;
+
+ for (i = 0; i < nr_pages; i++) {
+ swp_entry_t e = swp_entry(swp_type(swp), offset + i);
+ struct xarray *tree = swap_zswap_tree(e);
+ void *parked = xa_load(tree, offset + i);
+
+ if (!xa_is_value(parked))
+ continue;
+ xa_erase(tree, offset + i);
+ if (parked != ZSWAP_WRITEBACK_NO_SHADOW)
+ shadow = parked;
+ }
+
+ return shadow;
+}
+
/*
* Attempts to free an entry by adding a folio to the swap cache,
* decompressing the entry data into the folio, and issuing a
@@ -976,12 +1049,14 @@ static bool zswap_decompress(struct zswap_entry *entry, struct folio *folio)
static int zswap_writeback_entry(struct zswap_entry *entry,
swp_entry_t swpentry)
{
+ struct swap_cluster_info *ci;
struct xarray *tree;
pgoff_t offset = swp_offset(swpentry);
struct folio *folio;
struct mempolicy *mpol;
struct swap_info_struct *si;
struct swap_io_ctx ctx = {};
+ void *shadow;
int ret = 0;
/* try to allocate swap cache folio */
@@ -990,8 +1065,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, &shadow);
put_swap_device(si);
/*
@@ -1003,6 +1078,7 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
*/
if (IS_ERR(folio))
return PTR_ERR(folio);
+ folio_add_lru(folio);
/*
* folio is locked, and the swapcache is now secured against
@@ -1024,7 +1100,12 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
goto out;
}
- xa_erase(tree, offset);
+ /*
+ * A slot with no shadow already reads back as ZSWAP_WRITEBACK_NO_SHADOW:
+ * the swap table marks a swapped out slot with xa_mk_value(0) and holds
+ * the shadow, when there is one, in that same value.
+ */
+ xa_store(tree, offset, shadow, GFP_KERNEL);
count_vm_event(ZSWPWB);
if (entry->objcg)
@@ -1044,7 +1125,15 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
out:
if (ret) {
- swap_cache_del_folio(folio);
+ /*
+ * The allocation above overwrote the slot's shadow and nothing
+ * has been parked for it yet, so restore it as the folio leaves
+ * the swap cache, or the eviction it records is lost.
+ */
+ ci = swap_cluster_lock(__swap_entry_to_info(swpentry), offset);
+ __swap_cache_del_folio(ci, folio, swpentry, shadow);
+ swap_cluster_unlock(ci);
+ folio_ref_sub(folio, folio_nr_pages(folio));
folio_unlock(folio);
}
folio_put(folio);
@@ -1487,6 +1576,17 @@ bool zswap_store(struct folio *folio)
VM_WARN_ON_ONCE(!folio_test_locked(folio));
VM_WARN_ON_ONCE(!folio_test_swapcache(folio));
+ /*
+ * A writeback buffer whose IO failed is redirtied and left in the
+ * swap cache, so reclaim writes it out again. Writeback already
+ * decided this data belongs on disk, so send it there instead of
+ * compressing it back into zswap. This also leaves the shadow
+ * parked for the buffer in place, so it is still restored into
+ * the slot once the folio leaves the swap cache.
+ */
+ if (zswap_folio_is_writeback_buffer(folio))
+ return false;
+
if (!zswap_enabled)
goto check_old;
|
| 3/2 | 2026/09/11 11:18 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 9842 output: 26 thoughts: 660 Thoughts: **Analyzing This Patch for Syzkaller Fuzzing Potential** Okay, let's break this down. My initial assessment is that this patch, given its touch points within the core memory management subsystem (mm/), is definitely worth a closer look for fuzzing with syzkaller. We're talking about fundamental changes here, so we need to be thorough. First, I see that the patch touches a bunch of key files: `include/linux/zswap.h`, `mm/internal.h`, `mm/memory.c`, `mm/shmem.c`, `mm/swap.h`, `mm/swap_state.c`, `mm/swapfile.c`, `mm/vmscan.c`, `mm/workingset.c`, and `mm/zswap.c`. A significant overlap in the memory management space means this patch is interesting for fuzzing. My primary focus is on understanding *what* specifically has changed. It looks like we're dealing with modifications to zswap, swap cache behavior, workingset refault tracking, and swap-in logic within `do_swap_page` and `shmem_swapin_folio`, and the writeback in zswap. These are all core functionalities, and any subtle change in these areas could be a potential bug waiting to be found. Now, let's examine the modified logic in detail: The introduced/modified functions, like `zswap_folio_is_writeback_buffer`, `zswap_lookup_and_clear_shadows`, `workingset_refault_lru_managed`, `__swap_cache_alloc_folio`, `__swap_cache_do_del_folio`, `__remove_mapping`, the handling of shadows, writeback changes. This clearly shows substantial modifications in memory management. There's a lot of potential for syzkaller to uncover unexpected states or interactions here. Regarding "rules," I see that this patch is fully reachable in standard QEMU execution. The "WorthFuzzing" flag is set to TRUE, as we're looking at core subsystems with logic modifications in the workingset lifecycle and zswap writeback handling. This confirms my initial assessment. Next step: I'm going to determine what specific Kconfig options are required to enable the relevant functionality, especially those related to ZSWAP. I'll use grepper or file reading to look for ZSWAP and related configs to ensure syzkaller can reach all affected code paths. This will allow me to define the configurations necessary to accurately fuzz the patch. |
| 4/2 | 2026/09/11 11:18 | tool | grepper |
0mArgs:{
"Expression": "config ZSWAP",
"PathPrefix": "mm/"
}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"
} |
| 5/2 | 2026/09/11 11:18 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 11609 output: 30 thoughts: 24 Thoughts: Here's my thought process, as if I were considering this myself: **Investigating ZSWAP Dependencies in the Kernel Configuration** Okay, so I need to quickly refresh my memory on the `ZSWAP` configuration and its dependencies within the kernel build system. The logical place to start is the `mm/Kconfig` file. I'll jump right to around line 20, as that's usually where these memory management-related configurations tend to be grouped. I need to figure out what other kernel features or options *must* be enabled or are required before `ZSWAP` is even presented as a valid configuration option. This will save me time debugging down the line; there's no point checking *why* ZSWAP is disabled without verifying its dependencies. I'm looking for direct dependencies, like `EXPERIMENTAL`, or potentially indirect ones through other feature options that must be active before the `ZSWAP` option is present. I'm expecting this will involve `bool` or `tristate` options and some `depends on` statements. |
| 6/2 | 2026/09/11 11:18 | tool | read-file |
0mArgs:{
"File": "mm/Kconfig",
"FirstLine": 1,
"LineCount": 55
}Results: {
"Contents": " 1:\t# SPDX-License-Identifier: GPL-2.0-only\n 2:\t\n 3:\tmenu \"Memory Management options\"\n 4:\t\n 5:\t#\n 6:\t# For some reason microblaze and nios2 hard code SWAP=n. Hopefully we can\n 7:\t# add proper SWAP support to them, in which case this can be remove.\n 8:\t#\n 9:\tconfig ARCH_NO_SWAP\n 10:\t\tbool\n 11:\t\n 12:\tmenuconfig SWAP\n 13:\t\tbool \"Support for paging of anonymous memory (swap)\"\n 14:\t\tdepends on MMU \u0026\u0026 BLOCK \u0026\u0026 !ARCH_NO_SWAP\n 15:\t\tdefault y\n 16:\t\thelp\n 17:\t\t This option allows you to choose whether you want to have support\n 18:\t\t for so called swap devices or swap files in your kernel that are\n 19:\t\t used to provide more virtual memory than the actual RAM present\n 20:\t\t in your computer. If unsure say Y.\n 21:\t\n 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 32:\t\t in the case where decompressing from RAM is faster than swap device\n 33:\t\t reads, can also improve workload performance.\n 34:\t\n 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 45:\tconfig ZSWAP_SHRINKER_DEFAULT_ON\n 46:\t\tbool \"Shrink the zswap pool on memory pressure\"\n 47:\t\tdepends on ZSWAP\n 48:\t\tdefault n\n 49:\t\thelp\n 50:\t\t If selected, the zswap shrinker will be enabled, and the pages\n 51:\t\t stored in the zswap pool will become available for reclaim (i.e\n 52:\t\t written back to the backing swap device) on memory pressure.\n 53:\t\n 54:\t\t This means that zswap writeback could happen even if the pool is\n 55:\t\t not yet full, or the cgroup zswap limit has not been reached,\n"
} |
| 7/2 | 2026/09/11 11:18 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 12657 output: 27 thoughts: 133 Thoughts: Okay, let's break this down. **Analyzing Swap Configuration for Optimization** Right, so I'm thinking about swap configuration options. Specifically, `CONFIG_SWAP` and `CONFIG_ZSWAP` are the big ones. Naturally, I should check whether `CONFIG_ZSWAP_DEFAULT_ON` or `CONFIG_ZSWAP_SHRINKER_DEFAULT_ON` are relevant flags here, although the core of it will always center around enabling or disabling `ZSWAP` and `SWAP`. I need to figure out the right mix here. Now, I also need to be mindful of other configurations that might influence swap behavior. It's time to consider the memory control groups; does `MEMCG`, `MEMCG_V1`, or `MEMCG_SWAP` have a role in the configuration? I need to think about how these interact. And lastly, I should look directly at the implementation; I need to look at the code. I'll need to check the `mm/swap_state.c` file and potentially other areas for how `memcg1_swapin` is handled. Time to dig in! |
| 8/2 | 2026/09/11 11:18 | tool | grepper |
0mArgs:{
"Expression": "CONFIG_ZSWAP",
"PathPrefix": "mm/"
}Results: {
"Output": "mm/Makefile=79=obj-$(CONFIG_SWAP)\t+= page_io.o swap_state.o swapfile.o\nmm/Makefile:80:obj-$(CONFIG_ZSWAP)\t+= zswap.o\nmm/Makefile-81-obj-$(CONFIG_HAS_DMA)\t+= dmapool.o\n--\nmm/memcontrol.c=599=static const unsigned int memcg_vm_event_stat[] = {\n--\nmm/memcontrol.c-615-#endif\nmm/memcontrol.c:616:#ifdef CONFIG_ZSWAP\nmm/memcontrol.c-617-\tZSWPIN,\n--\nmm/memcontrol.c=1597=static const struct memory_stat memory_stats[] = {\n--\nmm/memcontrol.c-1607-\t{ \"shmem\",\t\t\tNR_SHMEM\t\t\t},\nmm/memcontrol.c:1608:#ifdef CONFIG_ZSWAP\nmm/memcontrol.c-1609-\t{ \"zswap\",\t\t\tMEMCG_ZSWAP_B\t\t\t},\n--\nmm/memcontrol.c=4266=mem_cgroup_css_alloc(struct cgroup_subsys_state *parent_css)\n--\nmm/memcontrol.c-4278-\tpage_counter_set_high(\u0026memcg-\u003ememory, PAGE_COUNTER_MAX);\nmm/memcontrol.c:4279:#ifdef CONFIG_ZSWAP\nmm/memcontrol.c-4280-\tmemcg-\u003ezswap_max = PAGE_COUNTER_MAX;\n--\nmm/memcontrol.c=4468=static void mem_cgroup_css_reset(struct cgroup_subsys_state *css)\n--\nmm/memcontrol.c-4474-\tWRITE_ONCE(memcg-\u003eoom_group, false);\nmm/memcontrol.c:4475:#ifdef CONFIG_ZSWAP\nmm/memcontrol.c-4476-\tWRITE_ONCE(memcg-\u003ezswap_max, PAGE_COUNTER_MAX);\n--\nmm/memcontrol.c=5746=static void __init memcg_struct_check(void)\n--\nmm/memcontrol.c-5799-\t\t\t\t vmstats);\nmm/memcontrol.c:5800:#ifdef CONFIG_ZSWAP\nmm/memcontrol.c-5801-\tCACHELINE_ASSERT_GROUP_MEMBER(struct mem_cgroup, memcg_read_mostly,\n--\nmm/memcontrol.c=6119=static struct cftype swap_files[] = {\n--\nmm/memcontrol.c-6153-\nmm/memcontrol.c:6154:#ifdef CONFIG_ZSWAP\nmm/memcontrol.c-6155-/**\n--\nmm/memcontrol.c=6331=static struct cftype zswap_files[] = {\n--\nmm/memcontrol.c-6349-};\nmm/memcontrol.c:6350:#endif /* CONFIG_ZSWAP */\nmm/memcontrol.c-6351-\nmm/memcontrol.c=6352=static int __init mem_cgroup_swap_init(void)\n--\nmm/memcontrol.c-6360-#endif\nmm/memcontrol.c:6361:#ifdef CONFIG_ZSWAP\nmm/memcontrol.c-6362-\tWARN_ON(cgroup_add_dfl_cftypes(\u0026memory_cgrp_subsys, zswap_files));\n--\nmm/vmstat.c=1199=const char * const vmstat_text[] = {\n--\nmm/vmstat.c-1464-#endif\nmm/vmstat.c:1465:#ifdef CONFIG_ZSWAP\nmm/vmstat.c-1466-\t[I(ZSWPIN)]\t\t\t\t= \"zswpin\",\n--\nmm/zswap.c=86=static int zswap_setup(void);\n--\nmm/zswap.c-88-/* Enable/disable zswap */\nmm/zswap.c:89:static DEFINE_STATIC_KEY_MAYBE(CONFIG_ZSWAP_DEFAULT_ON, zswap_ever_enabled);\nmm/zswap.c:90:static bool zswap_enabled = IS_ENABLED(CONFIG_ZSWAP_DEFAULT_ON);\nmm/zswap.c-91-static int zswap_enabled_param_set(const char *,\n--\nmm/zswap.c=97=module_param_cb(enabled, \u0026zswap_enabled_param_ops, \u0026zswap_enabled, 0644);\n--\nmm/zswap.c-99-/* Crypto compressor to use */\nmm/zswap.c:100:static char *zswap_compressor = CONFIG_ZSWAP_COMPRESSOR_DEFAULT;\nmm/zswap.c-101-static int zswap_compressor_param_set(const char *,\n--\nmm/zswap.c=121=static bool zswap_shrinker_enabled = IS_ENABLED(\nmm/zswap.c:122:\t\tCONFIG_ZSWAP_SHRINKER_DEFAULT_ON);\nmm/zswap.c-123-module_param_named(shrinker_enabled, zswap_shrinker_enabled, bool, 0644);\n--\nmm/zswap.c=130=bool zswap_never_enabled(void)\nmm/zswap.c-131-{\nmm/zswap.c:132:\treturn !static_branch_maybe(CONFIG_ZSWAP_DEFAULT_ON, \u0026zswap_ever_enabled);\nmm/zswap.c-133-}\n--\nmm/zswap.c=344=static struct zswap_pool *__zswap_pool_create_fallback(void)\n--\nmm/zswap.c-346-\tif (!crypto_has_acomp(zswap_compressor, 0, 0) \u0026\u0026\nmm/zswap.c:347:\t strcmp(zswap_compressor, CONFIG_ZSWAP_COMPRESSOR_DEFAULT)) {\nmm/zswap.c-348-\t\tpr_err(\"compressor %s not available, using default %s\\n\",\nmm/zswap.c:349:\t\t zswap_compressor, CONFIG_ZSWAP_COMPRESSOR_DEFAULT);\nmm/zswap.c-350-\t\tparam_free_charp(\u0026zswap_compressor);\nmm/zswap.c:351:\t\tzswap_compressor = CONFIG_ZSWAP_COMPRESSOR_DEFAULT;\nmm/zswap.c-352-\t}\n"
} |
| 9/2 | 2026/09/11 11:18 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 14708 output: 102 thoughts: 1085 Thoughts: **Analyzing the Patch's Focus Symbols** Okay, let's break down this patch and pinpoint the key functions we need to focus on for fuzzing and testing. First, I need to identify the functions that are either new or modified. I've got a list here: 1. `zswap_folio_is_writeback_buffer`: A new function within `mm/zswap.c` that checks if a folio is a zswap writeback buffer. Sounds specific to zswap logic. 2. `zswap_lookup_and_clear_shadows`: Another new zswap function in `mm/zswap.c`, handling the retrieval and clearing of parked shadows. Definitely zswap-specific. 3. `zswap_writeback_entry`: A *modified* static function. I'll need to examine how it's changed, but the zswap context is clear. The key is in the parking of the shadows and error handling. 4. `zswap_store`: This function in `mm/zswap.c` has been modified to include a check using `zswap_folio_is_writeback_buffer`, suggesting a change in its core logic concerning zswap's writeback behavior. 5. `workingset_refault_lru_managed`: A new function within `mm/workingset.c`. This is specifically dealing with refaulting of folios already managed by the LRU. This is interesting, as I know this logic. 6. `workingset_refault`: Modified in `mm/workingset.c`, but it delegates to a new function. 7. `__swap_cache_alloc_folio`: This one has a modified signature, specifically in `mm/swap_state.c`, which, given my understanding of the swap cache, is going to be important for how folios get allocated. 8. `swapin_sync`: This function looks pertinent to the overall feature logic. I need to be careful with `do_swap_page`. It's a page fault handler for swap entries, and while it's modified, it's a *very* generic function. We need to avoid listing ubiquitous functions that are called by nearly every program. Listing it would dilute the fuzzing effort. Let's make sure that these other functions (`zswap_folio_is_writeback_buffer`, `zswap_lookup_and_clear_shadows`, `zswap_writeback_entry`, `zswap_store`, `workingset_refault_lru_managed`, `__swap_cache_alloc_folio`) are better choices as focus symbols. They appear more specific to the altered feature logic. So, let's solidify the candidate focus symbols. I think the key ones are: * `zswap_folio_is_writeback_buffer` * `zswap_lookup_and_clear_shadows` * `zswap_writeback_entry` * `zswap_store` * `workingset_refault_lru_managed` * `__swap_cache_alloc_folio` * `swapin_sync` * `shmem_swapin_folio` Now, let's use `grepper` (or some other tool) to ensure these functions are actually defined in the kernel and aren't just static-inlined, so they are suitable symbols for focused fuzzing and testing. I will check the definitions of each symbol now. |
| 10/2 | 2026/09/11 11:18 | tool | grepper |
0mArgs:{
"Expression": "^(bool|void|int|struct folio \\*) ?\\*?(zswap_folio_is_writeback_buffer|zswap_lookup_and_clear_shadows|workingset_refault_lru_managed|zswap_writeback_entry|zswap_store|__swap_cache_alloc_folio|shmem_swapin_folio)",
"PathPrefix": "mm/"
}Results: {
"Output": "mm/internal.h=33=void workingset_refault(struct folio *folio, void *shadow);\nmm/internal.h:34:void workingset_refault_lru_managed(struct folio *folio, void *shadow);\nmm/internal.h-35-void workingset_activation(struct folio *folio);\n--\nmm/swap.h=314=void swap_cache_del_folio(struct folio *folio);\nmm/swap.h:315:struct folio *__swap_cache_alloc_folio(swp_entry_t target_entry, gfp_t gfp_mask,\nmm/swap.h-316-\t\t\t\t unsigned long orders, struct vm_fault *vmf,\n--\nmm/swap_state.c=419=static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,\n--\nmm/swap_state.c-528- */\nmm/swap_state.c:529:struct folio *__swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,\nmm/swap_state.c-530-\t\t\t\t unsigned long orders, struct vm_fault *vmf,\n--\nmm/workingset.c=606=void workingset_refault(struct folio *folio, void *shadow)\n--\nmm/workingset.c-625- */\nmm/workingset.c:626:void workingset_refault_lru_managed(struct folio *folio, void *shadow)\nmm/workingset.c-627-{\n--\nmm/zswap.c=913=static bool zswap_decompress(struct zswap_entry *entry, struct folio *folio)\n--\nmm/zswap.c-977- */\nmm/zswap.c:978:bool zswap_folio_is_writeback_buffer(struct folio *folio)\nmm/zswap.c-979-{\n--\nmm/zswap.c-1010- */\nmm/zswap.c:1011:void *zswap_lookup_and_clear_shadows(struct folio *folio)\nmm/zswap.c-1012-{\n--\nmm/zswap.c=1486=static bool zswap_store_page(struct folio *folio, long index,\n--\nmm/zswap.c-1565-\nmm/zswap.c:1566:bool zswap_store(struct folio *folio)\nmm/zswap.c-1567-{\n"
} |
| 11/2 | 2026/09/11 11:18 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 17296 output: 29 thoughts: 41 Thoughts: Okay, let's dive into this. Here's my thought process on the matter: **Analyzing Zswap and Shared Memory Swap Operations** Alright, time to get my hands dirty. I'm going to start by taking a close look at the kernel code related to zswap and shared memory swap operations. Specifically, I need to check out the implementations of `zswap_writeback_entry` and `shmem_swapin_folio`. These functions reside in `mm/zswap.c` and `mm/shmem.c` respectively. I'll need to understand how zswap handles the writing back of compressed data and how shared memory pages are swapped in. This will likely involve tracing their interaction with the page cache, swap cache, and the underlying block devices. I want to pay attention to any potential bottlenecks or areas where optimization might be possible, and I might need to consider how these functions work under different memory pressure scenarios. |
| 12/2 | 2026/09/11 11:19 | tool | grepper |
0mArgs:{
"Expression": "zswap_writeback_entry",
"PathPrefix": "mm/"
}Results: {
"Output": "mm/zswap.c=1011=void *zswap_lookup_and_clear_shadows(struct folio *folio)\n--\nmm/zswap.c-1048- */\nmm/zswap.c:1049:static int zswap_writeback_entry(struct zswap_entry *entry,\nmm/zswap.c-1050-\t\t\t\t swp_entry_t swpentry)\n--\nmm/zswap.c=1168=static enum lru_status shrink_memcg_cb(struct list_head *item, struct list_lru_one *l,\n--\nmm/zswap.c-1191-\t * 1. We extract the swp_entry_t to the stack, allowing\nmm/zswap.c:1192:\t * zswap_writeback_entry() to pin the swap entry and\nmm/zswap.c-1193-\t * then validate the zswap entry against that swap entry's\n--\nmm/zswap.c-1226-\nmm/zswap.c:1227:\twriteback_result = zswap_writeback_entry(entry, swpentry);\nmm/zswap.c-1228-\n"
} |
| 13/2 | 2026/09/11 11:19 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 17876 output: 29 thoughts: 13 Thoughts: Here's a summary of my thought process, as requested: **Diving into `shmem_swapin_folio`** Okay, time to dissect `shmem_swapin_folio`. This function is likely crucial for the Shared Memory (SHMEM) subsystem. I'm guessing it handles the process of bringing a folio (a page or group of pages, likely) back into physical memory from swap. Considering its name, "swapin" is a pretty good clue, as is "folio". I immediately want to understand the *why* and the *how*. Why is this folio being swapped in? Is it due to a page fault, a process resuming after being swapped out, or something else entirely? The context of its invocation is going to be incredibly important. I need to trace back and see the calling paths. And the "how" involves a series of questions: What resources are being manipulated? Is it locking critical sections? How is the folio's state managed during the swap-in process? What about error handling? Are there any potential race conditions I need to keep an eye out for? I suspect it may be interacting with the page cache, the swap cache, and the physical memory management structures. I anticipate a lot of pointer chasing and state transitions. I'm going to need to look for any calls to functions like `swap_readpage()`, `folio_migrate()`, `folio_putback()`, and anything related to memory reclaim to get the bigger picture. I'll need to pay close attention to the memory barriers used to ensure proper ordering, especially if we're dealing with multiple CPUs accessing the same memory region. Let's see how much of this plays out in the code! |
| 14/2 | 2026/09/11 11:19 | tool | grepper |
0mArgs:{
"Expression": "shmem_swapin_folio",
"PathPrefix": "mm/"
}Results: {
"Output": "mm/shmem.c=152=static unsigned long shmem_default_max_inodes(void)\n--\nmm/shmem.c-160-\nmm/shmem.c:161:static int shmem_swapin_folio(struct inode *inode, pgoff_t index,\nmm/shmem.c-162-\t\t\tstruct folio **foliop, enum sgp_type sgp, gfp_t gfp,\n--\nmm/shmem.c=1695=static int shmem_unuse_swap_entries(struct inode *inode,\n--\nmm/shmem.c-1705-\nmm/shmem.c:1706:\t\terror = shmem_swapin_folio(inode, indices[i], \u0026folio, SGP_CACHE,\nmm/shmem.c-1707-\t\t\t\t\tmapping_gfp_mask(mapping), NULL, NULL);\n--\nmm/shmem.c=2375=static int shmem_split_large_entry(struct inode *inode, pgoff_t index,\n--\nmm/shmem.c-2452- */\nmm/shmem.c:2453:static int shmem_swapin_folio(struct inode *inode, pgoff_t index,\nmm/shmem.c-2454-\t\t\t struct folio **foliop, enum sgp_type sgp,\n--\nmm/shmem.c=2632=static int shmem_get_folio_gfp(struct inode *inode, pgoff_t index,\n--\nmm/shmem.c-2664-\tif (xa_is_value(folio)) {\nmm/shmem.c:2665:\t\terror = shmem_swapin_folio(inode, index, \u0026folio,\nmm/shmem.c-2666-\t\t\t\t\t sgp, gfp, vmf, fault_type);\n"
} |
| 15/2 | 2026/09/11 11:19 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 18752 output: 26 thoughts: 21 Thoughts: Here's a summary of my immediate thought process, focusing on `swapin_sync` and `__swap_cache_alloc_folio`: **Initial Dive into Swap Operations** Alright, I'm thinking, let's drill down into the core swap mechanism. Specifically, I want to understand the interaction between `swapin_sync` and `__swap_cache_alloc_folio`. These two functions are likely fundamental to the process of bringing data back into RAM from the swap partition. My initial hypothesis is that `__swap_cache_alloc_folio` likely handles the allocation of folio structures and probably interacts with the swap cache. I'm guessing it might involve things like tracking page metadata, and managing the mapping of swap entries to physical memory. `swapin_sync` on the other hand, should be responsible for synchronizing the data being read in from swap. I will probably find it handling things like locking, reading from the disk, and updating page tables. I'll bet I'll find a race condition or performance bottleneck with `swapin_sync` if there is contention accessing the swap partition. |
| 16/2 | 2026/09/11 11:19 | tool | grepper |
0mArgs:{
"Expression": "swapin_sync",
"PathPrefix": "mm/"
}Results: {
"Output": "mm/memory.c=4844=vm_fault_t do_swap_page(struct vm_fault *vmf)\n--\nmm/memory.c-4936-\t\tif (data_race(si-\u003eflags \u0026 SWP_SYNCHRONOUS_IO))\nmm/memory.c:4937:\t\t\tfolio = swapin_sync(entry, GFP_HIGHUSER_MOVABLE,\nmm/memory.c-4938-\t\t\t\t\t thp_swapin_suitable_orders(vmf) | BIT(0),\n--\nmm/shmem.c=2242=static struct folio *shmem_swap_alloc_folio(struct inode *inode,\n--\nmm/shmem.c-2256-\tmpol = shmem_get_pgoff_policy(info, index, order, \u0026ilx);\nmm/shmem.c:2257:\tfolio = swapin_sync(entry, gfp, BIT(order), vmf, mpol, ilx);\nmm/shmem.c-2258-\tmpol_cond_put(mpol);\n--\nmm/swap.h=332=struct folio *swapin_readahead(swp_entry_t entry, gfp_t flag,\nmm/swap.h-333-\t\tstruct vm_fault *vmf);\nmm/swap.h:334:struct folio *swapin_sync(swp_entry_t entry, gfp_t flag, unsigned long orders,\nmm/swap.h-335-\t\t\t struct vm_fault *vmf, struct mempolicy *mpol, pgoff_t ilx);\n--\nmm/swap.h=408=static inline struct folio *swapin_readahead(swp_entry_t swp, gfp_t gfp_mask,\n--\nmm/swap.h-413-\nmm/swap.h:414:static inline struct folio *swapin_sync(\nmm/swap.h-415-\tswp_entry_t entry, gfp_t flag, unsigned long orders,\n--\nmm/swap_state.c=658=static struct folio *swap_cache_read_folio(struct swap_io_ctx *ctx,\n--\nmm/swap_state.c-689-/**\nmm/swap_state.c:690: * swapin_sync - swap-in one or multiple entries skipping readahead.\nmm/swap_state.c-691- * @entry: swap entry indicating the target slot\n--\nmm/swap_state.c-704- */\nmm/swap_state.c:705:struct folio *swapin_sync(swp_entry_t entry, gfp_t gfp, unsigned long orders,\nmm/swap_state.c-706-\t\t\t struct vm_fault *vmf, struct mempolicy *mpol, pgoff_t ilx)\n"
} |
| 17/2 | 2026/09/11 11:19 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 19745 output: 210 thoughts: 394 Thoughts: **My Assessment of the Patch's Fuzzing Suitability** Okay, let's nail down the fuzzing targets for this patch. I've pinpointed a solid set of focus symbols: `zswap_folio_is_writeback_buffer`, `zswap_lookup_and_clear_shadows`, `zswap_writeback_entry`, `zswap_store`, `workingset_refault_lru_managed`, and `__swap_cache_alloc_folio`. I also need to keep `swapin_sync` and `workingset_refault` in mind, although `workingset_refault_lru_managed` seems to be the more specific function here. The configurations I'm working with are `SWAP` and `ZSWAP`. Looking at the code, it's clear this patch is digging deep into memory management, particularly the workingset refault tracking and how zswap writeback and swap-in operations handle these shadow pages. I see the introduction of new helper functions, which is always a good flag, and the changes to swap cache allocation and writeback paths are particularly interesting. This touches core mm, which means the potential for memory corruption, races, leaks, or even assertion failures is there. The changes are reachable and warrant intensive fuzzing. Seems like the `WorthFuzzing` flag is set correctly. Let's get the tool call going. |
| 18/2 | 2026/09/11 11:19 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"SWAP",
"ZSWAP"
],
"FocusSymbols": [
"zswap_folio_is_writeback_buffer",
"zswap_lookup_and_clear_shadows",
"zswap_writeback_entry",
"zswap_store",
"workingset_refault_lru_managed",
"__swap_cache_alloc_folio"
],
"Reasoning": "The patch modifies core memory management and zswap subsystem logic, specifically refault tracking and shadow entry preservation across zswap writeback and swap-in paths. It introduces new helpers (zswap_folio_is_writeback_buffer, zswap_lookup_and_clear_shadows, workingset_refault_lru_managed) and modifies swap cache allocation and writeback handling. These changes are reachable and warrant fuzzing to detect regressions, memory leaks, or race conditions.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"SWAP",
"ZSWAP"
],
"FocusSymbols": [
"zswap_folio_is_writeback_buffer",
"zswap_lookup_and_clear_shadows",
"zswap_writeback_entry",
"zswap_store",
"workingset_refault_lru_managed",
"__swap_cache_alloc_folio"
],
"Reasoning": "The patch modifies core memory management and zswap subsystem logic, specifically refault tracking and shadow entry preservation across zswap writeback and swap-in paths. It introduces new helpers (zswap_folio_is_writeback_buffer, zswap_lookup_and_clear_shadows, workingset_refault_lru_managed) and modifies swap cache allocation and writeback handling. These changes are reachable and warrant fuzzing to detect regressions, memory leaks, or race conditions.",
"WorthFuzzing": true
} |
| 19/1 | 2026/09/11 11:19 | action | If |
0mArgs:{
"WorthFuzzing": true
}Results: null |
| 20/2 | 2026/09/11 11:19 | agent | kmsan-evaluator |
0mModel:core Args: null Results: {
"KMSANReasoning": "The patch series modifies core memory management code in mm/ (swap cache, zswap writeback, and workingset refault tracking for LRU-managed folios). It adjusts how shadow entries are parked in the zswap tree during writeback and restored/refaulted during swapin.\n\n1. No kernel-to-user info-leak risk: No data is exposed or copied to user space (no copy_to_user, netlink, ioctls, or BPF buffers).\n2. No uninitialized memory usage: All newly introduced or modified local variables (such as `shadow`, `shadow_parked`, and loop indices) are explicitly initialized before use or assigned by functions that return pointers/values.\n3. Bug applicability: Potential risks introduced by these changes involve page locking, race conditions in swapcache/LRU manipulation, refcounting, or invalid xarray operations. These are detectable by standard debugging tools (KASAN, LOCKDEP, VM_WARN/VM_BUG assertions), not KMSAN.\n\nTherefore, a dedicated KMSAN fuzzing session is not warranted.",
"NeedsKMSAN": false
}Instruction: You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.
CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
a comprehensive suite of debugging tools and sanitizers, including KASAN
(out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
(locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
or page allocations) and kernel-to-user memory info-leaks.
Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.
Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.
Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
attributes, ioctl output arguments, socket options, or BPF buffers) where fields
or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
uninitialized bytes of existing buffers.
Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
dereferences, locking deadlocks, or use-after-free bugs (these are already caught
by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.
Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit 605ec6b889c9da09a9bd88724ff9c3217d185b8e
Author: syz-cluster <triage@syzkaller.com>
Date: Fri Sep 11 11:18:34 2026 +0000
syz-cluster: applied patch under review
diff --git a/include/linux/zswap.h b/include/linux/zswap.h
index 30c193a1207e1..dfb7153236f5c 100644
--- a/include/linux/zswap.h
+++ b/include/linux/zswap.h
@@ -35,6 +35,8 @@ void zswap_lruvec_state_init(struct lruvec *lruvec);
void zswap_folio_swapin(struct folio *folio);
bool zswap_is_enabled(void);
bool zswap_never_enabled(void);
+bool zswap_folio_is_writeback_buffer(struct folio *folio);
+void *zswap_lookup_and_clear_shadows(struct folio *folio);
#else
struct zswap_lruvec_state {};
@@ -69,6 +71,16 @@ static inline bool zswap_never_enabled(void)
return true;
}
+static inline bool zswap_folio_is_writeback_buffer(struct folio *folio)
+{
+ return false;
+}
+
+static inline void *zswap_lookup_and_clear_shadows(struct folio *folio)
+{
+ return NULL;
+}
+
#endif
#endif /* _LINUX_ZSWAP_H */
diff --git a/mm/internal.h b/mm/internal.h
index 8be3d28d9c3d8..d19947a3c3385 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -31,6 +31,7 @@ void workingset_age_nonresident(struct lruvec *lruvec, unsigned long nr_pages);
void *workingset_eviction(struct folio *folio,
struct mem_cgroup *target_memcg);
void workingset_refault(struct folio *folio, void *shadow);
+void workingset_refault_lru_managed(struct folio *folio, void *shadow);
void workingset_activation(struct folio *folio);
/* mm/folio.c */
diff --git a/mm/memory.c b/mm/memory.c
index a2a63ae0967c1..0043a9237a8dd 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -4857,6 +4857,7 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
unsigned long page_idx;
unsigned long address;
pte_t *ptep;
+ void *shadow;
if (!pte_unmap_same(vmf))
goto out;
@@ -4984,6 +4985,10 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
goto out_page;
}
+ shadow = zswap_lookup_and_clear_shadows(folio);
+ if (shadow)
+ workingset_refault_lru_managed(folio, shadow);
+
/*
* KSM sometimes has to copy on read faults, for example, if
* folio->index of non-ksm folios would be nonlinear inside the
diff --git a/mm/shmem.c b/mm/shmem.c
index c3212fdece9b4..53f57ad194a39 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -2465,6 +2465,7 @@ static int shmem_swapin_folio(struct inode *inode, pgoff_t index,
struct folio *folio = NULL;
int error, nr_pages, order;
pgoff_t offset;
+ void *shadow;
VM_BUG_ON(!*foliop || !xa_is_value(*foliop));
index_entry = radix_to_swp_entry(*foliop);
@@ -2576,6 +2577,10 @@ static int shmem_swapin_folio(struct inode *inode, pgoff_t index,
*/
arch_swap_restore(folio_swap(swap, folio), folio);
+ shadow = zswap_lookup_and_clear_shadows(folio);
+ if (shadow)
+ workingset_refault_lru_managed(folio, shadow);
+
if (shmem_should_replace_folio(folio, gfp)) {
error = shmem_replace_folio(&folio, gfp, info, index, vma);
if (error)
diff --git a/mm/swap.h b/mm/swap.h
index 0b5d507739bcb..ddd108b41d33a 100644
--- a/mm/swap.h
+++ b/mm/swap.h
@@ -312,9 +312,10 @@ 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,
+ void **shadowp);
/* 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 305877e1f4d7b..e80a694dddb47 100644
--- a/mm/swap_state.c
+++ b/mm/swap_state.c
@@ -24,6 +24,7 @@
#include <linux/shmem_fs.h>
#include <linux/sysctl.h>
#include <linux/swap_ops.h>
+#include <linux/zswap.h>
#include "internal.h"
#include "swap_table.h"
#include "swap.h"
@@ -263,12 +264,21 @@ static void __swap_cache_do_del_folio(struct swap_cluster_info *ci,
unsigned int ci_start, ci_off, ci_end;
bool folio_swapped = false, need_free = false;
unsigned long nr_pages = folio_nr_pages(folio);
+ void *shadow_parked;
VM_WARN_ON_ONCE(__swap_entry_to_cluster(entry) != ci);
VM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);
VM_WARN_ON_ONCE_FOLIO(!folio_test_swapcache(folio), folio);
VM_WARN_ON_ONCE_FOLIO(folio_test_writeback(folio), folio);
+ /*
+ * A zswap writeback buffer parked the slot's original shadow in the
+ * zswap tree: restore it into the slot for later swap-in.
+ */
+ shadow_parked = zswap_lookup_and_clear_shadows(folio);
+ if (shadow_parked)
+ shadow = shadow_parked;
+
si = __swap_entry_to_info(entry);
ci_start = swp_cluster_offset(entry);
ci_end = ci_start + nr_pages;
@@ -409,7 +419,8 @@ void __swap_cache_replace_folio(struct swap_cluster_info *ci,
static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,
swp_entry_t targ_entry, gfp_t gfp,
unsigned int order, struct vm_fault *vmf,
- struct mempolicy *mpol, pgoff_t ilx)
+ struct mempolicy *mpol, pgoff_t ilx,
+ void **shadowp)
{
int err;
swp_entry_t entry;
@@ -483,37 +494,42 @@ static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,
/* memsw uncharges swap when folio is added to swap cache */
memcg1_swapin(folio);
- if (shadow)
- workingset_refault(folio, shadow);
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);
+ if (shadowp)
+ *shadowp = shadow;
+
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
* @vmf: fault information
* @mpol: NUMA memory allocation policy to be applied
* @ilx: NUMA interleave index, for use only when MPOL_INTERLEAVE
+ * @shadowp: Returns the shadow the allocation displaced, NULL to ignore
*
* Allocate a folio in the swap cache for one swap slot, typically before
* 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,
+ void **shadowp)
{
int order, err;
struct folio *ret;
@@ -528,7 +544,7 @@ struct folio *swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,
do {
ret = __swap_cache_alloc(ci, targ_entry, gfp, order,
- vmf, mpol, ilx);
+ vmf, mpol, ilx, shadowp);
if (!IS_ERR(ret))
break;
err = PTR_ERR(ret);
@@ -644,17 +660,23 @@ static struct folio *swap_cache_read_folio(struct swap_io_ctx *ctx,
pgoff_t ilx, bool readahead)
{
struct folio *folio;
+ void *shadow = NULL;
do {
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, &shadow);
} while (PTR_ERR(folio) == -EEXIST);
if (IS_ERR_OR_NULL(folio))
return NULL;
+ if (shadow)
+ workingset_refault(folio, shadow);
+
+ folio_add_lru(folio);
swap_read_folio(ctx, folio);
if (readahead) {
folio_set_readahead(folio);
@@ -685,17 +707,23 @@ struct folio *swapin_sync(swp_entry_t entry, gfp_t gfp, unsigned long orders,
{
struct swap_io_ctx ctx = {};
struct folio *folio;
+ void *shadow = NULL;
do {
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, &shadow);
} while (PTR_ERR(folio) == -EEXIST);
if (IS_ERR(folio))
return folio;
+ if (shadow)
+ workingset_refault(folio, shadow);
+
+ folio_add_lru(folio);
swap_read_folio(&ctx, folio);
swap_read_submit(&ctx);
return folio;
diff --git a/mm/swapfile.c b/mm/swapfile.c
index 48d3cd40defdb..6101f90e91d2a 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -1885,7 +1885,7 @@ void folio_put_swap(struct folio *folio, struct page *page)
* CPU1 CPU2
* do_swap_page()
* ... swapoff+swapon
- * swap_cache_alloc_folio()
+ * __swap_cache_alloc_folio()
* // check swap_map
* // verify PTE not changed
*
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 40d3f1b48a74c..1ca6489e50122 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -48,6 +48,7 @@
#include <linux/prefetch.h>
#include <linux/printk.h>
#include <linux/dax.h>
+#include <linux/zswap.h>
#include <linux/psi.h>
#include <linux/pagewalk.h>
#include <linux/shmem_fs.h>
@@ -753,7 +754,8 @@ static int __remove_mapping(struct address_space *mapping, struct folio *folio,
if (folio_test_swapcache(folio)) {
swp_entry_t swap = folio->swap;
- if (reclaimed && !mapping_exiting(mapping))
+ if (reclaimed && !mapping_exiting(mapping) &&
+ !zswap_folio_is_writeback_buffer(folio))
shadow = workingset_eviction(folio, target_memcg);
__memcg1_swapout(folio, ci);
__swap_cache_del_folio(ci, folio, swap, shadow);
diff --git a/mm/workingset.c b/mm/workingset.c
index 8412f4840ae35..8e45b0baeb36d 100644
--- a/mm/workingset.c
+++ b/mm/workingset.c
@@ -536,16 +536,8 @@ bool workingset_test_recent(void *shadow, bool file, bool *workingset,
return refault_distance <= workingset_size;
}
-/**
- * workingset_refault - Evaluate the refault of a previously evicted folio.
- * @folio: The freshly allocated replacement folio.
- * @shadow: Shadow entry of the evicted folio.
- *
- * Calculates and evaluates the refault distance of the previously
- * evicted folio in the context of the node and the memcg whose memory
- * pressure caused the eviction.
- */
-void workingset_refault(struct folio *folio, void *shadow)
+static void __workingset_refault(struct folio *folio, void *shadow,
+ bool lru_managed)
{
bool file = folio_is_file_lru(folio);
struct mem_cgroup *memcg;
@@ -577,7 +569,16 @@ void workingset_refault(struct folio *folio, void *shadow)
if (!workingset_test_recent(shadow, file, &workingset, true))
goto out;
- folio_set_active(folio);
+ /*
+ * An LRU-managed folio may sit in a per-CPU batch, which cannot be
+ * determined here: setting the flag would race the drain and leave it
+ * disagreeing with the list. folio_activate() is safe, but misses the
+ * activation for such a folio.
+ */
+ if (lru_managed)
+ folio_activate(folio);
+ else
+ folio_set_active(folio);
workingset_age_nonresident(lruvec, nr);
mod_lruvec_state(lruvec, WORKINGSET_ACTIVATE_BASE + file, nr);
@@ -590,6 +591,43 @@ void workingset_refault(struct folio *folio, void *shadow)
mem_cgroup_put(memcg);
}
+/**
+ * workingset_refault - Evaluate the refault of a previously evicted folio.
+ * @folio: The freshly allocated replacement folio.
+ * @shadow: Shadow entry of the evicted folio.
+ *
+ * Calculates and evaluates the refault distance of the previously
+ * evicted folio in the context of the node and the memcg whose memory
+ * pressure caused the eviction.
+ *
+ * Context: @folio must be locked and not on the LRU yet, the caller adds
+ * it with folio_add_lru() afterwards.
+ */
+void workingset_refault(struct folio *folio, void *shadow)
+{
+ __workingset_refault(folio, shadow, false);
+}
+
+/**
+ * workingset_refault_lru_managed - Evaluate the refault of a previously
+ * evicted folio that is already on the LRU.
+ * @folio: The folio the eviction is refaulted into.
+ * @shadow: Shadow entry of the evicted folio.
+ *
+ * Like workingset_refault(), but for a folio the caller has already added
+ * to the LRU, which has to be activated by moving it between the lists
+ * rather than by setting PG_active.
+ *
+ * The activation is best effort: a folio still sitting in a per-CPU LRU
+ * batch cannot be moved yet and is left inactive.
+ *
+ * Context: @folio must be locked and already added to the LRU.
+ */
+void workingset_refault_lru_managed(struct folio *folio, void *shadow)
+{
+ __workingset_refault(folio, shadow, true);
+}
+
/**
* workingset_activation - note a page activation
* @folio: Folio that is being activated.
diff --git a/mm/zswap.c b/mm/zswap.c
index c27e22d067e3e..904821db401f3 100644
--- a/mm/zswap.c
+++ b/mm/zswap.c
@@ -961,6 +961,79 @@ static bool zswap_decompress(struct zswap_entry *entry, struct folio *folio)
/*********************************
* writeback code
**********************************/
+
+#define ZSWAP_WRITEBACK_NO_SHADOW xa_mk_value(0)
+
+/**
+ * zswap_folio_is_writeback_buffer - is @folio a zswap writeback buffer?
+ * @folio: the folio being examined (typically a swap cache folio under reclaim)
+ *
+ * A folio is a zswap writeback buffer when every one of its swap offsets holds
+ * a parked writeback shadow (a real shadow or the ZSWAP_WRITEBACK_NO_SHADOW
+ * sentinel) in the zswap tree rather than a live zswap entry.
+ *
+ * Return: true if @folio is a writeback buffer, in which case the reclaim path
+ * must not mint a fresh workingset shadow for it.
+ */
+bool zswap_folio_is_writeback_buffer(struct folio *folio)
+{
+ swp_entry_t swp = folio->swap;
+ unsigned long nr_pages = folio_nr_pages(folio);
+ pgoff_t offset = swp_offset(swp);
+ unsigned long i;
+
+ if (zswap_never_enabled())
+ return false;
+
+ for (i = 0; i < nr_pages; i++) {
+ swp_entry_t e = swp_entry(swp_type(swp), offset + i);
+
+ if (!xa_is_value(xa_load(swap_zswap_tree(e), offset + i)))
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * zswap_lookup_and_clear_shadows - retrieve and clear @folio's parked shadow(s)
+ * @folio: the writeback buffer folio (or the swapin folio that consumed it)
+ *
+ * Remove any parked writeback shadows for @folio's swap offset(s) from the
+ * zswap tree.
+ *
+ * Return: the preserved workingset shadow, or NULL if the slot(s) had no shadow
+ * (sentinel only) or nothing parked. The caller either restores the returned
+ * shadow into the swap slot (buffer dropped) or feeds it to
+ * workingset_refault_lru_managed() (buffer consumed by a swapin); clearing
+ * here ensures the two paths never double-count.
+ */
+void *zswap_lookup_and_clear_shadows(struct folio *folio)
+{
+ swp_entry_t swp = folio->swap;
+ unsigned long nr_pages = folio_nr_pages(folio);
+ pgoff_t offset = swp_offset(swp);
+ void *shadow = NULL;
+ unsigned long i;
+
+ if (zswap_never_enabled())
+ return NULL;
+
+ for (i = 0; i < nr_pages; i++) {
+ swp_entry_t e = swp_entry(swp_type(swp), offset + i);
+ struct xarray *tree = swap_zswap_tree(e);
+ void *parked = xa_load(tree, offset + i);
+
+ if (!xa_is_value(parked))
+ continue;
+ xa_erase(tree, offset + i);
+ if (parked != ZSWAP_WRITEBACK_NO_SHADOW)
+ shadow = parked;
+ }
+
+ return shadow;
+}
+
/*
* Attempts to free an entry by adding a folio to the swap cache,
* decompressing the entry data into the folio, and issuing a
@@ -976,12 +1049,14 @@ static bool zswap_decompress(struct zswap_entry *entry, struct folio *folio)
static int zswap_writeback_entry(struct zswap_entry *entry,
swp_entry_t swpentry)
{
+ struct swap_cluster_info *ci;
struct xarray *tree;
pgoff_t offset = swp_offset(swpentry);
struct folio *folio;
struct mempolicy *mpol;
struct swap_info_struct *si;
struct swap_io_ctx ctx = {};
+ void *shadow;
int ret = 0;
/* try to allocate swap cache folio */
@@ -990,8 +1065,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, &shadow);
put_swap_device(si);
/*
@@ -1003,6 +1078,7 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
*/
if (IS_ERR(folio))
return PTR_ERR(folio);
+ folio_add_lru(folio);
/*
* folio is locked, and the swapcache is now secured against
@@ -1024,7 +1100,12 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
goto out;
}
- xa_erase(tree, offset);
+ /*
+ * A slot with no shadow already reads back as ZSWAP_WRITEBACK_NO_SHADOW:
+ * the swap table marks a swapped out slot with xa_mk_value(0) and holds
+ * the shadow, when there is one, in that same value.
+ */
+ xa_store(tree, offset, shadow, GFP_KERNEL);
count_vm_event(ZSWPWB);
if (entry->objcg)
@@ -1044,7 +1125,15 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
out:
if (ret) {
- swap_cache_del_folio(folio);
+ /*
+ * The allocation above overwrote the slot's shadow and nothing
+ * has been parked for it yet, so restore it as the folio leaves
+ * the swap cache, or the eviction it records is lost.
+ */
+ ci = swap_cluster_lock(__swap_entry_to_info(swpentry), offset);
+ __swap_cache_del_folio(ci, folio, swpentry, shadow);
+ swap_cluster_unlock(ci);
+ folio_ref_sub(folio, folio_nr_pages(folio));
folio_unlock(folio);
}
folio_put(folio);
@@ -1487,6 +1576,17 @@ bool zswap_store(struct folio *folio)
VM_WARN_ON_ONCE(!folio_test_locked(folio));
VM_WARN_ON_ONCE(!folio_test_swapcache(folio));
+ /*
+ * A writeback buffer whose IO failed is redirtied and left in the
+ * swap cache, so reclaim writes it out again. Writeback already
+ * decided this data belongs on disk, so send it there instead of
+ * compressing it back into zswap. This also leaves the shadow
+ * parked for the buffer in place, so it is still restored into
+ * the slot once the folio leaves the swap cache.
+ */
+ if (zswap_folio_is_writeback_buffer(folio))
+ return false;
+
if (!zswap_enabled)
goto check_old;
|
| 21/3 | 2026/09/11 11:19 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 9054 output: 35 thoughts: 838 Thoughts: **Kernel Memory Safety Evaluation: Zswap Shadow Entry Handling** Okay, let's break this down. My goal is to assess whether this patch series introduces or highlights any uninitialized memory vulnerabilities that warrant a dedicated KMSAN fuzzing session. It's a kernel patch touching several memory management areas. First, I see the affected files: `include/linux/zswap.h`, `mm/internal.h`, `mm/memory.c`, `mm/shmem.c`, `mm/swap.h`, `mm/swap_state.c`, `mm/swapfile.c`, `mm/vmscan.c`, `mm/workingset.c`, and `mm/zswap.c`. These all point towards workingset shadow entries and zswap writeback. The core of the patch seems to be about how the workingset shadow entries are handled when folios are swapped in and out via zswap writeback. Specifically, `__swap_cache_alloc_folio` now takes a `void **shadowp` parameter. Instead of immediately calling `workingset_refault` in `__swap_cache_alloc`, it's now returning the `shadow` value via `shadowp` (if it's not NULL). Callers like `swap_cache_read_folio` and `swapin_sync` then handle the `workingset_refault` call *after* getting the `shadow` value back. And, `do_swap_page` and `shmem_swapin_folio` are using `zswap_lookup_and_clear_shadows`. I see similar logic in `zswap_writeback_entry`, with the shadow being retrieved via `__swap_cache_alloc_folio` and stored in an `xa_store` call. If there's an error, it's being restored in `__swap_cache_del_folio`. My next step is to see if any potential for uninitialized memory reads exists. I'll focus on the callsites where `shadow` is being used to see how it is initially assigned, and if there are any paths where it could be read before being assigned a valid value. Let's look at `swap_cache_read_folio`: The `shadow` variable is initialized to `NULL`. The code calls `__swap_cache_alloc_folio`. If that succeeds, the allocated shadow value is returned and the caller will run `workingset_refault(folio, shadow)`. The `__swap_cache_alloc` assigns `shadow` from either a value from the `swap_cache`, or from what was just allocated. Let's look at that function directly to confirm the assignment and see what is actually happening. I will use the `read-file` or `grepper` command to investigate the relevant lines in `mm/swap_state.c` around lines 409-500. Then I'll re-evaluate the risk of uninitialized variable usage. |
| 22/3 | 2026/09/11 11:19 | tool | read-file |
0mArgs:{
"File": "mm/swap_state.c",
"FirstLine": 460,
"LineCount": 60
}Results: {
"Contents": " 460:\t\tspin_lock(\u0026ci-\u003elock);\n 461:\t\terr = __swap_cache_add_check(ci, targ_entry, nr_pages, \u0026shadow, \u0026memcg_id);\n 462:\t\tif (unlikely(err)) {\n 463:\t\t\tspin_unlock(\u0026ci-\u003elock);\n 464:\t\t\tfolio_put(folio);\n 465:\t\t\treturn ERR_PTR(err);\n 466:\t\t}\n 467:\t\n 468:\t\t__folio_set_locked(folio);\n 469:\t\t__folio_set_swapbacked(folio);\n 470:\t\t__swap_cache_do_add_folio(ci, folio, entry);\n 471:\t\tspin_unlock(\u0026ci-\u003elock);\n 472:\t\n 473:\t\tif (mem_cgroup_swapin_charge_folio(folio, memcg_id,\n 474:\t\t\t\t\t\t vmf ? vmf-\u003evma-\u003evm_mm : NULL, gfp)) {\n 475:\t\t\tspin_lock(\u0026ci-\u003elock);\n 476:\t\t\t__swap_cache_do_del_folio(ci, folio, entry, shadow);\n 477:\t\t\tspin_unlock(\u0026ci-\u003elock);\n 478:\t\t\tfolio_unlock(folio);\n 479:\t\t\t/* nr_pages refs from swap cache, 1 from allocation */\n 480:\t\t\tfolio_put_refs(folio, nr_pages + 1);\n 481:\t\t\tcount_mthp_stat(order, MTHP_STAT_SWPIN_FALLBACK_CHARGE);\n 482:\t\t\treturn ERR_PTR(-ENOMEM);\n 483:\t\t}\n 484:\t\n 485:\t\tif (order \u003e 1 \u0026\u0026 folio_memcg_alloc_deferred(folio)) {\n 486:\t\t\tspin_lock(\u0026ci-\u003elock);\n 487:\t\t\t__swap_cache_do_del_folio(ci, folio, entry, shadow);\n 488:\t\t\tspin_unlock(\u0026ci-\u003elock);\n 489:\t\t\tfolio_unlock(folio);\n 490:\t\t\t/* nr_pages refs from swap cache, 1 from allocation */\n 491:\t\t\tfolio_put_refs(folio, nr_pages + 1);\n 492:\t\t\treturn ERR_PTR(-ENOMEM);\n 493:\t\t}\n 494:\t\n 495:\t\t/* memsw uncharges swap when folio is added to swap cache */\n 496:\t\tmemcg1_swapin(folio);\n 497:\t\n 498:\t\tnode_stat_mod_folio(folio, NR_FILE_PAGES, nr_pages);\n 499:\t\tlruvec_stat_mod_folio(folio, NR_SWAPCACHE, nr_pages);\n 500:\t\n 501:\t\tif (shadowp)\n 502:\t\t\t*shadowp = shadow;\n 503:\t\n 504:\t\treturn folio;\n 505:\t}\n 506:\t\n 507:\t/**\n 508:\t * __swap_cache_alloc_folio - Allocate folio for swapped out slot in swap cache.\n 509:\t * @targ_entry: swap entry indicating the target slot\n 510:\t * @gfp: memory allocation flags\n 511:\t * @orders: allocation orders, must be non zero\n 512:\t * @vmf: fault information\n 513:\t * @mpol: NUMA memory allocation policy to be applied\n 514:\t * @ilx: NUMA interleave index, for use only when MPOL_INTERLEAVE\n 515:\t * @shadowp: Returns the shadow the allocation displaced, NULL to ignore\n 516:\t *\n 517:\t * Allocate a folio in the swap cache for one swap slot, typically before\n 518:\t * doing IO (e.g. swap in or zswap writeback). The swap slot indicated by\n 519:\t * @targ_entry must have a non-zero swap count (swapped out).\n"
} |
| 23/3 | 2026/09/11 11:19 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 11601 output: 252 thoughts: 1956 Thoughts: **KMSAN Assessment: Zswap and Swap Cache Refactoring** Alright, let's break this down. My goal is to determine if this patch series necessitates a KMSAN (Kernel Memory Sanitizer) check. Given my expertise in memory management, I'll assess this systematically. First, I examined the core functions: `zswap_writeback_entry`, `swapin_sync`, `do_swap_page`, `shmem_swapin_folio`, `__swap_cache_do_del_folio`, `zswap_lookup_and_clear_shadows`, `zswap_folio_is_writeback_buffer`, and `__workingset_refault`. I’m looking for any indication of uninitialized memory usage, especially since this involves folios, swap cache interactions, and the workingset, all of which are critical areas. The `shadow` variable, in particular, caught my eye, as it's passed around these functions. My observations are that in `__swap_cache_alloc_folio`, `shadow` might be initialized within the function by `__swap_cache_add_check`, which is called inside the function, or by `zswap_lookup_and_clear_shadows` in several places where `shadow` is used, like in `swapin_sync`, `do_swap_page`, and `__swap_cache_do_del_folio`. The `zswap_lookup_and_clear_shadows` is especially important because it clears `shadow` from an xarray, and the value retrieved may contain a pointer. I verified that in `swapin_sync`, `shadow` can be `NULL` but is checked. The `zswap_lookup_and_clear_shadows` always returns the value, even `NULL`, so the pointer will never become an uninitialized memory read. I dug into the code and confirmed that every time `shadow` or similar variables (`shadow_parked`) are used, they are either initialized to `NULL` explicitly or assigned a value before being read. Specifically, looking at `zswap_lookup_and_clear_shadows`, the value of `shadow` comes from the `xa_load` of the `swap_zswap_tree` xarray, ensuring the values are known. The other usages, such as `swapin_sync` and `do_swap_page`, ensure `shadow` is not used uninitialized. The important point is that this patch series doesn't introduce any new risks specifically addressed by KMSAN. I checked that this code doesn't: copy data to user-space, read uninitialized variables, change buffer bounds, or allow reading uninitialized heap/stack memory. The standard debugging tools like KASAN, LOCKDEP, and VM debugging are sufficient to detect the potential memory management bugs such as use-after-free, deadlocks, and out-of-bounds access that this patch series might introduce. In light of this analysis, I'm confident that KMSAN isn't needed here. The potential bugs in the subsystem will be found by other debuggers. Therefore, `NeedsKMSAN=false`. |
| 24/3 | 2026/09/11 11:19 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch series modifies core memory management code in mm/ (swap cache, zswap writeback, and workingset refault tracking for LRU-managed folios). It adjusts how shadow entries are parked in the zswap tree during writeback and restored/refaulted during swapin.\n\n1. No kernel-to-user info-leak risk: No data is exposed or copied to user space (no copy_to_user, netlink, ioctls, or BPF buffers).\n2. No uninitialized memory usage: All newly introduced or modified local variables (such as `shadow`, `shadow_parked`, and loop indices) are explicitly initialized before use or assigned by functions that return pointers/values.\n3. Bug applicability: Potential risks introduced by these changes involve page locking, race conditions in swapcache/LRU manipulation, refcounting, or invalid xarray operations. These are detectable by standard debugging tools (KASAN, LOCKDEP, VM_WARN/VM_BUG assertions), not KMSAN.\n\nTherefore, a dedicated KMSAN fuzzing session is not warranted.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch series modifies core memory management code in mm/ (swap cache, zswap writeback, and workingset refault tracking for LRU-managed folios). It adjusts how shadow entries are parked in the zswap tree during writeback and restored/refaulted during swapin.\n\n1. No kernel-to-user info-leak risk: No data is exposed or copied to user space (no copy_to_user, netlink, ioctls, or BPF buffers).\n2. No uninitialized memory usage: All newly introduced or modified local variables (such as `shadow`, `shadow_parked`, and loop indices) are explicitly initialized before use or assigned by functions that return pointers/values.\n3. Bug applicability: Potential risks introduced by these changes involve page locking, race conditions in swapcache/LRU manipulation, refcounting, or invalid xarray operations. These are detectable by standard debugging tools (KASAN, LOCKDEP, VM_WARN/VM_BUG assertions), not KMSAN.\n\nTherefore, a dedicated KMSAN fuzzing session is not warranted.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|