AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/08/23 16:14 flow patch-triage
2m Args:
null

Results:
{
  "EnableConfigs": [
    "9P_FS",
    "CEPH_FS",
    "FUSE_FS",
    "GFS2_FS",
    "NFS_FS",
    "CIFS",
    "VBOXSF_FS"
  ],
  "FocusSymbols": [
    "vfs_mkdir",
    "vfs_create",
    "vfs_lookup_open",
    "dentry_create",
    "filename_mkdirat",
    "filename_unlinkat",
    "filename_renameat2",
    "v9fs_vfs_atomic_open",
    "v9fs_vfs_atomic_open_dotl",
    "ceph_atomic_open",
    "fuse_atomic_open",
    "gfs2_atomic_open",
    "nfs_atomic_open",
    "nfs_atomic_open_v23",
    "cifs_atomic_open",
    "vboxsf_dir_atomic_open"
  ],
  "KMSANReasoning": "The patch series modifies the VFS and various filesystems to support creating directories via `openat()` with the `O_CREAT | O_DIRECTORY` flags. The changes primarily involve adding the `O_IS_MKDIR` macro, modifying `build_open_flags()` to handle the new flag combination, and updating `lookup_open()` and `vfs_mkdir()` paths in `fs/namei.c` to route directory creation requests to `vfs_mkdir_no_perm()` instead of regular file creation functions.\n\nThese changes are purely logical, altering control flow and flag handling within the VFS layer. The patch does not introduce new data structures sent to user space, nor does it allocate complex data structures with uninitialized fields. All variables used (such as `delegated_inode`) are properly zero-initialized. Any potential bugs introduced by these changes would likely be logic errors, NULL pointer dereferences, or out-of-bounds accesses, which are effectively caught by KASAN and standard bug detectors. There is no risk of uninitialized memory usage or info-leaks that would necessitate a dedicated KMSAN fuzzing session.",
  "NeedsKMSAN": false,
  "Reasoning": "The patch introduces a new feature `O_CREAT | O_DIRECTORY` for `openat()` to atomically create a directory and return a file descriptor. It modifies core VFS path resolution and creation logic in `fs/namei.c` and `fs/open.c`, and updates several filesystems' `atomic_open` implementations to handle the new flag combination. This is a significant functional change to reachable core kernel logic that should be heavily fuzzed.",
  "WorthFuzzing": true
}

1/1 2026/08/23 16:14 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit d6974f2dae03a314eedcd225149e126b66094b4d\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Sun Aug 23 16:14:08 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c\nindex 3829554ca3692..b1e0823c87b57 100644\n--- a/fs/9p/vfs_inode.c\n+++ b/fs/9p/vfs_inode.c\n@@ -776,6 +776,9 @@ v9fs_vfs_atomic_open(struct inode *dir, struct dentry *dentry,\n \tstruct inode *inode;\n \tint p9_omode;\n \n+\tif (O_IS_MKDIR(flags))\n+\t\tflags \u0026= ~O_CREAT;\n+\n \tif (d_in_lookup(dentry)) {\n \t\tstruct dentry *res = v9fs_vfs_lookup(dir, dentry, 0);\n \t\tif (res || d_really_is_positive(dentry))\ndiff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c\nindex 116b29e95f21e..64e0aba08da16 100644\n--- a/fs/9p/vfs_inode_dotl.c\n+++ b/fs/9p/vfs_inode_dotl.c\n@@ -238,6 +238,9 @@ v9fs_vfs_atomic_open_dotl(struct inode *dir, struct dentry *dentry,\n \tstruct v9fs_session_info *v9ses;\n \tstruct posix_acl *pacl = NULL, *dacl = NULL;\n \n+\tif (O_IS_MKDIR(flags))\n+\t\tflags \u0026= ~O_CREAT;\n+\n \tif (d_in_lookup(dentry)) {\n \t\tstruct dentry *res = v9fs_vfs_lookup(dir, dentry, 0);\n \t\tif (res || d_really_is_positive(dentry))\ndiff --git a/fs/ceph/file.c b/fs/ceph/file.c\nindex a4a2a4b6a027d..62163b7eca5f9 100644\n--- a/fs/ceph/file.c\n+++ b/fs/ceph/file.c\n@@ -812,6 +812,9 @@ int ceph_atomic_open(struct inode *dir, struct dentry *dentry,\n \t      dir, ceph_vinop(dir), dentry, dentry,\n \t      d_unhashed(dentry) ? \"unhashed\" : \"hashed\", flags, mode);\n \n+\tif (O_IS_MKDIR(flags))\n+\t\tflags \u0026= ~O_CREAT;\n+\n \tif (dentry-\u003ed_name.len \u003e NAME_MAX)\n \t\treturn -ENAMETOOLONG;\n \ndiff --git a/fs/fuse/dir.c b/fs/fuse/dir.c\nindex d4e0029810c0f..55d5844e26559 100644\n--- a/fs/fuse/dir.c\n+++ b/fs/fuse/dir.c\n@@ -935,6 +935,9 @@ static int fuse_atomic_open(struct inode *dir, struct dentry *entry,\n \tstruct mnt_idmap *idmap = file_mnt_idmap(file);\n \tstruct fuse_conn *fc = get_fuse_conn(dir);\n \n+\tif (O_IS_MKDIR(flags))\n+\t\tflags \u0026= ~O_CREAT;\n+\n \tif (fuse_is_bad(dir))\n \t\treturn -EIO;\n \ndiff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c\nindex f361876c55833..3ee1360f1bc27 100644\n--- a/fs/gfs2/inode.c\n+++ b/fs/gfs2/inode.c\n@@ -1386,6 +1386,9 @@ static int gfs2_atomic_open(struct inode *dir, struct dentry *dentry,\n {\n \tbool excl = !!(flags \u0026 O_EXCL);\n \n+\tif (O_IS_MKDIR(flags))\n+\t\tflags \u0026= ~O_CREAT;\n+\n \tif (d_in_lookup(dentry)) {\n \t\tstruct dentry *d = __gfs2_lookup(dir, dentry, file);\n \t\tif (file-\u003ef_mode \u0026 FMODE_OPENED) {\ndiff --git a/fs/namei.c b/fs/namei.c\nindex 20a6534ea3eff..e1a8f18385a52 100644\n--- a/fs/namei.c\n+++ b/fs/namei.c\n@@ -1382,13 +1382,13 @@ int may_linkat(struct mnt_idmap *idmap, const struct path *link)\n \n /**\n  * may_create_in_sticky - Check whether an O_CREAT open in a sticky directory\n- *\t\t\t  should be allowed, or not, on files that already\n- *\t\t\t  exist.\n+ *\t\t\t  should be allowed, or not, on files/directories that\n+ *\t\t\t  already exist.\n  * @idmap: idmap of the mount the inode was found from\n  * @nd: nameidata pathwalk data\n  * @inode: the inode of the file to open\n  *\n- * Block an O_CREAT open of a FIFO (or a regular file) when:\n+ * Block an O_CREAT open of a FIFO (or a regular file/directory) when:\n  *   - sysctl_protected_fifos (or sysctl_protected_regular) is enabled\n  *   - the file already exists\n  *   - we are in a sticky directory\n@@ -1416,6 +1416,14 @@ static int may_create_in_sticky(struct mnt_idmap *idmap, struct nameidata *nd,\n \tif (likely(!(dir_mode \u0026 S_ISVTX)))\n \t\treturn 0;\n \n+\t/*\n+\t * There is no separate sysctl for directory creation in sticky\n+\t * folders. Therefore, for the S_ISDIR case, disabling\n+\t * sysctl_protected_regular is not enough to allow creating a\n+\t * directory in a sticky folder, because that may surprise users\n+\t * not expecting that O_CREAT|O_DIRECTORY is possible on newer\n+\t * kernels.\n+\t */\n \tif (S_ISREG(inode-\u003ei_mode) \u0026\u0026 !sysctl_protected_regular)\n \t\treturn 0;\n \n@@ -1447,6 +1455,12 @@ static int may_create_in_sticky(struct mnt_idmap *idmap, struct nameidata *nd,\n \t\t\t\t\t      \"sticky_create_regular\");\n \t\t\treturn -EACCES;\n \t\t}\n+\n+\t\tif (S_ISDIR(inode-\u003ei_mode)) {\n+\t\t\taudit_log_path_denied(AUDIT_ANOM_CREAT,\n+\t\t\t\t\t      \"sticky_create_dir\");\n+\t\t\treturn -EACCES;\n+\t\t}\n \t}\n \n \treturn 0;\n@@ -2781,9 +2795,16 @@ static const char *path_init(struct nameidata *nd, unsigned flags)\n \treturn s;\n }\n \n+static inline bool trailing_slashes(const struct qstr *last)\n+{\n+\t/* last-\u003elen is set by hash_name() to the length of the current\n+\t * component -\u003ename, terminating with '/' or a NUL character. */\n+\treturn (bool)last-\u003ename[last-\u003elen];\n+}\n+\n static inline const char *lookup_last(struct nameidata *nd)\n {\n-\tif (nd-\u003elast_type == LAST_NORM \u0026\u0026 nd-\u003elast.name[nd-\u003elast.len])\n+\tif (nd-\u003elast_type == LAST_NORM \u0026\u0026 trailing_slashes(\u0026nd-\u003elast))\n \t\tnd-\u003eflags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;\n \n \treturn walk_component(nd, WALK_TRAILING);\n@@ -4159,6 +4180,24 @@ static inline umode_t vfs_prepare_mode(struct mnt_idmap *idmap,\n \treturn mode;\n }\n \n+static inline\n+int vfs_create_no_perm(struct mnt_idmap *idmap, struct dentry *dentry,\n+\t\t       umode_t mode, struct delegated_inode *di)\n+{\n+\tstruct inode *dir = d_inode(dentry-\u003ed_parent);\n+\tint error;\n+\n+\terror = try_break_deleg(dir, LEASE_BREAK_DIR_CREATE, di);\n+\tif (error)\n+\t\treturn error;\n+\n+\terror = dir-\u003ei_op-\u003ecreate(idmap, dir, dentry, mode);\n+\tif (!error)\n+\t\tfsnotify_create(dir, dentry);\n+\n+\treturn error;\n+}\n+\n /**\n  * vfs_create - create new file\n  * @idmap:\tidmap of the mount the inode was found from\n@@ -4191,13 +4230,8 @@ int vfs_create(struct mnt_idmap *idmap, struct dentry *dentry, umode_t mode,\n \terror = security_inode_create(dir, dentry, mode);\n \tif (error)\n \t\treturn error;\n-\terror = try_break_deleg(dir, LEASE_BREAK_DIR_CREATE, di);\n-\tif (error)\n-\t\treturn error;\n-\terror = dir-\u003ei_op-\u003ecreate(idmap, dir, dentry, mode);\n-\tif (!error)\n-\t\tfsnotify_create(dir, dentry);\n-\treturn error;\n+\n+\treturn vfs_create_no_perm(idmap, dentry, mode, di);\n }\n EXPORT_SYMBOL(vfs_create);\n \n@@ -4314,21 +4348,41 @@ static inline int open_to_namei_flags(int flag)\n \n static int may_o_create(struct mnt_idmap *idmap,\n \t\t\tconst struct path *dir, struct dentry *dentry,\n-\t\t\tumode_t mode)\n+\t\t\tint open_flag, umode_t mode)\n {\n-\tint error = security_path_mknod(dir, dentry, mode, 0);\n+\tstruct inode *dir_inode = dir-\u003edentry-\u003ed_inode;\n+\tbool create_dir = O_IS_MKDIR(open_flag);\n+\tint error;\n+\n+\tif (create_dir)\n+\t\terror = security_path_mkdir(dir, dentry, mode);\n+\telse\n+\t\terror = security_path_mknod(dir, dentry, mode, 0);\n \tif (error)\n \t\treturn error;\n \n \tif (!fsuidgid_has_mapping(dir-\u003edentry-\u003ed_sb, idmap))\n \t\treturn -EOVERFLOW;\n \n-\terror = inode_permission(idmap, dir-\u003edentry-\u003ed_inode,\n-\t\t\t\t MAY_WRITE | MAY_EXEC);\n+\terror = inode_permission(idmap, dir_inode, MAY_WRITE | MAY_EXEC);\n \tif (error)\n \t\treturn error;\n \n-\treturn security_inode_create(dir-\u003edentry-\u003ed_inode, dentry, mode);\n+\tif (create_dir)\n+\t\terror = security_inode_mkdir(dir_inode, dentry, mode);\n+\telse\n+\t\terror = security_inode_create(dir_inode, dentry, mode);\n+\n+\treturn error;\n+}\n+\n+static inline umode_t o_create_mode(struct mnt_idmap *idmap,\n+\t\tconst struct inode *dir, int open_flag, umode_t mode)\n+{\n+\tif (O_IS_MKDIR(open_flag))\n+\t\treturn vfs_prepare_mode(idmap, dir, mode, S_IRWXUGO | S_ISVTX, S_IFDIR);\n+\telse\n+\t\treturn vfs_prepare_mode(idmap, dir, mode, S_IALLUGO, S_IFREG);\n }\n \n /**\n@@ -4364,8 +4418,9 @@ static struct dentry *atomic_open(const struct path *path, struct dentry *dentry\n \n \tfile-\u003e__f_path.dentry = DENTRY_NOT_SET;\n \tfile-\u003e__f_path.mnt = path-\u003emnt;\n+\n \terror = dir_inode-\u003ei_op-\u003eatomic_open(dir_inode, dentry, file,\n-\t\t\t\t       open_to_namei_flags(open_flag), mode);\n+\t\t\t\t\t     open_to_namei_flags(open_flag), mode);\n \td_lookup_done(dentry);\n \n \tif (!error) {\n@@ -4410,10 +4465,21 @@ static struct dentry *atomic_open(const struct path *path, struct dentry *dentry\n \t\t}\n \t\tdput(dentry);\n \t\tdentry = ERR_PTR(error);\n+\t} else {\n+\t\tif (file-\u003ef_mode \u0026 FMODE_CREATED)\n+\t\t\tfsnotify_create(dir_inode, dentry);\n+\t\tif (file-\u003ef_mode \u0026 FMODE_OPENED)\n+\t\t\tfsnotify_open(file);\n \t}\n+\n+\n \treturn dentry;\n }\n \n+static inline\n+struct dentry *vfs_mkdir_no_perm(struct mnt_idmap *, struct inode *,\n+\t\t\t\t struct dentry *, umode_t,\n+\t\t\t\t struct delegated_inode *);\n /*\n  * Look up and maybe create and open the last component.\n  *\n@@ -4435,6 +4501,7 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,\n \tstruct mnt_idmap *idmap;\n \tstruct dentry *dir = nd-\u003epath.dentry;\n \tstruct inode *dir_inode = dir-\u003ed_inode;\n+\tbool create_dir = O_IS_MKDIR(op-\u003emode);\n \tint open_flag;\n \tstruct dentry *dentry;\n \tint error, create_error;\n@@ -4455,7 +4522,7 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,\n \t\t */\n \t}\n \tif (open_flag \u0026 O_CREAT)\n-\t\tinode_lock(dir_inode);\n+\t\tinode_lock_nested(dir_inode, I_MUTEX_PARENT);\n \telse\n \t\tinode_lock_shared(dir_inode);\n \n@@ -4507,12 +4574,17 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,\n \tif (open_flag \u0026 O_CREAT) {\n \t\tif (open_flag \u0026 O_EXCL)\n \t\t\topen_flag \u0026= ~O_TRUNC;\n-\t\tmode = vfs_prepare_mode(idmap, dir_inode, mode, mode, mode);\n+\t\tmode = o_create_mode(idmap, dir_inode, open_flag, mode);\n \t\tif (likely(got_write))\n \t\t\tcreate_error = may_o_create(idmap, \u0026nd-\u003epath,\n-\t\t\t\t\t\t    dentry, mode);\n+\t\t\t\t\t\t    dentry, open_flag, mode);\n \t\telse\n \t\t\tcreate_error = -EROFS;\n+\t\t/* Refuse to create a directory through a dangling (trailing)\n+\t\t * symlink. For regular files this has been allowed historically\n+\t\t * on O_CREAT without O_EXCL. */\n+\t\tif (unlikely(nd-\u003edepth) \u0026\u0026 create_dir \u0026\u0026 !create_error)\n+\t\t\tcreate_error = -EEXIST;\n \t}\n \tif (create_error)\n \t\topen_flag \u0026= ~O_CREAT;\n@@ -4537,6 +4609,7 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,\n \t\t\tdentry = res;\n \t\t}\n \t}\n+\n \tif (dentry-\u003ed_inode || !(op-\u003eopen_flag \u0026 O_CREAT)) {\n \t\t/*\n \t\t * No need to create a file.  If lookup returned a positive\n@@ -4554,26 +4627,26 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,\n \t\tgoto out_dput;\n \t}\n \n-\terror = try_break_deleg(dir_inode, LEASE_BREAK_DIR_CREATE, \u0026delegated_inode);\n-\tif (error)\n-\t\tgoto out_dput;\n-\n-\tfile-\u003ef_mode |= FMODE_CREATED;\n-\tif (!dir_inode-\u003ei_op-\u003ecreate) {\n+\tif ((create_dir \u0026\u0026 !dir_inode-\u003ei_op-\u003emkdir)\n+\t\t|| (!create_dir \u0026\u0026 !dir_inode-\u003ei_op-\u003ecreate)) {\n \t\terror = -EACCES;\n \t\tgoto out_dput;\n \t}\n \n-\terror = dir_inode-\u003ei_op-\u003ecreate(idmap, dir_inode, dentry, mode);\n+\tif (create_dir) {\n+\t\tstruct dentry *res = vfs_mkdir_no_perm(idmap, dir_inode, dentry,\n+\t\t\t\t\t\t       mode, \u0026delegated_inode);\n+\t\t\terror = PTR_ERR_OR_ZERO(res);\n+\t\t\tif (!error)\n+\t\t\t\tdentry = res;\n+\t} else {\n+\t\terror = vfs_create_no_perm(idmap, dentry, mode, \u0026delegated_inode);\n+\t}\n \tif (error)\n \t\tgoto out_dput;\n+\n+\tfile-\u003ef_mode |= FMODE_CREATED;\n out:\n-\tif (!IS_ERR(dentry)) {\n-\t\tif (file-\u003ef_mode \u0026 FMODE_CREATED)\n-\t\t\tfsnotify_create(dir_inode, dentry);\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@@ -4695,17 +4768,12 @@ struct file *vfs_lookup_open(struct path *parent, struct qstr *last,\n }\n EXPORT_SYMBOL_FOR_MODULES(vfs_lookup_open, \"nfsd\");\n \n-static inline bool trailing_slashes(struct nameidata *nd)\n-{\n-\treturn (bool)nd-\u003elast.name[nd-\u003elast.len];\n-}\n-\n static struct dentry *lookup_fast_for_open(struct nameidata *nd, int open_flag)\n {\n \tstruct dentry *dentry;\n \n \tif (open_flag \u0026 O_CREAT) {\n-\t\tif (trailing_slashes(nd))\n+\t\tif (trailing_slashes(\u0026nd-\u003elast) \u0026\u0026 !(open_flag \u0026 O_DIRECTORY))\n \t\t\treturn ERR_PTR(-EISDIR);\n \n \t\t/* Don't bother on an O_EXCL create */\n@@ -4713,7 +4781,7 @@ static struct dentry *lookup_fast_for_open(struct nameidata *nd, int open_flag)\n \t\t\treturn NULL;\n \t}\n \n-\tif (trailing_slashes(nd))\n+\tif (trailing_slashes(\u0026nd-\u003elast))\n \t\tnd-\u003eflags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;\n \n \tdentry = lookup_fast(nd);\n@@ -4806,8 +4874,9 @@ static int do_open(struct nameidata *nd,\n \tif (open_flag \u0026 O_CREAT) {\n \t\tif ((open_flag \u0026 O_EXCL) \u0026\u0026 !(file-\u003ef_mode \u0026 FMODE_CREATED))\n \t\t\treturn -EEXIST;\n-\t\tif (d_is_dir(nd-\u003epath.dentry))\n+\t\tif (!(open_flag \u0026 O_DIRECTORY) \u0026\u0026 d_is_dir(nd-\u003epath.dentry))\n \t\t\treturn -EISDIR;\n+\n \t\terror = may_create_in_sticky(idmap, nd,\n \t\t\t\t\t     d_backing_inode(nd-\u003epath.dentry));\n \t\tif (unlikely(error))\n@@ -5087,7 +5156,7 @@ static struct dentry *filename_create(int dfd, struct filename *name,\n \t * Do the final lookup.  Suppress 'create' if there is a trailing\n \t * '/', and a directory wasn't requested.\n \t */\n-\tif (last.name[last.len] \u0026\u0026 !want_dir)\n+\tif (trailing_slashes(\u0026last) \u0026\u0026 !want_dir)\n \t\tcreate_flags \u0026= ~LOOKUP_CREATE;\n \tdentry = start_dirop(path-\u003edentry, \u0026last, reval_flag | create_flags);\n \tif (IS_ERR(dentry))\n@@ -5182,7 +5251,7 @@ struct file *dentry_create(struct path *path, int flags, umode_t mode,\n \t\tpath-\u003edentry = dir;\n \t\tmode = vfs_prepare_mode(idmap, dir_inode, mode, S_IALLUGO, S_IFREG);\n \n-\t\tcreate_error = may_o_create(idmap, path, dentry, mode);\n+\t\tcreate_error = may_o_create(idmap, path, dentry, flags, mode);\n \t\tif (create_error)\n \t\t\tflags \u0026= ~O_CREAT;\n \n@@ -5356,6 +5425,34 @@ SYSCALL_DEFINE3(mknod, const char __user *, filename, umode_t, mode, unsigned, d\n \treturn filename_mknodat(AT_FDCWD, name, mode, dev);\n }\n \n+/* Returns the dentry to use (not NULL) or -E on error */\n+static inline\n+struct dentry *vfs_mkdir_no_perm(struct mnt_idmap *idmap, struct inode *dir,\n+\t\t\t\t struct dentry *dentry, umode_t mode,\n+\t\t\t\t struct delegated_inode *di)\n+{\n+\tint error;\n+\tstruct dentry *de;\n+\tunsigned max_links = dir-\u003ei_sb-\u003es_max_links;\n+\n+\tif (max_links \u0026\u0026 dir-\u003ei_nlink \u003e= max_links)\n+\t\treturn ERR_PTR(-EMLINK);\n+\n+\terror = try_break_deleg(dir, LEASE_BREAK_DIR_CREATE, di);\n+\tif (error)\n+\t\treturn ERR_PTR(error);\n+\n+\tde = dir-\u003ei_op-\u003emkdir(idmap, dir, dentry, mode);\n+\tif (IS_ERR(de))\n+\t\treturn de;\n+\tif (de) {\n+\t\tdput(dentry);\n+\t\tdentry = de;\n+\t}\n+\tfsnotify_mkdir(dir, dentry);\n+\treturn dentry;\n+}\n+\n /**\n  * vfs_mkdir - create directory returning correct dentry if possible\n  * @idmap:\t\tidmap of the mount the inode was found from\n@@ -5383,7 +5480,6 @@ struct dentry *vfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,\n \t\t\t struct delegated_inode *delegated_inode)\n {\n \tint error;\n-\tunsigned max_links = dir-\u003ei_sb-\u003es_max_links;\n \tstruct dentry *de;\n \n \terror = may_create_dentry(idmap, dir, dentry);\n@@ -5399,24 +5495,12 @@ struct dentry *vfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,\n \tif (error)\n \t\tgoto err;\n \n-\terror = -EMLINK;\n-\tif (max_links \u0026\u0026 dir-\u003ei_nlink \u003e= max_links)\n-\t\tgoto err;\n-\n-\terror = try_break_deleg(dir, LEASE_BREAK_DIR_CREATE, delegated_inode);\n-\tif (error)\n-\t\tgoto err;\n-\n-\tde = dir-\u003ei_op-\u003emkdir(idmap, dir, dentry, mode);\n-\terror = PTR_ERR(de);\n-\tif (IS_ERR(de))\n+\tde = vfs_mkdir_no_perm(idmap, dir, dentry, mode, delegated_inode);\n+\tif (IS_ERR(de)) {\n+\t\terror = PTR_ERR(de);\n \t\tgoto err;\n-\tif (de) {\n-\t\tdput(dentry);\n-\t\tdentry = de;\n \t}\n-\tfsnotify_mkdir(dir, dentry);\n-\treturn dentry;\n+\treturn de;\n \n err:\n \tend_creating(dentry);\n@@ -5703,7 +5787,7 @@ int filename_unlinkat(int dfd, struct filename *name)\n \t\tgoto exit_drop_write;\n \n \t/* Why not before? Because we want correct error value */\n-\tif (unlikely(last.name[last.len])) {\n+\tif (unlikely(trailing_slashes(\u0026last))) {\n \t\tif (d_is_dir(dentry))\n \t\t\terror = -EISDIR;\n \t\telse\n@@ -6305,16 +6389,16 @@ int filename_renameat2(int olddfd, struct filename *from,\n \tif (flags \u0026 RENAME_EXCHANGE) {\n \t\tif (!d_is_dir(rd.new_dentry)) {\n \t\t\terror = -ENOTDIR;\n-\t\t\tif (new_last.name[new_last.len])\n+\t\t\tif (trailing_slashes(\u0026new_last))\n \t\t\t\tgoto exit_unlock;\n \t\t}\n \t}\n \t/* unless the source is a directory trailing slashes give -ENOTDIR */\n \tif (!d_is_dir(rd.old_dentry)) {\n \t\terror = -ENOTDIR;\n-\t\tif (old_last.name[old_last.len])\n+\t\tif (trailing_slashes(\u0026old_last))\n \t\t\tgoto exit_unlock;\n-\t\tif (!(flags \u0026 RENAME_EXCHANGE) \u0026\u0026 new_last.name[new_last.len])\n+\t\tif (!(flags \u0026 RENAME_EXCHANGE) \u0026\u0026 trailing_slashes(\u0026new_last))\n \t\t\tgoto exit_unlock;\n \t}\n \ndiff --git a/fs/nfs/dir.c b/fs/nfs/dir.c\nindex 36f2e85889220..b4db703c9e8c7 100644\n--- a/fs/nfs/dir.c\n+++ b/fs/nfs/dir.c\n@@ -2121,6 +2121,9 @@ int nfs_atomic_open(struct inode *dir, struct dentry *dentry,\n \tdfprintk(VFS, \"NFS: atomic_open(%s/%llu), %pd\\n\",\n \t\t\tdir-\u003ei_sb-\u003es_id, dir-\u003ei_ino, dentry);\n \n+\tif (O_IS_MKDIR(open_flags))\n+\t\topen_flags \u0026= ~O_CREAT;\n+\n \terr = nfs_check_flags(open_flags);\n \tif (err)\n \t\treturn err;\n@@ -2313,6 +2316,9 @@ int nfs_atomic_open_v23(struct inode *dir, struct dentry *dentry,\n \t */\n \tint error = 0;\n \n+\tif (O_IS_MKDIR(open_flags))\n+\t\topen_flags \u0026= ~O_CREAT;\n+\n \tif (dentry-\u003ed_name.len \u003e NFS_SERVER(dir)-\u003enamelen)\n \t\treturn -ENAMETOOLONG;\n \ndiff --git a/fs/open.c b/fs/open.c\nindex 6b1c14e684a93..6cb5e2ad781f3 100644\n--- a/fs/open.c\n+++ b/fs/open.c\n@@ -1239,29 +1239,30 @@ inline int build_open_flags(const struct open_how *how, struct open_flags *op)\n \tif (WILL_CREATE(flags)) {\n \t\tif (how-\u003emode \u0026 ~S_IALLUGO)\n \t\t\treturn -EINVAL;\n-\t\top-\u003emode = how-\u003emode | S_IFREG;\n+\t\tif (O_IS_MKDIR(flags))\n+\t\t\top-\u003emode = how-\u003emode | S_IFDIR;\n+\t\telse\n+\t\t\top-\u003emode = how-\u003emode | S_IFREG;\n \t} else {\n \t\tif (how-\u003emode != 0)\n \t\t\treturn -EINVAL;\n \t\top-\u003emode = 0;\n \t}\n \n-\t/*\n-\t * Block bugs where O_DIRECTORY | O_CREAT created regular files.\n-\t * Note, that blocking O_DIRECTORY | O_CREAT here also protects\n-\t * O_TMPFILE below which requires O_DIRECTORY being raised.\n-\t */\n-\tif ((flags \u0026 (O_DIRECTORY | O_CREAT)) == (O_DIRECTORY | O_CREAT))\n-\t\treturn -EINVAL;\n-\n \t/* Now handle the creative implementation of O_TMPFILE. */\n \tif (flags \u0026 __O_TMPFILE) {\n \t\t/*\n \t\t * In order to ensure programs get explicit errors when trying\n \t\t * to use O_TMPFILE on old kernels we enforce that O_DIRECTORY\n-\t\t * is raised alongside __O_TMPFILE.\n+\t\t * is raised alongside __O_TMPFILE, but without O_CREAT. The\n+\t\t * reason for disallowing O_CREAT|O_TMPFILE is that\n+\t\t * O_DIRECTORY|O_CREAT used to work and created a regular file\n+\t\t * if nothing existed at the open path. Hence, allowing the\n+\t\t * combination would have caused O_CREAT|O_TMPFILE to create a\n+\t\t * regular (non-temporary) file on old kernels, while the caller\n+\t\t * would believe they created an actual O_TMPFILE.\n \t\t */\n-\t\tif (!(flags \u0026 O_DIRECTORY))\n+\t\tif (!(flags \u0026 O_DIRECTORY) || (flags \u0026 O_CREAT))\n \t\t\treturn -EINVAL;\n \t\tif (!(acc_mode \u0026 MAY_WRITE))\n \t\t\treturn -EINVAL;\n@@ -1318,6 +1319,15 @@ inline int build_open_flags(const struct open_how *how, struct open_flags *op)\n \n \top-\u003eintent = flags \u0026 O_PATH ? 0 : LOOKUP_OPEN;\n \n+\t/*\n+\t * Requesting write access on a directory can never succeed. Rather\n+\t * than performing a path-walk to determine whether the target is\n+\t * actually a directory (-EISDIR) or not (-ENOTDIR), we short-circuit\n+\t * to -ENOTDIR.\n+\t */\n+\tif ((flags \u0026 O_DIRECTORY) \u0026\u0026 !(flags \u0026 __O_TMPFILE) \u0026\u0026 (acc_mode \u0026 MAY_WRITE))\n+\t\treturn -ENOTDIR;\n+\n \tif (flags \u0026 O_CREAT) {\n \t\top-\u003eintent |= LOOKUP_CREATE;\n \t\tif (flags \u0026 O_EXCL) {\ndiff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c\nindex 7803bd5bd01f8..d1af90c981cd4 100644\n--- a/fs/smb/client/dir.c\n+++ b/fs/smb/client/dir.c\n@@ -538,6 +538,9 @@ int cifs_atomic_open(struct inode *dir, struct dentry *direntry,\n \tif (unlikely(cifs_forced_shutdown(cifs_sb)))\n \t\treturn smb_EIO(smb_eio_trace_forced_shutdown);\n \n+\tif (O_IS_MKDIR(oflags))\n+\t\toflags \u0026= ~O_CREAT;\n+\n \t/*\n \t * Posix open is only called (at lookup time) for file create now. For\n \t * opens (rather than creates), because we do not know if it is a file\ndiff --git a/fs/vboxsf/dir.c b/fs/vboxsf/dir.c\nindex 0b9eab157432d..6e306ddd722bf 100644\n--- a/fs/vboxsf/dir.c\n+++ b/fs/vboxsf/dir.c\n@@ -318,6 +318,9 @@ static int vboxsf_dir_atomic_open(struct inode *parent, struct dentry *dentry,\n \tu64 handle;\n \tint err;\n \n+\tif (O_IS_MKDIR(flags))\n+\t\tflags \u0026= ~O_CREAT;\n+\n \tif (d_in_lookup(dentry)) {\n \t\tstruct dentry *res = vboxsf_dir_lookup(parent, dentry, 0);\n \t\tif (res || d_really_is_positive(dentry))\ndiff --git a/include/linux/fcntl.h b/include/linux/fcntl.h\nindex 6ad6b9e7a226a..204e16bbe2634 100644\n--- a/include/linux/fcntl.h\n+++ b/include/linux/fcntl.h\n@@ -30,6 +30,12 @@\n  */\n #define __O_REGULAR\t\t(1 \u003c\u003c 30)\n \n+#define O_MKDIR_MASK\t(O_CREAT | O_DIRECTORY)\n+static inline bool O_IS_MKDIR(unsigned int flags)\n+{\n+\treturn (flags \u0026 O_MKDIR_MASK) == O_MKDIR_MASK;\n+}\n+\n /* List of all valid flags for the how-\u003eresolve argument: */\n #define VALID_RESOLVE_FLAGS \\\n \t(RESOLVE_NO_XDEV | RESOLVE_NO_MAGICLINKS | RESOLVE_NO_SYMLINKS | \\\ndiff --git a/tools/testing/selftests/filesystems/.gitignore b/tools/testing/selftests/filesystems/.gitignore\nindex 9eb185fb2f9dd..01c588d4c84f6 100644\n--- a/tools/testing/selftests/filesystems/.gitignore\n+++ b/tools/testing/selftests/filesystems/.gitignore\n@@ -1,4 +1,5 @@\n # SPDX-License-Identifier: GPL-2.0-only\n+open_o_creat_o_dir\n dnotify_test\n devpts_pts\n fclog\ndiff --git a/tools/testing/selftests/filesystems/Makefile b/tools/testing/selftests/filesystems/Makefile\nindex 03be337c1f351..0959bd26875ac 100644\n--- a/tools/testing/selftests/filesystems/Makefile\n+++ b/tools/testing/selftests/filesystems/Makefile\n@@ -1,7 +1,7 @@\n # SPDX-License-Identifier: GPL-2.0\n \n CFLAGS += $(KHDR_INCLUDES)\n-TEST_GEN_PROGS := devpts_pts file_stressor anon_inode_test kernfs_test fclog ustat_test\n+TEST_GEN_PROGS := open_o_creat_o_dir devpts_pts file_stressor anon_inode_test kernfs_test fclog ustat_test\n TEST_GEN_PROGS += idmapped_tmpfile\n TEST_GEN_PROGS_EXTENDED := dnotify_test\n \ndiff --git a/tools/testing/selftests/filesystems/open_o_creat_o_dir.c b/tools/testing/selftests/filesystems/open_o_creat_o_dir.c\nnew file mode 100644\nindex 0000000000000..be0ab34267e1d\n--- /dev/null\n+++ b/tools/testing/selftests/filesystems/open_o_creat_o_dir.c\n@@ -0,0 +1,201 @@\n+// SPDX-License-Identifier: GPL-2.0\n+#include \u003csys/stat.h\u003e\n+#include \u003cerrno.h\u003e\n+#include \u003climits.h\u003e\n+#include \u003cfcntl.h\u003e\n+\n+#include \"kselftest_harness.h\"\n+#include \"wrappers.h\"\n+\n+#define openat_o_mkdir_checked_flags(dfd, pathname, flags) ({\t\\\n+\tstruct stat __st;\t\t\t\t\t\t\\\n+\tint __fd = openat_o_mkdir(dfd, pathname, flags, S_IRWXU);\t\\\n+\tASSERT_GE(__fd, 0);\t\t\t\t\t\t\\\n+\tASSERT_EQ(fstat(__fd, \u0026__st), 0);\t\t\t\t\\\n+\tEXPECT_TRUE(S_ISDIR(__st.st_mode));\t\t\t\t\\\n+\t__fd;\t\t\t\t\t\t\t\t\\\n+})\n+\n+#define openat_o_mkdir_checked(dfd, pathname) \\\n+\topenat_o_mkdir_checked_flags(dfd, pathname, O_RDONLY)\n+\n+FIXTURE(open_o_creat_o_dir) {\n+\tchar dirpath[PATH_MAX];\n+\tint dfd;\n+};\n+\n+FIXTURE_SETUP(open_o_creat_o_dir)\n+{\n+\tstrcpy(self-\u003edirpath, \"/tmp/open_o_creat_o_dir_test.XXXXXX\");\n+\tASSERT_NE(mkdtemp(self-\u003edirpath), NULL);\n+\tself-\u003edfd = open(self-\u003edirpath, O_DIRECTORY);\n+\tASSERT_GE(self-\u003edfd, 0);\n+}\n+\n+FIXTURE_TEARDOWN(open_o_creat_o_dir)\n+{\n+\tclose(self-\u003edfd);\n+\trmdir(self-\u003edirpath);\n+}\n+\n+/* Does open_o_creat_o_dir return a fd at all? */\n+TEST_F(open_o_creat_o_dir, returns_fd)\n+{\n+\tint fd = openat_o_mkdir_checked(self-\u003edfd, \"newdir\");\n+\tEXPECT_EQ(close(fd), 0);\n+\tEXPECT_EQ(unlinkat(self-\u003edfd, \"newdir\", AT_REMOVEDIR), 0);\n+}\n+\n+/* The fd must refer to the directory that was just created. */\n+TEST_F(open_o_creat_o_dir, fd_is_created_dir)\n+{\n+\tint fd;\n+\tstruct stat st_via_fd, st_via_path;\n+\tchar path[PATH_MAX];\n+\n+\tfd = openat_o_mkdir_checked(self-\u003edfd, \"checkdir\");\n+\n+\tASSERT_EQ(fstat(fd, \u0026st_via_fd), 0);\n+\n+\tsnprintf(path, sizeof(path), \"%s/checkdir\", self-\u003edirpath);\n+\tASSERT_EQ(stat(path, \u0026st_via_path), 0);\n+\n+\tEXPECT_EQ(st_via_fd.st_ino, st_via_path.st_ino);\n+\tEXPECT_EQ(st_via_fd.st_dev, st_via_path.st_dev);\n+\n+\tEXPECT_EQ(close(fd), 0);\n+\tEXPECT_EQ(rmdir(path), 0);\n+}\n+\n+/* Missing parent component must fail with ENOENT. */\n+TEST_F(open_o_creat_o_dir, enoent_missing_parent)\n+{\n+\tEXPECT_EQ(openat_o_mkdir(self-\u003edfd, \"nonexistent/child\", O_RDONLY, S_IRWXU), -1);\n+\tEXPECT_EQ(errno, ENOENT);\n+}\n+\n+/* An invalid dfd must fail with EBADF. */\n+TEST_F(open_o_creat_o_dir, ebadf)\n+{\n+\tEXPECT_EQ(openat_o_mkdir(FD_INVALID, \"badfdir\", O_RDONLY, S_IRWXU), -1);\n+\tEXPECT_EQ(errno, EBADF);\n+}\n+\n+/* A dfd that points to a file (not a directory) must fail with ENOTDIR. */\n+TEST_F(open_o_creat_o_dir, enotdir_dfd)\n+{\n+\tint file_fd;\n+\n+\tfile_fd = openat(self-\u003edfd, \"file\",\n+\t\t\t O_CREAT | O_RDONLY, S_IRWXU);\n+\tASSERT_GE(file_fd, 0);\n+\n+\tEXPECT_EQ(openat_o_mkdir(file_fd, \"subdir\", O_RDONLY, S_IRWXU), -1);\n+\tEXPECT_EQ(errno, ENOTDIR);\n+\n+\tEXPECT_EQ(close(file_fd), 0);\n+\tEXPECT_EQ(unlinkat(self-\u003edfd, \"file\", 0), 0);\n+}\n+\n+/*\n+ * O_EXCL together with O_CREAT|O_DIRECTORY should succeed if the target\n+ * directory does not yet exist. After directory creation, repeating this\n+ * call must fail with EEXIST.\n+ */\n+TEST_F(open_o_creat_o_dir, o_excl_eexist)\n+{\n+\tint excldir_fd;\n+\n+\texcldir_fd = openat_o_mkdir_checked_flags(self-\u003edfd, \"excldir\", O_EXCL);\n+\n+\tEXPECT_EQ(openat_o_mkdir(excldir_fd, \".\", O_EXCL, S_IRWXU), -1);\n+\tEXPECT_EQ(errno, EEXIST);\n+\n+\tEXPECT_EQ(close(excldir_fd), 0);\n+\tEXPECT_EQ(unlinkat(self-\u003edfd, \"excldir\", AT_REMOVEDIR), 0);\n+}\n+\n+/*\n+ * O_CREAT|O_DIRECTORY on a path that already exists as a regular file\n+ * must fail with ENOTDIR.\n+ */\n+TEST_F(open_o_creat_o_dir, existing_file_enotdir)\n+{\n+\tint file_fd;\n+\n+\tfile_fd = openat(self-\u003edfd, \"regfile\",\n+\t\t\t O_CREAT | O_RDONLY, S_IRWXU);\n+\tASSERT_GE(file_fd, 0);\n+\tEXPECT_EQ(close(file_fd), 0);\n+\n+\tEXPECT_EQ(openat_o_mkdir(self-\u003edfd, \"regfile\", O_RDONLY, S_IRWXU), -1);\n+\tEXPECT_EQ(errno, ENOTDIR);\n+\n+\tEXPECT_EQ(unlinkat(self-\u003edfd, \"regfile\", 0), 0);\n+}\n+\n+/*\n+ * O_CREAT|O_DIRECTORY combined with a writable access mode must be\n+ * rejected: a directory cannot be opened for writing.\n+ */\n+TEST_F(open_o_creat_o_dir, rejects_writable_acc_mode)\n+{\n+\tEXPECT_EQ(openat_o_mkdir(self-\u003edfd, \"rdwrdir\", O_RDWR, S_IRWXU), -1);\n+\tEXPECT_EQ(errno, ENOTDIR);\n+\t/* Clean up if the kernel created the directory anyway. */\n+\tunlinkat(self-\u003edfd, \"rdwrdir\", AT_REMOVEDIR);\n+}\n+\n+/*\n+ * openat(O_CREAT|O_DIRECTORY) with a trailing slash should work.\n+ */\n+TEST_F(open_o_creat_o_dir, trailing_slash)\n+{\n+\tint fd = openat_o_mkdir_checked(self-\u003edfd, \"newdir/\");\n+\tEXPECT_EQ(close(fd), 0);\n+\tEXPECT_EQ(unlinkat(self-\u003edfd, \"newdir\", AT_REMOVEDIR), 0);\n+}\n+\n+/*\n+ * openat(O_CREAT) with a trailing slash but without O_DIRECTORY\n+ * must fail with EISDIR and must not create anything at the path.\n+ */\n+TEST_F(open_o_creat_o_dir, trailing_slash_no_o_dir)\n+{\n+\tint fd;\n+\tstruct stat st;\n+\n+\tfd = openat(self-\u003edfd, \"trailing/\", O_CREAT | O_RDONLY, S_IRWXU);\n+\tEXPECT_EQ(fd, -1);\n+\tEXPECT_EQ(errno, EISDIR);\n+\n+\tEXPECT_EQ(fstatat(self-\u003edfd, \"trailing\", \u0026st, 0), -1);\n+\tEXPECT_EQ(errno, ENOENT);\n+\n+\t/* Best-effort cleanup in case the kernel left a file behind. */\n+\tif (fd \u003e= 0)\n+\t\tclose(fd);\n+\tunlinkat(self-\u003edfd, \"trailing\", 0);\n+}\n+\n+/*\n+ * The returned fd must be usable as a dfd for further *at() calls.\n+ */\n+TEST_F(open_o_creat_o_dir, fd_usable_as_dfd)\n+{\n+\tint parent_fd, child_fd;\n+\tchar path[PATH_MAX];\n+\n+\tparent_fd = openat_o_mkdir_checked(self-\u003edfd, \"parent\");\n+\tchild_fd = openat_o_mkdir_checked(parent_fd, \"child\");\n+\n+\tEXPECT_EQ(close(child_fd), 0);\n+\tEXPECT_EQ(close(parent_fd), 0);\n+\n+\tsnprintf(path, sizeof(path), \"%s/parent/child\", self-\u003edirpath);\n+\tEXPECT_EQ(rmdir(path), 0);\n+\tsnprintf(path, sizeof(path), \"%s/parent\", self-\u003edirpath);\n+\tEXPECT_EQ(rmdir(path), 0);\n+}\n+\n+TEST_HARNESS_MAIN\ndiff --git a/tools/testing/selftests/filesystems/wrappers.h b/tools/testing/selftests/filesystems/wrappers.h\nindex 420ae4f908cf2..abe5b85cebdcd 100644\n--- a/tools/testing/selftests/filesystems/wrappers.h\n+++ b/tools/testing/selftests/filesystems/wrappers.h\n@@ -13,6 +13,10 @@\n #define STATX_MNT_ID_UNIQUE 0x00004000U /* Want/got extended stx_mount_id */\n #endif\n \n+#ifndef FD_INVALID\n+#define FD_INVALID -10009\n+#endif\n+\n static inline int sys_fsopen(const char *fsname, unsigned int flags)\n {\n \treturn syscall(__NR_fsopen, fsname, flags);\n@@ -105,4 +109,11 @@ static inline int sys_open_tree(int dfd, const char *filename, unsigned int flag\n \treturn syscall(__NR_open_tree, dfd, filename, flags);\n }\n \n+static inline int openat_o_mkdir(int dfd, const char *pathname,\n+\t\t\t\t unsigned int flags, mode_t mode)\n+{\n+\treturn syscall(__NR_openat, dfd, pathname,\n+\t\t       flags | O_DIRECTORY | O_CREAT, mode);\n+}\n+\n #endif\n"
}

2/1 2026/08/23 16:14 agent patch-evaluator
2m Model:
best-expensive

Args:
null

Results:
{
  "EnableConfigs": [
    "9P_FS",
    "CEPH_FS",
    "FUSE_FS",
    "GFS2_FS",
    "NFS_FS",
    "CIFS",
    "VBOXSF_FS"
  ],
  "FocusSymbols": [
    "vfs_mkdir",
    "vfs_create",
    "vfs_lookup_open",
    "dentry_create",
    "filename_mkdirat",
    "filename_unlinkat",
    "filename_renameat2",
    "v9fs_vfs_atomic_open",
    "v9fs_vfs_atomic_open_dotl",
    "ceph_atomic_open",
    "fuse_atomic_open",
    "gfs2_atomic_open",
    "nfs_atomic_open",
    "nfs_atomic_open_v23",
    "cifs_atomic_open",
    "vboxsf_dir_atomic_open"
  ],
  "Reasoning": "The patch introduces a new feature `O_CREAT | O_DIRECTORY` for `openat()` to atomically create a directory and return a file descriptor. It modifies core VFS path resolution and creation logic in `fs/namei.c` and `fs/open.c`, and updates several filesystems' `atomic_open` implementations to handle the new flag combination. This is a significant functional change to reachable core kernel logic that should be heavily fuzzed.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
  even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).
- Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed,
  ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU.
- Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove)
  that are executed only during PCI hot-unplug or sysfs driver unbind operations.

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
   Prefer non-static, non-inlined API entrypoint functions over internal static helper functions
   (which are inlined by the compiler and do not have distinct symbol addresses).
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs
   needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs
   output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit d6974f2dae03a314eedcd225149e126b66094b4d
Author: syz-cluster <triage@syzkaller.com>
Date:   Sun Aug 23 16:14:08 2026 +0000

    syz-cluster: applied patch under review

diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c
index 3829554ca3692..b1e0823c87b57 100644
--- a/fs/9p/vfs_inode.c
+++ b/fs/9p/vfs_inode.c
@@ -776,6 +776,9 @@ v9fs_vfs_atomic_open(struct inode *dir, struct dentry *dentry,
 	struct inode *inode;
 	int p9_omode;
 
+	if (O_IS_MKDIR(flags))
+		flags &= ~O_CREAT;
+
 	if (d_in_lookup(dentry)) {
 		struct dentry *res = v9fs_vfs_lookup(dir, dentry, 0);
 		if (res || d_really_is_positive(dentry))
diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c
index 116b29e95f21e..64e0aba08da16 100644
--- a/fs/9p/vfs_inode_dotl.c
+++ b/fs/9p/vfs_inode_dotl.c
@@ -238,6 +238,9 @@ v9fs_vfs_atomic_open_dotl(struct inode *dir, struct dentry *dentry,
 	struct v9fs_session_info *v9ses;
 	struct posix_acl *pacl = NULL, *dacl = NULL;
 
+	if (O_IS_MKDIR(flags))
+		flags &= ~O_CREAT;
+
 	if (d_in_lookup(dentry)) {
 		struct dentry *res = v9fs_vfs_lookup(dir, dentry, 0);
 		if (res || d_really_is_positive(dentry))
diff --git a/fs/ceph/file.c b/fs/ceph/file.c
index a4a2a4b6a027d..62163b7eca5f9 100644
--- a/fs/ceph/file.c
+++ b/fs/ceph/file.c
@@ -812,6 +812,9 @@ int ceph_atomic_open(struct inode *dir, struct dentry *dentry,
 	      dir, ceph_vinop(dir), dentry, dentry,
 	      d_unhashed(dentry) ? "unhashed" : "hashed", flags, mode);
 
+	if (O_IS_MKDIR(flags))
+		flags &= ~O_CREAT;
+
 	if (dentry->d_name.len > NAME_MAX)
 		return -ENAMETOOLONG;
 
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index d4e0029810c0f..55d5844e26559 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -935,6 +935,9 @@ static int fuse_atomic_open(struct inode *dir, struct dentry *entry,
 	struct mnt_idmap *idmap = file_mnt_idmap(file);
 	struct fuse_conn *fc = get_fuse_conn(dir);
 
+	if (O_IS_MKDIR(flags))
+		flags &= ~O_CREAT;
+
 	if (fuse_is_bad(dir))
 		return -EIO;
 
diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c
index f361876c55833..3ee1360f1bc27 100644
--- a/fs/gfs2/inode.c
+++ b/fs/gfs2/inode.c
@@ -1386,6 +1386,9 @@ static int gfs2_atomic_open(struct inode *dir, struct dentry *dentry,
 {
 	bool excl = !!(flags & O_EXCL);
 
+	if (O_IS_MKDIR(flags))
+		flags &= ~O_CREAT;
+
 	if (d_in_lookup(dentry)) {
 		struct dentry *d = __gfs2_lookup(dir, dentry, file);
 		if (file->f_mode & FMODE_OPENED) {
diff --git a/fs/namei.c b/fs/namei.c
index 20a6534ea3eff..e1a8f18385a52 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -1382,13 +1382,13 @@ int may_linkat(struct mnt_idmap *idmap, const struct path *link)
 
 /**
  * may_create_in_sticky - Check whether an O_CREAT open in a sticky directory
- *			  should be allowed, or not, on files that already
- *			  exist.
+ *			  should be allowed, or not, on files/directories that
+ *			  already exist.
  * @idmap: idmap of the mount the inode was found from
  * @nd: nameidata pathwalk data
  * @inode: the inode of the file to open
  *
- * Block an O_CREAT open of a FIFO (or a regular file) when:
+ * Block an O_CREAT open of a FIFO (or a regular file/directory) when:
  *   - sysctl_protected_fifos (or sysctl_protected_regular) is enabled
  *   - the file already exists
  *   - we are in a sticky directory
@@ -1416,6 +1416,14 @@ static int may_create_in_sticky(struct mnt_idmap *idmap, struct nameidata *nd,
 	if (likely(!(dir_mode & S_ISVTX)))
 		return 0;
 
+	/*
+	 * There is no separate sysctl for directory creation in sticky
+	 * folders. Therefore, for the S_ISDIR case, disabling
+	 * sysctl_protected_regular is not enough to allow creating a
+	 * directory in a sticky folder, because that may surprise users
+	 * not expecting that O_CREAT|O_DIRECTORY is possible on newer
+	 * kernels.
+	 */
 	if (S_ISREG(inode->i_mode) && !sysctl_protected_regular)
 		return 0;
 
@@ -1447,6 +1455,12 @@ static int may_create_in_sticky(struct mnt_idmap *idmap, struct nameidata *nd,
 					      "sticky_create_regular");
 			return -EACCES;
 		}
+
+		if (S_ISDIR(inode->i_mode)) {
+			audit_log_path_denied(AUDIT_ANOM_CREAT,
+					      "sticky_create_dir");
+			return -EACCES;
+		}
 	}
 
 	return 0;
@@ -2781,9 +2795,16 @@ static const char *path_init(struct nameidata *nd, unsigned flags)
 	return s;
 }
 
+static inline bool trailing_slashes(const struct qstr *last)
+{
+	/* last->len is set by hash_name() to the length of the current
+	 * component ->name, terminating with '/' or a NUL character. */
+	return (bool)last->name[last->len];
+}
+
 static inline const char *lookup_last(struct nameidata *nd)
 {
-	if (nd->last_type == LAST_NORM && nd->last.name[nd->last.len])
+	if (nd->last_type == LAST_NORM && trailing_slashes(&nd->last))
 		nd->flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
 
 	return walk_component(nd, WALK_TRAILING);
@@ -4159,6 +4180,24 @@ static inline umode_t vfs_prepare_mode(struct mnt_idmap *idmap,
 	return mode;
 }
 
+static inline
+int vfs_create_no_perm(struct mnt_idmap *idmap, struct dentry *dentry,
+		       umode_t mode, struct delegated_inode *di)
+{
+	struct inode *dir = d_inode(dentry->d_parent);
+	int error;
+
+	error = try_break_deleg(dir, LEASE_BREAK_DIR_CREATE, di);
+	if (error)
+		return error;
+
+	error = dir->i_op->create(idmap, dir, dentry, mode);
+	if (!error)
+		fsnotify_create(dir, dentry);
+
+	return error;
+}
+
 /**
  * vfs_create - create new file
  * @idmap:	idmap of the mount the inode was found from
@@ -4191,13 +4230,8 @@ int vfs_create(struct mnt_idmap *idmap, struct dentry *dentry, umode_t mode,
 	error = security_inode_create(dir, dentry, mode);
 	if (error)
 		return error;
-	error = try_break_deleg(dir, LEASE_BREAK_DIR_CREATE, di);
-	if (error)
-		return error;
-	error = dir->i_op->create(idmap, dir, dentry, mode);
-	if (!error)
-		fsnotify_create(dir, dentry);
-	return error;
+
+	return vfs_create_no_perm(idmap, dentry, mode, di);
 }
 EXPORT_SYMBOL(vfs_create);
 
@@ -4314,21 +4348,41 @@ static inline int open_to_namei_flags(int flag)
 
 static int may_o_create(struct mnt_idmap *idmap,
 			const struct path *dir, struct dentry *dentry,
-			umode_t mode)
+			int open_flag, umode_t mode)
 {
-	int error = security_path_mknod(dir, dentry, mode, 0);
+	struct inode *dir_inode = dir->dentry->d_inode;
+	bool create_dir = O_IS_MKDIR(open_flag);
+	int error;
+
+	if (create_dir)
+		error = security_path_mkdir(dir, dentry, mode);
+	else
+		error = security_path_mknod(dir, dentry, mode, 0);
 	if (error)
 		return error;
 
 	if (!fsuidgid_has_mapping(dir->dentry->d_sb, idmap))
 		return -EOVERFLOW;
 
-	error = inode_permission(idmap, dir->dentry->d_inode,
-				 MAY_WRITE | MAY_EXEC);
+	error = inode_permission(idmap, dir_inode, MAY_WRITE | MAY_EXEC);
 	if (error)
 		return error;
 
-	return security_inode_create(dir->dentry->d_inode, dentry, mode);
+	if (create_dir)
+		error = security_inode_mkdir(dir_inode, dentry, mode);
+	else
+		error = security_inode_create(dir_inode, dentry, mode);
+
+	return error;
+}
+
+static inline umode_t o_create_mode(struct mnt_idmap *idmap,
+		const struct inode *dir, int open_flag, umode_t mode)
+{
+	if (O_IS_MKDIR(open_flag))
+		return vfs_prepare_mode(idmap, dir, mode, S_IRWXUGO | S_ISVTX, S_IFDIR);
+	else
+		return vfs_prepare_mode(idmap, dir, mode, S_IALLUGO, S_IFREG);
 }
 
 /**
@@ -4364,8 +4418,9 @@ static struct dentry *atomic_open(const struct path *path, struct dentry *dentry
 
 	file->__f_path.dentry = DENTRY_NOT_SET;
 	file->__f_path.mnt = path->mnt;
+
 	error = dir_inode->i_op->atomic_open(dir_inode, dentry, file,
-				       open_to_namei_flags(open_flag), mode);
+					     open_to_namei_flags(open_flag), mode);
 	d_lookup_done(dentry);
 
 	if (!error) {
@@ -4410,10 +4465,21 @@ static struct dentry *atomic_open(const struct path *path, struct dentry *dentry
 		}
 		dput(dentry);
 		dentry = ERR_PTR(error);
+	} else {
+		if (file->f_mode & FMODE_CREATED)
+			fsnotify_create(dir_inode, dentry);
+		if (file->f_mode & FMODE_OPENED)
+			fsnotify_open(file);
 	}
+
+
 	return dentry;
 }
 
+static inline
+struct dentry *vfs_mkdir_no_perm(struct mnt_idmap *, struct inode *,
+				 struct dentry *, umode_t,
+				 struct delegated_inode *);
 /*
  * Look up and maybe create and open the last component.
  *
@@ -4435,6 +4501,7 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
 	struct mnt_idmap *idmap;
 	struct dentry *dir = nd->path.dentry;
 	struct inode *dir_inode = dir->d_inode;
+	bool create_dir = O_IS_MKDIR(op->mode);
 	int open_flag;
 	struct dentry *dentry;
 	int error, create_error;
@@ -4455,7 +4522,7 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
 		 */
 	}
 	if (open_flag & O_CREAT)
-		inode_lock(dir_inode);
+		inode_lock_nested(dir_inode, I_MUTEX_PARENT);
 	else
 		inode_lock_shared(dir_inode);
 
@@ -4507,12 +4574,17 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
 	if (open_flag & O_CREAT) {
 		if (open_flag & O_EXCL)
 			open_flag &= ~O_TRUNC;
-		mode = vfs_prepare_mode(idmap, dir_inode, mode, mode, mode);
+		mode = o_create_mode(idmap, dir_inode, open_flag, mode);
 		if (likely(got_write))
 			create_error = may_o_create(idmap, &nd->path,
-						    dentry, mode);
+						    dentry, open_flag, mode);
 		else
 			create_error = -EROFS;
+		/* Refuse to create a directory through a dangling (trailing)
+		 * symlink. For regular files this has been allowed historically
+		 * on O_CREAT without O_EXCL. */
+		if (unlikely(nd->depth) && create_dir && !create_error)
+			create_error = -EEXIST;
 	}
 	if (create_error)
 		open_flag &= ~O_CREAT;
@@ -4537,6 +4609,7 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
 			dentry = res;
 		}
 	}
+
 	if (dentry->d_inode || !(op->open_flag & O_CREAT)) {
 		/*
 		 * No need to create a file.  If lookup returned a positive
@@ -4554,26 +4627,26 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
 		goto out_dput;
 	}
 
-	error = try_break_deleg(dir_inode, LEASE_BREAK_DIR_CREATE, &delegated_inode);
-	if (error)
-		goto out_dput;
-
-	file->f_mode |= FMODE_CREATED;
-	if (!dir_inode->i_op->create) {
+	if ((create_dir && !dir_inode->i_op->mkdir)
+		|| (!create_dir && !dir_inode->i_op->create)) {
 		error = -EACCES;
 		goto out_dput;
 	}
 
-	error = dir_inode->i_op->create(idmap, dir_inode, dentry, mode);
+	if (create_dir) {
+		struct dentry *res = vfs_mkdir_no_perm(idmap, dir_inode, dentry,
+						       mode, &delegated_inode);
+			error = PTR_ERR_OR_ZERO(res);
+			if (!error)
+				dentry = res;
+	} else {
+		error = vfs_create_no_perm(idmap, dentry, mode, &delegated_inode);
+	}
 	if (error)
 		goto out_dput;
+
+	file->f_mode |= FMODE_CREATED;
 out:
-	if (!IS_ERR(dentry)) {
-		if (file->f_mode & FMODE_CREATED)
-			fsnotify_create(dir_inode, dentry);
-		if (file->f_mode & FMODE_OPENED)
-			fsnotify_open(file);
-	}
 	if ((open_flag & O_CREAT) || create_error)
 		inode_unlock(dir_inode);
 	else
@@ -4695,17 +4768,12 @@ struct file *vfs_lookup_open(struct path *parent, struct qstr *last,
 }
 EXPORT_SYMBOL_FOR_MODULES(vfs_lookup_open, "nfsd");
 
-static inline bool trailing_slashes(struct nameidata *nd)
-{
-	return (bool)nd->last.name[nd->last.len];
-}
-
 static struct dentry *lookup_fast_for_open(struct nameidata *nd, int open_flag)
 {
 	struct dentry *dentry;
 
 	if (open_flag & O_CREAT) {
-		if (trailing_slashes(nd))
+		if (trailing_slashes(&nd->last) && !(open_flag & O_DIRECTORY))
 			return ERR_PTR(-EISDIR);
 
 		/* Don't bother on an O_EXCL create */
@@ -4713,7 +4781,7 @@ static struct dentry *lookup_fast_for_open(struct nameidata *nd, int open_flag)
 			return NULL;
 	}
 
-	if (trailing_slashes(nd))
+	if (trailing_slashes(&nd->last))
 		nd->flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
 
 	dentry = lookup_fast(nd);
@@ -4806,8 +4874,9 @@ static int do_open(struct nameidata *nd,
 	if (open_flag & O_CREAT) {
 		if ((open_flag & O_EXCL) && !(file->f_mode & FMODE_CREATED))
 			return -EEXIST;
-		if (d_is_dir(nd->path.dentry))
+		if (!(open_flag & O_DIRECTORY) && d_is_dir(nd->path.dentry))
 			return -EISDIR;
+
 		error = may_create_in_sticky(idmap, nd,
 					     d_backing_inode(nd->path.dentry));
 		if (unlikely(error))
@@ -5087,7 +5156,7 @@ static struct dentry *filename_create(int dfd, struct filename *name,
 	 * Do the final lookup.  Suppress 'create' if there is a trailing
 	 * '/', and a directory wasn't requested.
 	 */
-	if (last.name[last.len] && !want_dir)
+	if (trailing_slashes(&last) && !want_dir)
 		create_flags &= ~LOOKUP_CREATE;
 	dentry = start_dirop(path->dentry, &last, reval_flag | create_flags);
 	if (IS_ERR(dentry))
@@ -5182,7 +5251,7 @@ struct file *dentry_create(struct path *path, int flags, umode_t mode,
 		path->dentry = dir;
 		mode = vfs_prepare_mode(idmap, dir_inode, mode, S_IALLUGO, S_IFREG);
 
-		create_error = may_o_create(idmap, path, dentry, mode);
+		create_error = may_o_create(idmap, path, dentry, flags, mode);
 		if (create_error)
 			flags &= ~O_CREAT;
 
@@ -5356,6 +5425,34 @@ SYSCALL_DEFINE3(mknod, const char __user *, filename, umode_t, mode, unsigned, d
 	return filename_mknodat(AT_FDCWD, name, mode, dev);
 }
 
+/* Returns the dentry to use (not NULL) or -E on error */
+static inline
+struct dentry *vfs_mkdir_no_perm(struct mnt_idmap *idmap, struct inode *dir,
+				 struct dentry *dentry, umode_t mode,
+				 struct delegated_inode *di)
+{
+	int error;
+	struct dentry *de;
+	unsigned max_links = dir->i_sb->s_max_links;
+
+	if (max_links && dir->i_nlink >= max_links)
+		return ERR_PTR(-EMLINK);
+
+	error = try_break_deleg(dir, LEASE_BREAK_DIR_CREATE, di);
+	if (error)
+		return ERR_PTR(error);
+
+	de = dir->i_op->mkdir(idmap, dir, dentry, mode);
+	if (IS_ERR(de))
+		return de;
+	if (de) {
+		dput(dentry);
+		dentry = de;
+	}
+	fsnotify_mkdir(dir, dentry);
+	return dentry;
+}
+
 /**
  * vfs_mkdir - create directory returning correct dentry if possible
  * @idmap:		idmap of the mount the inode was found from
@@ -5383,7 +5480,6 @@ struct dentry *vfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
 			 struct delegated_inode *delegated_inode)
 {
 	int error;
-	unsigned max_links = dir->i_sb->s_max_links;
 	struct dentry *de;
 
 	error = may_create_dentry(idmap, dir, dentry);
@@ -5399,24 +5495,12 @@ struct dentry *vfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
 	if (error)
 		goto err;
 
-	error = -EMLINK;
-	if (max_links && dir->i_nlink >= max_links)
-		goto err;
-
-	error = try_break_deleg(dir, LEASE_BREAK_DIR_CREATE, delegated_inode);
-	if (error)
-		goto err;
-
-	de = dir->i_op->mkdir(idmap, dir, dentry, mode);
-	error = PTR_ERR(de);
-	if (IS_ERR(de))
+	de = vfs_mkdir_no_perm(idmap, dir, dentry, mode, delegated_inode);
+	if (IS_ERR(de)) {
+		error = PTR_ERR(de);
 		goto err;
-	if (de) {
-		dput(dentry);
-		dentry = de;
 	}
-	fsnotify_mkdir(dir, dentry);
-	return dentry;
+	return de;
 
 err:
 	end_creating(dentry);
@@ -5703,7 +5787,7 @@ int filename_unlinkat(int dfd, struct filename *name)
 		goto exit_drop_write;
 
 	/* Why not before? Because we want correct error value */
-	if (unlikely(last.name[last.len])) {
+	if (unlikely(trailing_slashes(&last))) {
 		if (d_is_dir(dentry))
 			error = -EISDIR;
 		else
@@ -6305,16 +6389,16 @@ int filename_renameat2(int olddfd, struct filename *from,
 	if (flags & RENAME_EXCHANGE) {
 		if (!d_is_dir(rd.new_dentry)) {
 			error = -ENOTDIR;
-			if (new_last.name[new_last.len])
+			if (trailing_slashes(&new_last))
 				goto exit_unlock;
 		}
 	}
 	/* unless the source is a directory trailing slashes give -ENOTDIR */
 	if (!d_is_dir(rd.old_dentry)) {
 		error = -ENOTDIR;
-		if (old_last.name[old_last.len])
+		if (trailing_slashes(&old_last))
 			goto exit_unlock;
-		if (!(flags & RENAME_EXCHANGE) && new_last.name[new_last.len])
+		if (!(flags & RENAME_EXCHANGE) && trailing_slashes(&new_last))
 			goto exit_unlock;
 	}
 
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index 36f2e85889220..b4db703c9e8c7 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -2121,6 +2121,9 @@ int nfs_atomic_open(struct inode *dir, struct dentry *dentry,
 	dfprintk(VFS, "NFS: atomic_open(%s/%llu), %pd\n",
 			dir->i_sb->s_id, dir->i_ino, dentry);
 
+	if (O_IS_MKDIR(open_flags))
+		open_flags &= ~O_CREAT;
+
 	err = nfs_check_flags(open_flags);
 	if (err)
 		return err;
@@ -2313,6 +2316,9 @@ int nfs_atomic_open_v23(struct inode *dir, struct dentry *dentry,
 	 */
 	int error = 0;
 
+	if (O_IS_MKDIR(open_flags))
+		open_flags &= ~O_CREAT;
+
 	if (dentry->d_name.len > NFS_SERVER(dir)->namelen)
 		return -ENAMETOOLONG;
 
diff --git a/fs/open.c b/fs/open.c
index 6b1c14e684a93..6cb5e2ad781f3 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -1239,29 +1239,30 @@ inline int build_open_flags(const struct open_how *how, struct open_flags *op)
 	if (WILL_CREATE(flags)) {
 		if (how->mode & ~S_IALLUGO)
 			return -EINVAL;
-		op->mode = how->mode | S_IFREG;
+		if (O_IS_MKDIR(flags))
+			op->mode = how->mode | S_IFDIR;
+		else
+			op->mode = how->mode | S_IFREG;
 	} else {
 		if (how->mode != 0)
 			return -EINVAL;
 		op->mode = 0;
 	}
 
-	/*
-	 * Block bugs where O_DIRECTORY | O_CREAT created regular files.
-	 * Note, that blocking O_DIRECTORY | O_CREAT here also protects
-	 * O_TMPFILE below which requires O_DIRECTORY being raised.
-	 */
-	if ((flags & (O_DIRECTORY | O_CREAT)) == (O_DIRECTORY | O_CREAT))
-		return -EINVAL;
-
 	/* Now handle the creative implementation of O_TMPFILE. */
 	if (flags & __O_TMPFILE) {
 		/*
 		 * In order to ensure programs get explicit errors when trying
 		 * to use O_TMPFILE on old kernels we enforce that O_DIRECTORY
-		 * is raised alongside __O_TMPFILE.
+		 * is raised alongside __O_TMPFILE, but without O_CREAT. The
+		 * reason for disallowing O_CREAT|O_TMPFILE is that
+		 * O_DIRECTORY|O_CREAT used to work and created a regular file
+		 * if nothing existed at the open path. Hence, allowing the
+		 * combination would have caused O_CREAT|O_TMPFILE to create a
+		 * regular (non-temporary) file on old kernels, while the caller
+		 * would believe they created an actual O_TMPFILE.
 		 */
-		if (!(flags & O_DIRECTORY))
+		if (!(flags & O_DIRECTORY) || (flags & O_CREAT))
 			return -EINVAL;
 		if (!(acc_mode & MAY_WRITE))
 			return -EINVAL;
@@ -1318,6 +1319,15 @@ inline int build_open_flags(const struct open_how *how, struct open_flags *op)
 
 	op->intent = flags & O_PATH ? 0 : LOOKUP_OPEN;
 
+	/*
+	 * Requesting write access on a directory can never succeed. Rather
+	 * than performing a path-walk to determine whether the target is
+	 * actually a directory (-EISDIR) or not (-ENOTDIR), we short-circuit
+	 * to -ENOTDIR.
+	 */
+	if ((flags & O_DIRECTORY) && !(flags & __O_TMPFILE) && (acc_mode & MAY_WRITE))
+		return -ENOTDIR;
+
 	if (flags & O_CREAT) {
 		op->intent |= LOOKUP_CREATE;
 		if (flags & O_EXCL) {
diff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c
index 7803bd5bd01f8..d1af90c981cd4 100644
--- a/fs/smb/client/dir.c
+++ b/fs/smb/client/dir.c
@@ -538,6 +538,9 @@ int cifs_atomic_open(struct inode *dir, struct dentry *direntry,
 	if (unlikely(cifs_forced_shutdown(cifs_sb)))
 		return smb_EIO(smb_eio_trace_forced_shutdown);
 
+	if (O_IS_MKDIR(oflags))
+		oflags &= ~O_CREAT;
+
 	/*
 	 * Posix open is only called (at lookup time) for file create now. For
 	 * opens (rather than creates), because we do not know if it is a file
diff --git a/fs/vboxsf/dir.c b/fs/vboxsf/dir.c
index 0b9eab157432d..6e306ddd722bf 100644
--- a/fs/vboxsf/dir.c
+++ b/fs/vboxsf/dir.c
@@ -318,6 +318,9 @@ static int vboxsf_dir_atomic_open(struct inode *parent, struct dentry *dentry,
 	u64 handle;
 	int err;
 
+	if (O_IS_MKDIR(flags))
+		flags &= ~O_CREAT;
+
 	if (d_in_lookup(dentry)) {
 		struct dentry *res = vboxsf_dir_lookup(parent, dentry, 0);
 		if (res || d_really_is_positive(dentry))
diff --git a/include/linux/fcntl.h b/include/linux/fcntl.h
index 6ad6b9e7a226a..204e16bbe2634 100644
--- a/include/linux/fcntl.h
+++ b/include/linux/fcntl.h
@@ -30,6 +30,12 @@
  */
 #define __O_REGULAR		(1 << 30)
 
+#define O_MKDIR_MASK	(O_CREAT | O_DIRECTORY)
+static inline bool O_IS_MKDIR(unsigned int flags)
+{
+	return (flags & O_MKDIR_MASK) == O_MKDIR_MASK;
+}
+
 /* List of all valid flags for the how->resolve argument: */
 #define VALID_RESOLVE_FLAGS \
 	(RESOLVE_NO_XDEV | RESOLVE_NO_MAGICLINKS | RESOLVE_NO_SYMLINKS | \
diff --git a/tools/testing/selftests/filesystems/.gitignore b/tools/testing/selftests/filesystems/.gitignore
index 9eb185fb2f9dd..01c588d4c84f6 100644
--- a/tools/testing/selftests/filesystems/.gitignore
+++ b/tools/testing/selftests/filesystems/.gitignore
@@ -1,4 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0-only
+open_o_creat_o_dir
 dnotify_test
 devpts_pts
 fclog
diff --git a/tools/testing/selftests/filesystems/Makefile b/tools/testing/selftests/filesystems/Makefile
index 03be337c1f351..0959bd26875ac 100644
--- a/tools/testing/selftests/filesystems/Makefile
+++ b/tools/testing/selftests/filesystems/Makefile
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 
 CFLAGS += $(KHDR_INCLUDES)
-TEST_GEN_PROGS := devpts_pts file_stressor anon_inode_test kernfs_test fclog ustat_test
+TEST_GEN_PROGS := open_o_creat_o_dir devpts_pts file_stressor anon_inode_test kernfs_test fclog ustat_test
 TEST_GEN_PROGS += idmapped_tmpfile
 TEST_GEN_PROGS_EXTENDED := dnotify_test
 
diff --git a/tools/testing/selftests/filesystems/open_o_creat_o_dir.c b/tools/testing/selftests/filesystems/open_o_creat_o_dir.c
new file mode 100644
index 0000000000000..be0ab34267e1d
--- /dev/null
+++ b/tools/testing/selftests/filesystems/open_o_creat_o_dir.c
@@ -0,0 +1,201 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <sys/stat.h>
+#include <errno.h>
+#include <limits.h>
+#include <fcntl.h>
+
+#include "kselftest_harness.h"
+#include "wrappers.h"
+
+#define openat_o_mkdir_checked_flags(dfd, pathname, flags) ({	\
+	struct stat __st;						\
+	int __fd = openat_o_mkdir(dfd, pathname, flags, S_IRWXU);	\
+	ASSERT_GE(__fd, 0);						\
+	ASSERT_EQ(fstat(__fd, &__st), 0);				\
+	EXPECT_TRUE(S_ISDIR(__st.st_mode));				\
+	__fd;								\
+})
+
+#define openat_o_mkdir_checked(dfd, pathname) \
+	openat_o_mkdir_checked_flags(dfd, pathname, O_RDONLY)
+
+FIXTURE(open_o_creat_o_dir) {
+	char dirpath[PATH_MAX];
+	int dfd;
+};
+
+FIXTURE_SETUP(open_o_creat_o_dir)
+{
+	strcpy(self->dirpath, "/tmp/open_o_creat_o_dir_test.XXXXXX");
+	ASSERT_NE(mkdtemp(self->dirpath), NULL);
+	self->dfd = open(self->dirpath, O_DIRECTORY);
+	ASSERT_GE(self->dfd, 0);
+}
+
+FIXTURE_TEARDOWN(open_o_creat_o_dir)
+{
+	close(self->dfd);
+	rmdir(self->dirpath);
+}
+
+/* Does open_o_creat_o_dir return a fd at all? */
+TEST_F(open_o_creat_o_dir, returns_fd)
+{
+	int fd = openat_o_mkdir_checked(self->dfd, "newdir");
+	EXPECT_EQ(close(fd), 0);
+	EXPECT_EQ(unlinkat(self->dfd, "newdir", AT_REMOVEDIR), 0);
+}
+
+/* The fd must refer to the directory that was just created. */
+TEST_F(open_o_creat_o_dir, fd_is_created_dir)
+{
+	int fd;
+	struct stat st_via_fd, st_via_path;
+	char path[PATH_MAX];
+
+	fd = openat_o_mkdir_checked(self->dfd, "checkdir");
+
+	ASSERT_EQ(fstat(fd, &st_via_fd), 0);
+
+	snprintf(path, sizeof(path), "%s/checkdir", self->dirpath);
+	ASSERT_EQ(stat(path, &st_via_path), 0);
+
+	EXPECT_EQ(st_via_fd.st_ino, st_via_path.st_ino);
+	EXPECT_EQ(st_via_fd.st_dev, st_via_path.st_dev);
+
+	EXPECT_EQ(close(fd), 0);
+	EXPECT_EQ(rmdir(path), 0);
+}
+
+/* Missing parent component must fail with ENOENT. */
+TEST_F(open_o_creat_o_dir, enoent_missing_parent)
+{
+	EXPECT_EQ(openat_o_mkdir(self->dfd, "nonexistent/child", O_RDONLY, S_IRWXU), -1);
+	EXPECT_EQ(errno, ENOENT);
+}
+
+/* An invalid dfd must fail with EBADF. */
+TEST_F(open_o_creat_o_dir, ebadf)
+{
+	EXPECT_EQ(openat_o_mkdir(FD_INVALID, "badfdir", O_RDONLY, S_IRWXU), -1);
+	EXPECT_EQ(errno, EBADF);
+}
+
+/* A dfd that points to a file (not a directory) must fail with ENOTDIR. */
+TEST_F(open_o_creat_o_dir, enotdir_dfd)
+{
+	int file_fd;
+
+	file_fd = openat(self->dfd, "file",
+			 O_CREAT | O_RDONLY, S_IRWXU);
+	ASSERT_GE(file_fd, 0);
+
+	EXPECT_EQ(openat_o_mkdir(file_fd, "subdir", O_RDONLY, S_IRWXU), -1);
+	EXPECT_EQ(errno, ENOTDIR);
+
+	EXPECT_EQ(close(file_fd), 0);
+	EXPECT_EQ(unlinkat(self->dfd, "file", 0), 0);
+}
+
+/*
+ * O_EXCL together with O_CREAT|O_DIRECTORY should succeed if the target
+ * directory does not yet exist. After directory creation, repeating this
+ * call must fail with EEXIST.
+ */
+TEST_F(open_o_creat_o_dir, o_excl_eexist)
+{
+	int excldir_fd;
+
+	excldir_fd = openat_o_mkdir_checked_flags(self->dfd, "excldir", O_EXCL);
+
+	EXPECT_EQ(openat_o_mkdir(excldir_fd, ".", O_EXCL, S_IRWXU), -1);
+	EXPECT_EQ(errno, EEXIST);
+
+	EXPECT_EQ(close(excldir_fd), 0);
+	EXPECT_EQ(unlinkat(self->dfd, "excldir", AT_REMOVEDIR), 0);
+}
+
+/*
+ * O_CREAT|O_DIRECTORY on a path that already exists as a regular file
+ * must fail with ENOTDIR.
+ */
+TEST_F(open_o_creat_o_dir, existing_file_enotdir)
+{
+	int file_fd;
+
+	file_fd = openat(self->dfd, "regfile",
+			 O_CREAT | O_RDONLY, S_IRWXU);
+	ASSERT_GE(file_fd, 0);
+	EXPECT_EQ(close(file_fd), 0);
+
+	EXPECT_EQ(openat_o_mkdir(self->dfd, "regfile", O_RDONLY, S_IRWXU), -1);
+	EXPECT_EQ(errno, ENOTDIR);
+
+	EXPECT_EQ(unlinkat(self->dfd, "regfile", 0), 0);
+}
+
+/*
+ * O_CREAT|O_DIRECTORY combined with a writable access mode must be
+ * rejected: a directory cannot be opened for writing.
+ */
+TEST_F(open_o_creat_o_dir, rejects_writable_acc_mode)
+{
+	EXPECT_EQ(openat_o_mkdir(self->dfd, "rdwrdir", O_RDWR, S_IRWXU), -1);
+	EXPECT_EQ(errno, ENOTDIR);
+	/* Clean up if the kernel created the directory anyway. */
+	unlinkat(self->dfd, "rdwrdir", AT_REMOVEDIR);
+}
+
+/*
+ * openat(O_CREAT|O_DIRECTORY) with a trailing slash should work.
+ */
+TEST_F(open_o_creat_o_dir, trailing_slash)
+{
+	int fd = openat_o_mkdir_checked(self->dfd, "newdir/");
+	EXPECT_EQ(close(fd), 0);
+	EXPECT_EQ(unlinkat(self->dfd, "newdir", AT_REMOVEDIR), 0);
+}
+
+/*
+ * openat(O_CREAT) with a trailing slash but without O_DIRECTORY
+ * must fail with EISDIR and must not create anything at the path.
+ */
+TEST_F(open_o_creat_o_dir, trailing_slash_no_o_dir)
+{
+	int fd;
+	struct stat st;
+
+	fd = openat(self->dfd, "trailing/", O_CREAT | O_RDONLY, S_IRWXU);
+	EXPECT_EQ(fd, -1);
+	EXPECT_EQ(errno, EISDIR);
+
+	EXPECT_EQ(fstatat(self->dfd, "trailing", &st, 0), -1);
+	EXPECT_EQ(errno, ENOENT);
+
+	/* Best-effort cleanup in case the kernel left a file behind. */
+	if (fd >= 0)
+		close(fd);
+	unlinkat(self->dfd, "trailing", 0);
+}
+
+/*
+ * The returned fd must be usable as a dfd for further *at() calls.
+ */
+TEST_F(open_o_creat_o_dir, fd_usable_as_dfd)
+{
+	int parent_fd, child_fd;
+	char path[PATH_MAX];
+
+	parent_fd = openat_o_mkdir_checked(self->dfd, "parent");
+	child_fd = openat_o_mkdir_checked(parent_fd, "child");
+
+	EXPECT_EQ(close(child_fd), 0);
+	EXPECT_EQ(close(parent_fd), 0);
+
+	snprintf(path, sizeof(path), "%s/parent/child", self->dirpath);
+	EXPECT_EQ(rmdir(path), 0);
+	snprintf(path, sizeof(path), "%s/parent", self->dirpath);
+	EXPECT_EQ(rmdir(path), 0);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/filesystems/wrappers.h b/tools/testing/selftests/filesystems/wrappers.h
index 420ae4f908cf2..abe5b85cebdcd 100644
--- a/tools/testing/selftests/filesystems/wrappers.h
+++ b/tools/testing/selftests/filesystems/wrappers.h
@@ -13,6 +13,10 @@
 #define STATX_MNT_ID_UNIQUE 0x00004000U /* Want/got extended stx_mount_id */
 #endif
 
+#ifndef FD_INVALID
+#define FD_INVALID -10009
+#endif
+
 static inline int sys_fsopen(const char *fsname, unsigned int flags)
 {
 	return syscall(__NR_fsopen, fsname, flags);
@@ -105,4 +109,11 @@ static inline int sys_open_tree(int dfd, const char *filename, unsigned int flag
 	return syscall(__NR_open_tree, dfd, filename, flags);
 }
 
+static inline int openat_o_mkdir(int dfd, const char *pathname,
+				 unsigned int flags, mode_t mode)
+{
+	return syscall(__NR_openat, dfd, pathname,
+		       flags | O_DIRECTORY | O_CREAT, mode);
+}
+
 #endif

3/2 2026/08/23 16:14 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 13623
							output: 35
							thoughts: 1457
						

Thoughts:
**Analyzing the New Atomic Directory Creation Feature**

Okay, so we've got this new patch introducing `O_CREAT | O_DIRECTORY`, essentially `O_IS_MKDIR`, allowing for an atomic `openat()` that creates a directory and returns a file descriptor. My initial assessment is that this is a fairly substantial change, particularly to the VFS layer, specifically impacting `openat()` and path resolution/creation logic within `namei.c`.  Given my expertise, this immediately screams "fuzzing target."

The core modification in `fs/open.c` is pretty straightforward: adding a check for `O_IS_MKDIR` within the `WILL_CREATE` block. The definition of `O_IS_MKDIR` itself looks clean.  The interesting part is how this propagates through `namei.c`.  `may_o_create` now has a `create_dir` flag, and `lookup_open`'s logic branches to `vfs_mkdir_no_perm` or `vfs_create_no_perm` based on that flag. This is where the core functionality resides.

Now, let's identify the key symbols for fuzzing. We know `vfs_mkdir_no_perm`, `may_o_create`, `lookup_open`, `atomic_open`, `do_open`, `build_open_flags` are touched.  The filesystem-specific `atomic_open` implementations are also affected, but they are specific to different file systems and do not have an impact on the core VFS functions. Let's dig deeper: after checking, it turns out that `vfs_mkdir_no_perm`, `may_o_create`, `lookup_open`, `atomic_open`, and `do_open` are static to the files they are in. `build_open_flags` is inline, so it won't be exposed as a distinct symbol.

I need to identify any non-static functions that are modified. `vfs_create`, `vfs_mkdir`, `filename_create`, `dentry_create`, `filename_unlinkat`, and `filename_renameat2` are modified. Looking specifically at `fs/namei.c`, these are not static, which means these are the symbols exposed.

The key entry points are `do_sys_openat2`, the main `openat2` and `openat` handler, and `path_openat`. Also, the `do_filp_open` function is a core function.

Given the intent is to fuzz `O_CREAT | O_DIRECTORY`, the primary goal is to target `openat` and `openat2` with these flags.  Syzkaller already has coverage for `openat` generally, but the new logic is in these symbols: `vfs_mkdir`, `vfs_create`, and `lookup_open`. Because these functions aren't static, they can be targeted by fuzzing. If `lookup_open` is not inlined.

The focus should be on creating test cases that specifically exercise the atomic directory creation path to expose any potential regressions or subtle issues.



4/2 2026/08/23 16:14 tool grepper
0m Args:
{
  "Expression": "^static struct dentry \\*lookup_open",
  "PathPrefix": "fs/namei.c"
}

Results:
{
  "Output": "fs/namei.c=4480=struct dentry *vfs_mkdir_no_perm(struct mnt_idmap *, struct inode *,\n--\nfs/namei.c-4496- */\nfs/namei.c:4497:static struct dentry *lookup_open(struct nameidata *nd, struct file *file,\nfs/namei.c-4498-\t\t\t\t  const struct open_flags *op)\n"
}

5/2 2026/08/23 16:14 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 15909
							output: 35
							thoughts: 43
						

Thoughts:
Okay, here's my interpretation of those thoughts, summarized from my perspective as a seasoned expert:

**Understanding Syzkaller's Traceability**

Alright, so the key here is that `lookup_open` function is declared as `static` but it's *not* inlined. That's good from a debugging perspective, especially when working with something like Syzkaller. Because it's static, the compiler won't try to optimize it away into the call sites. If it *were* inlined, Syzkaller's tracing capabilities would be compromised, making it much harder to observe its behavior directly. I need to keep that in mind when examining the trace results. Now, the context has led me to `atomic_open` in `fs/namei.c`. Let's see how that fits into this picture...



6/2 2026/08/23 16:14 tool grepper
0m Args:
{
  "Expression": "^static struct dentry \\*atomic_open",
  "PathPrefix": "fs/namei.c"
}

Results:
{
  "Output": "fs/namei.c=4379=static inline umode_t o_create_mode(struct mnt_idmap *idmap,\n--\nfs/namei.c-4410- */\nfs/namei.c:4411:static struct dentry *atomic_open(const struct path *path, struct dentry *dentry,\nfs/namei.c-4412-\t\t\t\t  struct file *file,\n"
}

7/2 2026/08/23 16:14 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 16283
							output: 32
							thoughts: 27
						

Thoughts:
Here's my take on the situation:

**Reflecting on File System Operations**

Okay, so I'm digging into this file system code, specifically looking at file opening. I've noticed something interesting about `atomic_open`. It's defined as `static`, which isn't completely unexpected, but it's *also* not an `inline` function. That's a deliberate choice, and I need to figure out the implications. Typically, I'd expect an optimization here with something related to inlining, but maybe there's a good reason for it being `static` and not `inline` - perhaps to prevent code bloat, keep function scope tight, or because the compiler is unable to inline for certain uses, etc.

Now, I'm turning my attention to `do_open`, and I'm looking at it's definition in `fs/namei.c`. My mind is running through the different scenarios, looking to see how `atomic_open` is used. I'm wondering if `atomic_open` is used as a low level helper function and is never inlined to prevent complications of a function being called in multiple places. It is also possible that `atomic_open` is very long, so the compiler would not be able to inline it.



8/2 2026/08/23 16:14 tool grepper
0m Args:
{
  "Expression": "^static int do_open",
  "PathPrefix": "fs/namei.c"
}

Results:
{
  "Output": "fs/namei.c=4802=static const char *open_last_lookups(struct nameidata *nd,\n--\nfs/namei.c-4856- */\nfs/namei.c:4857:static int do_open(struct nameidata *nd,\nfs/namei.c-4858-\t\t   struct file *file, const struct open_flags *op)\n"
}

9/2 2026/08/23 16:14 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 16726
							output: 34
							thoughts: 533
						

Thoughts:
**Analyzing Filesystem Entrypoints**

Okay, so I'm digging into these filesystem entry points. My initial observations are that `do_open` is definitely static but not inlined, which is good. `vfs_mkdir` and `vfs_create` are both exported, so they're solid candidates.

Now, let's look at the filesystem-specific atomic open functions to get a broader view. I'm seeing: `v9fs_vfs_atomic_open`, `v9fs_vfs_atomic_open_dotl`, `ceph_atomic_open`, `fuse_atomic_open`, `gfs2_atomic_open`, `nfs_atomic_open`, `nfs_atomic_open_v23`, `cifs_atomic_open`, and `vboxsf_dir_atomic_open`. From a cursory glance, these all look like they're either not static or are static but used as function pointers – which means they won't be inlined. Except...wait, there's `fuse_atomic_open`. It *is* static: `static int fuse_atomic_open(struct inode *dir, struct dentry *entry,`. But I remember it's assigned to `inode_operations->atomic_open`, so the compiler won't be able to inline it in a useful way.

Let me make a quick list of what I'm focused on, just to keep everything straight: `vfs_mkdir`, `vfs_create`, `lookup_open`, `atomic_open`, `do_open`, `v9fs_vfs_atomic_open`, `v9fs_vfs_atomic_open_dotl`, `ceph_atomic_open`, `fuse_atomic_open`, `gfs2_atomic_open`, `nfs_atomic_open`, `nfs_atomic_open_v23`, `cifs_atomic_open`, and `vboxsf_dir_atomic_open`.

Right, the point of all this is to prefer non-static, non-inlined entry points. Okay, it looks like `lookup_open`, `atomic_open`, and `do_open` are static. So I can't be sure if they are inlined or not. Given their size, they are most likely not, but it's always safer to choose non-static if possible. `path_openat` is also static. However, `do_filp_open` is non-static, as are `vfs_mkdir` and `vfs_create`. I should double-check about `filename_create`.




10/2 2026/08/23 16:14 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "^struct dentry \\*filename_create",
  "PathPrefix": "fs/namei.c"
}

Results:
{
  "error": "no matches"
}

11/2 2026/08/23 16:14 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 17873
							output: 29
							thoughts: 10
						

Thoughts:
Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:

**My Initial Assessment of `filename_create`**

Right, `filename_create`… that's a static function. My mind immediately flags that because of the implications. It means this function is likely designed to be used without an instance of a class or a context tied to a specific object state. It's probably intended as a utility function, perhaps for generating filenames based on some specific criteria, independent of any object's current configuration. I need to consider what the best approach to working with this might be, considering it's static. Are there any potential issues with that approach? Does it depend on global state, or will it impact any testing or threading scenarios? I need to think about that, too.



12/2 2026/08/23 16:14 tool grepper
0m Args:
{
  "Expression": "filename_create",
  "PathPrefix": "fs/namei.c"
}

Results:
{
  "Output": "fs/namei.c=5107=struct file *do_file_open_root(const struct path *root,\n--\nfs/namei.c-5130-\nfs/namei.c:5131:static struct dentry *filename_create(int dfd, struct filename *name,\nfs/namei.c-5132-\t\t\t\t      struct path *path, unsigned int lookup_flags)\n--\nfs/namei.c=5180=struct dentry *start_creating_path(int dfd, const char *pathname,\n--\nfs/namei.c-5183-\tCLASS(filename_kernel, filename)(pathname);\nfs/namei.c:5184:\treturn filename_create(dfd, filename, path, lookup_flags);\nfs/namei.c-5185-}\n--\nfs/namei.c=5207=inline struct dentry *start_creating_user_path(\n--\nfs/namei.c-5211-\tCLASS(filename, filename)(pathname);\nfs/namei.c:5212:\treturn filename_create(dfd, filename, path, lookup_flags);\nfs/namei.c-5213-}\n--\nfs/namei.c=5362=int filename_mknodat(int dfd, struct filename *name, umode_t mode,\n--\nfs/namei.c-5375-retry:\nfs/namei.c:5376:\tdentry = filename_create(dfd, name, \u0026path, lookup_flags);\nfs/namei.c-5377-\tif (IS_ERR(dentry))\n--\nfs/namei.c=5511=int filename_mkdirat(int dfd, struct filename *name, umode_t mode)\n--\nfs/namei.c-5519-retry:\nfs/namei.c:5520:\tdentry = filename_create(dfd, name, \u0026path, lookup_flags);\nfs/namei.c-5521-\tif (IS_ERR(dentry))\n--\nfs/namei.c=5885=int filename_symlinkat(struct filename *from, int newdfd, struct filename *to)\n--\nfs/namei.c-5896-retry:\nfs/namei.c:5897:\tdentry = filename_create(newdfd, to, \u0026path, lookup_flags);\nfs/namei.c-5898-\tif (IS_ERR(dentry))\n--\nfs/namei.c=6033=int filename_linkat(int olddfd, struct filename *old,\n--\nfs/namei.c-6060-\nfs/namei.c:6061:\tnew_dentry = filename_create(newdfd, new, \u0026new_path,\nfs/namei.c-6062-\t\t\t\t\t(how \u0026 LOOKUP_REVAL));\n"
}

13/2 2026/08/23 16:14 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 18832
							output: 32
							thoughts: 81
						

Thoughts:
**Analyzing Function Visibility in the File System**

Okay, so I'm looking at these file system functions and taking stock of their declaration. It's important for understanding how they're exposed and how they interact. Let's see... I've noted that `filename_create` is declared as `static`. Right, meaning it's only visible within its compilation unit. Fine, internal helper or something, I'll keep that in mind.

Then I see `vfs_mkdir`, `vfs_create`, and `do_filp_open`, all non-static. Those functions are exposed and intended to be called from other parts of the kernel, probably even from userspace indirectly. Seems right.

Now, let's nail down this `do_sys_openat2` function. I think I've got it marked down as non-static as well. I'm going to double-check that, though, since its visibility is crucial to determining how applications interact with the system call. Need to be certain on that one.



14/2 2026/08/23 16:14 tool grepper
0m Args:
{
  "Expression": "do_sys_openat2",
  "PathPrefix": "fs/open.c"
}

Results:
{
  "Output": "fs/open.c=1416=EXPORT_SYMBOL(file_open_root);\nfs/open.c-1417-\nfs/open.c:1418:static int do_sys_openat2(int dfd, const char __user *filename,\nfs/open.c-1419-\t\t\t  struct open_how *how)\n--\nfs/open.c=1430=int do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)\n--\nfs/open.c-1432-\tstruct open_how how = build_open_how(flags, mode);\nfs/open.c:1433:\treturn do_sys_openat2(dfd, filename, \u0026how);\nfs/open.c-1434-}\n--\nfs/open.c=1452=SYSCALL_DEFINE4(openat2, int, dfd, const char __user *, filename,\n--\nfs/open.c-1475-\nfs/open.c:1476:\treturn do_sys_openat2(dfd, filename, \u0026tmp);\nfs/open.c-1477-}\n"
}

15/2 2026/08/23 16:14 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 19474
							output: 30
							thoughts: 111
						

Thoughts:
**Analyzing Kernel Function Visibility**

Okay, so I'm taking a look at these kernel functions and their scope. I see that `do_sys_openat2` is declared as static, which means its visibility is restricted to the file it's defined in. That's a common pattern, keeping internal helpers hidden. In contrast, `do_sys_open`, `vfs_mkdir`, `vfs_create`, `vfs_lookup_open`, `dentry_create`, `filename_unlinkat`, and `filename_renameat2` are all non-static. This indicates they are exposed for use by other parts of the kernel. Their broader scope suggests they're likely part of the VFS API or at least core filesystem functionality that's meant to be called from elsewhere. Now, I'm particularly interested in `dentry_create`. Let's dig deeper into that one and see how it fits into the overall picture.



16/2 2026/08/23 16:15 tool grepper
0m Args:
{
  "Expression": "dentry_create",
  "PathPrefix": "fs/namei.c"
}

Results:
{
  "Output": "fs/namei.c=5214=EXPORT_SYMBOL(start_creating_user_path);\n--\nfs/namei.c-5216-/**\nfs/namei.c:5217: * dentry_create - Create and open a file\nfs/namei.c-5218- * @path: path to create\n--\nfs/namei.c-5232- */\nfs/namei.c:5233:struct file *dentry_create(struct path *path, int flags, umode_t mode,\nfs/namei.c-5234-\t\t\t   const struct cred *cred)\n--\nfs/namei.c-5288-}\nfs/namei.c:5289:EXPORT_SYMBOL(dentry_create);\nfs/namei.c-5290-\n"
}

17/2 2026/08/23 16:15 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 20035
							output: 32
							thoughts: 22
						

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

**Diving into the Filesystem Landscape**

Okay, so `dentry_create` is exported, meaning it's a fundamental building block, accessible from outside its immediate module. That piques my interest. I need to understand its role better. My immediate next step is to trace where it's used. Based on that I immediately want to see the relationship between that function and others. A good place to look for that connection is `vfs_lookup_open`. That function deals with, well, looking up and opening files, so it's a likely candidate to interact with dentries. Let's see how `dentry_create` is actually utilized within that context. I expect a good view of how this all actually works together. I'm going to look at how that function may utilize the dentry cache, and how that cache is created.



18/2 2026/08/23 16:15 tool grepper
0m Args:
{
  "Expression": "vfs_lookup_open",
  "PathPrefix": "fs/namei.c"
}

Results:
{
  "Output": "fs/namei.c=4497=static struct dentry *lookup_open(struct nameidata *nd, struct file *file,\n--\nfs/namei.c-4675-/**\nfs/namei.c:4676: * vfs_lookup_open - open and possibly create a regular file\nfs/namei.c-4677- * @parent: directory to contain file\n--\nfs/namei.c-4697- */\nfs/namei.c:4698:struct file *vfs_lookup_open(struct path *parent, struct qstr *last,\nfs/namei.c-4699-\t\t\t     int open_flag, umode_t mode)\n--\nfs/namei.c-4768-}\nfs/namei.c:4769:EXPORT_SYMBOL_FOR_MODULES(vfs_lookup_open, \"nfsd\");\nfs/namei.c-4770-\n"
}

19/2 2026/08/23 16:15 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 20534
							output: 30
							thoughts: 22
						

Thoughts:
Here's a summary of my thought process:

**Diving into the Virtual Filesystem**

Okay, so `vfs_lookup_open` is definitely something I need to keep in mind. It's exported, so it's a key function in the VFS. Now, I'm thinking about related functions and how things interact, and my mind naturally jumps to `vfs_mkdir`. Since I'm working with the VFS and its various operations, `mkdir` is a fundamental one. Let's see how `vfs_mkdir` interacts with `vfs_lookup_open` and how the directory creation process is implemented. I need to understand the connection between looking up a file/directory (open) and creating a directory (mkdir), and how they are integrated.



20/2 2026/08/23 16:15 tool grepper
0m Args:
{
  "Expression": "vfs_mkdir",
  "PathPrefix": "fs/namei.c"
}

Results:
{
  "Output": "fs/namei.c=4479=static inline\nfs/namei.c:4480:struct dentry *vfs_mkdir_no_perm(struct mnt_idmap *, struct inode *,\nfs/namei.c-4481-\t\t\t\t struct dentry *, umode_t,\n--\nfs/namei.c=4497=static struct dentry *lookup_open(struct nameidata *nd, struct file *file,\n--\nfs/namei.c-4636-\tif (create_dir) {\nfs/namei.c:4637:\t\tstruct dentry *res = vfs_mkdir_no_perm(idmap, dir_inode, dentry,\nfs/namei.c-4638-\t\t\t\t\t\t       mode, \u0026delegated_inode);\n--\nfs/namei.c=5186=EXPORT_SYMBOL(start_creating_path);\n--\nfs/namei.c-5195- * if start_creating_path() returned a non-error.\nfs/namei.c:5196: * If vfs_mkdir() was called and it returned an error, that error *should*\nfs/namei.c-5197- * be passed to end_creating_path() together with the path.\n--\nfs/namei.c=5429=static inline\nfs/namei.c:5430:struct dentry *vfs_mkdir_no_perm(struct mnt_idmap *idmap, struct inode *dir,\nfs/namei.c-5431-\t\t\t\t struct dentry *dentry, umode_t mode,\n--\nfs/namei.c-5456-/**\nfs/namei.c:5457: * vfs_mkdir - create directory returning correct dentry if possible\nfs/namei.c-5458- * @idmap:\t\tidmap of the mount the inode was found from\n--\nfs/namei.c-5477- */\nfs/namei.c:5478:struct dentry *vfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,\nfs/namei.c-5479-\t\t\t struct dentry *dentry, umode_t mode,\n--\nfs/namei.c-5497-\nfs/namei.c:5498:\tde = vfs_mkdir_no_perm(idmap, dir, dentry, mode, delegated_inode);\nfs/namei.c-5499-\tif (IS_ERR(de)) {\n--\nfs/namei.c-5508-}\nfs/namei.c:5509:EXPORT_SYMBOL(vfs_mkdir);\nfs/namei.c-5510-\nfs/namei.c=5511=int filename_mkdirat(int dfd, struct filename *name, umode_t mode)\n--\nfs/namei.c-5526-\tif (!error) {\nfs/namei.c:5527:\t\tdentry = vfs_mkdir(mnt_idmap(path.mnt), path.dentry-\u003ed_inode,\nfs/namei.c-5528-\t\t\t\t   dentry, mode, \u0026delegated_inode);\n"
}

21/2 2026/08/23 16:15 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 21559
							output: 34
							thoughts: 503
						

Thoughts:
**Targeting `O_IS_MKDIR` Implementations**

Okay, so I'm looking at how `O_IS_MKDIR` flags are handled within the VFS. `vfs_mkdir` is exported, so that's a good starting point for analysis, and I also see `vfs_create` as a useful focus. I also know these symbols are important: `vfs_lookup_open`, `dentry_create`, `do_sys_open`, `filename_mkdirat`, `filename_unlinkat`, `filename_renameat2`, `v9fs_vfs_atomic_open`, `v9fs_vfs_atomic_open_dotl`, `ceph_atomic_open`, `gfs2_atomic_open`, `nfs_atomic_open`, `nfs_atomic_open_v23`, `cifs_atomic_open`, `vboxsf_dir_atomic_open`.

Wait, hold on. `do_sys_open` is a *very* hot path. Fuzzing that heavily might completely throw off any distribution metrics I'm trying to gather. The prompt is specifically telling me to avoid generic hot-path functions to avoid skewed test distributions, so I need to be more selective. `vfs_mkdir` and `vfs_create` are also somewhat hot, but they are more specific than `do_sys_open`. `vfs_lookup_open` is specific to nfsd, and `dentry_create` is specific to certain code paths. The filesystem-specific `atomic_open` functions are ideal because they are modified to handle `O_IS_MKDIR(flags)`.

So, let's focus on those filesystem-specific `atomic_open` functions:
*   `v9fs_vfs_atomic_open`
*   `v9fs_vfs_atomic_open_dotl`
*   `ceph_atomic_open`
*   `gfs2_atomic_open`
*   `nfs_atomic_open`
*   `nfs_atomic_open_v23`
*   `cifs_atomic_open`
*   `vboxsf_dir_atomic_open`

Now, before I commit to these, I need to check for any configuration options that might be required. The core part of the feature is VFS, so no special config is needed there. But for the filesystem implementations, I see: `NET_9P`, `CEPH_FS`, `FUSE_FS`, `GFS2_FS`, `NFS_FS`, `CIFS`, and `VBOXSF_FS`. I'll need to verify those config options are enabled for each of these filesystems to ensure they are available for testing.



22/2 2026/08/23 16:15 tool grepper
0m Args:
{
  "Expression": "config VBOXSF_FS",
  "PathPrefix": "fs/vboxsf/Kconfig"
}

Results:
{
  "Output": "fs/vboxsf/Kconfig:1:config VBOXSF_FS\nfs/vboxsf/Kconfig-2-\ttristate \"VirtualBox guest shared folder (vboxsf) support\"\n"
}

23/2 2026/08/23 16:15 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 22758
							output: 33
							thoughts: 9
						

Thoughts:
Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:

**My Immediate Assessment**

Ah, right. The prompt is incredibly succinct. When I see just "`VBOXSF_FS`," my mind immediately jumps to the implication. Given the context (or lack thereof, which is also telling!), it seems someone is likely referring to the VirtualBox Shared Folders Filesystem. There's no room for ambiguity here. The brevity itself is the key - it's a confirmation, an agreement, or perhaps a declaration. It’s the filesystem used to share folders between a VirtualBox host and guest operating systems. It's concise because there's little else to say. It's a standard piece of the puzzle, and the nod is enough.



24/2 2026/08/23 16:15 tool grepper
0m Args:
{
  "Expression": "config 9P_FS",
  "PathPrefix": "fs/9p/Kconfig"
}

Results:
{
  "Output": "fs/9p/Kconfig-1-# SPDX-License-Identifier: GPL-2.0-only\nfs/9p/Kconfig:2:config 9P_FS\nfs/9p/Kconfig-3-\ttristate \"Plan 9 Resource Sharing Support (9P2000)\"\n--\nfs/9p/Kconfig=14=if 9P_FS\nfs/9p/Kconfig:15:config 9P_FSCACHE\nfs/9p/Kconfig-16-\tbool \"Enable 9P client caching support\"\n--\nfs/9p/Kconfig-22-\nfs/9p/Kconfig:23:config 9P_FS_POSIX_ACL\nfs/9p/Kconfig-24-\tbool \"9P POSIX Access Control Lists\"\n--\nfs/9p/Kconfig=32=endif\n--\nfs/9p/Kconfig-34-\nfs/9p/Kconfig:35:config 9P_FS_SECURITY\nfs/9p/Kconfig-36-\tbool \"9P Security Labels\"\n"
}

25/2 2026/08/23 16:15 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 23243
							output: 31
							thoughts: 10
						

Thoughts:
Okay, here's my summary, taking on the persona you requested:

**Navigating Configuration Landscapes**

Alright, so the key here is that `9P_FS` is the config we're dealing with. Knowing that is the critical starting point. Everything flows from there, and understanding that configuration is the foundational element that will influence the next steps. I need to keep this specific config in mind as I proceed with any subsequent analysis or action.



26/2 2026/08/23 16:15 tool grepper
0m Args:
{
  "Expression": "config CEPH_FS",
  "PathPrefix": "fs/ceph/Kconfig"
}

Results:
{
  "Output": "fs/ceph/Kconfig-1-# SPDX-License-Identifier: GPL-2.0-only\nfs/ceph/Kconfig:2:config CEPH_FS\nfs/ceph/Kconfig-3-\ttristate \"Ceph distributed file system\"\n--\nfs/ceph/Kconfig=19=if CEPH_FS\nfs/ceph/Kconfig:20:config CEPH_FSCACHE\nfs/ceph/Kconfig-21-\tbool \"Enable Ceph client caching support\"\n--\nfs/ceph/Kconfig=27=endif\nfs/ceph/Kconfig-28-\nfs/ceph/Kconfig:29:config CEPH_FS_POSIX_ACL\nfs/ceph/Kconfig-30-\tbool \"Ceph POSIX Access Control Lists\"\n--\nfs/ceph/Kconfig-38-\nfs/ceph/Kconfig:39:config CEPH_FS_SECURITY_LABEL\nfs/ceph/Kconfig-40-\tbool \"CephFS Security Labels\"\n"
}

27/2 2026/08/23 16:15 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 23625
							output: 31
							thoughts: 10
						

Thoughts:
Okay, here's my interpretation of that thought as a first-person expert summary:

**Thinking About CephFS Configuration**

Right, so we're talking about CephFS, and the specific configuration item that's coming to mind is the `CEPH_FS` setting. That's the key. I immediately know we're not just discussing Ceph generally, but drilling down to its filesystem implementation. This means we're likely in the weeds of mounting, tuning, or troubleshooting a CephFS instance. We're either examining how it's defined, how it's behaving, or how it is implemented. I probably need some context around that setting to understand the issue, as well as the surrounding configurations.



28/2 2026/08/23 16:15 tool grepper
0m Args:
{
  "Expression": "config FUSE_FS",
  "PathPrefix": "fs/fuse/Kconfig"
}

Results:
{
  "Output": "fs/fuse/Kconfig-1-# SPDX-License-Identifier: GPL-2.0-only\nfs/fuse/Kconfig:2:config FUSE_FS\nfs/fuse/Kconfig-3-\ttristate \"FUSE (Filesystem in Userspace) support\"\n"
}

29/2 2026/08/23 16:15 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 23896
							output: 34
							thoughts: 10
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience, presented in the first person:

**Configuring the Filesystem Interface**

Right, `FUSE_FS`. That's the key. That's the configuration point for the File System in User Space, or FUSE filesystem implementation, I'm working with. It immediately focuses me on how the whole thing is set up, what parameters are available, and where the core logic resides. Am I dealing with a simple passthrough filesystem, something with caching, or a more complex distributed design? What are the implications of the user-space/kernel-space boundary with this setting? Understanding this config, the `FUSE_FS` setting, is the starting point for really digging into the details, and the performance characteristics, of this thing. It's the lynchpin, the place where all the pieces come together to define the behavior. It's where I need to start.



30/2 2026/08/23 16:15 tool grepper
0m Args:
{
  "Expression": "config GFS2_FS",
  "PathPrefix": "fs/gfs2/Kconfig"
}

Results:
{
  "Output": "fs/gfs2/Kconfig-1-# SPDX-License-Identifier: GPL-2.0-only\nfs/gfs2/Kconfig:2:config GFS2_FS\nfs/gfs2/Kconfig-3-\ttristate \"GFS2 file system support\"\n--\nfs/gfs2/Kconfig-26-\nfs/gfs2/Kconfig:27:config GFS2_FS_LOCKING_DLM\nfs/gfs2/Kconfig-28-\tbool \"GFS2 DLM locking\"\n"
}

31/2 2026/08/23 16:15 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 24281
							output: 31
							thoughts: 10
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience, presented in the first person:

**GFS2 Configuration Considerations**

Alright, so the core of this seems to be the configuration for GFS2. Thinking about this, `GFS2_FS` is the key. That implies we're going to dive into the specific filesystem parameters, presumably through a configuration file, command-line flags, or possibly even a dedicated management tool. My mind immediately starts running through the familiar aspects: block size, journal options, quota settings (if applicable), mount options, and any specific GFS2 tunable parameters that might be relevant for performance optimization or specific use cases. I'm wondering if this is a new deployment, an existing system being tuned, or something else entirely. Context is always key. Let's see what further detail we are working with.



32/2 2026/08/23 16:15 tool grepper
0m Args:
{
  "Expression": "config NFS_FS",
  "PathPrefix": "fs/nfs/Kconfig"
}

Results:
{
  "Output": "fs/nfs/Kconfig-1-# SPDX-License-Identifier: GPL-2.0-only\nfs/nfs/Kconfig:2:config NFS_FS\nfs/nfs/Kconfig-3-\ttristate \"NFS client support\"\n--\nfs/nfs/Kconfig=162=config ROOT_NFS\n--\nfs/nfs/Kconfig-172-\nfs/nfs/Kconfig:173:config NFS_FSCACHE\nfs/nfs/Kconfig-174-\tbool \"Provide NFS client caching support\"\n"
}

33/2 2026/08/23 16:15 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 24649
							output: 32
							thoughts: 10
						

Thoughts:
Okay, here's my summary, presented as a first-person account, assuming I'm an expert in the field:

**Reflecting on the Configuration Landscape**

Right, so I'm looking at this... "NFS_FS". It's a configuration item, a key one, no doubt. The specific naming convention immediately tells me it’s related to Network File System, the underlying file system configuration. I need to keep this in mind as I process the other components. It signals a foundational aspect of storage and access. Okay, I'm noting this particular configuration option; I will need to delve deeper into its settings and interactions. My expertise tells me, it might be about mounting points, export configurations, version negotiation, security options, the works. This single piece of information, "NFS_FS," will need to be cross-referenced with all the rest of the relevant configuration.



34/2 2026/08/23 16:15 tool grepper
0m Args:
{
  "Expression": "config CIFS",
  "PathPrefix": "fs/smb/client/Kconfig"
}

Results:
{
  "Output": "fs/smb/client/Kconfig-1-# SPDX-License-Identifier: GPL-2.0-only\nfs/smb/client/Kconfig:2:config CIFS\nfs/smb/client/Kconfig-3-\ttristate \"SMB3 and CIFS support (advanced network filesystem)\"\n--\nfs/smb/client/Kconfig-53-\nfs/smb/client/Kconfig:54:config CIFS_STATS2\nfs/smb/client/Kconfig-55-\tbool \"Extended statistics\"\n--\nfs/smb/client/Kconfig-67-\nfs/smb/client/Kconfig:68:config CIFS_ALLOW_INSECURE_LEGACY\nfs/smb/client/Kconfig-69-\tbool \"Support legacy servers which use less secure dialects\"\n--\nfs/smb/client/Kconfig-82-\nfs/smb/client/Kconfig:83:config CIFS_UPCALL\nfs/smb/client/Kconfig-84-\tbool \"Kerberos/SPNEGO advanced session setup\"\n--\nfs/smb/client/Kconfig-91-\nfs/smb/client/Kconfig:92:config CIFS_XATTR\nfs/smb/client/Kconfig-93-\tbool \"CIFS extended attributes\"\n--\nfs/smb/client/Kconfig-106-\nfs/smb/client/Kconfig:107:config CIFS_POSIX\nfs/smb/client/Kconfig-108-\tbool \"CIFS POSIX Extensions\"\n--\nfs/smb/client/Kconfig-118-\nfs/smb/client/Kconfig:119:config CIFS_DEBUG\nfs/smb/client/Kconfig-120-\tbool \"Enable CIFS debugging routines\"\n--\nfs/smb/client/Kconfig-127-\nfs/smb/client/Kconfig:128:config CIFS_DEBUG2\nfs/smb/client/Kconfig-129-\tbool \"Enable additional CIFS debugging routines\"\n--\nfs/smb/client/Kconfig-138-\nfs/smb/client/Kconfig:139:config CIFS_DEBUG_DUMP_KEYS\nfs/smb/client/Kconfig-140-\tbool \"Dump encryption keys for offline decryption (Unsafe)\"\n--\nfs/smb/client/Kconfig-148-\nfs/smb/client/Kconfig:149:config CIFS_DFS_UPCALL\nfs/smb/client/Kconfig-150-\tbool \"DFS feature support\"\n--\nfs/smb/client/Kconfig-161-\nfs/smb/client/Kconfig:162:config CIFS_SWN_UPCALL\nfs/smb/client/Kconfig-163-\tbool \"SWN feature support\"\n--\nfs/smb/client/Kconfig-172-\nfs/smb/client/Kconfig:173:config CIFS_NFSD_EXPORT\nfs/smb/client/Kconfig-174-\tbool \"Allow nfsd to export CIFS file system\"\n--\nfs/smb/client/Kconfig=179=if CIFS\nfs/smb/client/Kconfig-180-\nfs/smb/client/Kconfig:181:config CIFS_SMB_DIRECT\nfs/smb/client/Kconfig-182-\tbool \"SMB Direct support\"\n--\nfs/smb/client/Kconfig-190-\nfs/smb/client/Kconfig:191:config CIFS_FSCACHE\nfs/smb/client/Kconfig-192-\tbool \"Provide CIFS client caching support\"\n--\nfs/smb/client/Kconfig-198-\nfs/smb/client/Kconfig:199:config CIFS_ROOT\nfs/smb/client/Kconfig-200-\tbool \"SMB root file system (Experimental)\"\n--\nfs/smb/client/Kconfig-206-\nfs/smb/client/Kconfig:207:config CIFS_COMPRESSION\nfs/smb/client/Kconfig-208-\tbool \"SMB message compression (Experimental)\"\n"
}

35/2 2026/08/23 16:15 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 25923
							output: 35
							thoughts: 696
						

Thoughts:
**Analyzing the Atomic Directory Creation Patch**

Okay, so I'm looking at this patch and my initial thought is that it's introducing a new atomic directory creation feature via `openat()`, using `O_CREAT | O_DIRECTORY`. This is a pretty interesting addition, as atomically creating directories has often been a bit of a tricky area.

The configuration options associated with this are `9P_FS`, `CEPH_FS`, `FUSE_FS`, `GFS2_FS`, `NFS_FS`, `CIFS`, and `VBOXSF_FS` – those are the filesystems that are being updated to support this new atomic open operation. I'm noting that because it gives me a clear idea of which subsystems are affected.

It looks like the key change is in how the VFS interacts with `O_CREAT` and `O_IS_MKDIR(flags)`. Specifically, if `O_IS_MKDIR(flags)` is true (meaning we're trying to create a directory), then `O_CREAT` is cleared. The rationale is that `atomic_open` implementations often don't support directory creation directly. This means they can fall back to the standard lookup process, and `mkdir` will be called instead of `create`. This makes a lot of sense, keeping the behavior in line with what a developer would expect.

Now, the core of this change seems to be in `fs/namei.c` and `fs/open.c`. Those are the files I'll want to focus on when I'm assessing the impact of this. The key functions to look at are the core VFS functions: `vfs_mkdir`, `vfs_create`, `vfs_lookup_open`, `dentry_create`, `filename_mkdirat`, `filename_unlinkat`, and `filename_renameat2`. I'll need to pay close attention to how these functions handle the new flag combination.

I've confirmed that `filename_mkdirat`, `filename_unlinkat`, and `filename_renameat2` are *not* static, so they're accessible outside their respective files. Good. I also checked that `O_MKDIR_MASK` isn't used anywhere else, which further isolates the scope of the change.

From a testing perspective, the implications are pretty clear: this is a functional change to the core kernel logic, and it needs heavy fuzzing. I'll need to focus on those core VFS functions, along with their interaction with the updated filesystems' `atomic_open` implementations, such as `v9fs_vfs_atomic_open`, `ceph_atomic_open`, etc. I'm going to add these to my list of symbols to investigate more thoroughly.

So, in summary, this patch is introducing a new, albeit subtle, atomic directory creation feature using an existing flag. The VFS and a series of filesystem drivers will need to be looked at closely to analyze behavior and test thoroughly.



36/2 2026/08/23 16:16 tool grepper
0m Args:
{
  "Expression": "v9fs_vfs_atomic_open",
  "PathPrefix": "fs/9p/"
}

Results:
{
  "Output": "fs/9p/vfs_inode.c=579=v9fs_create(struct v9fs_session_info *v9ses, struct inode *dir,\n--\nfs/9p/vfs_inode.c-648- *\nfs/9p/vfs_inode.c:649: * open(.., O_CREAT) is handled in v9fs_vfs_atomic_open().  This is only called\nfs/9p/vfs_inode.c-650- * for mknod(2).\n--\nfs/9p/vfs_inode.c=767=static int\nfs/9p/vfs_inode.c:768:v9fs_vfs_atomic_open(struct inode *dir, struct dentry *dentry,\nfs/9p/vfs_inode.c-769-\t\t     struct file *file, unsigned int flags, umode_t mode)\n--\nfs/9p/vfs_inode.c=1368=static const struct inode_operations v9fs_dir_inode_operations_dotu = {\n--\nfs/9p/vfs_inode.c-1370-\t.lookup = v9fs_vfs_lookup,\nfs/9p/vfs_inode.c:1371:\t.atomic_open = v9fs_vfs_atomic_open,\nfs/9p/vfs_inode.c-1372-\t.symlink = v9fs_vfs_symlink,\n--\nfs/9p/vfs_inode.c=1383=static const struct inode_operations v9fs_dir_inode_operations = {\n--\nfs/9p/vfs_inode.c-1385-\t.lookup = v9fs_vfs_lookup,\nfs/9p/vfs_inode.c:1386:\t.atomic_open = v9fs_vfs_atomic_open,\nfs/9p/vfs_inode.c-1387-\t.unlink = v9fs_vfs_unlink,\n--\nfs/9p/vfs_inode_dotl.c=225=static int\nfs/9p/vfs_inode_dotl.c:226:v9fs_vfs_atomic_open_dotl(struct inode *dir, struct dentry *dentry,\nfs/9p/vfs_inode_dotl.c-227-\t\t\t  struct file *file, unsigned int flags, umode_t omode)\n--\nfs/9p/vfs_inode_dotl.c=964=const struct inode_operations v9fs_dir_inode_operations_dotl = {\nfs/9p/vfs_inode_dotl.c-965-\t.create = v9fs_vfs_create_dotl,\nfs/9p/vfs_inode_dotl.c:966:\t.atomic_open = v9fs_vfs_atomic_open_dotl,\nfs/9p/vfs_inode_dotl.c-967-\t.lookup = v9fs_vfs_lookup,\n"
}

37/2 2026/08/23 16:16 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 28064
							output: 324
							thoughts: 298
						

Thoughts:
**My Assessment of the New VFS Feature**

Okay, so I've been looking over this new patch. These atomic open functions, they're static but linked to `inode_operations->atomic_open`. That means they're not inlined, which is good – they have their own distinct symbol addresses. Perfect. I'll definitely add these to the FocusSymbols list; `ceph_atomic_open`, `gfs2_atomic_open`, and the others are crucial for fuzzing this.

Let me just quickly review those FocusSymbols to make sure I'm not missing anything. I've got `vfs_mkdir`, `vfs_create`, `vfs_lookup_open`, `dentry_create`, `filename_mkdirat`, `filename_unlinkat`, and `filename_renameat2`. Then there are the specific filesystem functions: `v9fs_vfs_atomic_open`, `v9fs_vfs_atomic_open_dotl`, `ceph_atomic_open`, `fuse_atomic_open`, `gfs2_atomic_open`, `nfs_atomic_open`, `nfs_atomic_open_v23`, `cifs_atomic_open`, and `vboxsf_dir_atomic_open`. Yep, that looks comprehensive.

Now, just checking the configs... `9P_FS`, `CEPH_FS`, `FUSE_FS`, `GFS2_FS`, `NFS_FS`, `CIFS`, and `VBOXSF_FS`. All the relevant filesystems are covered.

Yeah, I think this is solid. The patch introduces a new feature that modifies the core VFS logic *and* several filesystems. Considering the scope of the change, this is absolutely prime fuzzing material. It's a high-impact area with a lot of potential for interesting vulnerabilities. This is definitely something we need to prioritize.



38/2 2026/08/23 16:16 tool set-results
0m Args:
{
  "EnableConfigs": [
    "9P_FS",
    "CEPH_FS",
    "FUSE_FS",
    "GFS2_FS",
    "NFS_FS",
    "CIFS",
    "VBOXSF_FS"
  ],
  "FocusSymbols": [
    "vfs_mkdir",
    "vfs_create",
    "vfs_lookup_open",
    "dentry_create",
    "filename_mkdirat",
    "filename_unlinkat",
    "filename_renameat2",
    "v9fs_vfs_atomic_open",
    "v9fs_vfs_atomic_open_dotl",
    "ceph_atomic_open",
    "fuse_atomic_open",
    "gfs2_atomic_open",
    "nfs_atomic_open",
    "nfs_atomic_open_v23",
    "cifs_atomic_open",
    "vboxsf_dir_atomic_open"
  ],
  "Reasoning": "The patch introduces a new feature `O_CREAT | O_DIRECTORY` for `openat()` to atomically create a directory and return a file descriptor. It modifies core VFS path resolution and creation logic in `fs/namei.c` and `fs/open.c`, and updates several filesystems' `atomic_open` implementations to handle the new flag combination. This is a significant functional change to reachable core kernel logic that should be heavily fuzzed.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "9P_FS",
    "CEPH_FS",
    "FUSE_FS",
    "GFS2_FS",
    "NFS_FS",
    "CIFS",
    "VBOXSF_FS"
  ],
  "FocusSymbols": [
    "vfs_mkdir",
    "vfs_create",
    "vfs_lookup_open",
    "dentry_create",
    "filename_mkdirat",
    "filename_unlinkat",
    "filename_renameat2",
    "v9fs_vfs_atomic_open",
    "v9fs_vfs_atomic_open_dotl",
    "ceph_atomic_open",
    "fuse_atomic_open",
    "gfs2_atomic_open",
    "nfs_atomic_open",
    "nfs_atomic_open_v23",
    "cifs_atomic_open",
    "vboxsf_dir_atomic_open"
  ],
  "Reasoning": "The patch introduces a new feature `O_CREAT | O_DIRECTORY` for `openat()` to atomically create a directory and return a file descriptor. It modifies core VFS path resolution and creation logic in `fs/namei.c` and `fs/open.c`, and updates several filesystems' `atomic_open` implementations to handle the new flag combination. This is a significant functional change to reachable core kernel logic that should be heavily fuzzed.",
  "WorthFuzzing": true
}

39/1 2026/08/23 16:16 action If
0m Args:
{
  "WorthFuzzing": true
}

Results:
null

40/2 2026/08/23 16:16 agent kmsan-evaluator
0m Model:
best-expensive

Args:
null

Results:
{
  "KMSANReasoning": "The patch series modifies the VFS and various filesystems to support creating directories via `openat()` with the `O_CREAT | O_DIRECTORY` flags. The changes primarily involve adding the `O_IS_MKDIR` macro, modifying `build_open_flags()` to handle the new flag combination, and updating `lookup_open()` and `vfs_mkdir()` paths in `fs/namei.c` to route directory creation requests to `vfs_mkdir_no_perm()` instead of regular file creation functions.\n\nThese changes are purely logical, altering control flow and flag handling within the VFS layer. The patch does not introduce new data structures sent to user space, nor does it allocate complex data structures with uninitialized fields. All variables used (such as `delegated_inode`) are properly zero-initialized. Any potential bugs introduced by these changes would likely be logic errors, NULL pointer dereferences, or out-of-bounds accesses, which are effectively caught by KASAN and standard bug detectors. There is no risk of uninitialized memory usage or info-leaks that would necessitate a dedicated KMSAN fuzzing session.",
  "NeedsKMSAN": false
}

Instruction:
You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.

CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
  a comprehensive suite of debugging tools and sanitizers, including KASAN
  (out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
  (locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
  or page allocations) and kernel-to-user memory info-leaks.

Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.

Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.

Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
   attributes, ioctl output arguments, socket options, or BPF buffers) where fields
   or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
   or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
   could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
   uninitialized bytes of existing buffers.

Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
  dereferences, locking deadlocks, or use-after-free bugs (these are already caught
  by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
  or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.

Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit d6974f2dae03a314eedcd225149e126b66094b4d
Author: syz-cluster <triage@syzkaller.com>
Date:   Sun Aug 23 16:14:08 2026 +0000

    syz-cluster: applied patch under review

diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c
index 3829554ca3692..b1e0823c87b57 100644
--- a/fs/9p/vfs_inode.c
+++ b/fs/9p/vfs_inode.c
@@ -776,6 +776,9 @@ v9fs_vfs_atomic_open(struct inode *dir, struct dentry *dentry,
 	struct inode *inode;
 	int p9_omode;
 
+	if (O_IS_MKDIR(flags))
+		flags &= ~O_CREAT;
+
 	if (d_in_lookup(dentry)) {
 		struct dentry *res = v9fs_vfs_lookup(dir, dentry, 0);
 		if (res || d_really_is_positive(dentry))
diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c
index 116b29e95f21e..64e0aba08da16 100644
--- a/fs/9p/vfs_inode_dotl.c
+++ b/fs/9p/vfs_inode_dotl.c
@@ -238,6 +238,9 @@ v9fs_vfs_atomic_open_dotl(struct inode *dir, struct dentry *dentry,
 	struct v9fs_session_info *v9ses;
 	struct posix_acl *pacl = NULL, *dacl = NULL;
 
+	if (O_IS_MKDIR(flags))
+		flags &= ~O_CREAT;
+
 	if (d_in_lookup(dentry)) {
 		struct dentry *res = v9fs_vfs_lookup(dir, dentry, 0);
 		if (res || d_really_is_positive(dentry))
diff --git a/fs/ceph/file.c b/fs/ceph/file.c
index a4a2a4b6a027d..62163b7eca5f9 100644
--- a/fs/ceph/file.c
+++ b/fs/ceph/file.c
@@ -812,6 +812,9 @@ int ceph_atomic_open(struct inode *dir, struct dentry *dentry,
 	      dir, ceph_vinop(dir), dentry, dentry,
 	      d_unhashed(dentry) ? "unhashed" : "hashed", flags, mode);
 
+	if (O_IS_MKDIR(flags))
+		flags &= ~O_CREAT;
+
 	if (dentry->d_name.len > NAME_MAX)
 		return -ENAMETOOLONG;
 
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index d4e0029810c0f..55d5844e26559 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -935,6 +935,9 @@ static int fuse_atomic_open(struct inode *dir, struct dentry *entry,
 	struct mnt_idmap *idmap = file_mnt_idmap(file);
 	struct fuse_conn *fc = get_fuse_conn(dir);
 
+	if (O_IS_MKDIR(flags))
+		flags &= ~O_CREAT;
+
 	if (fuse_is_bad(dir))
 		return -EIO;
 
diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c
index f361876c55833..3ee1360f1bc27 100644
--- a/fs/gfs2/inode.c
+++ b/fs/gfs2/inode.c
@@ -1386,6 +1386,9 @@ static int gfs2_atomic_open(struct inode *dir, struct dentry *dentry,
 {
 	bool excl = !!(flags & O_EXCL);
 
+	if (O_IS_MKDIR(flags))
+		flags &= ~O_CREAT;
+
 	if (d_in_lookup(dentry)) {
 		struct dentry *d = __gfs2_lookup(dir, dentry, file);
 		if (file->f_mode & FMODE_OPENED) {
diff --git a/fs/namei.c b/fs/namei.c
index 20a6534ea3eff..e1a8f18385a52 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -1382,13 +1382,13 @@ int may_linkat(struct mnt_idmap *idmap, const struct path *link)
 
 /**
  * may_create_in_sticky - Check whether an O_CREAT open in a sticky directory
- *			  should be allowed, or not, on files that already
- *			  exist.
+ *			  should be allowed, or not, on files/directories that
+ *			  already exist.
  * @idmap: idmap of the mount the inode was found from
  * @nd: nameidata pathwalk data
  * @inode: the inode of the file to open
  *
- * Block an O_CREAT open of a FIFO (or a regular file) when:
+ * Block an O_CREAT open of a FIFO (or a regular file/directory) when:
  *   - sysctl_protected_fifos (or sysctl_protected_regular) is enabled
  *   - the file already exists
  *   - we are in a sticky directory
@@ -1416,6 +1416,14 @@ static int may_create_in_sticky(struct mnt_idmap *idmap, struct nameidata *nd,
 	if (likely(!(dir_mode & S_ISVTX)))
 		return 0;
 
+	/*
+	 * There is no separate sysctl for directory creation in sticky
+	 * folders. Therefore, for the S_ISDIR case, disabling
+	 * sysctl_protected_regular is not enough to allow creating a
+	 * directory in a sticky folder, because that may surprise users
+	 * not expecting that O_CREAT|O_DIRECTORY is possible on newer
+	 * kernels.
+	 */
 	if (S_ISREG(inode->i_mode) && !sysctl_protected_regular)
 		return 0;
 
@@ -1447,6 +1455,12 @@ static int may_create_in_sticky(struct mnt_idmap *idmap, struct nameidata *nd,
 					      "sticky_create_regular");
 			return -EACCES;
 		}
+
+		if (S_ISDIR(inode->i_mode)) {
+			audit_log_path_denied(AUDIT_ANOM_CREAT,
+					      "sticky_create_dir");
+			return -EACCES;
+		}
 	}
 
 	return 0;
@@ -2781,9 +2795,16 @@ static const char *path_init(struct nameidata *nd, unsigned flags)
 	return s;
 }
 
+static inline bool trailing_slashes(const struct qstr *last)
+{
+	/* last->len is set by hash_name() to the length of the current
+	 * component ->name, terminating with '/' or a NUL character. */
+	return (bool)last->name[last->len];
+}
+
 static inline const char *lookup_last(struct nameidata *nd)
 {
-	if (nd->last_type == LAST_NORM && nd->last.name[nd->last.len])
+	if (nd->last_type == LAST_NORM && trailing_slashes(&nd->last))
 		nd->flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
 
 	return walk_component(nd, WALK_TRAILING);
@@ -4159,6 +4180,24 @@ static inline umode_t vfs_prepare_mode(struct mnt_idmap *idmap,
 	return mode;
 }
 
+static inline
+int vfs_create_no_perm(struct mnt_idmap *idmap, struct dentry *dentry,
+		       umode_t mode, struct delegated_inode *di)
+{
+	struct inode *dir = d_inode(dentry->d_parent);
+	int error;
+
+	error = try_break_deleg(dir, LEASE_BREAK_DIR_CREATE, di);
+	if (error)
+		return error;
+
+	error = dir->i_op->create(idmap, dir, dentry, mode);
+	if (!error)
+		fsnotify_create(dir, dentry);
+
+	return error;
+}
+
 /**
  * vfs_create - create new file
  * @idmap:	idmap of the mount the inode was found from
@@ -4191,13 +4230,8 @@ int vfs_create(struct mnt_idmap *idmap, struct dentry *dentry, umode_t mode,
 	error = security_inode_create(dir, dentry, mode);
 	if (error)
 		return error;
-	error = try_break_deleg(dir, LEASE_BREAK_DIR_CREATE, di);
-	if (error)
-		return error;
-	error = dir->i_op->create(idmap, dir, dentry, mode);
-	if (!error)
-		fsnotify_create(dir, dentry);
-	return error;
+
+	return vfs_create_no_perm(idmap, dentry, mode, di);
 }
 EXPORT_SYMBOL(vfs_create);
 
@@ -4314,21 +4348,41 @@ static inline int open_to_namei_flags(int flag)
 
 static int may_o_create(struct mnt_idmap *idmap,
 			const struct path *dir, struct dentry *dentry,
-			umode_t mode)
+			int open_flag, umode_t mode)
 {
-	int error = security_path_mknod(dir, dentry, mode, 0);
+	struct inode *dir_inode = dir->dentry->d_inode;
+	bool create_dir = O_IS_MKDIR(open_flag);
+	int error;
+
+	if (create_dir)
+		error = security_path_mkdir(dir, dentry, mode);
+	else
+		error = security_path_mknod(dir, dentry, mode, 0);
 	if (error)
 		return error;
 
 	if (!fsuidgid_has_mapping(dir->dentry->d_sb, idmap))
 		return -EOVERFLOW;
 
-	error = inode_permission(idmap, dir->dentry->d_inode,
-				 MAY_WRITE | MAY_EXEC);
+	error = inode_permission(idmap, dir_inode, MAY_WRITE | MAY_EXEC);
 	if (error)
 		return error;
 
-	return security_inode_create(dir->dentry->d_inode, dentry, mode);
+	if (create_dir)
+		error = security_inode_mkdir(dir_inode, dentry, mode);
+	else
+		error = security_inode_create(dir_inode, dentry, mode);
+
+	return error;
+}
+
+static inline umode_t o_create_mode(struct mnt_idmap *idmap,
+		const struct inode *dir, int open_flag, umode_t mode)
+{
+	if (O_IS_MKDIR(open_flag))
+		return vfs_prepare_mode(idmap, dir, mode, S_IRWXUGO | S_ISVTX, S_IFDIR);
+	else
+		return vfs_prepare_mode(idmap, dir, mode, S_IALLUGO, S_IFREG);
 }
 
 /**
@@ -4364,8 +4418,9 @@ static struct dentry *atomic_open(const struct path *path, struct dentry *dentry
 
 	file->__f_path.dentry = DENTRY_NOT_SET;
 	file->__f_path.mnt = path->mnt;
+
 	error = dir_inode->i_op->atomic_open(dir_inode, dentry, file,
-				       open_to_namei_flags(open_flag), mode);
+					     open_to_namei_flags(open_flag), mode);
 	d_lookup_done(dentry);
 
 	if (!error) {
@@ -4410,10 +4465,21 @@ static struct dentry *atomic_open(const struct path *path, struct dentry *dentry
 		}
 		dput(dentry);
 		dentry = ERR_PTR(error);
+	} else {
+		if (file->f_mode & FMODE_CREATED)
+			fsnotify_create(dir_inode, dentry);
+		if (file->f_mode & FMODE_OPENED)
+			fsnotify_open(file);
 	}
+
+
 	return dentry;
 }
 
+static inline
+struct dentry *vfs_mkdir_no_perm(struct mnt_idmap *, struct inode *,
+				 struct dentry *, umode_t,
+				 struct delegated_inode *);
 /*
  * Look up and maybe create and open the last component.
  *
@@ -4435,6 +4501,7 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
 	struct mnt_idmap *idmap;
 	struct dentry *dir = nd->path.dentry;
 	struct inode *dir_inode = dir->d_inode;
+	bool create_dir = O_IS_MKDIR(op->mode);
 	int open_flag;
 	struct dentry *dentry;
 	int error, create_error;
@@ -4455,7 +4522,7 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
 		 */
 	}
 	if (open_flag & O_CREAT)
-		inode_lock(dir_inode);
+		inode_lock_nested(dir_inode, I_MUTEX_PARENT);
 	else
 		inode_lock_shared(dir_inode);
 
@@ -4507,12 +4574,17 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
 	if (open_flag & O_CREAT) {
 		if (open_flag & O_EXCL)
 			open_flag &= ~O_TRUNC;
-		mode = vfs_prepare_mode(idmap, dir_inode, mode, mode, mode);
+		mode = o_create_mode(idmap, dir_inode, open_flag, mode);
 		if (likely(got_write))
 			create_error = may_o_create(idmap, &nd->path,
-						    dentry, mode);
+						    dentry, open_flag, mode);
 		else
 			create_error = -EROFS;
+		/* Refuse to create a directory through a dangling (trailing)
+		 * symlink. For regular files this has been allowed historically
+		 * on O_CREAT without O_EXCL. */
+		if (unlikely(nd->depth) && create_dir && !create_error)
+			create_error = -EEXIST;
 	}
 	if (create_error)
 		open_flag &= ~O_CREAT;
@@ -4537,6 +4609,7 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
 			dentry = res;
 		}
 	}
+
 	if (dentry->d_inode || !(op->open_flag & O_CREAT)) {
 		/*
 		 * No need to create a file.  If lookup returned a positive
@@ -4554,26 +4627,26 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
 		goto out_dput;
 	}
 
-	error = try_break_deleg(dir_inode, LEASE_BREAK_DIR_CREATE, &delegated_inode);
-	if (error)
-		goto out_dput;
-
-	file->f_mode |= FMODE_CREATED;
-	if (!dir_inode->i_op->create) {
+	if ((create_dir && !dir_inode->i_op->mkdir)
+		|| (!create_dir && !dir_inode->i_op->create)) {
 		error = -EACCES;
 		goto out_dput;
 	}
 
-	error = dir_inode->i_op->create(idmap, dir_inode, dentry, mode);
+	if (create_dir) {
+		struct dentry *res = vfs_mkdir_no_perm(idmap, dir_inode, dentry,
+						       mode, &delegated_inode);
+			error = PTR_ERR_OR_ZERO(res);
+			if (!error)
+				dentry = res;
+	} else {
+		error = vfs_create_no_perm(idmap, dentry, mode, &delegated_inode);
+	}
 	if (error)
 		goto out_dput;
+
+	file->f_mode |= FMODE_CREATED;
 out:
-	if (!IS_ERR(dentry)) {
-		if (file->f_mode & FMODE_CREATED)
-			fsnotify_create(dir_inode, dentry);
-		if (file->f_mode & FMODE_OPENED)
-			fsnotify_open(file);
-	}
 	if ((open_flag & O_CREAT) || create_error)
 		inode_unlock(dir_inode);
 	else
@@ -4695,17 +4768,12 @@ struct file *vfs_lookup_open(struct path *parent, struct qstr *last,
 }
 EXPORT_SYMBOL_FOR_MODULES(vfs_lookup_open, "nfsd");
 
-static inline bool trailing_slashes(struct nameidata *nd)
-{
-	return (bool)nd->last.name[nd->last.len];
-}
-
 static struct dentry *lookup_fast_for_open(struct nameidata *nd, int open_flag)
 {
 	struct dentry *dentry;
 
 	if (open_flag & O_CREAT) {
-		if (trailing_slashes(nd))
+		if (trailing_slashes(&nd->last) && !(open_flag & O_DIRECTORY))
 			return ERR_PTR(-EISDIR);
 
 		/* Don't bother on an O_EXCL create */
@@ -4713,7 +4781,7 @@ static struct dentry *lookup_fast_for_open(struct nameidata *nd, int open_flag)
 			return NULL;
 	}
 
-	if (trailing_slashes(nd))
+	if (trailing_slashes(&nd->last))
 		nd->flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
 
 	dentry = lookup_fast(nd);
@@ -4806,8 +4874,9 @@ static int do_open(struct nameidata *nd,
 	if (open_flag & O_CREAT) {
 		if ((open_flag & O_EXCL) && !(file->f_mode & FMODE_CREATED))
 			return -EEXIST;
-		if (d_is_dir(nd->path.dentry))
+		if (!(open_flag & O_DIRECTORY) && d_is_dir(nd->path.dentry))
 			return -EISDIR;
+
 		error = may_create_in_sticky(idmap, nd,
 					     d_backing_inode(nd->path.dentry));
 		if (unlikely(error))
@@ -5087,7 +5156,7 @@ static struct dentry *filename_create(int dfd, struct filename *name,
 	 * Do the final lookup.  Suppress 'create' if there is a trailing
 	 * '/', and a directory wasn't requested.
 	 */
-	if (last.name[last.len] && !want_dir)
+	if (trailing_slashes(&last) && !want_dir)
 		create_flags &= ~LOOKUP_CREATE;
 	dentry = start_dirop(path->dentry, &last, reval_flag | create_flags);
 	if (IS_ERR(dentry))
@@ -5182,7 +5251,7 @@ struct file *dentry_create(struct path *path, int flags, umode_t mode,
 		path->dentry = dir;
 		mode = vfs_prepare_mode(idmap, dir_inode, mode, S_IALLUGO, S_IFREG);
 
-		create_error = may_o_create(idmap, path, dentry, mode);
+		create_error = may_o_create(idmap, path, dentry, flags, mode);
 		if (create_error)
 			flags &= ~O_CREAT;
 
@@ -5356,6 +5425,34 @@ SYSCALL_DEFINE3(mknod, const char __user *, filename, umode_t, mode, unsigned, d
 	return filename_mknodat(AT_FDCWD, name, mode, dev);
 }
 
+/* Returns the dentry to use (not NULL) or -E on error */
+static inline
+struct dentry *vfs_mkdir_no_perm(struct mnt_idmap *idmap, struct inode *dir,
+				 struct dentry *dentry, umode_t mode,
+				 struct delegated_inode *di)
+{
+	int error;
+	struct dentry *de;
+	unsigned max_links = dir->i_sb->s_max_links;
+
+	if (max_links && dir->i_nlink >= max_links)
+		return ERR_PTR(-EMLINK);
+
+	error = try_break_deleg(dir, LEASE_BREAK_DIR_CREATE, di);
+	if (error)
+		return ERR_PTR(error);
+
+	de = dir->i_op->mkdir(idmap, dir, dentry, mode);
+	if (IS_ERR(de))
+		return de;
+	if (de) {
+		dput(dentry);
+		dentry = de;
+	}
+	fsnotify_mkdir(dir, dentry);
+	return dentry;
+}
+
 /**
  * vfs_mkdir - create directory returning correct dentry if possible
  * @idmap:		idmap of the mount the inode was found from
@@ -5383,7 +5480,6 @@ struct dentry *vfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
 			 struct delegated_inode *delegated_inode)
 {
 	int error;
-	unsigned max_links = dir->i_sb->s_max_links;
 	struct dentry *de;
 
 	error = may_create_dentry(idmap, dir, dentry);
@@ -5399,24 +5495,12 @@ struct dentry *vfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,
 	if (error)
 		goto err;
 
-	error = -EMLINK;
-	if (max_links && dir->i_nlink >= max_links)
-		goto err;
-
-	error = try_break_deleg(dir, LEASE_BREAK_DIR_CREATE, delegated_inode);
-	if (error)
-		goto err;
-
-	de = dir->i_op->mkdir(idmap, dir, dentry, mode);
-	error = PTR_ERR(de);
-	if (IS_ERR(de))
+	de = vfs_mkdir_no_perm(idmap, dir, dentry, mode, delegated_inode);
+	if (IS_ERR(de)) {
+		error = PTR_ERR(de);
 		goto err;
-	if (de) {
-		dput(dentry);
-		dentry = de;
 	}
-	fsnotify_mkdir(dir, dentry);
-	return dentry;
+	return de;
 
 err:
 	end_creating(dentry);
@@ -5703,7 +5787,7 @@ int filename_unlinkat(int dfd, struct filename *name)
 		goto exit_drop_write;
 
 	/* Why not before? Because we want correct error value */
-	if (unlikely(last.name[last.len])) {
+	if (unlikely(trailing_slashes(&last))) {
 		if (d_is_dir(dentry))
 			error = -EISDIR;
 		else
@@ -6305,16 +6389,16 @@ int filename_renameat2(int olddfd, struct filename *from,
 	if (flags & RENAME_EXCHANGE) {
 		if (!d_is_dir(rd.new_dentry)) {
 			error = -ENOTDIR;
-			if (new_last.name[new_last.len])
+			if (trailing_slashes(&new_last))
 				goto exit_unlock;
 		}
 	}
 	/* unless the source is a directory trailing slashes give -ENOTDIR */
 	if (!d_is_dir(rd.old_dentry)) {
 		error = -ENOTDIR;
-		if (old_last.name[old_last.len])
+		if (trailing_slashes(&old_last))
 			goto exit_unlock;
-		if (!(flags & RENAME_EXCHANGE) && new_last.name[new_last.len])
+		if (!(flags & RENAME_EXCHANGE) && trailing_slashes(&new_last))
 			goto exit_unlock;
 	}
 
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index 36f2e85889220..b4db703c9e8c7 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -2121,6 +2121,9 @@ int nfs_atomic_open(struct inode *dir, struct dentry *dentry,
 	dfprintk(VFS, "NFS: atomic_open(%s/%llu), %pd\n",
 			dir->i_sb->s_id, dir->i_ino, dentry);
 
+	if (O_IS_MKDIR(open_flags))
+		open_flags &= ~O_CREAT;
+
 	err = nfs_check_flags(open_flags);
 	if (err)
 		return err;
@@ -2313,6 +2316,9 @@ int nfs_atomic_open_v23(struct inode *dir, struct dentry *dentry,
 	 */
 	int error = 0;
 
+	if (O_IS_MKDIR(open_flags))
+		open_flags &= ~O_CREAT;
+
 	if (dentry->d_name.len > NFS_SERVER(dir)->namelen)
 		return -ENAMETOOLONG;
 
diff --git a/fs/open.c b/fs/open.c
index 6b1c14e684a93..6cb5e2ad781f3 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -1239,29 +1239,30 @@ inline int build_open_flags(const struct open_how *how, struct open_flags *op)
 	if (WILL_CREATE(flags)) {
 		if (how->mode & ~S_IALLUGO)
 			return -EINVAL;
-		op->mode = how->mode | S_IFREG;
+		if (O_IS_MKDIR(flags))
+			op->mode = how->mode | S_IFDIR;
+		else
+			op->mode = how->mode | S_IFREG;
 	} else {
 		if (how->mode != 0)
 			return -EINVAL;
 		op->mode = 0;
 	}
 
-	/*
-	 * Block bugs where O_DIRECTORY | O_CREAT created regular files.
-	 * Note, that blocking O_DIRECTORY | O_CREAT here also protects
-	 * O_TMPFILE below which requires O_DIRECTORY being raised.
-	 */
-	if ((flags & (O_DIRECTORY | O_CREAT)) == (O_DIRECTORY | O_CREAT))
-		return -EINVAL;
-
 	/* Now handle the creative implementation of O_TMPFILE. */
 	if (flags & __O_TMPFILE) {
 		/*
 		 * In order to ensure programs get explicit errors when trying
 		 * to use O_TMPFILE on old kernels we enforce that O_DIRECTORY
-		 * is raised alongside __O_TMPFILE.
+		 * is raised alongside __O_TMPFILE, but without O_CREAT. The
+		 * reason for disallowing O_CREAT|O_TMPFILE is that
+		 * O_DIRECTORY|O_CREAT used to work and created a regular file
+		 * if nothing existed at the open path. Hence, allowing the
+		 * combination would have caused O_CREAT|O_TMPFILE to create a
+		 * regular (non-temporary) file on old kernels, while the caller
+		 * would believe they created an actual O_TMPFILE.
 		 */
-		if (!(flags & O_DIRECTORY))
+		if (!(flags & O_DIRECTORY) || (flags & O_CREAT))
 			return -EINVAL;
 		if (!(acc_mode & MAY_WRITE))
 			return -EINVAL;
@@ -1318,6 +1319,15 @@ inline int build_open_flags(const struct open_how *how, struct open_flags *op)
 
 	op->intent = flags & O_PATH ? 0 : LOOKUP_OPEN;
 
+	/*
+	 * Requesting write access on a directory can never succeed. Rather
+	 * than performing a path-walk to determine whether the target is
+	 * actually a directory (-EISDIR) or not (-ENOTDIR), we short-circuit
+	 * to -ENOTDIR.
+	 */
+	if ((flags & O_DIRECTORY) && !(flags & __O_TMPFILE) && (acc_mode & MAY_WRITE))
+		return -ENOTDIR;
+
 	if (flags & O_CREAT) {
 		op->intent |= LOOKUP_CREATE;
 		if (flags & O_EXCL) {
diff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c
index 7803bd5bd01f8..d1af90c981cd4 100644
--- a/fs/smb/client/dir.c
+++ b/fs/smb/client/dir.c
@@ -538,6 +538,9 @@ int cifs_atomic_open(struct inode *dir, struct dentry *direntry,
 	if (unlikely(cifs_forced_shutdown(cifs_sb)))
 		return smb_EIO(smb_eio_trace_forced_shutdown);
 
+	if (O_IS_MKDIR(oflags))
+		oflags &= ~O_CREAT;
+
 	/*
 	 * Posix open is only called (at lookup time) for file create now. For
 	 * opens (rather than creates), because we do not know if it is a file
diff --git a/fs/vboxsf/dir.c b/fs/vboxsf/dir.c
index 0b9eab157432d..6e306ddd722bf 100644
--- a/fs/vboxsf/dir.c
+++ b/fs/vboxsf/dir.c
@@ -318,6 +318,9 @@ static int vboxsf_dir_atomic_open(struct inode *parent, struct dentry *dentry,
 	u64 handle;
 	int err;
 
+	if (O_IS_MKDIR(flags))
+		flags &= ~O_CREAT;
+
 	if (d_in_lookup(dentry)) {
 		struct dentry *res = vboxsf_dir_lookup(parent, dentry, 0);
 		if (res || d_really_is_positive(dentry))
diff --git a/include/linux/fcntl.h b/include/linux/fcntl.h
index 6ad6b9e7a226a..204e16bbe2634 100644
--- a/include/linux/fcntl.h
+++ b/include/linux/fcntl.h
@@ -30,6 +30,12 @@
  */
 #define __O_REGULAR		(1 << 30)
 
+#define O_MKDIR_MASK	(O_CREAT | O_DIRECTORY)
+static inline bool O_IS_MKDIR(unsigned int flags)
+{
+	return (flags & O_MKDIR_MASK) == O_MKDIR_MASK;
+}
+
 /* List of all valid flags for the how->resolve argument: */
 #define VALID_RESOLVE_FLAGS \
 	(RESOLVE_NO_XDEV | RESOLVE_NO_MAGICLINKS | RESOLVE_NO_SYMLINKS | \
diff --git a/tools/testing/selftests/filesystems/.gitignore b/tools/testing/selftests/filesystems/.gitignore
index 9eb185fb2f9dd..01c588d4c84f6 100644
--- a/tools/testing/selftests/filesystems/.gitignore
+++ b/tools/testing/selftests/filesystems/.gitignore
@@ -1,4 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0-only
+open_o_creat_o_dir
 dnotify_test
 devpts_pts
 fclog
diff --git a/tools/testing/selftests/filesystems/Makefile b/tools/testing/selftests/filesystems/Makefile
index 03be337c1f351..0959bd26875ac 100644
--- a/tools/testing/selftests/filesystems/Makefile
+++ b/tools/testing/selftests/filesystems/Makefile
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0
 
 CFLAGS += $(KHDR_INCLUDES)
-TEST_GEN_PROGS := devpts_pts file_stressor anon_inode_test kernfs_test fclog ustat_test
+TEST_GEN_PROGS := open_o_creat_o_dir devpts_pts file_stressor anon_inode_test kernfs_test fclog ustat_test
 TEST_GEN_PROGS += idmapped_tmpfile
 TEST_GEN_PROGS_EXTENDED := dnotify_test
 
diff --git a/tools/testing/selftests/filesystems/open_o_creat_o_dir.c b/tools/testing/selftests/filesystems/open_o_creat_o_dir.c
new file mode 100644
index 0000000000000..be0ab34267e1d
--- /dev/null
+++ b/tools/testing/selftests/filesystems/open_o_creat_o_dir.c
@@ -0,0 +1,201 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <sys/stat.h>
+#include <errno.h>
+#include <limits.h>
+#include <fcntl.h>
+
+#include "kselftest_harness.h"
+#include "wrappers.h"
+
+#define openat_o_mkdir_checked_flags(dfd, pathname, flags) ({	\
+	struct stat __st;						\
+	int __fd = openat_o_mkdir(dfd, pathname, flags, S_IRWXU);	\
+	ASSERT_GE(__fd, 0);						\
+	ASSERT_EQ(fstat(__fd, &__st), 0);				\
+	EXPECT_TRUE(S_ISDIR(__st.st_mode));				\
+	__fd;								\
+})
+
+#define openat_o_mkdir_checked(dfd, pathname) \
+	openat_o_mkdir_checked_flags(dfd, pathname, O_RDONLY)
+
+FIXTURE(open_o_creat_o_dir) {
+	char dirpath[PATH_MAX];
+	int dfd;
+};
+
+FIXTURE_SETUP(open_o_creat_o_dir)
+{
+	strcpy(self->dirpath, "/tmp/open_o_creat_o_dir_test.XXXXXX");
+	ASSERT_NE(mkdtemp(self->dirpath), NULL);
+	self->dfd = open(self->dirpath, O_DIRECTORY);
+	ASSERT_GE(self->dfd, 0);
+}
+
+FIXTURE_TEARDOWN(open_o_creat_o_dir)
+{
+	close(self->dfd);
+	rmdir(self->dirpath);
+}
+
+/* Does open_o_creat_o_dir return a fd at all? */
+TEST_F(open_o_creat_o_dir, returns_fd)
+{
+	int fd = openat_o_mkdir_checked(self->dfd, "newdir");
+	EXPECT_EQ(close(fd), 0);
+	EXPECT_EQ(unlinkat(self->dfd, "newdir", AT_REMOVEDIR), 0);
+}
+
+/* The fd must refer to the directory that was just created. */
+TEST_F(open_o_creat_o_dir, fd_is_created_dir)
+{
+	int fd;
+	struct stat st_via_fd, st_via_path;
+	char path[PATH_MAX];
+
+	fd = openat_o_mkdir_checked(self->dfd, "checkdir");
+
+	ASSERT_EQ(fstat(fd, &st_via_fd), 0);
+
+	snprintf(path, sizeof(path), "%s/checkdir", self->dirpath);
+	ASSERT_EQ(stat(path, &st_via_path), 0);
+
+	EXPECT_EQ(st_via_fd.st_ino, st_via_path.st_ino);
+	EXPECT_EQ(st_via_fd.st_dev, st_via_path.st_dev);
+
+	EXPECT_EQ(close(fd), 0);
+	EXPECT_EQ(rmdir(path), 0);
+}
+
+/* Missing parent component must fail with ENOENT. */
+TEST_F(open_o_creat_o_dir, enoent_missing_parent)
+{
+	EXPECT_EQ(openat_o_mkdir(self->dfd, "nonexistent/child", O_RDONLY, S_IRWXU), -1);
+	EXPECT_EQ(errno, ENOENT);
+}
+
+/* An invalid dfd must fail with EBADF. */
+TEST_F(open_o_creat_o_dir, ebadf)
+{
+	EXPECT_EQ(openat_o_mkdir(FD_INVALID, "badfdir", O_RDONLY, S_IRWXU), -1);
+	EXPECT_EQ(errno, EBADF);
+}
+
+/* A dfd that points to a file (not a directory) must fail with ENOTDIR. */
+TEST_F(open_o_creat_o_dir, enotdir_dfd)
+{
+	int file_fd;
+
+	file_fd = openat(self->dfd, "file",
+			 O_CREAT | O_RDONLY, S_IRWXU);
+	ASSERT_GE(file_fd, 0);
+
+	EXPECT_EQ(openat_o_mkdir(file_fd, "subdir", O_RDONLY, S_IRWXU), -1);
+	EXPECT_EQ(errno, ENOTDIR);
+
+	EXPECT_EQ(close(file_fd), 0);
+	EXPECT_EQ(unlinkat(self->dfd, "file", 0), 0);
+}
+
+/*
+ * O_EXCL together with O_CREAT|O_DIRECTORY should succeed if the target
+ * directory does not yet exist. After directory creation, repeating this
+ * call must fail with EEXIST.
+ */
+TEST_F(open_o_creat_o_dir, o_excl_eexist)
+{
+	int excldir_fd;
+
+	excldir_fd = openat_o_mkdir_checked_flags(self->dfd, "excldir", O_EXCL);
+
+	EXPECT_EQ(openat_o_mkdir(excldir_fd, ".", O_EXCL, S_IRWXU), -1);
+	EXPECT_EQ(errno, EEXIST);
+
+	EXPECT_EQ(close(excldir_fd), 0);
+	EXPECT_EQ(unlinkat(self->dfd, "excldir", AT_REMOVEDIR), 0);
+}
+
+/*
+ * O_CREAT|O_DIRECTORY on a path that already exists as a regular file
+ * must fail with ENOTDIR.
+ */
+TEST_F(open_o_creat_o_dir, existing_file_enotdir)
+{
+	int file_fd;
+
+	file_fd = openat(self->dfd, "regfile",
+			 O_CREAT | O_RDONLY, S_IRWXU);
+	ASSERT_GE(file_fd, 0);
+	EXPECT_EQ(close(file_fd), 0);
+
+	EXPECT_EQ(openat_o_mkdir(self->dfd, "regfile", O_RDONLY, S_IRWXU), -1);
+	EXPECT_EQ(errno, ENOTDIR);
+
+	EXPECT_EQ(unlinkat(self->dfd, "regfile", 0), 0);
+}
+
+/*
+ * O_CREAT|O_DIRECTORY combined with a writable access mode must be
+ * rejected: a directory cannot be opened for writing.
+ */
+TEST_F(open_o_creat_o_dir, rejects_writable_acc_mode)
+{
+	EXPECT_EQ(openat_o_mkdir(self->dfd, "rdwrdir", O_RDWR, S_IRWXU), -1);
+	EXPECT_EQ(errno, ENOTDIR);
+	/* Clean up if the kernel created the directory anyway. */
+	unlinkat(self->dfd, "rdwrdir", AT_REMOVEDIR);
+}
+
+/*
+ * openat(O_CREAT|O_DIRECTORY) with a trailing slash should work.
+ */
+TEST_F(open_o_creat_o_dir, trailing_slash)
+{
+	int fd = openat_o_mkdir_checked(self->dfd, "newdir/");
+	EXPECT_EQ(close(fd), 0);
+	EXPECT_EQ(unlinkat(self->dfd, "newdir", AT_REMOVEDIR), 0);
+}
+
+/*
+ * openat(O_CREAT) with a trailing slash but without O_DIRECTORY
+ * must fail with EISDIR and must not create anything at the path.
+ */
+TEST_F(open_o_creat_o_dir, trailing_slash_no_o_dir)
+{
+	int fd;
+	struct stat st;
+
+	fd = openat(self->dfd, "trailing/", O_CREAT | O_RDONLY, S_IRWXU);
+	EXPECT_EQ(fd, -1);
+	EXPECT_EQ(errno, EISDIR);
+
+	EXPECT_EQ(fstatat(self->dfd, "trailing", &st, 0), -1);
+	EXPECT_EQ(errno, ENOENT);
+
+	/* Best-effort cleanup in case the kernel left a file behind. */
+	if (fd >= 0)
+		close(fd);
+	unlinkat(self->dfd, "trailing", 0);
+}
+
+/*
+ * The returned fd must be usable as a dfd for further *at() calls.
+ */
+TEST_F(open_o_creat_o_dir, fd_usable_as_dfd)
+{
+	int parent_fd, child_fd;
+	char path[PATH_MAX];
+
+	parent_fd = openat_o_mkdir_checked(self->dfd, "parent");
+	child_fd = openat_o_mkdir_checked(parent_fd, "child");
+
+	EXPECT_EQ(close(child_fd), 0);
+	EXPECT_EQ(close(parent_fd), 0);
+
+	snprintf(path, sizeof(path), "%s/parent/child", self->dirpath);
+	EXPECT_EQ(rmdir(path), 0);
+	snprintf(path, sizeof(path), "%s/parent", self->dirpath);
+	EXPECT_EQ(rmdir(path), 0);
+}
+
+TEST_HARNESS_MAIN
diff --git a/tools/testing/selftests/filesystems/wrappers.h b/tools/testing/selftests/filesystems/wrappers.h
index 420ae4f908cf2..abe5b85cebdcd 100644
--- a/tools/testing/selftests/filesystems/wrappers.h
+++ b/tools/testing/selftests/filesystems/wrappers.h
@@ -13,6 +13,10 @@
 #define STATX_MNT_ID_UNIQUE 0x00004000U /* Want/got extended stx_mount_id */
 #endif
 
+#ifndef FD_INVALID
+#define FD_INVALID -10009
+#endif
+
 static inline int sys_fsopen(const char *fsname, unsigned int flags)
 {
 	return syscall(__NR_fsopen, fsname, flags);
@@ -105,4 +109,11 @@ static inline int sys_open_tree(int dfd, const char *filename, unsigned int flag
 	return syscall(__NR_open_tree, dfd, filename, flags);
 }
 
+static inline int openat_o_mkdir(int dfd, const char *pathname,
+				 unsigned int flags, mode_t mode)
+{
+	return syscall(__NR_openat, dfd, pathname,
+		       flags | O_DIRECTORY | O_CREAT, mode);
+}
+
 #endif

41/3 2026/08/23 16:16 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 13620
							output: 33
							thoughts: 1202
						

Thoughts:
**KMSAN Fuzzing Session Evaluation**

Okay, so I need to decide if this patch series warrants a dedicated KMSAN fuzzing session. It's focused on enabling `O_CREAT | O_DIRECTORY` via `openat`, which is a neat feature. The changes touch a lot of code: the VFS, plus a bunch of filesystems – 9p, Ceph, FUSE, GFS2, NFS, SMB, vboxsf. We're looking at changes in `vfs_inode.c`, various directory handling in the file systems, `namei.c`, `open.c`, and a new selftest.

First, I need to understand what's actually changing. Mostly, there are these `O_IS_MKDIR(flags)` checks and flag manipulations in those `*_atomic_open` functions to treat the request to create a directory as `O_CREAT` *without* `O_DIRECTORY` as the common behavior of the patch.

In `namei.c`, it's modifying the sticky bit handling, how directory creation is handled in permission checks and the mode is set, as well as handling `fsnotify` events and the key `lookup_open` calls. It introduces `vfs_mkdir_no_perm` and `vfs_mkdir`. Also, it calls `vfs_mkdir_no_perm` where it used to call `vfs_create_no_perm`.

`open.c` is updated to set the mode and handle the tmpfile flag and return `-ENOTDIR` correctly, based on these new flags.

We've got the new `O_IS_MKDIR` macro defined in `fcntl.h` and the corresponding selftest.

So, am I seeing new UNINITIALIZED MEMORY risks? Not from the initial review. This patch doesn't introduce any new user-space exposed data structures, or allocations with potentially uninitialized fields, or introduce any new buffers or any copy calls. It's primarily control flow and flag handling, making it a logic change. If there are bugs, they're most likely logic errors, null pointer dereferences, or out-of-bounds accesses. KASAN, LOCKDEP, and the usual bug detectors *should* catch those.

Okay, let's just make *sure* we're not accidentally using an uninitialized variable anywhere. `vfs_mkdir_no_perm` takes all the expected arguments: an inode, dentry, mode, etc. and calls the inode's mkdir method. All good.

Now in `lookup_open`: If the create_dir flag is set, it calls `vfs_mkdir_no_perm`. It uses the `delegated_inode` struct, defined as `struct delegated_inode delegated_inode;`. Let me double-check that variable in `fs/namei.c` source code. It seems like the variable is declared and used in a way that *should* be safe.

Based on the evidence I see, this does *not* look like it warrants a dedicated KMSAN fuzzing session.



42/3 2026/08/23 16:16 tool grepper
0m Args:
{
  "Expression": "struct delegated_inode delegated_inode",
  "PathPrefix": "fs/namei.c"
}

Results:
{
  "Output": "fs/namei.c=4497=static struct dentry *lookup_open(struct nameidata *nd, struct file *file,\n--\nfs/namei.c-4499-{\nfs/namei.c:4500:\tstruct delegated_inode delegated_inode = { };\nfs/namei.c-4501-\tstruct mnt_idmap *idmap;\n--\nfs/namei.c=5511=int filename_mkdirat(int dfd, struct filename *name, umode_t mode)\n--\nfs/namei.c-5516-\tunsigned int lookup_flags = LOOKUP_DIRECTORY;\nfs/namei.c:5517:\tstruct delegated_inode delegated_inode = { };\nfs/namei.c-5518-\n--\nfs/namei.c=5617=int filename_rmdir(int dfd, struct filename *name)\n--\nfs/namei.c-5624-\tunsigned int lookup_flags = 0;\nfs/namei.c:5625:\tstruct delegated_inode delegated_inode = { };\nfs/namei.c-5626-retry:\n--\nfs/namei.c=5761=int filename_unlinkat(int dfd, struct filename *name)\n--\nfs/namei.c-5768-\tstruct inode *inode;\nfs/namei.c:5769:\tstruct delegated_inode delegated_inode = { };\nfs/namei.c-5770-\tunsigned int lookup_flags = 0;\n--\nfs/namei.c=5885=int filename_symlinkat(struct filename *from, int newdfd, struct filename *to)\n--\nfs/namei.c-5890-\tunsigned int lookup_flags = 0;\nfs/namei.c:5891:\tstruct delegated_inode delegated_inode = { };\nfs/namei.c-5892-\n--\nfs/namei.c=6033=int filename_linkat(int olddfd, struct filename *old,\n--\nfs/namei.c-6038-\tstruct path old_path, new_path;\nfs/namei.c:6039:\tstruct delegated_inode delegated_inode = { };\nfs/namei.c-6040-\tint how = 0;\n--\nfs/namei.c=6331=int filename_renameat2(int olddfd, struct filename *from,\n--\nfs/namei.c-6337-\tenum last_type old_type, new_type;\nfs/namei.c:6338:\tstruct delegated_inode delegated_inode = { };\nfs/namei.c-6339-\tunsigned int lookup_flags = 0;\n"
}

43/3 2026/08/23 16:16 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 16222
							output: 264
							thoughts: 408
						

Thoughts:
**My Assessment of the Openat O_CREAT | O_DIRECTORY Patch Series**

Okay, let's break this down. My initial assessment is that this patch series looks clean from a memory safety perspective. They're adding a feature, `O_CREAT | O_DIRECTORY`, to `openat`, which is sensible for managing directory creation. The `delegated_inode` is initialized as `{ }`, so we're starting with a clean slate there.

The changes primarily involve flag handling and control flow within the VFS and filesystems. It's essentially about routing directory creation requests via `openat` more consistently. We're talking about things like the `O_IS_MKDIR` macro, modifications to `build_open_flags()`, and updates to functions like `lookup_open()`, `vfs_mkdir()`, and `atomic_open()`. Importantly, it seems like they're ensuring directory creation goes down the proper, mkdir-specific paths, leveraging existing mechanisms like `vfs_mkdir_no_perm()` and `security_path_mkdir()`.

Thinking about potential risks, because it is purely logical changes, and it's not introducing complex new data structures or directly exposing internal kernel data to user space. Therefore, the traditional memory error detectors, particularly KASAN, are likely sufficient to detect any issues. Any bugs are much more likely to manifest as logic errors, dereferences, or out-of-bounds reads/writes. These are all things that KASAN should be able to flag effectively. The design seems sound and avoids the pitfalls that would warrant KMSAN.



44/3 2026/08/23 16:16 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch series modifies the VFS and various filesystems to support creating directories via `openat()` with the `O_CREAT | O_DIRECTORY` flags. The changes primarily involve adding the `O_IS_MKDIR` macro, modifying `build_open_flags()` to handle the new flag combination, and updating `lookup_open()` and `vfs_mkdir()` paths in `fs/namei.c` to route directory creation requests to `vfs_mkdir_no_perm()` instead of regular file creation functions.\n\nThese changes are purely logical, altering control flow and flag handling within the VFS layer. The patch does not introduce new data structures sent to user space, nor does it allocate complex data structures with uninitialized fields. All variables used (such as `delegated_inode`) are properly zero-initialized. Any potential bugs introduced by these changes would likely be logic errors, NULL pointer dereferences, or out-of-bounds accesses, which are effectively caught by KASAN and standard bug detectors. There is no risk of uninitialized memory usage or info-leaks that would necessitate a dedicated KMSAN fuzzing session.",
  "NeedsKMSAN": false
}

Results:
{
  "KMSANReasoning": "The patch series modifies the VFS and various filesystems to support creating directories via `openat()` with the `O_CREAT | O_DIRECTORY` flags. The changes primarily involve adding the `O_IS_MKDIR` macro, modifying `build_open_flags()` to handle the new flag combination, and updating `lookup_open()` and `vfs_mkdir()` paths in `fs/namei.c` to route directory creation requests to `vfs_mkdir_no_perm()` instead of regular file creation functions.\n\nThese changes are purely logical, altering control flow and flag handling within the VFS layer. The patch does not introduce new data structures sent to user space, nor does it allocate complex data structures with uninitialized fields. All variables used (such as `delegated_inode`) are properly zero-initialized. Any potential bugs introduced by these changes would likely be logic errors, NULL pointer dereferences, or out-of-bounds accesses, which are effectively caught by KASAN and standard bug detectors. There is no risk of uninitialized memory usage or info-leaks that would necessitate a dedicated KMSAN fuzzing session.",
  "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)