AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/09/04 04:15 flow patch-triage
1m Args:
null

Results:
{
  "EnableConfigs": [
    "PROVE_LOCKING",
    "NFS_FS"
  ],
  "FocusSymbols": [
    "d_duplicate",
    "d_alloc_parallel",
    "d_alloc_trylock",
    "d_splice_alias",
    "d_splice_alias_ops",
    "d_wait_lookup",
    "lookup_slow",
    "lookup_slow_killable",
    "lookup_open",
    "nfs_async_unlink_release",
    "nfs_call_unlink"
  ],
  "KMSANReasoning": "The patch series introduces Lockdep tracking for parallel dentry lookups (DCACHE_PAR_LOOKUP via struct lockdep_map in struct dentry), adds the d_duplicate and d_alloc_trylock helpers, updates locking flags (LOOKUP_SHARED) in VFS lookup routines (lookup_slow, lookup_open), and fixes filesystem documentation.\n\nNone of the changes introduce or modify uninitialized memory reads, buffer boundaries, or kernel-to-userspace data transfers. All modified data structures (e.g., struct dentry in __d_alloc) are properly initialized upon allocation. Any concurrency, deadlock, or refcounting issues resulting from these VFS locking/lookup refactorings are tracked and detected by standard detectors such as LOCKDEP and KASAN. Therefore, dedicated KMSAN fuzzing is not required.",
  "NeedsKMSAN": false,
  "Reasoning": "The patch series modifies core VFS dcache and namei logic, including parallel lookup locking, shared directory locking flags (LOOKUP_SHARED) in lookup_slow and lookup_open, lockdep map tracking for in-lookup dentries, updated assertions in d_splice_alias_ops, new dcache helpers (d_duplicate, d_alloc_trylock), and NFS asynchronous unlink lock tracking. These changes are in core reachable kernel subsystems and directly affect concurrency and invariant enforcement.",
  "WorthFuzzing": true
}

1/1 2026/09/04 04:15 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit d9a12ec1e5fe3f3e577d3b61836d86e6699430ea\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Fri Sep 4 04:15:56 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/Documentation/filesystems/porting.rst b/Documentation/filesystems/porting.rst\nindex 60880eb0c49d1..4e015f1bf1f84 100644\n--- a/Documentation/filesystems/porting.rst\n+++ b/Documentation/filesystems/porting.rst\n@@ -1203,16 +1203,16 @@ will fail-safe.\n \n ---\n \n-** mandatory**\n+**mandatory**\n \n lookup_one(), lookup_one_unlocked(), lookup_one_positive_unlocked() now\n take a qstr instead of a name and len.  These, not the \"one_len\"\n versions, should be used whenever accessing a filesystem from outside\n-that filesysmtem, through a mount point - which will have a mnt_idmap.\n+that filesystem, through a mount point - which will have a mnt_idmap.\n \n ---\n \n-** mandatory**\n+**mandatory**\n \n Functions try_lookup_one_len(), lookup_one_len(),\n lookup_one_len_unlocked() and lookup_positive_unlocked() have been\n@@ -1229,7 +1229,7 @@ already been performed such as after vfs_path_parent_lookup()\n \n ---\n \n-** mandatory**\n+**mandatory**\n \n d_hash_and_lookup() is no longer exported or available outside the VFS.\n Use try_lookup_noperm() instead.  This adds name validation and takes\n@@ -1370,7 +1370,7 @@ similar.\n \n ---\n \n-** mandatory**\n+**mandatory**\n \n lock_rename(), lock_rename_child(), unlock_rename() are no\n longer available.  Use start_renaming() or similar.\ndiff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst\nindex d3a93eec3945f..de8f8502056e8 100644\n--- a/Documentation/filesystems/vfs.rst\n+++ b/Documentation/filesystems/vfs.rst\n@@ -507,8 +507,8 @@ otherwise noted.\n \tdentry before the first mkdir returns.\n \n \tIf there is any chance this could happen, then the new inode\n-\tshould be d_drop()ed and attached with d_splice_alias().  The\n-\treturned dentry (if any) should be returned by -\u003emkdir().\n+\tshould be attached with d_splice_alias().  The returned\n+\tdentry (if any) should be returned by -\u003emkdir().\n \n ``rmdir``\n \tcalled by the rmdir(2) system call.  Only required if you want\ndiff --git a/fs/dcache.c b/fs/dcache.c\nindex 1b1a81f10da67..3af8b2ac699f9 100644\n--- a/fs/dcache.c\n+++ b/fs/dcache.c\n@@ -32,6 +32,7 @@\n #include \u003clinux/bit_spinlock.h\u003e\n #include \u003clinux/rculist_bl.h\u003e\n #include \u003clinux/list_lru.h\u003e\n+#include \u003clinux/namei.h\u003e\n #include \"internal.h\"\n #include \"mount.h\"\n \n@@ -1900,6 +1901,7 @@ EXPORT_SYMBOL(d_invalidate);\n  \n static struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name)\n {\n+\tstatic struct lock_class_key __lookup_key;\n \tstruct dentry *dentry;\n \tchar *dname;\n \tint err;\n@@ -1957,6 +1959,8 @@ static struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name)\n \tdentry-\u003ewaiters = NULL;\n \tINIT_HLIST_NODE(\u0026dentry-\u003ed_sib);\n \n+\tlockdep_init_map(\u0026dentry-\u003elookup_map, \"DCACHE_PAR_LOOKUP\", \u0026__lookup_key, 0);\n+\n \tif (dentry-\u003ed_op \u0026\u0026 dentry-\u003ed_op-\u003ed_init) {\n \t\terr = dentry-\u003ed_op-\u003ed_init(dentry);\n \t\tif (err) {\n@@ -1999,6 +2003,58 @@ struct dentry *d_alloc(struct dentry * parent, const struct qstr *name)\n }\n EXPORT_SYMBOL(d_alloc);\n \n+/**\n+ * d_duplicate - duplicate a dentry for combined atomic operation\n+ * @dentry: the dentry to duplicate\n+ *\n+ * Some rename operations need to be combined with another operation\n+ * inside the filesystem.\n+ * 1/ A cluster filesystem when renaming to an in-use file might need to\n+ *   first \"silly-rename\" that target out of the way before the main rename\n+ * 2/ A filesystem that supports white-out might want to create a whiteout\n+ *   in place of the file being moved.\n+ *\n+ * For this they need two dentries which temporarily have the same name,\n+ * before one is renamed.  d_duplicate() provides for this.  Given a\n+ * positive hashed dentry, it creates a second in-lookup dentry.\n+ * Because the original dentry exists, no other thread will try to\n+ * create an in-lookup dentry, so there can be no race in this create.\n+ *\n+ * The caller should d_move() the original to a new name, often via a\n+ * rename request, and should call d_lookup_done() on the newly created\n+ * dentry.  If the new is instantiated then the old MUST either be moved\n+ * or dropped.\n+ *\n+ * Parent must be locked.\n+ *\n+ * Returns: an in-lookup dentry, or -ENOMEM.\n+ */\n+struct dentry *d_duplicate(struct dentry *dentry)\n+{\n+\tunsigned int hash = dentry-\u003ed_name.hash;\n+\tstruct dentry *parent = dentry-\u003ed_parent;\n+\tstruct hlist_bl_head *b = in_lookup_hash(parent, hash);\n+\tstruct dentry *new = __d_alloc(parent-\u003ed_sb, \u0026dentry-\u003ed_name);\n+\n+\tif (unlikely(!new))\n+\t\treturn ERR_PTR(-ENOMEM);\n+\n+\tnew-\u003ed_flags |= DCACHE_PAR_LOOKUP;\n+\tlock_map_acquire_try(\u0026new-\u003elookup_map);\n+\tspin_lock(\u0026parent-\u003ed_lock);\n+\tnew-\u003ed_parent = dget_dlock(parent);\n+\thlist_add_head(\u0026new-\u003ed_sib, \u0026parent-\u003ed_children);\n+\tif (parent-\u003ed_flags \u0026 DCACHE_DISCONNECTED)\n+\t\tnew-\u003ed_flags |= DCACHE_DISCONNECTED;\n+\tspin_unlock(\u0026parent-\u003ed_lock);\n+\n+\thlist_bl_lock(b);\n+\thlist_bl_add_head(\u0026new-\u003ed_in_lookup_hash, b);\n+\thlist_bl_unlock(b);\n+\treturn new;\n+}\n+EXPORT_SYMBOL(d_duplicate);\n+\n struct dentry *d_alloc_anon(struct super_block *sb)\n {\n \treturn __d_alloc(sb, NULL);\n@@ -2168,7 +2224,6 @@ static void __d_instantiate(struct dentry *dentry, struct inode *inode)\n  * (or otherwise set) by the caller to indicate that it is now\n  * in use by the dcache.\n  */\n- \n void d_instantiate(struct dentry *entry, struct inode * inode)\n {\n \tBUG_ON(d_really_is_positive(entry));\n@@ -2750,6 +2805,14 @@ static inline void end_dir_add(struct inode *dir, unsigned int n)\n static void d_wait_lookup(struct dentry *dentry)\n {\n \tif (likely(d_in_lookup(dentry))) {\n+\t\t/*\n+\t\t * Tell lockdep we will wait for the lookup lock, after\n+\t\t * dropping -\u003ed_lock, but won't actually take it.\n+\t\t */\n+\t\tspin_release(\u0026dentry-\u003ed_lock.dep_map, 0);\n+\t\tlock_map_sync(\u0026dentry-\u003elookup_map);\n+\t\tspin_acquire(\u0026dentry-\u003ed_lock.dep_map, 0, 1, _THIS_IP_);\n+\n \t\tdentry-\u003ed_flags |= DCACHE_LOOKUP_WAITERS;\n \t\twait_var_event_spinlock(\u0026dentry-\u003ed_flags,\n \t\t\t\t\t!d_in_lookup(dentry),\n@@ -2757,8 +2820,16 @@ static void d_wait_lookup(struct dentry *dentry)\n \t}\n }\n \n-struct dentry *d_alloc_parallel(struct dentry *parent,\n-\t\t\t\tconst struct qstr *name)\n+/* What to do when __d_alloc_parallel finds a d_in_lookup dentry */\n+enum alloc_para {\n+\tALLOC_PARA_WAIT,\n+\tALLOC_PARA_FAIL,\n+};\n+\n+static inline\n+struct dentry *__d_alloc_parallel(struct dentry *parent,\n+\t\t\t\t  const struct qstr *name,\n+\t\t\t\t  enum alloc_para how)\n {\n \tunsigned int hash = name-\u003ehash;\n \tstruct hlist_bl_head *b = in_lookup_hash(parent, hash);\n@@ -2831,6 +2902,12 @@ struct dentry *d_alloc_parallel(struct dentry *parent,\n \t\t\tspin_unlock(\u0026dentry-\u003ed_lock);\n \t\t\tgoto retry;\n \t\t}\n+\t\tif (unlikely(how == ALLOC_PARA_FAIL)) {\n+\t\t\t/* mustn't wait for concurrent lookup to complete */\n+\t\t\tspin_unlock(\u0026dentry-\u003ed_lock);\n+\t\t\tdput(new);\n+\t\t\treturn ERR_PTR(-EWOULDBLOCK);\n+\t\t}\n \t\t/*\n \t\t * somebody is likely to be still doing lookup for it;\n \t\t * pin it and wait for them to finish\n@@ -2858,14 +2935,77 @@ struct dentry *d_alloc_parallel(struct dentry *parent,\n \t}\n \thlist_bl_add_head(\u0026new-\u003ed_in_lookup_hash, b);\n \thlist_bl_unlock(b);\n+\tlock_map_acquire_try(\u0026new-\u003elookup_map);\n \treturn new;\n mismatch:\n \tspin_unlock(\u0026dentry-\u003ed_lock);\n \tdput(dentry);\n \tgoto retry;\n }\n+\n+/**\n+ * d_alloc_parallel() - allocate a new dentry and ensure uniqueness\n+ * @parent: dentry of the parent\n+ * @name:   name of the dentry within that parent.\n+ *\n+ * A new dentry is allocated and, providing it is unique, added to the\n+ * relevant index.\n+ * If an existing dentry is found with the same parent/name that is\n+ * not d_in_lookup(), then that is returned instead.\n+ * If the existing dentry is d_in_lookup(), d_alloc_parallel() waits for\n+ * that lookup to complete before returning the dentry and then ensures the\n+ * match is still valid.\n+ * Thus if the returned dentry is d_in_lookup() then the caller has\n+ * exclusive access until it completes the lookup.\n+ * If the returned dentry is not d_in_lookup() then a lookup has\n+ * already completed.\n+ *\n+ * The @name must already have -\u003ehash set, as can be achieved\n+ * by e.g. try_lookup_noperm().\n+ *\n+ * Returns: the dentry, whether found or allocated, or an error %-ENOMEM.\n+ */\n+struct dentry *d_alloc_parallel(struct dentry *parent,\n+\t\t\t\tconst struct qstr *name)\n+{\n+\treturn __d_alloc_parallel(parent, name, ALLOC_PARA_WAIT);\n+}\n EXPORT_SYMBOL(d_alloc_parallel);\n \n+/**\n+ * d_alloc_trylock() - find or allocate a new dentry\n+ * @parent: dentry of the parent\n+ * @name:   name of the dentry within that parent.\n+ *\n+ * A new dentry is allocated and, providing it is unique, added to the\n+ * relevant index.\n+ * If an existing dentry is found with the same parent/name that is\n+ * not d_in_lookup() then that is returned instead.\n+ * If the existing dentry is d_in_lookup(), d_alloc_trylock()\n+ * returns with error %-EWOULDBLOCK.\n+ * Thus if the returned dentry is d_in_lookup() then the caller has\n+ * exclusive access until it completes the lookup.\n+ * If the returned dentry is not d_in_lookup() then a lookup has\n+ * already completed.\n+ *\n+ * The @name need not already have -\u003ehash set.\n+ *\n+ * Returns: the dentry, whether found or allocated, or an error\n+ *    %-ENOMEM, %-EWOULDBLOCK, %-EACCES (for a bad name) or\n+ *    anything returned by -\u003ed_hash().\n+ */\n+struct dentry *d_alloc_trylock(struct dentry *parent,\n+\t\t\t       struct qstr *name)\n+{\n+\tstruct dentry *de;\n+\n+\tde = try_lookup_noperm(name, parent);\n+\tif (!de)\n+\t\tde = __d_alloc_parallel(parent, name, ALLOC_PARA_FAIL);\n+\treturn de;\n+}\n+EXPORT_SYMBOL(d_alloc_trylock);\n+\n /*\n  * Move dentry from in-lookup state to busy-negative one.\n  *\n@@ -2894,6 +3034,7 @@ static void __d_lookup_unhash(struct dentry *dentry)\n \tb = in_lookup_hash(dentry-\u003ed_parent, dentry-\u003ed_name.hash);\n \thlist_bl_lock(b);\n \tdentry-\u003ed_flags \u0026= ~DCACHE_PAR_LOOKUP;\n+\tlock_map_release(\u0026dentry-\u003elookup_map);\n \t__hlist_bl_del(\u0026dentry-\u003ed_in_lookup_hash);\n \thlist_bl_unlock(b);\n \tdentry-\u003ewaiters = NULL;\n@@ -2931,15 +3072,10 @@ static inline void __d_add(struct dentry *dentry, struct inode *inode,\n \t}\n \tif (unlikely(ops))\n \t\td_set_d_op(dentry, ops);\n-\tif (inode) {\n-\t\tunsigned add_flags = d_flags_for_inode(inode);\n-\t\thlist_add_head(\u0026dentry-\u003ed_alias, \u0026inode-\u003ei_dentry);\n-\t\traw_write_seqcount_begin(\u0026dentry-\u003ed_seq);\n-\t\t__d_set_inode_and_type(dentry, inode, add_flags);\n-\t\traw_write_seqcount_end(\u0026dentry-\u003ed_seq);\n-\t\tfsnotify_update_flags(dentry);\n-\t}\n-\t__d_rehash(dentry);\n+\tif (inode)\n+\t\t__d_instantiate(dentry, inode);\n+\tif (d_unhashed(dentry))\n+\t\t__d_rehash(dentry);\n \tif (dir) {\n \t\tend_dir_add(dir, n);\n \t\t__d_wake_in_lookup_waiters(dentry);\n@@ -3241,7 +3377,7 @@ struct dentry *d_splice_alias_ops(struct inode *inode, struct dentry *dentry,\n \tif (IS_ERR(inode))\n \t\treturn ERR_CAST(inode);\n \n-\tBUG_ON(!d_unhashed(dentry));\n+\tBUG_ON(d_really_is_positive(dentry));\n \n \tif (!inode)\n \t\tgoto out;\n@@ -3297,6 +3433,8 @@ struct dentry *d_splice_alias_ops(struct inode *inode, struct dentry *dentry,\n  * @inode:  the inode which may have a disconnected dentry\n  * @dentry: a negative dentry which we want to point to the inode.\n  *\n+ * @dentry must be negative and may be in-lookup or unhashed or hashed.\n+ *\n  * If inode is a directory and has an IS_ROOT alias, then d_move that in\n  * place of the given dentry and return it, else simply d_add the inode\n  * to the dentry and return NULL.\n@@ -3304,16 +3442,14 @@ struct dentry *d_splice_alias_ops(struct inode *inode, struct dentry *dentry,\n  * If a non-IS_ROOT directory is found, the filesystem is corrupt, and\n  * we should error out: directories can't have multiple aliases.\n  *\n- * This is needed in the lookup routine of any filesystem that is exportable\n- * (via knfsd) so that we can build dcache paths to directories effectively.\n+ * This should be used to return the result of -\u003elookup() and to\n+ * instantiate the result of -\u003emkdir(), is often useful for\n+ * -\u003eatomic_open, and may be used to instantiate other objects.\n  *\n  * If a dentry was found and moved, then it is returned.  Otherwise NULL\n- * is returned.  This matches the expected return value of -\u003elookup.\n+ * is returned.  This matches the expected return value of -\u003elookup and\n+ * -\u003emkdir.\n  *\n- * Cluster filesystems may call this function with a negative, hashed dentry.\n- * In that case, we know that the inode will be a regular file, and also this\n- * will only occur during atomic_open. So we need to check for the dentry\n- * being already hashed only in the final case.\n  */\n struct dentry *d_splice_alias(struct inode *inode, struct dentry *dentry)\n {\ndiff --git a/fs/namei.c b/fs/namei.c\nindex 20a6534ea3eff..e978a75eb8a14 100644\n--- a/fs/namei.c\n+++ b/fs/namei.c\n@@ -1933,7 +1933,7 @@ static noinline struct dentry *lookup_slow(const struct qstr *name,\n \tstruct inode *inode = dir-\u003ed_inode;\n \tstruct dentry *res;\n \tinode_lock_shared(inode);\n-\tres = __lookup_slow(name, dir, flags);\n+\tres = __lookup_slow(name, dir, flags | LOOKUP_SHARED);\n \tinode_unlock_shared(inode);\n \treturn res;\n }\n@@ -1947,7 +1947,7 @@ static struct dentry *lookup_slow_killable(const struct qstr *name,\n \n \tif (inode_lock_shared_killable(inode))\n \t\treturn ERR_PTR(-EINTR);\n-\tres = __lookup_slow(name, dir, flags);\n+\tres = __lookup_slow(name, dir, flags | LOOKUP_SHARED);\n \tinode_unlock_shared(inode);\n \treturn res;\n }\n@@ -2946,8 +2946,8 @@ struct dentry *start_dirop(struct dentry *parent, struct qstr *name,\n  * end_dirop - signal completion of a dirop\n  * @de: the dentry which was returned by start_dirop or similar.\n  *\n- * If the de is an error, nothing happens. Otherwise any lock taken to\n- * protect the dentry is dropped and the dentry itself is release (dput()).\n+ * If the @de is an error, nothing happens. Otherwise any lock taken to\n+ * protect the dentry is dropped and the dentry itself is released (dput()).\n  */\n void end_dirop(struct dentry *de)\n {\n@@ -3210,7 +3210,7 @@ EXPORT_SYMBOL(lookup_one);\n /**\n  * lookup_one_unlocked - lookup single pathname component\n  * @idmap:\tidmap of the mount the lookup is performed from\n- * @name:\tqstr olding pathname component to lookup\n+ * @name:\tqstr holding pathname component to lookup\n  * @base:\tbase directory to lookup from\n  *\n  * This can be used for in-kernel filesystem clients such as file servers.\n@@ -3243,7 +3243,7 @@ EXPORT_SYMBOL(lookup_one_unlocked);\n /**\n  * lookup_one_positive_killable - lookup single pathname component\n  * @idmap:\tidmap of the mount the lookup is performed from\n- * @name:\tqstr olding pathname component to lookup\n+ * @name:\tqstr holding pathname component to lookup\n  * @base:\tbase directory to lookup from\n  *\n  * This helper will yield ERR_PTR(-ENOENT) on negatives. The helper returns\n@@ -3259,7 +3259,7 @@ EXPORT_SYMBOL(lookup_one_unlocked);\n  * the i_rwsem itself if necessary.  If a fatal signal is pending or\n  * delivered, it will return %-EINTR if the lock is needed.\n  *\n- * Returns: A dentry, possibly negative, or\n+ * Returns: A positive dentry, or\n  *\t   - same errors as lookup_one_unlocked() or\n  *\t   - ERR_PTR(-EINTR) if a fatal signal is pending.\n  */\n@@ -3381,7 +3381,7 @@ struct dentry *lookup_noperm_positive_unlocked(struct qstr *name,\n EXPORT_SYMBOL(lookup_noperm_positive_unlocked);\n \n /**\n- * start_creating - prepare to create a given name with permission checking\n+ * start_creating - prepare to access or create a given name with permission checking\n  * @idmap:  idmap of the mount\n  * @parent: directory in which to prepare to create the name\n  * @name:   the name to be created\n@@ -3413,8 +3413,8 @@ EXPORT_SYMBOL(start_creating);\n  * @parent: directory in which to find the name\n  * @name:   the name to be removed\n  *\n- * Locks are taken and a lookup in performed prior to removing\n- * an object from a directory.  Permission checking (MAY_EXEC) is performed\n+ * Locks are taken and a lookup is performed prior to removing an object\n+ * from a directory.  Permission checking (MAY_EXEC) is performed\n  * against @idmap.\n  *\n  * If the name doesn't exist, an error is returned.\n@@ -3440,7 +3440,7 @@ EXPORT_SYMBOL(start_removing);\n  * @parent: directory in which to prepare to create the name\n  * @name:   the name to be created\n  *\n- * Locks are taken and a lookup in performed prior to creating\n+ * Locks are taken and a lookup is performed prior to creating\n  * an object in a directory.  Permission checking (MAY_EXEC) is performed\n  * against @idmap.\n  *\n@@ -3469,7 +3469,7 @@ EXPORT_SYMBOL(start_creating_killable);\n  * @parent: directory in which to find the name\n  * @name:   the name to be removed\n  *\n- * Locks are taken and a lookup in performed prior to removing\n+ * Locks are taken and a lookup is performed prior to removing\n  * an object from a directory.  Permission checking (MAY_EXEC) is performed\n  * against @idmap.\n  *\n@@ -3499,7 +3499,7 @@ EXPORT_SYMBOL(start_removing_killable);\n  * @parent: directory in which to prepare to create the name\n  * @name:   the name to be created\n  *\n- * Locks are taken and a lookup in performed prior to creating\n+ * Locks are taken and a lookup is performed prior to creating\n  * an object in a directory.\n  *\n  * If the name already exists, a positive dentry is returned.\n@@ -3522,7 +3522,7 @@ EXPORT_SYMBOL(start_creating_noperm);\n  * @parent: directory in which to find the name\n  * @name:   the name to be removed\n  *\n- * Locks are taken and a lookup in performed prior to removing\n+ * Locks are taken and a lookup is performed prior to removing\n  * an object from a directory.\n  *\n  * If the name doesn't exist, an error is returned.\n@@ -3543,11 +3543,11 @@ struct dentry *start_removing_noperm(struct dentry *parent,\n EXPORT_SYMBOL(start_removing_noperm);\n \n /**\n- * start_creating_dentry - prepare to create a given dentry\n- * @parent: directory from which dentry should be removed\n- * @child:  the dentry to be removed\n+ * start_creating_dentry - prepare to access or create a given dentry\n+ * @parent: directory of dentry\n+ * @child:  the dentry to be prepared\n  *\n- * A lock is taken to protect the dentry again other dirops and\n+ * A lock is taken to protect the dentry against other dirops and\n  * the validity of the dentry is checked: correct parent and still hashed.\n  *\n  * If the dentry is valid and negative a reference is taken and\n@@ -3580,7 +3580,7 @@ EXPORT_SYMBOL(start_creating_dentry);\n  * @parent: directory from which dentry should be removed\n  * @child:  the dentry to be removed\n  *\n- * A lock is taken to protect the dentry again other dirops and\n+ * A lock is taken to protect the dentry against other dirops and\n  * the validity of the dentry is checked: correct parent and still hashed.\n  *\n  * If the dentry is valid and positive, a reference is taken and\n@@ -4440,12 +4440,14 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,\n \tint error, create_error;\n \tumode_t mode;\n \tbool got_write;\n+\tunsigned int shared_flag;\n \n retry:\n \topen_flag = op-\u003eopen_flag;\n \tgot_write = false;\n \tmode = op-\u003emode;\n \tcreate_error = 0;\n+\tshared_flag = (open_flag \u0026 O_CREAT) ? 0 : LOOKUP_SHARED;\n \n \tif (open_flag \u0026 (O_CREAT | O_TRUNC | O_WRONLY | O_RDWR)) {\n \t\tgot_write = !mnt_want_write(nd-\u003epath.mnt);\n@@ -4454,10 +4456,10 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,\n \t\t * a different error; we'll be dropping this one anyway.\n \t\t */\n \t}\n-\tif (open_flag \u0026 O_CREAT)\n-\t\tinode_lock(dir_inode);\n-\telse\n+\tif (shared_flag)\n \t\tinode_lock_shared(dir_inode);\n+\telse\n+\t\tinode_lock(dir_inode);\n \n \tif (unlikely(IS_DEADDIR(dir_inode))) {\n \t\tdentry = ERR_PTR(-ENOENT);\n@@ -4526,7 +4528,7 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,\n \n \tif (d_in_lookup(dentry)) {\n \t\tstruct dentry *res = dir_inode-\u003ei_op-\u003elookup(dir_inode, dentry,\n-\t\t\t\t\t\t\t     nd-\u003eflags);\n+\t\t\t\t\t\t\t     nd-\u003eflags | shared_flag);\n \t\td_lookup_done(dentry);\n \t\tif (unlikely(res)) {\n \t\t\tif (IS_ERR(res)) {\n@@ -4574,10 +4576,10 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,\n \t\tif (file-\u003ef_mode \u0026 FMODE_OPENED)\n \t\t\tfsnotify_open(file);\n \t}\n-\tif ((open_flag \u0026 O_CREAT) || create_error)\n-\t\tinode_unlock(dir_inode);\n-\telse\n+\tif (shared_flag)\n \t\tinode_unlock_shared(dir_inode);\n+\telse\n+\t\tinode_unlock(dir_inode);\n \n \tif (got_write)\n \t\tmnt_drop_write(nd-\u003epath.mnt);\ndiff --git a/fs/nfs/unlink.c b/fs/nfs/unlink.c\nindex b57cfaa4d516f..c8d712204e649 100644\n--- a/fs/nfs/unlink.c\n+++ b/fs/nfs/unlink.c\n@@ -67,6 +67,7 @@ static void nfs_async_unlink_release(void *calldata)\n \tstruct super_block *sb = dentry-\u003ed_sb;\n \n \tup_read_non_owner(\u0026NFS_I(d_inode(dentry-\u003ed_parent))-\u003ermdir_sem);\n+\td_lookup_acquire(dentry);\n \td_lookup_done(dentry);\n \tnfs_free_unlinkdata(data);\n \tdput(dentry);\n@@ -159,6 +160,8 @@ static int nfs_call_unlink(struct dentry *dentry, struct inode *inode, struct nf\n \t\treturn ret;\n \t}\n \tdata-\u003edentry = alias;\n+\td_lookup_release(alias);\n+\n \tnfs_do_call_unlink(inode, data);\n \treturn 1;\n }\ndiff --git a/include/linux/dcache.h b/include/linux/dcache.h\nindex 4b1ff99608e01..e7e3ef05313b8 100644\n--- a/include/linux/dcache.h\n+++ b/include/linux/dcache.h\n@@ -116,6 +116,8 @@ struct dentry {\n \t\t\t\t\t * possible!\n \t\t\t\t\t */\n \n+\t/* lockdep tracking of DCACHE_PAR_LOOKUP locks */\n+\tstruct lockdep_map\t\tlookup_map;\n \tstruct list_head d_lru;\t\t/* LRU list */\n \tstruct hlist_node d_sib;\t/* child of parent list */\n \tstruct hlist_head d_children;\t/* our children */\n@@ -257,7 +259,9 @@ extern void d_delete(struct dentry *);\n extern struct dentry * d_alloc(struct dentry *, const struct qstr *);\n extern struct dentry * d_alloc_anon(struct super_block *);\n extern struct dentry * d_alloc_parallel(struct dentry *, const struct qstr *);\n+extern struct dentry * d_alloc_trylock(struct dentry *, struct qstr *);\n extern struct dentry * d_splice_alias(struct inode *, struct dentry *);\n+struct dentry *d_duplicate(struct dentry *dentry);\n /* weird procfs mess; *NOT* exported */\n extern struct dentry * d_splice_alias_ops(struct inode *, struct dentry *,\n \t\t\t\t\t  const struct dentry_operations *);\n@@ -552,6 +556,36 @@ static inline int simple_positive(const struct dentry *dentry)\n \n unsigned long vfs_pressure_ratio(unsigned long val);\n \n+/**\n+ * d_lookup_release - release ownership of DCACHE_PAR_LOOKUP lock\n+ * @dentry: dentry that is locked\n+ *\n+ * If an in-lookup dentry is to be passed to another thread which\n+ * will drop the in-lookup lock, then d_lookup_release() must be called\n+ * to tell lockdep that this thread no lock holds the lock.  The\n+ * thread that receives the lock must call d_lookup_acquire() to\n+ * acquire the lock.\n+ */\n+static inline void d_lookup_release(struct dentry *dentry)\n+{\n+\tif (d_in_lookup(dentry))\n+\t\tlock_map_release(\u0026dentry-\u003elookup_map);\n+}\n+\n+/**\n+ * d_lookup_acquire - acquire ownership of DCACHE_PAR_LOOKUP lock\n+ * @dentry: dentry that is locked\n+ *\n+ * If an in-lookup dentry was passed to this thread, the\n+ * d_lookup_acquire() must be called to tell lockdep that this\n+ * thread now owns the DCACHE_PAR_LOOKUP lock.\n+ */\n+static inline void d_lookup_acquire(struct dentry *dentry)\n+{\n+\tif (d_in_lookup(dentry))\n+\t\tlock_map_acquire_try(\u0026dentry-\u003elookup_map);\n+}\n+\n /**\n  * d_inode - Get the actual inode of this dentry\n  * @dentry: The dentry to query\ndiff --git a/include/linux/namei.h b/include/linux/namei.h\nindex 86d657b24fc6a..65f0f5712885c 100644\n--- a/include/linux/namei.h\n+++ b/include/linux/namei.h\n@@ -32,8 +32,9 @@ enum { MAX_NESTED_LINKS = 8 };\n #define LOOKUP_CREATE\t\tBIT(17)\t/* ... in object creation */\n #define LOOKUP_EXCL\t\tBIT(18)\t/* ... in target must not exist */\n #define LOOKUP_RENAME_TARGET\tBIT(19)\t/* ... in destination of rename() */\n+#define LOOKUP_SHARED\t\tBIT(20) /* Parent lock is held shared */\n \n-/* 4 spare bits for intent */\n+/* 3 spare bits for intent */\n \n /* Scoping flags for lookup. */\n #define LOOKUP_NO_SYMLINKS\tBIT(24) /* No symlink crossing. */\n"
}

2/1 2026/09/04 04:15 agent patch-evaluator
0m Model:
core

Args:
null

Results:
{
  "EnableConfigs": [
    "PROVE_LOCKING",
    "NFS_FS"
  ],
  "FocusSymbols": [
    "d_duplicate",
    "d_alloc_parallel",
    "d_alloc_trylock",
    "d_splice_alias",
    "d_splice_alias_ops",
    "d_wait_lookup",
    "lookup_slow",
    "lookup_slow_killable",
    "lookup_open",
    "nfs_async_unlink_release",
    "nfs_call_unlink"
  ],
  "Reasoning": "The patch series modifies core VFS dcache and namei logic, including parallel lookup locking, shared directory locking flags (LOOKUP_SHARED) in lookup_slow and lookup_open, lockdep map tracking for in-lookup dentries, updated assertions in d_splice_alias_ops, new dcache helpers (d_duplicate, d_alloc_trylock), and NFS asynchronous unlink lock tracking. These changes are in core reachable kernel subsystems and directly affect concurrency and invariant enforcement.",
  "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 d9a12ec1e5fe3f3e577d3b61836d86e6699430ea
Author: syz-cluster <triage@syzkaller.com>
Date:   Fri Sep 4 04:15:56 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/filesystems/porting.rst b/Documentation/filesystems/porting.rst
index 60880eb0c49d1..4e015f1bf1f84 100644
--- a/Documentation/filesystems/porting.rst
+++ b/Documentation/filesystems/porting.rst
@@ -1203,16 +1203,16 @@ will fail-safe.
 
 ---
 
-** mandatory**
+**mandatory**
 
 lookup_one(), lookup_one_unlocked(), lookup_one_positive_unlocked() now
 take a qstr instead of a name and len.  These, not the "one_len"
 versions, should be used whenever accessing a filesystem from outside
-that filesysmtem, through a mount point - which will have a mnt_idmap.
+that filesystem, through a mount point - which will have a mnt_idmap.
 
 ---
 
-** mandatory**
+**mandatory**
 
 Functions try_lookup_one_len(), lookup_one_len(),
 lookup_one_len_unlocked() and lookup_positive_unlocked() have been
@@ -1229,7 +1229,7 @@ already been performed such as after vfs_path_parent_lookup()
 
 ---
 
-** mandatory**
+**mandatory**
 
 d_hash_and_lookup() is no longer exported or available outside the VFS.
 Use try_lookup_noperm() instead.  This adds name validation and takes
@@ -1370,7 +1370,7 @@ similar.
 
 ---
 
-** mandatory**
+**mandatory**
 
 lock_rename(), lock_rename_child(), unlock_rename() are no
 longer available.  Use start_renaming() or similar.
diff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst
index d3a93eec3945f..de8f8502056e8 100644
--- a/Documentation/filesystems/vfs.rst
+++ b/Documentation/filesystems/vfs.rst
@@ -507,8 +507,8 @@ otherwise noted.
 	dentry before the first mkdir returns.
 
 	If there is any chance this could happen, then the new inode
-	should be d_drop()ed and attached with d_splice_alias().  The
-	returned dentry (if any) should be returned by ->mkdir().
+	should be attached with d_splice_alias().  The returned
+	dentry (if any) should be returned by ->mkdir().
 
 ``rmdir``
 	called by the rmdir(2) system call.  Only required if you want
diff --git a/fs/dcache.c b/fs/dcache.c
index 1b1a81f10da67..3af8b2ac699f9 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -32,6 +32,7 @@
 #include <linux/bit_spinlock.h>
 #include <linux/rculist_bl.h>
 #include <linux/list_lru.h>
+#include <linux/namei.h>
 #include "internal.h"
 #include "mount.h"
 
@@ -1900,6 +1901,7 @@ EXPORT_SYMBOL(d_invalidate);
  
 static struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name)
 {
+	static struct lock_class_key __lookup_key;
 	struct dentry *dentry;
 	char *dname;
 	int err;
@@ -1957,6 +1959,8 @@ static struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name)
 	dentry->waiters = NULL;
 	INIT_HLIST_NODE(&dentry->d_sib);
 
+	lockdep_init_map(&dentry->lookup_map, "DCACHE_PAR_LOOKUP", &__lookup_key, 0);
+
 	if (dentry->d_op && dentry->d_op->d_init) {
 		err = dentry->d_op->d_init(dentry);
 		if (err) {
@@ -1999,6 +2003,58 @@ struct dentry *d_alloc(struct dentry * parent, const struct qstr *name)
 }
 EXPORT_SYMBOL(d_alloc);
 
+/**
+ * d_duplicate - duplicate a dentry for combined atomic operation
+ * @dentry: the dentry to duplicate
+ *
+ * Some rename operations need to be combined with another operation
+ * inside the filesystem.
+ * 1/ A cluster filesystem when renaming to an in-use file might need to
+ *   first "silly-rename" that target out of the way before the main rename
+ * 2/ A filesystem that supports white-out might want to create a whiteout
+ *   in place of the file being moved.
+ *
+ * For this they need two dentries which temporarily have the same name,
+ * before one is renamed.  d_duplicate() provides for this.  Given a
+ * positive hashed dentry, it creates a second in-lookup dentry.
+ * Because the original dentry exists, no other thread will try to
+ * create an in-lookup dentry, so there can be no race in this create.
+ *
+ * The caller should d_move() the original to a new name, often via a
+ * rename request, and should call d_lookup_done() on the newly created
+ * dentry.  If the new is instantiated then the old MUST either be moved
+ * or dropped.
+ *
+ * Parent must be locked.
+ *
+ * Returns: an in-lookup dentry, or -ENOMEM.
+ */
+struct dentry *d_duplicate(struct dentry *dentry)
+{
+	unsigned int hash = dentry->d_name.hash;
+	struct dentry *parent = dentry->d_parent;
+	struct hlist_bl_head *b = in_lookup_hash(parent, hash);
+	struct dentry *new = __d_alloc(parent->d_sb, &dentry->d_name);
+
+	if (unlikely(!new))
+		return ERR_PTR(-ENOMEM);
+
+	new->d_flags |= DCACHE_PAR_LOOKUP;
+	lock_map_acquire_try(&new->lookup_map);
+	spin_lock(&parent->d_lock);
+	new->d_parent = dget_dlock(parent);
+	hlist_add_head(&new->d_sib, &parent->d_children);
+	if (parent->d_flags & DCACHE_DISCONNECTED)
+		new->d_flags |= DCACHE_DISCONNECTED;
+	spin_unlock(&parent->d_lock);
+
+	hlist_bl_lock(b);
+	hlist_bl_add_head(&new->d_in_lookup_hash, b);
+	hlist_bl_unlock(b);
+	return new;
+}
+EXPORT_SYMBOL(d_duplicate);
+
 struct dentry *d_alloc_anon(struct super_block *sb)
 {
 	return __d_alloc(sb, NULL);
@@ -2168,7 +2224,6 @@ static void __d_instantiate(struct dentry *dentry, struct inode *inode)
  * (or otherwise set) by the caller to indicate that it is now
  * in use by the dcache.
  */
- 
 void d_instantiate(struct dentry *entry, struct inode * inode)
 {
 	BUG_ON(d_really_is_positive(entry));
@@ -2750,6 +2805,14 @@ static inline void end_dir_add(struct inode *dir, unsigned int n)
 static void d_wait_lookup(struct dentry *dentry)
 {
 	if (likely(d_in_lookup(dentry))) {
+		/*
+		 * Tell lockdep we will wait for the lookup lock, after
+		 * dropping ->d_lock, but won't actually take it.
+		 */
+		spin_release(&dentry->d_lock.dep_map, 0);
+		lock_map_sync(&dentry->lookup_map);
+		spin_acquire(&dentry->d_lock.dep_map, 0, 1, _THIS_IP_);
+
 		dentry->d_flags |= DCACHE_LOOKUP_WAITERS;
 		wait_var_event_spinlock(&dentry->d_flags,
 					!d_in_lookup(dentry),
@@ -2757,8 +2820,16 @@ static void d_wait_lookup(struct dentry *dentry)
 	}
 }
 
-struct dentry *d_alloc_parallel(struct dentry *parent,
-				const struct qstr *name)
+/* What to do when __d_alloc_parallel finds a d_in_lookup dentry */
+enum alloc_para {
+	ALLOC_PARA_WAIT,
+	ALLOC_PARA_FAIL,
+};
+
+static inline
+struct dentry *__d_alloc_parallel(struct dentry *parent,
+				  const struct qstr *name,
+				  enum alloc_para how)
 {
 	unsigned int hash = name->hash;
 	struct hlist_bl_head *b = in_lookup_hash(parent, hash);
@@ -2831,6 +2902,12 @@ struct dentry *d_alloc_parallel(struct dentry *parent,
 			spin_unlock(&dentry->d_lock);
 			goto retry;
 		}
+		if (unlikely(how == ALLOC_PARA_FAIL)) {
+			/* mustn't wait for concurrent lookup to complete */
+			spin_unlock(&dentry->d_lock);
+			dput(new);
+			return ERR_PTR(-EWOULDBLOCK);
+		}
 		/*
 		 * somebody is likely to be still doing lookup for it;
 		 * pin it and wait for them to finish
@@ -2858,14 +2935,77 @@ struct dentry *d_alloc_parallel(struct dentry *parent,
 	}
 	hlist_bl_add_head(&new->d_in_lookup_hash, b);
 	hlist_bl_unlock(b);
+	lock_map_acquire_try(&new->lookup_map);
 	return new;
 mismatch:
 	spin_unlock(&dentry->d_lock);
 	dput(dentry);
 	goto retry;
 }
+
+/**
+ * d_alloc_parallel() - allocate a new dentry and ensure uniqueness
+ * @parent: dentry of the parent
+ * @name:   name of the dentry within that parent.
+ *
+ * A new dentry is allocated and, providing it is unique, added to the
+ * relevant index.
+ * If an existing dentry is found with the same parent/name that is
+ * not d_in_lookup(), then that is returned instead.
+ * If the existing dentry is d_in_lookup(), d_alloc_parallel() waits for
+ * that lookup to complete before returning the dentry and then ensures the
+ * match is still valid.
+ * Thus if the returned dentry is d_in_lookup() then the caller has
+ * exclusive access until it completes the lookup.
+ * If the returned dentry is not d_in_lookup() then a lookup has
+ * already completed.
+ *
+ * The @name must already have ->hash set, as can be achieved
+ * by e.g. try_lookup_noperm().
+ *
+ * Returns: the dentry, whether found or allocated, or an error %-ENOMEM.
+ */
+struct dentry *d_alloc_parallel(struct dentry *parent,
+				const struct qstr *name)
+{
+	return __d_alloc_parallel(parent, name, ALLOC_PARA_WAIT);
+}
 EXPORT_SYMBOL(d_alloc_parallel);
 
+/**
+ * d_alloc_trylock() - find or allocate a new dentry
+ * @parent: dentry of the parent
+ * @name:   name of the dentry within that parent.
+ *
+ * A new dentry is allocated and, providing it is unique, added to the
+ * relevant index.
+ * If an existing dentry is found with the same parent/name that is
+ * not d_in_lookup() then that is returned instead.
+ * If the existing dentry is d_in_lookup(), d_alloc_trylock()
+ * returns with error %-EWOULDBLOCK.
+ * Thus if the returned dentry is d_in_lookup() then the caller has
+ * exclusive access until it completes the lookup.
+ * If the returned dentry is not d_in_lookup() then a lookup has
+ * already completed.
+ *
+ * The @name need not already have ->hash set.
+ *
+ * Returns: the dentry, whether found or allocated, or an error
+ *    %-ENOMEM, %-EWOULDBLOCK, %-EACCES (for a bad name) or
+ *    anything returned by ->d_hash().
+ */
+struct dentry *d_alloc_trylock(struct dentry *parent,
+			       struct qstr *name)
+{
+	struct dentry *de;
+
+	de = try_lookup_noperm(name, parent);
+	if (!de)
+		de = __d_alloc_parallel(parent, name, ALLOC_PARA_FAIL);
+	return de;
+}
+EXPORT_SYMBOL(d_alloc_trylock);
+
 /*
  * Move dentry from in-lookup state to busy-negative one.
  *
@@ -2894,6 +3034,7 @@ static void __d_lookup_unhash(struct dentry *dentry)
 	b = in_lookup_hash(dentry->d_parent, dentry->d_name.hash);
 	hlist_bl_lock(b);
 	dentry->d_flags &= ~DCACHE_PAR_LOOKUP;
+	lock_map_release(&dentry->lookup_map);
 	__hlist_bl_del(&dentry->d_in_lookup_hash);
 	hlist_bl_unlock(b);
 	dentry->waiters = NULL;
@@ -2931,15 +3072,10 @@ static inline void __d_add(struct dentry *dentry, struct inode *inode,
 	}
 	if (unlikely(ops))
 		d_set_d_op(dentry, ops);
-	if (inode) {
-		unsigned add_flags = d_flags_for_inode(inode);
-		hlist_add_head(&dentry->d_alias, &inode->i_dentry);
-		raw_write_seqcount_begin(&dentry->d_seq);
-		__d_set_inode_and_type(dentry, inode, add_flags);
-		raw_write_seqcount_end(&dentry->d_seq);
-		fsnotify_update_flags(dentry);
-	}
-	__d_rehash(dentry);
+	if (inode)
+		__d_instantiate(dentry, inode);
+	if (d_unhashed(dentry))
+		__d_rehash(dentry);
 	if (dir) {
 		end_dir_add(dir, n);
 		__d_wake_in_lookup_waiters(dentry);
@@ -3241,7 +3377,7 @@ struct dentry *d_splice_alias_ops(struct inode *inode, struct dentry *dentry,
 	if (IS_ERR(inode))
 		return ERR_CAST(inode);
 
-	BUG_ON(!d_unhashed(dentry));
+	BUG_ON(d_really_is_positive(dentry));
 
 	if (!inode)
 		goto out;
@@ -3297,6 +3433,8 @@ struct dentry *d_splice_alias_ops(struct inode *inode, struct dentry *dentry,
  * @inode:  the inode which may have a disconnected dentry
  * @dentry: a negative dentry which we want to point to the inode.
  *
+ * @dentry must be negative and may be in-lookup or unhashed or hashed.
+ *
  * If inode is a directory and has an IS_ROOT alias, then d_move that in
  * place of the given dentry and return it, else simply d_add the inode
  * to the dentry and return NULL.
@@ -3304,16 +3442,14 @@ struct dentry *d_splice_alias_ops(struct inode *inode, struct dentry *dentry,
  * If a non-IS_ROOT directory is found, the filesystem is corrupt, and
  * we should error out: directories can't have multiple aliases.
  *
- * This is needed in the lookup routine of any filesystem that is exportable
- * (via knfsd) so that we can build dcache paths to directories effectively.
+ * This should be used to return the result of ->lookup() and to
+ * instantiate the result of ->mkdir(), is often useful for
+ * ->atomic_open, and may be used to instantiate other objects.
  *
  * If a dentry was found and moved, then it is returned.  Otherwise NULL
- * is returned.  This matches the expected return value of ->lookup.
+ * is returned.  This matches the expected return value of ->lookup and
+ * ->mkdir.
  *
- * Cluster filesystems may call this function with a negative, hashed dentry.
- * In that case, we know that the inode will be a regular file, and also this
- * will only occur during atomic_open. So we need to check for the dentry
- * being already hashed only in the final case.
  */
 struct dentry *d_splice_alias(struct inode *inode, struct dentry *dentry)
 {
diff --git a/fs/namei.c b/fs/namei.c
index 20a6534ea3eff..e978a75eb8a14 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -1933,7 +1933,7 @@ static noinline struct dentry *lookup_slow(const struct qstr *name,
 	struct inode *inode = dir->d_inode;
 	struct dentry *res;
 	inode_lock_shared(inode);
-	res = __lookup_slow(name, dir, flags);
+	res = __lookup_slow(name, dir, flags | LOOKUP_SHARED);
 	inode_unlock_shared(inode);
 	return res;
 }
@@ -1947,7 +1947,7 @@ static struct dentry *lookup_slow_killable(const struct qstr *name,
 
 	if (inode_lock_shared_killable(inode))
 		return ERR_PTR(-EINTR);
-	res = __lookup_slow(name, dir, flags);
+	res = __lookup_slow(name, dir, flags | LOOKUP_SHARED);
 	inode_unlock_shared(inode);
 	return res;
 }
@@ -2946,8 +2946,8 @@ struct dentry *start_dirop(struct dentry *parent, struct qstr *name,
  * end_dirop - signal completion of a dirop
  * @de: the dentry which was returned by start_dirop or similar.
  *
- * If the de is an error, nothing happens. Otherwise any lock taken to
- * protect the dentry is dropped and the dentry itself is release (dput()).
+ * If the @de is an error, nothing happens. Otherwise any lock taken to
+ * protect the dentry is dropped and the dentry itself is released (dput()).
  */
 void end_dirop(struct dentry *de)
 {
@@ -3210,7 +3210,7 @@ EXPORT_SYMBOL(lookup_one);
 /**
  * lookup_one_unlocked - lookup single pathname component
  * @idmap:	idmap of the mount the lookup is performed from
- * @name:	qstr olding pathname component to lookup
+ * @name:	qstr holding pathname component to lookup
  * @base:	base directory to lookup from
  *
  * This can be used for in-kernel filesystem clients such as file servers.
@@ -3243,7 +3243,7 @@ EXPORT_SYMBOL(lookup_one_unlocked);
 /**
  * lookup_one_positive_killable - lookup single pathname component
  * @idmap:	idmap of the mount the lookup is performed from
- * @name:	qstr olding pathname component to lookup
+ * @name:	qstr holding pathname component to lookup
  * @base:	base directory to lookup from
  *
  * This helper will yield ERR_PTR(-ENOENT) on negatives. The helper returns
@@ -3259,7 +3259,7 @@ EXPORT_SYMBOL(lookup_one_unlocked);
  * the i_rwsem itself if necessary.  If a fatal signal is pending or
  * delivered, it will return %-EINTR if the lock is needed.
  *
- * Returns: A dentry, possibly negative, or
+ * Returns: A positive dentry, or
  *	   - same errors as lookup_one_unlocked() or
  *	   - ERR_PTR(-EINTR) if a fatal signal is pending.
  */
@@ -3381,7 +3381,7 @@ struct dentry *lookup_noperm_positive_unlocked(struct qstr *name,
 EXPORT_SYMBOL(lookup_noperm_positive_unlocked);
 
 /**
- * start_creating - prepare to create a given name with permission checking
+ * start_creating - prepare to access or create a given name with permission checking
  * @idmap:  idmap of the mount
  * @parent: directory in which to prepare to create the name
  * @name:   the name to be created
@@ -3413,8 +3413,8 @@ EXPORT_SYMBOL(start_creating);
  * @parent: directory in which to find the name
  * @name:   the name to be removed
  *
- * Locks are taken and a lookup in performed prior to removing
- * an object from a directory.  Permission checking (MAY_EXEC) is performed
+ * Locks are taken and a lookup is performed prior to removing an object
+ * from a directory.  Permission checking (MAY_EXEC) is performed
  * against @idmap.
  *
  * If the name doesn't exist, an error is returned.
@@ -3440,7 +3440,7 @@ EXPORT_SYMBOL(start_removing);
  * @parent: directory in which to prepare to create the name
  * @name:   the name to be created
  *
- * Locks are taken and a lookup in performed prior to creating
+ * Locks are taken and a lookup is performed prior to creating
  * an object in a directory.  Permission checking (MAY_EXEC) is performed
  * against @idmap.
  *
@@ -3469,7 +3469,7 @@ EXPORT_SYMBOL(start_creating_killable);
  * @parent: directory in which to find the name
  * @name:   the name to be removed
  *
- * Locks are taken and a lookup in performed prior to removing
+ * Locks are taken and a lookup is performed prior to removing
  * an object from a directory.  Permission checking (MAY_EXEC) is performed
  * against @idmap.
  *
@@ -3499,7 +3499,7 @@ EXPORT_SYMBOL(start_removing_killable);
  * @parent: directory in which to prepare to create the name
  * @name:   the name to be created
  *
- * Locks are taken and a lookup in performed prior to creating
+ * Locks are taken and a lookup is performed prior to creating
  * an object in a directory.
  *
  * If the name already exists, a positive dentry is returned.
@@ -3522,7 +3522,7 @@ EXPORT_SYMBOL(start_creating_noperm);
  * @parent: directory in which to find the name
  * @name:   the name to be removed
  *
- * Locks are taken and a lookup in performed prior to removing
+ * Locks are taken and a lookup is performed prior to removing
  * an object from a directory.
  *
  * If the name doesn't exist, an error is returned.
@@ -3543,11 +3543,11 @@ struct dentry *start_removing_noperm(struct dentry *parent,
 EXPORT_SYMBOL(start_removing_noperm);
 
 /**
- * start_creating_dentry - prepare to create a given dentry
- * @parent: directory from which dentry should be removed
- * @child:  the dentry to be removed
+ * start_creating_dentry - prepare to access or create a given dentry
+ * @parent: directory of dentry
+ * @child:  the dentry to be prepared
  *
- * A lock is taken to protect the dentry again other dirops and
+ * A lock is taken to protect the dentry against other dirops and
  * the validity of the dentry is checked: correct parent and still hashed.
  *
  * If the dentry is valid and negative a reference is taken and
@@ -3580,7 +3580,7 @@ EXPORT_SYMBOL(start_creating_dentry);
  * @parent: directory from which dentry should be removed
  * @child:  the dentry to be removed
  *
- * A lock is taken to protect the dentry again other dirops and
+ * A lock is taken to protect the dentry against other dirops and
  * the validity of the dentry is checked: correct parent and still hashed.
  *
  * If the dentry is valid and positive, a reference is taken and
@@ -4440,12 +4440,14 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
 	int error, create_error;
 	umode_t mode;
 	bool got_write;
+	unsigned int shared_flag;
 
 retry:
 	open_flag = op->open_flag;
 	got_write = false;
 	mode = op->mode;
 	create_error = 0;
+	shared_flag = (open_flag & O_CREAT) ? 0 : LOOKUP_SHARED;
 
 	if (open_flag & (O_CREAT | O_TRUNC | O_WRONLY | O_RDWR)) {
 		got_write = !mnt_want_write(nd->path.mnt);
@@ -4454,10 +4456,10 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
 		 * a different error; we'll be dropping this one anyway.
 		 */
 	}
-	if (open_flag & O_CREAT)
-		inode_lock(dir_inode);
-	else
+	if (shared_flag)
 		inode_lock_shared(dir_inode);
+	else
+		inode_lock(dir_inode);
 
 	if (unlikely(IS_DEADDIR(dir_inode))) {
 		dentry = ERR_PTR(-ENOENT);
@@ -4526,7 +4528,7 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
 
 	if (d_in_lookup(dentry)) {
 		struct dentry *res = dir_inode->i_op->lookup(dir_inode, dentry,
-							     nd->flags);
+							     nd->flags | shared_flag);
 		d_lookup_done(dentry);
 		if (unlikely(res)) {
 			if (IS_ERR(res)) {
@@ -4574,10 +4576,10 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
 		if (file->f_mode & FMODE_OPENED)
 			fsnotify_open(file);
 	}
-	if ((open_flag & O_CREAT) || create_error)
-		inode_unlock(dir_inode);
-	else
+	if (shared_flag)
 		inode_unlock_shared(dir_inode);
+	else
+		inode_unlock(dir_inode);
 
 	if (got_write)
 		mnt_drop_write(nd->path.mnt);
diff --git a/fs/nfs/unlink.c b/fs/nfs/unlink.c
index b57cfaa4d516f..c8d712204e649 100644
--- a/fs/nfs/unlink.c
+++ b/fs/nfs/unlink.c
@@ -67,6 +67,7 @@ static void nfs_async_unlink_release(void *calldata)
 	struct super_block *sb = dentry->d_sb;
 
 	up_read_non_owner(&NFS_I(d_inode(dentry->d_parent))->rmdir_sem);
+	d_lookup_acquire(dentry);
 	d_lookup_done(dentry);
 	nfs_free_unlinkdata(data);
 	dput(dentry);
@@ -159,6 +160,8 @@ static int nfs_call_unlink(struct dentry *dentry, struct inode *inode, struct nf
 		return ret;
 	}
 	data->dentry = alias;
+	d_lookup_release(alias);
+
 	nfs_do_call_unlink(inode, data);
 	return 1;
 }
diff --git a/include/linux/dcache.h b/include/linux/dcache.h
index 4b1ff99608e01..e7e3ef05313b8 100644
--- a/include/linux/dcache.h
+++ b/include/linux/dcache.h
@@ -116,6 +116,8 @@ struct dentry {
 					 * possible!
 					 */
 
+	/* lockdep tracking of DCACHE_PAR_LOOKUP locks */
+	struct lockdep_map		lookup_map;
 	struct list_head d_lru;		/* LRU list */
 	struct hlist_node d_sib;	/* child of parent list */
 	struct hlist_head d_children;	/* our children */
@@ -257,7 +259,9 @@ extern void d_delete(struct dentry *);
 extern struct dentry * d_alloc(struct dentry *, const struct qstr *);
 extern struct dentry * d_alloc_anon(struct super_block *);
 extern struct dentry * d_alloc_parallel(struct dentry *, const struct qstr *);
+extern struct dentry * d_alloc_trylock(struct dentry *, struct qstr *);
 extern struct dentry * d_splice_alias(struct inode *, struct dentry *);
+struct dentry *d_duplicate(struct dentry *dentry);
 /* weird procfs mess; *NOT* exported */
 extern struct dentry * d_splice_alias_ops(struct inode *, struct dentry *,
 					  const struct dentry_operations *);
@@ -552,6 +556,36 @@ static inline int simple_positive(const struct dentry *dentry)
 
 unsigned long vfs_pressure_ratio(unsigned long val);
 
+/**
+ * d_lookup_release - release ownership of DCACHE_PAR_LOOKUP lock
+ * @dentry: dentry that is locked
+ *
+ * If an in-lookup dentry is to be passed to another thread which
+ * will drop the in-lookup lock, then d_lookup_release() must be called
+ * to tell lockdep that this thread no lock holds the lock.  The
+ * thread that receives the lock must call d_lookup_acquire() to
+ * acquire the lock.
+ */
+static inline void d_lookup_release(struct dentry *dentry)
+{
+	if (d_in_lookup(dentry))
+		lock_map_release(&dentry->lookup_map);
+}
+
+/**
+ * d_lookup_acquire - acquire ownership of DCACHE_PAR_LOOKUP lock
+ * @dentry: dentry that is locked
+ *
+ * If an in-lookup dentry was passed to this thread, the
+ * d_lookup_acquire() must be called to tell lockdep that this
+ * thread now owns the DCACHE_PAR_LOOKUP lock.
+ */
+static inline void d_lookup_acquire(struct dentry *dentry)
+{
+	if (d_in_lookup(dentry))
+		lock_map_acquire_try(&dentry->lookup_map);
+}
+
 /**
  * d_inode - Get the actual inode of this dentry
  * @dentry: The dentry to query
diff --git a/include/linux/namei.h b/include/linux/namei.h
index 86d657b24fc6a..65f0f5712885c 100644
--- a/include/linux/namei.h
+++ b/include/linux/namei.h
@@ -32,8 +32,9 @@ enum { MAX_NESTED_LINKS = 8 };
 #define LOOKUP_CREATE		BIT(17)	/* ... in object creation */
 #define LOOKUP_EXCL		BIT(18)	/* ... in target must not exist */
 #define LOOKUP_RENAME_TARGET	BIT(19)	/* ... in destination of rename() */
+#define LOOKUP_SHARED		BIT(20) /* Parent lock is held shared */
 
-/* 4 spare bits for intent */
+/* 3 spare bits for intent */
 
 /* Scoping flags for lookup. */
 #define LOOKUP_NO_SYMLINKS	BIT(24) /* No symlink crossing. */

3/2 2026/09/04 04:15 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 11085
							output: 29
							thoughts: 708
						

Thoughts:
**Fuzzing Evaluation: dcache Patch Series**

Okay, so I've been asked to look over this patch series for fuzzing potential with syzkaller. Let's break this down.

First, the diff. The changes are focused on the Virtual File System (VFS), specifically the dcache and namei subsystems, with a little touch of NFS. This is *critical* infrastructure; we're talking about path lookup, dentry allocation, locking, and some important assertions. This is right in the heart of how the kernel interacts with the filesystem, so it's interesting.

The `Documentation/` changes are usually a good sign that they were thinking about fuzzing and trying to keep things easily available for those that want to contribute.

A deeper dive into the diff shows substantial changes: we have a new `lookup_map` under lockdep, new functions like `d_duplicate`, `d_alloc_trylock`, and `d_lookup_acquire/release` are introduced, `__d_add` logic is refactored, and lock handling in `lookup_open` changes based on shared flags. There is even a change from `BUG_ON(!d_unhashed(dentry))` to `BUG_ON(d_really_is_positive(dentry))`, which is always worth a look. The overall goal seems to be enhancing concurrency and handling some new cluster/overlay scenarios.

The modification of `__d_alloc`, `__d_add`, `lookup_open` and `nfs_async_unlink_release` really caught my eye. These functions are all reachable through standard filesystem calls like `open`, `lookup`, `unlink`, and `mkdir`, so, that's already a significant point in its favor.

**My assessment: This is absolutely worth fuzzing.** This has all the hallmarks of a patch series ripe for fuzzing: core subsystem modifications (VFS/dcache), new locking invariants introduced with lockdep, the addition of a lockdep map, modified assertions, changes to the core `__d_add` logic, and the `lookup_open` shared locking change is particularly intriguing. There's a high probability of finding interesting kernel states or race conditions here.

Now, let's look at the usual suspects. I should check `WorthFuzzing`, `FocusSymbols`, and `EnableConfigs` to see if there are any hints or pre-existing setup to make this easier. I need to make sure I don't miss any obvious entry points and that I have the right configuration enabled. Also, a quick `grepper` run for the new functions like `d_alloc_trylock`, and `d_duplicate` is crucial to get a holistic view of the callers. This will give me an idea about the overall scope and if this is a part of a larger patch series.



4/2 2026/09/04 04:16 tool grepper
0m Args:
{
  "Expression": "d_alloc_trylock|d_duplicate|LOOKUP_SHARED"
}

Results:
{
  "Output": "drivers/android/binder/freeze.rs=25=pub(crate) struct FreezeListener {\n--\ndrivers/android/binder/freeze.rs-42-    /// Number of cleared duplicates that can be deleted.\ndrivers/android/binder/freeze.rs:43:    num_cleared_duplicates: u64,\ndrivers/android/binder/freeze.rs-44-}\n--\ndrivers/android/binder/freeze.rs=86=    fn do_work(\n--\ndrivers/android/binder/freeze.rs-97-\ndrivers/android/binder/freeze.rs:98:        if freeze.num_cleared_duplicates \u003e 0 {\ndrivers/android/binder/freeze.rs:99:            freeze.num_cleared_duplicates -= 1;\ndrivers/android/binder/freeze.rs-100-            drop(node_refs);\n--\ndrivers/android/binder/freeze.rs-109-        if freeze.is_clearing {\ndrivers/android/binder/freeze.rs:110:            kernel::warn_on!(freeze.num_cleared_duplicates != 0);\ndrivers/android/binder/freeze.rs-111-            if freeze.num_pending_duplicates \u003e 0 {\n--\ndrivers/android/binder/freeze.rs=180=    pub(crate) fn request_freeze_notif(\n--\ndrivers/android/binder/freeze.rs-249-                        num_pending_duplicates: 0,\ndrivers/android/binder/freeze.rs:250:                        num_cleared_duplicates: 0,\ndrivers/android/binder/freeze.rs-251-                    },\n--\ndrivers/android/binder/freeze.rs-259-                } else {\ndrivers/android/binder/freeze.rs:260:                    dupe.num_cleared_duplicates += 1;\ndrivers/android/binder/freeze.rs-261-                }\n--\ndrivers/android/binder/freeze.rs=275=    pub(crate) fn freeze_notif_done(self: \u0026Arc\u003cSelf\u003e, reader: \u0026mut UserSliceReader) -\u003e Result\u003c()\u003e {\n--\ndrivers/android/binder/freeze.rs-291-            freeze.num_pending_duplicates -= 1;\ndrivers/android/binder/freeze.rs:292:            freeze.num_cleared_duplicates += 1;\ndrivers/android/binder/freeze.rs-293-        } else {\n--\ndrivers/gpu/drm/display/drm_dp_mst_topology.c=4395=struct edid *drm_dp_mst_get_edid(struct drm_connector *connector,\n--\ndrivers/gpu/drm/display/drm_dp_mst_topology.c-4403-\ndrivers/gpu/drm/display/drm_dp_mst_topology.c:4404:\tedid = drm_edid_duplicate(drm_edid_raw(drm_edid));\ndrivers/gpu/drm/display/drm_dp_mst_topology.c-4405-\n--\ndrivers/gpu/drm/drm_edid.c=2950=EXPORT_SYMBOL(drm_edid_read_switcheroo);\n--\ndrivers/gpu/drm/drm_edid.c-2952-/**\ndrivers/gpu/drm/drm_edid.c:2953: * drm_edid_duplicate - duplicate an EDID and the extensions\ndrivers/gpu/drm/drm_edid.c-2954- * @edid: EDID to duplicate\n--\ndrivers/gpu/drm/drm_edid.c-2957- */\ndrivers/gpu/drm/drm_edid.c:2958:struct edid *drm_edid_duplicate(const struct edid *edid)\ndrivers/gpu/drm/drm_edid.c-2959-{\n--\ndrivers/gpu/drm/drm_edid.c-2964-}\ndrivers/gpu/drm/drm_edid.c:2965:EXPORT_SYMBOL(drm_edid_duplicate);\ndrivers/gpu/drm/drm_edid.c-2966-\n--\ndrivers/gpu/drm/i915/display/intel_pmdemand.c=56=static struct intel_global_state *\ndrivers/gpu/drm/i915/display/intel_pmdemand.c:57:intel_pmdemand_duplicate_state(struct intel_global_obj *obj)\ndrivers/gpu/drm/i915/display/intel_pmdemand.c-58-{\n--\ndrivers/gpu/drm/i915/display/intel_pmdemand.c=74=static const struct intel_global_state_funcs intel_pmdemand_funcs = {\ndrivers/gpu/drm/i915/display/intel_pmdemand.c:75:\t.atomic_duplicate_state = intel_pmdemand_duplicate_state,\ndrivers/gpu/drm/i915/display/intel_pmdemand.c-76-\t.atomic_destroy_state = intel_pmdemand_destroy_state,\n--\ndrivers/gpu/drm/msm/adreno/a6xx_gpu_state.h=91=static const u32 a6xx_hlsq_duplicate_cluster[] = {\n--\ndrivers/gpu/drm/msm/adreno/a6xx_gpu_state.h-94-\ndrivers/gpu/drm/msm/adreno/a6xx_gpu_state.h:95:static const u32 a6xx_hlsq_2d_duplicate_cluster[] = {\ndrivers/gpu/drm/msm/adreno/a6xx_gpu_state.h-96-\t0xbd80, 0xbd80,\n--\ndrivers/gpu/drm/msm/adreno/a6xx_gpu_state.h=137=static const struct a6xx_dbgahb_cluster {\n--\ndrivers/gpu/drm/msm/adreno/a6xx_gpu_state.h-146-\tCLUSTER_DBGAHB(CLUSTER_SP_VS, 0x0002e000, 0x41, a6xx_hlsq_duplicate_cluster),\ndrivers/gpu/drm/msm/adreno/a6xx_gpu_state.h:147:\tCLUSTER_DBGAHB(CLUSTER_SP_VS, 0x0002f000, 0x45, a6xx_hlsq_2d_duplicate_cluster),\ndrivers/gpu/drm/msm/adreno/a6xx_gpu_state.h-148-\tCLUSTER_DBGAHB(CLUSTER_SP_VS, 0x0002a000, 0x21, a6xx_sp_duplicate_cluster),\n--\ndrivers/md/dm.c=1274=static size_t dm_dax_recovery_write(struct dax_device *dax_dev, pgoff_t pgoff,\n--\ndrivers/md/dm.c-1297- * with write BIOs flagged with BIO_EMULATES_ZONE_APPEND) and any bio serviced\ndrivers/md/dm.c:1298: * by __send_duplicate_bios().\ndrivers/md/dm.c-1299- *\n--\ndrivers/md/dm.c=1478=static void alloc_multiple_bios(struct bio_list *blist, struct clone_info *ci,\n--\ndrivers/md/dm.c-1507-\ndrivers/md/dm.c:1508:static unsigned int __send_duplicate_bios(struct clone_info *ci, struct dm_target *ti,\ndrivers/md/dm.c-1509-\t\t\t\t\t  unsigned int num_bios, unsigned int *len)\n--\ndrivers/md/dm.c=1537=static void __send_empty_flush(struct clone_info *ci)\n--\ndrivers/md/dm.c-1566-\t\t\tatomic_add(ti-\u003enum_flush_bios, \u0026ci-\u003eio-\u003eio_count);\ndrivers/md/dm.c:1567:\t\t\tbios = __send_duplicate_bios(ci, ti, ti-\u003enum_flush_bios,\ndrivers/md/dm.c-1568-\t\t\t\t\t\t     NULL);\n--\ndrivers/md/dm.c=1606=static void __send_abnormal_io(struct clone_info *ci, struct dm_target *ti,\n--\ndrivers/md/dm.c-1615-\tatomic_add(num_bios, \u0026ci-\u003eio-\u003eio_count);\ndrivers/md/dm.c:1616:\tbios = __send_duplicate_bios(ci, ti, num_bios, \u0026len);\ndrivers/md/dm.c-1617-\t/*\n--\ndrivers/md/dm.c=1902=static void __send_zone_reset_all_native(struct clone_info *ci,\n--\ndrivers/md/dm.c-1907-\tatomic_add(1, \u0026ci-\u003eio-\u003eio_count);\ndrivers/md/dm.c:1908:\tbios = __send_duplicate_bios(ci, ti, 1, NULL);\ndrivers/md/dm.c-1909-\tatomic_sub(1 - bios, \u0026ci-\u003eio-\u003eio_count);\n--\ndrivers/usb/typec/class.c=448=static int increment_duplicated_priority(struct device *dev, void *data)\n--\ndrivers/usb/typec/class.c-462-\ndrivers/usb/typec/class.c:463:static int find_duplicated_priority(struct device *dev, void *data)\ndrivers/usb/typec/class.c-464-{\n--\ndrivers/usb/typec/class.c=475=static int typec_mode_set_priority(struct typec_altmode *alt, const u8 priority)\n--\ndrivers/usb/typec/class.c-482-\twhile (res) {\ndrivers/usb/typec/class.c:483:\t\tres = device_for_each_child(\u0026port-\u003edev, \u0026alt, find_duplicated_priority);\ndrivers/usb/typec/class.c-484-\t\tif (res) {\n--\nfs/dcache.c=2004=EXPORT_SYMBOL(d_alloc);\n--\nfs/dcache.c-2006-/**\nfs/dcache.c:2007: * d_duplicate - duplicate a dentry for combined atomic operation\nfs/dcache.c-2008- * @dentry: the dentry to duplicate\n--\nfs/dcache.c-2017- * For this they need two dentries which temporarily have the same name,\nfs/dcache.c:2018: * before one is renamed.  d_duplicate() provides for this.  Given a\nfs/dcache.c-2019- * positive hashed dentry, it creates a second in-lookup dentry.\n--\nfs/dcache.c-2031- */\nfs/dcache.c:2032:struct dentry *d_duplicate(struct dentry *dentry)\nfs/dcache.c-2033-{\n--\nfs/dcache.c-2055-}\nfs/dcache.c:2056:EXPORT_SYMBOL(d_duplicate);\nfs/dcache.c-2057-\n--\nfs/dcache.c=2973=EXPORT_SYMBOL(d_alloc_parallel);\n--\nfs/dcache.c-2975-/**\nfs/dcache.c:2976: * d_alloc_trylock() - find or allocate a new dentry\nfs/dcache.c-2977- * @parent: dentry of the parent\n--\nfs/dcache.c-2983- * not d_in_lookup() then that is returned instead.\nfs/dcache.c:2984: * If the existing dentry is d_in_lookup(), d_alloc_trylock()\nfs/dcache.c-2985- * returns with error %-EWOULDBLOCK.\n--\nfs/dcache.c-2996- */\nfs/dcache.c:2997:struct dentry *d_alloc_trylock(struct dentry *parent,\nfs/dcache.c-2998-\t\t\t       struct qstr *name)\n--\nfs/dcache.c-3006-}\nfs/dcache.c:3007:EXPORT_SYMBOL(d_alloc_trylock);\nfs/dcache.c-3008-\n--\nfs/namei.c=1929=static noinline struct dentry *lookup_slow(const struct qstr *name,\n--\nfs/namei.c-1935-\tinode_lock_shared(inode);\nfs/namei.c:1936:\tres = __lookup_slow(name, dir, flags | LOOKUP_SHARED);\nfs/namei.c-1937-\tinode_unlock_shared(inode);\n--\nfs/namei.c=1941=static struct dentry *lookup_slow_killable(const struct qstr *name,\n--\nfs/namei.c-1949-\t\treturn ERR_PTR(-EINTR);\nfs/namei.c:1950:\tres = __lookup_slow(name, dir, flags | LOOKUP_SHARED);\nfs/namei.c-1951-\tinode_unlock_shared(inode);\n--\nfs/namei.c=4431=static struct dentry *lookup_open(struct nameidata *nd, struct file *file,\n--\nfs/namei.c-4449-\tcreate_error = 0;\nfs/namei.c:4450:\tshared_flag = (open_flag \u0026 O_CREAT) ? 0 : LOOKUP_SHARED;\nfs/namei.c-4451-\n--\ninclude/drm/drm_edid.h=441=struct edid *drm_get_edid_switcheroo(struct drm_connector *connector,\ninclude/drm/drm_edid.h-442-\t\t\t\t     struct i2c_adapter *adapter);\ninclude/drm/drm_edid.h:443:struct edid *drm_edid_duplicate(const struct edid *edid);\ninclude/drm/drm_edid.h-444-int drm_add_edid_modes(struct drm_connector *connector, struct edid *edid);\n--\ninclude/linux/dcache.h=261=extern struct dentry * d_alloc_parallel(struct dentry *, const struct qstr *);\ninclude/linux/dcache.h:262:extern struct dentry * d_alloc_trylock(struct dentry *, struct qstr *);\ninclude/linux/dcache.h-263-extern struct dentry * d_splice_alias(struct inode *, struct dentry *);\ninclude/linux/dcache.h:264:struct dentry *d_duplicate(struct dentry *dentry);\ninclude/linux/dcache.h-265-/* weird procfs mess; *NOT* exported */\n--\ninclude/linux/namei.h=12=enum { MAX_NESTED_LINKS = 8 };\n--\ninclude/linux/namei.h-34-#define LOOKUP_RENAME_TARGET\tBIT(19)\t/* ... in destination of rename() */\ninclude/linux/namei.h:35:#define LOOKUP_SHARED\t\tBIT(20) /* Parent lock is held shared */\ninclude/linux/namei.h-36-\n"
}

5/2 2026/09/04 04:16 llm patch-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 16250
							output: 233
							thoughts: 978
						

Thoughts:
**Fuzzing Strategy Assessment**

Okay, let's break this down systematically. First, I need to identify the key functions that have been modified or introduced by this patch.  I've gone through the code changes and the relevant functions are: `d_duplicate`, `d_wait_lookup`, `d_alloc_parallel`, `d_alloc_trylock`, `__d_lookup_unhash`, `__d_add`, `d_splice_alias_ops`, `d_splice_alias`, `__d_alloc` from `fs/dcache.c`; `lookup_slow`, `lookup_slow_killable`, and `lookup_open` from `fs/namei.c`; and `nfs_async_unlink_release` and `nfs_call_unlink` from `fs/nfs/unlink.c`. I also see changes in `include/linux/dcache.h` involving `d_lookup_release` and `d_lookup_acquire`.

Now, I need to filter these down to the most relevant targets for fuzzing. Functions like `d_alloc` are potentially too ubiquitous, but  `d_alloc_parallel`, `d_alloc_trylock`, `d_splice_alias`, `d_duplicate`, `lookup_slow`, `lookup_slow_killable`, `lookup_open`, `nfs_call_unlink` and `nfs_async_unlink_release` seem more targeted, and that `lookup_open`'s specific use in `O_CREAT` makes it a good candidate. The aim is to ensure the fuzzing effort is focused on the most interesting and impactful code changes.

I'm also considering potential configuration requirements. VFS and dcache are core features; no specific configuration is strictly required, although `CONFIG_LOCKDEP` is used if lockdep is enabled, and the NFS client uses `CONFIG_NFS_FS`.  For optimal fuzzing, I'll recommend enabling `PROVE_LOCKING` and `LOCKDEP` to exercise the lockdep integrations related to the `dentry->lookup_map`. I'll also want to make sure `CONFIG_NFS_FS` is set, since the changes touch NFS.

Given the scope of the changes – core VFS path lookup, parallel dentry allocation, shared directory locking during lookup and open operations, lockdep annotation for parallel lookups, d_splice_alias assertions, and the dentry helpers -  this is definitely worth fuzzing. The patch touches critical infrastructure and potential bugs in these areas could be quite impactful. The fuzzing should target the following: `d_duplicate`, `d_alloc_parallel`, `d_alloc_trylock`, `d_splice_alias`, `d_splice_alias_ops`, `d_wait_lookup`, `lookup_slow`, `lookup_slow_killable`, `lookup_open`, `nfs_async_unlink_release`, and `nfs_call_unlink`.

Now, I'll need to define the parameters for setting the results, confirming that `WorthFuzzing` is `true`, with clear reasoning about why it's worth fuzzing - the changes in parallel lookups, the locking mechanisms, shared directory locking in `lookup_slow` and `lookup_open`, the new dentry helpers such as `d_duplicate` and `d_alloc_trylock`, dentry lockdep tracking, and assertion changes in `d_splice_alias_ops`. I'll include the identified focus symbols and the suggested configurations: `PROVE_LOCKING`, and `NFS_FS`.  Finally, I will double-check all the parameters and call `set-results`.



6/2 2026/09/04 04:16 tool set-results
0m Args:
{
  "EnableConfigs": [
    "PROVE_LOCKING",
    "NFS_FS"
  ],
  "FocusSymbols": [
    "d_duplicate",
    "d_alloc_parallel",
    "d_alloc_trylock",
    "d_splice_alias",
    "d_splice_alias_ops",
    "d_wait_lookup",
    "lookup_slow",
    "lookup_slow_killable",
    "lookup_open",
    "nfs_async_unlink_release",
    "nfs_call_unlink"
  ],
  "Reasoning": "The patch series modifies core VFS dcache and namei logic, including parallel lookup locking, shared directory locking flags (LOOKUP_SHARED) in lookup_slow and lookup_open, lockdep map tracking for in-lookup dentries, updated assertions in d_splice_alias_ops, new dcache helpers (d_duplicate, d_alloc_trylock), and NFS asynchronous unlink lock tracking. These changes are in core reachable kernel subsystems and directly affect concurrency and invariant enforcement.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "PROVE_LOCKING",
    "NFS_FS"
  ],
  "FocusSymbols": [
    "d_duplicate",
    "d_alloc_parallel",
    "d_alloc_trylock",
    "d_splice_alias",
    "d_splice_alias_ops",
    "d_wait_lookup",
    "lookup_slow",
    "lookup_slow_killable",
    "lookup_open",
    "nfs_async_unlink_release",
    "nfs_call_unlink"
  ],
  "Reasoning": "The patch series modifies core VFS dcache and namei logic, including parallel lookup locking, shared directory locking flags (LOOKUP_SHARED) in lookup_slow and lookup_open, lockdep map tracking for in-lookup dentries, updated assertions in d_splice_alias_ops, new dcache helpers (d_duplicate, d_alloc_trylock), and NFS asynchronous unlink lock tracking. These changes are in core reachable kernel subsystems and directly affect concurrency and invariant enforcement.",
  "WorthFuzzing": true
}

7/1 2026/09/04 04:16 action If
0m Args:
{
  "WorthFuzzing": true
}

Results:
null

8/2 2026/09/04 04:16 agent kmsan-evaluator
0m Model:
core

Args:
null

Results:
{
  "KMSANReasoning": "The patch series introduces Lockdep tracking for parallel dentry lookups (DCACHE_PAR_LOOKUP via struct lockdep_map in struct dentry), adds the d_duplicate and d_alloc_trylock helpers, updates locking flags (LOOKUP_SHARED) in VFS lookup routines (lookup_slow, lookup_open), and fixes filesystem documentation.\n\nNone of the changes introduce or modify uninitialized memory reads, buffer boundaries, or kernel-to-userspace data transfers. All modified data structures (e.g., struct dentry in __d_alloc) are properly initialized upon allocation. Any concurrency, deadlock, or refcounting issues resulting from these VFS locking/lookup refactorings are tracked and detected by standard detectors such as LOCKDEP and KASAN. Therefore, dedicated KMSAN fuzzing is not required.",
  "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 d9a12ec1e5fe3f3e577d3b61836d86e6699430ea
Author: syz-cluster <triage@syzkaller.com>
Date:   Fri Sep 4 04:15:56 2026 +0000

    syz-cluster: applied patch under review

diff --git a/Documentation/filesystems/porting.rst b/Documentation/filesystems/porting.rst
index 60880eb0c49d1..4e015f1bf1f84 100644
--- a/Documentation/filesystems/porting.rst
+++ b/Documentation/filesystems/porting.rst
@@ -1203,16 +1203,16 @@ will fail-safe.
 
 ---
 
-** mandatory**
+**mandatory**
 
 lookup_one(), lookup_one_unlocked(), lookup_one_positive_unlocked() now
 take a qstr instead of a name and len.  These, not the "one_len"
 versions, should be used whenever accessing a filesystem from outside
-that filesysmtem, through a mount point - which will have a mnt_idmap.
+that filesystem, through a mount point - which will have a mnt_idmap.
 
 ---
 
-** mandatory**
+**mandatory**
 
 Functions try_lookup_one_len(), lookup_one_len(),
 lookup_one_len_unlocked() and lookup_positive_unlocked() have been
@@ -1229,7 +1229,7 @@ already been performed such as after vfs_path_parent_lookup()
 
 ---
 
-** mandatory**
+**mandatory**
 
 d_hash_and_lookup() is no longer exported or available outside the VFS.
 Use try_lookup_noperm() instead.  This adds name validation and takes
@@ -1370,7 +1370,7 @@ similar.
 
 ---
 
-** mandatory**
+**mandatory**
 
 lock_rename(), lock_rename_child(), unlock_rename() are no
 longer available.  Use start_renaming() or similar.
diff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst
index d3a93eec3945f..de8f8502056e8 100644
--- a/Documentation/filesystems/vfs.rst
+++ b/Documentation/filesystems/vfs.rst
@@ -507,8 +507,8 @@ otherwise noted.
 	dentry before the first mkdir returns.
 
 	If there is any chance this could happen, then the new inode
-	should be d_drop()ed and attached with d_splice_alias().  The
-	returned dentry (if any) should be returned by ->mkdir().
+	should be attached with d_splice_alias().  The returned
+	dentry (if any) should be returned by ->mkdir().
 
 ``rmdir``
 	called by the rmdir(2) system call.  Only required if you want
diff --git a/fs/dcache.c b/fs/dcache.c
index 1b1a81f10da67..3af8b2ac699f9 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -32,6 +32,7 @@
 #include <linux/bit_spinlock.h>
 #include <linux/rculist_bl.h>
 #include <linux/list_lru.h>
+#include <linux/namei.h>
 #include "internal.h"
 #include "mount.h"
 
@@ -1900,6 +1901,7 @@ EXPORT_SYMBOL(d_invalidate);
  
 static struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name)
 {
+	static struct lock_class_key __lookup_key;
 	struct dentry *dentry;
 	char *dname;
 	int err;
@@ -1957,6 +1959,8 @@ static struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name)
 	dentry->waiters = NULL;
 	INIT_HLIST_NODE(&dentry->d_sib);
 
+	lockdep_init_map(&dentry->lookup_map, "DCACHE_PAR_LOOKUP", &__lookup_key, 0);
+
 	if (dentry->d_op && dentry->d_op->d_init) {
 		err = dentry->d_op->d_init(dentry);
 		if (err) {
@@ -1999,6 +2003,58 @@ struct dentry *d_alloc(struct dentry * parent, const struct qstr *name)
 }
 EXPORT_SYMBOL(d_alloc);
 
+/**
+ * d_duplicate - duplicate a dentry for combined atomic operation
+ * @dentry: the dentry to duplicate
+ *
+ * Some rename operations need to be combined with another operation
+ * inside the filesystem.
+ * 1/ A cluster filesystem when renaming to an in-use file might need to
+ *   first "silly-rename" that target out of the way before the main rename
+ * 2/ A filesystem that supports white-out might want to create a whiteout
+ *   in place of the file being moved.
+ *
+ * For this they need two dentries which temporarily have the same name,
+ * before one is renamed.  d_duplicate() provides for this.  Given a
+ * positive hashed dentry, it creates a second in-lookup dentry.
+ * Because the original dentry exists, no other thread will try to
+ * create an in-lookup dentry, so there can be no race in this create.
+ *
+ * The caller should d_move() the original to a new name, often via a
+ * rename request, and should call d_lookup_done() on the newly created
+ * dentry.  If the new is instantiated then the old MUST either be moved
+ * or dropped.
+ *
+ * Parent must be locked.
+ *
+ * Returns: an in-lookup dentry, or -ENOMEM.
+ */
+struct dentry *d_duplicate(struct dentry *dentry)
+{
+	unsigned int hash = dentry->d_name.hash;
+	struct dentry *parent = dentry->d_parent;
+	struct hlist_bl_head *b = in_lookup_hash(parent, hash);
+	struct dentry *new = __d_alloc(parent->d_sb, &dentry->d_name);
+
+	if (unlikely(!new))
+		return ERR_PTR(-ENOMEM);
+
+	new->d_flags |= DCACHE_PAR_LOOKUP;
+	lock_map_acquire_try(&new->lookup_map);
+	spin_lock(&parent->d_lock);
+	new->d_parent = dget_dlock(parent);
+	hlist_add_head(&new->d_sib, &parent->d_children);
+	if (parent->d_flags & DCACHE_DISCONNECTED)
+		new->d_flags |= DCACHE_DISCONNECTED;
+	spin_unlock(&parent->d_lock);
+
+	hlist_bl_lock(b);
+	hlist_bl_add_head(&new->d_in_lookup_hash, b);
+	hlist_bl_unlock(b);
+	return new;
+}
+EXPORT_SYMBOL(d_duplicate);
+
 struct dentry *d_alloc_anon(struct super_block *sb)
 {
 	return __d_alloc(sb, NULL);
@@ -2168,7 +2224,6 @@ static void __d_instantiate(struct dentry *dentry, struct inode *inode)
  * (or otherwise set) by the caller to indicate that it is now
  * in use by the dcache.
  */
- 
 void d_instantiate(struct dentry *entry, struct inode * inode)
 {
 	BUG_ON(d_really_is_positive(entry));
@@ -2750,6 +2805,14 @@ static inline void end_dir_add(struct inode *dir, unsigned int n)
 static void d_wait_lookup(struct dentry *dentry)
 {
 	if (likely(d_in_lookup(dentry))) {
+		/*
+		 * Tell lockdep we will wait for the lookup lock, after
+		 * dropping ->d_lock, but won't actually take it.
+		 */
+		spin_release(&dentry->d_lock.dep_map, 0);
+		lock_map_sync(&dentry->lookup_map);
+		spin_acquire(&dentry->d_lock.dep_map, 0, 1, _THIS_IP_);
+
 		dentry->d_flags |= DCACHE_LOOKUP_WAITERS;
 		wait_var_event_spinlock(&dentry->d_flags,
 					!d_in_lookup(dentry),
@@ -2757,8 +2820,16 @@ static void d_wait_lookup(struct dentry *dentry)
 	}
 }
 
-struct dentry *d_alloc_parallel(struct dentry *parent,
-				const struct qstr *name)
+/* What to do when __d_alloc_parallel finds a d_in_lookup dentry */
+enum alloc_para {
+	ALLOC_PARA_WAIT,
+	ALLOC_PARA_FAIL,
+};
+
+static inline
+struct dentry *__d_alloc_parallel(struct dentry *parent,
+				  const struct qstr *name,
+				  enum alloc_para how)
 {
 	unsigned int hash = name->hash;
 	struct hlist_bl_head *b = in_lookup_hash(parent, hash);
@@ -2831,6 +2902,12 @@ struct dentry *d_alloc_parallel(struct dentry *parent,
 			spin_unlock(&dentry->d_lock);
 			goto retry;
 		}
+		if (unlikely(how == ALLOC_PARA_FAIL)) {
+			/* mustn't wait for concurrent lookup to complete */
+			spin_unlock(&dentry->d_lock);
+			dput(new);
+			return ERR_PTR(-EWOULDBLOCK);
+		}
 		/*
 		 * somebody is likely to be still doing lookup for it;
 		 * pin it and wait for them to finish
@@ -2858,14 +2935,77 @@ struct dentry *d_alloc_parallel(struct dentry *parent,
 	}
 	hlist_bl_add_head(&new->d_in_lookup_hash, b);
 	hlist_bl_unlock(b);
+	lock_map_acquire_try(&new->lookup_map);
 	return new;
 mismatch:
 	spin_unlock(&dentry->d_lock);
 	dput(dentry);
 	goto retry;
 }
+
+/**
+ * d_alloc_parallel() - allocate a new dentry and ensure uniqueness
+ * @parent: dentry of the parent
+ * @name:   name of the dentry within that parent.
+ *
+ * A new dentry is allocated and, providing it is unique, added to the
+ * relevant index.
+ * If an existing dentry is found with the same parent/name that is
+ * not d_in_lookup(), then that is returned instead.
+ * If the existing dentry is d_in_lookup(), d_alloc_parallel() waits for
+ * that lookup to complete before returning the dentry and then ensures the
+ * match is still valid.
+ * Thus if the returned dentry is d_in_lookup() then the caller has
+ * exclusive access until it completes the lookup.
+ * If the returned dentry is not d_in_lookup() then a lookup has
+ * already completed.
+ *
+ * The @name must already have ->hash set, as can be achieved
+ * by e.g. try_lookup_noperm().
+ *
+ * Returns: the dentry, whether found or allocated, or an error %-ENOMEM.
+ */
+struct dentry *d_alloc_parallel(struct dentry *parent,
+				const struct qstr *name)
+{
+	return __d_alloc_parallel(parent, name, ALLOC_PARA_WAIT);
+}
 EXPORT_SYMBOL(d_alloc_parallel);
 
+/**
+ * d_alloc_trylock() - find or allocate a new dentry
+ * @parent: dentry of the parent
+ * @name:   name of the dentry within that parent.
+ *
+ * A new dentry is allocated and, providing it is unique, added to the
+ * relevant index.
+ * If an existing dentry is found with the same parent/name that is
+ * not d_in_lookup() then that is returned instead.
+ * If the existing dentry is d_in_lookup(), d_alloc_trylock()
+ * returns with error %-EWOULDBLOCK.
+ * Thus if the returned dentry is d_in_lookup() then the caller has
+ * exclusive access until it completes the lookup.
+ * If the returned dentry is not d_in_lookup() then a lookup has
+ * already completed.
+ *
+ * The @name need not already have ->hash set.
+ *
+ * Returns: the dentry, whether found or allocated, or an error
+ *    %-ENOMEM, %-EWOULDBLOCK, %-EACCES (for a bad name) or
+ *    anything returned by ->d_hash().
+ */
+struct dentry *d_alloc_trylock(struct dentry *parent,
+			       struct qstr *name)
+{
+	struct dentry *de;
+
+	de = try_lookup_noperm(name, parent);
+	if (!de)
+		de = __d_alloc_parallel(parent, name, ALLOC_PARA_FAIL);
+	return de;
+}
+EXPORT_SYMBOL(d_alloc_trylock);
+
 /*
  * Move dentry from in-lookup state to busy-negative one.
  *
@@ -2894,6 +3034,7 @@ static void __d_lookup_unhash(struct dentry *dentry)
 	b = in_lookup_hash(dentry->d_parent, dentry->d_name.hash);
 	hlist_bl_lock(b);
 	dentry->d_flags &= ~DCACHE_PAR_LOOKUP;
+	lock_map_release(&dentry->lookup_map);
 	__hlist_bl_del(&dentry->d_in_lookup_hash);
 	hlist_bl_unlock(b);
 	dentry->waiters = NULL;
@@ -2931,15 +3072,10 @@ static inline void __d_add(struct dentry *dentry, struct inode *inode,
 	}
 	if (unlikely(ops))
 		d_set_d_op(dentry, ops);
-	if (inode) {
-		unsigned add_flags = d_flags_for_inode(inode);
-		hlist_add_head(&dentry->d_alias, &inode->i_dentry);
-		raw_write_seqcount_begin(&dentry->d_seq);
-		__d_set_inode_and_type(dentry, inode, add_flags);
-		raw_write_seqcount_end(&dentry->d_seq);
-		fsnotify_update_flags(dentry);
-	}
-	__d_rehash(dentry);
+	if (inode)
+		__d_instantiate(dentry, inode);
+	if (d_unhashed(dentry))
+		__d_rehash(dentry);
 	if (dir) {
 		end_dir_add(dir, n);
 		__d_wake_in_lookup_waiters(dentry);
@@ -3241,7 +3377,7 @@ struct dentry *d_splice_alias_ops(struct inode *inode, struct dentry *dentry,
 	if (IS_ERR(inode))
 		return ERR_CAST(inode);
 
-	BUG_ON(!d_unhashed(dentry));
+	BUG_ON(d_really_is_positive(dentry));
 
 	if (!inode)
 		goto out;
@@ -3297,6 +3433,8 @@ struct dentry *d_splice_alias_ops(struct inode *inode, struct dentry *dentry,
  * @inode:  the inode which may have a disconnected dentry
  * @dentry: a negative dentry which we want to point to the inode.
  *
+ * @dentry must be negative and may be in-lookup or unhashed or hashed.
+ *
  * If inode is a directory and has an IS_ROOT alias, then d_move that in
  * place of the given dentry and return it, else simply d_add the inode
  * to the dentry and return NULL.
@@ -3304,16 +3442,14 @@ struct dentry *d_splice_alias_ops(struct inode *inode, struct dentry *dentry,
  * If a non-IS_ROOT directory is found, the filesystem is corrupt, and
  * we should error out: directories can't have multiple aliases.
  *
- * This is needed in the lookup routine of any filesystem that is exportable
- * (via knfsd) so that we can build dcache paths to directories effectively.
+ * This should be used to return the result of ->lookup() and to
+ * instantiate the result of ->mkdir(), is often useful for
+ * ->atomic_open, and may be used to instantiate other objects.
  *
  * If a dentry was found and moved, then it is returned.  Otherwise NULL
- * is returned.  This matches the expected return value of ->lookup.
+ * is returned.  This matches the expected return value of ->lookup and
+ * ->mkdir.
  *
- * Cluster filesystems may call this function with a negative, hashed dentry.
- * In that case, we know that the inode will be a regular file, and also this
- * will only occur during atomic_open. So we need to check for the dentry
- * being already hashed only in the final case.
  */
 struct dentry *d_splice_alias(struct inode *inode, struct dentry *dentry)
 {
diff --git a/fs/namei.c b/fs/namei.c
index 20a6534ea3eff..e978a75eb8a14 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -1933,7 +1933,7 @@ static noinline struct dentry *lookup_slow(const struct qstr *name,
 	struct inode *inode = dir->d_inode;
 	struct dentry *res;
 	inode_lock_shared(inode);
-	res = __lookup_slow(name, dir, flags);
+	res = __lookup_slow(name, dir, flags | LOOKUP_SHARED);
 	inode_unlock_shared(inode);
 	return res;
 }
@@ -1947,7 +1947,7 @@ static struct dentry *lookup_slow_killable(const struct qstr *name,
 
 	if (inode_lock_shared_killable(inode))
 		return ERR_PTR(-EINTR);
-	res = __lookup_slow(name, dir, flags);
+	res = __lookup_slow(name, dir, flags | LOOKUP_SHARED);
 	inode_unlock_shared(inode);
 	return res;
 }
@@ -2946,8 +2946,8 @@ struct dentry *start_dirop(struct dentry *parent, struct qstr *name,
  * end_dirop - signal completion of a dirop
  * @de: the dentry which was returned by start_dirop or similar.
  *
- * If the de is an error, nothing happens. Otherwise any lock taken to
- * protect the dentry is dropped and the dentry itself is release (dput()).
+ * If the @de is an error, nothing happens. Otherwise any lock taken to
+ * protect the dentry is dropped and the dentry itself is released (dput()).
  */
 void end_dirop(struct dentry *de)
 {
@@ -3210,7 +3210,7 @@ EXPORT_SYMBOL(lookup_one);
 /**
  * lookup_one_unlocked - lookup single pathname component
  * @idmap:	idmap of the mount the lookup is performed from
- * @name:	qstr olding pathname component to lookup
+ * @name:	qstr holding pathname component to lookup
  * @base:	base directory to lookup from
  *
  * This can be used for in-kernel filesystem clients such as file servers.
@@ -3243,7 +3243,7 @@ EXPORT_SYMBOL(lookup_one_unlocked);
 /**
  * lookup_one_positive_killable - lookup single pathname component
  * @idmap:	idmap of the mount the lookup is performed from
- * @name:	qstr olding pathname component to lookup
+ * @name:	qstr holding pathname component to lookup
  * @base:	base directory to lookup from
  *
  * This helper will yield ERR_PTR(-ENOENT) on negatives. The helper returns
@@ -3259,7 +3259,7 @@ EXPORT_SYMBOL(lookup_one_unlocked);
  * the i_rwsem itself if necessary.  If a fatal signal is pending or
  * delivered, it will return %-EINTR if the lock is needed.
  *
- * Returns: A dentry, possibly negative, or
+ * Returns: A positive dentry, or
  *	   - same errors as lookup_one_unlocked() or
  *	   - ERR_PTR(-EINTR) if a fatal signal is pending.
  */
@@ -3381,7 +3381,7 @@ struct dentry *lookup_noperm_positive_unlocked(struct qstr *name,
 EXPORT_SYMBOL(lookup_noperm_positive_unlocked);
 
 /**
- * start_creating - prepare to create a given name with permission checking
+ * start_creating - prepare to access or create a given name with permission checking
  * @idmap:  idmap of the mount
  * @parent: directory in which to prepare to create the name
  * @name:   the name to be created
@@ -3413,8 +3413,8 @@ EXPORT_SYMBOL(start_creating);
  * @parent: directory in which to find the name
  * @name:   the name to be removed
  *
- * Locks are taken and a lookup in performed prior to removing
- * an object from a directory.  Permission checking (MAY_EXEC) is performed
+ * Locks are taken and a lookup is performed prior to removing an object
+ * from a directory.  Permission checking (MAY_EXEC) is performed
  * against @idmap.
  *
  * If the name doesn't exist, an error is returned.
@@ -3440,7 +3440,7 @@ EXPORT_SYMBOL(start_removing);
  * @parent: directory in which to prepare to create the name
  * @name:   the name to be created
  *
- * Locks are taken and a lookup in performed prior to creating
+ * Locks are taken and a lookup is performed prior to creating
  * an object in a directory.  Permission checking (MAY_EXEC) is performed
  * against @idmap.
  *
@@ -3469,7 +3469,7 @@ EXPORT_SYMBOL(start_creating_killable);
  * @parent: directory in which to find the name
  * @name:   the name to be removed
  *
- * Locks are taken and a lookup in performed prior to removing
+ * Locks are taken and a lookup is performed prior to removing
  * an object from a directory.  Permission checking (MAY_EXEC) is performed
  * against @idmap.
  *
@@ -3499,7 +3499,7 @@ EXPORT_SYMBOL(start_removing_killable);
  * @parent: directory in which to prepare to create the name
  * @name:   the name to be created
  *
- * Locks are taken and a lookup in performed prior to creating
+ * Locks are taken and a lookup is performed prior to creating
  * an object in a directory.
  *
  * If the name already exists, a positive dentry is returned.
@@ -3522,7 +3522,7 @@ EXPORT_SYMBOL(start_creating_noperm);
  * @parent: directory in which to find the name
  * @name:   the name to be removed
  *
- * Locks are taken and a lookup in performed prior to removing
+ * Locks are taken and a lookup is performed prior to removing
  * an object from a directory.
  *
  * If the name doesn't exist, an error is returned.
@@ -3543,11 +3543,11 @@ struct dentry *start_removing_noperm(struct dentry *parent,
 EXPORT_SYMBOL(start_removing_noperm);
 
 /**
- * start_creating_dentry - prepare to create a given dentry
- * @parent: directory from which dentry should be removed
- * @child:  the dentry to be removed
+ * start_creating_dentry - prepare to access or create a given dentry
+ * @parent: directory of dentry
+ * @child:  the dentry to be prepared
  *
- * A lock is taken to protect the dentry again other dirops and
+ * A lock is taken to protect the dentry against other dirops and
  * the validity of the dentry is checked: correct parent and still hashed.
  *
  * If the dentry is valid and negative a reference is taken and
@@ -3580,7 +3580,7 @@ EXPORT_SYMBOL(start_creating_dentry);
  * @parent: directory from which dentry should be removed
  * @child:  the dentry to be removed
  *
- * A lock is taken to protect the dentry again other dirops and
+ * A lock is taken to protect the dentry against other dirops and
  * the validity of the dentry is checked: correct parent and still hashed.
  *
  * If the dentry is valid and positive, a reference is taken and
@@ -4440,12 +4440,14 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
 	int error, create_error;
 	umode_t mode;
 	bool got_write;
+	unsigned int shared_flag;
 
 retry:
 	open_flag = op->open_flag;
 	got_write = false;
 	mode = op->mode;
 	create_error = 0;
+	shared_flag = (open_flag & O_CREAT) ? 0 : LOOKUP_SHARED;
 
 	if (open_flag & (O_CREAT | O_TRUNC | O_WRONLY | O_RDWR)) {
 		got_write = !mnt_want_write(nd->path.mnt);
@@ -4454,10 +4456,10 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
 		 * a different error; we'll be dropping this one anyway.
 		 */
 	}
-	if (open_flag & O_CREAT)
-		inode_lock(dir_inode);
-	else
+	if (shared_flag)
 		inode_lock_shared(dir_inode);
+	else
+		inode_lock(dir_inode);
 
 	if (unlikely(IS_DEADDIR(dir_inode))) {
 		dentry = ERR_PTR(-ENOENT);
@@ -4526,7 +4528,7 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
 
 	if (d_in_lookup(dentry)) {
 		struct dentry *res = dir_inode->i_op->lookup(dir_inode, dentry,
-							     nd->flags);
+							     nd->flags | shared_flag);
 		d_lookup_done(dentry);
 		if (unlikely(res)) {
 			if (IS_ERR(res)) {
@@ -4574,10 +4576,10 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
 		if (file->f_mode & FMODE_OPENED)
 			fsnotify_open(file);
 	}
-	if ((open_flag & O_CREAT) || create_error)
-		inode_unlock(dir_inode);
-	else
+	if (shared_flag)
 		inode_unlock_shared(dir_inode);
+	else
+		inode_unlock(dir_inode);
 
 	if (got_write)
 		mnt_drop_write(nd->path.mnt);
diff --git a/fs/nfs/unlink.c b/fs/nfs/unlink.c
index b57cfaa4d516f..c8d712204e649 100644
--- a/fs/nfs/unlink.c
+++ b/fs/nfs/unlink.c
@@ -67,6 +67,7 @@ static void nfs_async_unlink_release(void *calldata)
 	struct super_block *sb = dentry->d_sb;
 
 	up_read_non_owner(&NFS_I(d_inode(dentry->d_parent))->rmdir_sem);
+	d_lookup_acquire(dentry);
 	d_lookup_done(dentry);
 	nfs_free_unlinkdata(data);
 	dput(dentry);
@@ -159,6 +160,8 @@ static int nfs_call_unlink(struct dentry *dentry, struct inode *inode, struct nf
 		return ret;
 	}
 	data->dentry = alias;
+	d_lookup_release(alias);
+
 	nfs_do_call_unlink(inode, data);
 	return 1;
 }
diff --git a/include/linux/dcache.h b/include/linux/dcache.h
index 4b1ff99608e01..e7e3ef05313b8 100644
--- a/include/linux/dcache.h
+++ b/include/linux/dcache.h
@@ -116,6 +116,8 @@ struct dentry {
 					 * possible!
 					 */
 
+	/* lockdep tracking of DCACHE_PAR_LOOKUP locks */
+	struct lockdep_map		lookup_map;
 	struct list_head d_lru;		/* LRU list */
 	struct hlist_node d_sib;	/* child of parent list */
 	struct hlist_head d_children;	/* our children */
@@ -257,7 +259,9 @@ extern void d_delete(struct dentry *);
 extern struct dentry * d_alloc(struct dentry *, const struct qstr *);
 extern struct dentry * d_alloc_anon(struct super_block *);
 extern struct dentry * d_alloc_parallel(struct dentry *, const struct qstr *);
+extern struct dentry * d_alloc_trylock(struct dentry *, struct qstr *);
 extern struct dentry * d_splice_alias(struct inode *, struct dentry *);
+struct dentry *d_duplicate(struct dentry *dentry);
 /* weird procfs mess; *NOT* exported */
 extern struct dentry * d_splice_alias_ops(struct inode *, struct dentry *,
 					  const struct dentry_operations *);
@@ -552,6 +556,36 @@ static inline int simple_positive(const struct dentry *dentry)
 
 unsigned long vfs_pressure_ratio(unsigned long val);
 
+/**
+ * d_lookup_release - release ownership of DCACHE_PAR_LOOKUP lock
+ * @dentry: dentry that is locked
+ *
+ * If an in-lookup dentry is to be passed to another thread which
+ * will drop the in-lookup lock, then d_lookup_release() must be called
+ * to tell lockdep that this thread no lock holds the lock.  The
+ * thread that receives the lock must call d_lookup_acquire() to
+ * acquire the lock.
+ */
+static inline void d_lookup_release(struct dentry *dentry)
+{
+	if (d_in_lookup(dentry))
+		lock_map_release(&dentry->lookup_map);
+}
+
+/**
+ * d_lookup_acquire - acquire ownership of DCACHE_PAR_LOOKUP lock
+ * @dentry: dentry that is locked
+ *
+ * If an in-lookup dentry was passed to this thread, the
+ * d_lookup_acquire() must be called to tell lockdep that this
+ * thread now owns the DCACHE_PAR_LOOKUP lock.
+ */
+static inline void d_lookup_acquire(struct dentry *dentry)
+{
+	if (d_in_lookup(dentry))
+		lock_map_acquire_try(&dentry->lookup_map);
+}
+
 /**
  * d_inode - Get the actual inode of this dentry
  * @dentry: The dentry to query
diff --git a/include/linux/namei.h b/include/linux/namei.h
index 86d657b24fc6a..65f0f5712885c 100644
--- a/include/linux/namei.h
+++ b/include/linux/namei.h
@@ -32,8 +32,9 @@ enum { MAX_NESTED_LINKS = 8 };
 #define LOOKUP_CREATE		BIT(17)	/* ... in object creation */
 #define LOOKUP_EXCL		BIT(18)	/* ... in target must not exist */
 #define LOOKUP_RENAME_TARGET	BIT(19)	/* ... in destination of rename() */
+#define LOOKUP_SHARED		BIT(20) /* Parent lock is held shared */
 
-/* 4 spare bits for intent */
+/* 3 spare bits for intent */
 
 /* Scoping flags for lookup. */
 #define LOOKUP_NO_SYMLINKS	BIT(24) /* No symlink crossing. */

9/3 2026/09/04 04:16 llm kmsan-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 10297
							output: 35
							thoughts: 932
						

Thoughts:
**KMSAN Fuzzing Session Assessment**

Okay, let's break down this patch series and determine if a KMSAN fuzzing session is warranted. My primary concern, as always, is whether these changes introduce any new uninitialized memory issues or potential information leaks, especially to userspace, because frankly, KMSAN sessions are resource hogs and I'd prefer to avoid them if possible. The rule is simple: if KASAN, LOCKDEP, or any other standard detector can catch the bug, we *don't* need KMSAN. We only invoke the big guns when KMSAN is the only tool that can do the job.

So, let's run through the changes one by one:

1.  **Documentation Updates:** Typo fixes in `porting.rst` and `vfs.rst` - no concerns here. Straightforward documentation updates and no code changes. Safe.
2.  **`fs/dcache.c` Modifications:** This is where things get interesting. We've got a lot of changes in `fs/dcache.c`:

    *   Introduction of `lookup_map` and its initialization within `__d_alloc`. The `lookup_map` is initialized with `lockdep_init_map`, which should set the initial state. My initial thought is that since `__d_alloc` initializes everything, the `lookup_map` should also be initialized, and this does not warrant KMSAN fuzzing. But I should check `__d_alloc` to verify that `kmem_cache_alloc_lru` correctly initializes the fields.
    *   A new `d_duplicate()` helper, some lockdep annotations, and a `d_alloc_trylock()` function. These seem focused on concurrency and locking, but don't immediately scream "uninitialized memory."
    *   Changes to `__d_lookup_unhash`, `__d_add`, and `d_splice_alias_ops`. These appear to be refactorings and improvements around dentry management, with some lockdep-related changes. Again, these aren't directly related to uninitialized memory issues.
3.  **`fs/namei.c` Updates:** Primarily lock/unlock changes with a bit of refactoring for better `LOOKUP_SHARED` usage. No red flags here either.
4.  **`fs/nfs/unlink.c` Tweaks:** Some locking-related changes with `d_lookup_acquire` and `d_lookup_release`. This is likely correct-ness related and I'm not seeing any new potential for uninitialized memory or information leaks.
5.  **Header file additions:** Adding `lookup_map` and related functions in `include/linux/dcache.h` and `include/linux/namei.h`. These are just declarations and definitions and, by themselves, don't introduce any new risks.

Now, let's return to the `lookup_map` initialization in `__d_alloc`. Since the structure has been allocated with `kmem_cache_alloc_lru`, this should initialize the structure. But I need to double-check `__d_alloc` to be 100% sure. Based on an initial review of this patch, I think the changes are generally safe and don't introduce or expose any obvious new risks of uninitialized memory that would *require* KMSAN fuzzing. I'll need to confirm that `__d_alloc` truly initializes all members, including `lookup_map`. If that pans out, then I will consider this patch safe.



10/3 2026/09/04 04:17 tool grepper
0m Args:
{
  "Expression": "kmem_cache_alloc.*dentry",
  "PathPrefix": "fs/dcache.c"
}

Results:
{
  "Output": "fs/dcache.c=1902=static struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name)\n--\nfs/dcache.c-1908-\nfs/dcache.c:1909:\tdentry = kmem_cache_alloc_lru(dentry_cache, \u0026sb-\u003es_dentry_lru,\nfs/dcache.c-1910-\t\t\t\t      GFP_KERNEL);\n"
}

11/3 2026/09/04 04:17 llm kmsan-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 12154
							output: 35
							thoughts: 27
						

Thoughts:
Here's my summary, as though I'm thinking through this process:

**Diving into `__d_alloc`**

Okay, time to get to the heart of this. My initial approach is to zoom in on the `__d_alloc` function. I'm thinking specifically around lines 1900-1970. This area seems like a likely candidate for the issue I'm investigating. I'll use the "read-file" tool or function to quickly pull up that section of the code and get a direct look. I can then carefully examine the logic, variable usage, and potential memory management concerns within that specific range. Focusing on a defined section helps streamline the process.



12/3 2026/09/04 04:17 tool read-file
0m Args:
{
  "File": "fs/dcache.c",
  "FirstLine": 1900,
  "LineCount": 80
}

Results:
{
  "Contents": "1900:\t */\n1901:\t \n1902:\tstatic struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name)\n1903:\t{\n1904:\t\tstatic struct lock_class_key __lookup_key;\n1905:\t\tstruct dentry *dentry;\n1906:\t\tchar *dname;\n1907:\t\tint err;\n1908:\t\n1909:\t\tdentry = kmem_cache_alloc_lru(dentry_cache, \u0026sb-\u003es_dentry_lru,\n1910:\t\t\t\t\t      GFP_KERNEL);\n1911:\t\tif (!dentry)\n1912:\t\t\treturn NULL;\n1913:\t\n1914:\t\t/*\n1915:\t\t * We guarantee that the inline name is always NUL-terminated.\n1916:\t\t * This way the memcpy() done by the name switching in rename\n1917:\t\t * will still always have a NUL at the end, even if we might\n1918:\t\t * be overwriting an internal NUL character\n1919:\t\t */\n1920:\t\tdentry-\u003ed_shortname.string[DNAME_INLINE_LEN-1] = 0;\n1921:\t\tif (unlikely(!name)) {\n1922:\t\t\tname = \u0026slash_name;\n1923:\t\t\tdname = dentry-\u003ed_shortname.string;\n1924:\t\t} else if (name-\u003elen \u003e DNAME_INLINE_LEN-1) {\n1925:\t\t\tstruct external_name *p;\n1926:\t\n1927:\t\t\tp = kmalloc_flex(*p, name, name-\u003elen + 1,\n1928:\t\t\t\t\t GFP_KERNEL_ACCOUNT | __GFP_RECLAIMABLE);\n1929:\t\t\tif (!p) {\n1930:\t\t\t\tkmem_cache_free(dentry_cache, dentry); \n1931:\t\t\t\treturn NULL;\n1932:\t\t\t}\n1933:\t\t\tatomic_set(\u0026p-\u003ecount, 1);\n1934:\t\t\tdname = p-\u003ename;\n1935:\t\t} else  {\n1936:\t\t\tdname = dentry-\u003ed_shortname.string;\n1937:\t\t}\t\n1938:\t\n1939:\t\tdentry-\u003e__d_name.len = name-\u003elen;\n1940:\t\tdentry-\u003e__d_name.hash = name-\u003ehash;\n1941:\t\tmemcpy(dname, name-\u003ename, name-\u003elen);\n1942:\t\tdname[name-\u003elen] = 0;\n1943:\t\n1944:\t\t/* Make sure we always see the terminating NUL character */\n1945:\t\tsmp_store_release(\u0026dentry-\u003e__d_name.name, dname); /* ^^^ */\n1946:\t\n1947:\t\tdentry-\u003ed_flags = 0;\n1948:\t\tlockref_init(\u0026dentry-\u003ed_lockref);\n1949:\t\tseqcount_spinlock_init(\u0026dentry-\u003ed_seq, \u0026dentry-\u003ed_lock);\n1950:\t\tdentry-\u003ed_inode = NULL;\n1951:\t\tdentry-\u003ed_parent = dentry;\n1952:\t\tdentry-\u003ed_sb = sb;\n1953:\t\tdentry-\u003ed_op = sb-\u003e__s_d_op;\n1954:\t\tdentry-\u003ed_flags = sb-\u003es_d_flags;\n1955:\t\tdentry-\u003ed_fsdata = NULL;\n1956:\t\tINIT_HLIST_BL_NODE(\u0026dentry-\u003ed_hash);\n1957:\t\tINIT_LIST_HEAD(\u0026dentry-\u003ed_lru);\n1958:\t\tINIT_HLIST_HEAD(\u0026dentry-\u003ed_children);\n1959:\t\tdentry-\u003ewaiters = NULL;\n1960:\t\tINIT_HLIST_NODE(\u0026dentry-\u003ed_sib);\n1961:\t\n1962:\t\tlockdep_init_map(\u0026dentry-\u003elookup_map, \"DCACHE_PAR_LOOKUP\", \u0026__lookup_key, 0);\n1963:\t\n1964:\t\tif (dentry-\u003ed_op \u0026\u0026 dentry-\u003ed_op-\u003ed_init) {\n1965:\t\t\terr = dentry-\u003ed_op-\u003ed_init(dentry);\n1966:\t\t\tif (err) {\n1967:\t\t\t\tif (dname_external(dentry))\n1968:\t\t\t\t\tkfree(external_name(dentry));\n1969:\t\t\t\tkmem_cache_free(dentry_cache, dentry);\n1970:\t\t\t\treturn NULL;\n1971:\t\t\t}\n1972:\t\t}\n1973:\t\n1974:\t\tthis_cpu_inc(nr_dentry);\n1975:\t\n1976:\t\treturn dentry;\n1977:\t}\n1978:\t\n1979:\t/**\n"
}

13/3 2026/09/04 04:17 llm kmsan-evaluator
0m Model:
gemini-3.7-flash

Args:
null

Results:
null

Tokens:
							input: 13654
							output: 197
							thoughts: 706
						

Thoughts:
**KMSAN Assessment: Parallel Dentry Lookup Changes**

Okay, let's take a look at this. The goal here is to determine whether or not we need to flag this patch for KMSAN scrutiny. I know the drill: we have to be *really* sure about data leaks to userspace or uninitialized variable usage. Let's run through that checklist:

*   **First, are there any user-space exposures?** No. This patch touches documentation and file system internals: `dcache.c`, `namei.c`, `nfs/unlink.c` and a few headers. No new structures are being sent to userspace. We're adding `lookup_map` to `struct dentry` for lockdep and some helper functions, but not for direct communication with user-space.

*   **Next, are there conditionals or branching based on uninitialized data?** Doesn't seem so. The patch introduces `DCACHE_PAR_LOOKUP` to allow parallel lookups. We're adding new allocation helpers for non-blocking lookup (`ALLOC_PARA_FAIL`, `d_alloc_trylock`) and adding a `LOOKUP_SHARED` flag for locking updates. But there's nothing in the changes that hinges on reading uninitialized variables or struct fields to determine program flow.

*   **How about allocation of complex data structures?** We're introducing a new field `lookup_map` in `struct dentry` and using it for lockdep instrumentation in parallel lookups. I see some new dentry allocations and refactoring of `__d_add` to call `__d_instantiate` for parallel lookups. However, crucially, the new allocations via `__d_alloc` fully initialize everything, including `lockdep_init_map(&dentry->lookup_map, ...)`.

*   **Finally, is there a chance of uninitialized memory access via bounds checks, lengths, etc.?** No, I don't see any changes to buffer sizing, bounds checks, or length calculations that could potentially expose uninitialized memory.

Based on this, it's pretty clear that this patch *doesn't* introduce any KMSAN concerns. All of the new allocations initialize their fields properly. This is mainly about locking, parallelism, and dentry management. Any concurrent race conditions or locking issues that I can see should be caught by LOCKDEP, KASAN, or kernel asserts (`BUG_ON`).

So, the conclusion is: `NeedsKMSAN = false`. We're good to go.



14/3 2026/09/04 04:17 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch series introduces Lockdep tracking for parallel dentry lookups (DCACHE_PAR_LOOKUP via struct lockdep_map in struct dentry), adds the d_duplicate and d_alloc_trylock helpers, updates locking flags (LOOKUP_SHARED) in VFS lookup routines (lookup_slow, lookup_open), and fixes filesystem documentation.\n\nNone of the changes introduce or modify uninitialized memory reads, buffer boundaries, or kernel-to-userspace data transfers. All modified data structures (e.g., struct dentry in __d_alloc) are properly initialized upon allocation. Any concurrency, deadlock, or refcounting issues resulting from these VFS locking/lookup refactorings are tracked and detected by standard detectors such as LOCKDEP and KASAN. Therefore, dedicated KMSAN fuzzing is not required.",
  "NeedsKMSAN": false
}

Results:
{
  "KMSANReasoning": "The patch series introduces Lockdep tracking for parallel dentry lookups (DCACHE_PAR_LOOKUP via struct lockdep_map in struct dentry), adds the d_duplicate and d_alloc_trylock helpers, updates locking flags (LOOKUP_SHARED) in VFS lookup routines (lookup_slow, lookup_open), and fixes filesystem documentation.\n\nNone of the changes introduce or modify uninitialized memory reads, buffer boundaries, or kernel-to-userspace data transfers. All modified data structures (e.g., struct dentry in __d_alloc) are properly initialized upon allocation. Any concurrency, deadlock, or refcounting issues resulting from these VFS locking/lookup refactorings are tracked and detected by standard detectors such as LOCKDEP and KASAN. Therefore, dedicated KMSAN fuzzing is not required.",
  "NeedsKMSAN": false
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)