| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/09/09 11:10 | flow | patch-triage |
0mArgs:null Results: {
"EnableConfigs": [
"QUOTA",
"GFS2_FS",
"SECURITY_LANDLOCK"
],
"FocusSymbols": [
"sb_for_each_inodes",
"evict_inodes",
"sync_bdevs",
"drop_pagecache_sb",
"add_dquot_ref",
"gfs2_evict_inodes",
"hook_sb_delete"
],
"KMSANReasoning": "The patch refactors the internal superblock inode iteration mechanism across several subsystems (block, drop_caches, gfs2, vfs inode, quota, landlock) by introducing a generic helper `sb_for_each_inodes()` and iterator structure `struct inode_iter`.\n\n- Uninitialized memory / KMSAN perspective:\n - The newly introduced stack-allocated `struct inode_iter` in `sb_for_each_inodes()` is fully initialized in `inode_sb_iter_start()`.\n - All callback data payloads passed to the iterator functions are either NULL or pointers to fully initialized variables/structures (e.g., boolean cast, initialized linked list head, integer pointer).\n - No kernel data is copied or exposed to user space (no `copy_to_user`, ioctls, netlink messages, etc.).\n - No uninitialized memory reads or conditional branching on uninitialized fields are introduced.\n\n- KASAN / LOCKDEP perspective:\n - Potential issues introduced by this patch relate to list manipulation, refcounting (`__iget`/`iput`), locking semantics under `sb-\u003es_inode_list_lock` and `inode-\u003ei_lock`, or use-after-free/race conditions if list nodes are deleted during traversal. These are fully covered by KASAN, LOCKDEP, and `DEBUG_LIST`.\n\nTherefore, there is no risk of uninitialized memory usage or info-leaks, and a dedicated KMSAN fuzzing session is not warranted.",
"NeedsKMSAN": false,
"Reasoning": "The patch introduces a new superblock inode iteration framework (sb_for_each_inodes) with dynamic iterator list tracking (s_inodes_iters) and updates traversal loops in VFS (evict_inodes, drop_caches), block devices (sync_bdevs), quotas (add_dquot_ref), GFS2 (gfs2_evict_inodes), and Landlock (hook_sb_delete). This significantly alters locking, list mutation during traversal, and refcounting across core subsystems, making it worth fuzzing for races, list corruptions, or deadlocks.",
"WorthFuzzing": true
} |
| 1/1 | 2026/09/09 11:10 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit b46e3d96c9a1710720b83b39e9ffa126f9880d65\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Wed Sep 9 11:10:41 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/block/bdev.c b/block/bdev.c\nindex cd83230837406..ae472a062e51c 100644\n--- a/block/bdev.c\n+++ b/block/bdev.c\n@@ -1336,56 +1336,53 @@ void bdev_mark_dead(struct block_device *bdev, bool surprise)\n */\n EXPORT_SYMBOL_GPL(bdev_mark_dead);\n \n-void sync_bdevs(bool wait)\n+static int sync_bdevs_inode_iter_cb(struct inode *inode, void *data)\n {\n-\tstruct inode *inode, *old_inode = NULL;\n-\n-\tspin_lock(\u0026blockdev_superblock-\u003es_inode_list_lock);\n-\tlist_for_each_entry(inode, \u0026blockdev_superblock-\u003es_inodes, i_sb_list) {\n-\t\tstruct address_space *mapping = inode-\u003ei_mapping;\n-\t\tstruct block_device *bdev;\n+\tbool wait = (bool)data;\n+\tstruct block_device *bdev;\n+\tstruct address_space *mapping = inode-\u003ei_mapping;\n \n-\t\tspin_lock(\u0026inode-\u003ei_lock);\n-\t\tif (inode_state_read(inode) \u0026 (I_FREEING | I_WILL_FREE | I_NEW) ||\n-\t\t mapping-\u003enrpages == 0) {\n-\t\t\tspin_unlock(\u0026inode-\u003ei_lock);\n-\t\t\tcontinue;\n-\t\t}\n-\t\t__iget(inode);\n+\tif (mapping-\u003enrpages == 0) {\n \t\tspin_unlock(\u0026inode-\u003ei_lock);\n-\t\tspin_unlock(\u0026blockdev_superblock-\u003es_inode_list_lock);\n+\t\treturn 0;\n+\t}\n+\n+\t/*\n+\t * We hold a reference to 'inode' so it couldn't have been\n+\t * removed from s_inodes list while we dropped the\n+\t * s_inode_list_lock.\n+\t */\n+\t__iget(inode);\n+\tspin_unlock(\u0026inode-\u003ei_lock);\n+\tspin_unlock(\u0026blockdev_superblock-\u003es_inode_list_lock);\n+\tbdev = I_BDEV(inode);\n+\n+\tmutex_lock(\u0026bdev-\u003ebd_disk-\u003eopen_mutex);\n+\tif (!atomic_read(\u0026bdev-\u003ebd_openers)) {\n+\t\t; /* skip */\n+\t} else if (wait) {\n \t\t/*\n-\t\t * We hold a reference to 'inode' so it couldn't have been\n-\t\t * removed from s_inodes list while we dropped the\n-\t\t * s_inode_list_lock We cannot iput the inode now as we can\n-\t\t * be holding the last reference and we cannot iput it under\n-\t\t * s_inode_list_lock. So we keep the reference and iput it\n-\t\t * later.\n+\t\t * We keep the error status of individual mapping so\n+\t\t * that applications can catch the writeback error using\n+\t\t * fsync(2). See filemap_fdatawait_keep_errors() for\n+\t\t * details.\n \t\t */\n-\t\tiput(old_inode);\n-\t\told_inode = inode;\n-\t\tbdev = I_BDEV(inode);\n-\n-\t\tmutex_lock(\u0026bdev-\u003ebd_disk-\u003eopen_mutex);\n-\t\tif (!atomic_read(\u0026bdev-\u003ebd_openers)) {\n-\t\t\t; /* skip */\n-\t\t} else if (wait) {\n-\t\t\t/*\n-\t\t\t * We keep the error status of individual mapping so\n-\t\t\t * that applications can catch the writeback error using\n-\t\t\t * fsync(2). See filemap_fdatawait_keep_errors() for\n-\t\t\t * details.\n-\t\t\t */\n-\t\t\tfilemap_fdatawait_keep_errors(inode-\u003ei_mapping);\n-\t\t} else {\n-\t\t\tfilemap_fdatawrite(inode-\u003ei_mapping);\n-\t\t}\n-\t\tmutex_unlock(\u0026bdev-\u003ebd_disk-\u003eopen_mutex);\n-\n-\t\tspin_lock(\u0026blockdev_superblock-\u003es_inode_list_lock);\n+\t\tfilemap_fdatawait_keep_errors(inode-\u003ei_mapping);\n+\t} else {\n+\t\tfilemap_fdatawrite(inode-\u003ei_mapping);\n \t}\n-\tspin_unlock(\u0026blockdev_superblock-\u003es_inode_list_lock);\n-\tiput(old_inode);\n+\tmutex_unlock(\u0026bdev-\u003ebd_disk-\u003eopen_mutex);\n+\tiput(inode);\n+\n+\tspin_lock(\u0026blockdev_superblock-\u003es_inode_list_lock);\n+\n+\treturn 0;\n+}\n+\n+void sync_bdevs(bool wait)\n+{\n+\tsb_for_each_inodes(blockdev_superblock, INODE_ITER_NORMAL,\n+\t\t\t sync_bdevs_inode_iter_cb, (void *)wait);\n }\n \n /*\ndiff --git a/fs/drop_caches.c b/fs/drop_caches.c\nindex 49f56a598ecbc..0d475a5ff8cfb 100644\n--- a/fs/drop_caches.c\n+++ b/fs/drop_caches.c\n@@ -16,36 +16,30 @@\n /* A global variable is a bit ugly, but it keeps the code simple */\n static int sysctl_drop_caches;\n \n-static void drop_pagecache_sb(struct super_block *sb, void *unused)\n+static int drop_pagecache_inode_iter_cb(struct inode *inode, void *unused)\n {\n-\tstruct inode *inode, *toput_inode = NULL;\n+\tstruct super_block *sb = inode-\u003ei_sb;\n \n-\tspin_lock(\u0026sb-\u003es_inode_list_lock);\n-\tlist_for_each_entry(inode, \u0026sb-\u003es_inodes, i_sb_list) {\n-\t\tspin_lock(\u0026inode-\u003ei_lock);\n-\t\t/*\n-\t\t * We must skip inodes in unusual state. We may also skip\n-\t\t * inodes without pages but we deliberately won't in case\n-\t\t * we need to reschedule to avoid softlockups.\n-\t\t */\n-\t\tif ((inode_state_read(inode) \u0026 (I_FREEING | I_WILL_FREE | I_NEW)) ||\n-\t\t (mapping_empty(inode-\u003ei_mapping) \u0026\u0026 !need_resched())) {\n-\t\t\tspin_unlock(\u0026inode-\u003ei_lock);\n-\t\t\tcontinue;\n-\t\t}\n-\t\t__iget(inode);\n+\tif (mapping_empty(inode-\u003ei_mapping)) {\n \t\tspin_unlock(\u0026inode-\u003ei_lock);\n-\t\tspin_unlock(\u0026sb-\u003es_inode_list_lock);\n-\n-\t\tinvalidate_mapping_pages(inode-\u003ei_mapping, 0, -1);\n-\t\tiput(toput_inode);\n-\t\ttoput_inode = inode;\n-\n-\t\tcond_resched();\n-\t\tspin_lock(\u0026sb-\u003es_inode_list_lock);\n+\t\treturn 0;\n \t}\n+\n+\t__iget(inode);\n+\tspin_unlock(\u0026inode-\u003ei_lock);\n \tspin_unlock(\u0026sb-\u003es_inode_list_lock);\n-\tiput(toput_inode);\n+\n+\tinvalidate_mapping_pages(inode-\u003ei_mapping, 0, -1);\n+\tiput(inode);\n+\n+\tspin_lock(\u0026sb-\u003es_inode_list_lock);\n+\n+\treturn 0;\n+}\n+\n+static void drop_pagecache_sb(struct super_block *sb, void *unused)\n+{\n+\tsb_for_each_inodes(sb, INODE_ITER_NORMAL, drop_pagecache_inode_iter_cb, NULL);\n }\n \n static int drop_caches_sysctl_handler(const struct ctl_table *table, int write,\ndiff --git a/fs/gfs2/ops_fstype.c b/fs/gfs2/ops_fstype.c\nindex 718e0da7dfce0..acdbf9ca44a3f 100644\n--- a/fs/gfs2/ops_fstype.c\n+++ b/fs/gfs2/ops_fstype.c\n@@ -996,7 +996,7 @@ static int gfs2_lm_mount(struct gfs2_sbd *sdp, int silent)\n \t\tswitch (token) {\n \t\tcase Opt_jid:\n \t\t\tret = match_int(\u0026tmp[0], \u0026option);\n-\t\t\tif (ret || option \u003c 0) \n+\t\t\tif (ret || option \u003c 0)\n \t\t\t\tgoto hostdata_error;\n \t\t\tif (test_and_clear_bit(SDF_NOJOURNALID, \u0026sdp-\u003esd_flags))\n \t\t\t\tls-\u003els_jid = option;\n@@ -1719,6 +1719,19 @@ static int gfs2_meta_init_fs_context(struct fs_context *fc)\n \treturn 0;\n }\n \n+static int gfs2_evict_inode_iter_cb(struct inode *inode, void *unused)\n+{\n+\tstruct super_block *sb = inode-\u003ei_sb;\n+\n+\t__iget(inode);\n+\tspin_unlock(\u0026inode-\u003ei_lock);\n+\tspin_unlock(\u0026sb-\u003es_inode_list_lock);\n+\n+\tiput(inode);\n+\tspin_lock(\u0026sb-\u003es_inode_list_lock);\n+\treturn 0;\n+}\n+\n /**\n * gfs2_evict_inodes - evict inodes cooperatively\n * @sb: the superblock\n@@ -1741,31 +1754,10 @@ static int gfs2_meta_init_fs_context(struct fs_context *fc)\n */\n static void gfs2_evict_inodes(struct super_block *sb)\n {\n-\tstruct inode *inode, *toput_inode = NULL;\n \tstruct gfs2_sbd *sdp = sb-\u003es_fs_info;\n \n \tset_bit(SDF_EVICTING, \u0026sdp-\u003esd_flags);\n-\n-\tspin_lock(\u0026sb-\u003es_inode_list_lock);\n-\tlist_for_each_entry(inode, \u0026sb-\u003es_inodes, i_sb_list) {\n-\t\tspin_lock(\u0026inode-\u003ei_lock);\n-\t\tif ((inode_state_read(inode) \u0026 (I_FREEING | I_WILL_FREE | I_NEW)) \u0026\u0026\n-\t\t !need_resched()) {\n-\t\t\tspin_unlock(\u0026inode-\u003ei_lock);\n-\t\t\tcontinue;\n-\t\t}\n-\t\t__iget(inode);\n-\t\tspin_unlock(\u0026inode-\u003ei_lock);\n-\t\tspin_unlock(\u0026sb-\u003es_inode_list_lock);\n-\n-\t\tiput(toput_inode);\n-\t\ttoput_inode = inode;\n-\n-\t\tcond_resched();\n-\t\tspin_lock(\u0026sb-\u003es_inode_list_lock);\n-\t}\n-\tspin_unlock(\u0026sb-\u003es_inode_list_lock);\n-\tiput(toput_inode);\n+\tsb_for_each_inodes(sb, INODE_ITER_NORMAL, gfs2_evict_inode_iter_cb, NULL);\n }\n \n static void gfs2_kill_sb(struct super_block *sb)\ndiff --git a/fs/inode.c b/fs/inode.c\nindex ba7da39be4a31..07a5f48641af8 100644\n--- a/fs/inode.c\n+++ b/fs/inode.c\n@@ -69,6 +69,15 @@ const struct address_space_operations empty_aops = {\n };\n EXPORT_SYMBOL(empty_aops);\n \n+struct inode_iter {\n+\tstruct list_head\titers_node;\t/* sb-\u003es_inodes_iters */\n+\tstruct list_head\t*next;\t\t/* next node going to iterate */\n+\tunsigned int flags;\n+\tinode_iter_cb func;\n+\tvoid *data;\n+\tint ret;\n+};\n+\n static DEFINE_PER_CPU(unsigned long, nr_inodes);\n static DEFINE_PER_CPU(unsigned long, nr_unused);\n \n@@ -641,12 +650,96 @@ void inode_sb_list_add(struct inode *inode)\n }\n EXPORT_SYMBOL_GPL(inode_sb_list_add);\n \n+static void inode_sb_iter_start(struct super_block *sb, struct inode_iter *it,\n+\t\t\t\tunsigned int flags, inode_iter_cb fn, void *data)\n+{\n+\tit-\u003eflags = flags;\n+\tit-\u003efunc = fn;\n+\tit-\u003edata = data;\n+\tit-\u003eret = 0;\n+\tspin_lock(\u0026sb-\u003es_inode_list_lock);\n+\tit-\u003enext = sb-\u003es_inodes.next;\n+\tlist_add(\u0026it-\u003eiters_node, \u0026sb-\u003es_inodes_iters);\n+}\n+\n+static void inode_sb_iter_end(struct inode_iter *it, struct super_block *sb)\n+{\n+\tlist_del(\u0026it-\u003eiters_node);\n+\tspin_unlock(\u0026sb-\u003es_inode_list_lock);\n+}\n+\n+static bool inode_sb_iter_next(struct inode_iter *it, struct super_block *sb)\n+{\n+\tstruct inode *inode = NULL;\n+\tint ret;\n+\n+\twhile (!inode \u0026\u0026 it-\u003enext != \u0026sb-\u003es_inodes) {\n+\t\tinode = list_entry(it-\u003enext, struct inode, i_sb_list);\n+\t\tif (it-\u003eflags \u0026 INODE_ITER_UNUSED) {\n+\t\t\tif (icount_read_once(inode)) {\n+\t\t\t\tit-\u003enext = it-\u003enext-\u003enext;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\n+\t\t\tspin_lock(\u0026inode-\u003ei_lock);\n+\t\t\tif (icount_read(inode)) {\n+\t\t\t\tspin_unlock(\u0026inode-\u003ei_lock);\n+\t\t\t\tit-\u003enext = it-\u003enext-\u003enext;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t} else {\n+\t\t\tspin_lock(\u0026inode-\u003ei_lock);\n+\t\t}\n+\n+\t\tif ((it-\u003eflags \u0026 INODE_ITER_NORMAL) \u0026\u0026\n+\t\t (inode_state_read(inode) \u0026 (I_NEW | I_FREEING | I_WILL_FREE))) {\n+\t\t\tspin_unlock(\u0026inode-\u003ei_lock);\n+\t\t\tit-\u003enext = it-\u003enext-\u003enext;\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tit-\u003enext = it-\u003enext-\u003enext;\n+\t\tret = it-\u003efunc(inode, it-\u003edata);\n+\t\tif (ret) {\n+\t\t\tit-\u003eret = ret;\n+\t\t\treturn false;\n+\t\t}\n+\n+\t\tif (need_resched()) {\n+\t\t\tspin_unlock(\u0026sb-\u003es_inode_list_lock);\n+\t\t\tcond_resched();\n+\t\t\tspin_lock(\u0026sb-\u003es_inode_list_lock);\n+\t\t}\n+\t}\n+\n+\treturn it-\u003enext == \u0026sb-\u003es_inodes ? false : true;\n+}\n+\n+int sb_for_each_inodes(struct super_block *sb, unsigned int flags,\n+\t\t inode_iter_cb fn, void *data)\n+{\n+\tstruct inode_iter it;\n+\n+\tinode_sb_iter_start(sb, \u0026it, flags, fn, data);\n+\twhile (inode_sb_iter_next(\u0026it, sb))\n+\t\t;\n+\tinode_sb_iter_end(\u0026it, sb);\n+\n+\treturn it.ret;\n+}\n+EXPORT_SYMBOL(sb_for_each_inodes);\n+\n static inline void inode_sb_list_del(struct inode *inode)\n {\n \tstruct super_block *sb = inode-\u003ei_sb;\n+\tstruct inode_iter *it;\n \n \tif (!list_empty(\u0026inode-\u003ei_sb_list)) {\n \t\tspin_lock(\u0026sb-\u003es_inode_list_lock);\n+\t\tlist_for_each_entry(it, \u0026sb-\u003es_inodes_iters, iters_node) {\n+\t\t\tif (it-\u003enext == \u0026inode-\u003ei_sb_list)\n+\t\t\t\tit-\u003enext = inode-\u003ei_sb_list.next;\n+\t\t}\n \t\tlist_del_init(\u0026inode-\u003ei_sb_list);\n \t\tspin_unlock(\u0026sb-\u003es_inode_list_lock);\n \t}\n@@ -866,6 +959,17 @@ static void dispose_list(struct list_head *head)\n \t}\n }\n \n+static int evict_inodes_inode_iter_cb(struct inode *inode, void *data)\n+{\n+\tstruct list_head *dispose = (struct list_head *)data;\n+\n+\tinode_state_set(inode, I_FREEING);\n+\tinode_lru_list_del(inode);\n+\tspin_unlock(\u0026inode-\u003ei_lock);\n+\tlist_add(\u0026inode-\u003ei_lru, dispose);\n+\treturn 0;\n+}\n+\n /**\n * evict_inodes\t- evict all evictable inodes for a superblock\n * @sb:\t\tsuperblock to operate on\n@@ -877,44 +981,10 @@ static void dispose_list(struct list_head *head)\n */\n void evict_inodes(struct super_block *sb)\n {\n-\tstruct inode *inode;\n \tLIST_HEAD(dispose);\n+\tunsigned int flags = INODE_ITER_NORMAL | INODE_ITER_UNUSED;\n \n-again:\n-\tspin_lock(\u0026sb-\u003es_inode_list_lock);\n-\tlist_for_each_entry(inode, \u0026sb-\u003es_inodes, i_sb_list) {\n-\t\tif (icount_read_once(inode))\n-\t\t\tcontinue;\n-\n-\t\tspin_lock(\u0026inode-\u003ei_lock);\n-\t\tif (icount_read(inode)) {\n-\t\t\tspin_unlock(\u0026inode-\u003ei_lock);\n-\t\t\tcontinue;\n-\t\t}\n-\t\tif (inode_state_read(inode) \u0026 (I_NEW | I_FREEING | I_WILL_FREE)) {\n-\t\t\tspin_unlock(\u0026inode-\u003ei_lock);\n-\t\t\tcontinue;\n-\t\t}\n-\n-\t\tinode_state_set(inode, I_FREEING);\n-\t\tinode_lru_list_del(inode);\n-\t\tspin_unlock(\u0026inode-\u003ei_lock);\n-\t\tlist_add(\u0026inode-\u003ei_lru, \u0026dispose);\n-\n-\t\t/*\n-\t\t * We can have a ton of inodes to evict at unmount time given\n-\t\t * enough memory, check to see if we need to go to sleep for a\n-\t\t * bit so we don't livelock.\n-\t\t */\n-\t\tif (need_resched()) {\n-\t\t\tspin_unlock(\u0026sb-\u003es_inode_list_lock);\n-\t\t\tcond_resched();\n-\t\t\tdispose_list(\u0026dispose);\n-\t\t\tgoto again;\n-\t\t}\n-\t}\n-\tspin_unlock(\u0026sb-\u003es_inode_list_lock);\n-\n+\tsb_for_each_inodes(sb, flags, evict_inodes_inode_iter_cb, \u0026dispose);\n \tdispose_list(\u0026dispose);\n }\n EXPORT_SYMBOL_GPL(evict_inodes);\ndiff --git a/fs/quota/dquot.c b/fs/quota/dquot.c\nindex 204afc5e984bc..77c68149d4ae4 100644\n--- a/fs/quota/dquot.c\n+++ b/fs/quota/dquot.c\n@@ -1043,64 +1043,48 @@ static int dqinit_needed(struct inode *inode, int type)\n \treturn 0;\n }\n \n-/* This routine is guarded by s_umount semaphore */\n-static int add_dquot_ref(struct super_block *sb, int type)\n+static int add_dquot_ref_inode_iter_cb(struct inode *inode, void *data)\n {\n-\tstruct inode *inode, *old_inode = NULL;\n+\tint type = *(int *)data;\n+\tint err = 0;\n+\tstruct super_block *sb = inode-\u003ei_sb;\n #ifdef CONFIG_QUOTA_DEBUG\n \tint reserved = 0;\n #endif\n-\tint err = 0;\n \n-\tspin_lock(\u0026sb-\u003es_inode_list_lock);\n-\tlist_for_each_entry(inode, \u0026sb-\u003es_inodes, i_sb_list) {\n-\t\tspin_lock(\u0026inode-\u003ei_lock);\n-\t\tif ((inode_state_read(inode) \u0026 (I_FREEING | I_WILL_FREE | I_NEW)) ||\n-\t\t !atomic_read(\u0026inode-\u003ei_writecount) ||\n-\t\t !dqinit_needed(inode, type)) {\n-\t\t\tspin_unlock(\u0026inode-\u003ei_lock);\n-\t\t\tcontinue;\n-\t\t}\n-\t\t__iget(inode);\n+\tif (!atomic_read(\u0026inode-\u003ei_writecount) ||\n+\t !dqinit_needed(inode, type)) {\n \t\tspin_unlock(\u0026inode-\u003ei_lock);\n-\t\tspin_unlock(\u0026sb-\u003es_inode_list_lock);\n+\t\treturn 0;\n+\t}\n+\n+\t__iget(inode);\n+\tspin_unlock(\u0026inode-\u003ei_lock);\n+\tspin_unlock(\u0026sb-\u003es_inode_list_lock);\n \n #ifdef CONFIG_QUOTA_DEBUG\n-\t\tif (unlikely(inode_get_rsv_space(inode) \u003e 0))\n-\t\t\treserved = 1;\n+\tif (unlikely(inode_get_rsv_space(inode) \u003e 0))\n+\t\treserved = 1;\n #endif\n-\t\tiput(old_inode);\n-\t\terr = __dquot_initialize(inode, type);\n-\t\tif (err) {\n-\t\t\tiput(inode);\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\t/*\n-\t\t * We hold a reference to 'inode' so it couldn't have been\n-\t\t * removed from s_inodes list while we dropped the\n-\t\t * s_inode_list_lock. We cannot iput the inode now as we can be\n-\t\t * holding the last reference and we cannot iput it under\n-\t\t * s_inode_list_lock. So we keep the reference and iput it\n-\t\t * later.\n-\t\t */\n-\t\told_inode = inode;\n-\t\tcond_resched();\n-\t\tspin_lock(\u0026sb-\u003es_inode_list_lock);\n-\t}\n-\tspin_unlock(\u0026sb-\u003es_inode_list_lock);\n-\tiput(old_inode);\n-out:\n+\terr = __dquot_initialize(inode, type);\n #ifdef CONFIG_QUOTA_DEBUG\n-\tif (reserved) {\n-\t\tquota_error(sb, \"Writes happened before quota was turned on \"\n-\t\t\t\"thus quota information is probably inconsistent. \"\n-\t\t\t\"Please run quotacheck(8)\");\n-\t}\n+\tif (reserved)\n+\t\tquota_error(sb, \"Writes happened before quota was turned \"\n+\t\t\t \"on thus quota information is probably \"\n+\t\t\t \"inconsistent. Please run quotacheck(8)\");\n #endif\n+\tiput(inode);\n+\tspin_lock(\u0026sb-\u003es_inode_list_lock);\n \treturn err;\n }\n \n+/* This routine is guarded by s_umount semaphore */\n+static int add_dquot_ref(struct super_block *sb, int type)\n+{\n+\treturn sb_for_each_inodes(sb, INODE_ITER_NORMAL,\n+\t\t\t\t add_dquot_ref_inode_iter_cb, \u0026type);\n+}\n+\n static void remove_dquot_ref(struct super_block *sb, int type)\n {\n \tstruct inode *inode;\ndiff --git a/fs/super.c b/fs/super.c\nindex 05e4431730387..3e069150c5445 100644\n--- a/fs/super.c\n+++ b/fs/super.c\n@@ -382,6 +382,7 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags,\n \tspin_lock_init(\u0026s-\u003es_roots_lock);\n \tmutex_init(\u0026s-\u003es_sync_lock);\n \tINIT_LIST_HEAD(\u0026s-\u003es_inodes);\n+\tINIT_LIST_HEAD(\u0026s-\u003es_inodes_iters);\n \tspin_lock_init(\u0026s-\u003es_inode_list_lock);\n \tINIT_LIST_HEAD(\u0026s-\u003es_inodes_wb);\n \tspin_lock_init(\u0026s-\u003es_inode_wblist_lock);\ndiff --git a/include/linux/fs.h b/include/linux/fs.h\nindex f9d1e05e8ae68..f3176ab10e65f 100644\n--- a/include/linux/fs.h\n+++ b/include/linux/fs.h\n@@ -296,7 +296,7 @@ struct iattr {\n */\n #define FILESYSTEM_MAX_STACK_DEPTH 2\n \n-/** \n+/**\n * enum positive_aop_returns - aop return codes with specific semantics\n *\n * @AOP_WRITEPAGE_ACTIVATE: Informs the caller that page writeback has\n@@ -306,7 +306,7 @@ struct iattr {\n * \t\t\t be a candidate for writeback again in the near\n * \t\t\t future. Other callers must be careful to unlock\n * \t\t\t the page if they get this return. Returned by\n- * \t\t\t writepage(); \n+ *\t\t\t writepage();\n *\n * @AOP_TRUNCATED_PAGE: The AOP method that was handed a locked page has\n * \t\t\tunlocked it and the page might have been truncated.\n@@ -870,6 +870,21 @@ struct inode {\n \tvoid\t\t\t*i_private; /* fs or device private pointer */\n } __randomize_layout;\n \n+enum inode_iter_flags_enum {\n+\tINODE_ITER_NORMAL = (1U \u003c\u003c 1), /* Exclude inodes with (I_NEW | I_FREEING | I_WILL_FREE). */\n+\tINODE_ITER_UNUSED = (1U \u003c\u003c 2), /* Only return inodes with (i_count == 0). */\n+};\n+\n+/*\n+ * start end\n+ * inode-\u003ei_lock locked unlocked\n+ * sb-\u003es_inode_list_lock locked locked\n+ */\n+typedef int (*inode_iter_cb) (struct inode *, void *);\n+\n+int sb_for_each_inodes(struct super_block *sb, unsigned int flags,\n+\t\t inode_iter_cb fn, void *data);\n+\n /*\n * i_state handling\n *\n@@ -1315,8 +1330,10 @@ struct file *get_file_active(struct file **f);\n \n #define\tMAX_NON_LFS\t((1UL\u003c\u003c31) - 1)\n \n-/* Page cache limit. The filesystems should put that into their s_maxbytes \n- limits, otherwise bad things can happen in VM. */ \n+/*\n+ * Page cache limit. The filesystems should put that into their s_maxbytes\n+ * limits, otherwise bad things can happen in VM.\n+ */\n #if BITS_PER_LONG==32\n #define MAX_LFS_FILESIZE\t((loff_t)ULONG_MAX \u003c\u003c PAGE_SHIFT)\n #elif BITS_PER_LONG==64\n@@ -2285,7 +2302,7 @@ int sync_inode_metadata(struct inode *inode, int wait);\n struct file_system_type {\n \tconst char *name;\n \tint fs_flags;\n-#define FS_REQUIRES_DEV\t\t1 \n+#define FS_REQUIRES_DEV\t\t1\n #define FS_BINARY_MOUNTDATA\t2\n #define FS_HAS_SUBTYPE\t\t4\n #define FS_USERNS_MOUNT\t\t8\t/* Can be mounted by userns root */\n@@ -2907,7 +2924,7 @@ ssize_t __kernel_read(struct file *file, void *buf, size_t count, loff_t *pos);\n extern ssize_t kernel_write(struct file *, const void *, size_t, loff_t *);\n extern ssize_t __kernel_write(struct file *, const void *, size_t, loff_t *);\n extern struct file * open_exec(const char *);\n- \n+\n /* fs/dcache.c -- generic fs support functions */\n extern bool is_subdir(struct dentry *, struct dentry *);\n extern bool path_is_under(const struct path *, const struct path *);\ndiff --git a/include/linux/fs/super_types.h b/include/linux/fs/super_types.h\nindex ecd96aeb1cee7..1f81cc219b8ee 100644\n--- a/include/linux/fs/super_types.h\n+++ b/include/linux/fs/super_types.h\n@@ -269,9 +269,10 @@ struct super_block {\n \t */\n \tint s_stack_depth;\n \n-\t/* s_inode_list_lock protects s_inodes */\n+\t/* s_inode_list_lock protects s_inodes and s_inodes_iters */\n \tspinlock_t\t\t\t\ts_inode_list_lock ____cacheline_aligned_in_smp;\n \tstruct list_head\t\t\ts_inodes;\t/* all inodes */\n+\tstruct list_head\t\t\ts_inodes_iters; /* all iterators */\n \n \tspinlock_t\t\t\t\ts_inode_wblist_lock;\n \tstruct list_head\t\t\ts_inodes_wb;\t/* writeback inodes */\ndiff --git a/security/landlock/fs.c b/security/landlock/fs.c\nindex 30aa6ce13590a..3dcde8cbfb6af 100644\n--- a/security/landlock/fs.c\n+++ b/security/landlock/fs.c\n@@ -1369,110 +1369,80 @@ static void hook_inode_free_security_rcu(void *inode_security)\n \n /* Super-block hooks */\n \n-/*\n- * Release the inodes used in a security policy.\n- *\n- * Cf. fsnotify_unmount_inodes() and evict_inodes()\n- */\n-static void hook_sb_delete(struct super_block *const sb)\n+static int hook_sb_delete_inode_iter_cb(struct inode *inode, void *data)\n {\n-\tstruct inode *inode, *prev_inode = NULL;\n+\tstruct landlock_object *object;\n+\tstruct super_block *sb = inode-\u003ei_sb;\n \n-\tif (!landlock_initialized)\n-\t\treturn;\n+\tif (!atomic_read(\u0026inode-\u003ei_count)) {\n+\t\tspin_unlock(\u0026inode-\u003ei_lock);\n+\t\treturn 0;\n+\t}\n \n-\tspin_lock(\u0026sb-\u003es_inode_list_lock);\n-\tlist_for_each_entry(inode, \u0026sb-\u003es_inodes, i_sb_list) {\n-\t\tstruct landlock_object *object;\n+\trcu_read_lock();\n+\tobject = rcu_dereference(landlock_inode(inode)-\u003eobject);\n+\tif (!object) {\n+\t\trcu_read_unlock();\n+\t\tspin_unlock(\u0026inode-\u003ei_lock);\n+\t\treturn 0;\n+\t}\n+\t/* Keeps a reference to this inode until the next loop walk. */\n+\t__iget(inode);\n+\tspin_unlock(\u0026inode-\u003ei_lock);\n \n-\t\t/* Only handles referenced inodes. */\n-\t\tif (!icount_read_once(inode))\n-\t\t\tcontinue;\n+\t/*\n+\t * If there is no concurrent release_inode() ongoing, then we\n+\t * are in charge of calling iput() on this inode, otherwise we\n+\t * will just wait for it to finish.\n+\t */\n+\tspin_lock(\u0026object-\u003elock);\n+\tif (object-\u003eunderobj == inode) {\n+\t\tobject-\u003eunderobj = NULL;\n+\t\tspin_unlock(\u0026object-\u003elock);\n+\t\trcu_read_unlock();\n \n \t\t/*\n-\t\t * Protects against concurrent modification of inode (e.g.\n-\t\t * from get_inode_object()).\n+\t\t * Because object-\u003eunderobj was not NULL,\n+\t\t * release_inode() and get_inode_object() guarantee\n+\t\t * that it is safe to reset\n+\t\t * landlock_inode(inode)-\u003eobject while it is not NULL.\n+\t\t * It is therefore not necessary to lock inode-\u003ei_lock.\n \t\t */\n-\t\tspin_lock(\u0026inode-\u003ei_lock);\n+\t\trcu_assign_pointer(landlock_inode(inode)-\u003eobject, NULL);\n \t\t/*\n-\t\t * Checks I_FREEING and I_WILL_FREE to protect against a race\n-\t\t * condition when release_inode() just called iput(), which\n-\t\t * could lead to a NULL dereference of inode-\u003esecurity or a\n-\t\t * second call to iput() for the same Landlock object. Also\n-\t\t * checks I_NEW because such inode cannot be tied to an object.\n+\t\t * At this point, we own the ihold() reference that was\n+\t\t * originally set up by get_inode_object() and the\n+\t\t * __iget() reference that we just set in this loop\n+\t\t * walk. Therefore there are at least two references\n+\t\t * on the inode.\n \t\t */\n-\t\tif (inode_state_read(inode) \u0026\n-\t\t (I_FREEING | I_WILL_FREE | I_NEW)) {\n-\t\t\tspin_unlock(\u0026inode-\u003ei_lock);\n-\t\t\tcontinue;\n-\t\t}\n+\t\tiput_not_last(inode);\n+\t} else {\n+\t\tspin_unlock(\u0026object-\u003elock);\n+\t\trcu_read_unlock();\n+\t}\n \n-\t\trcu_read_lock();\n-\t\tobject = rcu_dereference(landlock_inode(inode)-\u003eobject);\n-\t\tif (!object) {\n-\t\t\trcu_read_unlock();\n-\t\t\tspin_unlock(\u0026inode-\u003ei_lock);\n-\t\t\tcontinue;\n-\t\t}\n-\t\t/* Keeps a reference to this inode until the next loop walk. */\n-\t\t__iget(inode);\n-\t\tspin_unlock(\u0026inode-\u003ei_lock);\n+\tspin_unlock(\u0026sb-\u003es_inode_list_lock);\n+\tiput(inode);\n+\tspin_lock(\u0026sb-\u003es_inode_list_lock);\n \n-\t\t/*\n-\t\t * If there is no concurrent release_inode() ongoing, then we\n-\t\t * are in charge of calling iput() on this inode, otherwise we\n-\t\t * will just wait for it to finish.\n-\t\t */\n-\t\tspin_lock(\u0026object-\u003elock);\n-\t\tif (object-\u003eunderobj == inode) {\n-\t\t\tobject-\u003eunderobj = NULL;\n-\t\t\tspin_unlock(\u0026object-\u003elock);\n-\t\t\trcu_read_unlock();\n+\treturn 0;\n+}\n \n-\t\t\t/*\n-\t\t\t * Because object-\u003eunderobj was not NULL,\n-\t\t\t * release_inode() and get_inode_object() guarantee\n-\t\t\t * that it is safe to reset\n-\t\t\t * landlock_inode(inode)-\u003eobject while it is not NULL.\n-\t\t\t * It is therefore not necessary to lock inode-\u003ei_lock.\n-\t\t\t */\n-\t\t\trcu_assign_pointer(landlock_inode(inode)-\u003eobject, NULL);\n-\t\t\t/*\n-\t\t\t * At this point, we own the ihold() reference that was\n-\t\t\t * originally set up by get_inode_object() and the\n-\t\t\t * __iget() reference that we just set in this loop\n-\t\t\t * walk. Therefore there are at least two references\n-\t\t\t * on the inode.\n-\t\t\t */\n-\t\t\tiput_not_last(inode);\n-\t\t} else {\n-\t\t\tspin_unlock(\u0026object-\u003elock);\n-\t\t\trcu_read_unlock();\n-\t\t}\n+/*\n+ * Release the inodes used in a security policy.\n+ *\n+ * Cf. fsnotify_unmount_inodes() and evict_inodes()\n+ */\n+static void hook_sb_delete(struct super_block *const sb)\n+{\n+\tunsigned int flags = INODE_ITER_NORMAL;\n \n-\t\tif (prev_inode) {\n-\t\t\t/*\n-\t\t\t * At this point, we still own the __iget() reference\n-\t\t\t * that we just set in this loop walk. Therefore we\n-\t\t\t * can drop the list lock and know that the inode won't\n-\t\t\t * disappear from under us until the next loop walk.\n-\t\t\t */\n-\t\t\tspin_unlock(\u0026sb-\u003es_inode_list_lock);\n-\t\t\t/*\n-\t\t\t * We can now actually put the inode reference from the\n-\t\t\t * previous loop walk, which is not needed anymore.\n-\t\t\t */\n-\t\t\tiput(prev_inode);\n-\t\t\tcond_resched();\n-\t\t\tspin_lock(\u0026sb-\u003es_inode_list_lock);\n-\t\t}\n-\t\tprev_inode = inode;\n-\t}\n-\tspin_unlock(\u0026sb-\u003es_inode_list_lock);\n+\tif (!landlock_initialized)\n+\t\treturn;\n+\n+\tsb_for_each_inodes(sb, flags, hook_sb_delete_inode_iter_cb, NULL);\n \n-\t/* Puts the inode reference from the last loop walk, if any. */\n-\tif (prev_inode)\n-\t\tiput(prev_inode);\n \t/* Waits for pending iput() in release_inode(). */\n \twait_var_event(\u0026landlock_superblock(sb)-\u003einode_refs,\n \t\t !atomic_long_read(\u0026landlock_superblock(sb)-\u003einode_refs));\n"
} |
| 2/1 | 2026/09/09 11:10 | agent | patch-evaluator |
0mModel:core Args: null Results: {
"EnableConfigs": [
"QUOTA",
"GFS2_FS",
"SECURITY_LANDLOCK"
],
"FocusSymbols": [
"sb_for_each_inodes",
"evict_inodes",
"sync_bdevs",
"drop_pagecache_sb",
"add_dquot_ref",
"gfs2_evict_inodes",
"hook_sb_delete"
],
"Reasoning": "The patch introduces a new superblock inode iteration framework (sb_for_each_inodes) with dynamic iterator list tracking (s_inodes_iters) and updates traversal loops in VFS (evict_inodes, drop_caches), block devices (sync_bdevs), quotas (add_dquot_ref), GFS2 (gfs2_evict_inodes), and Landlock (hook_sb_delete). This significantly alters locking, list mutation during traversal, and refcounting across core subsystems, making it worth fuzzing for races, list corruptions, or deadlocks.",
"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 b46e3d96c9a1710720b83b39e9ffa126f9880d65
Author: syz-cluster <triage@syzkaller.com>
Date: Wed Sep 9 11:10:41 2026 +0000
syz-cluster: applied patch under review
diff --git a/block/bdev.c b/block/bdev.c
index cd83230837406..ae472a062e51c 100644
--- a/block/bdev.c
+++ b/block/bdev.c
@@ -1336,56 +1336,53 @@ void bdev_mark_dead(struct block_device *bdev, bool surprise)
*/
EXPORT_SYMBOL_GPL(bdev_mark_dead);
-void sync_bdevs(bool wait)
+static int sync_bdevs_inode_iter_cb(struct inode *inode, void *data)
{
- struct inode *inode, *old_inode = NULL;
-
- spin_lock(&blockdev_superblock->s_inode_list_lock);
- list_for_each_entry(inode, &blockdev_superblock->s_inodes, i_sb_list) {
- struct address_space *mapping = inode->i_mapping;
- struct block_device *bdev;
+ bool wait = (bool)data;
+ struct block_device *bdev;
+ struct address_space *mapping = inode->i_mapping;
- spin_lock(&inode->i_lock);
- if (inode_state_read(inode) & (I_FREEING | I_WILL_FREE | I_NEW) ||
- mapping->nrpages == 0) {
- spin_unlock(&inode->i_lock);
- continue;
- }
- __iget(inode);
+ if (mapping->nrpages == 0) {
spin_unlock(&inode->i_lock);
- spin_unlock(&blockdev_superblock->s_inode_list_lock);
+ return 0;
+ }
+
+ /*
+ * We hold a reference to 'inode' so it couldn't have been
+ * removed from s_inodes list while we dropped the
+ * s_inode_list_lock.
+ */
+ __iget(inode);
+ spin_unlock(&inode->i_lock);
+ spin_unlock(&blockdev_superblock->s_inode_list_lock);
+ bdev = I_BDEV(inode);
+
+ mutex_lock(&bdev->bd_disk->open_mutex);
+ if (!atomic_read(&bdev->bd_openers)) {
+ ; /* skip */
+ } else if (wait) {
/*
- * We hold a reference to 'inode' so it couldn't have been
- * removed from s_inodes list while we dropped the
- * s_inode_list_lock We cannot iput the inode now as we can
- * be holding the last reference and we cannot iput it under
- * s_inode_list_lock. So we keep the reference and iput it
- * later.
+ * We keep the error status of individual mapping so
+ * that applications can catch the writeback error using
+ * fsync(2). See filemap_fdatawait_keep_errors() for
+ * details.
*/
- iput(old_inode);
- old_inode = inode;
- bdev = I_BDEV(inode);
-
- mutex_lock(&bdev->bd_disk->open_mutex);
- if (!atomic_read(&bdev->bd_openers)) {
- ; /* skip */
- } else if (wait) {
- /*
- * We keep the error status of individual mapping so
- * that applications can catch the writeback error using
- * fsync(2). See filemap_fdatawait_keep_errors() for
- * details.
- */
- filemap_fdatawait_keep_errors(inode->i_mapping);
- } else {
- filemap_fdatawrite(inode->i_mapping);
- }
- mutex_unlock(&bdev->bd_disk->open_mutex);
-
- spin_lock(&blockdev_superblock->s_inode_list_lock);
+ filemap_fdatawait_keep_errors(inode->i_mapping);
+ } else {
+ filemap_fdatawrite(inode->i_mapping);
}
- spin_unlock(&blockdev_superblock->s_inode_list_lock);
- iput(old_inode);
+ mutex_unlock(&bdev->bd_disk->open_mutex);
+ iput(inode);
+
+ spin_lock(&blockdev_superblock->s_inode_list_lock);
+
+ return 0;
+}
+
+void sync_bdevs(bool wait)
+{
+ sb_for_each_inodes(blockdev_superblock, INODE_ITER_NORMAL,
+ sync_bdevs_inode_iter_cb, (void *)wait);
}
/*
diff --git a/fs/drop_caches.c b/fs/drop_caches.c
index 49f56a598ecbc..0d475a5ff8cfb 100644
--- a/fs/drop_caches.c
+++ b/fs/drop_caches.c
@@ -16,36 +16,30 @@
/* A global variable is a bit ugly, but it keeps the code simple */
static int sysctl_drop_caches;
-static void drop_pagecache_sb(struct super_block *sb, void *unused)
+static int drop_pagecache_inode_iter_cb(struct inode *inode, void *unused)
{
- struct inode *inode, *toput_inode = NULL;
+ struct super_block *sb = inode->i_sb;
- spin_lock(&sb->s_inode_list_lock);
- list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
- spin_lock(&inode->i_lock);
- /*
- * We must skip inodes in unusual state. We may also skip
- * inodes without pages but we deliberately won't in case
- * we need to reschedule to avoid softlockups.
- */
- if ((inode_state_read(inode) & (I_FREEING | I_WILL_FREE | I_NEW)) ||
- (mapping_empty(inode->i_mapping) && !need_resched())) {
- spin_unlock(&inode->i_lock);
- continue;
- }
- __iget(inode);
+ if (mapping_empty(inode->i_mapping)) {
spin_unlock(&inode->i_lock);
- spin_unlock(&sb->s_inode_list_lock);
-
- invalidate_mapping_pages(inode->i_mapping, 0, -1);
- iput(toput_inode);
- toput_inode = inode;
-
- cond_resched();
- spin_lock(&sb->s_inode_list_lock);
+ return 0;
}
+
+ __iget(inode);
+ spin_unlock(&inode->i_lock);
spin_unlock(&sb->s_inode_list_lock);
- iput(toput_inode);
+
+ invalidate_mapping_pages(inode->i_mapping, 0, -1);
+ iput(inode);
+
+ spin_lock(&sb->s_inode_list_lock);
+
+ return 0;
+}
+
+static void drop_pagecache_sb(struct super_block *sb, void *unused)
+{
+ sb_for_each_inodes(sb, INODE_ITER_NORMAL, drop_pagecache_inode_iter_cb, NULL);
}
static int drop_caches_sysctl_handler(const struct ctl_table *table, int write,
diff --git a/fs/gfs2/ops_fstype.c b/fs/gfs2/ops_fstype.c
index 718e0da7dfce0..acdbf9ca44a3f 100644
--- a/fs/gfs2/ops_fstype.c
+++ b/fs/gfs2/ops_fstype.c
@@ -996,7 +996,7 @@ static int gfs2_lm_mount(struct gfs2_sbd *sdp, int silent)
switch (token) {
case Opt_jid:
ret = match_int(&tmp[0], &option);
- if (ret || option < 0)
+ if (ret || option < 0)
goto hostdata_error;
if (test_and_clear_bit(SDF_NOJOURNALID, &sdp->sd_flags))
ls->ls_jid = option;
@@ -1719,6 +1719,19 @@ static int gfs2_meta_init_fs_context(struct fs_context *fc)
return 0;
}
+static int gfs2_evict_inode_iter_cb(struct inode *inode, void *unused)
+{
+ struct super_block *sb = inode->i_sb;
+
+ __iget(inode);
+ spin_unlock(&inode->i_lock);
+ spin_unlock(&sb->s_inode_list_lock);
+
+ iput(inode);
+ spin_lock(&sb->s_inode_list_lock);
+ return 0;
+}
+
/**
* gfs2_evict_inodes - evict inodes cooperatively
* @sb: the superblock
@@ -1741,31 +1754,10 @@ static int gfs2_meta_init_fs_context(struct fs_context *fc)
*/
static void gfs2_evict_inodes(struct super_block *sb)
{
- struct inode *inode, *toput_inode = NULL;
struct gfs2_sbd *sdp = sb->s_fs_info;
set_bit(SDF_EVICTING, &sdp->sd_flags);
-
- spin_lock(&sb->s_inode_list_lock);
- list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
- spin_lock(&inode->i_lock);
- if ((inode_state_read(inode) & (I_FREEING | I_WILL_FREE | I_NEW)) &&
- !need_resched()) {
- spin_unlock(&inode->i_lock);
- continue;
- }
- __iget(inode);
- spin_unlock(&inode->i_lock);
- spin_unlock(&sb->s_inode_list_lock);
-
- iput(toput_inode);
- toput_inode = inode;
-
- cond_resched();
- spin_lock(&sb->s_inode_list_lock);
- }
- spin_unlock(&sb->s_inode_list_lock);
- iput(toput_inode);
+ sb_for_each_inodes(sb, INODE_ITER_NORMAL, gfs2_evict_inode_iter_cb, NULL);
}
static void gfs2_kill_sb(struct super_block *sb)
diff --git a/fs/inode.c b/fs/inode.c
index ba7da39be4a31..07a5f48641af8 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -69,6 +69,15 @@ const struct address_space_operations empty_aops = {
};
EXPORT_SYMBOL(empty_aops);
+struct inode_iter {
+ struct list_head iters_node; /* sb->s_inodes_iters */
+ struct list_head *next; /* next node going to iterate */
+ unsigned int flags;
+ inode_iter_cb func;
+ void *data;
+ int ret;
+};
+
static DEFINE_PER_CPU(unsigned long, nr_inodes);
static DEFINE_PER_CPU(unsigned long, nr_unused);
@@ -641,12 +650,96 @@ void inode_sb_list_add(struct inode *inode)
}
EXPORT_SYMBOL_GPL(inode_sb_list_add);
+static void inode_sb_iter_start(struct super_block *sb, struct inode_iter *it,
+ unsigned int flags, inode_iter_cb fn, void *data)
+{
+ it->flags = flags;
+ it->func = fn;
+ it->data = data;
+ it->ret = 0;
+ spin_lock(&sb->s_inode_list_lock);
+ it->next = sb->s_inodes.next;
+ list_add(&it->iters_node, &sb->s_inodes_iters);
+}
+
+static void inode_sb_iter_end(struct inode_iter *it, struct super_block *sb)
+{
+ list_del(&it->iters_node);
+ spin_unlock(&sb->s_inode_list_lock);
+}
+
+static bool inode_sb_iter_next(struct inode_iter *it, struct super_block *sb)
+{
+ struct inode *inode = NULL;
+ int ret;
+
+ while (!inode && it->next != &sb->s_inodes) {
+ inode = list_entry(it->next, struct inode, i_sb_list);
+ if (it->flags & INODE_ITER_UNUSED) {
+ if (icount_read_once(inode)) {
+ it->next = it->next->next;
+ continue;
+ }
+
+ spin_lock(&inode->i_lock);
+ if (icount_read(inode)) {
+ spin_unlock(&inode->i_lock);
+ it->next = it->next->next;
+ continue;
+ }
+ } else {
+ spin_lock(&inode->i_lock);
+ }
+
+ if ((it->flags & INODE_ITER_NORMAL) &&
+ (inode_state_read(inode) & (I_NEW | I_FREEING | I_WILL_FREE))) {
+ spin_unlock(&inode->i_lock);
+ it->next = it->next->next;
+ continue;
+ }
+
+ it->next = it->next->next;
+ ret = it->func(inode, it->data);
+ if (ret) {
+ it->ret = ret;
+ return false;
+ }
+
+ if (need_resched()) {
+ spin_unlock(&sb->s_inode_list_lock);
+ cond_resched();
+ spin_lock(&sb->s_inode_list_lock);
+ }
+ }
+
+ return it->next == &sb->s_inodes ? false : true;
+}
+
+int sb_for_each_inodes(struct super_block *sb, unsigned int flags,
+ inode_iter_cb fn, void *data)
+{
+ struct inode_iter it;
+
+ inode_sb_iter_start(sb, &it, flags, fn, data);
+ while (inode_sb_iter_next(&it, sb))
+ ;
+ inode_sb_iter_end(&it, sb);
+
+ return it.ret;
+}
+EXPORT_SYMBOL(sb_for_each_inodes);
+
static inline void inode_sb_list_del(struct inode *inode)
{
struct super_block *sb = inode->i_sb;
+ struct inode_iter *it;
if (!list_empty(&inode->i_sb_list)) {
spin_lock(&sb->s_inode_list_lock);
+ list_for_each_entry(it, &sb->s_inodes_iters, iters_node) {
+ if (it->next == &inode->i_sb_list)
+ it->next = inode->i_sb_list.next;
+ }
list_del_init(&inode->i_sb_list);
spin_unlock(&sb->s_inode_list_lock);
}
@@ -866,6 +959,17 @@ static void dispose_list(struct list_head *head)
}
}
+static int evict_inodes_inode_iter_cb(struct inode *inode, void *data)
+{
+ struct list_head *dispose = (struct list_head *)data;
+
+ inode_state_set(inode, I_FREEING);
+ inode_lru_list_del(inode);
+ spin_unlock(&inode->i_lock);
+ list_add(&inode->i_lru, dispose);
+ return 0;
+}
+
/**
* evict_inodes - evict all evictable inodes for a superblock
* @sb: superblock to operate on
@@ -877,44 +981,10 @@ static void dispose_list(struct list_head *head)
*/
void evict_inodes(struct super_block *sb)
{
- struct inode *inode;
LIST_HEAD(dispose);
+ unsigned int flags = INODE_ITER_NORMAL | INODE_ITER_UNUSED;
-again:
- spin_lock(&sb->s_inode_list_lock);
- list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
- if (icount_read_once(inode))
- continue;
-
- spin_lock(&inode->i_lock);
- if (icount_read(inode)) {
- spin_unlock(&inode->i_lock);
- continue;
- }
- if (inode_state_read(inode) & (I_NEW | I_FREEING | I_WILL_FREE)) {
- spin_unlock(&inode->i_lock);
- continue;
- }
-
- inode_state_set(inode, I_FREEING);
- inode_lru_list_del(inode);
- spin_unlock(&inode->i_lock);
- list_add(&inode->i_lru, &dispose);
-
- /*
- * We can have a ton of inodes to evict at unmount time given
- * enough memory, check to see if we need to go to sleep for a
- * bit so we don't livelock.
- */
- if (need_resched()) {
- spin_unlock(&sb->s_inode_list_lock);
- cond_resched();
- dispose_list(&dispose);
- goto again;
- }
- }
- spin_unlock(&sb->s_inode_list_lock);
-
+ sb_for_each_inodes(sb, flags, evict_inodes_inode_iter_cb, &dispose);
dispose_list(&dispose);
}
EXPORT_SYMBOL_GPL(evict_inodes);
diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c
index 204afc5e984bc..77c68149d4ae4 100644
--- a/fs/quota/dquot.c
+++ b/fs/quota/dquot.c
@@ -1043,64 +1043,48 @@ static int dqinit_needed(struct inode *inode, int type)
return 0;
}
-/* This routine is guarded by s_umount semaphore */
-static int add_dquot_ref(struct super_block *sb, int type)
+static int add_dquot_ref_inode_iter_cb(struct inode *inode, void *data)
{
- struct inode *inode, *old_inode = NULL;
+ int type = *(int *)data;
+ int err = 0;
+ struct super_block *sb = inode->i_sb;
#ifdef CONFIG_QUOTA_DEBUG
int reserved = 0;
#endif
- int err = 0;
- spin_lock(&sb->s_inode_list_lock);
- list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
- spin_lock(&inode->i_lock);
- if ((inode_state_read(inode) & (I_FREEING | I_WILL_FREE | I_NEW)) ||
- !atomic_read(&inode->i_writecount) ||
- !dqinit_needed(inode, type)) {
- spin_unlock(&inode->i_lock);
- continue;
- }
- __iget(inode);
+ if (!atomic_read(&inode->i_writecount) ||
+ !dqinit_needed(inode, type)) {
spin_unlock(&inode->i_lock);
- spin_unlock(&sb->s_inode_list_lock);
+ return 0;
+ }
+
+ __iget(inode);
+ spin_unlock(&inode->i_lock);
+ spin_unlock(&sb->s_inode_list_lock);
#ifdef CONFIG_QUOTA_DEBUG
- if (unlikely(inode_get_rsv_space(inode) > 0))
- reserved = 1;
+ if (unlikely(inode_get_rsv_space(inode) > 0))
+ reserved = 1;
#endif
- iput(old_inode);
- err = __dquot_initialize(inode, type);
- if (err) {
- iput(inode);
- goto out;
- }
-
- /*
- * We hold a reference to 'inode' so it couldn't have been
- * removed from s_inodes list while we dropped the
- * s_inode_list_lock. We cannot iput the inode now as we can be
- * holding the last reference and we cannot iput it under
- * s_inode_list_lock. So we keep the reference and iput it
- * later.
- */
- old_inode = inode;
- cond_resched();
- spin_lock(&sb->s_inode_list_lock);
- }
- spin_unlock(&sb->s_inode_list_lock);
- iput(old_inode);
-out:
+ err = __dquot_initialize(inode, type);
#ifdef CONFIG_QUOTA_DEBUG
- if (reserved) {
- quota_error(sb, "Writes happened before quota was turned on "
- "thus quota information is probably inconsistent. "
- "Please run quotacheck(8)");
- }
+ if (reserved)
+ quota_error(sb, "Writes happened before quota was turned "
+ "on thus quota information is probably "
+ "inconsistent. Please run quotacheck(8)");
#endif
+ iput(inode);
+ spin_lock(&sb->s_inode_list_lock);
return err;
}
+/* This routine is guarded by s_umount semaphore */
+static int add_dquot_ref(struct super_block *sb, int type)
+{
+ return sb_for_each_inodes(sb, INODE_ITER_NORMAL,
+ add_dquot_ref_inode_iter_cb, &type);
+}
+
static void remove_dquot_ref(struct super_block *sb, int type)
{
struct inode *inode;
diff --git a/fs/super.c b/fs/super.c
index 05e4431730387..3e069150c5445 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -382,6 +382,7 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags,
spin_lock_init(&s->s_roots_lock);
mutex_init(&s->s_sync_lock);
INIT_LIST_HEAD(&s->s_inodes);
+ INIT_LIST_HEAD(&s->s_inodes_iters);
spin_lock_init(&s->s_inode_list_lock);
INIT_LIST_HEAD(&s->s_inodes_wb);
spin_lock_init(&s->s_inode_wblist_lock);
diff --git a/include/linux/fs.h b/include/linux/fs.h
index f9d1e05e8ae68..f3176ab10e65f 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -296,7 +296,7 @@ struct iattr {
*/
#define FILESYSTEM_MAX_STACK_DEPTH 2
-/**
+/**
* enum positive_aop_returns - aop return codes with specific semantics
*
* @AOP_WRITEPAGE_ACTIVATE: Informs the caller that page writeback has
@@ -306,7 +306,7 @@ struct iattr {
* be a candidate for writeback again in the near
* future. Other callers must be careful to unlock
* the page if they get this return. Returned by
- * writepage();
+ * writepage();
*
* @AOP_TRUNCATED_PAGE: The AOP method that was handed a locked page has
* unlocked it and the page might have been truncated.
@@ -870,6 +870,21 @@ struct inode {
void *i_private; /* fs or device private pointer */
} __randomize_layout;
+enum inode_iter_flags_enum {
+ INODE_ITER_NORMAL = (1U << 1), /* Exclude inodes with (I_NEW | I_FREEING | I_WILL_FREE). */
+ INODE_ITER_UNUSED = (1U << 2), /* Only return inodes with (i_count == 0). */
+};
+
+/*
+ * start end
+ * inode->i_lock locked unlocked
+ * sb->s_inode_list_lock locked locked
+ */
+typedef int (*inode_iter_cb) (struct inode *, void *);
+
+int sb_for_each_inodes(struct super_block *sb, unsigned int flags,
+ inode_iter_cb fn, void *data);
+
/*
* i_state handling
*
@@ -1315,8 +1330,10 @@ struct file *get_file_active(struct file **f);
#define MAX_NON_LFS ((1UL<<31) - 1)
-/* Page cache limit. The filesystems should put that into their s_maxbytes
- limits, otherwise bad things can happen in VM. */
+/*
+ * Page cache limit. The filesystems should put that into their s_maxbytes
+ * limits, otherwise bad things can happen in VM.
+ */
#if BITS_PER_LONG==32
#define MAX_LFS_FILESIZE ((loff_t)ULONG_MAX << PAGE_SHIFT)
#elif BITS_PER_LONG==64
@@ -2285,7 +2302,7 @@ int sync_inode_metadata(struct inode *inode, int wait);
struct file_system_type {
const char *name;
int fs_flags;
-#define FS_REQUIRES_DEV 1
+#define FS_REQUIRES_DEV 1
#define FS_BINARY_MOUNTDATA 2
#define FS_HAS_SUBTYPE 4
#define FS_USERNS_MOUNT 8 /* Can be mounted by userns root */
@@ -2907,7 +2924,7 @@ ssize_t __kernel_read(struct file *file, void *buf, size_t count, loff_t *pos);
extern ssize_t kernel_write(struct file *, const void *, size_t, loff_t *);
extern ssize_t __kernel_write(struct file *, const void *, size_t, loff_t *);
extern struct file * open_exec(const char *);
-
+
/* fs/dcache.c -- generic fs support functions */
extern bool is_subdir(struct dentry *, struct dentry *);
extern bool path_is_under(const struct path *, const struct path *);
diff --git a/include/linux/fs/super_types.h b/include/linux/fs/super_types.h
index ecd96aeb1cee7..1f81cc219b8ee 100644
--- a/include/linux/fs/super_types.h
+++ b/include/linux/fs/super_types.h
@@ -269,9 +269,10 @@ struct super_block {
*/
int s_stack_depth;
- /* s_inode_list_lock protects s_inodes */
+ /* s_inode_list_lock protects s_inodes and s_inodes_iters */
spinlock_t s_inode_list_lock ____cacheline_aligned_in_smp;
struct list_head s_inodes; /* all inodes */
+ struct list_head s_inodes_iters; /* all iterators */
spinlock_t s_inode_wblist_lock;
struct list_head s_inodes_wb; /* writeback inodes */
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 30aa6ce13590a..3dcde8cbfb6af 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -1369,110 +1369,80 @@ static void hook_inode_free_security_rcu(void *inode_security)
/* Super-block hooks */
-/*
- * Release the inodes used in a security policy.
- *
- * Cf. fsnotify_unmount_inodes() and evict_inodes()
- */
-static void hook_sb_delete(struct super_block *const sb)
+static int hook_sb_delete_inode_iter_cb(struct inode *inode, void *data)
{
- struct inode *inode, *prev_inode = NULL;
+ struct landlock_object *object;
+ struct super_block *sb = inode->i_sb;
- if (!landlock_initialized)
- return;
+ if (!atomic_read(&inode->i_count)) {
+ spin_unlock(&inode->i_lock);
+ return 0;
+ }
- spin_lock(&sb->s_inode_list_lock);
- list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
- struct landlock_object *object;
+ rcu_read_lock();
+ object = rcu_dereference(landlock_inode(inode)->object);
+ if (!object) {
+ rcu_read_unlock();
+ spin_unlock(&inode->i_lock);
+ return 0;
+ }
+ /* Keeps a reference to this inode until the next loop walk. */
+ __iget(inode);
+ spin_unlock(&inode->i_lock);
- /* Only handles referenced inodes. */
- if (!icount_read_once(inode))
- continue;
+ /*
+ * If there is no concurrent release_inode() ongoing, then we
+ * are in charge of calling iput() on this inode, otherwise we
+ * will just wait for it to finish.
+ */
+ spin_lock(&object->lock);
+ if (object->underobj == inode) {
+ object->underobj = NULL;
+ spin_unlock(&object->lock);
+ rcu_read_unlock();
/*
- * Protects against concurrent modification of inode (e.g.
- * from get_inode_object()).
+ * Because object->underobj was not NULL,
+ * release_inode() and get_inode_object() guarantee
+ * that it is safe to reset
+ * landlock_inode(inode)->object while it is not NULL.
+ * It is therefore not necessary to lock inode->i_lock.
*/
- spin_lock(&inode->i_lock);
+ rcu_assign_pointer(landlock_inode(inode)->object, NULL);
/*
- * Checks I_FREEING and I_WILL_FREE to protect against a race
- * condition when release_inode() just called iput(), which
- * could lead to a NULL dereference of inode->security or a
- * second call to iput() for the same Landlock object. Also
- * checks I_NEW because such inode cannot be tied to an object.
+ * At this point, we own the ihold() reference that was
+ * originally set up by get_inode_object() and the
+ * __iget() reference that we just set in this loop
+ * walk. Therefore there are at least two references
+ * on the inode.
*/
- if (inode_state_read(inode) &
- (I_FREEING | I_WILL_FREE | I_NEW)) {
- spin_unlock(&inode->i_lock);
- continue;
- }
+ iput_not_last(inode);
+ } else {
+ spin_unlock(&object->lock);
+ rcu_read_unlock();
+ }
- rcu_read_lock();
- object = rcu_dereference(landlock_inode(inode)->object);
- if (!object) {
- rcu_read_unlock();
- spin_unlock(&inode->i_lock);
- continue;
- }
- /* Keeps a reference to this inode until the next loop walk. */
- __iget(inode);
- spin_unlock(&inode->i_lock);
+ spin_unlock(&sb->s_inode_list_lock);
+ iput(inode);
+ spin_lock(&sb->s_inode_list_lock);
- /*
- * If there is no concurrent release_inode() ongoing, then we
- * are in charge of calling iput() on this inode, otherwise we
- * will just wait for it to finish.
- */
- spin_lock(&object->lock);
- if (object->underobj == inode) {
- object->underobj = NULL;
- spin_unlock(&object->lock);
- rcu_read_unlock();
+ return 0;
+}
- /*
- * Because object->underobj was not NULL,
- * release_inode() and get_inode_object() guarantee
- * that it is safe to reset
- * landlock_inode(inode)->object while it is not NULL.
- * It is therefore not necessary to lock inode->i_lock.
- */
- rcu_assign_pointer(landlock_inode(inode)->object, NULL);
- /*
- * At this point, we own the ihold() reference that was
- * originally set up by get_inode_object() and the
- * __iget() reference that we just set in this loop
- * walk. Therefore there are at least two references
- * on the inode.
- */
- iput_not_last(inode);
- } else {
- spin_unlock(&object->lock);
- rcu_read_unlock();
- }
+/*
+ * Release the inodes used in a security policy.
+ *
+ * Cf. fsnotify_unmount_inodes() and evict_inodes()
+ */
+static void hook_sb_delete(struct super_block *const sb)
+{
+ unsigned int flags = INODE_ITER_NORMAL;
- if (prev_inode) {
- /*
- * At this point, we still own the __iget() reference
- * that we just set in this loop walk. Therefore we
- * can drop the list lock and know that the inode won't
- * disappear from under us until the next loop walk.
- */
- spin_unlock(&sb->s_inode_list_lock);
- /*
- * We can now actually put the inode reference from the
- * previous loop walk, which is not needed anymore.
- */
- iput(prev_inode);
- cond_resched();
- spin_lock(&sb->s_inode_list_lock);
- }
- prev_inode = inode;
- }
- spin_unlock(&sb->s_inode_list_lock);
+ if (!landlock_initialized)
+ return;
+
+ sb_for_each_inodes(sb, flags, hook_sb_delete_inode_iter_cb, NULL);
- /* Puts the inode reference from the last loop walk, if any. */
- if (prev_inode)
- iput(prev_inode);
/* Waits for pending iput() in release_inode(). */
wait_var_event(&landlock_superblock(sb)->inode_refs,
!atomic_long_read(&landlock_superblock(sb)->inode_refs));
|
| 3/2 | 2026/09/09 11:10 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 11997 output: 21 thoughts: 523 Thoughts: **Analyzing the Patch Series: WorthFuzzing = True** Okay, let's dive into this patch series. My initial impression is that it's introducing a new, generic inode iteration mechanism on superblocks. The core of this seems to be the `sb_for_each_inodes()` function, alongside its related iterator functions. It looks like a substantial refactoring, touching a variety of crucial subsystems. Let me break down the changes I'm seeing. This patch updates a bunch of call sites across the VFS layer, the block layer, GFS2, Quota, and even Landlock! Specifically, I'm seeing modifications in `block/bdev.c`, `fs/drop_caches.c`, `fs/gfs2/ops_fstype.c`, `fs/inode.c` (this one's *key*), `fs/quota/dquot.c`, `fs/super.c`, along with updates to include files like `include/linux/fs.h` and `include/linux/fs/super_types.h`, and even a security module, `security/landlock/fs.c`. My primary concern with this kind of change is whether it introduces any vulnerabilities. We know that any modification to core VFS can lead to vulnerabilities, and this definitely touches core VFS: `fs/inode.c`, `fs/super.c`, `fs/drop_caches.c`, `block/bdev.c`, `fs/quota/dquot.c`, `security/landlock/fs.c`. Also, it's introducing a new list traversal/iterator tracking logic (with `s_inodes_iters` and its related functions). This involves locking with `s_inode_list_lock` and `inode->i_lock`, as well as callback semantics and refcount manipulation with `__iget` and `iput`. All of these things are prime candidates for fuzzing, which means the code is *core reachable executable*. This kind of refactoring could reveal subtle race conditions, memory corruption issues, or even logic errors that wouldn't normally appear. Therefore, my conclusion is clear: `WorthFuzzing = true`. I need to dig into this thoroughly. I should probably use `grepper` (or similar tools) to check the configurations and inspect the source code further. This requires careful consideration. |
| 4/2 | 2026/09/09 11:10 | tool | grepper |
0mArgs:{
"Expression": "sb_for_each_inodes"
}Results: {
"Output": "block/bdev.c=1382=void sync_bdevs(bool wait)\nblock/bdev.c-1383-{\nblock/bdev.c:1384:\tsb_for_each_inodes(blockdev_superblock, INODE_ITER_NORMAL,\nblock/bdev.c-1385-\t\t\t sync_bdevs_inode_iter_cb, (void *)wait);\n--\nfs/drop_caches.c=40=static void drop_pagecache_sb(struct super_block *sb, void *unused)\nfs/drop_caches.c-41-{\nfs/drop_caches.c:42:\tsb_for_each_inodes(sb, INODE_ITER_NORMAL, drop_pagecache_inode_iter_cb, NULL);\nfs/drop_caches.c-43-}\n--\nfs/gfs2/ops_fstype.c=1755=static void gfs2_evict_inodes(struct super_block *sb)\n--\nfs/gfs2/ops_fstype.c-1759-\tset_bit(SDF_EVICTING, \u0026sdp-\u003esd_flags);\nfs/gfs2/ops_fstype.c:1760:\tsb_for_each_inodes(sb, INODE_ITER_NORMAL, gfs2_evict_inode_iter_cb, NULL);\nfs/gfs2/ops_fstype.c-1761-}\n--\nfs/inode.c=671=static bool inode_sb_iter_next(struct inode_iter *it, struct super_block *sb)\n--\nfs/inode.c-717-\nfs/inode.c:718:int sb_for_each_inodes(struct super_block *sb, unsigned int flags,\nfs/inode.c-719-\t\t inode_iter_cb fn, void *data)\n--\nfs/inode.c-729-}\nfs/inode.c:730:EXPORT_SYMBOL(sb_for_each_inodes);\nfs/inode.c-731-\n--\nfs/inode.c=982=void evict_inodes(struct super_block *sb)\n--\nfs/inode.c-986-\nfs/inode.c:987:\tsb_for_each_inodes(sb, flags, evict_inodes_inode_iter_cb, \u0026dispose);\nfs/inode.c-988-\tdispose_list(\u0026dispose);\n--\nfs/quota/dquot.c=1082=static int add_dquot_ref(struct super_block *sb, int type)\nfs/quota/dquot.c-1083-{\nfs/quota/dquot.c:1084:\treturn sb_for_each_inodes(sb, INODE_ITER_NORMAL,\nfs/quota/dquot.c-1085-\t\t\t\t add_dquot_ref_inode_iter_cb, \u0026type);\n--\ninclude/linux/fs.h=883=typedef int (*inode_iter_cb) (struct inode *, void *);\ninclude/linux/fs.h-884-\ninclude/linux/fs.h:885:int sb_for_each_inodes(struct super_block *sb, unsigned int flags,\ninclude/linux/fs.h-886-\t\t inode_iter_cb fn, void *data);\n--\nsecurity/landlock/fs.c=1437=static void hook_sb_delete(struct super_block *const sb)\n--\nsecurity/landlock/fs.c-1443-\nsecurity/landlock/fs.c:1444:\tsb_for_each_inodes(sb, flags, hook_sb_delete_inode_iter_cb, NULL);\nsecurity/landlock/fs.c-1445-\n"
} |
| 5/2 | 2026/09/09 11:10 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 13985 output: 33 thoughts: 20 Thoughts: Here's my take on those thoughts, tailored for an expert audience: **Diving into the Inode Iterator** Okay, so I need to refresh my memory on how the iterator for inodes in `fs/inode.c` actually works. I want to specifically understand its implementation and, more importantly, how it's being *used*. Is it a classic iterator pattern, or is there some custom spin on it to optimize for file system access patterns? I'm particularly interested in any potential concurrency implications – does the iteration handle locking correctly across multiple threads or processes accessing the inode table? Are there any potential performance bottlenecks that spring to mind when seeing how it's laid out? I need to analyze its usage context to determine the type of access it is being used for, and the impact of the iterators performance in these situations. Time to start digging... |
| 6/2 | 2026/09/09 11:10 | tool | read-file |
0mArgs:{
"File": "fs/inode.c",
"FirstLine": 645,
"LineCount": 85
}Results: {
"Contents": " 645:\t\tstruct super_block *sb = inode-\u003ei_sb;\n 646:\t\n 647:\t\tspin_lock(\u0026sb-\u003es_inode_list_lock);\n 648:\t\tlist_add(\u0026inode-\u003ei_sb_list, \u0026sb-\u003es_inodes);\n 649:\t\tspin_unlock(\u0026sb-\u003es_inode_list_lock);\n 650:\t}\n 651:\tEXPORT_SYMBOL_GPL(inode_sb_list_add);\n 652:\t\n 653:\tstatic void inode_sb_iter_start(struct super_block *sb, struct inode_iter *it,\n 654:\t\t\t\t\tunsigned int flags, inode_iter_cb fn, void *data)\n 655:\t{\n 656:\t\tit-\u003eflags = flags;\n 657:\t\tit-\u003efunc = fn;\n 658:\t\tit-\u003edata = data;\n 659:\t\tit-\u003eret = 0;\n 660:\t\tspin_lock(\u0026sb-\u003es_inode_list_lock);\n 661:\t\tit-\u003enext = sb-\u003es_inodes.next;\n 662:\t\tlist_add(\u0026it-\u003eiters_node, \u0026sb-\u003es_inodes_iters);\n 663:\t}\n 664:\t\n 665:\tstatic void inode_sb_iter_end(struct inode_iter *it, struct super_block *sb)\n 666:\t{\n 667:\t\tlist_del(\u0026it-\u003eiters_node);\n 668:\t\tspin_unlock(\u0026sb-\u003es_inode_list_lock);\n 669:\t}\n 670:\t\n 671:\tstatic bool inode_sb_iter_next(struct inode_iter *it, struct super_block *sb)\n 672:\t{\n 673:\t\tstruct inode *inode = NULL;\n 674:\t\tint ret;\n 675:\t\n 676:\t\twhile (!inode \u0026\u0026 it-\u003enext != \u0026sb-\u003es_inodes) {\n 677:\t\t\tinode = list_entry(it-\u003enext, struct inode, i_sb_list);\n 678:\t\t\tif (it-\u003eflags \u0026 INODE_ITER_UNUSED) {\n 679:\t\t\t\tif (icount_read_once(inode)) {\n 680:\t\t\t\t\tit-\u003enext = it-\u003enext-\u003enext;\n 681:\t\t\t\t\tcontinue;\n 682:\t\t\t\t}\n 683:\t\n 684:\t\t\t\tspin_lock(\u0026inode-\u003ei_lock);\n 685:\t\t\t\tif (icount_read(inode)) {\n 686:\t\t\t\t\tspin_unlock(\u0026inode-\u003ei_lock);\n 687:\t\t\t\t\tit-\u003enext = it-\u003enext-\u003enext;\n 688:\t\t\t\t\tcontinue;\n 689:\t\t\t\t}\n 690:\t\t\t} else {\n 691:\t\t\t\tspin_lock(\u0026inode-\u003ei_lock);\n 692:\t\t\t}\n 693:\t\n 694:\t\t\tif ((it-\u003eflags \u0026 INODE_ITER_NORMAL) \u0026\u0026\n 695:\t\t\t (inode_state_read(inode) \u0026 (I_NEW | I_FREEING | I_WILL_FREE))) {\n 696:\t\t\t\tspin_unlock(\u0026inode-\u003ei_lock);\n 697:\t\t\t\tit-\u003enext = it-\u003enext-\u003enext;\n 698:\t\t\t\tcontinue;\n 699:\t\t\t}\n 700:\t\n 701:\t\t\tit-\u003enext = it-\u003enext-\u003enext;\n 702:\t\t\tret = it-\u003efunc(inode, it-\u003edata);\n 703:\t\t\tif (ret) {\n 704:\t\t\t\tit-\u003eret = ret;\n 705:\t\t\t\treturn false;\n 706:\t\t\t}\n 707:\t\n 708:\t\t\tif (need_resched()) {\n 709:\t\t\t\tspin_unlock(\u0026sb-\u003es_inode_list_lock);\n 710:\t\t\t\tcond_resched();\n 711:\t\t\t\tspin_lock(\u0026sb-\u003es_inode_list_lock);\n 712:\t\t\t}\n 713:\t\t}\n 714:\t\n 715:\t\treturn it-\u003enext == \u0026sb-\u003es_inodes ? false : true;\n 716:\t}\n 717:\t\n 718:\tint sb_for_each_inodes(struct super_block *sb, unsigned int flags,\n 719:\t\t\t inode_iter_cb fn, void *data)\n 720:\t{\n 721:\t\tstruct inode_iter it;\n 722:\t\n 723:\t\tinode_sb_iter_start(sb, \u0026it, flags, fn, data);\n 724:\t\twhile (inode_sb_iter_next(\u0026it, sb))\n 725:\t\t\t;\n 726:\t\tinode_sb_iter_end(\u0026it, sb);\n 727:\t\n 728:\t\treturn it.ret;\n 729:\t}\n"
} |
| 7/2 | 2026/09/09 11:10 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 15480 output: 38 thoughts: 542 Thoughts: **Diving into this new iterator's intricacies** Okay, so I'm looking closely at `sb_for_each_inodes`. Right away, the `inode_sb_iter_next()` function grabs an `inode` from a list using `list_entry`. It checks flags, potentially acquires `inode->i_lock` (depending on the code path), and then calls `it->func` with the `inode` and `it->data`. Standard stuff, but the interaction with the locking is where it gets interesting. Now, `inode_sb_list_del` in `fs/inode.c`. I see it removing an inode from the superblock's inode list. And here's the kicker: it iterates *through* the `s_inodes_iters` list on the superblock. If it finds an iterator pointing at the inode being deleted, it updates `it->next`! This is critical. During those iteration callbacks, the code drops `inode->i_lock` and `sb->s_inode_list_lock`, does some work (things like `__iget`, `iput`, even `invalidate_mapping_pages`, and file operations), and then reacquires `sb->s_inode_list_lock` before returning. The implications are huge. An inode can be deleted *while* the lock is dropped. When that happens, `inode_sb_list_del` *updates the iterator's next pointer*, so that it isn't orphaned. It's cleverly handling concurrent modification during iteration. This iterator mechanism is brand new and touches superblock inode list locking, refcounting, and traversal across VFS, quota, Landlock, GFS2, and even block devices! So, which configurations are likely to be relevant? `fs/inode.c`, `fs/super.c`, `block/bdev.c`, and `fs/drop_caches.c` are always there. But what about the modules? `fs/quota/dquot.c` depends on `CONFIG_QUOTA`, `fs/gfs2` depends on `CONFIG_GFS2_FS`, and `security/landlock` depends on `CONFIG_SECURITY_LANDLOCK`. I'm going to quickly check the Kconfig files for Quota, Landlock, and GFS2 just to confirm these dependencies and make sure I'm not missing anything obvious. This requires more thorough analysis, but this iterator seems novel and highly relevant to concurrent filesystem structures. |
| 8/2 | 2026/09/09 11:10 | tool | grepper |
0mArgs:{
"Expression": "config (QUOTA|SECURITY_LANDLOCK|GFS2_FS)\\b",
"PathPrefix": "fs/"
}Results: {
"Output": "fs/gfs2/Kconfig-1-# SPDX-License-Identifier: GPL-2.0-only\nfs/gfs2/Kconfig:2:config GFS2_FS\nfs/gfs2/Kconfig-3-\ttristate \"GFS2 file system support\"\n--\nfs/quota/Kconfig-5-\nfs/quota/Kconfig:6:config QUOTA\nfs/quota/Kconfig-7-\tbool \"Quota support\"\n"
} |
| 9/2 | 2026/09/09 11:10 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 16711 output: 27 thoughts: 13 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience: **Kernel Security Exploration** Alright, before I dive too deep into this problem, I need to take a step back and make sure I'm not missing anything obvious. Security is always paramount, and landlock is a likely area to scrutinize. I need to quickly assess if the issue intersects with landlock's capabilities. Specifically, does something change at the landlock level that might contribute? It's easy to get tunnel vision when debugging. Then, of course, I should look at the Kconfig options that might influence the kernel configuration. Are there any relevant security features that are disabled or enabled in a way that could affect this situation? Maybe I can change them to verify the behaviour. I need to make sure I'm not working with an unusual, or non-default, build before I start chasing ghosts. |
| 10/2 | 2026/09/09 11:11 | tool | grepper |
0mArgs:{
"Expression": "config SECURITY_LANDLOCK",
"PathPrefix": "security/"
}Results: {
"Output": "security/landlock/Kconfig-2-\nsecurity/landlock/Kconfig:3:config SECURITY_LANDLOCK\nsecurity/landlock/Kconfig-4-\tbool \"Landlock support\"\n--\nsecurity/landlock/Kconfig-23-\nsecurity/landlock/Kconfig:24:config SECURITY_LANDLOCK_LOG\nsecurity/landlock/Kconfig-25-\tbool\n--\nsecurity/landlock/Kconfig-28-\nsecurity/landlock/Kconfig:29:config SECURITY_LANDLOCK_KUNIT_TEST\nsecurity/landlock/Kconfig-30-\tbool \"KUnit tests for Landlock\" if !KUNIT_ALL_TESTS\n"
} |
| 11/2 | 2026/09/09 11:11 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 17110 output: 231 thoughts: 893 Thoughts: **Fuzzing Strategy for Kernel Patch Analysis** Alright, let's break this down. My goal is to identify and justify fuzzing this patch. First, I need to pinpoint the kernel functions introduced or significantly altered. Looks like the patch touches several areas: block devices (`bdev.c`), page cache (`drop_caches.c`), GFS2 filesystems (`gfs2/ops_fstype.c`), general inodes (`inode.c`), quotas (`quota/dquot.c`), and Landlock security (`security/landlock/fs.c`). I've extracted the key functions from the patch that seem relevant. I've specifically targeted functions that are *not* ubiquitous, low-level kernel routines. I'm looking for entry points or iterators that directly exercise the changed code. Based on my analysis, these functions stand out: `sb_for_each_inodes`, `evict_inodes`, `drop_pagecache_sb`, `sync_bdevs`, `add_dquot_ref`, `gfs2_evict_inodes`, and `hook_sb_delete`. Now, these aren't your typical lifecycle hot-paths; they're very specific VFS, filesystem, or security entry points and iterators. So, I know they're not going to be massively called everywhere, but they likely are entry points that will exercise this code. I've also identified the relevant kernel configurations that are affected: `QUOTA`, `GFS2_FS`, and `SECURITY_LANDLOCK`. Now, to solidify this, I need to articulate why this warrants fuzzing. This patch is refactoring superblock inode list iteration across multiple areas, introducing `sb_for_each_inodes` and an active iterator mechanism (`s_inodes_iters`). This change implies alterations to locking, list traversal, and concurrent inode deletion/eviction semantics. That's a huge red flag: race conditions, list corruption, and locking issues are all likely here. Given all this, I'm confident in saying that this patch is worth fuzzing. I've gathered a clear reasoning, identified the functions, and listed relevant configurations. I can now go ahead and call `set-results`. I am ready to move on. |
| 12/2 | 2026/09/09 11:11 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"QUOTA",
"GFS2_FS",
"SECURITY_LANDLOCK"
],
"FocusSymbols": [
"sb_for_each_inodes",
"evict_inodes",
"sync_bdevs",
"drop_pagecache_sb",
"add_dquot_ref",
"gfs2_evict_inodes",
"hook_sb_delete"
],
"Reasoning": "The patch introduces a new superblock inode iteration framework (sb_for_each_inodes) with dynamic iterator list tracking (s_inodes_iters) and updates traversal loops in VFS (evict_inodes, drop_caches), block devices (sync_bdevs), quotas (add_dquot_ref), GFS2 (gfs2_evict_inodes), and Landlock (hook_sb_delete). This significantly alters locking, list mutation during traversal, and refcounting across core subsystems, making it worth fuzzing for races, list corruptions, or deadlocks.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"QUOTA",
"GFS2_FS",
"SECURITY_LANDLOCK"
],
"FocusSymbols": [
"sb_for_each_inodes",
"evict_inodes",
"sync_bdevs",
"drop_pagecache_sb",
"add_dquot_ref",
"gfs2_evict_inodes",
"hook_sb_delete"
],
"Reasoning": "The patch introduces a new superblock inode iteration framework (sb_for_each_inodes) with dynamic iterator list tracking (s_inodes_iters) and updates traversal loops in VFS (evict_inodes, drop_caches), block devices (sync_bdevs), quotas (add_dquot_ref), GFS2 (gfs2_evict_inodes), and Landlock (hook_sb_delete). This significantly alters locking, list mutation during traversal, and refcounting across core subsystems, making it worth fuzzing for races, list corruptions, or deadlocks.",
"WorthFuzzing": true
} |
| 13/1 | 2026/09/09 11:11 | action | If |
0mArgs:{
"WorthFuzzing": true
}Results: null |
| 14/2 | 2026/09/09 11:11 | agent | kmsan-evaluator |
0mModel:core Args: null Results: {
"KMSANReasoning": "The patch refactors the internal superblock inode iteration mechanism across several subsystems (block, drop_caches, gfs2, vfs inode, quota, landlock) by introducing a generic helper `sb_for_each_inodes()` and iterator structure `struct inode_iter`.\n\n- Uninitialized memory / KMSAN perspective:\n - The newly introduced stack-allocated `struct inode_iter` in `sb_for_each_inodes()` is fully initialized in `inode_sb_iter_start()`.\n - All callback data payloads passed to the iterator functions are either NULL or pointers to fully initialized variables/structures (e.g., boolean cast, initialized linked list head, integer pointer).\n - No kernel data is copied or exposed to user space (no `copy_to_user`, ioctls, netlink messages, etc.).\n - No uninitialized memory reads or conditional branching on uninitialized fields are introduced.\n\n- KASAN / LOCKDEP perspective:\n - Potential issues introduced by this patch relate to list manipulation, refcounting (`__iget`/`iput`), locking semantics under `sb-\u003es_inode_list_lock` and `inode-\u003ei_lock`, or use-after-free/race conditions if list nodes are deleted during traversal. These are fully covered by KASAN, LOCKDEP, and `DEBUG_LIST`.\n\nTherefore, there is no risk of uninitialized memory usage or info-leaks, and 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 b46e3d96c9a1710720b83b39e9ffa126f9880d65
Author: syz-cluster <triage@syzkaller.com>
Date: Wed Sep 9 11:10:41 2026 +0000
syz-cluster: applied patch under review
diff --git a/block/bdev.c b/block/bdev.c
index cd83230837406..ae472a062e51c 100644
--- a/block/bdev.c
+++ b/block/bdev.c
@@ -1336,56 +1336,53 @@ void bdev_mark_dead(struct block_device *bdev, bool surprise)
*/
EXPORT_SYMBOL_GPL(bdev_mark_dead);
-void sync_bdevs(bool wait)
+static int sync_bdevs_inode_iter_cb(struct inode *inode, void *data)
{
- struct inode *inode, *old_inode = NULL;
-
- spin_lock(&blockdev_superblock->s_inode_list_lock);
- list_for_each_entry(inode, &blockdev_superblock->s_inodes, i_sb_list) {
- struct address_space *mapping = inode->i_mapping;
- struct block_device *bdev;
+ bool wait = (bool)data;
+ struct block_device *bdev;
+ struct address_space *mapping = inode->i_mapping;
- spin_lock(&inode->i_lock);
- if (inode_state_read(inode) & (I_FREEING | I_WILL_FREE | I_NEW) ||
- mapping->nrpages == 0) {
- spin_unlock(&inode->i_lock);
- continue;
- }
- __iget(inode);
+ if (mapping->nrpages == 0) {
spin_unlock(&inode->i_lock);
- spin_unlock(&blockdev_superblock->s_inode_list_lock);
+ return 0;
+ }
+
+ /*
+ * We hold a reference to 'inode' so it couldn't have been
+ * removed from s_inodes list while we dropped the
+ * s_inode_list_lock.
+ */
+ __iget(inode);
+ spin_unlock(&inode->i_lock);
+ spin_unlock(&blockdev_superblock->s_inode_list_lock);
+ bdev = I_BDEV(inode);
+
+ mutex_lock(&bdev->bd_disk->open_mutex);
+ if (!atomic_read(&bdev->bd_openers)) {
+ ; /* skip */
+ } else if (wait) {
/*
- * We hold a reference to 'inode' so it couldn't have been
- * removed from s_inodes list while we dropped the
- * s_inode_list_lock We cannot iput the inode now as we can
- * be holding the last reference and we cannot iput it under
- * s_inode_list_lock. So we keep the reference and iput it
- * later.
+ * We keep the error status of individual mapping so
+ * that applications can catch the writeback error using
+ * fsync(2). See filemap_fdatawait_keep_errors() for
+ * details.
*/
- iput(old_inode);
- old_inode = inode;
- bdev = I_BDEV(inode);
-
- mutex_lock(&bdev->bd_disk->open_mutex);
- if (!atomic_read(&bdev->bd_openers)) {
- ; /* skip */
- } else if (wait) {
- /*
- * We keep the error status of individual mapping so
- * that applications can catch the writeback error using
- * fsync(2). See filemap_fdatawait_keep_errors() for
- * details.
- */
- filemap_fdatawait_keep_errors(inode->i_mapping);
- } else {
- filemap_fdatawrite(inode->i_mapping);
- }
- mutex_unlock(&bdev->bd_disk->open_mutex);
-
- spin_lock(&blockdev_superblock->s_inode_list_lock);
+ filemap_fdatawait_keep_errors(inode->i_mapping);
+ } else {
+ filemap_fdatawrite(inode->i_mapping);
}
- spin_unlock(&blockdev_superblock->s_inode_list_lock);
- iput(old_inode);
+ mutex_unlock(&bdev->bd_disk->open_mutex);
+ iput(inode);
+
+ spin_lock(&blockdev_superblock->s_inode_list_lock);
+
+ return 0;
+}
+
+void sync_bdevs(bool wait)
+{
+ sb_for_each_inodes(blockdev_superblock, INODE_ITER_NORMAL,
+ sync_bdevs_inode_iter_cb, (void *)wait);
}
/*
diff --git a/fs/drop_caches.c b/fs/drop_caches.c
index 49f56a598ecbc..0d475a5ff8cfb 100644
--- a/fs/drop_caches.c
+++ b/fs/drop_caches.c
@@ -16,36 +16,30 @@
/* A global variable is a bit ugly, but it keeps the code simple */
static int sysctl_drop_caches;
-static void drop_pagecache_sb(struct super_block *sb, void *unused)
+static int drop_pagecache_inode_iter_cb(struct inode *inode, void *unused)
{
- struct inode *inode, *toput_inode = NULL;
+ struct super_block *sb = inode->i_sb;
- spin_lock(&sb->s_inode_list_lock);
- list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
- spin_lock(&inode->i_lock);
- /*
- * We must skip inodes in unusual state. We may also skip
- * inodes without pages but we deliberately won't in case
- * we need to reschedule to avoid softlockups.
- */
- if ((inode_state_read(inode) & (I_FREEING | I_WILL_FREE | I_NEW)) ||
- (mapping_empty(inode->i_mapping) && !need_resched())) {
- spin_unlock(&inode->i_lock);
- continue;
- }
- __iget(inode);
+ if (mapping_empty(inode->i_mapping)) {
spin_unlock(&inode->i_lock);
- spin_unlock(&sb->s_inode_list_lock);
-
- invalidate_mapping_pages(inode->i_mapping, 0, -1);
- iput(toput_inode);
- toput_inode = inode;
-
- cond_resched();
- spin_lock(&sb->s_inode_list_lock);
+ return 0;
}
+
+ __iget(inode);
+ spin_unlock(&inode->i_lock);
spin_unlock(&sb->s_inode_list_lock);
- iput(toput_inode);
+
+ invalidate_mapping_pages(inode->i_mapping, 0, -1);
+ iput(inode);
+
+ spin_lock(&sb->s_inode_list_lock);
+
+ return 0;
+}
+
+static void drop_pagecache_sb(struct super_block *sb, void *unused)
+{
+ sb_for_each_inodes(sb, INODE_ITER_NORMAL, drop_pagecache_inode_iter_cb, NULL);
}
static int drop_caches_sysctl_handler(const struct ctl_table *table, int write,
diff --git a/fs/gfs2/ops_fstype.c b/fs/gfs2/ops_fstype.c
index 718e0da7dfce0..acdbf9ca44a3f 100644
--- a/fs/gfs2/ops_fstype.c
+++ b/fs/gfs2/ops_fstype.c
@@ -996,7 +996,7 @@ static int gfs2_lm_mount(struct gfs2_sbd *sdp, int silent)
switch (token) {
case Opt_jid:
ret = match_int(&tmp[0], &option);
- if (ret || option < 0)
+ if (ret || option < 0)
goto hostdata_error;
if (test_and_clear_bit(SDF_NOJOURNALID, &sdp->sd_flags))
ls->ls_jid = option;
@@ -1719,6 +1719,19 @@ static int gfs2_meta_init_fs_context(struct fs_context *fc)
return 0;
}
+static int gfs2_evict_inode_iter_cb(struct inode *inode, void *unused)
+{
+ struct super_block *sb = inode->i_sb;
+
+ __iget(inode);
+ spin_unlock(&inode->i_lock);
+ spin_unlock(&sb->s_inode_list_lock);
+
+ iput(inode);
+ spin_lock(&sb->s_inode_list_lock);
+ return 0;
+}
+
/**
* gfs2_evict_inodes - evict inodes cooperatively
* @sb: the superblock
@@ -1741,31 +1754,10 @@ static int gfs2_meta_init_fs_context(struct fs_context *fc)
*/
static void gfs2_evict_inodes(struct super_block *sb)
{
- struct inode *inode, *toput_inode = NULL;
struct gfs2_sbd *sdp = sb->s_fs_info;
set_bit(SDF_EVICTING, &sdp->sd_flags);
-
- spin_lock(&sb->s_inode_list_lock);
- list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
- spin_lock(&inode->i_lock);
- if ((inode_state_read(inode) & (I_FREEING | I_WILL_FREE | I_NEW)) &&
- !need_resched()) {
- spin_unlock(&inode->i_lock);
- continue;
- }
- __iget(inode);
- spin_unlock(&inode->i_lock);
- spin_unlock(&sb->s_inode_list_lock);
-
- iput(toput_inode);
- toput_inode = inode;
-
- cond_resched();
- spin_lock(&sb->s_inode_list_lock);
- }
- spin_unlock(&sb->s_inode_list_lock);
- iput(toput_inode);
+ sb_for_each_inodes(sb, INODE_ITER_NORMAL, gfs2_evict_inode_iter_cb, NULL);
}
static void gfs2_kill_sb(struct super_block *sb)
diff --git a/fs/inode.c b/fs/inode.c
index ba7da39be4a31..07a5f48641af8 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -69,6 +69,15 @@ const struct address_space_operations empty_aops = {
};
EXPORT_SYMBOL(empty_aops);
+struct inode_iter {
+ struct list_head iters_node; /* sb->s_inodes_iters */
+ struct list_head *next; /* next node going to iterate */
+ unsigned int flags;
+ inode_iter_cb func;
+ void *data;
+ int ret;
+};
+
static DEFINE_PER_CPU(unsigned long, nr_inodes);
static DEFINE_PER_CPU(unsigned long, nr_unused);
@@ -641,12 +650,96 @@ void inode_sb_list_add(struct inode *inode)
}
EXPORT_SYMBOL_GPL(inode_sb_list_add);
+static void inode_sb_iter_start(struct super_block *sb, struct inode_iter *it,
+ unsigned int flags, inode_iter_cb fn, void *data)
+{
+ it->flags = flags;
+ it->func = fn;
+ it->data = data;
+ it->ret = 0;
+ spin_lock(&sb->s_inode_list_lock);
+ it->next = sb->s_inodes.next;
+ list_add(&it->iters_node, &sb->s_inodes_iters);
+}
+
+static void inode_sb_iter_end(struct inode_iter *it, struct super_block *sb)
+{
+ list_del(&it->iters_node);
+ spin_unlock(&sb->s_inode_list_lock);
+}
+
+static bool inode_sb_iter_next(struct inode_iter *it, struct super_block *sb)
+{
+ struct inode *inode = NULL;
+ int ret;
+
+ while (!inode && it->next != &sb->s_inodes) {
+ inode = list_entry(it->next, struct inode, i_sb_list);
+ if (it->flags & INODE_ITER_UNUSED) {
+ if (icount_read_once(inode)) {
+ it->next = it->next->next;
+ continue;
+ }
+
+ spin_lock(&inode->i_lock);
+ if (icount_read(inode)) {
+ spin_unlock(&inode->i_lock);
+ it->next = it->next->next;
+ continue;
+ }
+ } else {
+ spin_lock(&inode->i_lock);
+ }
+
+ if ((it->flags & INODE_ITER_NORMAL) &&
+ (inode_state_read(inode) & (I_NEW | I_FREEING | I_WILL_FREE))) {
+ spin_unlock(&inode->i_lock);
+ it->next = it->next->next;
+ continue;
+ }
+
+ it->next = it->next->next;
+ ret = it->func(inode, it->data);
+ if (ret) {
+ it->ret = ret;
+ return false;
+ }
+
+ if (need_resched()) {
+ spin_unlock(&sb->s_inode_list_lock);
+ cond_resched();
+ spin_lock(&sb->s_inode_list_lock);
+ }
+ }
+
+ return it->next == &sb->s_inodes ? false : true;
+}
+
+int sb_for_each_inodes(struct super_block *sb, unsigned int flags,
+ inode_iter_cb fn, void *data)
+{
+ struct inode_iter it;
+
+ inode_sb_iter_start(sb, &it, flags, fn, data);
+ while (inode_sb_iter_next(&it, sb))
+ ;
+ inode_sb_iter_end(&it, sb);
+
+ return it.ret;
+}
+EXPORT_SYMBOL(sb_for_each_inodes);
+
static inline void inode_sb_list_del(struct inode *inode)
{
struct super_block *sb = inode->i_sb;
+ struct inode_iter *it;
if (!list_empty(&inode->i_sb_list)) {
spin_lock(&sb->s_inode_list_lock);
+ list_for_each_entry(it, &sb->s_inodes_iters, iters_node) {
+ if (it->next == &inode->i_sb_list)
+ it->next = inode->i_sb_list.next;
+ }
list_del_init(&inode->i_sb_list);
spin_unlock(&sb->s_inode_list_lock);
}
@@ -866,6 +959,17 @@ static void dispose_list(struct list_head *head)
}
}
+static int evict_inodes_inode_iter_cb(struct inode *inode, void *data)
+{
+ struct list_head *dispose = (struct list_head *)data;
+
+ inode_state_set(inode, I_FREEING);
+ inode_lru_list_del(inode);
+ spin_unlock(&inode->i_lock);
+ list_add(&inode->i_lru, dispose);
+ return 0;
+}
+
/**
* evict_inodes - evict all evictable inodes for a superblock
* @sb: superblock to operate on
@@ -877,44 +981,10 @@ static void dispose_list(struct list_head *head)
*/
void evict_inodes(struct super_block *sb)
{
- struct inode *inode;
LIST_HEAD(dispose);
+ unsigned int flags = INODE_ITER_NORMAL | INODE_ITER_UNUSED;
-again:
- spin_lock(&sb->s_inode_list_lock);
- list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
- if (icount_read_once(inode))
- continue;
-
- spin_lock(&inode->i_lock);
- if (icount_read(inode)) {
- spin_unlock(&inode->i_lock);
- continue;
- }
- if (inode_state_read(inode) & (I_NEW | I_FREEING | I_WILL_FREE)) {
- spin_unlock(&inode->i_lock);
- continue;
- }
-
- inode_state_set(inode, I_FREEING);
- inode_lru_list_del(inode);
- spin_unlock(&inode->i_lock);
- list_add(&inode->i_lru, &dispose);
-
- /*
- * We can have a ton of inodes to evict at unmount time given
- * enough memory, check to see if we need to go to sleep for a
- * bit so we don't livelock.
- */
- if (need_resched()) {
- spin_unlock(&sb->s_inode_list_lock);
- cond_resched();
- dispose_list(&dispose);
- goto again;
- }
- }
- spin_unlock(&sb->s_inode_list_lock);
-
+ sb_for_each_inodes(sb, flags, evict_inodes_inode_iter_cb, &dispose);
dispose_list(&dispose);
}
EXPORT_SYMBOL_GPL(evict_inodes);
diff --git a/fs/quota/dquot.c b/fs/quota/dquot.c
index 204afc5e984bc..77c68149d4ae4 100644
--- a/fs/quota/dquot.c
+++ b/fs/quota/dquot.c
@@ -1043,64 +1043,48 @@ static int dqinit_needed(struct inode *inode, int type)
return 0;
}
-/* This routine is guarded by s_umount semaphore */
-static int add_dquot_ref(struct super_block *sb, int type)
+static int add_dquot_ref_inode_iter_cb(struct inode *inode, void *data)
{
- struct inode *inode, *old_inode = NULL;
+ int type = *(int *)data;
+ int err = 0;
+ struct super_block *sb = inode->i_sb;
#ifdef CONFIG_QUOTA_DEBUG
int reserved = 0;
#endif
- int err = 0;
- spin_lock(&sb->s_inode_list_lock);
- list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
- spin_lock(&inode->i_lock);
- if ((inode_state_read(inode) & (I_FREEING | I_WILL_FREE | I_NEW)) ||
- !atomic_read(&inode->i_writecount) ||
- !dqinit_needed(inode, type)) {
- spin_unlock(&inode->i_lock);
- continue;
- }
- __iget(inode);
+ if (!atomic_read(&inode->i_writecount) ||
+ !dqinit_needed(inode, type)) {
spin_unlock(&inode->i_lock);
- spin_unlock(&sb->s_inode_list_lock);
+ return 0;
+ }
+
+ __iget(inode);
+ spin_unlock(&inode->i_lock);
+ spin_unlock(&sb->s_inode_list_lock);
#ifdef CONFIG_QUOTA_DEBUG
- if (unlikely(inode_get_rsv_space(inode) > 0))
- reserved = 1;
+ if (unlikely(inode_get_rsv_space(inode) > 0))
+ reserved = 1;
#endif
- iput(old_inode);
- err = __dquot_initialize(inode, type);
- if (err) {
- iput(inode);
- goto out;
- }
-
- /*
- * We hold a reference to 'inode' so it couldn't have been
- * removed from s_inodes list while we dropped the
- * s_inode_list_lock. We cannot iput the inode now as we can be
- * holding the last reference and we cannot iput it under
- * s_inode_list_lock. So we keep the reference and iput it
- * later.
- */
- old_inode = inode;
- cond_resched();
- spin_lock(&sb->s_inode_list_lock);
- }
- spin_unlock(&sb->s_inode_list_lock);
- iput(old_inode);
-out:
+ err = __dquot_initialize(inode, type);
#ifdef CONFIG_QUOTA_DEBUG
- if (reserved) {
- quota_error(sb, "Writes happened before quota was turned on "
- "thus quota information is probably inconsistent. "
- "Please run quotacheck(8)");
- }
+ if (reserved)
+ quota_error(sb, "Writes happened before quota was turned "
+ "on thus quota information is probably "
+ "inconsistent. Please run quotacheck(8)");
#endif
+ iput(inode);
+ spin_lock(&sb->s_inode_list_lock);
return err;
}
+/* This routine is guarded by s_umount semaphore */
+static int add_dquot_ref(struct super_block *sb, int type)
+{
+ return sb_for_each_inodes(sb, INODE_ITER_NORMAL,
+ add_dquot_ref_inode_iter_cb, &type);
+}
+
static void remove_dquot_ref(struct super_block *sb, int type)
{
struct inode *inode;
diff --git a/fs/super.c b/fs/super.c
index 05e4431730387..3e069150c5445 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -382,6 +382,7 @@ static struct super_block *alloc_super(struct file_system_type *type, int flags,
spin_lock_init(&s->s_roots_lock);
mutex_init(&s->s_sync_lock);
INIT_LIST_HEAD(&s->s_inodes);
+ INIT_LIST_HEAD(&s->s_inodes_iters);
spin_lock_init(&s->s_inode_list_lock);
INIT_LIST_HEAD(&s->s_inodes_wb);
spin_lock_init(&s->s_inode_wblist_lock);
diff --git a/include/linux/fs.h b/include/linux/fs.h
index f9d1e05e8ae68..f3176ab10e65f 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -296,7 +296,7 @@ struct iattr {
*/
#define FILESYSTEM_MAX_STACK_DEPTH 2
-/**
+/**
* enum positive_aop_returns - aop return codes with specific semantics
*
* @AOP_WRITEPAGE_ACTIVATE: Informs the caller that page writeback has
@@ -306,7 +306,7 @@ struct iattr {
* be a candidate for writeback again in the near
* future. Other callers must be careful to unlock
* the page if they get this return. Returned by
- * writepage();
+ * writepage();
*
* @AOP_TRUNCATED_PAGE: The AOP method that was handed a locked page has
* unlocked it and the page might have been truncated.
@@ -870,6 +870,21 @@ struct inode {
void *i_private; /* fs or device private pointer */
} __randomize_layout;
+enum inode_iter_flags_enum {
+ INODE_ITER_NORMAL = (1U << 1), /* Exclude inodes with (I_NEW | I_FREEING | I_WILL_FREE). */
+ INODE_ITER_UNUSED = (1U << 2), /* Only return inodes with (i_count == 0). */
+};
+
+/*
+ * start end
+ * inode->i_lock locked unlocked
+ * sb->s_inode_list_lock locked locked
+ */
+typedef int (*inode_iter_cb) (struct inode *, void *);
+
+int sb_for_each_inodes(struct super_block *sb, unsigned int flags,
+ inode_iter_cb fn, void *data);
+
/*
* i_state handling
*
@@ -1315,8 +1330,10 @@ struct file *get_file_active(struct file **f);
#define MAX_NON_LFS ((1UL<<31) - 1)
-/* Page cache limit. The filesystems should put that into their s_maxbytes
- limits, otherwise bad things can happen in VM. */
+/*
+ * Page cache limit. The filesystems should put that into their s_maxbytes
+ * limits, otherwise bad things can happen in VM.
+ */
#if BITS_PER_LONG==32
#define MAX_LFS_FILESIZE ((loff_t)ULONG_MAX << PAGE_SHIFT)
#elif BITS_PER_LONG==64
@@ -2285,7 +2302,7 @@ int sync_inode_metadata(struct inode *inode, int wait);
struct file_system_type {
const char *name;
int fs_flags;
-#define FS_REQUIRES_DEV 1
+#define FS_REQUIRES_DEV 1
#define FS_BINARY_MOUNTDATA 2
#define FS_HAS_SUBTYPE 4
#define FS_USERNS_MOUNT 8 /* Can be mounted by userns root */
@@ -2907,7 +2924,7 @@ ssize_t __kernel_read(struct file *file, void *buf, size_t count, loff_t *pos);
extern ssize_t kernel_write(struct file *, const void *, size_t, loff_t *);
extern ssize_t __kernel_write(struct file *, const void *, size_t, loff_t *);
extern struct file * open_exec(const char *);
-
+
/* fs/dcache.c -- generic fs support functions */
extern bool is_subdir(struct dentry *, struct dentry *);
extern bool path_is_under(const struct path *, const struct path *);
diff --git a/include/linux/fs/super_types.h b/include/linux/fs/super_types.h
index ecd96aeb1cee7..1f81cc219b8ee 100644
--- a/include/linux/fs/super_types.h
+++ b/include/linux/fs/super_types.h
@@ -269,9 +269,10 @@ struct super_block {
*/
int s_stack_depth;
- /* s_inode_list_lock protects s_inodes */
+ /* s_inode_list_lock protects s_inodes and s_inodes_iters */
spinlock_t s_inode_list_lock ____cacheline_aligned_in_smp;
struct list_head s_inodes; /* all inodes */
+ struct list_head s_inodes_iters; /* all iterators */
spinlock_t s_inode_wblist_lock;
struct list_head s_inodes_wb; /* writeback inodes */
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 30aa6ce13590a..3dcde8cbfb6af 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -1369,110 +1369,80 @@ static void hook_inode_free_security_rcu(void *inode_security)
/* Super-block hooks */
-/*
- * Release the inodes used in a security policy.
- *
- * Cf. fsnotify_unmount_inodes() and evict_inodes()
- */
-static void hook_sb_delete(struct super_block *const sb)
+static int hook_sb_delete_inode_iter_cb(struct inode *inode, void *data)
{
- struct inode *inode, *prev_inode = NULL;
+ struct landlock_object *object;
+ struct super_block *sb = inode->i_sb;
- if (!landlock_initialized)
- return;
+ if (!atomic_read(&inode->i_count)) {
+ spin_unlock(&inode->i_lock);
+ return 0;
+ }
- spin_lock(&sb->s_inode_list_lock);
- list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
- struct landlock_object *object;
+ rcu_read_lock();
+ object = rcu_dereference(landlock_inode(inode)->object);
+ if (!object) {
+ rcu_read_unlock();
+ spin_unlock(&inode->i_lock);
+ return 0;
+ }
+ /* Keeps a reference to this inode until the next loop walk. */
+ __iget(inode);
+ spin_unlock(&inode->i_lock);
- /* Only handles referenced inodes. */
- if (!icount_read_once(inode))
- continue;
+ /*
+ * If there is no concurrent release_inode() ongoing, then we
+ * are in charge of calling iput() on this inode, otherwise we
+ * will just wait for it to finish.
+ */
+ spin_lock(&object->lock);
+ if (object->underobj == inode) {
+ object->underobj = NULL;
+ spin_unlock(&object->lock);
+ rcu_read_unlock();
/*
- * Protects against concurrent modification of inode (e.g.
- * from get_inode_object()).
+ * Because object->underobj was not NULL,
+ * release_inode() and get_inode_object() guarantee
+ * that it is safe to reset
+ * landlock_inode(inode)->object while it is not NULL.
+ * It is therefore not necessary to lock inode->i_lock.
*/
- spin_lock(&inode->i_lock);
+ rcu_assign_pointer(landlock_inode(inode)->object, NULL);
/*
- * Checks I_FREEING and I_WILL_FREE to protect against a race
- * condition when release_inode() just called iput(), which
- * could lead to a NULL dereference of inode->security or a
- * second call to iput() for the same Landlock object. Also
- * checks I_NEW because such inode cannot be tied to an object.
+ * At this point, we own the ihold() reference that was
+ * originally set up by get_inode_object() and the
+ * __iget() reference that we just set in this loop
+ * walk. Therefore there are at least two references
+ * on the inode.
*/
- if (inode_state_read(inode) &
- (I_FREEING | I_WILL_FREE | I_NEW)) {
- spin_unlock(&inode->i_lock);
- continue;
- }
+ iput_not_last(inode);
+ } else {
+ spin_unlock(&object->lock);
+ rcu_read_unlock();
+ }
- rcu_read_lock();
- object = rcu_dereference(landlock_inode(inode)->object);
- if (!object) {
- rcu_read_unlock();
- spin_unlock(&inode->i_lock);
- continue;
- }
- /* Keeps a reference to this inode until the next loop walk. */
- __iget(inode);
- spin_unlock(&inode->i_lock);
+ spin_unlock(&sb->s_inode_list_lock);
+ iput(inode);
+ spin_lock(&sb->s_inode_list_lock);
- /*
- * If there is no concurrent release_inode() ongoing, then we
- * are in charge of calling iput() on this inode, otherwise we
- * will just wait for it to finish.
- */
- spin_lock(&object->lock);
- if (object->underobj == inode) {
- object->underobj = NULL;
- spin_unlock(&object->lock);
- rcu_read_unlock();
+ return 0;
+}
- /*
- * Because object->underobj was not NULL,
- * release_inode() and get_inode_object() guarantee
- * that it is safe to reset
- * landlock_inode(inode)->object while it is not NULL.
- * It is therefore not necessary to lock inode->i_lock.
- */
- rcu_assign_pointer(landlock_inode(inode)->object, NULL);
- /*
- * At this point, we own the ihold() reference that was
- * originally set up by get_inode_object() and the
- * __iget() reference that we just set in this loop
- * walk. Therefore there are at least two references
- * on the inode.
- */
- iput_not_last(inode);
- } else {
- spin_unlock(&object->lock);
- rcu_read_unlock();
- }
+/*
+ * Release the inodes used in a security policy.
+ *
+ * Cf. fsnotify_unmount_inodes() and evict_inodes()
+ */
+static void hook_sb_delete(struct super_block *const sb)
+{
+ unsigned int flags = INODE_ITER_NORMAL;
- if (prev_inode) {
- /*
- * At this point, we still own the __iget() reference
- * that we just set in this loop walk. Therefore we
- * can drop the list lock and know that the inode won't
- * disappear from under us until the next loop walk.
- */
- spin_unlock(&sb->s_inode_list_lock);
- /*
- * We can now actually put the inode reference from the
- * previous loop walk, which is not needed anymore.
- */
- iput(prev_inode);
- cond_resched();
- spin_lock(&sb->s_inode_list_lock);
- }
- prev_inode = inode;
- }
- spin_unlock(&sb->s_inode_list_lock);
+ if (!landlock_initialized)
+ return;
+
+ sb_for_each_inodes(sb, flags, hook_sb_delete_inode_iter_cb, NULL);
- /* Puts the inode reference from the last loop walk, if any. */
- if (prev_inode)
- iput(prev_inode);
/* Waits for pending iput() in release_inode(). */
wait_var_event(&landlock_superblock(sb)->inode_refs,
!atomic_long_read(&landlock_superblock(sb)->inode_refs));
|
| 15/3 | 2026/09/09 11:11 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 11209 output: 341 thoughts: 2265 Thoughts: **KMSAN Evaluation: Patch Introducing `sb_for_each_inodes`** Okay, let's break this down. My goal is to determine if this patch, which introduces a new iterator for inodes within a superblock, warrants a dedicated KMSAN fuzzing session, or if it's sufficiently covered by existing KASAN and other debugging tools. First, I need to fully understand the patch itself. It's essentially introducing a helper function, `sb_for_each_inodes`, and an associated iterator structure, `inode_iter`, to streamline iteration through the `s_inodes` list of a superblock. Several existing functions that iterate inodes are being refactored to use this new iterator. The patch also adds a `s_inodes_iters` list to the `super_block` structure to track the iterators. Now, let's drill down into the potential for uninitialized memory issues, info leaks, or any other KMSAN-specific concerns. I've examined the code closely. The `inode_iter` structure itself is stack-allocated within the `sb_for_each_inodes` function, and all its fields are initialized within the `inode_sb_iter_start` function. There are no uninitialized memory reads within the iterator. Specifically, the patch does not copy any kernel data to user space. It also looks like the `data` parameter passed to the callback function is either a null pointer, or an initialized variable. The `inode_sb_iter_next` function's logic is clear, even if there is a logical bug in it, it is not an uninitialized memory issue. So, am I seeing anything that would fall into the categories where KMSAN is specifically needed? No, not really. The primary risks appear to be related to concurrency, locking, and potentially use-after-free bugs. Things like dropping and retaking the `s_inode_list_lock` while `it->next` is updated. These kinds of bugs are the bread and butter of KASAN, LOCKDEP, and the standard kernel debugging infrastructure. I've reviewed the guidelines. I need to ask myself: Does this patch introduce or modify: 1. Kernel structures sent to user space? No. 2. Conditional logic that depends on potentially uninitialized variables? No. 3. Allocation or initialization of complex data structures where uninitialized fields could be read? No. 4. Bounds checks or logic in a way that allows surrounding code to access uninitialized bytes? No. Therefore, this patch does not expose any new uninitialized memory risks. All structures touched by the patch are fully initialized and the introduced bugs are detectable by KASAN and the standard bug detectors. No KMSAN is needed. I'm comfortable setting `NeedsKMSAN=false` for this patch. |
| 16/3 | 2026/09/09 11:11 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch refactors the internal superblock inode iteration mechanism across several subsystems (block, drop_caches, gfs2, vfs inode, quota, landlock) by introducing a generic helper `sb_for_each_inodes()` and iterator structure `struct inode_iter`.\n\n- Uninitialized memory / KMSAN perspective:\n - The newly introduced stack-allocated `struct inode_iter` in `sb_for_each_inodes()` is fully initialized in `inode_sb_iter_start()`.\n - All callback data payloads passed to the iterator functions are either NULL or pointers to fully initialized variables/structures (e.g., boolean cast, initialized linked list head, integer pointer).\n - No kernel data is copied or exposed to user space (no `copy_to_user`, ioctls, netlink messages, etc.).\n - No uninitialized memory reads or conditional branching on uninitialized fields are introduced.\n\n- KASAN / LOCKDEP perspective:\n - Potential issues introduced by this patch relate to list manipulation, refcounting (`__iget`/`iput`), locking semantics under `sb-\u003es_inode_list_lock` and `inode-\u003ei_lock`, or use-after-free/race conditions if list nodes are deleted during traversal. These are fully covered by KASAN, LOCKDEP, and `DEBUG_LIST`.\n\nTherefore, there is no risk of uninitialized memory usage or info-leaks, and a dedicated KMSAN fuzzing session is not warranted.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch refactors the internal superblock inode iteration mechanism across several subsystems (block, drop_caches, gfs2, vfs inode, quota, landlock) by introducing a generic helper `sb_for_each_inodes()` and iterator structure `struct inode_iter`.\n\n- Uninitialized memory / KMSAN perspective:\n - The newly introduced stack-allocated `struct inode_iter` in `sb_for_each_inodes()` is fully initialized in `inode_sb_iter_start()`.\n - All callback data payloads passed to the iterator functions are either NULL or pointers to fully initialized variables/structures (e.g., boolean cast, initialized linked list head, integer pointer).\n - No kernel data is copied or exposed to user space (no `copy_to_user`, ioctls, netlink messages, etc.).\n - No uninitialized memory reads or conditional branching on uninitialized fields are introduced.\n\n- KASAN / LOCKDEP perspective:\n - Potential issues introduced by this patch relate to list manipulation, refcounting (`__iget`/`iput`), locking semantics under `sb-\u003es_inode_list_lock` and `inode-\u003ei_lock`, or use-after-free/race conditions if list nodes are deleted during traversal. These are fully covered by KASAN, LOCKDEP, and `DEBUG_LIST`.\n\nTherefore, there is no risk of uninitialized memory usage or info-leaks, and 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) |
|---|