AI Trajectory


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

Results:
{
  "EnableConfigs": [
    "9P_FS",
    "CEPH_FS",
    "FUSE_FS",
    "GFS2_FS",
    "NFS_FS",
    "CIFS",
    "VBOXSF_FS"
  ],
  "FocusSymbols": [
    "lookup_open",
    "atomic_open",
    "may_o_create",
    "build_open_flags",
    "do_open",
    "dentry_create",
    "vfs_mkdir",
    "vfs_create",
    "open_last_lookups",
    "lookup_fast_for_open",
    "may_create_in_sticky",
    "filename_create",
    "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 support for creating directories via `openat(..., O_CREAT | O_DIRECTORY, ...)`. It modifies core VFS path resolution and file creation logic in `fs/namei.c` and `fs/open.c`, including `lookup_open`, `atomic_open`, and `vfs_mkdir`. It also updates multiple filesystems (9p, ceph, fuse, gfs2, nfs, smb, vboxsf) to handle the new flag combination. This is a significant functional change to core kernel logic and should be fuzzed.",
  "WorthFuzzing": true
}

1/1 2026/07/04 16:59 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 97949077d4ed767e2cd4cd6fafb458bea5fc4858\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Sat Jul 4 16:59:48 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 5783d0336f96ac..bb555e5ba26150 100644\n--- a/fs/9p/vfs_inode.c\n+++ b/fs/9p/vfs_inode.c\n@@ -777,6 +777,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 f7396d20cb6cdc..ff3facb420c224 100644\n--- a/fs/9p/vfs_inode_dotl.c\n+++ b/fs/9p/vfs_inode_dotl.c\n@@ -239,6 +239,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 71161f2b215136..e79abdbd9f61e9 100644\n--- a/fs/ceph/file.c\n+++ b/fs/ceph/file.c\n@@ -811,6 +811,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 0e2a1039fa43ea..e01869565d5ee5 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 8a77794bbd4afc..9859ae0341a99e 100644\n--- a/fs/gfs2/inode.c\n+++ b/fs/gfs2/inode.c\n@@ -1387,6 +1387,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 19ce43c9a6e662..030cac4fbe555e 100644\n--- a/fs/namei.c\n+++ b/fs/namei.c\n@@ -1382,13 +1382,12 @@ 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\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 +1415,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 +1454,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 +2794,14 @@ 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+\treturn (bool)last.name[last.len];\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(nd-\u003elast))\n \t\tnd-\u003eflags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;\n \n \treturn walk_component(nd, WALK_TRAILING);\n@@ -4164,6 +4182,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, true);\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@@ -4196,13 +4232,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, true);\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@@ -4319,73 +4350,122 @@ 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\tumode_t mode, bool create_dir)\n {\n-\tint error = security_path_mknod(dir, dentry, mode, 0);\n+\tstruct inode *dir_inode = dir-\u003edentry-\u003ed_inode;\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, umode_t mode, bool create_dir)\n+{\n+\tif (create_dir)\n+\t\treturn vfs_prepare_mode(idmap, dir, mode, S_IRWXUGO | S_ISVTX, 0);\n+\telse\n+\t\treturn vfs_prepare_mode(idmap, dir, mode, S_IALLUGO, S_IFREG);\n }\n \n /*\n- * Attempt to atomically look up, create and open a file from a negative\n- * dentry.\n- *\n- * Returns 0 if successful.  The file will have been created and attached to\n- * @file by the filesystem calling finish_open().\n- *\n- * If the file was looked up only or didn't need creating, FMODE_OPENED won't\n- * be set.  The caller will need to perform the open themselves.  @path will\n- * have been updated to point to the new dentry.  This may be negative.\n- *\n- * Returns an error code otherwise.\n+ * atomic_open() - attempt to atomically look up, create and open a file\n+ * from a negative dentry.\n+ * @path:          parent directory path\n+ * @dentry:        child to -\u003eatomic_open()\n+ * @open_flag:     open flags\n+ * @mode:          create mode\n+ * @create_error:  return value from may_o_create()\n+ *\n+ * If FMODE_OPENED is set, the file will have been attached to @file by the\n+ * filesystem calling finish_open(). If FMODE_OPENED isn't set, the\n+ * filesystem instead called finish_no_open() and the caller will need to\n+ * perform the open themselves.\n+ *\n+ * FMODE_CREATED is set when the call to -\u003eatomic_open() actually created\n+ * the file.\n+ *\n+ * Returns the opened/looked-up dentry on success or ERR_PTR(-E) on failure.\n+ * On error, atomic_open() consumes @dentry.\n  */\n static struct dentry *atomic_open(const struct path *path, struct dentry *dentry,\n \t\t\t\t  struct file *file,\n-\t\t\t\t  int open_flag, umode_t mode)\n+\t\t\t\t  int open_flag, umode_t mode, bool create_error)\n {\n \tstruct dentry *const DENTRY_NOT_SET = (void *) -1UL;\n \tstruct inode *dir =  path-\u003edentry-\u003ed_inode;\n+\tbool failed_create = false;\n \tint error;\n \n \tfile-\u003e__f_path.dentry = DENTRY_NOT_SET;\n \tfile-\u003e__f_path.mnt = path-\u003emnt;\n+\n \terror = dir-\u003ei_op-\u003eatomic_open(dir, dentry, file,\n \t\t\t\t       open_to_namei_flags(open_flag), mode);\n \td_lookup_done(dentry);\n \tif (!error) {\n \t\tif (file-\u003ef_mode \u0026 FMODE_OPENED) {\n-\t\t\tif (unlikely(dentry != file-\u003ef_path.dentry)) {\n+\t\t\t// finish_open() called\n+\t\t\tstruct dentry *opened = file-\u003ef_path.dentry;\n+\t\t\tif (unlikely(opened != dentry)) {\n \t\t\t\tdput(dentry);\n-\t\t\t\tdentry = dget(file-\u003ef_path.dentry);\n+\t\t\t\tdentry = dget(opened);\n \t\t\t}\n-\t\t} else if (WARN_ON(file-\u003ef_path.dentry == DENTRY_NOT_SET)) {\n-\t\t\terror = -EIO;\n-\t\t} else {\n-\t\t\tif (file-\u003ef_path.dentry) {\n+\t\t} else if (likely(file-\u003ef_path.dentry != DENTRY_NOT_SET)) {\n+\t\t\t// finish_no_open() called\n+\t\t\tstruct dentry *replaced = file-\u003ef_path.dentry;\n+\t\t\tif (replaced) {\n \t\t\t\tdput(dentry);\n-\t\t\t\tdentry = file-\u003ef_path.dentry;\n+\t\t\t\tdentry = replaced;\n \t\t\t}\n \t\t\tif (unlikely(d_is_negative(dentry)))\n \t\t\t\terror = -ENOENT;\n+\t\t\tif (error \u0026\u0026 create_error) // should have created, but errored before\n+\t\t\t\tfailed_create = true;\n+\t\t} else {\n+\t\t\tconst char *fsname = dentry-\u003ed_sb-\u003es_type-\u003ename;\n+\t\t\tWARN(1, \"%s: -\u003eatomic_open() left file-\u003ef_path.dentry unset!\\n\", fsname);\n+\t\t\terror = -EIO;\n \t\t}\n \t}\n+\n \tif (error) {\n+\t\tif (failed_create)\n+\t\t\taudit_inode_child(dir, dentry, AUDIT_TYPE_CHILD_CREATE);\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, dentry);\n+\t\tif (file-\u003ef_mode \u0026 FMODE_OPENED)\n+\t\t\tfsnotify_open(file);\n \t}\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@@ -4412,6 +4492,7 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,\n \tstruct dentry *dentry;\n \tint error, create_error = 0;\n \tumode_t mode = op-\u003emode;\n+\tbool create_dir = O_IS_MKDIR(open_flag);\n \n \tif (unlikely(IS_DEADDIR(dir_inode)))\n \t\treturn ERR_PTR(-ENOENT);\n@@ -4437,7 +4518,7 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,\n \t\tdentry = NULL;\n \t}\n \tif (dentry-\u003ed_inode) {\n-\t\t/* Cached positive dentry: will open in f_op-\u003eopen */\n+\t\t/* Cached positive dentry: will open in do_open(). */\n \t\treturn dentry;\n \t}\n \n@@ -4459,19 +4540,24 @@ 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-\u003ed_inode, mode, mode, mode);\n+\t\tmode = o_create_mode(idmap, dir_inode, mode, create_dir);\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, mode, create_dir);\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 \tif (dir_inode-\u003ei_op-\u003eatomic_open) {\n \t\tif (nd-\u003eflags \u0026 LOOKUP_DIRECTORY)\n \t\t\topen_flag |= O_DIRECTORY;\n-\t\tdentry = atomic_open(\u0026nd-\u003epath, dentry, file, open_flag, mode);\n+\t\tdentry = atomic_open(\u0026nd-\u003epath, dentry, file, open_flag, mode, create_error);\n \t\tif (unlikely(create_error) \u0026\u0026 dentry == ERR_PTR(-ENOENT))\n \t\t\tdentry = ERR_PTR(create_error);\n \t\treturn dentry;\n@@ -4490,30 +4576,44 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,\n \t\t\tdentry = res;\n \t\t}\n \t}\n+\tif (dentry-\u003ed_inode || !(op-\u003eopen_flag \u0026 O_CREAT)) {\n+\t\t/* No need to create a file. If lookup returned a positive\n+\t\t * dentry, the file will be opened in do_open(). */\n+\t\treturn dentry;\n+\t}\n \n-\t/* Negative dentry, just create the file */\n-\tif (!dentry-\u003ed_inode \u0026\u0026 (open_flag \u0026 O_CREAT)) {\n-\t\t/* but break the directory lease first! */\n-\t\terror = try_break_deleg(dir_inode, LEASE_BREAK_DIR_CREATE, delegated_inode);\n-\t\tif (error)\n-\t\t\tgoto out_dput;\n-\n-\t\tfile-\u003ef_mode |= FMODE_CREATED;\n-\t\taudit_inode_child(dir_inode, dentry, AUDIT_TYPE_CHILD_CREATE);\n-\t\tif (!dir_inode-\u003ei_op-\u003ecreate) {\n-\t\t\terror = -EACCES;\n-\t\t\tgoto out_dput;\n-\t\t}\n+\t/* Negative dentry with O_CREAT flag set */\n+\taudit_inode_child(dir-\u003ed_inode, dentry, AUDIT_TYPE_CHILD_CREATE);\n \n-\t\terror = dir_inode-\u003ei_op-\u003ecreate(idmap, dir_inode, dentry,\n-\t\t\t\t\t\tmode, open_flag \u0026 O_EXCL);\n-\t\tif (error)\n-\t\t\tgoto out_dput;\n-\t}\n-\tif (unlikely(create_error) \u0026\u0026 !dentry-\u003ed_inode) {\n+\tif (unlikely(create_error)) {\n+\t\t/* Should have done a create, but we already errored */\n \t\terror = create_error;\n \t\tgoto out_dput;\n \t}\n+\n+\tif ((create_dir \u0026\u0026 !dir_inode-\u003ei_op-\u003emkdir)\n+\t    || (!create_dir \u0026\u0026 !dir_inode-\u003ei_op-\u003ecreate)) {\n+\t\terror = -EACCES;\n+\t\tgoto out_dput;\n+\t}\n+\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, delegated_inode);\n+\t\tif (IS_ERR(res)) {\n+\t\t\terror = PTR_ERR(res);\n+\t\t} else {\n+\t\t\terror = 0;\n+\t\t\tdentry = res;\n+\t\t}\n+\t} else {\n+\t\terror = vfs_create_no_perm(idmap, dentry, mode, delegated_inode);\n+\t}\n+\tif (error)\n+\t\tgoto out_dput;\n+\n+\tfile-\u003ef_mode |= FMODE_CREATED;\n+\n \treturn dentry;\n \n out_dput:\n@@ -4521,17 +4621,12 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,\n \treturn ERR_PTR(error);\n }\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(nd-\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@@ -4539,7 +4634,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(nd-\u003elast))\n \t\tnd-\u003eflags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;\n \n \tdentry = lookup_fast(nd);\n@@ -4602,16 +4697,12 @@ static const char *open_last_lookups(struct nameidata *nd,\n \t\t */\n \t}\n \tif (open_flag \u0026 O_CREAT)\n-\t\tinode_lock(dir-\u003ed_inode);\n+\t\tinode_lock_nested(dir-\u003ed_inode, I_MUTEX_PARENT);\n \telse\n \t\tinode_lock_shared(dir-\u003ed_inode);\n+\n \tdentry = lookup_open(nd, file, op, got_write, \u0026delegated_inode);\n-\tif (!IS_ERR(dentry)) {\n-\t\tif (file-\u003ef_mode \u0026 FMODE_CREATED)\n-\t\t\tfsnotify_create(dir-\u003ed_inode, dentry);\n-\t\tif (file-\u003ef_mode \u0026 FMODE_OPENED)\n-\t\t\tfsnotify_open(file);\n-\t}\n+\n \tif (open_flag \u0026 O_CREAT)\n \t\tinode_unlock(dir-\u003ed_inode);\n \telse\n@@ -4669,8 +4760,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@@ -4950,7 +5042,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(last) \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@@ -5045,13 +5137,13 @@ 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, mode, /*create_dir=*/ false);\n \t\tif (create_error)\n \t\t\tflags \u0026= ~O_CREAT;\n \n \t\t/* atomic_open will dput(dentry) on error */\n \t\tdget(orig_dentry);\n-\t\tdentry = atomic_open(path, dentry, file, flags, mode);\n+\t\tdentry = atomic_open(path, dentry, file, flags, mode, create_error);\n \t\terror = PTR_ERR_OR_ZERO(dentry);\n \n \t\tif (IS_ERR(dentry))\n@@ -5064,13 +5156,6 @@ struct file *dentry_create(struct path *path, int flags, umode_t mode,\n \t\tif (unlikely(create_error) \u0026\u0026 error == -ENOENT)\n \t\t\terror = create_error;\n \n-\t\tif (!error) {\n-\t\t\tif (file-\u003ef_mode \u0026 FMODE_CREATED)\n-\t\t\t\tfsnotify_create(dir-\u003ed_inode, dentry);\n-\t\t\tif (file-\u003ef_mode \u0026 FMODE_OPENED)\n-\t\t\t\tfsnotify_open(file);\n-\t\t}\n-\n \t\tpath-\u003edentry = dentry;\n \n \t} else {\n@@ -5222,6 +5307,39 @@ SYSCALL_DEFINE3(mknod, const char __user *, filename, umode_t, mode, unsigned, d\n \treturn filename_mknodat(AT_FDCWD, name, mode, dev);\n }\n \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+\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, di);\n+\tif (error)\n+\t\tgoto err;\n+\n+\tde = dir-\u003ei_op-\u003emkdir(idmap, dir, dentry, mode);\n+\tif (IS_ERR(de)) {\n+\t\terror = PTR_ERR(de);\n+\t\tgoto err;\n+\t}\n+\tif (de) {\n+\t\tdput(dentry);\n+\t\tdentry = de;\n+\t}\n+\tfsnotify_mkdir(dir, dentry);\n+\treturn dentry;\n+\n+err:\n+\treturn ERR_PTR(error);\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@@ -5249,7 +5367,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@@ -5265,24 +5382,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@@ -5569,7 +5674,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(last))) {\n \t\tif (d_is_dir(dentry))\n \t\t\terror = -EISDIR;\n \t\telse\n@@ -6171,16 +6276,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(new_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(old_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(new_last))\n \t\t\tgoto exit_unlock;\n \t}\n \ndiff --git a/fs/nfs/dir.c b/fs/nfs/dir.c\nindex c7b723c18620c9..c815d9aa9b69da 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 408925d7bd0b77..87e8864d948087 100644\n--- a/fs/open.c\n+++ b/fs/open.c\n@@ -1190,29 +1190,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@@ -1269,6 +1270,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 88a4a1787ff047..75bf86cc06122e 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 c5bd3271aa9616..77ae2f696fd437 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 6ad6b9e7a226af..6b75277be559dd 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 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 a78f894157de33..d779a7945cf803 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 a7ec2ba2dd8334..b60950f8b15c21 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\n+TEST_GEN_PROGS := open_o_creat_o_dir devpts_pts file_stressor anon_inode_test kernfs_test fclog\n TEST_GEN_PROGS += idmapped_tmpfile\n TEST_GEN_PROGS_EXTENDED := dnotify_test\n \ndiff --git a/tools/testing/selftests/filesystems/fclog.c b/tools/testing/selftests/filesystems/fclog.c\nindex 551c4a0f395a12..593a5136e99192 100644\n--- a/tools/testing/selftests/filesystems/fclog.c\n+++ b/tools/testing/selftests/filesystems/fclog.c\n@@ -6,10 +6,8 @@\n \n #include \u003cassert.h\u003e\n #include \u003cerrno.h\u003e\n+#include \u003cfcntl.h\u003e\n #include \u003csched.h\u003e\n-#include \u003cstdio.h\u003e\n-#include \u003cstdlib.h\u003e\n-#include \u003cstring.h\u003e\n #include \u003cunistd.h\u003e\n #include \u003csys/mount.h\u003e\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 00000000000000..df3cdbae2c8524\n--- /dev/null\n+++ b/tools/testing/selftests/filesystems/open_o_creat_o_dir.c\n@@ -0,0 +1,197 @@\n+// SPDX-License-Identifier: GPL-2.0\n+#include \u003csys/stat.h\u003e\n+#include \u003csys/syscall.h\u003e\n+#include \u003cerrno.h\u003e\n+#include \u003climits.h\u003e\n+#include \u003cfcntl.h\u003e\n+\n+#include \"kselftest_harness.h\"\n+\n+static inline int open_o_creat_o_dir(int dfd, const char *pathname,\n+\t\t\t\t     mode_t mode, unsigned int flags)\n+{\n+\treturn syscall(__NR_openat, dfd, pathname,\n+\t\t       flags | O_DIRECTORY | O_CREAT, mode);\n+}\n+\n+#define open_o_creat_o_dir_checked_flags(dfd, pathname, flags) ({\t\\\n+\tstruct stat __st;\t\t\t\t\t\t\\\n+\tint __fd = open_o_creat_o_dir(dfd, pathname, S_IRWXU, flags);\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 open_o_creat_o_dir_checked(dfd, pathname) \\\n+\topen_o_creat_o_dir_checked_flags(dfd, pathname, 0)\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 = open_o_creat_o_dir_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 = open_o_creat_o_dir_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(open_o_creat_o_dir(self-\u003edfd, \"nonexistent/child\", S_IRWXU, 0), -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(open_o_creat_o_dir(-42, \"badfdir\", S_IRWXU, 0), -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_WRONLY, S_IRWXU);\n+\tASSERT_GE(file_fd, 0);\n+\n+\tEXPECT_EQ(open_o_creat_o_dir(file_fd, \"subdir\", S_IRWXU, 0), -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 must fail with EEXIST when\n+ * the target directory already exists.\n+ */\n+TEST_F(open_o_creat_o_dir, o_excl_eexist)\n+{\n+\tint fd;\n+\n+\tfd = open_o_creat_o_dir_checked_flags(self-\u003edfd, \"excldir\", O_EXCL);\n+\tEXPECT_EQ(close(fd), 0);\n+\n+\tEXPECT_EQ(open_o_creat_o_dir(self-\u003edfd, \"excldir\", S_IRWXU, O_EXCL), -1);\n+\tEXPECT_EQ(errno, EEXIST);\n+\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_WRONLY, S_IRWXU);\n+\tASSERT_GE(file_fd, 0);\n+\tEXPECT_EQ(close(file_fd), 0);\n+\n+\tEXPECT_EQ(open_o_creat_o_dir(self-\u003edfd, \"regfile\", S_IRWXU, 0), -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(open_o_creat_o_dir(self-\u003edfd, \"rdwrdir\", S_IRWXU, O_RDWR), -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) 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_WRONLY, 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 = open_o_creat_o_dir_checked(self-\u003edfd, \"parent\");\n+\tchild_fd = open_o_creat_o_dir_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\n"
}

2/1 2026/07/04 16:59 agent patch-evaluator
2m Model:
gemini-3.1-pro-preview

Args:
null

Results:
{
  "EnableConfigs": [
    "9P_FS",
    "CEPH_FS",
    "FUSE_FS",
    "GFS2_FS",
    "NFS_FS",
    "CIFS",
    "VBOXSF_FS"
  ],
  "FocusSymbols": [
    "lookup_open",
    "atomic_open",
    "may_o_create",
    "build_open_flags",
    "do_open",
    "dentry_create",
    "vfs_mkdir",
    "vfs_create",
    "open_last_lookups",
    "lookup_fast_for_open",
    "may_create_in_sticky",
    "filename_create",
    "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 support for creating directories via `openat(..., O_CREAT | O_DIRECTORY, ...)`. It modifies core VFS path resolution and file creation logic in `fs/namei.c` and `fs/open.c`, including `lookup_open`, `atomic_open`, and `vfs_mkdir`. It also updates multiple filesystems (9p, ceph, fuse, gfs2, nfs, smb, vboxsf) to handle the new flag combination. This is a significant functional change to core kernel logic and should be fuzzed.",
  "WorthFuzzing": true
}

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

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

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).

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.
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.
   Do not list too generic configs (we already have them enabled). Only list those that
   specifically cover the modified code. 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 97949077d4ed767e2cd4cd6fafb458bea5fc4858
Author: syz-cluster <triage@syzkaller.com>
Date:   Sat Jul 4 16:59:48 2026 +0000

    syz-cluster: applied patch under review

diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c
index 5783d0336f96ac..bb555e5ba26150 100644
--- a/fs/9p/vfs_inode.c
+++ b/fs/9p/vfs_inode.c
@@ -777,6 +777,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 f7396d20cb6cdc..ff3facb420c224 100644
--- a/fs/9p/vfs_inode_dotl.c
+++ b/fs/9p/vfs_inode_dotl.c
@@ -239,6 +239,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 71161f2b215136..e79abdbd9f61e9 100644
--- a/fs/ceph/file.c
+++ b/fs/ceph/file.c
@@ -811,6 +811,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 0e2a1039fa43ea..e01869565d5ee5 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 8a77794bbd4afc..9859ae0341a99e 100644
--- a/fs/gfs2/inode.c
+++ b/fs/gfs2/inode.c
@@ -1387,6 +1387,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 19ce43c9a6e662..030cac4fbe555e 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -1382,13 +1382,12 @@ 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
  * @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 +1415,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 +1454,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 +2794,14 @@ static const char *path_init(struct nameidata *nd, unsigned flags)
 	return s;
 }
 
+static inline bool trailing_slashes(const struct qstr last)
+{
+	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);
@@ -4164,6 +4182,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, true);
+	if (!error)
+		fsnotify_create(dir, dentry);
+
+	return error;
+}
+
 /**
  * vfs_create - create new file
  * @idmap:	idmap of the mount the inode was found from
@@ -4196,13 +4232,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, true);
-	if (!error)
-		fsnotify_create(dir, dentry);
-	return error;
+
+	return vfs_create_no_perm(idmap, dentry, mode, di);
 }
 EXPORT_SYMBOL(vfs_create);
 
@@ -4319,73 +4350,122 @@ 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)
+			umode_t mode, bool create_dir)
 {
-	int error = security_path_mknod(dir, dentry, mode, 0);
+	struct inode *dir_inode = dir->dentry->d_inode;
+	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, umode_t mode, bool create_dir)
+{
+	if (create_dir)
+		return vfs_prepare_mode(idmap, dir, mode, S_IRWXUGO | S_ISVTX, 0);
+	else
+		return vfs_prepare_mode(idmap, dir, mode, S_IALLUGO, S_IFREG);
 }
 
 /*
- * Attempt to atomically look up, create and open a file from a negative
- * dentry.
- *
- * Returns 0 if successful.  The file will have been created and attached to
- * @file by the filesystem calling finish_open().
- *
- * If the file was looked up only or didn't need creating, FMODE_OPENED won't
- * be set.  The caller will need to perform the open themselves.  @path will
- * have been updated to point to the new dentry.  This may be negative.
- *
- * Returns an error code otherwise.
+ * atomic_open() - attempt to atomically look up, create and open a file
+ * from a negative dentry.
+ * @path:          parent directory path
+ * @dentry:        child to ->atomic_open()
+ * @open_flag:     open flags
+ * @mode:          create mode
+ * @create_error:  return value from may_o_create()
+ *
+ * If FMODE_OPENED is set, the file will have been attached to @file by the
+ * filesystem calling finish_open(). If FMODE_OPENED isn't set, the
+ * filesystem instead called finish_no_open() and the caller will need to
+ * perform the open themselves.
+ *
+ * FMODE_CREATED is set when the call to ->atomic_open() actually created
+ * the file.
+ *
+ * Returns the opened/looked-up dentry on success or ERR_PTR(-E) on failure.
+ * On error, atomic_open() consumes @dentry.
  */
 static struct dentry *atomic_open(const struct path *path, struct dentry *dentry,
 				  struct file *file,
-				  int open_flag, umode_t mode)
+				  int open_flag, umode_t mode, bool create_error)
 {
 	struct dentry *const DENTRY_NOT_SET = (void *) -1UL;
 	struct inode *dir =  path->dentry->d_inode;
+	bool failed_create = false;
 	int error;
 
 	file->__f_path.dentry = DENTRY_NOT_SET;
 	file->__f_path.mnt = path->mnt;
+
 	error = dir->i_op->atomic_open(dir, dentry, file,
 				       open_to_namei_flags(open_flag), mode);
 	d_lookup_done(dentry);
 	if (!error) {
 		if (file->f_mode & FMODE_OPENED) {
-			if (unlikely(dentry != file->f_path.dentry)) {
+			// finish_open() called
+			struct dentry *opened = file->f_path.dentry;
+			if (unlikely(opened != dentry)) {
 				dput(dentry);
-				dentry = dget(file->f_path.dentry);
+				dentry = dget(opened);
 			}
-		} else if (WARN_ON(file->f_path.dentry == DENTRY_NOT_SET)) {
-			error = -EIO;
-		} else {
-			if (file->f_path.dentry) {
+		} else if (likely(file->f_path.dentry != DENTRY_NOT_SET)) {
+			// finish_no_open() called
+			struct dentry *replaced = file->f_path.dentry;
+			if (replaced) {
 				dput(dentry);
-				dentry = file->f_path.dentry;
+				dentry = replaced;
 			}
 			if (unlikely(d_is_negative(dentry)))
 				error = -ENOENT;
+			if (error && create_error) // should have created, but errored before
+				failed_create = true;
+		} else {
+			const char *fsname = dentry->d_sb->s_type->name;
+			WARN(1, "%s: ->atomic_open() left file->f_path.dentry unset!\n", fsname);
+			error = -EIO;
 		}
 	}
+
 	if (error) {
+		if (failed_create)
+			audit_inode_child(dir, dentry, AUDIT_TYPE_CHILD_CREATE);
 		dput(dentry);
 		dentry = ERR_PTR(error);
+	} else {
+		if (file->f_mode & FMODE_CREATED)
+			fsnotify_create(dir, 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.
  *
@@ -4412,6 +4492,7 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
 	struct dentry *dentry;
 	int error, create_error = 0;
 	umode_t mode = op->mode;
+	bool create_dir = O_IS_MKDIR(open_flag);
 
 	if (unlikely(IS_DEADDIR(dir_inode)))
 		return ERR_PTR(-ENOENT);
@@ -4437,7 +4518,7 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
 		dentry = NULL;
 	}
 	if (dentry->d_inode) {
-		/* Cached positive dentry: will open in f_op->open */
+		/* Cached positive dentry: will open in do_open(). */
 		return dentry;
 	}
 
@@ -4459,19 +4540,24 @@ 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->d_inode, mode, mode, mode);
+		mode = o_create_mode(idmap, dir_inode, mode, create_dir);
 		if (likely(got_write))
 			create_error = may_o_create(idmap, &nd->path,
-						    dentry, mode);
+						    dentry, mode, create_dir);
 		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;
 	if (dir_inode->i_op->atomic_open) {
 		if (nd->flags & LOOKUP_DIRECTORY)
 			open_flag |= O_DIRECTORY;
-		dentry = atomic_open(&nd->path, dentry, file, open_flag, mode);
+		dentry = atomic_open(&nd->path, dentry, file, open_flag, mode, create_error);
 		if (unlikely(create_error) && dentry == ERR_PTR(-ENOENT))
 			dentry = ERR_PTR(create_error);
 		return dentry;
@@ -4490,30 +4576,44 @@ 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
+		 * dentry, the file will be opened in do_open(). */
+		return dentry;
+	}
 
-	/* Negative dentry, just create the file */
-	if (!dentry->d_inode && (open_flag & O_CREAT)) {
-		/* but break the directory lease first! */
-		error = try_break_deleg(dir_inode, LEASE_BREAK_DIR_CREATE, delegated_inode);
-		if (error)
-			goto out_dput;
-
-		file->f_mode |= FMODE_CREATED;
-		audit_inode_child(dir_inode, dentry, AUDIT_TYPE_CHILD_CREATE);
-		if (!dir_inode->i_op->create) {
-			error = -EACCES;
-			goto out_dput;
-		}
+	/* Negative dentry with O_CREAT flag set */
+	audit_inode_child(dir->d_inode, dentry, AUDIT_TYPE_CHILD_CREATE);
 
-		error = dir_inode->i_op->create(idmap, dir_inode, dentry,
-						mode, open_flag & O_EXCL);
-		if (error)
-			goto out_dput;
-	}
-	if (unlikely(create_error) && !dentry->d_inode) {
+	if (unlikely(create_error)) {
+		/* Should have done a create, but we already errored */
 		error = create_error;
 		goto out_dput;
 	}
+
+	if ((create_dir && !dir_inode->i_op->mkdir)
+	    || (!create_dir && !dir_inode->i_op->create)) {
+		error = -EACCES;
+		goto out_dput;
+	}
+
+	if (create_dir) {
+		struct dentry *res = vfs_mkdir_no_perm(idmap, dir_inode, dentry,
+						       mode, delegated_inode);
+		if (IS_ERR(res)) {
+			error = PTR_ERR(res);
+		} else {
+			error = 0;
+			dentry = res;
+		}
+	} else {
+		error = vfs_create_no_perm(idmap, dentry, mode, delegated_inode);
+	}
+	if (error)
+		goto out_dput;
+
+	file->f_mode |= FMODE_CREATED;
+
 	return dentry;
 
 out_dput:
@@ -4521,17 +4621,12 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file,
 	return ERR_PTR(error);
 }
 
-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 */
@@ -4539,7 +4634,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);
@@ -4602,16 +4697,12 @@ static const char *open_last_lookups(struct nameidata *nd,
 		 */
 	}
 	if (open_flag & O_CREAT)
-		inode_lock(dir->d_inode);
+		inode_lock_nested(dir->d_inode, I_MUTEX_PARENT);
 	else
 		inode_lock_shared(dir->d_inode);
+
 	dentry = lookup_open(nd, file, op, got_write, &delegated_inode);
-	if (!IS_ERR(dentry)) {
-		if (file->f_mode & FMODE_CREATED)
-			fsnotify_create(dir->d_inode, dentry);
-		if (file->f_mode & FMODE_OPENED)
-			fsnotify_open(file);
-	}
+
 	if (open_flag & O_CREAT)
 		inode_unlock(dir->d_inode);
 	else
@@ -4669,8 +4760,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))
@@ -4950,7 +5042,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))
@@ -5045,13 +5137,13 @@ 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, mode, /*create_dir=*/ false);
 		if (create_error)
 			flags &= ~O_CREAT;
 
 		/* atomic_open will dput(dentry) on error */
 		dget(orig_dentry);
-		dentry = atomic_open(path, dentry, file, flags, mode);
+		dentry = atomic_open(path, dentry, file, flags, mode, create_error);
 		error = PTR_ERR_OR_ZERO(dentry);
 
 		if (IS_ERR(dentry))
@@ -5064,13 +5156,6 @@ struct file *dentry_create(struct path *path, int flags, umode_t mode,
 		if (unlikely(create_error) && error == -ENOENT)
 			error = create_error;
 
-		if (!error) {
-			if (file->f_mode & FMODE_CREATED)
-				fsnotify_create(dir->d_inode, dentry);
-			if (file->f_mode & FMODE_OPENED)
-				fsnotify_open(file);
-		}
-
 		path->dentry = dentry;
 
 	} else {
@@ -5222,6 +5307,39 @@ SYSCALL_DEFINE3(mknod, const char __user *, filename, umode_t, mode, unsigned, d
 	return filename_mknodat(AT_FDCWD, name, mode, dev);
 }
 
+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;
+
+	error = -EMLINK;
+	if (max_links && dir->i_nlink >= max_links)
+		goto err;
+
+	error = try_break_deleg(dir, LEASE_BREAK_DIR_CREATE, di);
+	if (error)
+		goto err;
+
+	de = dir->i_op->mkdir(idmap, dir, dentry, mode);
+	if (IS_ERR(de)) {
+		error = PTR_ERR(de);
+		goto err;
+	}
+	if (de) {
+		dput(dentry);
+		dentry = de;
+	}
+	fsnotify_mkdir(dir, dentry);
+	return dentry;
+
+err:
+	return ERR_PTR(error);
+}
+
 /**
  * vfs_mkdir - create directory returning correct dentry if possible
  * @idmap:		idmap of the mount the inode was found from
@@ -5249,7 +5367,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);
@@ -5265,24 +5382,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);
@@ -5569,7 +5674,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
@@ -6171,16 +6276,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 c7b723c18620c9..c815d9aa9b69da 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 408925d7bd0b77..87e8864d948087 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -1190,29 +1190,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;
@@ -1269,6 +1270,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 88a4a1787ff047..75bf86cc06122e 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 c5bd3271aa9616..77ae2f696fd437 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 6ad6b9e7a226af..6b75277be559dd 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 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 a78f894157de33..d779a7945cf803 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 a7ec2ba2dd8334..b60950f8b15c21 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
+TEST_GEN_PROGS := open_o_creat_o_dir devpts_pts file_stressor anon_inode_test kernfs_test fclog
 TEST_GEN_PROGS += idmapped_tmpfile
 TEST_GEN_PROGS_EXTENDED := dnotify_test
 
diff --git a/tools/testing/selftests/filesystems/fclog.c b/tools/testing/selftests/filesystems/fclog.c
index 551c4a0f395a12..593a5136e99192 100644
--- a/tools/testing/selftests/filesystems/fclog.c
+++ b/tools/testing/selftests/filesystems/fclog.c
@@ -6,10 +6,8 @@
 
 #include <assert.h>
 #include <errno.h>
+#include <fcntl.h>
 #include <sched.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
 #include <unistd.h>
 #include <sys/mount.h>
 
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 00000000000000..df3cdbae2c8524
--- /dev/null
+++ b/tools/testing/selftests/filesystems/open_o_creat_o_dir.c
@@ -0,0 +1,197 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <errno.h>
+#include <limits.h>
+#include <fcntl.h>
+
+#include "kselftest_harness.h"
+
+static inline int open_o_creat_o_dir(int dfd, const char *pathname,
+				     mode_t mode, unsigned int flags)
+{
+	return syscall(__NR_openat, dfd, pathname,
+		       flags | O_DIRECTORY | O_CREAT, mode);
+}
+
+#define open_o_creat_o_dir_checked_flags(dfd, pathname, flags) ({	\
+	struct stat __st;						\
+	int __fd = open_o_creat_o_dir(dfd, pathname, S_IRWXU, flags);	\
+	ASSERT_GE(__fd, 0);						\
+	ASSERT_EQ(fstat(__fd, &__st), 0);				\
+	EXPECT_TRUE(S_ISDIR(__st.st_mode));				\
+	__fd;								\
+})
+
+#define open_o_creat_o_dir_checked(dfd, pathname) \
+	open_o_creat_o_dir_checked_flags(dfd, pathname, 0)
+
+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 = open_o_creat_o_dir_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 = open_o_creat_o_dir_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(open_o_creat_o_dir(self->dfd, "nonexistent/child", S_IRWXU, 0), -1);
+	EXPECT_EQ(errno, ENOENT);
+}
+
+/* An invalid dfd must fail with EBADF. */
+TEST_F(open_o_creat_o_dir, ebadf)
+{
+	EXPECT_EQ(open_o_creat_o_dir(-42, "badfdir", S_IRWXU, 0), -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_WRONLY, S_IRWXU);
+	ASSERT_GE(file_fd, 0);
+
+	EXPECT_EQ(open_o_creat_o_dir(file_fd, "subdir", S_IRWXU, 0), -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 must fail with EEXIST when
+ * the target directory already exists.
+ */
+TEST_F(open_o_creat_o_dir, o_excl_eexist)
+{
+	int fd;
+
+	fd = open_o_creat_o_dir_checked_flags(self->dfd, "excldir", O_EXCL);
+	EXPECT_EQ(close(fd), 0);
+
+	EXPECT_EQ(open_o_creat_o_dir(self->dfd, "excldir", S_IRWXU, O_EXCL), -1);
+	EXPECT_EQ(errno, EEXIST);
+
+	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_WRONLY, S_IRWXU);
+	ASSERT_GE(file_fd, 0);
+	EXPECT_EQ(close(file_fd), 0);
+
+	EXPECT_EQ(open_o_creat_o_dir(self->dfd, "regfile", S_IRWXU, 0), -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(open_o_creat_o_dir(self->dfd, "rdwrdir", S_IRWXU, O_RDWR), -1);
+	EXPECT_EQ(errno, ENOTDIR);
+	/* Clean up if the kernel created the directory anyway. */
+	unlinkat(self->dfd, "rdwrdir", AT_REMOVEDIR);
+}
+
+/*
+ * 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_WRONLY, 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 = open_o_creat_o_dir_checked(self->dfd, "parent");
+	child_fd = open_o_creat_o_dir_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

3/2 2026/07/04 16:59 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 14854
							output: 75
							thoughts: 1468
						

Thoughts:
**Fuzzing Plan: Enhancing Directory Creation with `openat`**

Okay, so we have this patch that's fundamentally changing how `O_CREAT` and `O_DIRECTORY` interact within `openat` and related system calls. Specifically, it's allowing the creation of directories using `O_CREAT | O_DIRECTORY`, a functionality that was previously either not supported or had inconsistent behavior. This is a core VFS change that adds support for creating directories via `openat(..., O_CREAT | O_DIRECTORY, ...)`.

It modifies `fs/namei.c`, `fs/open.c`, and several filesystems to handle this new behavior. From what I can see, the filesystems (9p, ceph, fuse, gfs2, nfs, smb, vboxsf) are mostly involved in clearing `O_CREAT` if `O_IS_MKDIR(flags)` is true within their `atomic_open` implementations. `O_IS_MKDIR(flags)` is defined as `(flags & O_MKDIR_MASK) == O_MKDIR_MASK` where `O_MKDIR_MASK` is `O_CREAT | O_DIRECTORY`.

The crucial change lies in the VFS: `vfs_create_no_perm` and `vfs_mkdir_no_perm` have been introduced. `atomic_open` and `lookup_open` are modified to accommodate this new `create_dir = O_IS_MKDIR(open_flag)` flag. If `create_dir` is true, the system calls `vfs_mkdir_no_perm` instead of `vfs_create_no_perm`, and `may_o_create` now takes a `create_dir` parameter and calls security hooks for mkdir operations.

This is a functional change and absolutely demands fuzzing to ensure stability and correctness. We have to make sure we hit every possible scenario.

My focus should be on fuzzing these key functions:

*   `lookup_open`
*   `atomic_open` (particularly the VFS implementation in `fs/namei.c`)
*   `vfs_mkdir_no_perm`
*   `vfs_create_no_perm`
*   `may_o_create`
*   `build_open_flags`
*   `do_open`
*   `dentry_create`
*   `vfs_mkdir`
*   `vfs_create`

I think I have a good handle on how the filesystems are interacting with this. Specifically, they're clearing `O_CREAT` in `atomic_open`, and this seems to be by design. If `atomic_open` clears `O_CREAT`, it won't attempt to create, and the VFS will handle the creation via `vfs_mkdir_no_perm`. This mechanism looks nice.

Now, let's verify that these key functions are available for instrumentation and fuzzing.



4/2 2026/07/04 17:00 tool grepper
0m Args:
{
  "Expression": "^(static )?(inline )?(struct dentry \\*|int|void|struct file \\*) (lookup_open|atomic_open|may_o_create|build_open_flags|do_open|dentry_create|vfs_mkdir|vfs_create)\\("
}

Results:
{
  "Output": "arch/x86/kernel/apm_32.c=1537=static int do_release(struct inode *inode, struct file *filp)\n--\narch/x86/kernel/apm_32.c-1574-\narch/x86/kernel/apm_32.c:1575:static int do_open(struct inode *inode, struct file *filp)\narch/x86/kernel/apm_32.c-1576-{\n--\ndrivers/net/ethernet/xircom/xirc2ps_cs.c=296=static int do_config(struct net_device *dev, struct ifmap *map);\ndrivers/net/ethernet/xircom/xirc2ps_cs.c:297:static int do_open(struct net_device *dev);\ndrivers/net/ethernet/xircom/xirc2ps_cs.c-298-static int do_ioctl(struct net_device *dev, struct ifreq *rq, int cmd);\n--\nfs/namei.c=4186=int vfs_create_no_perm(struct mnt_idmap *idmap, struct dentry *dentry,\n--\nfs/namei.c-4217- */\nfs/namei.c:4218:int vfs_create(struct mnt_idmap *idmap, struct dentry *dentry, umode_t mode,\nfs/namei.c-4219-\t       struct delegated_inode *di)\n--\nfs/namei.c=4344=static inline int open_to_namei_flags(int flag)\n--\nfs/namei.c-4350-\nfs/namei.c:4351:static int may_o_create(struct mnt_idmap *idmap,\nfs/namei.c-4352-\t\t\tconst struct path *dir, struct dentry *dentry,\n--\nfs/namei.c=4655=static const char *open_last_lookups(struct nameidata *nd,\n--\nfs/namei.c-4742- */\nfs/namei.c:4743:static int do_open(struct nameidata *nd,\nfs/namei.c-4744-\t\t   struct file *file, const struct open_flags *op)\n--\nfs/nfs/dir.c=2076=static struct nfs_open_context *create_nfs_open_context(struct dentry *dentry, int open_flags, struct file *filp)\n--\nfs/nfs/dir.c-2080-\nfs/nfs/dir.c:2081:static int do_open(struct inode *inode, struct file *filp)\nfs/nfs/dir.c-2082-{\n--\nfs/open.c=1136=inline struct open_how build_open_how(int flags, umode_t mode)\n--\nfs/open.c-1151-\nfs/open.c:1152:inline int build_open_flags(const struct open_how *how, struct open_flags *op)\nfs/open.c-1153-{\n--\ninclude/linux/fs.h=1758=bool inode_owner_or_capable(struct mnt_idmap *idmap,\n--\ninclude/linux/fs.h-1763- */\ninclude/linux/fs.h:1764:int vfs_create(struct mnt_idmap *, struct dentry *, umode_t,\ninclude/linux/fs.h-1765-\t       struct delegated_inode *);\n--\ntools/perf/util/dso.c=545=static void close_first_dso(void);\ntools/perf/util/dso.c-546-\ntools/perf/util/dso.c:547:static int do_open(char *name) EXCLUSIVE_LOCKS_REQUIRED(_dso__data_open_lock)\ntools/perf/util/dso.c-548-{\n"
}

5/2 2026/07/04 17:00 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 18062
							output: 33
							thoughts: 24
						

Thoughts:
Here's my take on those thoughts:

**Navigating the Filesystem Kernel Code**

Okay, so I need to dig into the filesystem code again. Specifically, I'm trying to understand how filename lookups and atomic opens are handled. My first move will be to zero in on `fs/namei.c`. I expect to find the core logic for both `lookup_open` and `atomic_open` routines defined there. That's usually the go-to place for anything related to name resolution and the initial setup of file operations. Time to fire up the editor and see what's what.



6/2 2026/07/04 17:00 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "^(static )?(struct dentry \\*) (lookup_open|atomic_open)\\("
}

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

7/2 2026/07/04 17:00 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 18262
							output: 35
							thoughts: 30
						

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

**Navigating the `dentry` Jungle: Return Types and Function Signatures**

Right, so I'm thinking about a function that clearly deals with `dentry` structures. I'm trying to quickly ascertain its precise return type. My initial guess is that it *could* be `struct dentry *` or perhaps even `const struct dentry *`, with some variations in spacing around the asterisk – you know how those things can be. To avoid any ambiguity or unnecessary parsing of the source code, the most efficient approach is to directly target the function calls themselves. I'll just use `grep` to quickly find where the function is called.



8/2 2026/07/04 17:00 tool grepper
0m Args:
{
  "Expression": "^(static )?(struct dentry \\*\\s*)?(lookup_open|atomic_open)\\("
}

Results:
{
  "Output": "fs/namei.c=4380=static inline umode_t o_create_mode(struct mnt_idmap *idmap,\n--\nfs/namei.c-4408- */\nfs/namei.c:4409:static struct dentry *atomic_open(const struct path *path, struct dentry *dentry,\nfs/namei.c-4410-\t\t\t\t  struct file *file,\n--\nfs/namei.c=4466=struct dentry *vfs_mkdir_no_perm(struct mnt_idmap *, struct inode *,\n--\nfs/namei.c-4483- */\nfs/namei.c:4484:static struct dentry *lookup_open(struct nameidata *nd, struct file *file,\nfs/namei.c-4485-\t\t\t\t  const struct open_flags *op,\n"
}

9/2 2026/07/04 17:00 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 18715
							output: 27
							thoughts: 20
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience:

**Analyzing `dentry_create` and `vfs_mkdir`**

Alright, let's dive into the core of this operation. My immediate focus is on `dentry_create` and `vfs_mkdir`. I want to quickly refresh my understanding of their interaction in the context of creating a new directory. I'm thinking about the dentry lifecycle and how the VFS layer handles the requests, especially the inode allocation. I need to refresh the details of the locking mechanisms involved in these functions. Are there any contention points to look at, or potential race conditions? Is `vfs_mkdir` just a wrapper, or does it perform substantial work itself? How does `dentry_create` affect the cache and ensure consistency across the FS? I'm curious about the specific parameters these functions accept and their effect on the resulting dentry and inode. I'm running through all this to quickly build a mental map of the whole operation.



10/2 2026/07/04 17:00 tool grepper
0m Args:
{
  "Expression": "^(struct file \\*\\s*)?dentry_create\\("
}

Results:
{
  "Output": "fs/namei.c=5100=EXPORT_SYMBOL(start_creating_user_path);\n--\nfs/namei.c-5118- */\nfs/namei.c:5119:struct file *dentry_create(struct path *path, int flags, umode_t mode,\nfs/namei.c-5120-\t\t\t   const struct cred *cred)\n--\ninclude/linux/fs.h=2479=struct file *dentry_open_nonotify(const struct path *path, int flags,\ninclude/linux/fs.h-2480-\t\t\t\t  const struct cred *cred);\ninclude/linux/fs.h:2481:struct file *dentry_create(struct path *path, int flags, umode_t mode,\ninclude/linux/fs.h-2482-\t\t\t   const struct cred *cred);\n"
}

11/2 2026/07/04 17:00 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 19204
							output: 28
							thoughts: 7
						

Thoughts:
Okay, here's my interpretation of that thought process, tailored for an expert audience, and presented as if it were my own internal monologue:

**Thinking About Filesystems and Directory Creation**

Alright, so we're talking about the low-level, filesystem interaction here. The mental tick I get from "vfs_mkdir" is the critical function within a Virtual File System (VFS) layer. I immediately think about the core mechanics – how that single call translates to actions across different underlying filesystems. We're not just creating a directory; we're orchestrating the *request* for a directory creation.

My mind jumps to the VFS's role: it's the abstraction layer. `vfs_mkdir` itself doesn't *do* the mkdir. It takes the parameters—the path, permissions, etc.—and then it dispatches the actual work to the appropriate underlying filesystem. I instantly start considering the implications: how does the VFS select the right filesystem based on the path? Is it based on mount points, on a filesystem-specific structure or data, or a combination?

I'm thinking about the arguments to `vfs_mkdir` and what they imply. The path itself triggers considerations about path resolution. Does the VFS handle symbolic links along the path? How does it deal with relative paths vs. absolute paths? The permissions, too, are instantly a point of focus. We have to consider how to handle UID/GIDs (User/Group IDs), and the translation layer that occurs at the VFS level before the specific filesystem gets involved. Does the underlying filesystem support the permissions model? What is the standard behavior in this case, and how does `vfs_mkdir` adapt to it?

The error handling aspects are also at the forefront. What are the common error codes that might come back? "Permission denied," "file exists," "out of space," "invalid path"... I start mapping those potential errors to the specific checks that `vfs_mkdir` might do at the VFS level, and also the sorts of errors that would come back from a real filesystem. I'm wondering what is the expected error handling strategy. Does the VFS attempt retry or other logic, or does it simply return the error code?

Basically, I'm thinking about the intricate dance between the VFS layer and the filesystem driver. `vfs_mkdir` is just a key that activates this relationship, so there's a lot of underlying filesystem mechanics and interactions that I'm considering.



12/2 2026/07/04 17:00 tool grepper
0m Args:
{
  "Expression": "^(struct dentry \\*\\s*)?vfs_mkdir\\("
}

Results:
{
  "Output": "Documentation/filesystems/porting.rst=1312=instead.\n--\nDocumentation/filesystems/porting.rst-1317-\nDocumentation/filesystems/porting.rst:1318:vfs_mkdir() now returns a dentry - the one returned by -\u003emkdir().  If\nDocumentation/filesystems/porting.rst-1319-that dentry is different from the dentry passed in, including if it is\n--\nfs/namei.c=5311=struct dentry *vfs_mkdir_no_perm(struct mnt_idmap *idmap, struct inode *dir,\n--\nfs/namei.c-5364- */\nfs/namei.c:5365:struct dentry *vfs_mkdir(struct mnt_idmap *idmap, struct inode *dir,\nfs/namei.c-5366-\t\t\t struct dentry *dentry, umode_t mode,\n--\ninclude/linux/fs.h=1764=int vfs_create(struct mnt_idmap *, struct dentry *, umode_t,\ninclude/linux/fs.h-1765-\t       struct delegated_inode *);\ninclude/linux/fs.h:1766:struct dentry *vfs_mkdir(struct mnt_idmap *, struct inode *,\ninclude/linux/fs.h-1767-\t\t\t struct dentry *, umode_t, struct delegated_inode *);\n"
}

13/2 2026/07/04 17:00 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 20124
							output: 28
							thoughts: 1003
						

Thoughts:
**Identifying Key Functions and Configuration for Testing**

Okay, so I'm diving into this code and figuring out the critical functions related to this patch. Initially, I was focusing on:

-   `lookup_open`
-   `atomic_open`
-   `may_o_create`
-   `build_open_flags`
-   `do_open`
-   `dentry_create`
-   `vfs_mkdir`
-   `vfs_create`

I had a quick thought about including the static inline functions `vfs_mkdir_no_perm` and `vfs_create_no_perm` but decided against it, as syzkaller might struggle to trace those. It's safer to stick to the non-inline calling functions.

Then I realized `do_open` is also static, so I went back to make sure, it is called from openat, which is always enabled.

Then I remembered the patch also touches various filesystems. It looks like each of them now handles `O_IS_MKDIR(flags)` in their `atomic_open` calls. So I need to add these as well:

-   `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`

That led me to think about filesystem configs. I listed them at first, but then I stopped myself. The prompt says, "Do not list too generic configs (we already have them enabled)." So I need to go look into Kconfig files for any ifdefs. I should not make any assumptions. Let's see if there are any config options related to the filesystem changes or the core functions in `fs/namei.c` and `fs/open.c`. I have to check the Kconfig files now.



14/2 2026/07/04 17:01 tool grepper
0m Args:
{
  "Expression": "#ifdef",
  "PathPrefix": "fs/namei.c"
}

Results:
{
  "Output": "fs/namei.c=374=static int check_acl(struct mnt_idmap *idmap,\n--\nfs/namei.c-376-{\nfs/namei.c:377:#ifdef CONFIG_FS_POSIX_ACL\nfs/namei.c-378-\tstruct posix_acl *acl;\n--\nfs/namei.c=413=static inline bool no_acl_inode(struct inode *inode)\nfs/namei.c-414-{\nfs/namei.c:415:#ifdef CONFIG_FS_POSIX_ACL\nfs/namei.c-416-\treturn likely(!READ_ONCE(inode-\u003ei_acl));\n--\nfs/namei.c=1207=static int sysctl_protected_regular __read_mostly;\nfs/namei.c-1208-\nfs/namei.c:1209:#ifdef CONFIG_SYSCTL\nfs/namei.c-1210-static const struct ctl_table namei_sysctls[] = {\n--\nfs/namei.c=2278=static __always_inline const char *walk_component(struct nameidata *nd, int flags)\n--\nfs/namei.c-2320- */\nfs/namei.c:2321:#ifdef CONFIG_DCACHE_WORD_ACCESS\nfs/namei.c-2322-\n--\nfs/namei.c-2324-\nfs/namei.c:2325:#ifdef HASH_MIX\nfs/namei.c-2326-\n--\nfs/namei.c=2465=static inline const char *hash_name(struct nameidata *nd,\n--\nfs/namei.c-2516- */\nfs/namei.c:2517:#ifdef __BIG_ENDIAN\nfs/namei.c-2518-  #define LAST_WORD_IS_DOT\t(0x2eul \u003c\u003c (BITS_PER_LONG-8))\n--\nfs/namei.c=3627=EXPORT_SYMBOL(start_removing_dentry);\nfs/namei.c-3628-\nfs/namei.c:3629:#ifdef CONFIG_UNIX98_PTYS\nfs/namei.c-3630-int path_pts(struct path *path)\n"
}

15/2 2026/07/04 17:01 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 22225
							output: 33
							thoughts: 575
						

Thoughts:
**Analyzing VFS Modifications for Potential Feature Flagging**

Okay, so I'm digging into this code, specifically in `fs/namei.c` and `fs/open.c`, and I'm not seeing any preprocessor directives (`#ifdef`) around the changes. That's a key first observation. If this is a new feature or change, it might require feature flagging, and the absence of `ifdefs` is noteworthy.

I've got a list of the modified functions: `vfs_create_no_perm`, `vfs_create`, `may_o_create`, `o_create_mode`, `atomic_open`, `vfs_mkdir_no_perm`, `lookup_open`, `lookup_fast_for_open`, `open_last_lookups`, `do_open`, `filename_create`, `dentry_create`, `vfs_mkdir`, `filename_unlinkat`, `filename_renameat2` and `build_open_flags`. Now, these are all core VFS functions, and I should really focus on the key ones: `lookup_open`, `atomic_open`, `may_o_create`, `build_open_flags`, `do_open`, `dentry_create`, `vfs_mkdir`, `vfs_create`, `vfs_mkdir_no_perm`, `vfs_create_no_perm`, `o_create_mode`, `open_last_lookups`, and `lookup_fast_for_open`. These are the functions where the actual modifications likely reside.

Wait a second... `atomic_open` in `fs/namei.c` is static, which is interesting. I should check if there are other `atomic_open` functions because it could be a crucial part of the change. Yes, there are filesystem-specific implementations: `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`.

Since this touches multiple filesystems, I need to consider configuration dependencies. I should look into the relevant Kconfig options: `9P_FS`, `CEPH_FS`, `FUSE_FS`, `GFS2_FS`, `NFS_FS`, `CIFS`, and `VBOXSF_FS`. That will tell me if the modifications are conditional on any of these filesystem options being enabled. Now, let's just make sure I have all the facts. Checking the Kconfig for `9P_FS`, just to verify that.



16/2 2026/07/04 17:01 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"
}

17/2 2026/07/04 17:01 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 23709
							output: 31
							thoughts: 21
						

Thoughts:
**Checking the File System Configuration**

Okay, so the prompt is pointing me towards `9P_FS`. My initial instinct is to delve into the file system configurations to see how this is handled. That means the next logical step is to check `fs/ceph/Kconfig`. I'll want to see how `9P_FS` relates to the broader Ceph file system configuration, and if there's any direct connection or if it's treated separately. This should provide context and direction for understanding the integration.



18/2 2026/07/04 17:01 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"
}

19/2 2026/07/04 17:01 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 24117
							output: 31
							thoughts: 21
						

Thoughts:
Okay, here's my summarized thought process, assuming I'm an expert:

**Investigating a Filesystem Configuration Option**

Right, let's start with `CEPH_FS`. That's the key. I need to understand this filesystem configuration option and how it's defined and enabled.

Now, let's dive into the kernel configuration files. The logical place to look for its definition would be under the fuse module since it's a userspace filesystem. Specifically, I'll examine `fs/fuse/Kconfig` to see the dependencies and any other details. I'm expecting to find its definition there, along with any relevant context. I'll quickly check if `CEPH_FS` has an immediate link, or if `fuse` is a dependency. This should give me a good understanding of where it fits into the broader kernel configuration landscape.



20/2 2026/07/04 17:01 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"
}

21/2 2026/07/04 17:01 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 24429
							output: 34
							thoughts: 23
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience:

**Kernel Configuration Context**

Right, `FUSE_FS`. Affirmative. Makes perfect sense in this context.

Now, I'm already thinking about where to find the related configurations. My immediate mental map is taking me to the `fs/` directory, specifically within the kernel source tree. Let's drill down and check the `gfs2` configuration file. Looking at `fs/gfs2/Kconfig` will give me a clear picture of how this filesystem is integrated and managed in relation to the overall kernel build process. Time to quickly cross reference the GFS2 Kconfig and determine dependencies, defaults, and available options related to FUSE.



22/2 2026/07/04 17:01 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"
}

23/2 2026/07/04 17:01 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 24783
							output: 31
							thoughts: 22
						

Thoughts:
**My Initial Assessment**

Okay, so the prompt is pointing me toward something related to GFS2, a clustered file system. Specifically, it's asking about something involving `GFS2_FS`. My immediate thought is, "Yes, that's definitely the key." We're likely dealing with some configuration option or kernel feature specifically tied to GFS2.

Given the context, I think the next logical step is to dive into the kernel configuration files, particularly within the NFS subtree. Let's start by examining `fs/nfs/Kconfig`. It's a sensible starting point since NFS often integrates with various file systems. Perhaps there's a dependency, a conflict, or some related configuration option that's relevant to understand how GFS2 interacts with or is treated in relation to NFS. Let's see what's in there.



24/2 2026/07/04 17:01 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"
}

25/2 2026/07/04 17:01 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 25162
							output: 32
							thoughts: 24
						

Thoughts:
Okay, here's my interpretation of those thoughts, crafted with the understanding that I'm speaking to a fellow expert:

**NFS_FS Confirmation and Next Steps**

Yep, that confirms it: we're dealing with `NFS_FS` here. My immediate follow-up is to drill down into the Samba client's configuration. The most logical place to start digging is `fs/smb/client/Kconfig`. I need to understand exactly how the Samba client interacts with the kernel's file system setup in this specific context. This `Kconfig` file should give me the details I need to evaluate and confirm the issue.



26/2 2026/07/04 17:01 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"
}

27/2 2026/07/04 17:01 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 26398
							output: 34
							thoughts: 21
						

Thoughts:
Here's my thought process, as if I were considering this:

**CIFS and Kernel Configuration: A Quick Check**

Okay, so the prompt mentions `CIFS`. Immediately, I think of the Common Internet File System, a core component for file sharing, likely within a Linux kernel context. My mind jumps to where this would be defined, the kernel configuration. I need to quickly determine if it's enabled and where to look.

Right, my first instinct is to check the relevant Kconfig file. Let's see... `fs/vboxsf/Kconfig` sounds like a plausible location. That will have configuration options related to virtualbox shared folders. Hopefully, there is a reference to CIFS there, or perhaps the appropriate dependency information, which will give me a clear picture.



28/2 2026/07/04 17:01 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"
}

29/2 2026/07/04 17:01 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 26678
							output: 32
							thoughts: 711
						

Thoughts:
**My Analysis of Filesystem Configuration and Symbol Selection**

Okay, so I'm considering the necessary configurations. Yes, `VBOXSF_FS` needs to be enabled. I see the provided list of filesystems: `9P_FS`, `CEPH_FS`, `FUSE_FS`, `GFS2_FS`, `NFS_FS`, `CIFS`, and `VBOXSF_FS`.

But, wait, the prompt specifically asks to avoid listing generic configs and to focus only on those directly related to the modified code. Syzkaller usually includes `FUSE_FS`, `NFS_FS`, `CIFS`, and `9P_FS` by default. Should I list them again? Technically, they *are* related because the changes touch these filesystems. However, the core of this patch seems to be the VFS changes, and the filesystem changes are just minor adjustments. I'll include them anyway, just to be safe. It shouldn't hurt to have them explicitly listed.

Now, let's turn to the `FocusSymbols`. I have the following list: `lookup_open`, `atomic_open`, `may_o_create`, `build_open_flags`, `do_open`, `dentry_create`, `vfs_mkdir`, `vfs_create`, `vfs_mkdir_no_perm`, `vfs_create_no_perm`, `o_create_mode`, `open_last_lookups`, `lookup_fast_for_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`.

Hold on, I need to double-check something. The `atomic_open` function in `fs/namei.c` is declared as static. And, `vfs_mkdir_no_perm`, `vfs_create_no_perm`, and `o_create_mode` are static inline functions. Syzkaller won't be able to trace static inline functions, so I must remove these functions from the FocusSymbols list.

So, I will remove these from my list:
- `vfs_mkdir_no_perm`
- `vfs_create_no_perm`
- `o_create_mode`

Let's verify the rest. I check `atomic_open`. It is defined as `static struct dentry *atomic_open(...)`, so, thankfully, it is traceable.
`lookup_open` is `static struct dentry *lookup_open(...)`, also traceable.
`may_o_create` is `static int may_o_create(...)`, also traceable.
`build_open_flags` is inline, but let me check it to be sure. Aha, it's `inline int build_open_flags(const struct open_how *how, struct open_flags *op)`. It *might* be inlined. I will need to check `fs/open.c` to be sure.



30/2 2026/07/04 17:01 tool grepper
0m Args:
{
  "Expression": "inline int build_open_flags",
  "PathPrefix": "fs/open.c"
}

Results:
{
  "Output": "fs/open.c=1136=inline struct open_how build_open_how(int flags, umode_t mode)\n--\nfs/open.c-1151-\nfs/open.c:1152:inline int build_open_flags(const struct open_how *how, struct open_flags *op)\nfs/open.c-1153-{\n"
}

31/2 2026/07/04 17:01 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 28234
							output: 30
							thoughts: 810
						

Thoughts:
**Refining the Traceability Map**

Okay, let's take stock. I've been reviewing the code related to file creation and opening, trying to build a comprehensive list of potentially relevant functions. Initially, I identified a core group of functions that seemed directly involved: `inline int build_open_flags`, `static int do_open(...)`, `struct file *dentry_create(...)`, `struct dentry *vfs_mkdir(...)`, `int vfs_create(...)`, `static const char *open_last_lookups(...)`, and `static struct dentry *lookup_fast_for_open(...)`. Each of these looks immediately traceable.

I then refined the "FocusSymbols" list, adding these to my existing candidates: `lookup_open`, `atomic_open`, `may_o_create`. This seemed like a solid starting point.

To broaden the net, I then examined `fs/namei.c` to identify other modified functions. This revealed a few more interesting candidates: `may_create_in_sticky`, `lookup_last`, `filename_create`, `filename_unlinkat`, and `filename_renameat2`. `lookup_last` is static inline, so that might be less interesting, but it's worth noting.

Now, let's trace the newly identified functions: `static int may_create_in_sticky(...)`, `static struct dentry *filename_create(...)`, `int filename_unlinkat(...)`, and `int filename_renameat2(...)`. I should check `may_create_in_sticky` and `filename_create` for now.



32/2 2026/07/04 17:01 tool grepper
0m Args:
{
  "Expression": "^(static )?(struct dentry \\*\\s*)?filename_create\\("
}

Results:
{
  "Output": "fs/namei.c=4993=struct file *do_file_open_root(const struct path *root,\n--\nfs/namei.c-5016-\nfs/namei.c:5017:static struct dentry *filename_create(int dfd, struct filename *name,\nfs/namei.c-5018-\t\t\t\t      struct path *path, unsigned int lookup_flags)\n"
}

33/2 2026/07/04 17:01 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 29531
							output: 31
							thoughts: 10
						

Thoughts:
Okay, here's the summary, rendered as a first-person thought process, keeping in mind the user is an expert.

**The Traceability of `filename_create`**

Right, so `filename_create`... It's readily apparent that this is a traceable function. We're not dealing with some black box here. The very nature of creating a filename, especially in a system that might be logging or auditing, inherently leaves breadcrumbs. The act itself is a point of potential inspection. We're probably looking at logs of the function call, maybe timestamps, perhaps even information on the user or process that triggered it, and the parameters that went into generating the filename. Depending on the system's architecture, there could be database entries, configuration files that define filename conventions, or even security policies that govern what can be created. The level of traceability will, of course, depend on the specific implementation, the logging level, and any auditing mechanisms in place, but the fundamental act itself leaves a trail. I'd need more context to say *how* traceable, but *that* it's traceable is a given, at least on some level.



34/2 2026/07/04 17:02 tool grepper
0m Args:
{
  "Expression": "^(static )?(int\\s*)?may_create_in_sticky\\("
}

Results:
{
  "Output": "fs/namei.c=1360=int may_linkat(struct mnt_idmap *idmap, const struct path *link)\n--\nfs/namei.c-1408- */\nfs/namei.c:1409:static int may_create_in_sticky(struct mnt_idmap *idmap, struct nameidata *nd,\nfs/namei.c-1410-\t\t\t\tstruct inode *const inode)\n"
}

35/2 2026/07/04 17:02 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 29935
							output: 402
							thoughts: 634
						

Thoughts:
**My Analysis of the Patch's Scope**

Okay, so I've traced the core functions touched by this patch and I'm confident about the list of `FocusSymbols`. Yes, `may_create_in_sticky` is definitely traceable. Here's my definitive list:

*   `lookup_open`
*   `atomic_open`
*   `may_o_create`
*   `build_open_flags`
*   `do_open`
*   `dentry_create`
*   `vfs_mkdir`
*   `vfs_create`
*   `open_last_lookups`
*   `lookup_fast_for_open`
*   `may_create_in_sticky`
*   `filename_create`
*   `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`

I had to double-check `build_open_flags` because it's inline, but it's exported and used, so it's relevant.

This represents a comprehensive look at the code modified by the patch.

I see that this patch is introducing support for creating directories using `openat(..., O_CREAT | O_DIRECTORY, ...)`. This is a pretty significant change. It modifies core VFS components involved in path resolution and file creation, specifically in `fs/namei.c` and `fs/open.c`. It's going to affect key functions like `lookup_open`, `atomic_open`, and `vfs_mkdir`. It also necessitates updates to a variety of filesystems, namely, 9p, ceph, fuse, gfs2, nfs, smb, and vboxsf, to correctly handle the new `O_DIRECTORY` flag in conjunction with `O_CREAT`.

The patch updates these filesystems to clear the `O_CREAT` flag when `O_DIRECTORY` is present, which lets the VFS layer handle the directory creation correctly. This means a substantial functional change to core kernel logic, warranting thorough fuzzing.

I've also confirmed the relevant configurations this will impact:

*   `9P_FS`
*   `CEPH_FS`
*   `FUSE_FS`
*   `GFS2_FS`
*   `NFS_FS`
*   `CIFS`
*   `VBOXSF_FS`

The presence of the selftest, `tools/testing/selftests/filesystems/open_o_creat_o_dir.c`, further confirms it's a functional change, not just a refactor.

Alright, I'm confident that this is a solid analysis. Time to call `set-results`.



36/2 2026/07/04 17:02 tool set-results
0m Args:
{
  "EnableConfigs": [
    "9P_FS",
    "CEPH_FS",
    "FUSE_FS",
    "GFS2_FS",
    "NFS_FS",
    "CIFS",
    "VBOXSF_FS"
  ],
  "FocusSymbols": [
    "lookup_open",
    "atomic_open",
    "may_o_create",
    "build_open_flags",
    "do_open",
    "dentry_create",
    "vfs_mkdir",
    "vfs_create",
    "open_last_lookups",
    "lookup_fast_for_open",
    "may_create_in_sticky",
    "filename_create",
    "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 support for creating directories via `openat(..., O_CREAT | O_DIRECTORY, ...)`. It modifies core VFS path resolution and file creation logic in `fs/namei.c` and `fs/open.c`, including `lookup_open`, `atomic_open`, and `vfs_mkdir`. It also updates multiple filesystems (9p, ceph, fuse, gfs2, nfs, smb, vboxsf) to handle the new flag combination. This is a significant functional change to core kernel logic and should be fuzzed.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "9P_FS",
    "CEPH_FS",
    "FUSE_FS",
    "GFS2_FS",
    "NFS_FS",
    "CIFS",
    "VBOXSF_FS"
  ],
  "FocusSymbols": [
    "lookup_open",
    "atomic_open",
    "may_o_create",
    "build_open_flags",
    "do_open",
    "dentry_create",
    "vfs_mkdir",
    "vfs_create",
    "open_last_lookups",
    "lookup_fast_for_open",
    "may_create_in_sticky",
    "filename_create",
    "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 support for creating directories via `openat(..., O_CREAT | O_DIRECTORY, ...)`. It modifies core VFS path resolution and file creation logic in `fs/namei.c` and `fs/open.c`, including `lookup_open`, `atomic_open`, and `vfs_mkdir`. It also updates multiple filesystems (9p, ceph, fuse, gfs2, nfs, smb, vboxsf) to handle the new flag combination. This is a significant functional change to core kernel logic and should be fuzzed.",
  "WorthFuzzing": true
}

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)