| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/07/29 18:49 | flow | patch-triage |
1mArgs:null Results: {
"EnableConfigs": [
"AFS_FS"
],
"FocusSymbols": [
"afs_is_owned_by_me",
"afs_have_same_owner",
"vfs_inode_is_owned_by_me",
"setattr_prepare",
"inode_owner_or_capable",
"vfs_setlease",
"__check_sticky",
"vfs_dedupe_file_range_one"
],
"Reasoning": "The patch introduces new VFS inode operations `is_owned_by_me` and `have_same_owner` to allow filesystems (like AFS) to override default UID-based ownership checks. It implements these for AFS and modifies several core VFS functions (chown, chgrp, lease, sticky bit checks, dedupe, symlink following) to use the new helpers. This is a functional change in core VFS and AFS that affects file permission checks and should be fuzzed.",
"WorthFuzzing": true
} |
| 1/1 | 2026/07/29 18:49 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit ac99a978e7180cb24d9ae151d752bcb97ec44dab\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Wed Jul 29 18:49:06 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst\nindex 7c753148af888..893419e003eaf 100644\n--- a/Documentation/filesystems/vfs.rst\n+++ b/Documentation/filesystems/vfs.rst\n@@ -445,6 +445,9 @@ As of kernel 2.6.22, the following members are defined:\n \t\t\t\t struct dentry *dentry, struct file_kattr *fa);\n \t\tint (*fileattr_get)(struct dentry *dentry, struct file_kattr *fa);\n \t struct offset_ctx *(*get_offset_ctx)(struct inode *inode);\n+\t\tint (*is_owned_by_me)(struct mnt_idmap *idmap, struct inode *inode);\n+\t\tint (*have_same_owner)(struct mnt_idmap *idmap, struct inode *inode,\n+\t\t\t\t struct dentry *dentry);\n \t};\n \n Again, all methods are called without any locks being held, unless\n@@ -634,6 +637,24 @@ otherwise noted.\n filesystem must define this operation to use\n simple_offset_dir_operations.\n \n+``is_owned_by_me``\n+\tcalled to determine if the file can be considered to be 'owned' by\n+\tthe owner of the process or if the process has a token that grants\n+\tit ownership privileges. If unset, the default is to compare i_uid\n+\tto current_fsuid() - but this may give incorrect results for some\n+\tnetwork or plug-in block filesystems. For example, AFS determines\n+\townership entirely according to an obtained token and i_uid may not\n+\teven be from the same ID space as current_uid().\n+\n+``have_same_owner``\n+\tcalled to determine if an inode has the same owner as its immediate\n+\tparent on the path walked. If unset, the default is to simply\n+\tcompare the i_uid of both. For example, AFS compares the owner IDs\n+\tof both - but these are a 64-bit values on some variants that might\n+\tnot fit into a kuid_t and cifs has GUIDs that cannot be compared to\n+\tkuid_t.\n+\n+\n The Address Space Object\n ========================\n \ndiff --git a/fs/afs/dir.c b/fs/afs/dir.c\nindex 6df56fe9163fd..ee2f5ec0b2c67 100644\n--- a/fs/afs/dir.c\n+++ b/fs/afs/dir.c\n@@ -69,6 +69,8 @@ const struct inode_operations afs_dir_inode_operations = {\n \t.permission\t= afs_permission,\n \t.getattr\t= afs_getattr,\n \t.setattr\t= afs_setattr,\n+\t.is_owned_by_me\t= afs_is_owned_by_me,\n+\t.have_same_owner = afs_have_same_owner,\n };\n \n const struct address_space_operations afs_dir_aops = {\ndiff --git a/fs/afs/file.c b/fs/afs/file.c\nindex 0467742bfeee3..ba44493ccaf6e 100644\n--- a/fs/afs/file.c\n+++ b/fs/afs/file.c\n@@ -49,6 +49,8 @@ const struct inode_operations afs_file_inode_operations = {\n \t.getattr\t= afs_getattr,\n \t.setattr\t= afs_setattr,\n \t.permission\t= afs_permission,\n+\t.is_owned_by_me\t= afs_is_owned_by_me,\n+\t.have_same_owner = afs_have_same_owner,\n };\n \n const struct address_space_operations afs_file_aops = {\ndiff --git a/fs/afs/inode.c b/fs/afs/inode.c\nindex 14f39a9bea6cf..af85a88d943dd 100644\n--- a/fs/afs/inode.c\n+++ b/fs/afs/inode.c\n@@ -91,6 +91,7 @@ static int afs_inode_init_from_status(struct afs_operation *op,\n \tinode-\u003ei_flags |= S_NOATIME;\n \tinode-\u003ei_uid = make_kuid(\u0026init_user_ns, status-\u003eowner);\n \tinode-\u003ei_gid = make_kgid(\u0026init_user_ns, status-\u003egroup);\n+\tinode-\u003ei_opflags |= IOP_OWNERSHIP_OVERRIDE;\n \tset_nlink(\u0026vnode-\u003enetfs.inode, status-\u003enlink);\n \n \ti_size_write(inode, status-\u003esize);\ndiff --git a/fs/afs/internal.h b/fs/afs/internal.h\nindex 290873bac89b3..f15dbbc8dd264 100644\n--- a/fs/afs/internal.h\n+++ b/fs/afs/internal.h\n@@ -1531,6 +1531,9 @@ extern struct key *afs_request_key(struct afs_cell *);\n extern struct key *afs_request_key_rcu(struct afs_cell *);\n extern int afs_check_permit(struct afs_vnode *, struct key *, afs_access_t *);\n extern int afs_permission(struct mnt_idmap *, struct inode *, int);\n+int afs_is_owned_by_me(struct mnt_idmap *idmap, struct inode *inode);\n+int afs_have_same_owner(struct mnt_idmap *idmap, struct inode *inode1,\n+\t\t\tstruct inode *inode2);\n extern void __exit afs_clean_up_permit_cache(void);\n \n /*\ndiff --git a/fs/afs/security.c b/fs/afs/security.c\nindex 6d00d62a65edf..7092f5acdcd6a 100644\n--- a/fs/afs/security.c\n+++ b/fs/afs/security.c\n@@ -510,6 +510,52 @@ int afs_permission(struct mnt_idmap *idmap, struct inode *inode,\n \treturn ret;\n }\n \n+/*\n+ * Determine if an inode is owned by 'me' - whatever that means for the\n+ * filesystem. In the case of AFS, this means that the file is owned by the\n+ * AFS user represented by the Rx Security Class token held in a key. Returns\n+ * 0 if owned by me, 1 if not; can also return an error.\n+ */\n+int afs_is_owned_by_me(struct mnt_idmap *idmap, struct inode *inode)\n+{\n+\tstruct afs_vnode *vnode = AFS_FS_I(inode);\n+\tafs_access_t access;\n+\tstruct key *key;\n+\tint ret;\n+\n+\tif (S_ISDIR(inode-\u003ei_mode))\n+\t\treturn 1; /* The ADMIN right check doesn't work for directories. */\n+\n+\tkey = afs_request_key(vnode-\u003evolume-\u003ecell);\n+\tif (IS_ERR(key))\n+\t\treturn PTR_ERR(key);\n+\n+\t/* Get the access rights for the key on this file. */\n+\tret = afs_check_permit(vnode, key, \u0026access);\n+\tif (ret \u003c 0)\n+\t\tgoto error;\n+\n+\t/* We get the ADMINISTER bit if we own the file. */\n+\tret = (access \u0026 AFS_ACE_ADMINISTER) ? 0 : 1;\n+error:\n+\tkey_put(key);\n+\treturn ret;\n+}\n+\n+/*\n+ * Determine if a file has the same owner as its parent - whatever that means\n+ * for the filesystem. In the case of AFS, this means comparing their AFS\n+ * UIDs. Returns 0 if same, 1 if not same; can also return an error.\n+ */\n+int afs_have_same_owner(struct mnt_idmap *idmap, struct inode *inode1,\n+\t\t\tstruct inode *inode2)\n+{\n+\tconst struct afs_vnode *vnode1 = AFS_FS_I(inode1);\n+\tconst struct afs_vnode *vnode2 = AFS_FS_I(inode2);\n+\n+\treturn vnode1-\u003estatus.owner != vnode2-\u003estatus.owner;\n+}\n+\n void __exit afs_clean_up_permit_cache(void)\n {\n \tint i;\ndiff --git a/fs/afs/symlink.c b/fs/afs/symlink.c\nindex 16b4823cb7b7e..ed834ba90e85a 100644\n--- a/fs/afs/symlink.c\n+++ b/fs/afs/symlink.c\n@@ -271,6 +271,8 @@ int afs_symlink_writepages(struct address_space *mapping,\n const struct inode_operations afs_symlink_inode_operations = {\n \t.get_link\t= afs_get_link,\n \t.readlink\t= afs_readlink,\n+\t.is_owned_by_me\t= afs_is_owned_by_me,\n+\t.have_same_owner = afs_have_same_owner,\n };\n \n const struct address_space_operations afs_symlink_aops = {\ndiff --git a/fs/attr.c b/fs/attr.c\nindex 4f437fabb7f0f..b7e8b5d49a9fe 100644\n--- a/fs/attr.c\n+++ b/fs/attr.c\n@@ -16,6 +16,7 @@\n #include \u003clinux/fcntl.h\u003e\n #include \u003clinux/filelock.h\u003e\n #include \u003clinux/security.h\u003e\n+#include \"internal.h\"\n \n /**\n * setattr_should_drop_sgid - determine whether the setgid bit needs to be\n@@ -91,19 +92,21 @@ EXPORT_SYMBOL(setattr_should_drop_suidgid);\n * permissions. On non-idmapped mounts or if permission checking is to be\n * performed on the raw inode simply pass @nop_mnt_idmap.\n */\n-static bool chown_ok(struct mnt_idmap *idmap,\n-\t\t const struct inode *inode, vfsuid_t ia_vfsuid)\n+static int chown_ok(struct mnt_idmap *idmap,\n+\t\t struct inode *inode, vfsuid_t ia_vfsuid)\n {\n \tvfsuid_t vfsuid = i_uid_into_vfsuid(idmap, inode);\n-\tif (vfsuid_eq_kuid(vfsuid, current_fsuid()) \u0026\u0026\n-\t vfsuid_eq(ia_vfsuid, vfsuid))\n-\t\treturn true;\n+\tint ret;\n+\n+\tret = vfs_inode_is_owned_by_me(idmap, inode);\n+\tif (ret \u003c= 0)\n+\t\treturn ret;\n \tif (capable_wrt_inode_uidgid(idmap, inode, CAP_CHOWN))\n-\t\treturn true;\n+\t\treturn 0;\n \tif (!vfsuid_valid(vfsuid) \u0026\u0026\n \t ns_capable(inode-\u003ei_sb-\u003es_user_ns, CAP_CHOWN))\n-\t\treturn true;\n-\treturn false;\n+\t\treturn 0;\n+\treturn -EPERM;\n }\n \n /**\n@@ -118,23 +121,27 @@ static bool chown_ok(struct mnt_idmap *idmap,\n * permissions. On non-idmapped mounts or if permission checking is to be\n * performed on the raw inode simply pass @nop_mnt_idmap.\n */\n-static bool chgrp_ok(struct mnt_idmap *idmap,\n-\t\t const struct inode *inode, vfsgid_t ia_vfsgid)\n+static int chgrp_ok(struct mnt_idmap *idmap,\n+\t\t struct inode *inode, vfsgid_t ia_vfsgid)\n {\n \tvfsgid_t vfsgid = i_gid_into_vfsgid(idmap, inode);\n-\tvfsuid_t vfsuid = i_uid_into_vfsuid(idmap, inode);\n-\tif (vfsuid_eq_kuid(vfsuid, current_fsuid())) {\n+\tint ret;\n+\n+\tret = vfs_inode_is_owned_by_me(idmap, inode);\n+\tif (ret \u003c 0)\n+\t\treturn ret;\n+\tif (ret == 0) {\n \t\tif (vfsgid_eq(ia_vfsgid, vfsgid))\n-\t\t\treturn true;\n+\t\t\treturn 0;\n \t\tif (vfsgid_in_group_p(ia_vfsgid))\n-\t\t\treturn true;\n+\t\t\treturn 0;\n \t}\n \tif (capable_wrt_inode_uidgid(idmap, inode, CAP_CHOWN))\n-\t\treturn true;\n+\t\treturn 0;\n \tif (!vfsgid_valid(vfsgid) \u0026\u0026\n \t ns_capable(inode-\u003ei_sb-\u003es_user_ns, CAP_CHOWN))\n-\t\treturn true;\n-\treturn false;\n+\t\treturn 0;\n+\treturn -EPERM;\n }\n \n /**\n@@ -163,6 +170,7 @@ int setattr_prepare(struct mnt_idmap *idmap, struct dentry *dentry,\n {\n \tstruct inode *inode = d_inode(dentry);\n \tunsigned int ia_valid = attr-\u003eia_valid;\n+\tint ret;\n \n \t/*\n \t * First check size constraints. These can't be overridden using\n@@ -189,14 +197,18 @@ int setattr_prepare(struct mnt_idmap *idmap, struct dentry *dentry,\n \t\tgoto kill_priv;\n \n \t/* Make sure a caller can chown. */\n-\tif ((ia_valid \u0026 ATTR_UID) \u0026\u0026\n-\t !chown_ok(idmap, inode, attr-\u003eia_vfsuid))\n-\t\treturn -EPERM;\n+\tif (ia_valid \u0026 ATTR_UID) {\n+\t\tret = chown_ok(idmap, inode, attr-\u003eia_vfsuid);\n+\t\tif (ret \u003c 0)\n+\t\t\treturn ret;\n+\t}\n \n \t/* Make sure caller can chgrp. */\n-\tif ((ia_valid \u0026 ATTR_GID) \u0026\u0026\n-\t !chgrp_ok(idmap, inode, attr-\u003eia_vfsgid))\n-\t\treturn -EPERM;\n+\tif (ia_valid \u0026 ATTR_GID) {\n+\t\tret = chgrp_ok(idmap, inode, attr-\u003eia_vfsgid);\n+\t\tif (ret \u003c 0)\n+\t\t\treturn ret;\n+\t}\n \n \t/* Make sure a caller can chmod. */\n \tif (ia_valid \u0026 ATTR_MODE) {\ndiff --git a/fs/coredump.c b/fs/coredump.c\nindex e68a76ff92a38..3f32e73aaeda2 100644\n--- a/fs/coredump.c\n+++ b/fs/coredump.c\n@@ -954,7 +954,7 @@ static bool coredump_file(struct core_name *cn, struct coredump_params *cprm,\n \t * filesystem.\n \t */\n \tidmap = file_mnt_idmap(file);\n-\tif (!vfsuid_eq_kuid(i_uid_into_vfsuid(idmap, inode), current_fsuid())) {\n+\tif (vfs_inode_is_owned_by_me(idmap, inode) != 0) {\n \t\tcoredump_report_failure(\"Core dump to %s aborted: cannot preserve file owner\", cn-\u003ecorename);\n \t\treturn false;\n \t}\ndiff --git a/fs/inode.c b/fs/inode.c\nindex 31c5b9ee3a81d..a7ec830ab683f 100644\n--- a/fs/inode.c\n+++ b/fs/inode.c\n@@ -2750,16 +2750,19 @@ EXPORT_SYMBOL(inode_init_owner);\n * On non-idmapped mounts or if permission checking is to be performed on the\n * raw inode simply pass @nop_mnt_idmap.\n */\n-bool inode_owner_or_capable(struct mnt_idmap *idmap,\n-\t\t\t const struct inode *inode)\n+bool inode_owner_or_capable(struct mnt_idmap *idmap, struct inode *inode)\n {\n \tvfsuid_t vfsuid;\n \tstruct user_namespace *ns;\n+\tint ret;\n \n-\tvfsuid = i_uid_into_vfsuid(idmap, inode);\n-\tif (vfsuid_eq_kuid(vfsuid, current_fsuid()))\n+\tret = vfs_inode_is_owned_by_me(idmap, inode);\n+\tif (ret == 0)\n \t\treturn true;\n+\tif (ret \u003c 0)\n+\t\treturn false;\n \n+\tvfsuid = i_uid_into_vfsuid(idmap, inode);\n \tns = current_user_ns();\n \tif (vfsuid_has_mapping(ns, vfsuid) \u0026\u0026 ns_capable(ns, CAP_FOWNER))\n \t\treturn true;\ndiff --git a/fs/internal.h b/fs/internal.h\nindex 355d93f922086..be45cc6388619 100644\n--- a/fs/internal.h\n+++ b/fs/internal.h\n@@ -51,6 +51,7 @@ extern int finish_clean_context(struct fs_context *fc);\n /*\n * namei.c\n */\n+int vfs_inode_is_owned_by_me(struct mnt_idmap *idmap, struct inode *inode);\n extern int filename_lookup(int dfd, struct filename *name, unsigned flags,\n \t\t\t struct path *path, const struct path *root);\n int filename_rmdir(int dfd, struct filename *name);\ndiff --git a/fs/locks.c b/fs/locks.c\nindex 6e4ff7fcec053..02f810dc9e7e7 100644\n--- a/fs/locks.c\n+++ b/fs/locks.c\n@@ -68,6 +68,7 @@\n #include \u003ctrace/events/filelock.h\u003e\n \n #include \u003clinux/uaccess.h\u003e\n+#include \"internal.h\"\n \n static struct file_lock *file_lock(struct file_lock_core *flc)\n {\n@@ -2136,10 +2137,12 @@ int\n vfs_setlease(struct file *filp, int arg, struct file_lease **lease, void **priv)\n {\n \tstruct inode *inode = file_inode(filp);\n-\tvfsuid_t vfsuid = i_uid_into_vfsuid(file_mnt_idmap(filp), inode);\n \tint error;\n \n-\tif ((!vfsuid_eq_kuid(vfsuid, current_fsuid())) \u0026\u0026 !capable(CAP_LEASE))\n+\terror = vfs_inode_is_owned_by_me(file_mnt_idmap(filp), inode);\n+\tif (error \u003c 0)\n+\t\treturn error;\n+\tif (error != 0 \u0026\u0026 !capable(CAP_LEASE))\n \t\treturn -EACCES;\n \terror = security_file_lock(filp, arg);\n \tif (error)\ndiff --git a/fs/namei.c b/fs/namei.c\nindex 19ce43c9a6e66..72f94d204f93f 100644\n--- a/fs/namei.c\n+++ b/fs/namei.c\n@@ -55,8 +55,8 @@\n * The new code replaces the old recursive symlink resolution with\n * an iterative one (in case of non-nested symlink chains). It does\n * this with calls to \u003cfs\u003e_follow_link().\n- * As a side effect, dir_namei(), _namei() and follow_link() are now \n- * replaced with a single function lookup_dentry() that can handle all \n+ * As a side effect, dir_namei(), _namei() and follow_link() are now\n+ * replaced with a single function lookup_dentry() that can handle all\n * the special cases of the former code.\n *\n * With the new dcache, the pathname is stored at each inode, at least as\n@@ -1255,6 +1255,72 @@ fs_initcall(init_fs_namei_sysctls);\n \n #endif /* CONFIG_SYSCTL */\n \n+/*\n+ * Determine if an inode is owned by the process (allowing for fsuid override),\n+ * returning 0 if so, 1 if not and a negative error code if there was a problem\n+ * making the determination.\n+ */\n+int vfs_inode_is_owned_by_me(struct mnt_idmap *idmap, struct inode *inode)\n+{\n+\tif (unlikely(inode-\u003ei_opflags \u0026 IOP_OWNERSHIP_OVERRIDE))\n+\t\treturn inode-\u003ei_op-\u003eis_owned_by_me(idmap, inode);\n+\tif (vfsuid_eq_kuid(i_uid_into_vfsuid(idmap, inode), current_fsuid()))\n+\t\treturn 0;\n+\treturn 1; /* Not same. */\n+}\n+\n+/*\n+ * Determine if an inode has the same owner as its parent directory, returning\n+ * 0 if so, 1 if not and a negative error code if there was a problem making\n+ * the determination.\n+ */\n+static int vfs_inode_and_dir_have_same_owner(struct mnt_idmap *idmap, struct inode *inode,\n+\t\t\t\t\t const struct nameidata *nd)\n+{\n+\tif (unlikely(inode-\u003ei_opflags \u0026 IOP_OWNERSHIP_OVERRIDE)) {\n+\t\tstruct dentry *parent;\n+\t\tstruct inode *dir;\n+\t\tint ret;\n+\n+\t\tif (inode != nd-\u003einode) {\n+\t\t\tdir = nd-\u003einode;\n+\t\t\tret = inode-\u003ei_op-\u003ehave_same_owner(idmap, inode, dir);\n+\t\t} else if (nd-\u003eflags \u0026 LOOKUP_RCU) {\n+\t\t\tparent = READ_ONCE(nd-\u003epath.dentry);\n+\t\t\tdir = READ_ONCE(parent-\u003ed_inode);\n+\t\t\tif (!dir)\n+\t\t\t\treturn -ECHILD;\n+\t\t\tret = inode-\u003ei_op-\u003ehave_same_owner(idmap, inode, dir);\n+\t\t} else {\n+\t\t\tparent = dget_parent(nd-\u003epath.dentry);\n+\t\t\tdir = parent-\u003ed_inode;\n+\t\t\tret = inode-\u003ei_op-\u003ehave_same_owner(idmap, inode, dir);\n+\t\t\tdput(parent);\n+\t\t}\n+\t\treturn ret;\n+\t}\n+\n+\tif (vfsuid_valid(nd-\u003edir_vfsuid) \u0026\u0026\n+\t vfsuid_eq(i_uid_into_vfsuid(idmap, inode), nd-\u003edir_vfsuid))\n+\t\treturn 0;\n+\treturn 1; /* Not same. */\n+}\n+\n+/*\n+ * Determine if two inodes have the same owner, returning 0 if so, 1 if not and\n+ * a negative error code if there was a problem making the determination.\n+ */\n+static int vfs_inodes_have_same_owner(struct mnt_idmap *idmap, struct inode *inode,\n+\t\t\t\t struct inode *dir)\n+{\n+\tif (unlikely(inode-\u003ei_opflags \u0026 IOP_OWNERSHIP_OVERRIDE))\n+\t\treturn inode-\u003ei_op-\u003ehave_same_owner(idmap, inode, dir);\n+\tif (vfsuid_eq(i_uid_into_vfsuid(idmap, inode),\n+\t\t i_uid_into_vfsuid(idmap, dir)))\n+\t\treturn 0;\n+\treturn 1; /* Not same. */\n+}\n+\n /**\n * may_follow_link - Check symlink following for unsafe situations\n * @nd: nameidata pathwalk data\n@@ -1271,27 +1337,28 @@ fs_initcall(init_fs_namei_sysctls);\n *\n * Returns 0 if following the symlink is allowed, -ve on error.\n */\n-static inline int may_follow_link(struct nameidata *nd, const struct inode *inode)\n+static inline int may_follow_link(struct nameidata *nd, struct inode *inode)\n {\n \tstruct mnt_idmap *idmap;\n-\tvfsuid_t vfsuid;\n+\tint ret;\n \n \tif (!sysctl_protected_symlinks)\n \t\treturn 0;\n \n-\tidmap = mnt_idmap(nd-\u003epath.mnt);\n-\tvfsuid = i_uid_into_vfsuid(idmap, inode);\n-\t/* Allowed if owner and follower match. */\n-\tif (vfsuid_eq_kuid(vfsuid, current_fsuid()))\n-\t\treturn 0;\n-\n \t/* Allowed if parent directory not sticky and world-writable. */\n \tif ((nd-\u003edir_mode \u0026 (S_ISVTX|S_IWOTH)) != (S_ISVTX|S_IWOTH))\n \t\treturn 0;\n \n+\tidmap = mnt_idmap(nd-\u003epath.mnt);\n+\t/* Allowed if owner and follower match. */\n+\tret = vfs_inode_is_owned_by_me(idmap, inode);\n+\tif (ret \u003c= 0)\n+\t\treturn ret;\n+\n \t/* Allowed if parent directory and link owner match. */\n-\tif (vfsuid_valid(nd-\u003edir_vfsuid) \u0026\u0026 vfsuid_eq(nd-\u003edir_vfsuid, vfsuid))\n-\t\treturn 0;\n+\tret = vfs_inode_and_dir_have_same_owner(idmap, inode, nd);\n+\tif (ret \u003c= 0)\n+\t\treturn ret;\n \n \tif (nd-\u003eflags \u0026 LOOKUP_RCU)\n \t\treturn -ECHILD;\n@@ -1389,12 +1456,12 @@ int may_linkat(struct mnt_idmap *idmap, const struct path *link)\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- * - sysctl_protected_fifos (or sysctl_protected_regular) is enabled\n- * - the file already exists\n- * - we are in a sticky directory\n- * - we don't own the file\n+ * - sysctl_protected_fifos (or sysctl_protected_regular) is enabled,\n+ * - the file already exists,\n+ * - we are in a sticky directory,\n+ * - the directory is world writable,\n+ * - we don't own the file and\n * - the owner of the directory doesn't own the file\n- * - the directory is world writable\n * If the sysctl_protected_fifos (or sysctl_protected_regular) is set to 2\n * the directory doesn't have to be world writable: being group writable will\n * be enough.\n@@ -1405,13 +1472,45 @@ int may_linkat(struct mnt_idmap *idmap, const struct path *link)\n * On non-idmapped mounts or if permission checking is to be performed on the\n * raw inode simply pass @nop_mnt_idmap.\n *\n+ * For a filesystem (e.g. a network filesystem) that has a separate ID space\n+ * and has foreign IDs (maybe even non-integer IDs), i_uid cannot be compared\n+ * to current_fsuid() and may not be directly comparable to another i_uid.\n+ * Instead, the filesystem is asked to perform the comparisons. With network\n+ * filesystems, there also exists the possibility of doing anonymous\n+ * operations and having anonymously-owned objects.\n+ *\n+ * We have the following scenarios:\n+ *\n+ *\tUSER\tDIR\tFILE\tFILE\tALLOWED\n+ *\t\tOWNER\tOWNER\tSTATE\n+ *\t=======\t=======\t=======\t=======\t=======\n+ *\tA\tA\t-\tNew\tYes\n+ *\tA\tA\tA\tExists\tYes\n+ *\tA\tA\tC\tExists\tNo\n+ *\tA\tB\t-\tNew\tYes\n+ *\tA\tB\tA\tExists\tYes, FO==U\n+ *\tA\tB\tB\tExists\tYes, FO==DO\n+ *\tA\tB\tC\tExists\tNo\n+ *\tA\tanon[1]\t-\tNew\tYes\n+ *\tA\tanon[1]\tA\tExists\tYes\n+ *\tA\tanon[1]\tC\tExists\tNo\n+ *\tanon\tA\t-\tNew\tYes\n+ *\tanon\tA\tA\tExists\tYes, FO==DO\n+ *\tanon\tanon[1]\t-\tNew\tYes\n+ *\tanon\tanon[1]\t-\tExists\tNo\n+ *\tanon\tA\tA\tExists\tYes, FO==DO\n+ *\tanon\tA\tC\tExists\tNo\n+ *\tanon\tA\tanon\tExists\tNo\n+ *\n+ * [1] Can anonymously-owned dirs be sticky?\n+ *\n * Returns 0 if the open is allowed, -ve on error.\n */\n static int may_create_in_sticky(struct mnt_idmap *idmap, struct nameidata *nd,\n-\t\t\t\tstruct inode *const inode)\n+\t\t\t\tstruct inode *inode)\n {\n \tumode_t dir_mode = nd-\u003edir_mode;\n-\tvfsuid_t dir_vfsuid = nd-\u003edir_vfsuid, i_vfsuid;\n+\tint ret;\n \n \tif (likely(!(dir_mode \u0026 S_ISVTX)))\n \t\treturn 0;\n@@ -1422,13 +1521,13 @@ static int may_create_in_sticky(struct mnt_idmap *idmap, struct nameidata *nd,\n \tif (S_ISFIFO(inode-\u003ei_mode) \u0026\u0026 !sysctl_protected_fifos)\n \t\treturn 0;\n \n-\ti_vfsuid = i_uid_into_vfsuid(idmap, inode);\n-\n-\tif (vfsuid_eq(i_vfsuid, dir_vfsuid))\n-\t\treturn 0;\n+\tret = vfs_inode_and_dir_have_same_owner(idmap, inode, nd);\n+\tif (ret \u003c= 0)\n+\t\treturn ret;\n \n-\tif (vfsuid_eq_kuid(i_vfsuid, current_fsuid()))\n-\t\treturn 0;\n+\tret = vfs_inode_is_owned_by_me(idmap, inode);\n+\tif (ret \u003c= 0)\n+\t\treturn ret;\n \n \tif (likely(dir_mode \u0026 0002)) {\n \t\taudit_log_path_denied(AUDIT_ANOM_CREAT, \"sticky_create\");\n@@ -3645,12 +3744,14 @@ EXPORT_SYMBOL(user_path_at);\n int __check_sticky(struct mnt_idmap *idmap, struct inode *dir,\n \t\t struct inode *inode)\n {\n-\tkuid_t fsuid = current_fsuid();\n+\tint ret;\n \n-\tif (vfsuid_eq_kuid(i_uid_into_vfsuid(idmap, inode), fsuid))\n-\t\treturn 0;\n-\tif (vfsuid_eq_kuid(i_uid_into_vfsuid(idmap, dir), fsuid))\n-\t\treturn 0;\n+\tret = vfs_inode_is_owned_by_me(idmap, inode);\n+\tif (ret \u003c= 0)\n+\t\treturn ret;\n+\tret = vfs_inodes_have_same_owner(idmap, inode, dir);\n+\tif (ret \u003c= 0)\n+\t\treturn ret;\n \treturn !capable_wrt_inode_uidgid(idmap, inode, CAP_FOWNER);\n }\n EXPORT_SYMBOL(__check_sticky);\ndiff --git a/fs/remap_range.c b/fs/remap_range.c\nindex 26afbbbfb10c2..9eee93c270012 100644\n--- a/fs/remap_range.c\n+++ b/fs/remap_range.c\n@@ -413,20 +413,22 @@ loff_t vfs_clone_file_range(struct file *file_in, loff_t pos_in,\n EXPORT_SYMBOL(vfs_clone_file_range);\n \n /* Check whether we are allowed to dedupe the destination file */\n-static bool may_dedupe_file(struct file *file)\n+static int may_dedupe_file(struct file *file)\n {\n \tstruct mnt_idmap *idmap = file_mnt_idmap(file);\n \tstruct inode *inode = file_inode(file);\n+\tint ret;\n \n \tif (capable(CAP_SYS_ADMIN))\n-\t\treturn true;\n+\t\treturn 0;\n \tif (file-\u003ef_mode \u0026 FMODE_WRITE)\n-\t\treturn true;\n-\tif (vfsuid_eq_kuid(i_uid_into_vfsuid(idmap, inode), current_fsuid()))\n-\t\treturn true;\n+\t\treturn 0;\n+\tret = vfs_inode_is_owned_by_me(idmap, inode);\n+\tif (ret \u003c= 0)\n+\t\treturn ret;\n \tif (!inode_permission(idmap, inode, MAY_WRITE))\n-\t\treturn true;\n-\treturn false;\n+\t\treturn 0;\n+\treturn -EPERM;\n }\n \n loff_t vfs_dedupe_file_range_one(struct file *src_file, loff_t src_pos,\n@@ -459,8 +461,8 @@ loff_t vfs_dedupe_file_range_one(struct file *src_file, loff_t src_pos,\n \tif (ret)\n \t\treturn ret;\n \n-\tret = -EPERM;\n-\tif (!may_dedupe_file(dst_file))\n+\tret = may_dedupe_file(dst_file);\n+\tif (ret \u003c 0)\n \t\tgoto out_drop_write;\n \n \tret = -EXDEV;\ndiff --git a/include/linux/fs.h b/include/linux/fs.h\nindex 50ce731a2b78f..8013c13a353d6 100644\n--- a/include/linux/fs.h\n+++ b/include/linux/fs.h\n@@ -628,6 +628,7 @@ is_uncached_acl(struct posix_acl *acl)\n #define IOP_CACHED_LINK\t\t0x0040\n #define IOP_FASTPERM_MAY_EXEC\t0x0080\n #define IOP_FLCTX\t\t0x0100\n+#define IOP_OWNERSHIP_OVERRIDE\t0x0200\t/* Use -\u003eis_owned_by_me() and -\u003ehave_same_owner() */\n \n /*\n * Inode state bits. Protected by inode-\u003ei_lock\n@@ -1755,8 +1756,7 @@ static inline bool file_write_not_started(const struct file *file)\n \treturn sb_write_not_started(file_inode(file)-\u003ei_sb);\n }\n \n-bool inode_owner_or_capable(struct mnt_idmap *idmap,\n-\t\t\t const struct inode *inode);\n+bool inode_owner_or_capable(struct mnt_idmap *idmap, struct inode *inode);\n \n /*\n * VFS helper functions..\n@@ -2036,6 +2036,9 @@ struct inode_operations {\n \t\t\t struct dentry *dentry, struct file_kattr *fa);\n \tint (*fileattr_get)(struct dentry *dentry, struct file_kattr *fa);\n \tstruct offset_ctx *(*get_offset_ctx)(struct inode *inode);\n+\tint (*is_owned_by_me)(struct mnt_idmap *idmap, struct inode *inode);\n+\tint (*have_same_owner)(struct mnt_idmap *idmap, struct inode *inode1,\n+\t\t\t struct inode *inode2);\n } ____cacheline_aligned;\n \n /* Did the driver provide valid mmap hook configuration? */\n"
} |
| 2/1 | 2026/07/29 18:49 | agent | patch-evaluator |
0mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"AFS_FS"
],
"FocusSymbols": [
"afs_is_owned_by_me",
"afs_have_same_owner",
"vfs_inode_is_owned_by_me",
"setattr_prepare",
"inode_owner_or_capable",
"vfs_setlease",
"__check_sticky",
"vfs_dedupe_file_range_one"
],
"Reasoning": "The patch introduces new VFS inode operations `is_owned_by_me` and `have_same_owner` to allow filesystems (like AFS) to override default UID-based ownership checks. It implements these for AFS and modifies several core VFS functions (chown, chgrp, lease, sticky bit checks, dedupe, symlink following) to use the new helpers. This is a functional change in core VFS and AFS that affects file permission checks 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). - Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed, ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU. - Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove) that are executed only during PCI hot-unplug or sysfs driver unbind operations. If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. Prefer non-static, non-inlined API entrypoint functions over internal static helper functions (which are inlined by the compiler and do not have distinct symbol addresses). 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit ac99a978e7180cb24d9ae151d752bcb97ec44dab
Author: syz-cluster <triage@syzkaller.com>
Date: Wed Jul 29 18:49:06 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/filesystems/vfs.rst b/Documentation/filesystems/vfs.rst
index 7c753148af888..893419e003eaf 100644
--- a/Documentation/filesystems/vfs.rst
+++ b/Documentation/filesystems/vfs.rst
@@ -445,6 +445,9 @@ As of kernel 2.6.22, the following members are defined:
struct dentry *dentry, struct file_kattr *fa);
int (*fileattr_get)(struct dentry *dentry, struct file_kattr *fa);
struct offset_ctx *(*get_offset_ctx)(struct inode *inode);
+ int (*is_owned_by_me)(struct mnt_idmap *idmap, struct inode *inode);
+ int (*have_same_owner)(struct mnt_idmap *idmap, struct inode *inode,
+ struct dentry *dentry);
};
Again, all methods are called without any locks being held, unless
@@ -634,6 +637,24 @@ otherwise noted.
filesystem must define this operation to use
simple_offset_dir_operations.
+``is_owned_by_me``
+ called to determine if the file can be considered to be 'owned' by
+ the owner of the process or if the process has a token that grants
+ it ownership privileges. If unset, the default is to compare i_uid
+ to current_fsuid() - but this may give incorrect results for some
+ network or plug-in block filesystems. For example, AFS determines
+ ownership entirely according to an obtained token and i_uid may not
+ even be from the same ID space as current_uid().
+
+``have_same_owner``
+ called to determine if an inode has the same owner as its immediate
+ parent on the path walked. If unset, the default is to simply
+ compare the i_uid of both. For example, AFS compares the owner IDs
+ of both - but these are a 64-bit values on some variants that might
+ not fit into a kuid_t and cifs has GUIDs that cannot be compared to
+ kuid_t.
+
+
The Address Space Object
========================
diff --git a/fs/afs/dir.c b/fs/afs/dir.c
index 6df56fe9163fd..ee2f5ec0b2c67 100644
--- a/fs/afs/dir.c
+++ b/fs/afs/dir.c
@@ -69,6 +69,8 @@ const struct inode_operations afs_dir_inode_operations = {
.permission = afs_permission,
.getattr = afs_getattr,
.setattr = afs_setattr,
+ .is_owned_by_me = afs_is_owned_by_me,
+ .have_same_owner = afs_have_same_owner,
};
const struct address_space_operations afs_dir_aops = {
diff --git a/fs/afs/file.c b/fs/afs/file.c
index 0467742bfeee3..ba44493ccaf6e 100644
--- a/fs/afs/file.c
+++ b/fs/afs/file.c
@@ -49,6 +49,8 @@ const struct inode_operations afs_file_inode_operations = {
.getattr = afs_getattr,
.setattr = afs_setattr,
.permission = afs_permission,
+ .is_owned_by_me = afs_is_owned_by_me,
+ .have_same_owner = afs_have_same_owner,
};
const struct address_space_operations afs_file_aops = {
diff --git a/fs/afs/inode.c b/fs/afs/inode.c
index 14f39a9bea6cf..af85a88d943dd 100644
--- a/fs/afs/inode.c
+++ b/fs/afs/inode.c
@@ -91,6 +91,7 @@ static int afs_inode_init_from_status(struct afs_operation *op,
inode->i_flags |= S_NOATIME;
inode->i_uid = make_kuid(&init_user_ns, status->owner);
inode->i_gid = make_kgid(&init_user_ns, status->group);
+ inode->i_opflags |= IOP_OWNERSHIP_OVERRIDE;
set_nlink(&vnode->netfs.inode, status->nlink);
i_size_write(inode, status->size);
diff --git a/fs/afs/internal.h b/fs/afs/internal.h
index 290873bac89b3..f15dbbc8dd264 100644
--- a/fs/afs/internal.h
+++ b/fs/afs/internal.h
@@ -1531,6 +1531,9 @@ extern struct key *afs_request_key(struct afs_cell *);
extern struct key *afs_request_key_rcu(struct afs_cell *);
extern int afs_check_permit(struct afs_vnode *, struct key *, afs_access_t *);
extern int afs_permission(struct mnt_idmap *, struct inode *, int);
+int afs_is_owned_by_me(struct mnt_idmap *idmap, struct inode *inode);
+int afs_have_same_owner(struct mnt_idmap *idmap, struct inode *inode1,
+ struct inode *inode2);
extern void __exit afs_clean_up_permit_cache(void);
/*
diff --git a/fs/afs/security.c b/fs/afs/security.c
index 6d00d62a65edf..7092f5acdcd6a 100644
--- a/fs/afs/security.c
+++ b/fs/afs/security.c
@@ -510,6 +510,52 @@ int afs_permission(struct mnt_idmap *idmap, struct inode *inode,
return ret;
}
+/*
+ * Determine if an inode is owned by 'me' - whatever that means for the
+ * filesystem. In the case of AFS, this means that the file is owned by the
+ * AFS user represented by the Rx Security Class token held in a key. Returns
+ * 0 if owned by me, 1 if not; can also return an error.
+ */
+int afs_is_owned_by_me(struct mnt_idmap *idmap, struct inode *inode)
+{
+ struct afs_vnode *vnode = AFS_FS_I(inode);
+ afs_access_t access;
+ struct key *key;
+ int ret;
+
+ if (S_ISDIR(inode->i_mode))
+ return 1; /* The ADMIN right check doesn't work for directories. */
+
+ key = afs_request_key(vnode->volume->cell);
+ if (IS_ERR(key))
+ return PTR_ERR(key);
+
+ /* Get the access rights for the key on this file. */
+ ret = afs_check_permit(vnode, key, &access);
+ if (ret < 0)
+ goto error;
+
+ /* We get the ADMINISTER bit if we own the file. */
+ ret = (access & AFS_ACE_ADMINISTER) ? 0 : 1;
+error:
+ key_put(key);
+ return ret;
+}
+
+/*
+ * Determine if a file has the same owner as its parent - whatever that means
+ * for the filesystem. In the case of AFS, this means comparing their AFS
+ * UIDs. Returns 0 if same, 1 if not same; can also return an error.
+ */
+int afs_have_same_owner(struct mnt_idmap *idmap, struct inode *inode1,
+ struct inode *inode2)
+{
+ const struct afs_vnode *vnode1 = AFS_FS_I(inode1);
+ const struct afs_vnode *vnode2 = AFS_FS_I(inode2);
+
+ return vnode1->status.owner != vnode2->status.owner;
+}
+
void __exit afs_clean_up_permit_cache(void)
{
int i;
diff --git a/fs/afs/symlink.c b/fs/afs/symlink.c
index 16b4823cb7b7e..ed834ba90e85a 100644
--- a/fs/afs/symlink.c
+++ b/fs/afs/symlink.c
@@ -271,6 +271,8 @@ int afs_symlink_writepages(struct address_space *mapping,
const struct inode_operations afs_symlink_inode_operations = {
.get_link = afs_get_link,
.readlink = afs_readlink,
+ .is_owned_by_me = afs_is_owned_by_me,
+ .have_same_owner = afs_have_same_owner,
};
const struct address_space_operations afs_symlink_aops = {
diff --git a/fs/attr.c b/fs/attr.c
index 4f437fabb7f0f..b7e8b5d49a9fe 100644
--- a/fs/attr.c
+++ b/fs/attr.c
@@ -16,6 +16,7 @@
#include <linux/fcntl.h>
#include <linux/filelock.h>
#include <linux/security.h>
+#include "internal.h"
/**
* setattr_should_drop_sgid - determine whether the setgid bit needs to be
@@ -91,19 +92,21 @@ EXPORT_SYMBOL(setattr_should_drop_suidgid);
* permissions. On non-idmapped mounts or if permission checking is to be
* performed on the raw inode simply pass @nop_mnt_idmap.
*/
-static bool chown_ok(struct mnt_idmap *idmap,
- const struct inode *inode, vfsuid_t ia_vfsuid)
+static int chown_ok(struct mnt_idmap *idmap,
+ struct inode *inode, vfsuid_t ia_vfsuid)
{
vfsuid_t vfsuid = i_uid_into_vfsuid(idmap, inode);
- if (vfsuid_eq_kuid(vfsuid, current_fsuid()) &&
- vfsuid_eq(ia_vfsuid, vfsuid))
- return true;
+ int ret;
+
+ ret = vfs_inode_is_owned_by_me(idmap, inode);
+ if (ret <= 0)
+ return ret;
if (capable_wrt_inode_uidgid(idmap, inode, CAP_CHOWN))
- return true;
+ return 0;
if (!vfsuid_valid(vfsuid) &&
ns_capable(inode->i_sb->s_user_ns, CAP_CHOWN))
- return true;
- return false;
+ return 0;
+ return -EPERM;
}
/**
@@ -118,23 +121,27 @@ static bool chown_ok(struct mnt_idmap *idmap,
* permissions. On non-idmapped mounts or if permission checking is to be
* performed on the raw inode simply pass @nop_mnt_idmap.
*/
-static bool chgrp_ok(struct mnt_idmap *idmap,
- const struct inode *inode, vfsgid_t ia_vfsgid)
+static int chgrp_ok(struct mnt_idmap *idmap,
+ struct inode *inode, vfsgid_t ia_vfsgid)
{
vfsgid_t vfsgid = i_gid_into_vfsgid(idmap, inode);
- vfsuid_t vfsuid = i_uid_into_vfsuid(idmap, inode);
- if (vfsuid_eq_kuid(vfsuid, current_fsuid())) {
+ int ret;
+
+ ret = vfs_inode_is_owned_by_me(idmap, inode);
+ if (ret < 0)
+ return ret;
+ if (ret == 0) {
if (vfsgid_eq(ia_vfsgid, vfsgid))
- return true;
+ return 0;
if (vfsgid_in_group_p(ia_vfsgid))
- return true;
+ return 0;
}
if (capable_wrt_inode_uidgid(idmap, inode, CAP_CHOWN))
- return true;
+ return 0;
if (!vfsgid_valid(vfsgid) &&
ns_capable(inode->i_sb->s_user_ns, CAP_CHOWN))
- return true;
- return false;
+ return 0;
+ return -EPERM;
}
/**
@@ -163,6 +170,7 @@ int setattr_prepare(struct mnt_idmap *idmap, struct dentry *dentry,
{
struct inode *inode = d_inode(dentry);
unsigned int ia_valid = attr->ia_valid;
+ int ret;
/*
* First check size constraints. These can't be overridden using
@@ -189,14 +197,18 @@ int setattr_prepare(struct mnt_idmap *idmap, struct dentry *dentry,
goto kill_priv;
/* Make sure a caller can chown. */
- if ((ia_valid & ATTR_UID) &&
- !chown_ok(idmap, inode, attr->ia_vfsuid))
- return -EPERM;
+ if (ia_valid & ATTR_UID) {
+ ret = chown_ok(idmap, inode, attr->ia_vfsuid);
+ if (ret < 0)
+ return ret;
+ }
/* Make sure caller can chgrp. */
- if ((ia_valid & ATTR_GID) &&
- !chgrp_ok(idmap, inode, attr->ia_vfsgid))
- return -EPERM;
+ if (ia_valid & ATTR_GID) {
+ ret = chgrp_ok(idmap, inode, attr->ia_vfsgid);
+ if (ret < 0)
+ return ret;
+ }
/* Make sure a caller can chmod. */
if (ia_valid & ATTR_MODE) {
diff --git a/fs/coredump.c b/fs/coredump.c
index e68a76ff92a38..3f32e73aaeda2 100644
--- a/fs/coredump.c
+++ b/fs/coredump.c
@@ -954,7 +954,7 @@ static bool coredump_file(struct core_name *cn, struct coredump_params *cprm,
* filesystem.
*/
idmap = file_mnt_idmap(file);
- if (!vfsuid_eq_kuid(i_uid_into_vfsuid(idmap, inode), current_fsuid())) {
+ if (vfs_inode_is_owned_by_me(idmap, inode) != 0) {
coredump_report_failure("Core dump to %s aborted: cannot preserve file owner", cn->corename);
return false;
}
diff --git a/fs/inode.c b/fs/inode.c
index 31c5b9ee3a81d..a7ec830ab683f 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -2750,16 +2750,19 @@ EXPORT_SYMBOL(inode_init_owner);
* On non-idmapped mounts or if permission checking is to be performed on the
* raw inode simply pass @nop_mnt_idmap.
*/
-bool inode_owner_or_capable(struct mnt_idmap *idmap,
- const struct inode *inode)
+bool inode_owner_or_capable(struct mnt_idmap *idmap, struct inode *inode)
{
vfsuid_t vfsuid;
struct user_namespace *ns;
+ int ret;
- vfsuid = i_uid_into_vfsuid(idmap, inode);
- if (vfsuid_eq_kuid(vfsuid, current_fsuid()))
+ ret = vfs_inode_is_owned_by_me(idmap, inode);
+ if (ret == 0)
return true;
+ if (ret < 0)
+ return false;
+ vfsuid = i_uid_into_vfsuid(idmap, inode);
ns = current_user_ns();
if (vfsuid_has_mapping(ns, vfsuid) && ns_capable(ns, CAP_FOWNER))
return true;
diff --git a/fs/internal.h b/fs/internal.h
index 355d93f922086..be45cc6388619 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -51,6 +51,7 @@ extern int finish_clean_context(struct fs_context *fc);
/*
* namei.c
*/
+int vfs_inode_is_owned_by_me(struct mnt_idmap *idmap, struct inode *inode);
extern int filename_lookup(int dfd, struct filename *name, unsigned flags,
struct path *path, const struct path *root);
int filename_rmdir(int dfd, struct filename *name);
diff --git a/fs/locks.c b/fs/locks.c
index 6e4ff7fcec053..02f810dc9e7e7 100644
--- a/fs/locks.c
+++ b/fs/locks.c
@@ -68,6 +68,7 @@
#include <trace/events/filelock.h>
#include <linux/uaccess.h>
+#include "internal.h"
static struct file_lock *file_lock(struct file_lock_core *flc)
{
@@ -2136,10 +2137,12 @@ int
vfs_setlease(struct file *filp, int arg, struct file_lease **lease, void **priv)
{
struct inode *inode = file_inode(filp);
- vfsuid_t vfsuid = i_uid_into_vfsuid(file_mnt_idmap(filp), inode);
int error;
- if ((!vfsuid_eq_kuid(vfsuid, current_fsuid())) && !capable(CAP_LEASE))
+ error = vfs_inode_is_owned_by_me(file_mnt_idmap(filp), inode);
+ if (error < 0)
+ return error;
+ if (error != 0 && !capable(CAP_LEASE))
return -EACCES;
error = security_file_lock(filp, arg);
if (error)
diff --git a/fs/namei.c b/fs/namei.c
index 19ce43c9a6e66..72f94d204f93f 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -55,8 +55,8 @@
* The new code replaces the old recursive symlink resolution with
* an iterative one (in case of non-nested symlink chains). It does
* this with calls to <fs>_follow_link().
- * As a side effect, dir_namei(), _namei() and follow_link() are now
- * replaced with a single function lookup_dentry() that can handle all
+ * As a side effect, dir_namei(), _namei() and follow_link() are now
+ * replaced with a single function lookup_dentry() that can handle all
* the special cases of the former code.
*
* With the new dcache, the pathname is stored at each inode, at least as
@@ -1255,6 +1255,72 @@ fs_initcall(init_fs_namei_sysctls);
#endif /* CONFIG_SYSCTL */
+/*
+ * Determine if an inode is owned by the process (allowing for fsuid override),
+ * returning 0 if so, 1 if not and a negative error code if there was a problem
+ * making the determination.
+ */
+int vfs_inode_is_owned_by_me(struct mnt_idmap *idmap, struct inode *inode)
+{
+ if (unlikely(inode->i_opflags & IOP_OWNERSHIP_OVERRIDE))
+ return inode->i_op->is_owned_by_me(idmap, inode);
+ if (vfsuid_eq_kuid(i_uid_into_vfsuid(idmap, inode), current_fsuid()))
+ return 0;
+ return 1; /* Not same. */
+}
+
+/*
+ * Determine if an inode has the same owner as its parent directory, returning
+ * 0 if so, 1 if not and a negative error code if there was a problem making
+ * the determination.
+ */
+static int vfs_inode_and_dir_have_same_owner(struct mnt_idmap *idmap, struct inode *inode,
+ const struct nameidata *nd)
+{
+ if (unlikely(inode->i_opflags & IOP_OWNERSHIP_OVERRIDE)) {
+ struct dentry *parent;
+ struct inode *dir;
+ int ret;
+
+ if (inode != nd->inode) {
+ dir = nd->inode;
+ ret = inode->i_op->have_same_owner(idmap, inode, dir);
+ } else if (nd->flags & LOOKUP_RCU) {
+ parent = READ_ONCE(nd->path.dentry);
+ dir = READ_ONCE(parent->d_inode);
+ if (!dir)
+ return -ECHILD;
+ ret = inode->i_op->have_same_owner(idmap, inode, dir);
+ } else {
+ parent = dget_parent(nd->path.dentry);
+ dir = parent->d_inode;
+ ret = inode->i_op->have_same_owner(idmap, inode, dir);
+ dput(parent);
+ }
+ return ret;
+ }
+
+ if (vfsuid_valid(nd->dir_vfsuid) &&
+ vfsuid_eq(i_uid_into_vfsuid(idmap, inode), nd->dir_vfsuid))
+ return 0;
+ return 1; /* Not same. */
+}
+
+/*
+ * Determine if two inodes have the same owner, returning 0 if so, 1 if not and
+ * a negative error code if there was a problem making the determination.
+ */
+static int vfs_inodes_have_same_owner(struct mnt_idmap *idmap, struct inode *inode,
+ struct inode *dir)
+{
+ if (unlikely(inode->i_opflags & IOP_OWNERSHIP_OVERRIDE))
+ return inode->i_op->have_same_owner(idmap, inode, dir);
+ if (vfsuid_eq(i_uid_into_vfsuid(idmap, inode),
+ i_uid_into_vfsuid(idmap, dir)))
+ return 0;
+ return 1; /* Not same. */
+}
+
/**
* may_follow_link - Check symlink following for unsafe situations
* @nd: nameidata pathwalk data
@@ -1271,27 +1337,28 @@ fs_initcall(init_fs_namei_sysctls);
*
* Returns 0 if following the symlink is allowed, -ve on error.
*/
-static inline int may_follow_link(struct nameidata *nd, const struct inode *inode)
+static inline int may_follow_link(struct nameidata *nd, struct inode *inode)
{
struct mnt_idmap *idmap;
- vfsuid_t vfsuid;
+ int ret;
if (!sysctl_protected_symlinks)
return 0;
- idmap = mnt_idmap(nd->path.mnt);
- vfsuid = i_uid_into_vfsuid(idmap, inode);
- /* Allowed if owner and follower match. */
- if (vfsuid_eq_kuid(vfsuid, current_fsuid()))
- return 0;
-
/* Allowed if parent directory not sticky and world-writable. */
if ((nd->dir_mode & (S_ISVTX|S_IWOTH)) != (S_ISVTX|S_IWOTH))
return 0;
+ idmap = mnt_idmap(nd->path.mnt);
+ /* Allowed if owner and follower match. */
+ ret = vfs_inode_is_owned_by_me(idmap, inode);
+ if (ret <= 0)
+ return ret;
+
/* Allowed if parent directory and link owner match. */
- if (vfsuid_valid(nd->dir_vfsuid) && vfsuid_eq(nd->dir_vfsuid, vfsuid))
- return 0;
+ ret = vfs_inode_and_dir_have_same_owner(idmap, inode, nd);
+ if (ret <= 0)
+ return ret;
if (nd->flags & LOOKUP_RCU)
return -ECHILD;
@@ -1389,12 +1456,12 @@ int may_linkat(struct mnt_idmap *idmap, const struct path *link)
* @inode: the inode of the file to open
*
* Block an O_CREAT open of a FIFO (or a regular file) when:
- * - sysctl_protected_fifos (or sysctl_protected_regular) is enabled
- * - the file already exists
- * - we are in a sticky directory
- * - we don't own the file
+ * - sysctl_protected_fifos (or sysctl_protected_regular) is enabled,
+ * - the file already exists,
+ * - we are in a sticky directory,
+ * - the directory is world writable,
+ * - we don't own the file and
* - the owner of the directory doesn't own the file
- * - the directory is world writable
* If the sysctl_protected_fifos (or sysctl_protected_regular) is set to 2
* the directory doesn't have to be world writable: being group writable will
* be enough.
@@ -1405,13 +1472,45 @@ int may_linkat(struct mnt_idmap *idmap, const struct path *link)
* On non-idmapped mounts or if permission checking is to be performed on the
* raw inode simply pass @nop_mnt_idmap.
*
+ * For a filesystem (e.g. a network filesystem) that has a separate ID space
+ * and has foreign IDs (maybe even non-integer IDs), i_uid cannot be compared
+ * to current_fsuid() and may not be directly comparable to another i_uid.
+ * Instead, the filesystem is asked to perform the comparisons. With network
+ * filesystems, there also exists the possibility of doing anonymous
+ * operations and having anonymously-owned objects.
+ *
+ * We have the following scenarios:
+ *
+ * USER DIR FILE FILE ALLOWED
+ * OWNER OWNER STATE
+ * ======= ======= ======= ======= =======
+ * A A - New Yes
+ * A A A Exists Yes
+ * A A C Exists No
+ * A B - New Yes
+ * A B A Exists Yes, FO==U
+ * A B B Exists Yes, FO==DO
+ * A B C Exists No
+ * A anon[1] - New Yes
+ * A anon[1] A Exists Yes
+ * A anon[1] C Exists No
+ * anon A - New Yes
+ * anon A A Exists Yes, FO==DO
+ * anon anon[1] - New Yes
+ * anon anon[1] - Exists No
+ * anon A A Exists Yes, FO==DO
+ * anon A C Exists No
+ * anon A anon Exists No
+ *
+ * [1] Can anonymously-owned dirs be sticky?
+ *
* Returns 0 if the open is allowed, -ve on error.
*/
static int may_create_in_sticky(struct mnt_idmap *idmap, struct nameidata *nd,
- struct inode *const inode)
+ struct inode *inode)
{
umode_t dir_mode = nd->dir_mode;
- vfsuid_t dir_vfsuid = nd->dir_vfsuid, i_vfsuid;
+ int ret;
if (likely(!(dir_mode & S_ISVTX)))
return 0;
@@ -1422,13 +1521,13 @@ static int may_create_in_sticky(struct mnt_idmap *idmap, struct nameidata *nd,
if (S_ISFIFO(inode->i_mode) && !sysctl_protected_fifos)
return 0;
- i_vfsuid = i_uid_into_vfsuid(idmap, inode);
-
- if (vfsuid_eq(i_vfsuid, dir_vfsuid))
- return 0;
+ ret = vfs_inode_and_dir_have_same_owner(idmap, inode, nd);
+ if (ret <= 0)
+ return ret;
- if (vfsuid_eq_kuid(i_vfsuid, current_fsuid()))
- return 0;
+ ret = vfs_inode_is_owned_by_me(idmap, inode);
+ if (ret <= 0)
+ return ret;
if (likely(dir_mode & 0002)) {
audit_log_path_denied(AUDIT_ANOM_CREAT, "sticky_create");
@@ -3645,12 +3744,14 @@ EXPORT_SYMBOL(user_path_at);
int __check_sticky(struct mnt_idmap *idmap, struct inode *dir,
struct inode *inode)
{
- kuid_t fsuid = current_fsuid();
+ int ret;
- if (vfsuid_eq_kuid(i_uid_into_vfsuid(idmap, inode), fsuid))
- return 0;
- if (vfsuid_eq_kuid(i_uid_into_vfsuid(idmap, dir), fsuid))
- return 0;
+ ret = vfs_inode_is_owned_by_me(idmap, inode);
+ if (ret <= 0)
+ return ret;
+ ret = vfs_inodes_have_same_owner(idmap, inode, dir);
+ if (ret <= 0)
+ return ret;
return !capable_wrt_inode_uidgid(idmap, inode, CAP_FOWNER);
}
EXPORT_SYMBOL(__check_sticky);
diff --git a/fs/remap_range.c b/fs/remap_range.c
index 26afbbbfb10c2..9eee93c270012 100644
--- a/fs/remap_range.c
+++ b/fs/remap_range.c
@@ -413,20 +413,22 @@ loff_t vfs_clone_file_range(struct file *file_in, loff_t pos_in,
EXPORT_SYMBOL(vfs_clone_file_range);
/* Check whether we are allowed to dedupe the destination file */
-static bool may_dedupe_file(struct file *file)
+static int may_dedupe_file(struct file *file)
{
struct mnt_idmap *idmap = file_mnt_idmap(file);
struct inode *inode = file_inode(file);
+ int ret;
if (capable(CAP_SYS_ADMIN))
- return true;
+ return 0;
if (file->f_mode & FMODE_WRITE)
- return true;
- if (vfsuid_eq_kuid(i_uid_into_vfsuid(idmap, inode), current_fsuid()))
- return true;
+ return 0;
+ ret = vfs_inode_is_owned_by_me(idmap, inode);
+ if (ret <= 0)
+ return ret;
if (!inode_permission(idmap, inode, MAY_WRITE))
- return true;
- return false;
+ return 0;
+ return -EPERM;
}
loff_t vfs_dedupe_file_range_one(struct file *src_file, loff_t src_pos,
@@ -459,8 +461,8 @@ loff_t vfs_dedupe_file_range_one(struct file *src_file, loff_t src_pos,
if (ret)
return ret;
- ret = -EPERM;
- if (!may_dedupe_file(dst_file))
+ ret = may_dedupe_file(dst_file);
+ if (ret < 0)
goto out_drop_write;
ret = -EXDEV;
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 50ce731a2b78f..8013c13a353d6 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -628,6 +628,7 @@ is_uncached_acl(struct posix_acl *acl)
#define IOP_CACHED_LINK 0x0040
#define IOP_FASTPERM_MAY_EXEC 0x0080
#define IOP_FLCTX 0x0100
+#define IOP_OWNERSHIP_OVERRIDE 0x0200 /* Use ->is_owned_by_me() and ->have_same_owner() */
/*
* Inode state bits. Protected by inode->i_lock
@@ -1755,8 +1756,7 @@ static inline bool file_write_not_started(const struct file *file)
return sb_write_not_started(file_inode(file)->i_sb);
}
-bool inode_owner_or_capable(struct mnt_idmap *idmap,
- const struct inode *inode);
+bool inode_owner_or_capable(struct mnt_idmap *idmap, struct inode *inode);
/*
* VFS helper functions..
@@ -2036,6 +2036,9 @@ struct inode_operations {
struct dentry *dentry, struct file_kattr *fa);
int (*fileattr_get)(struct dentry *dentry, struct file_kattr *fa);
struct offset_ctx *(*get_offset_ctx)(struct inode *inode);
+ int (*is_owned_by_me)(struct mnt_idmap *idmap, struct inode *inode);
+ int (*have_same_owner)(struct mnt_idmap *idmap, struct inode *inode1,
+ struct inode *inode2);
} ____cacheline_aligned;
/* Did the driver provide valid mmap hook configuration? */
|
| 3/2 | 2026/07/29 18:49 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 11163 output: 23 thoughts: 600 Thoughts: **Analyzing the Impact of the New Inode Operations** Okay, so I'm looking at this patch and my initial thought is that it's introducing some pretty fundamental changes to how the VFS layer handles file ownership, specifically for filesystems like AFS that use tokens instead of the traditional UID/GID model. We've got two new `inode_operations`: `is_owned_by_me` and `have_same_owner`. These are crucial because they override the standard ownership checks within the VFS, which is key for AFS. The patch implements these in AFS as `afs_is_owned_by_me` and `afs_have_same_owner`. The big picture here is that a whole slew of VFS functions are being modified to use these new helpers: `vfs_inode_is_owned_by_me` and `vfs_inode_and_dir_have_same_owner` and `vfs_inodes_have_same_owner`. It's touching core operations like `chown`, `chgrp`, `coredump`, lease management, symlink following, sticky bit checks, and even file deduplication! This is a pretty significant alteration to file system behavior and definitely warrants some serious fuzzing. My primary focus now is pinpointing the most vulnerable areas for fuzzing. We have three newly introduced VFS helpers: `vfs_inode_is_owned_by_me`, `vfs_inode_and_dir_have_same_owner` and `vfs_inodes_have_same_owner`. On the AFS side, we've got `afs_is_owned_by_me` and `afs_have_same_owner`. These are the functions to target first. Beyond that, the patched VFS functions are where the real fun lies: `setattr_prepare`, which calls `chown_ok` and `chgrp_ok`, `coredump_file` (not a primary fuzzing target directly, but it provides a pathway), `inode_owner_or_capable`, `vfs_setlease`, and `may_follow_link`, `may_create_in_sticky`, and `__check_sticky`. The dedupe implementation via `vfs_dedupe_file_range_one` (which calls `may_dedupe_file`) also needs a look. `may_follow_link` and `may_create_in_sticky` are marked as static functions so my team will need to pay close attention to the context of these functions. I need to confirm there are no conditional compilation shenanigans here, so I'll check for any necessary `CONFIG` options. AFS looks like it's gated by `CONFIG_AFS_FS`, but the VFS changes are always enabled. Finally, I'll do a quick search in the kernel source tree for `afs_is_owned_by_me` and `afs_have_same_owner` just to be doubly sure these functions are indeed in the codebase. Then it's time to start crafting some fuzzing harnesses. This could be interesting. |
| 4/2 | 2026/07/29 18:49 | tool | grepper |
0mArgs:{
"Expression": "afs_is_owned_by_me"
}Results: {
"Output": "fs/afs/dir.c=60=const struct inode_operations afs_dir_inode_operations = {\n--\nfs/afs/dir.c-71-\t.setattr\t= afs_setattr,\nfs/afs/dir.c:72:\t.is_owned_by_me\t= afs_is_owned_by_me,\nfs/afs/dir.c-73-\t.have_same_owner = afs_have_same_owner,\n--\nfs/afs/file.c=48=const struct inode_operations afs_file_inode_operations = {\n--\nfs/afs/file.c-51-\t.permission\t= afs_permission,\nfs/afs/file.c:52:\t.is_owned_by_me\t= afs_is_owned_by_me,\nfs/afs/file.c-53-\t.have_same_owner = afs_have_same_owner,\n--\nfs/afs/internal.h=1533=extern int afs_permission(struct mnt_idmap *, struct inode *, int);\nfs/afs/internal.h:1534:int afs_is_owned_by_me(struct mnt_idmap *idmap, struct inode *inode);\nfs/afs/internal.h-1535-int afs_have_same_owner(struct mnt_idmap *idmap, struct inode *inode1,\n--\nfs/afs/security.c=431=int afs_permission(struct mnt_idmap *idmap, struct inode *inode,\n--\nfs/afs/security.c-518- */\nfs/afs/security.c:519:int afs_is_owned_by_me(struct mnt_idmap *idmap, struct inode *inode)\nfs/afs/security.c-520-{\n--\nfs/afs/symlink.c=271=const struct inode_operations afs_symlink_inode_operations = {\n--\nfs/afs/symlink.c-273-\t.readlink\t= afs_readlink,\nfs/afs/symlink.c:274:\t.is_owned_by_me\t= afs_is_owned_by_me,\nfs/afs/symlink.c-275-\t.have_same_owner = afs_have_same_owner,\n"
} |
| 5/2 | 2026/07/29 18:49 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 13040 output: 19 thoughts: 111 Thoughts: **Analyzing File Ownership and Access Control Functions** Okay, let's break down these functions. I see a focused set here related to file ownership and access control, specifically within a virtual filesystem and AFS context. The core seems to be around verifying permissions, setting attributes, and potentially handling file leases or deduplication. I'm noting the functions carefully: `afs_is_owned_by_me`, `afs_have_same_owner`, `vfs_inode_is_owned_by_me`, `setattr_prepare`, `inode_owner_or_capable`, `vfs_setlease`, `__check_sticky`, and `vfs_dedupe_file_range_one`. The naming conventions suggest a clear distinction between AFS-specific logic and more general VFS functions. My initial instinct is that this is dealing with a system that has a more complex file-ownership management system than a basic system. The presence of `afs_*` functions is the key thing I am seeing here. I will look to see if AFS_FS is the only config present because that suggests that this code is part of a larger file system with a number of different file systems that have differing security implications. |
| 6/2 | 2026/07/29 18:49 | tool | grepper |
0mArgs:{
"Expression": "config AFS_FS"
}Results: {
"Output": "fs/afs/Kconfig-1-# SPDX-License-Identifier: GPL-2.0-only\nfs/afs/Kconfig:2:config AFS_FS\nfs/afs/Kconfig-3-\ttristate \"Andrew File System support (AFS)\"\n--\nfs/afs/Kconfig=17=config AFS_DEBUG\n--\nfs/afs/Kconfig-26-\nfs/afs/Kconfig:27:config AFS_FSCACHE\nfs/afs/Kconfig-28-\tbool \"Provide AFS client caching support\"\n"
} |
| 7/2 | 2026/07/29 18:49 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 13566 output: 32 thoughts: 20 Thoughts: Here's a summary of my thought process: **Kconfig Dependency Check for AFS_FS** Okay, so I need to figure out the dependencies related to the `AFS_FS` filesystem feature in the kernel configuration. The first place I'm going to check is `fs/afs/Kconfig`. That's where I expect to find the relevant configuration options and, critically, any dependencies on other features or modules. This is the standard place to start when digging into kernel build configurations. I'm looking for clues about what needs to be enabled or defined before `AFS_FS` can be properly configured. |
| 8/2 | 2026/07/29 18:49 | tool | read-file |
0mArgs:{
"File": "fs/afs/Kconfig",
"FirstLine": 1,
"LineCount": 20
}Results: {
"Contents": " 1:\t# SPDX-License-Identifier: GPL-2.0-only\n 2:\tconfig AFS_FS\n 3:\t\ttristate \"Andrew File System support (AFS)\"\n 4:\t\tdepends on INET\n 5:\t\tselect AF_RXRPC\n 6:\t\tselect DNS_RESOLVER\n 7:\t\tselect NETFS_SUPPORT\n 8:\t\tselect CRYPTO_KRB5\n 9:\t\thelp\n 10:\t\t If you say Y here, you will get an experimental Andrew File System\n 11:\t\t driver. It currently only supports unsecured read-only AFS access.\n 12:\t\n 13:\t\t See \u003cfile:Documentation/filesystems/afs.rst\u003e for more information.\n 14:\t\n 15:\t\t If unsure, say N.\n 16:\t\n 17:\tconfig AFS_DEBUG\n 18:\t\tbool \"AFS dynamic debugging\"\n 19:\t\tdepends on AFS_FS\n 20:\t\thelp\n"
} |
| 9/2 | 2026/07/29 18:49 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 14003 output: 30 thoughts: 135 Thoughts: **My Initial Thoughts on this Configuration and Focus Symbols** Okay, so I'm looking at this `AFS_FS` configuration, and it's contingent on `INET`, which makes sense. I see the dependencies at a high level. Now, I need to zero in on these symbols. I've got a list of focus symbols here. Let's see... we've got a mix of things related to ownership checks within the AFS file system - `afs_is_owned_by_me`, `afs_have_same_owner`, `vfs_inode_is_owned_by_me`, as well as more general VFS functionality like `setattr_prepare`, `inode_owner_or_capable`, `vfs_setlease`, `__check_sticky`, and `vfs_dedupe_file_range_one`. Given the nature of AFS, which often involves distributed ownership and potentially more complex access control, these seem like relevant pieces. I am particularly interested in how ownership is managed, and it seems this is how the system checks this. `vfs_inode_is_owned_by_me` is the one that really sticks out. Before I go further, let's just make sure `vfs_inode_is_owned_by_me` is actually exported. This is a basic sanity check, but important to confirm that it's accessible where I need it. I might need to cross-reference the kernel's symbol table to verify. |
| 10/2 | 2026/07/29 18:49 | tool | grepper |
0mError:no matches Args: {
"Expression": "EXPORT_SYMBOL.*vfs_inode_is_owned_by_me"
}Results: {
"error": "no matches"
} |
| 11/2 | 2026/07/29 18:49 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 14500 output: 26 thoughts: 53 Thoughts: Okay, here's my take on those thoughts, framed as my own reasoning: **Deep Dive into Kernel Ownership Checks** Alright, so I'm dealing with something related to file system ownership, but it's not a straightforward exported function or a simple static value. That's immediately interesting, meaning I probably have to dig deeper into the kernel's internals. My gut feeling is that the related code lives in `fs/namei.c`. I need to figure out how the kernel determines if an inode is "owned" by the current process, for whatever reason. Therefore, my next step is clear. I'm going to jump into `fs/namei.c` and specifically look for a function – I'm betting it's aptly named – like `vfs_inode_is_owned_by_me`. That's where I expect to find the core logic for the check I'm interested in. The naming convention strongly suggests it's the correct starting point. Let's see what's actually in there. |
| 12/2 | 2026/07/29 18:49 | tool | grepper |
0mArgs:{
"Expression": "vfs_inode_is_owned_by_me"
}Results: {
"Output": "fs/attr.c=95=static int chown_ok(struct mnt_idmap *idmap,\n--\nfs/attr.c-100-\nfs/attr.c:101:\tret = vfs_inode_is_owned_by_me(idmap, inode);\nfs/attr.c-102-\tif (ret \u003c= 0)\n--\nfs/attr.c=124=static int chgrp_ok(struct mnt_idmap *idmap,\n--\nfs/attr.c-129-\nfs/attr.c:130:\tret = vfs_inode_is_owned_by_me(idmap, inode);\nfs/attr.c-131-\tif (ret \u003c 0)\n--\nfs/coredump.c=878=static bool coredump_file(struct core_name *cn, struct coredump_params *cprm,\n--\nfs/coredump.c-956-\tidmap = file_mnt_idmap(file);\nfs/coredump.c:957:\tif (vfs_inode_is_owned_by_me(idmap, inode) != 0) {\nfs/coredump.c-958-\t\tcoredump_report_failure(\"Core dump to %s aborted: cannot preserve file owner\", cn-\u003ecorename);\n--\nfs/inode.c=2753=bool inode_owner_or_capable(struct mnt_idmap *idmap, struct inode *inode)\n--\nfs/inode.c-2758-\nfs/inode.c:2759:\tret = vfs_inode_is_owned_by_me(idmap, inode);\nfs/inode.c-2760-\tif (ret == 0)\n--\nfs/internal.h=49=extern int finish_clean_context(struct fs_context *fc);\n--\nfs/internal.h-53- */\nfs/internal.h:54:int vfs_inode_is_owned_by_me(struct mnt_idmap *idmap, struct inode *inode);\nfs/internal.h-55-extern int filename_lookup(int dfd, struct filename *name, unsigned flags,\n--\nfs/locks.c=2137=vfs_setlease(struct file *filp, int arg, struct file_lease **lease, void **priv)\n--\nfs/locks.c-2141-\nfs/locks.c:2142:\terror = vfs_inode_is_owned_by_me(file_mnt_idmap(filp), inode);\nfs/locks.c-2143-\tif (error \u003c 0)\n--\nfs/namei.c=1254=fs_initcall(init_fs_namei_sysctls);\n--\nfs/namei.c-1262- */\nfs/namei.c:1263:int vfs_inode_is_owned_by_me(struct mnt_idmap *idmap, struct inode *inode)\nfs/namei.c-1264-{\n--\nfs/namei.c=1340=static inline int may_follow_link(struct nameidata *nd, struct inode *inode)\n--\nfs/namei.c-1353-\t/* Allowed if owner and follower match. */\nfs/namei.c:1354:\tret = vfs_inode_is_owned_by_me(idmap, inode);\nfs/namei.c-1355-\tif (ret \u003c= 0)\n--\nfs/namei.c=1509=static int may_create_in_sticky(struct mnt_idmap *idmap, struct nameidata *nd,\n--\nfs/namei.c-1527-\nfs/namei.c:1528:\tret = vfs_inode_is_owned_by_me(idmap, inode);\nfs/namei.c-1529-\tif (ret \u003c= 0)\n--\nfs/namei.c=3744=int __check_sticky(struct mnt_idmap *idmap, struct inode *dir,\n--\nfs/namei.c-3748-\nfs/namei.c:3749:\tret = vfs_inode_is_owned_by_me(idmap, inode);\nfs/namei.c-3750-\tif (ret \u003c= 0)\n--\nfs/remap_range.c=416=static int may_dedupe_file(struct file *file)\n--\nfs/remap_range.c-425-\t\treturn 0;\nfs/remap_range.c:426:\tret = vfs_inode_is_owned_by_me(idmap, inode);\nfs/remap_range.c-427-\tif (ret \u003c= 0)\n"
} |
| 13/2 | 2026/07/29 18:49 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 16006 output: 19 thoughts: 230 Thoughts: **Analyzing File Ownership and Access Control Functions** Okay, let's break down this set of functions. I'm seeing a core group here that deals with checking ownership and permissions – functions like `vfs_inode_is_owned_by_me`, `afs_is_owned_by_me`, and `afs_have_same_owner`. These are the basics, I expect. We've also got `setattr_prepare`, which likely handles preparing attribute changes, and `inode_owner_or_capable`, which is exactly what it sounds like – checking ownership or capability. Then we have some more specific functions such as `vfs_setlease`, `__check_sticky`, and `vfs_dedupe_file_range_one` that I need to place into the larger context. My next step is to examine the relationship of some other, related functions and how these might fit. I'm curious about the static functions `vfs_inode_and_dir_have_same_owner` and `vfs_inodes_have_same_owner` and how they're used. Aha, they're static in `fs/namei.c`. Good to know, that places them within the VFS namespace. I need to keep those in mind as I build the larger picture. I was also thinking of including `may_create_in_sticky` and `may_follow_link` in my analysis, but since they are also static functions, that might not be as important, but good to keep in mind. Wait, is `may_linkat` calling `may_follow_link`? No, no, `may_follow_link` is called from `follow_link` or a similar function, I need to keep the context straight here. Given the use of `may_follow_link`, I will now need to look at its call sites. |
| 14/2 | 2026/07/29 18:49 | tool | grepper |
0mArgs:{
"Expression": "may_follow_link"
}Results: {
"Output": "Documentation/filesystems/path-lookup.rst=1130=component of the lookup, so we will check userspace flag ``LOOKUP_FOLLOW`` to\nDocumentation/filesystems/path-lookup.rst:1131:decide whether follow it when it is a symlink and call ``may_follow_link()`` to\nDocumentation/filesystems/path-lookup.rst-1132-check if we have privilege to follow it.\n--\nfs/namei.c=1313=static int vfs_inodes_have_same_owner(struct mnt_idmap *idmap, struct inode *inode,\n--\nfs/namei.c-1324-/**\nfs/namei.c:1325: * may_follow_link - Check symlink following for unsafe situations\nfs/namei.c-1326- * @nd: nameidata pathwalk data\n--\nfs/namei.c-1339- */\nfs/namei.c:1340:static inline int may_follow_link(struct nameidata *nd, struct inode *inode)\nfs/namei.c-1341-{\n--\nfs/namei.c=2106=static noinline const char *pick_link(struct nameidata *nd, struct path *link,\n--\nfs/namei.c-2133-\tif (flags \u0026 WALK_TRAILING) {\nfs/namei.c:2134:\t\terror = may_follow_link(nd, inode);\nfs/namei.c-2135-\t\tif (unlikely(error))\n"
} |
| 15/2 | 2026/07/29 18:49 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 17047 output: 21 thoughts: 29 Thoughts: Here's a summary of my thought process: **Analyzing Link Traversal Logic** Okay, so `pick_link` is responsible for, well, picking a link. And it appears that it's calling `may_follow_link`. That makes sense - we need to make a decision about whether to actually *follow* the link after we've picked it. Now, what's crucial here is how we define the parameters for picking. I need to understand the conditions under which a link is deemed 'followable.' Before digging into `may_follow_link`'s code directly, I need to check the wider picture, as in, who are the callers of `may_create_in_sticky`? I want to understand what influences the decision to create a sticky. Understanding those broader contexts will help me trace the logic more effectively. From this, I will be able to check for any links that are related to the construction of a sticky. |
| 16/2 | 2026/07/29 18:49 | tool | grepper |
0mArgs:{
"Expression": "may_create_in_sticky"
}Results: {
"Output": "fs/namei.c=1427=int may_linkat(struct mnt_idmap *idmap, const struct path *link)\n--\nfs/namei.c-1450-/**\nfs/namei.c:1451: * may_create_in_sticky - Check whether an O_CREAT open in a sticky directory\nfs/namei.c-1452- *\t\t\t should be allowed, or not, on files that already\n--\nfs/namei.c-1508- */\nfs/namei.c:1509:static int may_create_in_sticky(struct mnt_idmap *idmap, struct nameidata *nd,\nfs/namei.c-1510-\t\t\t\tstruct inode *inode)\n--\nfs/namei.c=4753=static int do_open(struct nameidata *nd,\n--\nfs/namei.c-4774-\t\t\treturn -EISDIR;\nfs/namei.c:4775:\t\terror = may_create_in_sticky(idmap, nd,\nfs/namei.c-4776-\t\t\t\t\t d_backing_inode(nd-\u003epath.dentry));\n"
} |
| 17/2 | 2026/07/29 18:50 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 17601 output: 224 thoughts: 428 Thoughts: **Analyzing the Security Implications of This Patch** Okay, so `do_open` calls `may_create_in_sticky`, which means we need to meticulously examine the security implications. Let's start by identifying the relevant focus symbols. I've got a good list here: `afs_is_owned_by_me`, `afs_have_same_owner`, `vfs_inode_is_owned_by_me`, `setattr_prepare`, `inode_owner_or_capable`, `vfs_setlease`, `__check_sticky`, and `vfs_dedupe_file_range_one`. Now, let's confirm the location of `vfs_inode_is_owned_by_me`. Yes, it's right there in `fs/namei.c`. Good. Next, is `AFS_FS` the *only* configuration needed? Seems that way; the AFS-specific part is enabled by `AFS_FS`, and the VFS portion is always active. I need to make a note to list `AFS_FS` under `EnableConfigs`. Now, let's recap the reasoning behind this. This patch introduces new VFS inode operations, `is_owned_by_me` and `have_same_owner`, to allow filesystems like AFS to override the default UID-based ownership checks. The patch implements these operations for AFS and also modifies several core VFS functions such as chown, chgrp, lease, sticky bit checks, dedupe, and symlink following to leverage the new helpers. This is a *significant* functional change in the core VFS and AFS, and it fundamentally impacts file permission checks. This absolutely needs fuzzing. To summarize, here's my updated list of focus symbols and configurations: Focus symbols: - `afs_is_owned_by_me` - `afs_have_same_owner` - `vfs_inode_is_owned_by_me` - `setattr_prepare` - `inode_owner_or_capable` - `vfs_setlease` - `__check_sticky` - `vfs_dedupe_file_range_one` Configs: - `AFS_FS` Alright, time to execute `set-results`. |
| 18/2 | 2026/07/29 18:50 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"AFS_FS"
],
"FocusSymbols": [
"afs_is_owned_by_me",
"afs_have_same_owner",
"vfs_inode_is_owned_by_me",
"setattr_prepare",
"inode_owner_or_capable",
"vfs_setlease",
"__check_sticky",
"vfs_dedupe_file_range_one"
],
"Reasoning": "The patch introduces new VFS inode operations `is_owned_by_me` and `have_same_owner` to allow filesystems (like AFS) to override default UID-based ownership checks. It implements these for AFS and modifies several core VFS functions (chown, chgrp, lease, sticky bit checks, dedupe, symlink following) to use the new helpers. This is a functional change in core VFS and AFS that affects file permission checks and should be fuzzed.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"AFS_FS"
],
"FocusSymbols": [
"afs_is_owned_by_me",
"afs_have_same_owner",
"vfs_inode_is_owned_by_me",
"setattr_prepare",
"inode_owner_or_capable",
"vfs_setlease",
"__check_sticky",
"vfs_dedupe_file_range_one"
],
"Reasoning": "The patch introduces new VFS inode operations `is_owned_by_me` and `have_same_owner` to allow filesystems (like AFS) to override default UID-based ownership checks. It implements these for AFS and modifies several core VFS functions (chown, chgrp, lease, sticky bit checks, dedupe, symlink following) to use the new helpers. This is a functional change in core VFS and AFS that affects file permission checks and should be fuzzed.",
"WorthFuzzing": true
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|