| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/10/06 00:34 | flow | patch-triage |
3mArgs:null Results: {
"EnableConfigs": [
"BPF_SYSCALL",
"BPF_LSM",
"SECURITY_LANDLOCK"
],
"FocusSymbols": [
"vfs_walk_ancestors",
"vfs_walk_start",
"vfs_walk_next",
"vfs_walk_end",
"vfs_walk_handover",
"mnt_undo_legitimize",
"bpf_iter_path_ancestors_new",
"bpf_iter_path_ancestors_next",
"bpf_iter_path_ancestors_destroy",
"bpf_iter_path_ancestors_rcu_new",
"bpf_iter_path_ancestors_rcu_next",
"bpf_iter_path_ancestors_rcu_destroy",
"bpf_path_ancestors_legitimize",
"bpf_path_put"
],
"KMSANReasoning": "The patch series introduces stepwise VFS path ancestor walking infrastructure (vfs_walk_ancestors) used by Landlock and exposes BPF open-coded iterators (bpf_iter_path_ancestors and bpf_iter_path_ancestors_rcu) along with associated kfuncs.\n\nEvaluating the changes for uninitialized memory risks vs. KASAN/LOCKDEP:\n1. Memory initialization:\n - In `bpf_iter_path_ancestors_next()`, `held = kmalloc_obj(*held)` allocates a `struct path` which is immediately fully populated by `*held = *pos` (a 16-byte struct with two pointer fields and no padding).\n - In `bpf_path_ancestors_legitimize()`, the destination iterator `kit` is immediately zeroed using `memset(kit, 0, sizeof(*kit))` before any operations.\n - In `vfs_walk_start()`, when `VFS_WALK_RCU` is set, `m_seq` and `seq` are explicitly sampled via `read_seqbegin()` and `raw_seqcount_begin()`. In non-RCU mode, branches that read `seq` or `m_seq` are strictly guarded by `aw-\u003eflags \u0026 VFS_WALK_RCU` and never execute.\n - In `security/landlock/fs.c`, the new `struct landlock_walk_state state` is stack-allocated with designated initializers (zeroing unmentioned fields) and explicitly initializes all remaining fields before being passed to `vfs_walk_ancestors()`.\n2. Kernel-to-user info leaks:\n - No kernel data structures or iterator states are copied to userspace via `copy_to_user`, netlink, ioctl, or socket buffers.\n3. KASAN vs KMSAN applicability:\n - The primary risks of this patch involve reference counting (e.g. `dget`/`dput`, `path_get`/`path_put`, `mnt_undo_legitimize`), lockless RCU concurrency races, or BPF verifier reference tracking. Any memory corruption, use-after-free, or double-free resulting from reference count errors is detected by KASAN and standard refcount tracking, while locking issues are caught by LOCKDEP.\n - There are no uninitialized memory usages or information leaks that would require a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false,
"Reasoning": "The patch introduces a stepwise VFS ancestor path walking engine (vfs_walk_*) supporting both sleepable referenced walks and lockless RCU walks, rewrites Landlock path access checks to use it, modifies mount reference management (mnt_undo_legitimize, delayed_mntput), introduces BPF path iterator kfuncs (bpf_iter_path_ancestors_*), and updates the BPF verifier. These changes alter core VFS and BPF control flow, concurrency handling, and reference counting, making them highly relevant for fuzzing.",
"WorthFuzzing": true
} |
| 1/1 | 2026/10/06 00:34 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 0b8b5fc9ad0c8a6575b46e5c6fe1969366398c37\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Tue Oct 6 00:34:04 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c\nindex 08f0847c49708..9dad65df42674 100644\n--- a/fs/bpf_fs_kfuncs.c\n+++ b/fs/bpf_fs_kfuncs.c\n@@ -13,7 +13,11 @@\n #include \u003clinux/kernfs.h\u003e\n #include \u003clinux/lsm_hooks.h\u003e\n #include \u003clinux/mm.h\u003e\n+#include \u003clinux/namei.h\u003e\n #include \u003clinux/net.h\u003e\n+#include \u003clinux/slab.h\u003e\n+\n+#include \"internal.h\"\n #include \u003clinux/xattr.h\u003e\n \n __bpf_kfunc_start_defs();\n@@ -500,6 +504,239 @@ __bpf_kfunc struct inode *bpf_real_data_inode(struct file *file)\n \n __bpf_kfunc_end_defs();\n \n+enum bpf_path_ancestors_flag {\n+\t/* bpf_path_ancestors[_rcu]_pos_flags() bits */\n+\tBPF_PATH_ANCESTORS_DISCONNECTED\t= (1 \u003c\u003c 0),\n+\t/* the position is a mountpoint a mount crossing landed on */\n+\tBPF_PATH_ANCESTORS_MOUNTPOINT\t= (1 \u003c\u003c 1),\n+\t/* the iteration ended on a failed allocation, not at the root */\n+\tBPF_PATH_ANCESTORS_NOMEM\t= (1 \u003c\u003c 2),\n+\t/* the lockless iteration lost a race and reached no conclusion */\n+\tBPF_PATH_ANCESTORS_RETRY\t= (1 \u003c\u003c 3),\n+};\n+\n+/*\n+ * Walks over a path's ancestors, in two variants differing in how\n+ * positions are kept alive:\n+ *\n+ * bpf_iter_path_ancestors runs with references. Its kfuncs are\n+ * sleepable, so the iteration may sleep between positions.\n+ *\n+ * Each position is handed to the program as an acquired reference of its\n+ * own, to release with bpf_path_put(). The walk's own reference moves on\n+ * with the walk, so a position that outlives the step it came from - a\n+ * dentry saved for later, or the path a sleepable kfunc is still working\n+ * on - has to be kept alive by the program's reference rather than by the\n+ * iterator's.\n+ *\n+ * bpf_iter_path_ancestors_rcu runs lockless over one RCU read-side\n+ * critical section, which the verifier enforces around the whole\n+ * iteration and within which sleeping is impossible. Its positions are\n+ * borrowed, not acquired: they are only valid until the next step, and\n+ * nothing derived from one may be passed to a kfunc demanding a trusted\n+ * argument. A lost race ends the iteration with\n+ * BPF_PATH_ANCESTORS_RETRY set; the program discards what it derived\n+ * from the walk and retries, typically on the referenced variant, or\n+ * escalates mid-walk with bpf_path_ancestors_legitimize().\n+ */\n+struct bpf_iter_path_ancestors {\n+\t__u64 __opaque[5];\n+} __aligned(8);\n+\n+struct bpf_iter_path_ancestors_rcu {\n+\t__u64 __opaque[5];\n+} __aligned(8);\n+\n+struct bpf_path_ancestors_kern {\n+\tstruct vfs_ancestor_walk aw;\n+\tint step;\t/* last vfs_walk_next() result, or -ENOMEM */\n+} __aligned(8);\n+\n+static int bpf_path_ancestors_new(struct bpf_path_ancestors_kern *kit,\n+\t\t\t\t struct path *path, u64 flags,\n+\t\t\t\t unsigned int walk_flags)\n+{\n+\tBUILD_BUG_ON(sizeof(struct bpf_path_ancestors_kern) \u003e\n+\t\t sizeof(struct bpf_iter_path_ancestors));\n+\tBUILD_BUG_ON(__alignof__(struct bpf_path_ancestors_kern) !=\n+\t\t __alignof__(struct bpf_iter_path_ancestors));\n+\tBUILD_BUG_ON(sizeof(struct bpf_iter_path_ancestors) !=\n+\t\t sizeof(struct bpf_iter_path_ancestors_rcu));\n+\tBUILD_BUG_ON(__alignof__(struct bpf_iter_path_ancestors) !=\n+\t\t __alignof__(struct bpf_iter_path_ancestors_rcu));\n+\n+\tif (flags) {\n+\t\t/* A zeroed walk makes destroying the iterator a no-op. */\n+\t\tmemset(kit, 0, sizeof(*kit));\n+\t\tkit-\u003estep = 1;\n+\t\treturn -EINVAL;\n+\t}\n+\tkit-\u003estep = 0;\n+\tvfs_walk_start(\u0026kit-\u003eaw, path, walk_flags);\n+\treturn 0;\n+}\n+\n+/* The walk's own view of the next position, which the step after it ends. */\n+static struct path *bpf_path_ancestors_step(struct bpf_path_ancestors_kern *kit)\n+{\n+\tif (kit-\u003estep)\n+\t\treturn NULL;\n+\tkit-\u003estep = vfs_walk_next(\u0026kit-\u003eaw);\n+\treturn kit-\u003estep ? NULL : \u0026kit-\u003eaw.pos;\n+}\n+\n+static u32 bpf_path_ancestors_flags(const struct bpf_path_ancestors_kern *kit)\n+{\n+\tu32 flags = 0;\n+\n+\tif (kit-\u003estep == -ENOMEM)\n+\t\treturn BPF_PATH_ANCESTORS_NOMEM;\n+\tif (kit-\u003estep == -ECHILD)\n+\t\treturn BPF_PATH_ANCESTORS_RETRY;\n+\tif (!kit-\u003estep) {\n+\t\tif (kit-\u003eaw.pos_flags \u0026 VFS_WALK_POS_DISCONNECTED)\n+\t\t\tflags |= BPF_PATH_ANCESTORS_DISCONNECTED;\n+\t\tif (kit-\u003eaw.pos_flags \u0026 VFS_WALK_POS_MOUNTPOINT)\n+\t\t\tflags |= BPF_PATH_ANCESTORS_MOUNTPOINT;\n+\t}\n+\treturn flags;\n+}\n+\n+__bpf_kfunc_start_defs();\n+\n+__bpf_kfunc int bpf_iter_path_ancestors_new(struct bpf_iter_path_ancestors *it,\n+\t\t\t\t\t struct path *path, u64 flags)\n+{\n+\treturn bpf_path_ancestors_new((void *)it, path, flags, 0);\n+}\n+\n+/**\n+ * bpf_iter_path_ancestors_next - acquire the walk's next position\n+ * @it: the iterator\n+ *\n+ * Return: the next position with a reference held, to release with\n+ * bpf_path_put(), or NULL once the walk has passed the real root - or on\n+ * an allocation failure, which ends the iteration and is reported as\n+ * %BPF_PATH_ANCESTORS_NOMEM by bpf_path_ancestors_pos_flags().\n+ */\n+__bpf_kfunc struct path *\n+bpf_iter_path_ancestors_next(struct bpf_iter_path_ancestors *it)\n+{\n+\tstruct bpf_path_ancestors_kern *kit = (void *)it;\n+\tstruct path *pos = bpf_path_ancestors_step(kit);\n+\tstruct path *held;\n+\n+\tif (!pos)\n+\t\treturn NULL;\n+\t/*\n+\t * The position must outlive the walk's own view of it, so it gets a\n+\t * reference and a struct path of its own to live in: struct path is\n+\t * a value type, with nothing a BPF reference could be taken on\n+\t * otherwise. Sleepable, so no atomic allocation.\n+\t */\n+\theld = kmalloc_obj(*held);\n+\tif (!held) {\n+\t\tkit-\u003estep = -ENOMEM;\n+\t\treturn NULL;\n+\t}\n+\t*held = *pos;\n+\tpath_get(held);\n+\treturn held;\n+}\n+\n+__bpf_kfunc void\n+bpf_iter_path_ancestors_destroy(struct bpf_iter_path_ancestors *it)\n+{\n+\tvfs_walk_end(\u0026((struct bpf_path_ancestors_kern *)it)-\u003eaw);\n+}\n+\n+__bpf_kfunc u32\n+bpf_path_ancestors_pos_flags(struct bpf_iter_path_ancestors *it__iter)\n+{\n+\treturn bpf_path_ancestors_flags((void *)it__iter);\n+}\n+\n+__bpf_kfunc int\n+bpf_iter_path_ancestors_rcu_new(struct bpf_iter_path_ancestors_rcu *it,\n+\t\t\t\tstruct path *path, u64 flags)\n+{\n+\treturn bpf_path_ancestors_new((void *)it, path, flags, VFS_WALK_RCU);\n+}\n+\n+/*\n+ * Unlike the referenced variant, this hands out the walk's own position:\n+ * a lockless iteration holds no references to pass on, and the verifier\n+ * keeps the whole of it inside one RCU read-side critical section.\n+ */\n+__bpf_kfunc struct path *\n+bpf_iter_path_ancestors_rcu_next(struct bpf_iter_path_ancestors_rcu *it)\n+{\n+\treturn bpf_path_ancestors_step((void *)it);\n+}\n+\n+__bpf_kfunc void\n+bpf_iter_path_ancestors_rcu_destroy(struct bpf_iter_path_ancestors_rcu *it)\n+{\n+\tvfs_walk_end(\u0026((struct bpf_path_ancestors_kern *)it)-\u003eaw);\n+}\n+\n+__bpf_kfunc u32\n+bpf_path_ancestors_rcu_pos_flags(struct bpf_iter_path_ancestors_rcu *it__iter)\n+{\n+\treturn bpf_path_ancestors_flags((void *)it__iter);\n+}\n+\n+/**\n+ * bpf_path_ancestors_legitimize - hand a lockless iteration over to references\n+ * @it__uninit: referenced ancestor iterator to begin at @rcu_it__iter's\n+ * current position; destroy it with\n+ * bpf_iter_path_ancestors_destroy() whether this succeeds or not\n+ * @rcu_it__iter: lockless ancestor iterator, on the position to escalate at\n+ *\n+ * Mirrors unlazy_walk(): acquires the lockless iteration's current position\n+ * and leaves @it__uninit ready to continue from it with references, which\n+ * its first bpf_iter_path_ancestors_next() then yields - necessarily after\n+ * the program has left its RCU read-side critical section, since that kfunc\n+ * is sleepable. Sleepable work on the escalated position therefore happens\n+ * on the iteration's own reference, and nothing is allocated here.\n+ *\n+ * Return: 0, -%ENOENT if the lockless iteration was not on a position, or\n+ * -%ECHILD if it lost the race to acquire one; %BPF_PATH_ANCESTORS_RETRY is\n+ * then also flagged, and the program has reached no conclusion about the\n+ * ancestry. @it__uninit is initialized whatever this returns, so a program\n+ * need not branch on the result: a walk that could not be escalated simply\n+ * yields no position.\n+ */\n+__bpf_kfunc int\n+bpf_path_ancestors_legitimize(struct bpf_iter_path_ancestors *it__uninit,\n+\t\t\t struct bpf_iter_path_ancestors_rcu *rcu_it__iter)\n+{\n+\tstruct bpf_path_ancestors_kern *rcu_kit = (void *)rcu_it__iter;\n+\tstruct bpf_path_ancestors_kern *kit = (void *)it__uninit;\n+\n+\t/* A zeroed walk makes destroying the iterator a no-op. */\n+\tmemset(kit, 0, sizeof(*kit));\n+\tkit-\u003estep = 1;\n+\n+\t/* Drained, or already failed: nothing to hand over. */\n+\tif (rcu_kit-\u003estep)\n+\t\treturn -ENOENT;\n+\tif (!vfs_walk_handover(\u0026kit-\u003eaw, \u0026rcu_kit-\u003eaw)) {\n+\t\trcu_kit-\u003estep = -ECHILD;\n+\t\treturn -ECHILD;\n+\t}\n+\tkit-\u003estep = 0;\n+\treturn 0;\n+}\n+\n+__bpf_kfunc void bpf_path_put(struct path *path)\n+{\n+\tpath_put(path);\n+\tkfree(path);\n+}\n+\n+__bpf_kfunc_end_defs();\n+\n BTF_KFUNCS_START(bpf_fs_kfunc_set_ids)\n BTF_ID_FLAGS(func, bpf_get_task_exe_file, KF_ACQUIRE | KF_RET_NULL)\n BTF_ID_FLAGS(func, bpf_put_file, KF_RELEASE)\n@@ -513,6 +750,17 @@ BTF_ID_FLAGS(func, bpf_inode_init_xattr)\n #ifdef CONFIG_NET\n BTF_ID_FLAGS(func, bpf_sock_read_xattr, KF_RCU)\n #endif\n+BTF_ID_FLAGS(func, bpf_iter_path_ancestors_new, KF_ITER_NEW | KF_SLEEPABLE)\n+BTF_ID_FLAGS(func, bpf_iter_path_ancestors_next,\n+\t KF_ITER_NEXT | KF_ACQUIRE | KF_RET_NULL | KF_SLEEPABLE)\n+BTF_ID_FLAGS(func, bpf_iter_path_ancestors_destroy, KF_ITER_DESTROY | KF_SLEEPABLE)\n+BTF_ID_FLAGS(func, bpf_path_ancestors_pos_flags)\n+BTF_ID_FLAGS(func, bpf_iter_path_ancestors_rcu_new, KF_ITER_NEW | KF_RCU_PROTECTED)\n+BTF_ID_FLAGS(func, bpf_iter_path_ancestors_rcu_next, KF_ITER_NEXT | KF_RET_NULL)\n+BTF_ID_FLAGS(func, bpf_iter_path_ancestors_rcu_destroy, KF_ITER_DESTROY)\n+BTF_ID_FLAGS(func, bpf_path_ancestors_rcu_pos_flags)\n+BTF_ID_FLAGS(func, bpf_path_ancestors_legitimize)\n+BTF_ID_FLAGS(func, bpf_path_put, KF_RELEASE | KF_SLEEPABLE)\n BTF_KFUNCS_END(bpf_fs_kfunc_set_ids)\n \n /* Side-effecting kfuncs that stay exclusive to LSM programs. */\ndiff --git a/fs/internal.h b/fs/internal.h\nindex c658c8a5ebd56..8a5696157121b 100644\n--- a/fs/internal.h\n+++ b/fs/internal.h\n@@ -71,6 +71,28 @@ struct dentry *start_dirop(struct dentry *parent, struct qstr *name,\n \t\t\t unsigned int lookup_flags);\n int lookup_noperm_common(struct qstr *qname, struct dentry *base);\n \n+/*\n+ * The stepwise engine under vfs_walk_ancestors(); fs-internal so iterating\n+ * consumers (BPF) can drive it, with the walk invariants staying in namei.c.\n+ * In rcu mode (%VFS_WALK_RCU) the caller holds rcu_read_lock() over the\n+ * whole walk, no references are held, and vfs_walk_next() returning -ECHILD\n+ * invalidates everything derived from the walk.\n+ */\n+struct vfs_ancestor_walk {\n+\tstruct path pos;\n+\tunsigned int seq;\t/* pos.dentry-\u003ed_seq sample (rcu mode) */\n+\tunsigned int m_seq;\t/* mount_lock sample (rcu mode) */\n+\tunsigned int pos_flags;\t/* VFS_WALK_POS_* describing pos */\n+\tunsigned int flags;\t/* VFS_WALK_* */\n+};\n+\n+void vfs_walk_start(struct vfs_ancestor_walk *aw, const struct path *path,\n+\t\t unsigned int flags);\n+int vfs_walk_next(struct vfs_ancestor_walk *aw);\n+void vfs_walk_end(struct vfs_ancestor_walk *aw);\n+bool vfs_walk_handover(struct vfs_ancestor_walk *to,\n+\t\t struct vfs_ancestor_walk *from);\n+\n void __init filename_init(void);\n \n /*\ndiff --git a/fs/mount.h b/fs/mount.h\nindex 94fcc306d21e7..e714040f8e8fd 100644\n--- a/fs/mount.h\n+++ b/fs/mount.h\n@@ -140,6 +140,7 @@ static inline int is_mounted(struct vfsmount *mnt)\n extern struct mount *__lookup_mnt(struct vfsmount *, struct dentry *);\n \n extern int __legitimize_mnt(struct vfsmount *, unsigned);\n+void mnt_undo_legitimize(struct mount *mnt);\n \n static inline bool __path_is_mountpoint(const struct path *path)\n {\ndiff --git a/fs/namei.c b/fs/namei.c\nindex 20a6534ea3eff..31f96602d4e94 100644\n--- a/fs/namei.c\n+++ b/fs/namei.c\n@@ -2152,34 +2152,69 @@ static __always_inline const char *step_into(struct nameidata *nd, int flags,\n \treturn step_into_slowpath(nd, flags, dentry);\n }\n \n-static struct dentry *follow_dotdot_rcu(struct nameidata *nd)\n+/**\n+ * __path_walk_parent_rcu - step towards the parent of the given struct path\n+ * @path: position to step up from; updated in place on a mount crossing,\n+ * which is first if @path is the root of a mounted tree. No\n+ * references are acquired; the callers layer their own bookkeeping\n+ * (path_connected(), nameidata updates, ...) on top\n+ * @root: boundary as for choose_mountpoint_rcu(); if zero'ed, walk all the\n+ * way to the global root\n+ * @flags: %LOOKUP_NO_XDEV fails a mount crossing with -ECHILD\n+ * @m_seq: the walk's mount_lock sample\n+ * @seqp: d_seq sample validating @path-\u003edentry; updated to cover the new\n+ * @path-\u003edentry when a mount is crossed\n+ * @next_seqp: set to the returned parent's d_seq sample\n+ *\n+ * Returns: the parent dentry (which is @path-\u003edentry itself if that is a\n+ * disconnected root), NULL if @path is in the root with nothing to cross\n+ * into, or ERR_PTR(-ECHILD) when a concurrent change was detected.\n+ */\n+static struct dentry *__path_walk_parent_rcu(struct path *path,\n+\t\t\t\t\t const struct path *root, int flags,\n+\t\t\t\t\t unsigned int m_seq, unsigned int *seqp,\n+\t\t\t\t\t unsigned int *next_seqp)\n {\n \tstruct dentry *parent, *old;\n \n-\tif (path_equal(\u0026nd-\u003epath, \u0026nd-\u003eroot))\n-\t\tgoto in_root;\n-\tif (unlikely(nd-\u003epath.dentry == nd-\u003epath.mnt-\u003emnt_root)) {\n-\t\tstruct path path;\n-\t\tunsigned seq;\n-\t\tif (!choose_mountpoint_rcu(real_mount(nd-\u003epath.mnt),\n-\t\t\t\t\t \u0026nd-\u003eroot, \u0026path, \u0026seq))\n-\t\t\tgoto in_root;\n-\t\tif (unlikely(nd-\u003eflags \u0026 LOOKUP_NO_XDEV))\n+\tif (unlikely(path-\u003edentry == path-\u003emnt-\u003emnt_root)) {\n+\t\tstruct path mounted;\n+\t\tunsigned int seq;\n+\n+\t\tif (!choose_mountpoint_rcu(real_mount(path-\u003emnt),\n+\t\t\t\t\t root, \u0026mounted, \u0026seq))\n+\t\t\treturn NULL;\n+\t\tif (unlikely(flags \u0026 LOOKUP_NO_XDEV))\n \t\t\treturn ERR_PTR(-ECHILD);\n-\t\tnd-\u003epath = path;\n-\t\tnd-\u003einode = path.dentry-\u003ed_inode;\n-\t\tnd-\u003eseq = seq;\n+\t\t*path = mounted;\n+\t\t*seqp = seq;\n \t\t// makes sure that non-RCU pathwalk could reach this state\n-\t\tif (read_seqretry(\u0026mount_lock, nd-\u003em_seq))\n+\t\tif (read_seqretry(\u0026mount_lock, m_seq))\n \t\t\treturn ERR_PTR(-ECHILD);\n \t\t/* we know that mountpoint was pinned */\n \t}\n-\told = nd-\u003epath.dentry;\n+\told = path-\u003edentry;\n \tparent = old-\u003ed_parent;\n-\tnd-\u003enext_seq = read_seqcount_begin(\u0026parent-\u003ed_seq);\n+\t*next_seqp = read_seqcount_begin(\u0026parent-\u003ed_seq);\n \t// makes sure that non-RCU pathwalk could reach this state\n-\tif (read_seqcount_retry(\u0026old-\u003ed_seq, nd-\u003eseq))\n+\tif (read_seqcount_retry(\u0026old-\u003ed_seq, *seqp))\n \t\treturn ERR_PTR(-ECHILD);\n+\treturn parent;\n+}\n+\n+static struct dentry *follow_dotdot_rcu(struct nameidata *nd)\n+{\n+\tstruct dentry *parent;\n+\n+\tif (path_equal(\u0026nd-\u003epath, \u0026nd-\u003eroot))\n+\t\tgoto in_root;\n+\tparent = __path_walk_parent_rcu(\u0026nd-\u003epath, \u0026nd-\u003eroot, nd-\u003eflags,\n+\t\t\t\t\tnd-\u003em_seq, \u0026nd-\u003eseq, \u0026nd-\u003enext_seq);\n+\tif (!parent)\n+\t\tgoto in_root;\n+\tif (IS_ERR(parent))\n+\t\treturn parent;\n+\tnd-\u003einode = nd-\u003epath.dentry-\u003ed_inode;\n \tif (unlikely(!path_connected(nd-\u003epath.mnt, parent)))\n \t\treturn ERR_PTR(-ECHILD);\n \treturn parent;\n@@ -2192,36 +2227,308 @@ static struct dentry *follow_dotdot_rcu(struct nameidata *nd)\n \treturn nd-\u003epath.dentry;\n }\n \n-static struct dentry *follow_dotdot(struct nameidata *nd)\n+/**\n+ * __path_walk_parent - step towards the parent of the given struct path\n+ * @path: position to step up from; if it is the root of a mounted tree, it\n+ * is first updated to the mount point that tree is mounted on\n+ * @root: boundary not to be crossed; if zero'ed, walk all the way to the\n+ * global root\n+ * @flags: %LOOKUP_NO_XDEV returns -EXDEV once @path was updated to a new\n+ * mount; %LOOKUP_BENEATH returns -EXDEV at @root or at the root of\n+ * an unmounted tree, where @path-\u003edentry itself is returned\n+ * otherwise\n+ *\n+ * Returns: the parent dentry with its refcount incremented, or an ERR_PTR().\n+ */\n+static struct dentry *__path_walk_parent(struct path *path, const struct path *root, int flags)\n {\n-\tstruct dentry *parent;\n-\n-\tif (path_equal(\u0026nd-\u003epath, \u0026nd-\u003eroot))\n+\tif (path_equal(path, root))\n \t\tgoto in_root;\n-\tif (unlikely(nd-\u003epath.dentry == nd-\u003epath.mnt-\u003emnt_root)) {\n-\t\tstruct path path;\n+\tif (unlikely(path-\u003edentry == path-\u003emnt-\u003emnt_root)) {\n+\t\tstruct path new_path;\n \n-\t\tif (!choose_mountpoint(real_mount(nd-\u003epath.mnt),\n-\t\t\t\t \u0026nd-\u003eroot, \u0026path))\n+\t\tif (!choose_mountpoint(real_mount(path-\u003emnt),\n+\t\t\t\t root, \u0026new_path))\n \t\t\tgoto in_root;\n-\t\tpath_put(\u0026nd-\u003epath);\n-\t\tnd-\u003epath = path;\n-\t\tnd-\u003einode = path.dentry-\u003ed_inode;\n-\t\tif (unlikely(nd-\u003eflags \u0026 LOOKUP_NO_XDEV))\n+\t\tpath_put(path);\n+\t\t*path = new_path;\n+\t\tif (unlikely(flags \u0026 LOOKUP_NO_XDEV))\n \t\t\treturn ERR_PTR(-EXDEV);\n \t}\n \t/* rare case of legitimate dget_parent()... */\n-\tparent = dget_parent(nd-\u003epath.dentry);\n+\treturn dget_parent(path-\u003edentry);\n+\n+in_root:\n+\tif (unlikely(flags \u0026 LOOKUP_BENEATH))\n+\t\treturn ERR_PTR(-EXDEV);\n+\treturn dget(path-\u003edentry);\n+}\n+\n+static unsigned int vfs_walk_pos_flags(const struct vfsmount *mnt,\n+\t\t\t\t const struct dentry *dentry)\n+{\n+\tif (unlikely(IS_ROOT(dentry) \u0026\u0026 dentry != mnt-\u003emnt_root))\n+\t\treturn VFS_WALK_POS_DISCONNECTED;\n+\treturn 0;\n+}\n+\n+/* No boundary: ancestor walks go up to the real root. */\n+static const struct path vfs_walk_no_root;\n+\n+/* Internal walk flag: the starting position has been yielded. */\n+#define VFS_WALK_STARTED\tBIT(31)\n+\n+/**\n+ * vfs_walk_start - begin a stepwise ancestor walk\n+ * @aw: walk state, valid until vfs_walk_end()\n+ * @path: position to walk up from; never modified\n+ * @flags: %VFS_WALK_RCU to walk lockless; the caller then holds\n+ * rcu_read_lock() from before vfs_walk_start() until after\n+ * vfs_walk_end(), and owns no references on yielded positions.\n+ */\n+void vfs_walk_start(struct vfs_ancestor_walk *aw, const struct path *path,\n+\t\t unsigned int flags)\n+{\n+\taw-\u003epos = *path;\n+\taw-\u003eflags = flags;\n+\taw-\u003epos_flags = 0;\n+\tif (flags \u0026 VFS_WALK_RCU) {\n+\t\tRCU_LOCKDEP_WARN(!rcu_read_lock_held(),\n+\t\t\t\t \"rcu-mode ancestor walk outside of RCU read-side critical section\");\n+\t\taw-\u003em_seq = read_seqbegin(\u0026mount_lock);\n+\t\taw-\u003eseq = raw_seqcount_begin(\u0026aw-\u003epos.dentry-\u003ed_seq);\n+\t} else {\n+\t\tpath_get(\u0026aw-\u003epos);\n+\t}\n+}\n+\n+static int vfs_walk_step_ref(struct vfs_ancestor_walk *aw)\n+{\n+\tstruct dentry *parent;\n+\n+\tif (unlikely(aw-\u003epos_flags \u0026 VFS_WALK_POS_DISCONNECTED)) {\n+\t\t/* Resume at the root of the disconnected position's mount. */\n+\t\tdput(aw-\u003epos.dentry);\n+\t\taw-\u003epos.dentry = dget(aw-\u003epos.mnt-\u003emnt_root);\n+\t\taw-\u003epos_flags = 0;\n+\t\treturn 0;\n+\t}\n+\tparent = __path_walk_parent(\u0026aw-\u003epos, \u0026vfs_walk_no_root,\n+\t\t\t\t LOOKUP_BENEATH);\n+\tif (IS_ERR(parent))\n+\t\t/* The real root. */\n+\t\treturn 1;\n+\t/*\n+\t * A mount crossing can step onto a disconnected root, whose parent\n+\t * is itself: only then is the mountpoint itself yielded, flagged.\n+\t */\n+\taw-\u003epos_flags = parent == aw-\u003epos.dentry ?\n+\t\tVFS_WALK_POS_DISCONNECTED | VFS_WALK_POS_MOUNTPOINT :\n+\t\tvfs_walk_pos_flags(aw-\u003epos.mnt, parent);\n+\tdput(aw-\u003epos.dentry);\n+\taw-\u003epos.dentry = parent;\n+\treturn 0;\n+}\n+\n+static int vfs_walk_step_rcu(struct vfs_ancestor_walk *aw)\n+{\n+\tstruct dentry *parent;\n+\tunsigned int next_seq;\n+\n+\tif (unlikely(aw-\u003epos_flags \u0026 VFS_WALK_POS_DISCONNECTED)) {\n+\t\t/* Resume at the root of the disconnected position's mount. */\n+\t\taw-\u003epos.dentry = aw-\u003epos.mnt-\u003emnt_root;\n+\t\taw-\u003eseq = raw_seqcount_begin(\u0026aw-\u003epos.dentry-\u003ed_seq);\n+\t\taw-\u003epos_flags = 0;\n+\t\treturn read_seqretry(\u0026mount_lock, aw-\u003em_seq) ? -ECHILD : 0;\n+\t}\n+\n+\tparent = __path_walk_parent_rcu(\u0026aw-\u003epos, \u0026vfs_walk_no_root, 0,\n+\t\t\t\t\taw-\u003em_seq, \u0026aw-\u003eseq, \u0026next_seq);\n+\tif (!parent)\n+\t\t/* The real root, unless the mount tree moved. */\n+\t\treturn read_seqretry(\u0026mount_lock, aw-\u003em_seq) ? -ECHILD : 1;\n+\tif (IS_ERR(parent))\n+\t\treturn PTR_ERR(parent);\n+\t/* A crossing onto a disconnected root, as in vfs_walk_step_ref(). */\n+\taw-\u003epos_flags = parent == aw-\u003epos.dentry ?\n+\t\tVFS_WALK_POS_DISCONNECTED | VFS_WALK_POS_MOUNTPOINT :\n+\t\tvfs_walk_pos_flags(aw-\u003epos.mnt, parent);\n+\taw-\u003epos.dentry = parent;\n+\taw-\u003eseq = next_seq;\n+\treturn 0;\n+}\n+\n+/**\n+ * vfs_walk_next - yield the walk's next position in @aw-\u003epos\n+ * @aw: the walk\n+ *\n+ * The first call yields the starting position itself. @aw-\u003epos_flags\n+ * carries the position's %VFS_WALK_POS_* flags, with the semantics\n+ * described at vfs_walk_ancestors().\n+ *\n+ * Returns: 0 with @aw-\u003epos valid, 1 once the walk has passed the real\n+ * root, -ECHILD when an rcu-mode walk lost a race and must be retried\n+ * (typically in the referenced mode).\n+ */\n+int vfs_walk_next(struct vfs_ancestor_walk *aw)\n+{\n+\tif (aw-\u003eflags \u0026 VFS_WALK_STARTED) {\n+\t\tint err = (aw-\u003eflags \u0026 VFS_WALK_RCU) ?\n+\t\t\tvfs_walk_step_rcu(aw) : vfs_walk_step_ref(aw);\n+\n+\t\tif (err)\n+\t\t\treturn err;\n+\t} else {\n+\t\taw-\u003eflags |= VFS_WALK_STARTED;\n+\t\taw-\u003epos_flags = vfs_walk_pos_flags(aw-\u003epos.mnt, aw-\u003epos.dentry);\n+\t}\n+\t/* The flags must describe the dentry the seq covers. */\n+\tif ((aw-\u003eflags \u0026 VFS_WALK_RCU) \u0026\u0026\n+\t read_seqcount_retry(\u0026aw-\u003epos.dentry-\u003ed_seq, aw-\u003eseq))\n+\t\treturn -ECHILD;\n+\treturn 0;\n+}\n+\n+/**\n+ * vfs_walk_end - finish a stepwise ancestor walk\n+ * @aw: the walk; its positions, including @aw-\u003epos, become invalid\n+ */\n+void vfs_walk_end(struct vfs_ancestor_walk *aw)\n+{\n+\tif (!(aw-\u003eflags \u0026 VFS_WALK_RCU))\n+\t\tpath_put(\u0026aw-\u003epos);\n+}\n+\n+/**\n+ * vfs_walk_handover - continue an rcu-mode walk with references\n+ * @to: walk state to begin at @from's current position, owning the\n+ * references acquired on it; valid until vfs_walk_end() either way\n+ * @from: an rcu-mode walk, positioned by a 0 return from vfs_walk_next()\n+ *\n+ * Mirrors unlazy_walk(): @from's current position is legitimized and\n+ * becomes the starting position of the referenced walk @to, which the\n+ * first vfs_walk_next() on it yields. A failed legitimization leaves no\n+ * partial references behind and a successful one moves straight into @to,\n+ * so nothing is ever put here and the handover is safe within the caller's\n+ * RCU read-side critical section - where a path_put() would not be, dput()\n+ * being allowed to sleep. @to itself is only usable once the caller has\n+ * left it, its walk being reference-based.\n+ *\n+ * @from is untouched on success and may keep stepping, lockless, from\n+ * where it stands. The references do not conclude its walk: a concurrent\n+ * rename may relocate the position the instant they are taken, as it may\n+ * during any reference-based walk.\n+ *\n+ * Returns: false iff @from lost a race; it is then dead, as after -ECHILD\n+ * from vfs_walk_next(), and @to is zeroed - safe to vfs_walk_end(), but\n+ * not to step, so the caller has to remember it never started.\n+ */\n+bool vfs_walk_handover(struct vfs_ancestor_walk *to,\n+\t\t struct vfs_ancestor_walk *from)\n+{\n+\tstruct path pos = from-\u003epos;\n+\tint err;\n+\n+\terr = __legitimize_mnt(pos.mnt, from-\u003em_seq);\n+\tif (unlikely(err)) {\n+\t\tif (err \u003c 0)\n+\t\t\tmnt_undo_legitimize(real_mount(pos.mnt));\n+\t\tgoto dead;\n+\t}\n+\tif (unlikely(read_seqcount_retry(\u0026pos.dentry-\u003ed_seq, from-\u003eseq) ||\n+\t\t !lockref_get_not_dead(\u0026pos.dentry-\u003ed_lockref))) {\n+\t\tmnt_undo_legitimize(real_mount(pos.mnt));\n+\t\tgoto dead;\n+\t}\n+\tto-\u003epos = pos;\n+\tto-\u003eflags = 0;\n+\tto-\u003epos_flags = 0;\n+\treturn true;\n+\n+dead:\n+\tmemset(to, 0, sizeof(*to));\n+\treturn false;\n+}\n+\n+/**\n+ * vfs_walk_ancestors - invoke a callback on a path and each of its ancestors\n+ * @path: path to walk up from; the caller's path is never modified\n+ * @cb: callback invoked on @path, then on each ancestor up to the real\n+ * root, crossing mount boundaries. @cb must not sleep and returns\n+ * %VFS_WALK_CONTINUE, %VFS_WALK_STOP or a negative errno to abort the\n+ * walk; -ECHILD is reserved (see below). @ancestor is only valid\n+ * during the invocation; @cb must take its own references to keep a\n+ * position.\n+ * A position whose dentry is a disconnected root is flagged with\n+ * %VFS_WALK_POS_DISCONNECTED (plus %VFS_WALK_POS_MOUNTPOINT when it\n+ * is a mountpoint a mount crossing landed on rather than a parent);\n+ * if @cb continues over it, the walk resumes at the root of that\n+ * position's mount.\n+ * With %VFS_WALK_RCU, @cb accepts positions the walk holds no\n+ * references on: the walk then runs lockless (under rcu_read_lock)\n+ * and returns -ECHILD when it loses a race, or when @cb returns\n+ * -ECHILD. The caller should then discard any state @cb accumulated\n+ * and retry without %VFS_WALK_RCU.\n+ * @data: opaque argument passed to @cb\n+ * @flags: %VFS_WALK_RCU if @cb copes with unreferenced positions\n+ *\n+ * Returns: 0 once the real root was reached, 1 if @cb stopped the walk,\n+ * -ECHILD if a lockless walk must be retried with references, or the\n+ * negative errno @cb aborted with.\n+ */\n+int vfs_walk_ancestors(const struct path *path,\n+\t\t int (*cb)(const struct path *ancestor,\n+\t\t\t\t unsigned int pos_flags, void *data),\n+\t\t void *data, unsigned int flags)\n+{\n+\tstruct vfs_ancestor_walk aw;\n+\tint ret;\n+\n+\tif (flags \u0026 VFS_WALK_RCU)\n+\t\trcu_read_lock();\n+\tvfs_walk_start(\u0026aw, path, flags);\n+\tfor (;;) {\n+\t\tret = vfs_walk_next(\u0026aw);\n+\t\tif (ret) {\n+\t\t\tif (ret == 1)\n+\t\t\t\t/* The real root was reached. */\n+\t\t\t\tret = 0;\n+\t\t\tbreak;\n+\t\t}\n+\t\tret = cb(\u0026aw.pos, aw.pos_flags, data);\n+\t\tif (ret \u003c 0)\n+\t\t\tbreak;\n+\t\tif (ret == VFS_WALK_STOP) {\n+\t\t\tret = 1;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\tvfs_walk_end(\u0026aw);\n+\tif (flags \u0026 VFS_WALK_RCU)\n+\t\trcu_read_unlock();\n+\treturn ret;\n+}\n+\n+static struct dentry *follow_dotdot(struct nameidata *nd)\n+{\n+\tstruct dentry *parent;\n+\n+\tif (path_equal(\u0026nd-\u003epath, \u0026nd-\u003eroot)) {\n+\t\t/* nd-\u003eroot is never path_connected()-checked. */\n+\t\tif (unlikely(nd-\u003eflags \u0026 LOOKUP_BENEATH))\n+\t\t\treturn ERR_PTR(-EXDEV);\n+\t\treturn dget(nd-\u003epath.dentry);\n+\t}\n+\tparent = __path_walk_parent(\u0026nd-\u003epath, \u0026nd-\u003eroot, nd-\u003eflags);\n+\tnd-\u003einode = nd-\u003epath.dentry-\u003ed_inode;\n+\tif (IS_ERR(parent))\n+\t\treturn parent;\n \tif (unlikely(!path_connected(nd-\u003epath.mnt, parent))) {\n \t\tdput(parent);\n \t\treturn ERR_PTR(-ENOENT);\n \t}\n \treturn parent;\n-\n-in_root:\n-\tif (unlikely(nd-\u003eflags \u0026 LOOKUP_BENEATH))\n-\t\treturn ERR_PTR(-EXDEV);\n-\treturn dget(nd-\u003epath.dentry);\n }\n \n static const char *handle_dots(struct nameidata *nd, enum last_type type)\ndiff --git a/fs/namespace.c b/fs/namespace.c\nindex 580877e46b1ac..920b8a85f9fad 100644\n--- a/fs/namespace.c\n+++ b/fs/namespace.c\n@@ -767,11 +767,8 @@ static bool legitimize_mnt(struct vfsmount *bastard, unsigned seq)\n \tint res = __legitimize_mnt(bastard, seq);\n \tif (likely(!res))\n \t\treturn true;\n-\tif (unlikely(res \u003c 0)) {\n-\t\trcu_read_unlock();\n-\t\tmntput(bastard);\n-\t\trcu_read_lock();\n-\t}\n+\tif (unlikely(res \u003c 0))\n+\t\tmnt_undo_legitimize(real_mount(bastard));\n \treturn false;\n }\n \n@@ -1330,8 +1327,19 @@ static void delayed_mntput(struct work_struct *unused)\n \tstruct llist_node *node = llist_del_all(\u0026delayed_mntput_list);\n \tstruct mount *m, *t;\n \n-\tllist_for_each_entry_safe(m, t, node, mnt_llist)\n-\t\tcleanup_mnt(m);\n+\tllist_for_each_entry_safe(m, t, node, mnt_llist) {\n+\t\t/*\n+\t\t * MNT_DOOMED is only ever set on the mounts\n+\t\t * mntput_no_expire_slowpath() queues here, never on the\n+\t\t * kept-count ones mnt_undo_legitimize() queues. Route on it\n+\t\t * rather than on mnt_get_count(), which a concurrent failed\n+\t\t * __legitimize_mnt() can transiently inflate.\n+\t\t */\n+\t\tif (m-\u003emnt.mnt_flags \u0026 MNT_DOOMED)\n+\t\t\tcleanup_mnt(m);\n+\t\telse\n+\t\t\tmntput(\u0026m-\u003emnt);\t/* kept by mnt_undo_legitimize() */\n+\t}\n }\n static DECLARE_DELAYED_WORK(delayed_mntput_work, delayed_mntput);\n \n@@ -1423,6 +1431,47 @@ void mntput(struct vfsmount *mnt)\n }\n EXPORT_SYMBOL(mntput);\n \n+/**\n+ * mnt_undo_legitimize - drop a count __legitimize_mnt() asked us to put\n+ * @mnt: the mount __legitimize_mnt() failed on\n+ *\n+ * Like the mntput() that a failed __legitimize_mnt() normally obliges,\n+ * but callable from the RCU read-side critical section the legitimization\n+ * ran under: when the count turns out to be the mount's last, it is kept\n+ * and the mount handed to delayed_mntput() for the sleepable final put.\n+ * Unlike mntput(), mnt_expiry_mark is left alone: a walker that failed\n+ * to legitimize the mount never used it.\n+ */\n+void mnt_undo_legitimize(struct mount *mnt)\n+{\n+\tif (likely(READ_ONCE(mnt-\u003emnt_ns))) {\n+\t\t/* Not the final count, as in mntput_no_expire(). */\n+\t\tmnt_add_count(mnt, -1);\n+\t\treturn;\n+\t}\n+\tlock_mount_hash();\n+\t/*\n+\t * As in mntput_no_expire(): make sure that if a concurrent\n+\t * __legitimize_mnt() has not seen us grab mount_lock, we'll see\n+\t * its refcount increment here.\n+\t */\n+\tsmp_mb();\n+\tif (likely(mnt_get_count(mnt) \u003e 1)) {\n+\t\tmnt_add_count(mnt, -1);\n+\t\tunlock_mount_hash();\n+\t\treturn;\n+\t}\n+\tunlock_mount_hash();\n+\t/*\n+\t * Ours is the last count: nothing else can reach the mount any\n+\t * more, which makes its mnt_llist ours to use. delayed_mntput()\n+\t * tells our kept-count mount from the doomed ones by MNT_DOOMED.\n+\t */\n+\tWARN_ON_ONCE(mnt-\u003emnt.mnt_flags \u0026 MNT_DOOMED);\n+\tif (llist_add(\u0026mnt-\u003emnt_llist, \u0026delayed_mntput_list))\n+\t\tschedule_delayed_work(\u0026delayed_mntput_work, 1);\n+}\n+\n struct vfsmount *mntget(struct vfsmount *mnt)\n {\n \tif (mnt)\ndiff --git a/include/linux/namei.h b/include/linux/namei.h\nindex 86d657b24fc6a..8820a2833213d 100644\n--- a/include/linux/namei.h\n+++ b/include/linux/namei.h\n@@ -162,6 +162,23 @@ extern int follow_down_one(struct path *);\n extern int follow_down(struct path *path, unsigned int flags);\n extern int follow_up(struct path *);\n \n+/* vfs_walk_ancestors() flags */\n+#define VFS_WALK_RCU\t\t\tBIT(0)\n+\n+/* per-position flags passed to the vfs_walk_ancestors() callback */\n+#define VFS_WALK_POS_DISCONNECTED\tBIT(0)\n+/* the position is a mountpoint landed on by a mount crossing */\n+#define VFS_WALK_POS_MOUNTPOINT\t\tBIT(1)\n+\n+/* vfs_walk_ancestors() callback verdicts; negative values abort the walk */\n+#define VFS_WALK_STOP\t\t0\n+#define VFS_WALK_CONTINUE\t1\n+\n+int vfs_walk_ancestors(const struct path *path,\n+\t\t int (*cb)(const struct path *ancestor,\n+\t\t\t\t unsigned int pos_flags, void *data),\n+\t\t void *data, unsigned int flags);\n+\n int start_renaming(struct renamedata *rd, int lookup_flags,\n \t\t struct qstr *old_last, struct qstr *new_last);\n int start_renaming_dentry(struct renamedata *rd, int lookup_flags,\ndiff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c\nindex fd3c0206bd67d..3294b2a431674 100644\n--- a/kernel/bpf/verifier.c\n+++ b/kernel/bpf/verifier.c\n@@ -6300,6 +6300,10 @@ BTF_TYPE_SAFE_TRUSTED(struct scx_sub_detach_args) {\n \tstruct sched_ext_ops *ops;\n };\n \n+BTF_TYPE_SAFE_TRUSTED(struct path) {\n+\tstruct dentry *dentry;\n+};\n+\n BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct dentry) {\n \tstruct inode *d_inode;\n };\n@@ -6352,6 +6356,7 @@ static bool type_is_trusted(struct bpf_verifier_env *env,\n \tBTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED(struct scx_cpu_release_args));\n \tBTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED(struct scx_sub_attach_args));\n \tBTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED(struct scx_sub_detach_args));\n+\tBTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED(struct path));\n \n \treturn btf_nested_type_is_trusted(\u0026env-\u003elog, reg, field_name, btf_id, \"__safe_trusted\");\n }\n@@ -8307,6 +8312,24 @@ static bool is_iter_destroy_kfunc(struct bpf_call_arg_meta *meta)\n \treturn meta-\u003ekfunc_flags \u0026 KF_ITER_DESTROY;\n }\n \n+/*\n+ * An \"__uninit\"-suffixed iterator argument of a kfunc that is not itself an\n+ * iterator method: the kfunc initializes that iterator state, as a\n+ * bpf_iter_\u003ctype\u003e_new() does, but from inputs the constructor naming\n+ * convention cannot express - e.g. another, already-initialized iterator.\n+ * Only iterator-typed arguments qualify, so \"__uninit\" dynptr out-arguments\n+ * are not reclassified.\n+ */\n+static bool is_kfunc_arg_iter_init(struct bpf_call_arg_meta *meta, int arg_idx,\n+\t\t\t\t const struct btf_param *arg)\n+{\n+\tif (is_iter_kfunc(meta))\n+\t\treturn false;\n+\n+\treturn btf_param_match_suffix(meta-\u003ebtf, arg, \"__uninit\") \u0026\u0026\n+\t btf_check_iter_arg(meta-\u003ebtf, meta-\u003efunc_proto, arg_idx) \u003e= 0;\n+}\n+\n static bool is_kfunc_arg_iter(struct bpf_call_arg_meta *meta, int arg_idx,\n \t\t\t const struct btf_param *arg)\n {\n@@ -8317,7 +8340,11 @@ static bool is_kfunc_arg_iter(struct bpf_call_arg_meta *meta, int arg_idx,\n \t\treturn arg_idx == 0;\n \n \t/* iter passed as an argument to a generic kfunc */\n-\treturn btf_param_match_suffix(meta-\u003ebtf, arg, \"__iter\");\n+\tif (btf_param_match_suffix(meta-\u003ebtf, arg, \"__iter\"))\n+\t\treturn true;\n+\n+\t/* iter state a generic kfunc initializes */\n+\treturn is_kfunc_arg_iter_init(meta, arg_idx, arg);\n }\n \n static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\n@@ -8327,6 +8354,7 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *\n \tstruct bpf_func_state *state = bpf_func(env, reg);\n \tconst struct btf_type *t;\n \tint spi, err, i, nr_slots, btf_id;\n+\tbool init;\n \n \tif (reg-\u003etype != PTR_TO_STACK) {\n \t\tverbose(env, \"%s expected pointer to an iterator on stack\\n\",\n@@ -8358,8 +8386,16 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *\n \tt = btf_type_by_id(meta-\u003ebtf, btf_id);\n \tnr_slots = t-\u003esize / BPF_REG_SIZE;\n \n-\tif (is_iter_new_kfunc(meta)) {\n-\t\t/* bpf_iter_\u003ctype\u003e_new() expects pointer to uninit iter state */\n+\t/*\n+\t * Whether this argument is the iterator the call initializes, rather\n+\t * than an initialized one it operates on.\n+\t */\n+\tinit = is_iter_new_kfunc(meta) ||\n+\t is_kfunc_arg_iter_init(meta, arg,\n+\t\t\t\t \u0026btf_params(meta-\u003efunc_proto)[arg]);\n+\n+\tif (init) {\n+\t\t/* expects a pointer to uninit iter state */\n \t\tif (!is_iter_reg_valid_uninit(env, reg, nr_slots)) {\n \t\t\tverbose(env, \"expected uninitialized iter_%s as %s\\n\",\n \t\t\t\titer_type_str(meta-\u003ebtf, btf_id), reg_arg_name(env, argno));\n@@ -8643,6 +8679,13 @@ static int process_iter_next_call(struct bpf_verifier_env *env, int insn_idx,\n \t/* switch to DRAINED state, but keep the depth unchanged */\n \t/* mark current iter state as drained and assume returned NULL */\n \tcur_iter-\u003eiter.state = BPF_ITER_STATE_DRAINED;\n+\t/*\n+\t * An acquiring iter_next() hands out nothing once drained: the\n+\t * acquired reference exists only in the forked active state, not on\n+\t * this NULL-returning branch.\n+\t */\n+\tif (meta-\u003ekfunc_flags \u0026 KF_ACQUIRE)\n+\t\tWARN_ON_ONCE(release_reference_nomark(env, cur_fr-\u003eregs[BPF_REG_0].id));\n \t__mark_reg_const_zero(env, \u0026cur_fr-\u003eregs[BPF_REG_0]);\n \n \treturn 0;\ndiff --git a/security/landlock/fs.c b/security/landlock/fs.c\nindex cab43892ec2f0..369e2e9c83ad7 100644\n--- a/security/landlock/fs.c\n+++ b/security/landlock/fs.c\n@@ -751,6 +751,107 @@ static void test_is_eacces_with_write(struct kunit *const test)\n #undef IE_TRUE\n #undef IE_FALSE\n \n+struct landlock_walk_state {\n+\tconst struct landlock_domain *domain;\n+\tstruct layer_masks *layer_masks_parent1, *layer_masks_parent2;\n+\tconst struct layer_masks *layer_masks_child1, *layer_masks_child2;\n+\taccess_mask_t access_request_parent1, access_request_parent2;\n+\taccess_mask_t access_masked_parent1, access_masked_parent2;\n+\tbool child1_is_directory, child2_is_directory;\n+\tbool allowed_parent1, allowed_parent2, is_dom_check;\n+};\n+\n+static int check_access_path_walk(const struct path *const ancestor,\n+\t\t\t\t const unsigned int pos_flags,\n+\t\t\t\t void *const data)\n+{\n+\tstruct landlock_walk_state *const state = data;\n+\tstruct landlock_id id = {\n+\t\t.type = LANDLOCK_KEY_INODE,\n+\t};\n+\n+\tif (unlikely(pos_flags \u0026 VFS_WALK_POS_DISCONNECTED)) {\n+\t\tif (likely(ancestor-\u003emnt-\u003emnt_flags \u0026 MNT_INTERNAL)) {\n+\t\t\t/*\n+\t\t\t * Stops and allows access when reaching disconnected\n+\t\t\t * root directories that are part of internal\n+\t\t\t * filesystems (e.g. nsfs, which is reachable through\n+\t\t\t * /proc/\u003cpid\u003e/ns/\u003cnamespace\u003e).\n+\t\t\t */\n+\t\t\tstate-\u003eallowed_parent1 = true;\n+\t\t\tstate-\u003eallowed_parent2 = true;\n+\t\t\treturn VFS_WALK_STOP;\n+\t\t}\n+\t\t/*\n+\t\t * The old loop never visited the mountpoints a mount\n+\t\t * crossing lands on: don't match them against rules. Other\n+\t\t * disconnected roots keep being matched below, as before.\n+\t\t */\n+\t\tif (pos_flags \u0026 VFS_WALK_POS_MOUNTPOINT)\n+\t\t\treturn VFS_WALK_CONTINUE;\n+\t}\n+\n+\t/*\n+\t * If at least all accesses allowed on the destination are already\n+\t * allowed on the source, respectively if there is at least as much as\n+\t * restrictions on the destination than on the source, then we can\n+\t * safely refer files from the source to the destination without\n+\t * risking a privilege escalation. This also applies in the case of\n+\t * RENAME_EXCHANGE, which implies checks on both direction. This is\n+\t * crucial for standalone multilayered security policies. Furthermore,\n+\t * this helps avoid policy writers to shoot themselves in the foot.\n+\t */\n+\tif (unlikely(state-\u003eis_dom_check \u0026\u0026\n+\t\t no_more_access(state-\u003elayer_masks_parent1,\n+\t\t\t\t state-\u003elayer_masks_child1,\n+\t\t\t\t state-\u003echild1_is_directory,\n+\t\t\t\t state-\u003elayer_masks_parent2,\n+\t\t\t\t state-\u003elayer_masks_child2,\n+\t\t\t\t state-\u003echild2_is_directory))) {\n+\t\t/*\n+\t\t * Now, downgrades the remaining checks from domain handled\n+\t\t * accesses to requested accesses.\n+\t\t */\n+\t\tstate-\u003eis_dom_check = false;\n+\t\tstate-\u003eaccess_masked_parent1 = state-\u003eaccess_request_parent1;\n+\t\tstate-\u003eaccess_masked_parent2 = state-\u003eaccess_request_parent2;\n+\n+\t\tstate-\u003eallowed_parent1 =\n+\t\t\tstate-\u003eallowed_parent1 ||\n+\t\t\tscope_to_request(state-\u003eaccess_masked_parent1,\n+\t\t\t\t\t state-\u003elayer_masks_parent1);\n+\t\tstate-\u003eallowed_parent2 =\n+\t\t\tstate-\u003eallowed_parent2 ||\n+\t\t\tscope_to_request(state-\u003eaccess_masked_parent2,\n+\t\t\t\t\t state-\u003elayer_masks_parent2);\n+\n+\t\t/* Stops when all accesses are granted. */\n+\t\tif (state-\u003eallowed_parent1 \u0026\u0026 state-\u003eallowed_parent2)\n+\t\t\treturn VFS_WALK_STOP;\n+\t}\n+\n+\tif (get_inode_id(ancestor-\u003edentry, \u0026id)) {\n+\t\tstate-\u003eallowed_parent1 =\n+\t\t\tstate-\u003eallowed_parent1 ||\n+\t\t\tunmask_layers_fs(state-\u003edomain, id,\n+\t\t\t\t\t state-\u003eaccess_masked_parent1,\n+\t\t\t\t\t state-\u003elayer_masks_parent1,\n+\t\t\t\t\t ancestor-\u003edentry);\n+\t\tstate-\u003eallowed_parent2 =\n+\t\t\tstate-\u003eallowed_parent2 ||\n+\t\t\tunmask_layers_fs(state-\u003edomain, id,\n+\t\t\t\t\t state-\u003eaccess_masked_parent2,\n+\t\t\t\t\t state-\u003elayer_masks_parent2,\n+\t\t\t\t\t ancestor-\u003edentry);\n+\t}\n+\n+\t/* Stops when a rule from each layer grants access. */\n+\tif (state-\u003eallowed_parent1 \u0026\u0026 state-\u003eallowed_parent2)\n+\t\treturn VFS_WALK_STOP;\n+\n+\treturn VFS_WALK_CONTINUE;\n+}\n+\n /**\n * is_access_to_paths_allowed - Check accesses for requests with a common path\n *\n@@ -803,16 +904,16 @@ is_access_to_paths_allowed(const struct landlock_domain *const domain,\n \t\t\t struct landlock_request *const log_request_parent2,\n \t\t\t struct dentry *const dentry_child2)\n {\n-\tbool allowed_parent1 = false, allowed_parent2 = false, is_dom_check,\n-\t child1_is_directory = true, child2_is_directory = true;\n-\tstruct path walker_path;\n-\tstruct landlock_id id = {\n-\t\t.type = LANDLOCK_KEY_INODE,\n-\t};\n-\taccess_mask_t access_masked_parent1, access_masked_parent2;\n \tstruct layer_masks _layer_masks_child1, _layer_masks_child2;\n-\tstruct layer_masks *layer_masks_child1 = NULL,\n-\t\t\t *layer_masks_child2 = NULL;\n+\tstruct landlock_walk_state state = {\n+\t\t.domain = domain,\n+\t\t.layer_masks_parent1 = layer_masks_parent1,\n+\t\t.layer_masks_parent2 = layer_masks_parent2,\n+\t\t.access_request_parent1 = access_request_parent1,\n+\t\t.access_request_parent2 = access_request_parent2,\n+\t\t.child1_is_directory = true,\n+\t\t.child2_is_directory = true,\n+\t};\n \n \tif (!access_request_parent1 \u0026\u0026 !access_request_parent2)\n \t\treturn true;\n@@ -826,37 +927,39 @@ is_access_to_paths_allowed(const struct landlock_domain *const domain,\n \tif (WARN_ON_ONCE(!layer_masks_parent1))\n \t\treturn false;\n \n-\tallowed_parent1 = is_layer_masks_allowed(layer_masks_parent1);\n+\tstate.allowed_parent1 = is_layer_masks_allowed(layer_masks_parent1);\n \n \tif (unlikely(layer_masks_parent2)) {\n \t\tif (WARN_ON_ONCE(!dentry_child1))\n \t\t\treturn false;\n \n-\t\tallowed_parent2 = is_layer_masks_allowed(layer_masks_parent2);\n+\t\tstate.allowed_parent2 =\n+\t\t\tis_layer_masks_allowed(layer_masks_parent2);\n \n \t\t/*\n \t\t * For a double request, first check for potential privilege\n \t\t * escalation by looking at domain handled accesses (which are\n \t\t * a superset of the meaningful requested accesses).\n \t\t */\n-\t\taccess_masked_parent1 = access_masked_parent2 =\n+\t\tstate.access_masked_parent1 =\n \t\t\tlandlock_union_access_masks(domain).fs;\n-\t\tis_dom_check = true;\n+\t\tstate.access_masked_parent2 = state.access_masked_parent1;\n+\t\tstate.is_dom_check = true;\n \t} else {\n \t\tif (WARN_ON_ONCE(dentry_child1 || dentry_child2))\n \t\t\treturn false;\n \t\t/* For a simple request, only check for requested accesses. */\n-\t\taccess_masked_parent1 = access_request_parent1;\n-\t\taccess_masked_parent2 = access_request_parent2;\n+\t\tstate.access_masked_parent1 = access_request_parent1;\n+\t\tstate.access_masked_parent2 = access_request_parent2;\n \t\t/*\n \t\t * Simple requests have no parent2 to check, so parent2 is\n \t\t * trivially allowed. This must be set explicitly because the\n-\t\t * get_inode_id() gate in the pathwalk loop may prevent\n+\t\t * get_inode_id() gate in the walk callback may prevent\n \t\t * landlock_unmask_layers() from being called (which would\n \t\t * otherwise return true for NULL masks as a side effect).\n \t\t */\n-\t\tallowed_parent2 = true;\n-\t\tis_dom_check = false;\n+\t\tstate.allowed_parent2 = true;\n+\t\tstate.is_dom_check = false;\n \t}\n \n \tif (unlikely(dentry_child1)) {\n@@ -872,8 +975,8 @@ is_access_to_paths_allowed(const struct landlock_domain *const domain,\n \t\tif (handled \u0026\u0026 get_inode_id(dentry_child1, \u0026id))\n \t\t\tunmask_layers_fs(domain, id, handled,\n \t\t\t\t\t \u0026_layer_masks_child1, dentry_child1);\n-\t\tlayer_masks_child1 = \u0026_layer_masks_child1;\n-\t\tchild1_is_directory = d_is_dir(dentry_child1);\n+\t\tstate.layer_masks_child1 = \u0026_layer_masks_child1;\n+\t\tstate.child1_is_directory = d_is_dir(dentry_child1);\n \t}\n \tif (unlikely(dentry_child2)) {\n \t\tstruct landlock_id id = {\n@@ -888,118 +991,16 @@ is_access_to_paths_allowed(const struct landlock_domain *const domain,\n \t\tif (handled \u0026\u0026 get_inode_id(dentry_child2, \u0026id))\n \t\t\tunmask_layers_fs(domain, id, handled,\n \t\t\t\t\t \u0026_layer_masks_child2, dentry_child2);\n-\t\tlayer_masks_child2 = \u0026_layer_masks_child2;\n-\t\tchild2_is_directory = d_is_dir(dentry_child2);\n+\t\tstate.layer_masks_child2 = \u0026_layer_masks_child2;\n+\t\tstate.child2_is_directory = d_is_dir(dentry_child2);\n \t}\n \n-\twalker_path = *path;\n-\tpath_get(\u0026walker_path);\n \t/*\n \t * We need to walk through all the hierarchy to not miss any relevant\n-\t * restriction.\n+\t * restriction. Reaching the real root without a grant from each\n+\t * layer denies access.\n \t */\n-\twhile (true) {\n-\t\t/*\n-\t\t * If at least all accesses allowed on the destination are\n-\t\t * already allowed on the source, respectively if there is at\n-\t\t * least as much as restrictions on the destination than on the\n-\t\t * source, then we can safely refer files from the source to\n-\t\t * the destination without risking a privilege escalation.\n-\t\t * This also applies in the case of RENAME_EXCHANGE, which\n-\t\t * implies checks on both direction. This is crucial for\n-\t\t * standalone multilayered security policies. Furthermore,\n-\t\t * this helps avoid policy writers to shoot themselves in the\n-\t\t * foot.\n-\t\t */\n-\t\tif (unlikely(is_dom_check \u0026\u0026\n-\t\t\t no_more_access(\n-\t\t\t\t layer_masks_parent1, layer_masks_child1,\n-\t\t\t\t child1_is_directory, layer_masks_parent2,\n-\t\t\t\t layer_masks_child2,\n-\t\t\t\t child2_is_directory))) {\n-\t\t\t/*\n-\t\t\t * Now, downgrades the remaining checks from domain\n-\t\t\t * handled accesses to requested accesses.\n-\t\t\t */\n-\t\t\tis_dom_check = false;\n-\t\t\taccess_masked_parent1 = access_request_parent1;\n-\t\t\taccess_masked_parent2 = access_request_parent2;\n-\n-\t\t\tallowed_parent1 =\n-\t\t\t\tallowed_parent1 ||\n-\t\t\t\tscope_to_request(access_masked_parent1,\n-\t\t\t\t\t\t layer_masks_parent1);\n-\t\t\tallowed_parent2 =\n-\t\t\t\tallowed_parent2 ||\n-\t\t\t\tscope_to_request(access_masked_parent2,\n-\t\t\t\t\t\t layer_masks_parent2);\n-\n-\t\t\t/* Stops when all accesses are granted. */\n-\t\t\tif (allowed_parent1 \u0026\u0026 allowed_parent2)\n-\t\t\t\tbreak;\n-\t\t}\n-\n-\t\tif (get_inode_id(walker_path.dentry, \u0026id)) {\n-\t\t\tallowed_parent1 =\n-\t\t\t\tallowed_parent1 ||\n-\t\t\t\tunmask_layers_fs(domain, id,\n-\t\t\t\t\t\t access_masked_parent1,\n-\t\t\t\t\t\t layer_masks_parent1,\n-\t\t\t\t\t\t walker_path.dentry);\n-\t\t\tallowed_parent2 =\n-\t\t\t\tallowed_parent2 ||\n-\t\t\t\tunmask_layers_fs(domain, id,\n-\t\t\t\t\t\t access_masked_parent2,\n-\t\t\t\t\t\t layer_masks_parent2,\n-\t\t\t\t\t\t walker_path.dentry);\n-\t\t}\n-\n-\t\t/* Stops when a rule from each layer grants access. */\n-\t\tif (allowed_parent1 \u0026\u0026 allowed_parent2)\n-\t\t\tbreak;\n-\n-jump_up:\n-\t\tif (walker_path.dentry == walker_path.mnt-\u003emnt_root) {\n-\t\t\tif (follow_up(\u0026walker_path)) {\n-\t\t\t\t/* Ignores hidden mount points. */\n-\t\t\t\tgoto jump_up;\n-\t\t\t} else {\n-\t\t\t\t/*\n-\t\t\t\t * Stops at the real root. Denies access\n-\t\t\t\t * because not all layers have granted access.\n-\t\t\t\t */\n-\t\t\t\tbreak;\n-\t\t\t}\n-\t\t}\n-\n-\t\tif (unlikely(IS_ROOT(walker_path.dentry))) {\n-\t\t\tif (likely(walker_path.mnt-\u003emnt_flags \u0026 MNT_INTERNAL)) {\n-\t\t\t\t/*\n-\t\t\t\t * Stops and allows access when reaching disconnected root\n-\t\t\t\t * directories that are part of internal filesystems (e.g. nsfs,\n-\t\t\t\t * which is reachable through /proc/\u003cpid\u003e/ns/\u003cnamespace\u003e).\n-\t\t\t\t */\n-\t\t\t\tallowed_parent1 = true;\n-\t\t\t\tallowed_parent2 = true;\n-\t\t\t\tbreak;\n-\t\t\t}\n-\n-\t\t\t/*\n-\t\t\t * We reached a disconnected root directory from a bind mount.\n-\t\t\t * Let's continue the walk with the mount point we missed.\n-\t\t\t */\n-\t\t\tdput(walker_path.dentry);\n-\t\t\twalker_path.dentry = walker_path.mnt-\u003emnt_root;\n-\t\t\tdget(walker_path.dentry);\n-\t\t} else {\n-\t\t\tstruct dentry *const parent_dentry =\n-\t\t\t\tdget_parent(walker_path.dentry);\n-\n-\t\t\tdput(walker_path.dentry);\n-\t\t\twalker_path.dentry = parent_dentry;\n-\t\t}\n-\t}\n-\tpath_put(\u0026walker_path);\n+\tvfs_walk_ancestors(path, check_access_path_walk, \u0026state, 0);\n \n \t/*\n \t * Check CONFIG_SECURITY_LANDLOCK_LOG to enable elision of\n@@ -1007,24 +1008,24 @@ is_access_to_paths_allowed(const struct landlock_domain *const domain,\n \t * dead code elimination.\n \t */\n #ifdef CONFIG_SECURITY_LANDLOCK_LOG\n-\tif (!allowed_parent1 \u0026\u0026 log_request_parent1) {\n+\tif (!state.allowed_parent1 \u0026\u0026 log_request_parent1) {\n \t\tlog_request_parent1-\u003etype = LANDLOCK_REQUEST_FS_ACCESS;\n \t\tlog_request_parent1-\u003eaudit.type = LSM_AUDIT_DATA_PATH;\n \t\tlog_request_parent1-\u003eaudit.u.path = *path;\n-\t\tlog_request_parent1-\u003eaccess = access_masked_parent1;\n+\t\tlog_request_parent1-\u003eaccess = state.access_masked_parent1;\n \t\tlog_request_parent1-\u003elayer_masks = layer_masks_parent1;\n \t}\n \n-\tif (!allowed_parent2 \u0026\u0026 log_request_parent2) {\n+\tif (!state.allowed_parent2 \u0026\u0026 log_request_parent2) {\n \t\tlog_request_parent2-\u003etype = LANDLOCK_REQUEST_FS_ACCESS;\n \t\tlog_request_parent2-\u003eaudit.type = LSM_AUDIT_DATA_PATH;\n \t\tlog_request_parent2-\u003eaudit.u.path = *path;\n-\t\tlog_request_parent2-\u003eaccess = access_masked_parent2;\n+\t\tlog_request_parent2-\u003eaccess = state.access_masked_parent2;\n \t\tlog_request_parent2-\u003elayer_masks = layer_masks_parent2;\n \t}\n #endif /* CONFIG_SECURITY_LANDLOCK_LOG */\n \n-\treturn allowed_parent1 \u0026\u0026 allowed_parent2;\n+\treturn state.allowed_parent1 \u0026\u0026 state.allowed_parent2;\n }\n \n static int current_check_access_path(const struct path *const path,\ndiff --git a/tools/testing/selftests/bpf/prog_tests/path_ancestors.c b/tools/testing/selftests/bpf/prog_tests/path_ancestors.c\nnew file mode 100644\nindex 0000000000000..ce1ded844c3a5\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/prog_tests/path_ancestors.c\n@@ -0,0 +1,77 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Copyright (c) 2026 Justin Suess */\n+\n+#include \u003csys/stat.h\u003e\n+#include \u003csys/xattr.h\u003e\n+#include \u003cstdlib.h\u003e\n+#include \u003cunistd.h\u003e\n+#include \u003ctest_progs.h\u003e\n+#include \"path_ancestors.skel.h\"\n+\n+void test_path_ancestors(void)\n+{\n+\tchar base[] = \"/tmp/path_ancestors_XXXXXX\";\n+\tstruct path_ancestors *skel = NULL;\n+\tchar suba[280], subb[280];\n+\tbool xattr_works;\n+\tint err;\n+\n+\tif (!ASSERT_OK_PTR(mkdtemp(base), \"mkdtemp\"))\n+\t\treturn;\n+\tsnprintf(suba, sizeof(suba), \"%s/a\", base);\n+\tsnprintf(subb, sizeof(subb), \"%s/a/b\", base);\n+\tif (!ASSERT_OK(mkdir(suba, 0755), \"mkdir_a\"))\n+\t\tgoto out_rm;\n+\n+\t/* Read back by the program at the escalated position (== base). */\n+\terr = setxattr(base, \"user.walk\", \"hello\", 6, 0);\n+\txattr_works = !err;\n+\tif (err \u0026\u0026 errno != EOPNOTSUPP \u0026\u0026 !ASSERT_OK(err, \"setxattr\"))\n+\t\tgoto out_rm;\n+\n+\tskel = path_ancestors__open_and_load();\n+\tif (!ASSERT_OK_PTR(skel, \"open_and_load\"))\n+\t\tgoto out_rm;\n+\tskel-\u003ebss-\u003emonitored_pid = getpid();\n+\tif (!ASSERT_OK(path_ancestors__attach(skel), \"attach\"))\n+\t\tgoto out;\n+\n+\t/* mkdir b: the hook sees dir == suba, whose ancestry is walked. */\n+\tif (!ASSERT_OK(mkdir(subb, 0755), \"mkdir_b\"))\n+\t\tgoto out;\n+\n+\tASSERT_EQ(skel-\u003ebss-\u003etest_err, 0, \"test_err\");\n+\tASSERT_EQ(skel-\u003ebss-\u003eescalate_err, 0, \"escalate_err\");\n+\t/* suba, base, /tmp, / at least; equality across modes is the point. */\n+\tASSERT_GE(skel-\u003ebss-\u003ercu_count, 3, \"rcu_count\");\n+\tASSERT_EQ(skel-\u003ebss-\u003eref_count, skel-\u003ebss-\u003ercu_count, \"ref_vs_rcu\");\n+\tASSERT_EQ(skel-\u003ebss-\u003ercu_ns_count, skel-\u003ebss-\u003ercu_count,\n+\t\t \"nonsleepable_vs_rcu\");\n+\t/*\n+\t * The escalated position is walked twice: once lockless, then again\n+\t * as the resumed referenced iteration's first position.\n+\t */\n+\tASSERT_EQ(skel-\u003ebss-\u003ehybrid_count, skel-\u003ebss-\u003ercu_count + 1,\n+\t\t \"hybrid_vs_rcu\");\n+\tASSERT_EQ(skel-\u003ebss-\u003eretry_flags, 0, \"no_retry\");\n+\tASSERT_EQ(skel-\u003ebss-\u003eref_flags, 0, \"ref_flags\");\n+\n+\t/* The acquired second position, used after its step was taken. */\n+\tASSERT_STREQ(skel-\u003ebss-\u003esecond_path, base, \"second_path\");\n+\tASSERT_EQ(skel-\u003ebss-\u003esecond_len, strlen(base) + 1, \"second_len\");\n+\n+\t/* The escalated position is the walk's second one: base. */\n+\tASSERT_STREQ(skel-\u003ebss-\u003eescalated_path, base, \"escalated_path\");\n+\tASSERT_EQ(skel-\u003ebss-\u003eescalated_len, strlen(base) + 1, \"escalated_len\");\n+\tif (xattr_works) {\n+\t\tASSERT_EQ(skel-\u003ebss-\u003exattr_ret, 6, \"xattr_len\");\n+\t\tASSERT_STREQ(skel-\u003ebss-\u003exattr_value, \"hello\", \"xattr_value\");\n+\t}\n+\n+out:\n+\tpath_ancestors__destroy(skel);\n+out_rm:\n+\trmdir(subb);\n+\trmdir(suba);\n+\trmdir(base);\n+}\ndiff --git a/tools/testing/selftests/bpf/progs/path_ancestors.c b/tools/testing/selftests/bpf/progs/path_ancestors.c\nnew file mode 100644\nindex 0000000000000..50ce0ce163ddd\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/path_ancestors.c\n@@ -0,0 +1,134 @@\n+// SPDX-License-Identifier: GPL-2.0\n+/* Copyright (c) 2026 Justin Suess */\n+\n+#include \"vmlinux.h\"\n+#include \u003cerrno.h\u003e\n+#include \u003cbpf/bpf_helpers.h\u003e\n+#include \u003cbpf/bpf_tracing.h\u003e\n+#include \"bpf_kfuncs.h\"\n+\n+char _license[] SEC(\"license\") = \"GPL\";\n+\n+__u32 monitored_pid;\n+\n+int rcu_count;\t\t/* positions seen by the pure lockless walk */\n+int rcu_ns_count;\t/* ditto, from the non-sleepable program */\n+int ref_count;\t\t/* positions seen by the pure referenced walk */\n+int hybrid_count;\t/* positions seen by the lockless+escalate walk */\n+int retry_flags;\t/* BPF_PATH_ANCESTORS_RETRY observations */\n+int ref_flags;\t\t/* pos flags seen by the referenced walk */\n+int second_len;\t\t/* d_path length of the walk's second position */\n+int xattr_ret;\t\t/* xattr read at the escalated position */\n+int escalated_len;\t/* d_path length of the escalated position */\n+int escalate_err;\t/* bpf_path_ancestors_legitimize() result */\n+int test_err;\n+char second_path[256];\n+char escalated_path[256];\n+char xattr_value[16];\n+\n+static bool monitored(void)\n+{\n+\treturn (bpf_get_current_pid_tgid() \u003e\u003e 32) == monitored_pid;\n+}\n+\n+/*\n+ * Lockless walk from a non-sleepable program: the RCU critical section is\n+ * implicit, no bpf_rcu_read_lock() needed.\n+ */\n+SEC(\"lsm/path_mkdir\")\n+int BPF_PROG(rcu_nonsleepable, const struct path *dir, struct dentry *dentry,\n+\t umode_t mode)\n+{\n+\tstruct bpf_iter_path_ancestors_rcu rit;\n+\n+\tif (!monitored())\n+\t\treturn 0;\n+\n+\tbpf_iter_path_ancestors_rcu_new(\u0026rit, (struct path *)dir, 0);\n+\twhile (bpf_iter_path_ancestors_rcu_next(\u0026rit))\n+\t\trcu_ns_count++;\n+\tretry_flags |= bpf_path_ancestors_rcu_pos_flags(\u0026rit);\n+\tbpf_iter_path_ancestors_rcu_destroy(\u0026rit);\n+\treturn 0;\n+}\n+\n+SEC(\"lsm.s/path_mkdir\")\n+int BPF_PROG(walk_modes, const struct path *dir, struct dentry *dentry,\n+\t umode_t mode)\n+{\n+\tstruct bpf_iter_path_ancestors_rcu rit;\n+\tstruct bpf_iter_path_ancestors it;\n+\tstruct bpf_dynptr value_ptr;\n+\tstruct path *pos;\n+\n+\tif (!monitored())\n+\t\treturn 0;\n+\n+\t/*\n+\t * Mode 1: pure lockless, under an explicit RCU critical section.\n+\t * Positions are borrowed, so nothing is released here.\n+\t */\n+\tbpf_rcu_read_lock();\n+\tbpf_iter_path_ancestors_rcu_new(\u0026rit, (struct path *)dir, 0);\n+\twhile (bpf_iter_path_ancestors_rcu_next(\u0026rit))\n+\t\trcu_count++;\n+\tretry_flags |= bpf_path_ancestors_rcu_pos_flags(\u0026rit);\n+\tbpf_iter_path_ancestors_rcu_destroy(\u0026rit);\n+\tbpf_rcu_read_unlock();\n+\n+\t/*\n+\t * Mode 2: pure referenced. Every position comes acquired, so it\n+\t * stays valid for sleepable work and past the step that yielded it.\n+\t */\n+\tbpf_iter_path_ancestors_new(\u0026it, (struct path *)dir, 0);\n+\twhile ((pos = bpf_iter_path_ancestors_next(\u0026it))) {\n+\t\tref_count++;\n+\t\tref_flags |= bpf_path_ancestors_pos_flags(\u0026it);\n+\t\tif (ref_count == 2)\n+\t\t\tsecond_len = bpf_path_d_path(pos, second_path,\n+\t\t\t\t\t\t sizeof(second_path));\n+\t\tbpf_path_put(pos);\n+\t}\n+\tbpf_iter_path_ancestors_destroy(\u0026it);\n+\n+\t/*\n+\t * Mode 3: hybrid. Walk lockless to the second position, then hand\n+\t * that position over to a referenced iteration which resumes there.\n+\t */\n+\tbpf_rcu_read_lock();\n+\tbpf_iter_path_ancestors_rcu_new(\u0026rit, (struct path *)dir, 0);\n+\twhile (bpf_iter_path_ancestors_rcu_next(\u0026rit)) {\n+\t\thybrid_count++;\n+\t\tif (hybrid_count == 2)\n+\t\t\tbreak;\n+\t}\n+\tescalate_err = bpf_path_ancestors_legitimize(\u0026it, \u0026rit);\n+\tretry_flags |= bpf_path_ancestors_rcu_pos_flags(\u0026rit);\n+\tbpf_iter_path_ancestors_rcu_destroy(\u0026rit);\n+\tbpf_rcu_read_unlock();\n+\n+\tif (escalate_err)\n+\t\ttest_err = 1;\n+\n+\t/*\n+\t * Out of the RCU critical section. The resumed iteration's first\n+\t * position is the escalated one, kept alive by the reference the\n+\t * iteration hands out, so sleepable work can run on it.\n+\t */\n+\twhile ((pos = bpf_iter_path_ancestors_next(\u0026it))) {\n+\t\thybrid_count++;\n+\t\tif (hybrid_count == 3) {\n+\t\t\tescalated_len = bpf_path_d_path(pos, escalated_path,\n+\t\t\t\t\t\t\tsizeof(escalated_path));\n+\t\t\tbpf_dynptr_from_mem(xattr_value, sizeof(xattr_value),\n+\t\t\t\t\t 0, \u0026value_ptr);\n+\t\t\t/* A trusted path's dentry is trusted, never NULL. */\n+\t\t\txattr_ret = bpf_get_dentry_xattr(pos-\u003edentry,\n+\t\t\t\t\t\t\t \"user.walk\",\n+\t\t\t\t\t\t\t \u0026value_ptr);\n+\t\t}\n+\t\tbpf_path_put(pos);\n+\t}\n+\tbpf_iter_path_ancestors_destroy(\u0026it);\n+\treturn 0;\n+}\n"
} |
| 2/1 | 2026/10/06 00:34 | agent | patch-evaluator |
0mModel:core Args: null Results: {
"EnableConfigs": [
"BPF_SYSCALL",
"BPF_LSM",
"SECURITY_LANDLOCK"
],
"FocusSymbols": [
"vfs_walk_ancestors",
"vfs_walk_start",
"vfs_walk_next",
"vfs_walk_end",
"vfs_walk_handover",
"mnt_undo_legitimize",
"bpf_iter_path_ancestors_new",
"bpf_iter_path_ancestors_next",
"bpf_iter_path_ancestors_destroy",
"bpf_iter_path_ancestors_rcu_new",
"bpf_iter_path_ancestors_rcu_next",
"bpf_iter_path_ancestors_rcu_destroy",
"bpf_path_ancestors_legitimize",
"bpf_path_put"
],
"Reasoning": "The patch introduces a stepwise VFS ancestor path walking engine (vfs_walk_*) supporting both sleepable referenced walks and lockless RCU walks, rewrites Landlock path access checks to use it, modifies mount reference management (mnt_undo_legitimize, delayed_mntput), introduces BPF path iterator kfuncs (bpf_iter_path_ancestors_*), and updates the BPF verifier. These changes alter core VFS and BPF control flow, concurrency handling, and reference counting, making them highly relevant for fuzzing.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.
IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.
================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.
- REACHABILITY IS THE PRIMARY GATE:
Fuzzing can only discover bugs in code that can actually execute in standard virtualized
environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
regardless of whether it adds assertions or complex logic.
- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
Patch authors routinely label changes as "cleanups", "refactorings", or state
"No functional change intended". Do NOT take these claims at face value.
Code refactorings that rearrange logic, introduce helper functions, or alter state management
in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
If reachable executable code is modified or refactored, it MUST be fuzzed.
- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
Even if the author believes the invariant always holds, fuzzing is essential to verify whether
an unusual sequence of operations can violate it.
================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:
- Non-kernel and non-executable changes:
* Modifications to Documentation/, comments, or spelling fixes.
* User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
that do not affect the compiled kernel image (vmlinux) or kernel modules.
* Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
that do not alter control flow or data structures.
* Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
* Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
* Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
physical PCI hot-unplug or manual sysfs driver unbinding.
* Code paths exclusive to architectures other than the target architecture.
================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
* Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
* Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
* Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
* Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).
================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:
- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
Do NOT list generic, ubiquitous functions called by almost every program in the corpus
(including, but not limited to: general memory allocators and deallocators, page fault
and trap handlers, or core synchronization primitives; this is not an exhaustive list).
Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
which severely dilutes fuzzing effort away from the actual changes.
- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
List functions that specifically implement the logic being added or altered, or direct API entrypoints
for the subsystem feature under review.
- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).
================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit 0b8b5fc9ad0c8a6575b46e5c6fe1969366398c37
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Oct 6 00:34:04 2026 +0000
syz-cluster: applied patch under review
diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c
index 08f0847c49708..9dad65df42674 100644
--- a/fs/bpf_fs_kfuncs.c
+++ b/fs/bpf_fs_kfuncs.c
@@ -13,7 +13,11 @@
#include <linux/kernfs.h>
#include <linux/lsm_hooks.h>
#include <linux/mm.h>
+#include <linux/namei.h>
#include <linux/net.h>
+#include <linux/slab.h>
+
+#include "internal.h"
#include <linux/xattr.h>
__bpf_kfunc_start_defs();
@@ -500,6 +504,239 @@ __bpf_kfunc struct inode *bpf_real_data_inode(struct file *file)
__bpf_kfunc_end_defs();
+enum bpf_path_ancestors_flag {
+ /* bpf_path_ancestors[_rcu]_pos_flags() bits */
+ BPF_PATH_ANCESTORS_DISCONNECTED = (1 << 0),
+ /* the position is a mountpoint a mount crossing landed on */
+ BPF_PATH_ANCESTORS_MOUNTPOINT = (1 << 1),
+ /* the iteration ended on a failed allocation, not at the root */
+ BPF_PATH_ANCESTORS_NOMEM = (1 << 2),
+ /* the lockless iteration lost a race and reached no conclusion */
+ BPF_PATH_ANCESTORS_RETRY = (1 << 3),
+};
+
+/*
+ * Walks over a path's ancestors, in two variants differing in how
+ * positions are kept alive:
+ *
+ * bpf_iter_path_ancestors runs with references. Its kfuncs are
+ * sleepable, so the iteration may sleep between positions.
+ *
+ * Each position is handed to the program as an acquired reference of its
+ * own, to release with bpf_path_put(). The walk's own reference moves on
+ * with the walk, so a position that outlives the step it came from - a
+ * dentry saved for later, or the path a sleepable kfunc is still working
+ * on - has to be kept alive by the program's reference rather than by the
+ * iterator's.
+ *
+ * bpf_iter_path_ancestors_rcu runs lockless over one RCU read-side
+ * critical section, which the verifier enforces around the whole
+ * iteration and within which sleeping is impossible. Its positions are
+ * borrowed, not acquired: they are only valid until the next step, and
+ * nothing derived from one may be passed to a kfunc demanding a trusted
+ * argument. A lost race ends the iteration with
+ * BPF_PATH_ANCESTORS_RETRY set; the program discards what it derived
+ * from the walk and retries, typically on the referenced variant, or
+ * escalates mid-walk with bpf_path_ancestors_legitimize().
+ */
+struct bpf_iter_path_ancestors {
+ __u64 __opaque[5];
+} __aligned(8);
+
+struct bpf_iter_path_ancestors_rcu {
+ __u64 __opaque[5];
+} __aligned(8);
+
+struct bpf_path_ancestors_kern {
+ struct vfs_ancestor_walk aw;
+ int step; /* last vfs_walk_next() result, or -ENOMEM */
+} __aligned(8);
+
+static int bpf_path_ancestors_new(struct bpf_path_ancestors_kern *kit,
+ struct path *path, u64 flags,
+ unsigned int walk_flags)
+{
+ BUILD_BUG_ON(sizeof(struct bpf_path_ancestors_kern) >
+ sizeof(struct bpf_iter_path_ancestors));
+ BUILD_BUG_ON(__alignof__(struct bpf_path_ancestors_kern) !=
+ __alignof__(struct bpf_iter_path_ancestors));
+ BUILD_BUG_ON(sizeof(struct bpf_iter_path_ancestors) !=
+ sizeof(struct bpf_iter_path_ancestors_rcu));
+ BUILD_BUG_ON(__alignof__(struct bpf_iter_path_ancestors) !=
+ __alignof__(struct bpf_iter_path_ancestors_rcu));
+
+ if (flags) {
+ /* A zeroed walk makes destroying the iterator a no-op. */
+ memset(kit, 0, sizeof(*kit));
+ kit->step = 1;
+ return -EINVAL;
+ }
+ kit->step = 0;
+ vfs_walk_start(&kit->aw, path, walk_flags);
+ return 0;
+}
+
+/* The walk's own view of the next position, which the step after it ends. */
+static struct path *bpf_path_ancestors_step(struct bpf_path_ancestors_kern *kit)
+{
+ if (kit->step)
+ return NULL;
+ kit->step = vfs_walk_next(&kit->aw);
+ return kit->step ? NULL : &kit->aw.pos;
+}
+
+static u32 bpf_path_ancestors_flags(const struct bpf_path_ancestors_kern *kit)
+{
+ u32 flags = 0;
+
+ if (kit->step == -ENOMEM)
+ return BPF_PATH_ANCESTORS_NOMEM;
+ if (kit->step == -ECHILD)
+ return BPF_PATH_ANCESTORS_RETRY;
+ if (!kit->step) {
+ if (kit->aw.pos_flags & VFS_WALK_POS_DISCONNECTED)
+ flags |= BPF_PATH_ANCESTORS_DISCONNECTED;
+ if (kit->aw.pos_flags & VFS_WALK_POS_MOUNTPOINT)
+ flags |= BPF_PATH_ANCESTORS_MOUNTPOINT;
+ }
+ return flags;
+}
+
+__bpf_kfunc_start_defs();
+
+__bpf_kfunc int bpf_iter_path_ancestors_new(struct bpf_iter_path_ancestors *it,
+ struct path *path, u64 flags)
+{
+ return bpf_path_ancestors_new((void *)it, path, flags, 0);
+}
+
+/**
+ * bpf_iter_path_ancestors_next - acquire the walk's next position
+ * @it: the iterator
+ *
+ * Return: the next position with a reference held, to release with
+ * bpf_path_put(), or NULL once the walk has passed the real root - or on
+ * an allocation failure, which ends the iteration and is reported as
+ * %BPF_PATH_ANCESTORS_NOMEM by bpf_path_ancestors_pos_flags().
+ */
+__bpf_kfunc struct path *
+bpf_iter_path_ancestors_next(struct bpf_iter_path_ancestors *it)
+{
+ struct bpf_path_ancestors_kern *kit = (void *)it;
+ struct path *pos = bpf_path_ancestors_step(kit);
+ struct path *held;
+
+ if (!pos)
+ return NULL;
+ /*
+ * The position must outlive the walk's own view of it, so it gets a
+ * reference and a struct path of its own to live in: struct path is
+ * a value type, with nothing a BPF reference could be taken on
+ * otherwise. Sleepable, so no atomic allocation.
+ */
+ held = kmalloc_obj(*held);
+ if (!held) {
+ kit->step = -ENOMEM;
+ return NULL;
+ }
+ *held = *pos;
+ path_get(held);
+ return held;
+}
+
+__bpf_kfunc void
+bpf_iter_path_ancestors_destroy(struct bpf_iter_path_ancestors *it)
+{
+ vfs_walk_end(&((struct bpf_path_ancestors_kern *)it)->aw);
+}
+
+__bpf_kfunc u32
+bpf_path_ancestors_pos_flags(struct bpf_iter_path_ancestors *it__iter)
+{
+ return bpf_path_ancestors_flags((void *)it__iter);
+}
+
+__bpf_kfunc int
+bpf_iter_path_ancestors_rcu_new(struct bpf_iter_path_ancestors_rcu *it,
+ struct path *path, u64 flags)
+{
+ return bpf_path_ancestors_new((void *)it, path, flags, VFS_WALK_RCU);
+}
+
+/*
+ * Unlike the referenced variant, this hands out the walk's own position:
+ * a lockless iteration holds no references to pass on, and the verifier
+ * keeps the whole of it inside one RCU read-side critical section.
+ */
+__bpf_kfunc struct path *
+bpf_iter_path_ancestors_rcu_next(struct bpf_iter_path_ancestors_rcu *it)
+{
+ return bpf_path_ancestors_step((void *)it);
+}
+
+__bpf_kfunc void
+bpf_iter_path_ancestors_rcu_destroy(struct bpf_iter_path_ancestors_rcu *it)
+{
+ vfs_walk_end(&((struct bpf_path_ancestors_kern *)it)->aw);
+}
+
+__bpf_kfunc u32
+bpf_path_ancestors_rcu_pos_flags(struct bpf_iter_path_ancestors_rcu *it__iter)
+{
+ return bpf_path_ancestors_flags((void *)it__iter);
+}
+
+/**
+ * bpf_path_ancestors_legitimize - hand a lockless iteration over to references
+ * @it__uninit: referenced ancestor iterator to begin at @rcu_it__iter's
+ * current position; destroy it with
+ * bpf_iter_path_ancestors_destroy() whether this succeeds or not
+ * @rcu_it__iter: lockless ancestor iterator, on the position to escalate at
+ *
+ * Mirrors unlazy_walk(): acquires the lockless iteration's current position
+ * and leaves @it__uninit ready to continue from it with references, which
+ * its first bpf_iter_path_ancestors_next() then yields - necessarily after
+ * the program has left its RCU read-side critical section, since that kfunc
+ * is sleepable. Sleepable work on the escalated position therefore happens
+ * on the iteration's own reference, and nothing is allocated here.
+ *
+ * Return: 0, -%ENOENT if the lockless iteration was not on a position, or
+ * -%ECHILD if it lost the race to acquire one; %BPF_PATH_ANCESTORS_RETRY is
+ * then also flagged, and the program has reached no conclusion about the
+ * ancestry. @it__uninit is initialized whatever this returns, so a program
+ * need not branch on the result: a walk that could not be escalated simply
+ * yields no position.
+ */
+__bpf_kfunc int
+bpf_path_ancestors_legitimize(struct bpf_iter_path_ancestors *it__uninit,
+ struct bpf_iter_path_ancestors_rcu *rcu_it__iter)
+{
+ struct bpf_path_ancestors_kern *rcu_kit = (void *)rcu_it__iter;
+ struct bpf_path_ancestors_kern *kit = (void *)it__uninit;
+
+ /* A zeroed walk makes destroying the iterator a no-op. */
+ memset(kit, 0, sizeof(*kit));
+ kit->step = 1;
+
+ /* Drained, or already failed: nothing to hand over. */
+ if (rcu_kit->step)
+ return -ENOENT;
+ if (!vfs_walk_handover(&kit->aw, &rcu_kit->aw)) {
+ rcu_kit->step = -ECHILD;
+ return -ECHILD;
+ }
+ kit->step = 0;
+ return 0;
+}
+
+__bpf_kfunc void bpf_path_put(struct path *path)
+{
+ path_put(path);
+ kfree(path);
+}
+
+__bpf_kfunc_end_defs();
+
BTF_KFUNCS_START(bpf_fs_kfunc_set_ids)
BTF_ID_FLAGS(func, bpf_get_task_exe_file, KF_ACQUIRE | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_put_file, KF_RELEASE)
@@ -513,6 +750,17 @@ BTF_ID_FLAGS(func, bpf_inode_init_xattr)
#ifdef CONFIG_NET
BTF_ID_FLAGS(func, bpf_sock_read_xattr, KF_RCU)
#endif
+BTF_ID_FLAGS(func, bpf_iter_path_ancestors_new, KF_ITER_NEW | KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_iter_path_ancestors_next,
+ KF_ITER_NEXT | KF_ACQUIRE | KF_RET_NULL | KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_iter_path_ancestors_destroy, KF_ITER_DESTROY | KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_path_ancestors_pos_flags)
+BTF_ID_FLAGS(func, bpf_iter_path_ancestors_rcu_new, KF_ITER_NEW | KF_RCU_PROTECTED)
+BTF_ID_FLAGS(func, bpf_iter_path_ancestors_rcu_next, KF_ITER_NEXT | KF_RET_NULL)
+BTF_ID_FLAGS(func, bpf_iter_path_ancestors_rcu_destroy, KF_ITER_DESTROY)
+BTF_ID_FLAGS(func, bpf_path_ancestors_rcu_pos_flags)
+BTF_ID_FLAGS(func, bpf_path_ancestors_legitimize)
+BTF_ID_FLAGS(func, bpf_path_put, KF_RELEASE | KF_SLEEPABLE)
BTF_KFUNCS_END(bpf_fs_kfunc_set_ids)
/* Side-effecting kfuncs that stay exclusive to LSM programs. */
diff --git a/fs/internal.h b/fs/internal.h
index c658c8a5ebd56..8a5696157121b 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -71,6 +71,28 @@ struct dentry *start_dirop(struct dentry *parent, struct qstr *name,
unsigned int lookup_flags);
int lookup_noperm_common(struct qstr *qname, struct dentry *base);
+/*
+ * The stepwise engine under vfs_walk_ancestors(); fs-internal so iterating
+ * consumers (BPF) can drive it, with the walk invariants staying in namei.c.
+ * In rcu mode (%VFS_WALK_RCU) the caller holds rcu_read_lock() over the
+ * whole walk, no references are held, and vfs_walk_next() returning -ECHILD
+ * invalidates everything derived from the walk.
+ */
+struct vfs_ancestor_walk {
+ struct path pos;
+ unsigned int seq; /* pos.dentry->d_seq sample (rcu mode) */
+ unsigned int m_seq; /* mount_lock sample (rcu mode) */
+ unsigned int pos_flags; /* VFS_WALK_POS_* describing pos */
+ unsigned int flags; /* VFS_WALK_* */
+};
+
+void vfs_walk_start(struct vfs_ancestor_walk *aw, const struct path *path,
+ unsigned int flags);
+int vfs_walk_next(struct vfs_ancestor_walk *aw);
+void vfs_walk_end(struct vfs_ancestor_walk *aw);
+bool vfs_walk_handover(struct vfs_ancestor_walk *to,
+ struct vfs_ancestor_walk *from);
+
void __init filename_init(void);
/*
diff --git a/fs/mount.h b/fs/mount.h
index 94fcc306d21e7..e714040f8e8fd 100644
--- a/fs/mount.h
+++ b/fs/mount.h
@@ -140,6 +140,7 @@ static inline int is_mounted(struct vfsmount *mnt)
extern struct mount *__lookup_mnt(struct vfsmount *, struct dentry *);
extern int __legitimize_mnt(struct vfsmount *, unsigned);
+void mnt_undo_legitimize(struct mount *mnt);
static inline bool __path_is_mountpoint(const struct path *path)
{
diff --git a/fs/namei.c b/fs/namei.c
index 20a6534ea3eff..31f96602d4e94 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -2152,34 +2152,69 @@ static __always_inline const char *step_into(struct nameidata *nd, int flags,
return step_into_slowpath(nd, flags, dentry);
}
-static struct dentry *follow_dotdot_rcu(struct nameidata *nd)
+/**
+ * __path_walk_parent_rcu - step towards the parent of the given struct path
+ * @path: position to step up from; updated in place on a mount crossing,
+ * which is first if @path is the root of a mounted tree. No
+ * references are acquired; the callers layer their own bookkeeping
+ * (path_connected(), nameidata updates, ...) on top
+ * @root: boundary as for choose_mountpoint_rcu(); if zero'ed, walk all the
+ * way to the global root
+ * @flags: %LOOKUP_NO_XDEV fails a mount crossing with -ECHILD
+ * @m_seq: the walk's mount_lock sample
+ * @seqp: d_seq sample validating @path->dentry; updated to cover the new
+ * @path->dentry when a mount is crossed
+ * @next_seqp: set to the returned parent's d_seq sample
+ *
+ * Returns: the parent dentry (which is @path->dentry itself if that is a
+ * disconnected root), NULL if @path is in the root with nothing to cross
+ * into, or ERR_PTR(-ECHILD) when a concurrent change was detected.
+ */
+static struct dentry *__path_walk_parent_rcu(struct path *path,
+ const struct path *root, int flags,
+ unsigned int m_seq, unsigned int *seqp,
+ unsigned int *next_seqp)
{
struct dentry *parent, *old;
- if (path_equal(&nd->path, &nd->root))
- goto in_root;
- if (unlikely(nd->path.dentry == nd->path.mnt->mnt_root)) {
- struct path path;
- unsigned seq;
- if (!choose_mountpoint_rcu(real_mount(nd->path.mnt),
- &nd->root, &path, &seq))
- goto in_root;
- if (unlikely(nd->flags & LOOKUP_NO_XDEV))
+ if (unlikely(path->dentry == path->mnt->mnt_root)) {
+ struct path mounted;
+ unsigned int seq;
+
+ if (!choose_mountpoint_rcu(real_mount(path->mnt),
+ root, &mounted, &seq))
+ return NULL;
+ if (unlikely(flags & LOOKUP_NO_XDEV))
return ERR_PTR(-ECHILD);
- nd->path = path;
- nd->inode = path.dentry->d_inode;
- nd->seq = seq;
+ *path = mounted;
+ *seqp = seq;
// makes sure that non-RCU pathwalk could reach this state
- if (read_seqretry(&mount_lock, nd->m_seq))
+ if (read_seqretry(&mount_lock, m_seq))
return ERR_PTR(-ECHILD);
/* we know that mountpoint was pinned */
}
- old = nd->path.dentry;
+ old = path->dentry;
parent = old->d_parent;
- nd->next_seq = read_seqcount_begin(&parent->d_seq);
+ *next_seqp = read_seqcount_begin(&parent->d_seq);
// makes sure that non-RCU pathwalk could reach this state
- if (read_seqcount_retry(&old->d_seq, nd->seq))
+ if (read_seqcount_retry(&old->d_seq, *seqp))
return ERR_PTR(-ECHILD);
+ return parent;
+}
+
+static struct dentry *follow_dotdot_rcu(struct nameidata *nd)
+{
+ struct dentry *parent;
+
+ if (path_equal(&nd->path, &nd->root))
+ goto in_root;
+ parent = __path_walk_parent_rcu(&nd->path, &nd->root, nd->flags,
+ nd->m_seq, &nd->seq, &nd->next_seq);
+ if (!parent)
+ goto in_root;
+ if (IS_ERR(parent))
+ return parent;
+ nd->inode = nd->path.dentry->d_inode;
if (unlikely(!path_connected(nd->path.mnt, parent)))
return ERR_PTR(-ECHILD);
return parent;
@@ -2192,36 +2227,308 @@ static struct dentry *follow_dotdot_rcu(struct nameidata *nd)
return nd->path.dentry;
}
-static struct dentry *follow_dotdot(struct nameidata *nd)
+/**
+ * __path_walk_parent - step towards the parent of the given struct path
+ * @path: position to step up from; if it is the root of a mounted tree, it
+ * is first updated to the mount point that tree is mounted on
+ * @root: boundary not to be crossed; if zero'ed, walk all the way to the
+ * global root
+ * @flags: %LOOKUP_NO_XDEV returns -EXDEV once @path was updated to a new
+ * mount; %LOOKUP_BENEATH returns -EXDEV at @root or at the root of
+ * an unmounted tree, where @path->dentry itself is returned
+ * otherwise
+ *
+ * Returns: the parent dentry with its refcount incremented, or an ERR_PTR().
+ */
+static struct dentry *__path_walk_parent(struct path *path, const struct path *root, int flags)
{
- struct dentry *parent;
-
- if (path_equal(&nd->path, &nd->root))
+ if (path_equal(path, root))
goto in_root;
- if (unlikely(nd->path.dentry == nd->path.mnt->mnt_root)) {
- struct path path;
+ if (unlikely(path->dentry == path->mnt->mnt_root)) {
+ struct path new_path;
- if (!choose_mountpoint(real_mount(nd->path.mnt),
- &nd->root, &path))
+ if (!choose_mountpoint(real_mount(path->mnt),
+ root, &new_path))
goto in_root;
- path_put(&nd->path);
- nd->path = path;
- nd->inode = path.dentry->d_inode;
- if (unlikely(nd->flags & LOOKUP_NO_XDEV))
+ path_put(path);
+ *path = new_path;
+ if (unlikely(flags & LOOKUP_NO_XDEV))
return ERR_PTR(-EXDEV);
}
/* rare case of legitimate dget_parent()... */
- parent = dget_parent(nd->path.dentry);
+ return dget_parent(path->dentry);
+
+in_root:
+ if (unlikely(flags & LOOKUP_BENEATH))
+ return ERR_PTR(-EXDEV);
+ return dget(path->dentry);
+}
+
+static unsigned int vfs_walk_pos_flags(const struct vfsmount *mnt,
+ const struct dentry *dentry)
+{
+ if (unlikely(IS_ROOT(dentry) && dentry != mnt->mnt_root))
+ return VFS_WALK_POS_DISCONNECTED;
+ return 0;
+}
+
+/* No boundary: ancestor walks go up to the real root. */
+static const struct path vfs_walk_no_root;
+
+/* Internal walk flag: the starting position has been yielded. */
+#define VFS_WALK_STARTED BIT(31)
+
+/**
+ * vfs_walk_start - begin a stepwise ancestor walk
+ * @aw: walk state, valid until vfs_walk_end()
+ * @path: position to walk up from; never modified
+ * @flags: %VFS_WALK_RCU to walk lockless; the caller then holds
+ * rcu_read_lock() from before vfs_walk_start() until after
+ * vfs_walk_end(), and owns no references on yielded positions.
+ */
+void vfs_walk_start(struct vfs_ancestor_walk *aw, const struct path *path,
+ unsigned int flags)
+{
+ aw->pos = *path;
+ aw->flags = flags;
+ aw->pos_flags = 0;
+ if (flags & VFS_WALK_RCU) {
+ RCU_LOCKDEP_WARN(!rcu_read_lock_held(),
+ "rcu-mode ancestor walk outside of RCU read-side critical section");
+ aw->m_seq = read_seqbegin(&mount_lock);
+ aw->seq = raw_seqcount_begin(&aw->pos.dentry->d_seq);
+ } else {
+ path_get(&aw->pos);
+ }
+}
+
+static int vfs_walk_step_ref(struct vfs_ancestor_walk *aw)
+{
+ struct dentry *parent;
+
+ if (unlikely(aw->pos_flags & VFS_WALK_POS_DISCONNECTED)) {
+ /* Resume at the root of the disconnected position's mount. */
+ dput(aw->pos.dentry);
+ aw->pos.dentry = dget(aw->pos.mnt->mnt_root);
+ aw->pos_flags = 0;
+ return 0;
+ }
+ parent = __path_walk_parent(&aw->pos, &vfs_walk_no_root,
+ LOOKUP_BENEATH);
+ if (IS_ERR(parent))
+ /* The real root. */
+ return 1;
+ /*
+ * A mount crossing can step onto a disconnected root, whose parent
+ * is itself: only then is the mountpoint itself yielded, flagged.
+ */
+ aw->pos_flags = parent == aw->pos.dentry ?
+ VFS_WALK_POS_DISCONNECTED | VFS_WALK_POS_MOUNTPOINT :
+ vfs_walk_pos_flags(aw->pos.mnt, parent);
+ dput(aw->pos.dentry);
+ aw->pos.dentry = parent;
+ return 0;
+}
+
+static int vfs_walk_step_rcu(struct vfs_ancestor_walk *aw)
+{
+ struct dentry *parent;
+ unsigned int next_seq;
+
+ if (unlikely(aw->pos_flags & VFS_WALK_POS_DISCONNECTED)) {
+ /* Resume at the root of the disconnected position's mount. */
+ aw->pos.dentry = aw->pos.mnt->mnt_root;
+ aw->seq = raw_seqcount_begin(&aw->pos.dentry->d_seq);
+ aw->pos_flags = 0;
+ return read_seqretry(&mount_lock, aw->m_seq) ? -ECHILD : 0;
+ }
+
+ parent = __path_walk_parent_rcu(&aw->pos, &vfs_walk_no_root, 0,
+ aw->m_seq, &aw->seq, &next_seq);
+ if (!parent)
+ /* The real root, unless the mount tree moved. */
+ return read_seqretry(&mount_lock, aw->m_seq) ? -ECHILD : 1;
+ if (IS_ERR(parent))
+ return PTR_ERR(parent);
+ /* A crossing onto a disconnected root, as in vfs_walk_step_ref(). */
+ aw->pos_flags = parent == aw->pos.dentry ?
+ VFS_WALK_POS_DISCONNECTED | VFS_WALK_POS_MOUNTPOINT :
+ vfs_walk_pos_flags(aw->pos.mnt, parent);
+ aw->pos.dentry = parent;
+ aw->seq = next_seq;
+ return 0;
+}
+
+/**
+ * vfs_walk_next - yield the walk's next position in @aw->pos
+ * @aw: the walk
+ *
+ * The first call yields the starting position itself. @aw->pos_flags
+ * carries the position's %VFS_WALK_POS_* flags, with the semantics
+ * described at vfs_walk_ancestors().
+ *
+ * Returns: 0 with @aw->pos valid, 1 once the walk has passed the real
+ * root, -ECHILD when an rcu-mode walk lost a race and must be retried
+ * (typically in the referenced mode).
+ */
+int vfs_walk_next(struct vfs_ancestor_walk *aw)
+{
+ if (aw->flags & VFS_WALK_STARTED) {
+ int err = (aw->flags & VFS_WALK_RCU) ?
+ vfs_walk_step_rcu(aw) : vfs_walk_step_ref(aw);
+
+ if (err)
+ return err;
+ } else {
+ aw->flags |= VFS_WALK_STARTED;
+ aw->pos_flags = vfs_walk_pos_flags(aw->pos.mnt, aw->pos.dentry);
+ }
+ /* The flags must describe the dentry the seq covers. */
+ if ((aw->flags & VFS_WALK_RCU) &&
+ read_seqcount_retry(&aw->pos.dentry->d_seq, aw->seq))
+ return -ECHILD;
+ return 0;
+}
+
+/**
+ * vfs_walk_end - finish a stepwise ancestor walk
+ * @aw: the walk; its positions, including @aw->pos, become invalid
+ */
+void vfs_walk_end(struct vfs_ancestor_walk *aw)
+{
+ if (!(aw->flags & VFS_WALK_RCU))
+ path_put(&aw->pos);
+}
+
+/**
+ * vfs_walk_handover - continue an rcu-mode walk with references
+ * @to: walk state to begin at @from's current position, owning the
+ * references acquired on it; valid until vfs_walk_end() either way
+ * @from: an rcu-mode walk, positioned by a 0 return from vfs_walk_next()
+ *
+ * Mirrors unlazy_walk(): @from's current position is legitimized and
+ * becomes the starting position of the referenced walk @to, which the
+ * first vfs_walk_next() on it yields. A failed legitimization leaves no
+ * partial references behind and a successful one moves straight into @to,
+ * so nothing is ever put here and the handover is safe within the caller's
+ * RCU read-side critical section - where a path_put() would not be, dput()
+ * being allowed to sleep. @to itself is only usable once the caller has
+ * left it, its walk being reference-based.
+ *
+ * @from is untouched on success and may keep stepping, lockless, from
+ * where it stands. The references do not conclude its walk: a concurrent
+ * rename may relocate the position the instant they are taken, as it may
+ * during any reference-based walk.
+ *
+ * Returns: false iff @from lost a race; it is then dead, as after -ECHILD
+ * from vfs_walk_next(), and @to is zeroed - safe to vfs_walk_end(), but
+ * not to step, so the caller has to remember it never started.
+ */
+bool vfs_walk_handover(struct vfs_ancestor_walk *to,
+ struct vfs_ancestor_walk *from)
+{
+ struct path pos = from->pos;
+ int err;
+
+ err = __legitimize_mnt(pos.mnt, from->m_seq);
+ if (unlikely(err)) {
+ if (err < 0)
+ mnt_undo_legitimize(real_mount(pos.mnt));
+ goto dead;
+ }
+ if (unlikely(read_seqcount_retry(&pos.dentry->d_seq, from->seq) ||
+ !lockref_get_not_dead(&pos.dentry->d_lockref))) {
+ mnt_undo_legitimize(real_mount(pos.mnt));
+ goto dead;
+ }
+ to->pos = pos;
+ to->flags = 0;
+ to->pos_flags = 0;
+ return true;
+
+dead:
+ memset(to, 0, sizeof(*to));
+ return false;
+}
+
+/**
+ * vfs_walk_ancestors - invoke a callback on a path and each of its ancestors
+ * @path: path to walk up from; the caller's path is never modified
+ * @cb: callback invoked on @path, then on each ancestor up to the real
+ * root, crossing mount boundaries. @cb must not sleep and returns
+ * %VFS_WALK_CONTINUE, %VFS_WALK_STOP or a negative errno to abort the
+ * walk; -ECHILD is reserved (see below). @ancestor is only valid
+ * during the invocation; @cb must take its own references to keep a
+ * position.
+ * A position whose dentry is a disconnected root is flagged with
+ * %VFS_WALK_POS_DISCONNECTED (plus %VFS_WALK_POS_MOUNTPOINT when it
+ * is a mountpoint a mount crossing landed on rather than a parent);
+ * if @cb continues over it, the walk resumes at the root of that
+ * position's mount.
+ * With %VFS_WALK_RCU, @cb accepts positions the walk holds no
+ * references on: the walk then runs lockless (under rcu_read_lock)
+ * and returns -ECHILD when it loses a race, or when @cb returns
+ * -ECHILD. The caller should then discard any state @cb accumulated
+ * and retry without %VFS_WALK_RCU.
+ * @data: opaque argument passed to @cb
+ * @flags: %VFS_WALK_RCU if @cb copes with unreferenced positions
+ *
+ * Returns: 0 once the real root was reached, 1 if @cb stopped the walk,
+ * -ECHILD if a lockless walk must be retried with references, or the
+ * negative errno @cb aborted with.
+ */
+int vfs_walk_ancestors(const struct path *path,
+ int (*cb)(const struct path *ancestor,
+ unsigned int pos_flags, void *data),
+ void *data, unsigned int flags)
+{
+ struct vfs_ancestor_walk aw;
+ int ret;
+
+ if (flags & VFS_WALK_RCU)
+ rcu_read_lock();
+ vfs_walk_start(&aw, path, flags);
+ for (;;) {
+ ret = vfs_walk_next(&aw);
+ if (ret) {
+ if (ret == 1)
+ /* The real root was reached. */
+ ret = 0;
+ break;
+ }
+ ret = cb(&aw.pos, aw.pos_flags, data);
+ if (ret < 0)
+ break;
+ if (ret == VFS_WALK_STOP) {
+ ret = 1;
+ break;
+ }
+ }
+ vfs_walk_end(&aw);
+ if (flags & VFS_WALK_RCU)
+ rcu_read_unlock();
+ return ret;
+}
+
+static struct dentry *follow_dotdot(struct nameidata *nd)
+{
+ struct dentry *parent;
+
+ if (path_equal(&nd->path, &nd->root)) {
+ /* nd->root is never path_connected()-checked. */
+ if (unlikely(nd->flags & LOOKUP_BENEATH))
+ return ERR_PTR(-EXDEV);
+ return dget(nd->path.dentry);
+ }
+ parent = __path_walk_parent(&nd->path, &nd->root, nd->flags);
+ nd->inode = nd->path.dentry->d_inode;
+ if (IS_ERR(parent))
+ return parent;
if (unlikely(!path_connected(nd->path.mnt, parent))) {
dput(parent);
return ERR_PTR(-ENOENT);
}
return parent;
-
-in_root:
- if (unlikely(nd->flags & LOOKUP_BENEATH))
- return ERR_PTR(-EXDEV);
- return dget(nd->path.dentry);
}
static const char *handle_dots(struct nameidata *nd, enum last_type type)
diff --git a/fs/namespace.c b/fs/namespace.c
index 580877e46b1ac..920b8a85f9fad 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -767,11 +767,8 @@ static bool legitimize_mnt(struct vfsmount *bastard, unsigned seq)
int res = __legitimize_mnt(bastard, seq);
if (likely(!res))
return true;
- if (unlikely(res < 0)) {
- rcu_read_unlock();
- mntput(bastard);
- rcu_read_lock();
- }
+ if (unlikely(res < 0))
+ mnt_undo_legitimize(real_mount(bastard));
return false;
}
@@ -1330,8 +1327,19 @@ static void delayed_mntput(struct work_struct *unused)
struct llist_node *node = llist_del_all(&delayed_mntput_list);
struct mount *m, *t;
- llist_for_each_entry_safe(m, t, node, mnt_llist)
- cleanup_mnt(m);
+ llist_for_each_entry_safe(m, t, node, mnt_llist) {
+ /*
+ * MNT_DOOMED is only ever set on the mounts
+ * mntput_no_expire_slowpath() queues here, never on the
+ * kept-count ones mnt_undo_legitimize() queues. Route on it
+ * rather than on mnt_get_count(), which a concurrent failed
+ * __legitimize_mnt() can transiently inflate.
+ */
+ if (m->mnt.mnt_flags & MNT_DOOMED)
+ cleanup_mnt(m);
+ else
+ mntput(&m->mnt); /* kept by mnt_undo_legitimize() */
+ }
}
static DECLARE_DELAYED_WORK(delayed_mntput_work, delayed_mntput);
@@ -1423,6 +1431,47 @@ void mntput(struct vfsmount *mnt)
}
EXPORT_SYMBOL(mntput);
+/**
+ * mnt_undo_legitimize - drop a count __legitimize_mnt() asked us to put
+ * @mnt: the mount __legitimize_mnt() failed on
+ *
+ * Like the mntput() that a failed __legitimize_mnt() normally obliges,
+ * but callable from the RCU read-side critical section the legitimization
+ * ran under: when the count turns out to be the mount's last, it is kept
+ * and the mount handed to delayed_mntput() for the sleepable final put.
+ * Unlike mntput(), mnt_expiry_mark is left alone: a walker that failed
+ * to legitimize the mount never used it.
+ */
+void mnt_undo_legitimize(struct mount *mnt)
+{
+ if (likely(READ_ONCE(mnt->mnt_ns))) {
+ /* Not the final count, as in mntput_no_expire(). */
+ mnt_add_count(mnt, -1);
+ return;
+ }
+ lock_mount_hash();
+ /*
+ * As in mntput_no_expire(): make sure that if a concurrent
+ * __legitimize_mnt() has not seen us grab mount_lock, we'll see
+ * its refcount increment here.
+ */
+ smp_mb();
+ if (likely(mnt_get_count(mnt) > 1)) {
+ mnt_add_count(mnt, -1);
+ unlock_mount_hash();
+ return;
+ }
+ unlock_mount_hash();
+ /*
+ * Ours is the last count: nothing else can reach the mount any
+ * more, which makes its mnt_llist ours to use. delayed_mntput()
+ * tells our kept-count mount from the doomed ones by MNT_DOOMED.
+ */
+ WARN_ON_ONCE(mnt->mnt.mnt_flags & MNT_DOOMED);
+ if (llist_add(&mnt->mnt_llist, &delayed_mntput_list))
+ schedule_delayed_work(&delayed_mntput_work, 1);
+}
+
struct vfsmount *mntget(struct vfsmount *mnt)
{
if (mnt)
diff --git a/include/linux/namei.h b/include/linux/namei.h
index 86d657b24fc6a..8820a2833213d 100644
--- a/include/linux/namei.h
+++ b/include/linux/namei.h
@@ -162,6 +162,23 @@ extern int follow_down_one(struct path *);
extern int follow_down(struct path *path, unsigned int flags);
extern int follow_up(struct path *);
+/* vfs_walk_ancestors() flags */
+#define VFS_WALK_RCU BIT(0)
+
+/* per-position flags passed to the vfs_walk_ancestors() callback */
+#define VFS_WALK_POS_DISCONNECTED BIT(0)
+/* the position is a mountpoint landed on by a mount crossing */
+#define VFS_WALK_POS_MOUNTPOINT BIT(1)
+
+/* vfs_walk_ancestors() callback verdicts; negative values abort the walk */
+#define VFS_WALK_STOP 0
+#define VFS_WALK_CONTINUE 1
+
+int vfs_walk_ancestors(const struct path *path,
+ int (*cb)(const struct path *ancestor,
+ unsigned int pos_flags, void *data),
+ void *data, unsigned int flags);
+
int start_renaming(struct renamedata *rd, int lookup_flags,
struct qstr *old_last, struct qstr *new_last);
int start_renaming_dentry(struct renamedata *rd, int lookup_flags,
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index fd3c0206bd67d..3294b2a431674 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -6300,6 +6300,10 @@ BTF_TYPE_SAFE_TRUSTED(struct scx_sub_detach_args) {
struct sched_ext_ops *ops;
};
+BTF_TYPE_SAFE_TRUSTED(struct path) {
+ struct dentry *dentry;
+};
+
BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct dentry) {
struct inode *d_inode;
};
@@ -6352,6 +6356,7 @@ static bool type_is_trusted(struct bpf_verifier_env *env,
BTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED(struct scx_cpu_release_args));
BTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED(struct scx_sub_attach_args));
BTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED(struct scx_sub_detach_args));
+ BTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED(struct path));
return btf_nested_type_is_trusted(&env->log, reg, field_name, btf_id, "__safe_trusted");
}
@@ -8307,6 +8312,24 @@ static bool is_iter_destroy_kfunc(struct bpf_call_arg_meta *meta)
return meta->kfunc_flags & KF_ITER_DESTROY;
}
+/*
+ * An "__uninit"-suffixed iterator argument of a kfunc that is not itself an
+ * iterator method: the kfunc initializes that iterator state, as a
+ * bpf_iter_<type>_new() does, but from inputs the constructor naming
+ * convention cannot express - e.g. another, already-initialized iterator.
+ * Only iterator-typed arguments qualify, so "__uninit" dynptr out-arguments
+ * are not reclassified.
+ */
+static bool is_kfunc_arg_iter_init(struct bpf_call_arg_meta *meta, int arg_idx,
+ const struct btf_param *arg)
+{
+ if (is_iter_kfunc(meta))
+ return false;
+
+ return btf_param_match_suffix(meta->btf, arg, "__uninit") &&
+ btf_check_iter_arg(meta->btf, meta->func_proto, arg_idx) >= 0;
+}
+
static bool is_kfunc_arg_iter(struct bpf_call_arg_meta *meta, int arg_idx,
const struct btf_param *arg)
{
@@ -8317,7 +8340,11 @@ static bool is_kfunc_arg_iter(struct bpf_call_arg_meta *meta, int arg_idx,
return arg_idx == 0;
/* iter passed as an argument to a generic kfunc */
- return btf_param_match_suffix(meta->btf, arg, "__iter");
+ if (btf_param_match_suffix(meta->btf, arg, "__iter"))
+ return true;
+
+ /* iter state a generic kfunc initializes */
+ return is_kfunc_arg_iter_init(meta, arg_idx, arg);
}
static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
@@ -8327,6 +8354,7 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *
struct bpf_func_state *state = bpf_func(env, reg);
const struct btf_type *t;
int spi, err, i, nr_slots, btf_id;
+ bool init;
if (reg->type != PTR_TO_STACK) {
verbose(env, "%s expected pointer to an iterator on stack\n",
@@ -8358,8 +8386,16 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *
t = btf_type_by_id(meta->btf, btf_id);
nr_slots = t->size / BPF_REG_SIZE;
- if (is_iter_new_kfunc(meta)) {
- /* bpf_iter_<type>_new() expects pointer to uninit iter state */
+ /*
+ * Whether this argument is the iterator the call initializes, rather
+ * than an initialized one it operates on.
+ */
+ init = is_iter_new_kfunc(meta) ||
+ is_kfunc_arg_iter_init(meta, arg,
+ &btf_params(meta->func_proto)[arg]);
+
+ if (init) {
+ /* expects a pointer to uninit iter state */
if (!is_iter_reg_valid_uninit(env, reg, nr_slots)) {
verbose(env, "expected uninitialized iter_%s as %s\n",
iter_type_str(meta->btf, btf_id), reg_arg_name(env, argno));
@@ -8643,6 +8679,13 @@ static int process_iter_next_call(struct bpf_verifier_env *env, int insn_idx,
/* switch to DRAINED state, but keep the depth unchanged */
/* mark current iter state as drained and assume returned NULL */
cur_iter->iter.state = BPF_ITER_STATE_DRAINED;
+ /*
+ * An acquiring iter_next() hands out nothing once drained: the
+ * acquired reference exists only in the forked active state, not on
+ * this NULL-returning branch.
+ */
+ if (meta->kfunc_flags & KF_ACQUIRE)
+ WARN_ON_ONCE(release_reference_nomark(env, cur_fr->regs[BPF_REG_0].id));
__mark_reg_const_zero(env, &cur_fr->regs[BPF_REG_0]);
return 0;
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index cab43892ec2f0..369e2e9c83ad7 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -751,6 +751,107 @@ static void test_is_eacces_with_write(struct kunit *const test)
#undef IE_TRUE
#undef IE_FALSE
+struct landlock_walk_state {
+ const struct landlock_domain *domain;
+ struct layer_masks *layer_masks_parent1, *layer_masks_parent2;
+ const struct layer_masks *layer_masks_child1, *layer_masks_child2;
+ access_mask_t access_request_parent1, access_request_parent2;
+ access_mask_t access_masked_parent1, access_masked_parent2;
+ bool child1_is_directory, child2_is_directory;
+ bool allowed_parent1, allowed_parent2, is_dom_check;
+};
+
+static int check_access_path_walk(const struct path *const ancestor,
+ const unsigned int pos_flags,
+ void *const data)
+{
+ struct landlock_walk_state *const state = data;
+ struct landlock_id id = {
+ .type = LANDLOCK_KEY_INODE,
+ };
+
+ if (unlikely(pos_flags & VFS_WALK_POS_DISCONNECTED)) {
+ if (likely(ancestor->mnt->mnt_flags & MNT_INTERNAL)) {
+ /*
+ * Stops and allows access when reaching disconnected
+ * root directories that are part of internal
+ * filesystems (e.g. nsfs, which is reachable through
+ * /proc/<pid>/ns/<namespace>).
+ */
+ state->allowed_parent1 = true;
+ state->allowed_parent2 = true;
+ return VFS_WALK_STOP;
+ }
+ /*
+ * The old loop never visited the mountpoints a mount
+ * crossing lands on: don't match them against rules. Other
+ * disconnected roots keep being matched below, as before.
+ */
+ if (pos_flags & VFS_WALK_POS_MOUNTPOINT)
+ return VFS_WALK_CONTINUE;
+ }
+
+ /*
+ * If at least all accesses allowed on the destination are already
+ * allowed on the source, respectively if there is at least as much as
+ * restrictions on the destination than on the source, then we can
+ * safely refer files from the source to the destination without
+ * risking a privilege escalation. This also applies in the case of
+ * RENAME_EXCHANGE, which implies checks on both direction. This is
+ * crucial for standalone multilayered security policies. Furthermore,
+ * this helps avoid policy writers to shoot themselves in the foot.
+ */
+ if (unlikely(state->is_dom_check &&
+ no_more_access(state->layer_masks_parent1,
+ state->layer_masks_child1,
+ state->child1_is_directory,
+ state->layer_masks_parent2,
+ state->layer_masks_child2,
+ state->child2_is_directory))) {
+ /*
+ * Now, downgrades the remaining checks from domain handled
+ * accesses to requested accesses.
+ */
+ state->is_dom_check = false;
+ state->access_masked_parent1 = state->access_request_parent1;
+ state->access_masked_parent2 = state->access_request_parent2;
+
+ state->allowed_parent1 =
+ state->allowed_parent1 ||
+ scope_to_request(state->access_masked_parent1,
+ state->layer_masks_parent1);
+ state->allowed_parent2 =
+ state->allowed_parent2 ||
+ scope_to_request(state->access_masked_parent2,
+ state->layer_masks_parent2);
+
+ /* Stops when all accesses are granted. */
+ if (state->allowed_parent1 && state->allowed_parent2)
+ return VFS_WALK_STOP;
+ }
+
+ if (get_inode_id(ancestor->dentry, &id)) {
+ state->allowed_parent1 =
+ state->allowed_parent1 ||
+ unmask_layers_fs(state->domain, id,
+ state->access_masked_parent1,
+ state->layer_masks_parent1,
+ ancestor->dentry);
+ state->allowed_parent2 =
+ state->allowed_parent2 ||
+ unmask_layers_fs(state->domain, id,
+ state->access_masked_parent2,
+ state->layer_masks_parent2,
+ ancestor->dentry);
+ }
+
+ /* Stops when a rule from each layer grants access. */
+ if (state->allowed_parent1 && state->allowed_parent2)
+ return VFS_WALK_STOP;
+
+ return VFS_WALK_CONTINUE;
+}
+
/**
* is_access_to_paths_allowed - Check accesses for requests with a common path
*
@@ -803,16 +904,16 @@ is_access_to_paths_allowed(const struct landlock_domain *const domain,
struct landlock_request *const log_request_parent2,
struct dentry *const dentry_child2)
{
- bool allowed_parent1 = false, allowed_parent2 = false, is_dom_check,
- child1_is_directory = true, child2_is_directory = true;
- struct path walker_path;
- struct landlock_id id = {
- .type = LANDLOCK_KEY_INODE,
- };
- access_mask_t access_masked_parent1, access_masked_parent2;
struct layer_masks _layer_masks_child1, _layer_masks_child2;
- struct layer_masks *layer_masks_child1 = NULL,
- *layer_masks_child2 = NULL;
+ struct landlock_walk_state state = {
+ .domain = domain,
+ .layer_masks_parent1 = layer_masks_parent1,
+ .layer_masks_parent2 = layer_masks_parent2,
+ .access_request_parent1 = access_request_parent1,
+ .access_request_parent2 = access_request_parent2,
+ .child1_is_directory = true,
+ .child2_is_directory = true,
+ };
if (!access_request_parent1 && !access_request_parent2)
return true;
@@ -826,37 +927,39 @@ is_access_to_paths_allowed(const struct landlock_domain *const domain,
if (WARN_ON_ONCE(!layer_masks_parent1))
return false;
- allowed_parent1 = is_layer_masks_allowed(layer_masks_parent1);
+ state.allowed_parent1 = is_layer_masks_allowed(layer_masks_parent1);
if (unlikely(layer_masks_parent2)) {
if (WARN_ON_ONCE(!dentry_child1))
return false;
- allowed_parent2 = is_layer_masks_allowed(layer_masks_parent2);
+ state.allowed_parent2 =
+ is_layer_masks_allowed(layer_masks_parent2);
/*
* For a double request, first check for potential privilege
* escalation by looking at domain handled accesses (which are
* a superset of the meaningful requested accesses).
*/
- access_masked_parent1 = access_masked_parent2 =
+ state.access_masked_parent1 =
landlock_union_access_masks(domain).fs;
- is_dom_check = true;
+ state.access_masked_parent2 = state.access_masked_parent1;
+ state.is_dom_check = true;
} else {
if (WARN_ON_ONCE(dentry_child1 || dentry_child2))
return false;
/* For a simple request, only check for requested accesses. */
- access_masked_parent1 = access_request_parent1;
- access_masked_parent2 = access_request_parent2;
+ state.access_masked_parent1 = access_request_parent1;
+ state.access_masked_parent2 = access_request_parent2;
/*
* Simple requests have no parent2 to check, so parent2 is
* trivially allowed. This must be set explicitly because the
- * get_inode_id() gate in the pathwalk loop may prevent
+ * get_inode_id() gate in the walk callback may prevent
* landlock_unmask_layers() from being called (which would
* otherwise return true for NULL masks as a side effect).
*/
- allowed_parent2 = true;
- is_dom_check = false;
+ state.allowed_parent2 = true;
+ state.is_dom_check = false;
}
if (unlikely(dentry_child1)) {
@@ -872,8 +975,8 @@ is_access_to_paths_allowed(const struct landlock_domain *const domain,
if (handled && get_inode_id(dentry_child1, &id))
unmask_layers_fs(domain, id, handled,
&_layer_masks_child1, dentry_child1);
- layer_masks_child1 = &_layer_masks_child1;
- child1_is_directory = d_is_dir(dentry_child1);
+ state.layer_masks_child1 = &_layer_masks_child1;
+ state.child1_is_directory = d_is_dir(dentry_child1);
}
if (unlikely(dentry_child2)) {
struct landlock_id id = {
@@ -888,118 +991,16 @@ is_access_to_paths_allowed(const struct landlock_domain *const domain,
if (handled && get_inode_id(dentry_child2, &id))
unmask_layers_fs(domain, id, handled,
&_layer_masks_child2, dentry_child2);
- layer_masks_child2 = &_layer_masks_child2;
- child2_is_directory = d_is_dir(dentry_child2);
+ state.layer_masks_child2 = &_layer_masks_child2;
+ state.child2_is_directory = d_is_dir(dentry_child2);
}
- walker_path = *path;
- path_get(&walker_path);
/*
* We need to walk through all the hierarchy to not miss any relevant
- * restriction.
+ * restriction. Reaching the real root without a grant from each
+ * layer denies access.
*/
- while (true) {
- /*
- * If at least all accesses allowed on the destination are
- * already allowed on the source, respectively if there is at
- * least as much as restrictions on the destination than on the
- * source, then we can safely refer files from the source to
- * the destination without risking a privilege escalation.
- * This also applies in the case of RENAME_EXCHANGE, which
- * implies checks on both direction. This is crucial for
- * standalone multilayered security policies. Furthermore,
- * this helps avoid policy writers to shoot themselves in the
- * foot.
- */
- if (unlikely(is_dom_check &&
- no_more_access(
- layer_masks_parent1, layer_masks_child1,
- child1_is_directory, layer_masks_parent2,
- layer_masks_child2,
- child2_is_directory))) {
- /*
- * Now, downgrades the remaining checks from domain
- * handled accesses to requested accesses.
- */
- is_dom_check = false;
- access_masked_parent1 = access_request_parent1;
- access_masked_parent2 = access_request_parent2;
-
- allowed_parent1 =
- allowed_parent1 ||
- scope_to_request(access_masked_parent1,
- layer_masks_parent1);
- allowed_parent2 =
- allowed_parent2 ||
- scope_to_request(access_masked_parent2,
- layer_masks_parent2);
-
- /* Stops when all accesses are granted. */
- if (allowed_parent1 && allowed_parent2)
- break;
- }
-
- if (get_inode_id(walker_path.dentry, &id)) {
- allowed_parent1 =
- allowed_parent1 ||
- unmask_layers_fs(domain, id,
- access_masked_parent1,
- layer_masks_parent1,
- walker_path.dentry);
- allowed_parent2 =
- allowed_parent2 ||
- unmask_layers_fs(domain, id,
- access_masked_parent2,
- layer_masks_parent2,
- walker_path.dentry);
- }
-
- /* Stops when a rule from each layer grants access. */
- if (allowed_parent1 && allowed_parent2)
- break;
-
-jump_up:
- if (walker_path.dentry == walker_path.mnt->mnt_root) {
- if (follow_up(&walker_path)) {
- /* Ignores hidden mount points. */
- goto jump_up;
- } else {
- /*
- * Stops at the real root. Denies access
- * because not all layers have granted access.
- */
- break;
- }
- }
-
- if (unlikely(IS_ROOT(walker_path.dentry))) {
- if (likely(walker_path.mnt->mnt_flags & MNT_INTERNAL)) {
- /*
- * Stops and allows access when reaching disconnected root
- * directories that are part of internal filesystems (e.g. nsfs,
- * which is reachable through /proc/<pid>/ns/<namespace>).
- */
- allowed_parent1 = true;
- allowed_parent2 = true;
- break;
- }
-
- /*
- * We reached a disconnected root directory from a bind mount.
- * Let's continue the walk with the mount point we missed.
- */
- dput(walker_path.dentry);
- walker_path.dentry = walker_path.mnt->mnt_root;
- dget(walker_path.dentry);
- } else {
- struct dentry *const parent_dentry =
- dget_parent(walker_path.dentry);
-
- dput(walker_path.dentry);
- walker_path.dentry = parent_dentry;
- }
- }
- path_put(&walker_path);
+ vfs_walk_ancestors(path, check_access_path_walk, &state, 0);
/*
* Check CONFIG_SECURITY_LANDLOCK_LOG to enable elision of
@@ -1007,24 +1008,24 @@ is_access_to_paths_allowed(const struct landlock_domain *const domain,
* dead code elimination.
*/
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
- if (!allowed_parent1 && log_request_parent1) {
+ if (!state.allowed_parent1 && log_request_parent1) {
log_request_parent1->type = LANDLOCK_REQUEST_FS_ACCESS;
log_request_parent1->audit.type = LSM_AUDIT_DATA_PATH;
log_request_parent1->audit.u.path = *path;
- log_request_parent1->access = access_masked_parent1;
+ log_request_parent1->access = state.access_masked_parent1;
log_request_parent1->layer_masks = layer_masks_parent1;
}
- if (!allowed_parent2 && log_request_parent2) {
+ if (!state.allowed_parent2 && log_request_parent2) {
log_request_parent2->type = LANDLOCK_REQUEST_FS_ACCESS;
log_request_parent2->audit.type = LSM_AUDIT_DATA_PATH;
log_request_parent2->audit.u.path = *path;
- log_request_parent2->access = access_masked_parent2;
+ log_request_parent2->access = state.access_masked_parent2;
log_request_parent2->layer_masks = layer_masks_parent2;
}
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
- return allowed_parent1 && allowed_parent2;
+ return state.allowed_parent1 && state.allowed_parent2;
}
static int current_check_access_path(const struct path *const path,
diff --git a/tools/testing/selftests/bpf/prog_tests/path_ancestors.c b/tools/testing/selftests/bpf/prog_tests/path_ancestors.c
new file mode 100644
index 0000000000000..ce1ded844c3a5
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/path_ancestors.c
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Justin Suess */
+
+#include <sys/stat.h>
+#include <sys/xattr.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <test_progs.h>
+#include "path_ancestors.skel.h"
+
+void test_path_ancestors(void)
+{
+ char base[] = "/tmp/path_ancestors_XXXXXX";
+ struct path_ancestors *skel = NULL;
+ char suba[280], subb[280];
+ bool xattr_works;
+ int err;
+
+ if (!ASSERT_OK_PTR(mkdtemp(base), "mkdtemp"))
+ return;
+ snprintf(suba, sizeof(suba), "%s/a", base);
+ snprintf(subb, sizeof(subb), "%s/a/b", base);
+ if (!ASSERT_OK(mkdir(suba, 0755), "mkdir_a"))
+ goto out_rm;
+
+ /* Read back by the program at the escalated position (== base). */
+ err = setxattr(base, "user.walk", "hello", 6, 0);
+ xattr_works = !err;
+ if (err && errno != EOPNOTSUPP && !ASSERT_OK(err, "setxattr"))
+ goto out_rm;
+
+ skel = path_ancestors__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "open_and_load"))
+ goto out_rm;
+ skel->bss->monitored_pid = getpid();
+ if (!ASSERT_OK(path_ancestors__attach(skel), "attach"))
+ goto out;
+
+ /* mkdir b: the hook sees dir == suba, whose ancestry is walked. */
+ if (!ASSERT_OK(mkdir(subb, 0755), "mkdir_b"))
+ goto out;
+
+ ASSERT_EQ(skel->bss->test_err, 0, "test_err");
+ ASSERT_EQ(skel->bss->escalate_err, 0, "escalate_err");
+ /* suba, base, /tmp, / at least; equality across modes is the point. */
+ ASSERT_GE(skel->bss->rcu_count, 3, "rcu_count");
+ ASSERT_EQ(skel->bss->ref_count, skel->bss->rcu_count, "ref_vs_rcu");
+ ASSERT_EQ(skel->bss->rcu_ns_count, skel->bss->rcu_count,
+ "nonsleepable_vs_rcu");
+ /*
+ * The escalated position is walked twice: once lockless, then again
+ * as the resumed referenced iteration's first position.
+ */
+ ASSERT_EQ(skel->bss->hybrid_count, skel->bss->rcu_count + 1,
+ "hybrid_vs_rcu");
+ ASSERT_EQ(skel->bss->retry_flags, 0, "no_retry");
+ ASSERT_EQ(skel->bss->ref_flags, 0, "ref_flags");
+
+ /* The acquired second position, used after its step was taken. */
+ ASSERT_STREQ(skel->bss->second_path, base, "second_path");
+ ASSERT_EQ(skel->bss->second_len, strlen(base) + 1, "second_len");
+
+ /* The escalated position is the walk's second one: base. */
+ ASSERT_STREQ(skel->bss->escalated_path, base, "escalated_path");
+ ASSERT_EQ(skel->bss->escalated_len, strlen(base) + 1, "escalated_len");
+ if (xattr_works) {
+ ASSERT_EQ(skel->bss->xattr_ret, 6, "xattr_len");
+ ASSERT_STREQ(skel->bss->xattr_value, "hello", "xattr_value");
+ }
+
+out:
+ path_ancestors__destroy(skel);
+out_rm:
+ rmdir(subb);
+ rmdir(suba);
+ rmdir(base);
+}
diff --git a/tools/testing/selftests/bpf/progs/path_ancestors.c b/tools/testing/selftests/bpf/progs/path_ancestors.c
new file mode 100644
index 0000000000000..50ce0ce163ddd
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/path_ancestors.c
@@ -0,0 +1,134 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Justin Suess */
+
+#include "vmlinux.h"
+#include <errno.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_kfuncs.h"
+
+char _license[] SEC("license") = "GPL";
+
+__u32 monitored_pid;
+
+int rcu_count; /* positions seen by the pure lockless walk */
+int rcu_ns_count; /* ditto, from the non-sleepable program */
+int ref_count; /* positions seen by the pure referenced walk */
+int hybrid_count; /* positions seen by the lockless+escalate walk */
+int retry_flags; /* BPF_PATH_ANCESTORS_RETRY observations */
+int ref_flags; /* pos flags seen by the referenced walk */
+int second_len; /* d_path length of the walk's second position */
+int xattr_ret; /* xattr read at the escalated position */
+int escalated_len; /* d_path length of the escalated position */
+int escalate_err; /* bpf_path_ancestors_legitimize() result */
+int test_err;
+char second_path[256];
+char escalated_path[256];
+char xattr_value[16];
+
+static bool monitored(void)
+{
+ return (bpf_get_current_pid_tgid() >> 32) == monitored_pid;
+}
+
+/*
+ * Lockless walk from a non-sleepable program: the RCU critical section is
+ * implicit, no bpf_rcu_read_lock() needed.
+ */
+SEC("lsm/path_mkdir")
+int BPF_PROG(rcu_nonsleepable, const struct path *dir, struct dentry *dentry,
+ umode_t mode)
+{
+ struct bpf_iter_path_ancestors_rcu rit;
+
+ if (!monitored())
+ return 0;
+
+ bpf_iter_path_ancestors_rcu_new(&rit, (struct path *)dir, 0);
+ while (bpf_iter_path_ancestors_rcu_next(&rit))
+ rcu_ns_count++;
+ retry_flags |= bpf_path_ancestors_rcu_pos_flags(&rit);
+ bpf_iter_path_ancestors_rcu_destroy(&rit);
+ return 0;
+}
+
+SEC("lsm.s/path_mkdir")
+int BPF_PROG(walk_modes, const struct path *dir, struct dentry *dentry,
+ umode_t mode)
+{
+ struct bpf_iter_path_ancestors_rcu rit;
+ struct bpf_iter_path_ancestors it;
+ struct bpf_dynptr value_ptr;
+ struct path *pos;
+
+ if (!monitored())
+ return 0;
+
+ /*
+ * Mode 1: pure lockless, under an explicit RCU critical section.
+ * Positions are borrowed, so nothing is released here.
+ */
+ bpf_rcu_read_lock();
+ bpf_iter_path_ancestors_rcu_new(&rit, (struct path *)dir, 0);
+ while (bpf_iter_path_ancestors_rcu_next(&rit))
+ rcu_count++;
+ retry_flags |= bpf_path_ancestors_rcu_pos_flags(&rit);
+ bpf_iter_path_ancestors_rcu_destroy(&rit);
+ bpf_rcu_read_unlock();
+
+ /*
+ * Mode 2: pure referenced. Every position comes acquired, so it
+ * stays valid for sleepable work and past the step that yielded it.
+ */
+ bpf_iter_path_ancestors_new(&it, (struct path *)dir, 0);
+ while ((pos = bpf_iter_path_ancestors_next(&it))) {
+ ref_count++;
+ ref_flags |= bpf_path_ancestors_pos_flags(&it);
+ if (ref_count == 2)
+ second_len = bpf_path_d_path(pos, second_path,
+ sizeof(second_path));
+ bpf_path_put(pos);
+ }
+ bpf_iter_path_ancestors_destroy(&it);
+
+ /*
+ * Mode 3: hybrid. Walk lockless to the second position, then hand
+ * that position over to a referenced iteration which resumes there.
+ */
+ bpf_rcu_read_lock();
+ bpf_iter_path_ancestors_rcu_new(&rit, (struct path *)dir, 0);
+ while (bpf_iter_path_ancestors_rcu_next(&rit)) {
+ hybrid_count++;
+ if (hybrid_count == 2)
+ break;
+ }
+ escalate_err = bpf_path_ancestors_legitimize(&it, &rit);
+ retry_flags |= bpf_path_ancestors_rcu_pos_flags(&rit);
+ bpf_iter_path_ancestors_rcu_destroy(&rit);
+ bpf_rcu_read_unlock();
+
+ if (escalate_err)
+ test_err = 1;
+
+ /*
+ * Out of the RCU critical section. The resumed iteration's first
+ * position is the escalated one, kept alive by the reference the
+ * iteration hands out, so sleepable work can run on it.
+ */
+ while ((pos = bpf_iter_path_ancestors_next(&it))) {
+ hybrid_count++;
+ if (hybrid_count == 3) {
+ escalated_len = bpf_path_d_path(pos, escalated_path,
+ sizeof(escalated_path));
+ bpf_dynptr_from_mem(xattr_value, sizeof(xattr_value),
+ 0, &value_ptr);
+ /* A trusted path's dentry is trusted, never NULL. */
+ xattr_ret = bpf_get_dentry_xattr(pos->dentry,
+ "user.walk",
+ &value_ptr);
+ }
+ bpf_path_put(pos);
+ }
+ bpf_iter_path_ancestors_destroy(&it);
+ return 0;
+}
|
| 3/2 | 2026/10/06 00:34 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 23171 output: 30 thoughts: 964 Thoughts: **Analyzing the Patch for Fuzzing Potential** Okay, let's dive into this patch. My primary goal is to determine if it's "WorthFuzzing," and to do so, I need to understand the changes, their reachability, and identify key aspects for fuzzing. First, I see a substantial set of modifications. It's touching core VFS components (`fs/namei.c`, `fs/namespace.c`), the Landlock LSM (`security/landlock/fs.c`), and the BPF subsystem (`kernel/bpf/verifier.c`, `fs/bpf_fs_kfuncs.c`). The addition of BPF kfuncs for path ancestor iteration is particularly interesting (`bpf_iter_path_ancestors_new`, etc.). This also involves internal VFS structures, such as `struct vfs_ancestor_walk`. The patch introduces `mnt_undo_legitimize` and modifies existing functions like `delayed_mntput` and `legitimize_mnt`. The changes to `fs/namei.c` with the refactoring of path walking (`vfs_walk_*`) and the integration of `vfs_walk_ancestors` into Landlock's access checks are also significant. Specifically, it looks like there's a refactoring of the VFS walking logic, with new functions like `vfs_walk_start`, `vfs_walk_next`, etc., which are used both internally and, crucially, by Landlock's access control. Landlock is used in a lot of places so that makes it a critical part of this patch. The use of `vfs_walk_ancestors` and its integration into `is_access_to_paths_allowed` in Landlock is now a critical part of this patch, as that LSM call is made for nearly every file operation in Landlock environments! My initial assessment is that this is highly reachable, absolutely "WorthFuzzing." VFS path lookup and Landlock access checks are fundamental parts of the kernel, and the BPF kfuncs provide a new interface, making them a very interesting target. The criteria for fuzzing appear to be met. We're looking at: * **Core Subsystems:** VFS, Landlock (which is relevant to syscall handling, and security), and BPF are all involved. * **Refactorings and Code Cleanups:** The VFS walking logic and Landlock's access check refactoring clearly qualify. * **Reachable Functionality:** The patch modifies code that is critical for path lookups, filesystem access checks, and BPF program execution, which is *very* reachable. Therefore, "WorthFuzzing" is TRUE. Now, I need to determine the specific "FocusSymbols" and "EnableConfigs." I'll need to go through the kernel source and search for the config options and symbols involved. I will use `grep` or manual inspection to get the correct information and verify what exactly we need to enable, and the new symbols and functions that are the most interesting for fuzzing. |
| 4/2 | 2026/10/06 00:34 | tool | grepper |
0mArgs:{
"Expression": "bpf_fs_kfunc",
"PathPrefixes": [
"fs/"
]
}Results: {
"Output": "fs/Makefile=128=obj-$(CONFIG_ZONEFS_FS)\t\t+= zonefs/\nfs/Makefile:129:obj-$(CONFIG_BPF_LSM)\t\t+= bpf_fs_kfuncs.o\nfs/Makefile-130-obj-$(CONFIG_RESCTRL_FS)\t+= resctrl/\n--\nfs/bpf_fs_kfuncs.c=738=__bpf_kfunc_end_defs();\nfs/bpf_fs_kfuncs.c-739-\nfs/bpf_fs_kfuncs.c:740:BTF_KFUNCS_START(bpf_fs_kfunc_set_ids)\nfs/bpf_fs_kfuncs.c-741-BTF_ID_FLAGS(func, bpf_get_task_exe_file, KF_ACQUIRE | KF_RET_NULL)\n--\nfs/bpf_fs_kfuncs.c=763=BTF_ID_FLAGS(func, bpf_path_put, KF_RELEASE | KF_SLEEPABLE)\nfs/bpf_fs_kfuncs.c:764:BTF_KFUNCS_END(bpf_fs_kfunc_set_ids)\nfs/bpf_fs_kfuncs.c-765-\nfs/bpf_fs_kfuncs.c-766-/* Side-effecting kfuncs that stay exclusive to LSM programs. */\nfs/bpf_fs_kfuncs.c:767:BTF_SET_START(bpf_fs_kfunc_lsm_only_ids)\nfs/bpf_fs_kfuncs.c-768-BTF_ID(func, bpf_set_dentry_xattr)\nfs/bpf_fs_kfuncs.c=769=BTF_ID(func, bpf_remove_dentry_xattr)\nfs/bpf_fs_kfuncs.c:770:BTF_SET_END(bpf_fs_kfunc_lsm_only_ids)\nfs/bpf_fs_kfuncs.c-771-\n--\nfs/bpf_fs_kfuncs.c=776=BTF_SET_END(bpf_inode_init_xattr_hooks)\nfs/bpf_fs_kfuncs.c-777-\nfs/bpf_fs_kfuncs.c:778:static int bpf_fs_kfuncs_filter(const struct bpf_prog *prog, u32 kfunc_id)\nfs/bpf_fs_kfuncs.c-779-{\nfs/bpf_fs_kfuncs.c:780:\tif (!btf_id_set8_contains(\u0026bpf_fs_kfunc_set_ids, kfunc_id))\nfs/bpf_fs_kfuncs.c-781-\t\treturn 0;\n--\nfs/bpf_fs_kfuncs.c-797-\tif (bpf_prog_is_binfmt_misc_ops(prog) \u0026\u0026\nfs/bpf_fs_kfuncs.c:798:\t !btf_id_set_contains(\u0026bpf_fs_kfunc_lsm_only_ids, kfunc_id))\nfs/bpf_fs_kfuncs.c-799-\t\treturn 0;\n--\nfs/bpf_fs_kfuncs.c=824=bool bpf_lsm_has_d_inode_locked(const struct bpf_prog *prog)\n--\nfs/bpf_fs_kfuncs.c-828-\nfs/bpf_fs_kfuncs.c:829:static const struct btf_kfunc_id_set bpf_fs_kfunc_set = {\nfs/bpf_fs_kfuncs.c-830-\t.owner = THIS_MODULE,\nfs/bpf_fs_kfuncs.c:831:\t.set = \u0026bpf_fs_kfunc_set_ids,\nfs/bpf_fs_kfuncs.c:832:\t.filter = bpf_fs_kfuncs_filter,\nfs/bpf_fs_kfuncs.c-833-};\nfs/bpf_fs_kfuncs.c-834-\nfs/bpf_fs_kfuncs.c:835:static int __init bpf_fs_kfuncs_init(void)\nfs/bpf_fs_kfuncs.c-836-{\n--\nfs/bpf_fs_kfuncs.c-838-\nfs/bpf_fs_kfuncs.c:839:\tret = register_btf_kfunc_id_set(BPF_PROG_TYPE_LSM, \u0026bpf_fs_kfunc_set);\nfs/bpf_fs_kfuncs.c-840-\tif (ret || !IS_ENABLED(CONFIG_BINFMT_MISC_BPF))\n--\nfs/bpf_fs_kfuncs.c-842-\treturn register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS,\nfs/bpf_fs_kfuncs.c:843:\t\t\t\t\t \u0026bpf_fs_kfunc_set);\nfs/bpf_fs_kfuncs.c-844-}\nfs/bpf_fs_kfuncs.c-845-\nfs/bpf_fs_kfuncs.c:846:late_initcall(bpf_fs_kfuncs_init);\n"
} |
| 5/2 | 2026/10/06 00:34 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 26168 output: 32 thoughts: 42 Thoughts: Okay, here's my interpretation of that thought process, assuming I'm deep in the kernel development trenches: **BPF and LSM Integration: A Quick Check** Right, so `fs/Makefile` has this line: `obj-$(CONFIG_BPF_LSM) += bpf_fs_kfuncs.o`. That immediately triggers a mental flag. I see the connection between the filesystem code and BPF-based LSM (Linux Security Modules) functionality. My instinct is to confirm the dependencies in `Kconfig`. I need to ensure that the configuration options are set up correctly for this to work. I need to make sure `CONFIG_BPF_LSM` is properly defined. I will need to look into this a little further and verify the relationships in `Kconfig`. Any missing or misconfigured dependencies would cause problems down the line. It is not something I want to leave to chance. Time to go dig into those configuration files. |
| 6/2 | 2026/10/06 00:34 | tool | grepper |
0mArgs:{
"Expression": "config BPF_LSM",
"PathPrefixes": [
"kernel/bpf/"
]
}Results: {
"Output": "kernel/bpf/Kconfig=103=source \"kernel/bpf/preload/Kconfig\"\nkernel/bpf/Kconfig-104-\nkernel/bpf/Kconfig:105:config BPF_LSM\nkernel/bpf/Kconfig-106-\tbool \"Enable BPF LSM Instrumentation\"\n"
} |
| 7/2 | 2026/10/06 00:34 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 26532 output: 35 thoughts: 20 Thoughts: Okay, let's dive into this. I'm going to take a look at the `kernel/bpf/Kconfig` file. Specifically, I need to focus around line 105. I'm betting that's where the heart of the matter lies, probably dealing with the core configuration options related to BPF within the kernel. I expect this section will expose the settings for enabling and disabling various BPF features, like JIT compilation, extensions, or perhaps even specific helper functions. My expertise tells me these Kconfig files are the lifeblood of kernel configuration, and this line will likely contain the key to understanding how these BPF capabilities are structured and controlled during kernel builds. Let's see what we can find. |
| 8/2 | 2026/10/06 00:34 | tool | read-file |
0mArgs:{
"File": "kernel/bpf/Kconfig",
"FirstLine": 100,
"LineCount": 25
}Results: {
"Contents": " 100:\t\t Provide the kfuncs needed for BPF programs to encrypt and decrypt\n 101:\t\t data. The supported algorithms are AES-CBC and AES-ECB.\n 102:\t\n 103:\tsource \"kernel/bpf/preload/Kconfig\"\n 104:\t\n 105:\tconfig BPF_LSM\n 106:\t\tbool \"Enable BPF LSM Instrumentation\"\n 107:\t\tdepends on BPF_EVENTS\n 108:\t\tdepends on BPF_SYSCALL\n 109:\t\tdepends on SECURITY\n 110:\t\tdepends on BPF_JIT\n 111:\t\thelp\n 112:\t\t Enables instrumentation of the security hooks with BPF programs for\n 113:\t\t implementing dynamic MAC and Audit Policies.\n 114:\t\n 115:\t\t If you are unsure how to answer this question, answer N.\n 116:\t\n 117:\tconfig BPF_JIT_KASAN\n 118:\t\tbool\n 119:\t\tdepends on HAVE_EBPF_JIT_KASAN\n 120:\t\tdepends on KASAN_GENERIC\n 121:\t\tdepends on KASAN_VMALLOC\n 122:\t\tdepends on BPF_JIT\n 123:\t\tdefault y\n 124:\t\thelp\n"
} |
| 9/2 | 2026/10/06 00:34 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 27096 output: 28 thoughts: 10 Thoughts: Okay, let's dive into this. Here's my understanding, presented as my own thought process: **Reviewing LANDLOCK Configuration** Right, so we've covered the basics. Now, before we go any further, I need to make absolutely sure we haven't overlooked any details regarding the LANDLOCK configuration. It's crucial, obviously. I need to refresh my understanding of all the options, the interplay between them, and how they specifically relate to this scenario. Gotta be thorough. No point in rushing ahead until I've solidified this particular area. |
| 10/2 | 2026/10/06 00:34 | tool | grepper |
0mArgs:{
"Expression": "config SECURITY_LANDLOCK",
"PathPrefixes": [
"security/"
]
}Results: {
"Output": "security/landlock/Kconfig-2-\nsecurity/landlock/Kconfig:3:config SECURITY_LANDLOCK\nsecurity/landlock/Kconfig-4-\tbool \"Landlock support\"\n--\nsecurity/landlock/Kconfig-23-\nsecurity/landlock/Kconfig:24:config SECURITY_LANDLOCK_LOG\nsecurity/landlock/Kconfig-25-\tbool\n--\nsecurity/landlock/Kconfig-28-\nsecurity/landlock/Kconfig:29:config SECURITY_LANDLOCK_KUNIT_TEST\nsecurity/landlock/Kconfig-30-\tbool \"KUnit tests for Landlock\" if !KUNIT_ALL_TESTS\n"
} |
| 11/2 | 2026/10/06 00:34 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 27424 output: 33 thoughts: 21 Thoughts: Okay, let's dive into this Kconfig file, specifically the first twenty lines of `security/landlock/Kconfig`. I'm going to quickly parse these lines and extract the core configuration aspects. I'm looking for the key features and dependencies that define Landlock's configuration space. This should be a relatively quick scan, but I need to make sure I don't miss any subtleties. I'll pay attention to the default values, any conditional logic expressed by `depends on` statements, and any obvious grouping of options. My goal is to grasp the overall structure and the foundational options that developers will need to interact with when enabling or customizing Landlock. I want a mental model of the core configuration parameters. |
| 12/2 | 2026/10/06 00:34 | tool | read-file |
0mArgs:{
"File": "security/landlock/Kconfig",
"FirstLine": 1,
"LineCount": 20
}Results: {
"Contents": " 1:\t# SPDX-License-Identifier: GPL-2.0-only\n 2:\t\n 3:\tconfig SECURITY_LANDLOCK\n 4:\t\tbool \"Landlock support\"\n 5:\t\tdepends on SECURITY\n 6:\t\tselect SECURITY_NETWORK\n 7:\t\tselect SECURITY_PATH\n 8:\t\thelp\n 9:\t\t Landlock is a sandboxing mechanism that enables processes to restrict\n 10:\t\t themselves (and their future children) by gradually enforcing\n 11:\t\t tailored access control policies. A Landlock security policy is a\n 12:\t\t set of access rights (e.g. open a file in read-only, make a\n 13:\t\t directory, etc.) tied to a file hierarchy. Such policy can be\n 14:\t\t configured and enforced by any processes for themselves using the\n 15:\t\t dedicated system calls: landlock_create_ruleset(),\n 16:\t\t landlock_add_rule(), and landlock_restrict_self().\n 17:\t\n 18:\t\t See Documentation/userspace-api/landlock.rst for further information.\n 19:\t\n 20:\t\t If you are unsure how to answer this question, answer N. Otherwise,\n"
} |
| 13/2 | 2026/10/06 00:34 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 27940 output: 31 thoughts: 2180 Thoughts: **Fuzzing Strategy for Path Walking and BPF Code** Alright, let's break this down. First, I'm looking at the recent code changes, specifically focusing on modifications to the file system (VFS) and BPF (Berkeley Packet Filter) subsystems. My initial assessment? This is prime territory for fuzzing with syzkaller. There's a lot of potential for uncovered edge cases, race conditions, and general robustness testing that the changes introduce, especially considering the deep integration with core kernel functionalities. I see new functions and modifications in `fs/bpf_fs_kfuncs.c`, `fs/namei.c`, `fs/namespace.c`, `kernel/bpf/verifier.c`, and `security/landlock/fs.c`. Specifically, I'm watching: * The refactoring of VFS namei and path walking, dealing with `follow_dotdot_rcu`, `follow_dotdot`, `__path_walk_parent`, and `__path_walk_parent_rcu`. Changes to the namespace mount reference counting with `mnt_undo_legitimize`, `delayed_mntput`, and `legitimize_mnt`. * The use of `vfs_walk_ancestors` for Landlock's LSM hook execution with the `check_access_path_walk` callback. * The newly-introduced BPF kfuncs for path ancestor iteration. We're talking sleepable vs RCU modes, and the various `bpf_iter_path_ancestors_*` functions, along with reference counting and lockless handoff mechanics. Important to note is `bpf_path_ancestors_legitimize`. * And finally, there are verifier changes in BPF, specifically in argument processing and reference tracking with `release_reference_nomark` in `process_iter_next_call`. My gut tells me that all of this is an obvious win for syzkaller. Next, I'm looking at added defensive checks. This is important to determine if anything looks like a known vulnerability in the new code, or checks that might cause crashes during fuzzing. I've spotted: * `RCU_LOCKDEP_WARN(!rcu_read_lock_held(), ...)` in `vfs_walk_start`. * `WARN_ON_ONCE(mnt->mnt.mnt_flags & MNT_DOOMED)` in `mnt_undo_legitimize`. * `WARN_ON_ONCE(release_reference_nomark(env, cur_fr->regs[BPF_REG_0].id))` in `process_iter_next_call`. These are good because they provide early detection of potential issues. Now, the configuration aspects. I need to make sure the fuzzing environment is properly set up. The essential configurations I'll need are: 1. `BPF_SYSCALL` 2. `BPF_LSM` 3. `SECURITY_LANDLOCK` `BPF_LSM` requires `BPF_EVENTS`, `BPF_SYSCALL`, `SECURITY`, and `BPF_JIT`. `SECURITY_LANDLOCK` depends on `SECURITY`. Also, VFS, namei, and namespace are typically built-in. Crucially, I've confirmed that `BPF_LSM` and `SECURITY_LANDLOCK` are the primary feature flags enabling the code I'm interested in. Finally, the most important part: FocusSymbols. I need to strategically pick non-ubiquitous kernel functions to focus fuzzing on. Ubiquitous functions are called a lot, and won't be as effective for directed fuzzing. I need to check the function calls involved in the features and bugfixes that I want to test. I want to avoid the functions that are called by almost every syzkaller program, or are ubiquitous in the kernel. This is especially true of functions that are called in the hot path. Let's start with a list of the more important functions and test from that: * `vfs_walk_ancestors` * `vfs_walk_start` * `vfs_walk_next` * `vfs_walk_handover` * `mnt_undo_legitimize` * `__path_walk_parent_rcu` * `__path_walk_parent` * `check_access_path_walk` * `bpf_iter_path_ancestors_new` * `bpf_iter_path_ancestors_next` * `bpf_iter_path_ancestors_destroy` * |
| 14/2 | 2026/10/06 00:34 | tool | grepper |
0mArgs:{
"Expression": "#define __bpf_kfunc",
"PathPrefixes": [
"include/"
]
}Results: {
"Output": "include/linux/btf.h-90- */\ninclude/linux/btf.h:91:#define __bpf_kfunc __used __retain __noclone noinline\ninclude/linux/btf.h-92-\ninclude/linux/btf.h:93:#define __bpf_kfunc_start_defs()\t\t\t\t\t \\\ninclude/linux/btf.h-94-\t__diag_push();\t\t\t\t\t\t\t \\\n--\ninclude/linux/btf.h-99-\ninclude/linux/btf.h:100:#define __bpf_kfunc_end_defs() __diag_pop()\ninclude/linux/btf.h-101-#define __bpf_hook_start() __bpf_kfunc_start_defs()\n"
} |
| 15/2 | 2026/10/06 00:34 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 31375 output: 340 thoughts: 982 Thoughts: **Analysis of New VFS Ancestor Walk and BPF Integration** Okay, so I've been digging into this new functionality. The crucial thing is that every `__bpf_kfunc` is guaranteed a unique symbol because of its attributes. This is important for symbol lookups and hooking. Now, let's identify the right FocusSymbols. My initial thought is that `vfs_walk_ancestors`, `vfs_walk_start`, `vfs_walk_next`, `vfs_walk_handover`, `mnt_undo_legitimize`, the `bpf_iter_path_ancestors_*` family, and `bpf_path_put` are the key players. These directly represent the new ancestor walking engine and the BPF iterator kfuncs. I briefly considered `delayed_mntput` and `vfs_walk_end`, but `mnt_undo_legitimize` is the direct entry point and the others don't align as well with the rules for FocusSymbols: "List functions that specifically implement the logic being added or altered, or direct API entrypoints..." They are definitely *not* ubiquitous functions. Next, I need to make sure the right configs are set. I need `BPF_LSM`, `BPF_SYSCALL`, and `SECURITY_LANDLOCK`. Looking through the relevant Makefiles and Kconfig files confirms this. `BPF_LSM` depends on `BPF_EVENTS`, `BPF_SYSCALL`, `SECURITY`, and `BPF_JIT`. `SECURITY_LANDLOCK` relies on `SECURITY` and selects `SECURITY_NETWORK` and `SECURITY_PATH`. Alright, I've got to double-check if this is "WorthFuzzing". Considering the subsystems touched, VFS, BPF, and LSM (Landlock), the code's reachability in virtualized environments, and the new locking, reference counting, and walking logic across RCU and sleepable contexts (including handling races with `mnt_undo_legitimize` and `delayed_mntput`), and the assertions (`RCU_LOCKDEP_WARN`, `WARN_ON_ONCE`), this is *definitely* WorthFuzzing. My reasoning boils down to this: A new generic VFS ancestor walk mechanism is being introduced, and it's being integrated into Landlock and the BPF iterator kfuncs. This involves modifying mount reference lifecycle and includes new assertions. All code paths are reachable from user space. Okay, I'm confident in my analysis. Let's call `set-results`. |
| 16/2 | 2026/10/06 00:34 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"BPF_SYSCALL",
"BPF_LSM",
"SECURITY_LANDLOCK"
],
"FocusSymbols": [
"vfs_walk_ancestors",
"vfs_walk_start",
"vfs_walk_next",
"vfs_walk_end",
"vfs_walk_handover",
"mnt_undo_legitimize",
"bpf_iter_path_ancestors_new",
"bpf_iter_path_ancestors_next",
"bpf_iter_path_ancestors_destroy",
"bpf_iter_path_ancestors_rcu_new",
"bpf_iter_path_ancestors_rcu_next",
"bpf_iter_path_ancestors_rcu_destroy",
"bpf_path_ancestors_legitimize",
"bpf_path_put"
],
"Reasoning": "The patch introduces a stepwise VFS ancestor path walking engine (vfs_walk_*) supporting both sleepable referenced walks and lockless RCU walks, rewrites Landlock path access checks to use it, modifies mount reference management (mnt_undo_legitimize, delayed_mntput), introduces BPF path iterator kfuncs (bpf_iter_path_ancestors_*), and updates the BPF verifier. These changes alter core VFS and BPF control flow, concurrency handling, and reference counting, making them highly relevant for fuzzing.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"BPF_SYSCALL",
"BPF_LSM",
"SECURITY_LANDLOCK"
],
"FocusSymbols": [
"vfs_walk_ancestors",
"vfs_walk_start",
"vfs_walk_next",
"vfs_walk_end",
"vfs_walk_handover",
"mnt_undo_legitimize",
"bpf_iter_path_ancestors_new",
"bpf_iter_path_ancestors_next",
"bpf_iter_path_ancestors_destroy",
"bpf_iter_path_ancestors_rcu_new",
"bpf_iter_path_ancestors_rcu_next",
"bpf_iter_path_ancestors_rcu_destroy",
"bpf_path_ancestors_legitimize",
"bpf_path_put"
],
"Reasoning": "The patch introduces a stepwise VFS ancestor path walking engine (vfs_walk_*) supporting both sleepable referenced walks and lockless RCU walks, rewrites Landlock path access checks to use it, modifies mount reference management (mnt_undo_legitimize, delayed_mntput), introduces BPF path iterator kfuncs (bpf_iter_path_ancestors_*), and updates the BPF verifier. These changes alter core VFS and BPF control flow, concurrency handling, and reference counting, making them highly relevant for fuzzing.",
"WorthFuzzing": true
} |
| 17/1 | 2026/10/06 00:34 | action | If |
2mArgs:{
"WorthFuzzing": true
}Results: null |
| 18/2 | 2026/10/06 00:34 | agent | kmsan-evaluator |
2mModel:core Args: null Results: {
"KMSANReasoning": "The patch series introduces stepwise VFS path ancestor walking infrastructure (vfs_walk_ancestors) used by Landlock and exposes BPF open-coded iterators (bpf_iter_path_ancestors and bpf_iter_path_ancestors_rcu) along with associated kfuncs.\n\nEvaluating the changes for uninitialized memory risks vs. KASAN/LOCKDEP:\n1. Memory initialization:\n - In `bpf_iter_path_ancestors_next()`, `held = kmalloc_obj(*held)` allocates a `struct path` which is immediately fully populated by `*held = *pos` (a 16-byte struct with two pointer fields and no padding).\n - In `bpf_path_ancestors_legitimize()`, the destination iterator `kit` is immediately zeroed using `memset(kit, 0, sizeof(*kit))` before any operations.\n - In `vfs_walk_start()`, when `VFS_WALK_RCU` is set, `m_seq` and `seq` are explicitly sampled via `read_seqbegin()` and `raw_seqcount_begin()`. In non-RCU mode, branches that read `seq` or `m_seq` are strictly guarded by `aw-\u003eflags \u0026 VFS_WALK_RCU` and never execute.\n - In `security/landlock/fs.c`, the new `struct landlock_walk_state state` is stack-allocated with designated initializers (zeroing unmentioned fields) and explicitly initializes all remaining fields before being passed to `vfs_walk_ancestors()`.\n2. Kernel-to-user info leaks:\n - No kernel data structures or iterator states are copied to userspace via `copy_to_user`, netlink, ioctl, or socket buffers.\n3. KASAN vs KMSAN applicability:\n - The primary risks of this patch involve reference counting (e.g. `dget`/`dput`, `path_get`/`path_put`, `mnt_undo_legitimize`), lockless RCU concurrency races, or BPF verifier reference tracking. Any memory corruption, use-after-free, or double-free resulting from reference count errors is detected by KASAN and standard refcount tracking, while locking issues are caught by LOCKDEP.\n - There are no uninitialized memory usages or information leaks that would require a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false
}Instruction: You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.
CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
a comprehensive suite of debugging tools and sanitizers, including KASAN
(out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
(locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
or page allocations) and kernel-to-user memory info-leaks.
Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.
Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.
Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
attributes, ioctl output arguments, socket options, or BPF buffers) where fields
or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
uninitialized bytes of existing buffers.
Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
dereferences, locking deadlocks, or use-after-free bugs (these are already caught
by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.
Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit 0b8b5fc9ad0c8a6575b46e5c6fe1969366398c37
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Oct 6 00:34:04 2026 +0000
syz-cluster: applied patch under review
diff --git a/fs/bpf_fs_kfuncs.c b/fs/bpf_fs_kfuncs.c
index 08f0847c49708..9dad65df42674 100644
--- a/fs/bpf_fs_kfuncs.c
+++ b/fs/bpf_fs_kfuncs.c
@@ -13,7 +13,11 @@
#include <linux/kernfs.h>
#include <linux/lsm_hooks.h>
#include <linux/mm.h>
+#include <linux/namei.h>
#include <linux/net.h>
+#include <linux/slab.h>
+
+#include "internal.h"
#include <linux/xattr.h>
__bpf_kfunc_start_defs();
@@ -500,6 +504,239 @@ __bpf_kfunc struct inode *bpf_real_data_inode(struct file *file)
__bpf_kfunc_end_defs();
+enum bpf_path_ancestors_flag {
+ /* bpf_path_ancestors[_rcu]_pos_flags() bits */
+ BPF_PATH_ANCESTORS_DISCONNECTED = (1 << 0),
+ /* the position is a mountpoint a mount crossing landed on */
+ BPF_PATH_ANCESTORS_MOUNTPOINT = (1 << 1),
+ /* the iteration ended on a failed allocation, not at the root */
+ BPF_PATH_ANCESTORS_NOMEM = (1 << 2),
+ /* the lockless iteration lost a race and reached no conclusion */
+ BPF_PATH_ANCESTORS_RETRY = (1 << 3),
+};
+
+/*
+ * Walks over a path's ancestors, in two variants differing in how
+ * positions are kept alive:
+ *
+ * bpf_iter_path_ancestors runs with references. Its kfuncs are
+ * sleepable, so the iteration may sleep between positions.
+ *
+ * Each position is handed to the program as an acquired reference of its
+ * own, to release with bpf_path_put(). The walk's own reference moves on
+ * with the walk, so a position that outlives the step it came from - a
+ * dentry saved for later, or the path a sleepable kfunc is still working
+ * on - has to be kept alive by the program's reference rather than by the
+ * iterator's.
+ *
+ * bpf_iter_path_ancestors_rcu runs lockless over one RCU read-side
+ * critical section, which the verifier enforces around the whole
+ * iteration and within which sleeping is impossible. Its positions are
+ * borrowed, not acquired: they are only valid until the next step, and
+ * nothing derived from one may be passed to a kfunc demanding a trusted
+ * argument. A lost race ends the iteration with
+ * BPF_PATH_ANCESTORS_RETRY set; the program discards what it derived
+ * from the walk and retries, typically on the referenced variant, or
+ * escalates mid-walk with bpf_path_ancestors_legitimize().
+ */
+struct bpf_iter_path_ancestors {
+ __u64 __opaque[5];
+} __aligned(8);
+
+struct bpf_iter_path_ancestors_rcu {
+ __u64 __opaque[5];
+} __aligned(8);
+
+struct bpf_path_ancestors_kern {
+ struct vfs_ancestor_walk aw;
+ int step; /* last vfs_walk_next() result, or -ENOMEM */
+} __aligned(8);
+
+static int bpf_path_ancestors_new(struct bpf_path_ancestors_kern *kit,
+ struct path *path, u64 flags,
+ unsigned int walk_flags)
+{
+ BUILD_BUG_ON(sizeof(struct bpf_path_ancestors_kern) >
+ sizeof(struct bpf_iter_path_ancestors));
+ BUILD_BUG_ON(__alignof__(struct bpf_path_ancestors_kern) !=
+ __alignof__(struct bpf_iter_path_ancestors));
+ BUILD_BUG_ON(sizeof(struct bpf_iter_path_ancestors) !=
+ sizeof(struct bpf_iter_path_ancestors_rcu));
+ BUILD_BUG_ON(__alignof__(struct bpf_iter_path_ancestors) !=
+ __alignof__(struct bpf_iter_path_ancestors_rcu));
+
+ if (flags) {
+ /* A zeroed walk makes destroying the iterator a no-op. */
+ memset(kit, 0, sizeof(*kit));
+ kit->step = 1;
+ return -EINVAL;
+ }
+ kit->step = 0;
+ vfs_walk_start(&kit->aw, path, walk_flags);
+ return 0;
+}
+
+/* The walk's own view of the next position, which the step after it ends. */
+static struct path *bpf_path_ancestors_step(struct bpf_path_ancestors_kern *kit)
+{
+ if (kit->step)
+ return NULL;
+ kit->step = vfs_walk_next(&kit->aw);
+ return kit->step ? NULL : &kit->aw.pos;
+}
+
+static u32 bpf_path_ancestors_flags(const struct bpf_path_ancestors_kern *kit)
+{
+ u32 flags = 0;
+
+ if (kit->step == -ENOMEM)
+ return BPF_PATH_ANCESTORS_NOMEM;
+ if (kit->step == -ECHILD)
+ return BPF_PATH_ANCESTORS_RETRY;
+ if (!kit->step) {
+ if (kit->aw.pos_flags & VFS_WALK_POS_DISCONNECTED)
+ flags |= BPF_PATH_ANCESTORS_DISCONNECTED;
+ if (kit->aw.pos_flags & VFS_WALK_POS_MOUNTPOINT)
+ flags |= BPF_PATH_ANCESTORS_MOUNTPOINT;
+ }
+ return flags;
+}
+
+__bpf_kfunc_start_defs();
+
+__bpf_kfunc int bpf_iter_path_ancestors_new(struct bpf_iter_path_ancestors *it,
+ struct path *path, u64 flags)
+{
+ return bpf_path_ancestors_new((void *)it, path, flags, 0);
+}
+
+/**
+ * bpf_iter_path_ancestors_next - acquire the walk's next position
+ * @it: the iterator
+ *
+ * Return: the next position with a reference held, to release with
+ * bpf_path_put(), or NULL once the walk has passed the real root - or on
+ * an allocation failure, which ends the iteration and is reported as
+ * %BPF_PATH_ANCESTORS_NOMEM by bpf_path_ancestors_pos_flags().
+ */
+__bpf_kfunc struct path *
+bpf_iter_path_ancestors_next(struct bpf_iter_path_ancestors *it)
+{
+ struct bpf_path_ancestors_kern *kit = (void *)it;
+ struct path *pos = bpf_path_ancestors_step(kit);
+ struct path *held;
+
+ if (!pos)
+ return NULL;
+ /*
+ * The position must outlive the walk's own view of it, so it gets a
+ * reference and a struct path of its own to live in: struct path is
+ * a value type, with nothing a BPF reference could be taken on
+ * otherwise. Sleepable, so no atomic allocation.
+ */
+ held = kmalloc_obj(*held);
+ if (!held) {
+ kit->step = -ENOMEM;
+ return NULL;
+ }
+ *held = *pos;
+ path_get(held);
+ return held;
+}
+
+__bpf_kfunc void
+bpf_iter_path_ancestors_destroy(struct bpf_iter_path_ancestors *it)
+{
+ vfs_walk_end(&((struct bpf_path_ancestors_kern *)it)->aw);
+}
+
+__bpf_kfunc u32
+bpf_path_ancestors_pos_flags(struct bpf_iter_path_ancestors *it__iter)
+{
+ return bpf_path_ancestors_flags((void *)it__iter);
+}
+
+__bpf_kfunc int
+bpf_iter_path_ancestors_rcu_new(struct bpf_iter_path_ancestors_rcu *it,
+ struct path *path, u64 flags)
+{
+ return bpf_path_ancestors_new((void *)it, path, flags, VFS_WALK_RCU);
+}
+
+/*
+ * Unlike the referenced variant, this hands out the walk's own position:
+ * a lockless iteration holds no references to pass on, and the verifier
+ * keeps the whole of it inside one RCU read-side critical section.
+ */
+__bpf_kfunc struct path *
+bpf_iter_path_ancestors_rcu_next(struct bpf_iter_path_ancestors_rcu *it)
+{
+ return bpf_path_ancestors_step((void *)it);
+}
+
+__bpf_kfunc void
+bpf_iter_path_ancestors_rcu_destroy(struct bpf_iter_path_ancestors_rcu *it)
+{
+ vfs_walk_end(&((struct bpf_path_ancestors_kern *)it)->aw);
+}
+
+__bpf_kfunc u32
+bpf_path_ancestors_rcu_pos_flags(struct bpf_iter_path_ancestors_rcu *it__iter)
+{
+ return bpf_path_ancestors_flags((void *)it__iter);
+}
+
+/**
+ * bpf_path_ancestors_legitimize - hand a lockless iteration over to references
+ * @it__uninit: referenced ancestor iterator to begin at @rcu_it__iter's
+ * current position; destroy it with
+ * bpf_iter_path_ancestors_destroy() whether this succeeds or not
+ * @rcu_it__iter: lockless ancestor iterator, on the position to escalate at
+ *
+ * Mirrors unlazy_walk(): acquires the lockless iteration's current position
+ * and leaves @it__uninit ready to continue from it with references, which
+ * its first bpf_iter_path_ancestors_next() then yields - necessarily after
+ * the program has left its RCU read-side critical section, since that kfunc
+ * is sleepable. Sleepable work on the escalated position therefore happens
+ * on the iteration's own reference, and nothing is allocated here.
+ *
+ * Return: 0, -%ENOENT if the lockless iteration was not on a position, or
+ * -%ECHILD if it lost the race to acquire one; %BPF_PATH_ANCESTORS_RETRY is
+ * then also flagged, and the program has reached no conclusion about the
+ * ancestry. @it__uninit is initialized whatever this returns, so a program
+ * need not branch on the result: a walk that could not be escalated simply
+ * yields no position.
+ */
+__bpf_kfunc int
+bpf_path_ancestors_legitimize(struct bpf_iter_path_ancestors *it__uninit,
+ struct bpf_iter_path_ancestors_rcu *rcu_it__iter)
+{
+ struct bpf_path_ancestors_kern *rcu_kit = (void *)rcu_it__iter;
+ struct bpf_path_ancestors_kern *kit = (void *)it__uninit;
+
+ /* A zeroed walk makes destroying the iterator a no-op. */
+ memset(kit, 0, sizeof(*kit));
+ kit->step = 1;
+
+ /* Drained, or already failed: nothing to hand over. */
+ if (rcu_kit->step)
+ return -ENOENT;
+ if (!vfs_walk_handover(&kit->aw, &rcu_kit->aw)) {
+ rcu_kit->step = -ECHILD;
+ return -ECHILD;
+ }
+ kit->step = 0;
+ return 0;
+}
+
+__bpf_kfunc void bpf_path_put(struct path *path)
+{
+ path_put(path);
+ kfree(path);
+}
+
+__bpf_kfunc_end_defs();
+
BTF_KFUNCS_START(bpf_fs_kfunc_set_ids)
BTF_ID_FLAGS(func, bpf_get_task_exe_file, KF_ACQUIRE | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_put_file, KF_RELEASE)
@@ -513,6 +750,17 @@ BTF_ID_FLAGS(func, bpf_inode_init_xattr)
#ifdef CONFIG_NET
BTF_ID_FLAGS(func, bpf_sock_read_xattr, KF_RCU)
#endif
+BTF_ID_FLAGS(func, bpf_iter_path_ancestors_new, KF_ITER_NEW | KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_iter_path_ancestors_next,
+ KF_ITER_NEXT | KF_ACQUIRE | KF_RET_NULL | KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_iter_path_ancestors_destroy, KF_ITER_DESTROY | KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_path_ancestors_pos_flags)
+BTF_ID_FLAGS(func, bpf_iter_path_ancestors_rcu_new, KF_ITER_NEW | KF_RCU_PROTECTED)
+BTF_ID_FLAGS(func, bpf_iter_path_ancestors_rcu_next, KF_ITER_NEXT | KF_RET_NULL)
+BTF_ID_FLAGS(func, bpf_iter_path_ancestors_rcu_destroy, KF_ITER_DESTROY)
+BTF_ID_FLAGS(func, bpf_path_ancestors_rcu_pos_flags)
+BTF_ID_FLAGS(func, bpf_path_ancestors_legitimize)
+BTF_ID_FLAGS(func, bpf_path_put, KF_RELEASE | KF_SLEEPABLE)
BTF_KFUNCS_END(bpf_fs_kfunc_set_ids)
/* Side-effecting kfuncs that stay exclusive to LSM programs. */
diff --git a/fs/internal.h b/fs/internal.h
index c658c8a5ebd56..8a5696157121b 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -71,6 +71,28 @@ struct dentry *start_dirop(struct dentry *parent, struct qstr *name,
unsigned int lookup_flags);
int lookup_noperm_common(struct qstr *qname, struct dentry *base);
+/*
+ * The stepwise engine under vfs_walk_ancestors(); fs-internal so iterating
+ * consumers (BPF) can drive it, with the walk invariants staying in namei.c.
+ * In rcu mode (%VFS_WALK_RCU) the caller holds rcu_read_lock() over the
+ * whole walk, no references are held, and vfs_walk_next() returning -ECHILD
+ * invalidates everything derived from the walk.
+ */
+struct vfs_ancestor_walk {
+ struct path pos;
+ unsigned int seq; /* pos.dentry->d_seq sample (rcu mode) */
+ unsigned int m_seq; /* mount_lock sample (rcu mode) */
+ unsigned int pos_flags; /* VFS_WALK_POS_* describing pos */
+ unsigned int flags; /* VFS_WALK_* */
+};
+
+void vfs_walk_start(struct vfs_ancestor_walk *aw, const struct path *path,
+ unsigned int flags);
+int vfs_walk_next(struct vfs_ancestor_walk *aw);
+void vfs_walk_end(struct vfs_ancestor_walk *aw);
+bool vfs_walk_handover(struct vfs_ancestor_walk *to,
+ struct vfs_ancestor_walk *from);
+
void __init filename_init(void);
/*
diff --git a/fs/mount.h b/fs/mount.h
index 94fcc306d21e7..e714040f8e8fd 100644
--- a/fs/mount.h
+++ b/fs/mount.h
@@ -140,6 +140,7 @@ static inline int is_mounted(struct vfsmount *mnt)
extern struct mount *__lookup_mnt(struct vfsmount *, struct dentry *);
extern int __legitimize_mnt(struct vfsmount *, unsigned);
+void mnt_undo_legitimize(struct mount *mnt);
static inline bool __path_is_mountpoint(const struct path *path)
{
diff --git a/fs/namei.c b/fs/namei.c
index 20a6534ea3eff..31f96602d4e94 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -2152,34 +2152,69 @@ static __always_inline const char *step_into(struct nameidata *nd, int flags,
return step_into_slowpath(nd, flags, dentry);
}
-static struct dentry *follow_dotdot_rcu(struct nameidata *nd)
+/**
+ * __path_walk_parent_rcu - step towards the parent of the given struct path
+ * @path: position to step up from; updated in place on a mount crossing,
+ * which is first if @path is the root of a mounted tree. No
+ * references are acquired; the callers layer their own bookkeeping
+ * (path_connected(), nameidata updates, ...) on top
+ * @root: boundary as for choose_mountpoint_rcu(); if zero'ed, walk all the
+ * way to the global root
+ * @flags: %LOOKUP_NO_XDEV fails a mount crossing with -ECHILD
+ * @m_seq: the walk's mount_lock sample
+ * @seqp: d_seq sample validating @path->dentry; updated to cover the new
+ * @path->dentry when a mount is crossed
+ * @next_seqp: set to the returned parent's d_seq sample
+ *
+ * Returns: the parent dentry (which is @path->dentry itself if that is a
+ * disconnected root), NULL if @path is in the root with nothing to cross
+ * into, or ERR_PTR(-ECHILD) when a concurrent change was detected.
+ */
+static struct dentry *__path_walk_parent_rcu(struct path *path,
+ const struct path *root, int flags,
+ unsigned int m_seq, unsigned int *seqp,
+ unsigned int *next_seqp)
{
struct dentry *parent, *old;
- if (path_equal(&nd->path, &nd->root))
- goto in_root;
- if (unlikely(nd->path.dentry == nd->path.mnt->mnt_root)) {
- struct path path;
- unsigned seq;
- if (!choose_mountpoint_rcu(real_mount(nd->path.mnt),
- &nd->root, &path, &seq))
- goto in_root;
- if (unlikely(nd->flags & LOOKUP_NO_XDEV))
+ if (unlikely(path->dentry == path->mnt->mnt_root)) {
+ struct path mounted;
+ unsigned int seq;
+
+ if (!choose_mountpoint_rcu(real_mount(path->mnt),
+ root, &mounted, &seq))
+ return NULL;
+ if (unlikely(flags & LOOKUP_NO_XDEV))
return ERR_PTR(-ECHILD);
- nd->path = path;
- nd->inode = path.dentry->d_inode;
- nd->seq = seq;
+ *path = mounted;
+ *seqp = seq;
// makes sure that non-RCU pathwalk could reach this state
- if (read_seqretry(&mount_lock, nd->m_seq))
+ if (read_seqretry(&mount_lock, m_seq))
return ERR_PTR(-ECHILD);
/* we know that mountpoint was pinned */
}
- old = nd->path.dentry;
+ old = path->dentry;
parent = old->d_parent;
- nd->next_seq = read_seqcount_begin(&parent->d_seq);
+ *next_seqp = read_seqcount_begin(&parent->d_seq);
// makes sure that non-RCU pathwalk could reach this state
- if (read_seqcount_retry(&old->d_seq, nd->seq))
+ if (read_seqcount_retry(&old->d_seq, *seqp))
return ERR_PTR(-ECHILD);
+ return parent;
+}
+
+static struct dentry *follow_dotdot_rcu(struct nameidata *nd)
+{
+ struct dentry *parent;
+
+ if (path_equal(&nd->path, &nd->root))
+ goto in_root;
+ parent = __path_walk_parent_rcu(&nd->path, &nd->root, nd->flags,
+ nd->m_seq, &nd->seq, &nd->next_seq);
+ if (!parent)
+ goto in_root;
+ if (IS_ERR(parent))
+ return parent;
+ nd->inode = nd->path.dentry->d_inode;
if (unlikely(!path_connected(nd->path.mnt, parent)))
return ERR_PTR(-ECHILD);
return parent;
@@ -2192,36 +2227,308 @@ static struct dentry *follow_dotdot_rcu(struct nameidata *nd)
return nd->path.dentry;
}
-static struct dentry *follow_dotdot(struct nameidata *nd)
+/**
+ * __path_walk_parent - step towards the parent of the given struct path
+ * @path: position to step up from; if it is the root of a mounted tree, it
+ * is first updated to the mount point that tree is mounted on
+ * @root: boundary not to be crossed; if zero'ed, walk all the way to the
+ * global root
+ * @flags: %LOOKUP_NO_XDEV returns -EXDEV once @path was updated to a new
+ * mount; %LOOKUP_BENEATH returns -EXDEV at @root or at the root of
+ * an unmounted tree, where @path->dentry itself is returned
+ * otherwise
+ *
+ * Returns: the parent dentry with its refcount incremented, or an ERR_PTR().
+ */
+static struct dentry *__path_walk_parent(struct path *path, const struct path *root, int flags)
{
- struct dentry *parent;
-
- if (path_equal(&nd->path, &nd->root))
+ if (path_equal(path, root))
goto in_root;
- if (unlikely(nd->path.dentry == nd->path.mnt->mnt_root)) {
- struct path path;
+ if (unlikely(path->dentry == path->mnt->mnt_root)) {
+ struct path new_path;
- if (!choose_mountpoint(real_mount(nd->path.mnt),
- &nd->root, &path))
+ if (!choose_mountpoint(real_mount(path->mnt),
+ root, &new_path))
goto in_root;
- path_put(&nd->path);
- nd->path = path;
- nd->inode = path.dentry->d_inode;
- if (unlikely(nd->flags & LOOKUP_NO_XDEV))
+ path_put(path);
+ *path = new_path;
+ if (unlikely(flags & LOOKUP_NO_XDEV))
return ERR_PTR(-EXDEV);
}
/* rare case of legitimate dget_parent()... */
- parent = dget_parent(nd->path.dentry);
+ return dget_parent(path->dentry);
+
+in_root:
+ if (unlikely(flags & LOOKUP_BENEATH))
+ return ERR_PTR(-EXDEV);
+ return dget(path->dentry);
+}
+
+static unsigned int vfs_walk_pos_flags(const struct vfsmount *mnt,
+ const struct dentry *dentry)
+{
+ if (unlikely(IS_ROOT(dentry) && dentry != mnt->mnt_root))
+ return VFS_WALK_POS_DISCONNECTED;
+ return 0;
+}
+
+/* No boundary: ancestor walks go up to the real root. */
+static const struct path vfs_walk_no_root;
+
+/* Internal walk flag: the starting position has been yielded. */
+#define VFS_WALK_STARTED BIT(31)
+
+/**
+ * vfs_walk_start - begin a stepwise ancestor walk
+ * @aw: walk state, valid until vfs_walk_end()
+ * @path: position to walk up from; never modified
+ * @flags: %VFS_WALK_RCU to walk lockless; the caller then holds
+ * rcu_read_lock() from before vfs_walk_start() until after
+ * vfs_walk_end(), and owns no references on yielded positions.
+ */
+void vfs_walk_start(struct vfs_ancestor_walk *aw, const struct path *path,
+ unsigned int flags)
+{
+ aw->pos = *path;
+ aw->flags = flags;
+ aw->pos_flags = 0;
+ if (flags & VFS_WALK_RCU) {
+ RCU_LOCKDEP_WARN(!rcu_read_lock_held(),
+ "rcu-mode ancestor walk outside of RCU read-side critical section");
+ aw->m_seq = read_seqbegin(&mount_lock);
+ aw->seq = raw_seqcount_begin(&aw->pos.dentry->d_seq);
+ } else {
+ path_get(&aw->pos);
+ }
+}
+
+static int vfs_walk_step_ref(struct vfs_ancestor_walk *aw)
+{
+ struct dentry *parent;
+
+ if (unlikely(aw->pos_flags & VFS_WALK_POS_DISCONNECTED)) {
+ /* Resume at the root of the disconnected position's mount. */
+ dput(aw->pos.dentry);
+ aw->pos.dentry = dget(aw->pos.mnt->mnt_root);
+ aw->pos_flags = 0;
+ return 0;
+ }
+ parent = __path_walk_parent(&aw->pos, &vfs_walk_no_root,
+ LOOKUP_BENEATH);
+ if (IS_ERR(parent))
+ /* The real root. */
+ return 1;
+ /*
+ * A mount crossing can step onto a disconnected root, whose parent
+ * is itself: only then is the mountpoint itself yielded, flagged.
+ */
+ aw->pos_flags = parent == aw->pos.dentry ?
+ VFS_WALK_POS_DISCONNECTED | VFS_WALK_POS_MOUNTPOINT :
+ vfs_walk_pos_flags(aw->pos.mnt, parent);
+ dput(aw->pos.dentry);
+ aw->pos.dentry = parent;
+ return 0;
+}
+
+static int vfs_walk_step_rcu(struct vfs_ancestor_walk *aw)
+{
+ struct dentry *parent;
+ unsigned int next_seq;
+
+ if (unlikely(aw->pos_flags & VFS_WALK_POS_DISCONNECTED)) {
+ /* Resume at the root of the disconnected position's mount. */
+ aw->pos.dentry = aw->pos.mnt->mnt_root;
+ aw->seq = raw_seqcount_begin(&aw->pos.dentry->d_seq);
+ aw->pos_flags = 0;
+ return read_seqretry(&mount_lock, aw->m_seq) ? -ECHILD : 0;
+ }
+
+ parent = __path_walk_parent_rcu(&aw->pos, &vfs_walk_no_root, 0,
+ aw->m_seq, &aw->seq, &next_seq);
+ if (!parent)
+ /* The real root, unless the mount tree moved. */
+ return read_seqretry(&mount_lock, aw->m_seq) ? -ECHILD : 1;
+ if (IS_ERR(parent))
+ return PTR_ERR(parent);
+ /* A crossing onto a disconnected root, as in vfs_walk_step_ref(). */
+ aw->pos_flags = parent == aw->pos.dentry ?
+ VFS_WALK_POS_DISCONNECTED | VFS_WALK_POS_MOUNTPOINT :
+ vfs_walk_pos_flags(aw->pos.mnt, parent);
+ aw->pos.dentry = parent;
+ aw->seq = next_seq;
+ return 0;
+}
+
+/**
+ * vfs_walk_next - yield the walk's next position in @aw->pos
+ * @aw: the walk
+ *
+ * The first call yields the starting position itself. @aw->pos_flags
+ * carries the position's %VFS_WALK_POS_* flags, with the semantics
+ * described at vfs_walk_ancestors().
+ *
+ * Returns: 0 with @aw->pos valid, 1 once the walk has passed the real
+ * root, -ECHILD when an rcu-mode walk lost a race and must be retried
+ * (typically in the referenced mode).
+ */
+int vfs_walk_next(struct vfs_ancestor_walk *aw)
+{
+ if (aw->flags & VFS_WALK_STARTED) {
+ int err = (aw->flags & VFS_WALK_RCU) ?
+ vfs_walk_step_rcu(aw) : vfs_walk_step_ref(aw);
+
+ if (err)
+ return err;
+ } else {
+ aw->flags |= VFS_WALK_STARTED;
+ aw->pos_flags = vfs_walk_pos_flags(aw->pos.mnt, aw->pos.dentry);
+ }
+ /* The flags must describe the dentry the seq covers. */
+ if ((aw->flags & VFS_WALK_RCU) &&
+ read_seqcount_retry(&aw->pos.dentry->d_seq, aw->seq))
+ return -ECHILD;
+ return 0;
+}
+
+/**
+ * vfs_walk_end - finish a stepwise ancestor walk
+ * @aw: the walk; its positions, including @aw->pos, become invalid
+ */
+void vfs_walk_end(struct vfs_ancestor_walk *aw)
+{
+ if (!(aw->flags & VFS_WALK_RCU))
+ path_put(&aw->pos);
+}
+
+/**
+ * vfs_walk_handover - continue an rcu-mode walk with references
+ * @to: walk state to begin at @from's current position, owning the
+ * references acquired on it; valid until vfs_walk_end() either way
+ * @from: an rcu-mode walk, positioned by a 0 return from vfs_walk_next()
+ *
+ * Mirrors unlazy_walk(): @from's current position is legitimized and
+ * becomes the starting position of the referenced walk @to, which the
+ * first vfs_walk_next() on it yields. A failed legitimization leaves no
+ * partial references behind and a successful one moves straight into @to,
+ * so nothing is ever put here and the handover is safe within the caller's
+ * RCU read-side critical section - where a path_put() would not be, dput()
+ * being allowed to sleep. @to itself is only usable once the caller has
+ * left it, its walk being reference-based.
+ *
+ * @from is untouched on success and may keep stepping, lockless, from
+ * where it stands. The references do not conclude its walk: a concurrent
+ * rename may relocate the position the instant they are taken, as it may
+ * during any reference-based walk.
+ *
+ * Returns: false iff @from lost a race; it is then dead, as after -ECHILD
+ * from vfs_walk_next(), and @to is zeroed - safe to vfs_walk_end(), but
+ * not to step, so the caller has to remember it never started.
+ */
+bool vfs_walk_handover(struct vfs_ancestor_walk *to,
+ struct vfs_ancestor_walk *from)
+{
+ struct path pos = from->pos;
+ int err;
+
+ err = __legitimize_mnt(pos.mnt, from->m_seq);
+ if (unlikely(err)) {
+ if (err < 0)
+ mnt_undo_legitimize(real_mount(pos.mnt));
+ goto dead;
+ }
+ if (unlikely(read_seqcount_retry(&pos.dentry->d_seq, from->seq) ||
+ !lockref_get_not_dead(&pos.dentry->d_lockref))) {
+ mnt_undo_legitimize(real_mount(pos.mnt));
+ goto dead;
+ }
+ to->pos = pos;
+ to->flags = 0;
+ to->pos_flags = 0;
+ return true;
+
+dead:
+ memset(to, 0, sizeof(*to));
+ return false;
+}
+
+/**
+ * vfs_walk_ancestors - invoke a callback on a path and each of its ancestors
+ * @path: path to walk up from; the caller's path is never modified
+ * @cb: callback invoked on @path, then on each ancestor up to the real
+ * root, crossing mount boundaries. @cb must not sleep and returns
+ * %VFS_WALK_CONTINUE, %VFS_WALK_STOP or a negative errno to abort the
+ * walk; -ECHILD is reserved (see below). @ancestor is only valid
+ * during the invocation; @cb must take its own references to keep a
+ * position.
+ * A position whose dentry is a disconnected root is flagged with
+ * %VFS_WALK_POS_DISCONNECTED (plus %VFS_WALK_POS_MOUNTPOINT when it
+ * is a mountpoint a mount crossing landed on rather than a parent);
+ * if @cb continues over it, the walk resumes at the root of that
+ * position's mount.
+ * With %VFS_WALK_RCU, @cb accepts positions the walk holds no
+ * references on: the walk then runs lockless (under rcu_read_lock)
+ * and returns -ECHILD when it loses a race, or when @cb returns
+ * -ECHILD. The caller should then discard any state @cb accumulated
+ * and retry without %VFS_WALK_RCU.
+ * @data: opaque argument passed to @cb
+ * @flags: %VFS_WALK_RCU if @cb copes with unreferenced positions
+ *
+ * Returns: 0 once the real root was reached, 1 if @cb stopped the walk,
+ * -ECHILD if a lockless walk must be retried with references, or the
+ * negative errno @cb aborted with.
+ */
+int vfs_walk_ancestors(const struct path *path,
+ int (*cb)(const struct path *ancestor,
+ unsigned int pos_flags, void *data),
+ void *data, unsigned int flags)
+{
+ struct vfs_ancestor_walk aw;
+ int ret;
+
+ if (flags & VFS_WALK_RCU)
+ rcu_read_lock();
+ vfs_walk_start(&aw, path, flags);
+ for (;;) {
+ ret = vfs_walk_next(&aw);
+ if (ret) {
+ if (ret == 1)
+ /* The real root was reached. */
+ ret = 0;
+ break;
+ }
+ ret = cb(&aw.pos, aw.pos_flags, data);
+ if (ret < 0)
+ break;
+ if (ret == VFS_WALK_STOP) {
+ ret = 1;
+ break;
+ }
+ }
+ vfs_walk_end(&aw);
+ if (flags & VFS_WALK_RCU)
+ rcu_read_unlock();
+ return ret;
+}
+
+static struct dentry *follow_dotdot(struct nameidata *nd)
+{
+ struct dentry *parent;
+
+ if (path_equal(&nd->path, &nd->root)) {
+ /* nd->root is never path_connected()-checked. */
+ if (unlikely(nd->flags & LOOKUP_BENEATH))
+ return ERR_PTR(-EXDEV);
+ return dget(nd->path.dentry);
+ }
+ parent = __path_walk_parent(&nd->path, &nd->root, nd->flags);
+ nd->inode = nd->path.dentry->d_inode;
+ if (IS_ERR(parent))
+ return parent;
if (unlikely(!path_connected(nd->path.mnt, parent))) {
dput(parent);
return ERR_PTR(-ENOENT);
}
return parent;
-
-in_root:
- if (unlikely(nd->flags & LOOKUP_BENEATH))
- return ERR_PTR(-EXDEV);
- return dget(nd->path.dentry);
}
static const char *handle_dots(struct nameidata *nd, enum last_type type)
diff --git a/fs/namespace.c b/fs/namespace.c
index 580877e46b1ac..920b8a85f9fad 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -767,11 +767,8 @@ static bool legitimize_mnt(struct vfsmount *bastard, unsigned seq)
int res = __legitimize_mnt(bastard, seq);
if (likely(!res))
return true;
- if (unlikely(res < 0)) {
- rcu_read_unlock();
- mntput(bastard);
- rcu_read_lock();
- }
+ if (unlikely(res < 0))
+ mnt_undo_legitimize(real_mount(bastard));
return false;
}
@@ -1330,8 +1327,19 @@ static void delayed_mntput(struct work_struct *unused)
struct llist_node *node = llist_del_all(&delayed_mntput_list);
struct mount *m, *t;
- llist_for_each_entry_safe(m, t, node, mnt_llist)
- cleanup_mnt(m);
+ llist_for_each_entry_safe(m, t, node, mnt_llist) {
+ /*
+ * MNT_DOOMED is only ever set on the mounts
+ * mntput_no_expire_slowpath() queues here, never on the
+ * kept-count ones mnt_undo_legitimize() queues. Route on it
+ * rather than on mnt_get_count(), which a concurrent failed
+ * __legitimize_mnt() can transiently inflate.
+ */
+ if (m->mnt.mnt_flags & MNT_DOOMED)
+ cleanup_mnt(m);
+ else
+ mntput(&m->mnt); /* kept by mnt_undo_legitimize() */
+ }
}
static DECLARE_DELAYED_WORK(delayed_mntput_work, delayed_mntput);
@@ -1423,6 +1431,47 @@ void mntput(struct vfsmount *mnt)
}
EXPORT_SYMBOL(mntput);
+/**
+ * mnt_undo_legitimize - drop a count __legitimize_mnt() asked us to put
+ * @mnt: the mount __legitimize_mnt() failed on
+ *
+ * Like the mntput() that a failed __legitimize_mnt() normally obliges,
+ * but callable from the RCU read-side critical section the legitimization
+ * ran under: when the count turns out to be the mount's last, it is kept
+ * and the mount handed to delayed_mntput() for the sleepable final put.
+ * Unlike mntput(), mnt_expiry_mark is left alone: a walker that failed
+ * to legitimize the mount never used it.
+ */
+void mnt_undo_legitimize(struct mount *mnt)
+{
+ if (likely(READ_ONCE(mnt->mnt_ns))) {
+ /* Not the final count, as in mntput_no_expire(). */
+ mnt_add_count(mnt, -1);
+ return;
+ }
+ lock_mount_hash();
+ /*
+ * As in mntput_no_expire(): make sure that if a concurrent
+ * __legitimize_mnt() has not seen us grab mount_lock, we'll see
+ * its refcount increment here.
+ */
+ smp_mb();
+ if (likely(mnt_get_count(mnt) > 1)) {
+ mnt_add_count(mnt, -1);
+ unlock_mount_hash();
+ return;
+ }
+ unlock_mount_hash();
+ /*
+ * Ours is the last count: nothing else can reach the mount any
+ * more, which makes its mnt_llist ours to use. delayed_mntput()
+ * tells our kept-count mount from the doomed ones by MNT_DOOMED.
+ */
+ WARN_ON_ONCE(mnt->mnt.mnt_flags & MNT_DOOMED);
+ if (llist_add(&mnt->mnt_llist, &delayed_mntput_list))
+ schedule_delayed_work(&delayed_mntput_work, 1);
+}
+
struct vfsmount *mntget(struct vfsmount *mnt)
{
if (mnt)
diff --git a/include/linux/namei.h b/include/linux/namei.h
index 86d657b24fc6a..8820a2833213d 100644
--- a/include/linux/namei.h
+++ b/include/linux/namei.h
@@ -162,6 +162,23 @@ extern int follow_down_one(struct path *);
extern int follow_down(struct path *path, unsigned int flags);
extern int follow_up(struct path *);
+/* vfs_walk_ancestors() flags */
+#define VFS_WALK_RCU BIT(0)
+
+/* per-position flags passed to the vfs_walk_ancestors() callback */
+#define VFS_WALK_POS_DISCONNECTED BIT(0)
+/* the position is a mountpoint landed on by a mount crossing */
+#define VFS_WALK_POS_MOUNTPOINT BIT(1)
+
+/* vfs_walk_ancestors() callback verdicts; negative values abort the walk */
+#define VFS_WALK_STOP 0
+#define VFS_WALK_CONTINUE 1
+
+int vfs_walk_ancestors(const struct path *path,
+ int (*cb)(const struct path *ancestor,
+ unsigned int pos_flags, void *data),
+ void *data, unsigned int flags);
+
int start_renaming(struct renamedata *rd, int lookup_flags,
struct qstr *old_last, struct qstr *new_last);
int start_renaming_dentry(struct renamedata *rd, int lookup_flags,
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index fd3c0206bd67d..3294b2a431674 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -6300,6 +6300,10 @@ BTF_TYPE_SAFE_TRUSTED(struct scx_sub_detach_args) {
struct sched_ext_ops *ops;
};
+BTF_TYPE_SAFE_TRUSTED(struct path) {
+ struct dentry *dentry;
+};
+
BTF_TYPE_SAFE_TRUSTED_OR_NULL(struct dentry) {
struct inode *d_inode;
};
@@ -6352,6 +6356,7 @@ static bool type_is_trusted(struct bpf_verifier_env *env,
BTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED(struct scx_cpu_release_args));
BTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED(struct scx_sub_attach_args));
BTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED(struct scx_sub_detach_args));
+ BTF_TYPE_EMIT(BTF_TYPE_SAFE_TRUSTED(struct path));
return btf_nested_type_is_trusted(&env->log, reg, field_name, btf_id, "__safe_trusted");
}
@@ -8307,6 +8312,24 @@ static bool is_iter_destroy_kfunc(struct bpf_call_arg_meta *meta)
return meta->kfunc_flags & KF_ITER_DESTROY;
}
+/*
+ * An "__uninit"-suffixed iterator argument of a kfunc that is not itself an
+ * iterator method: the kfunc initializes that iterator state, as a
+ * bpf_iter_<type>_new() does, but from inputs the constructor naming
+ * convention cannot express - e.g. another, already-initialized iterator.
+ * Only iterator-typed arguments qualify, so "__uninit" dynptr out-arguments
+ * are not reclassified.
+ */
+static bool is_kfunc_arg_iter_init(struct bpf_call_arg_meta *meta, int arg_idx,
+ const struct btf_param *arg)
+{
+ if (is_iter_kfunc(meta))
+ return false;
+
+ return btf_param_match_suffix(meta->btf, arg, "__uninit") &&
+ btf_check_iter_arg(meta->btf, meta->func_proto, arg_idx) >= 0;
+}
+
static bool is_kfunc_arg_iter(struct bpf_call_arg_meta *meta, int arg_idx,
const struct btf_param *arg)
{
@@ -8317,7 +8340,11 @@ static bool is_kfunc_arg_iter(struct bpf_call_arg_meta *meta, int arg_idx,
return arg_idx == 0;
/* iter passed as an argument to a generic kfunc */
- return btf_param_match_suffix(meta->btf, arg, "__iter");
+ if (btf_param_match_suffix(meta->btf, arg, "__iter"))
+ return true;
+
+ /* iter state a generic kfunc initializes */
+ return is_kfunc_arg_iter_init(meta, arg_idx, arg);
}
static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
@@ -8327,6 +8354,7 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *
struct bpf_func_state *state = bpf_func(env, reg);
const struct btf_type *t;
int spi, err, i, nr_slots, btf_id;
+ bool init;
if (reg->type != PTR_TO_STACK) {
verbose(env, "%s expected pointer to an iterator on stack\n",
@@ -8358,8 +8386,16 @@ static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *
t = btf_type_by_id(meta->btf, btf_id);
nr_slots = t->size / BPF_REG_SIZE;
- if (is_iter_new_kfunc(meta)) {
- /* bpf_iter_<type>_new() expects pointer to uninit iter state */
+ /*
+ * Whether this argument is the iterator the call initializes, rather
+ * than an initialized one it operates on.
+ */
+ init = is_iter_new_kfunc(meta) ||
+ is_kfunc_arg_iter_init(meta, arg,
+ &btf_params(meta->func_proto)[arg]);
+
+ if (init) {
+ /* expects a pointer to uninit iter state */
if (!is_iter_reg_valid_uninit(env, reg, nr_slots)) {
verbose(env, "expected uninitialized iter_%s as %s\n",
iter_type_str(meta->btf, btf_id), reg_arg_name(env, argno));
@@ -8643,6 +8679,13 @@ static int process_iter_next_call(struct bpf_verifier_env *env, int insn_idx,
/* switch to DRAINED state, but keep the depth unchanged */
/* mark current iter state as drained and assume returned NULL */
cur_iter->iter.state = BPF_ITER_STATE_DRAINED;
+ /*
+ * An acquiring iter_next() hands out nothing once drained: the
+ * acquired reference exists only in the forked active state, not on
+ * this NULL-returning branch.
+ */
+ if (meta->kfunc_flags & KF_ACQUIRE)
+ WARN_ON_ONCE(release_reference_nomark(env, cur_fr->regs[BPF_REG_0].id));
__mark_reg_const_zero(env, &cur_fr->regs[BPF_REG_0]);
return 0;
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index cab43892ec2f0..369e2e9c83ad7 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -751,6 +751,107 @@ static void test_is_eacces_with_write(struct kunit *const test)
#undef IE_TRUE
#undef IE_FALSE
+struct landlock_walk_state {
+ const struct landlock_domain *domain;
+ struct layer_masks *layer_masks_parent1, *layer_masks_parent2;
+ const struct layer_masks *layer_masks_child1, *layer_masks_child2;
+ access_mask_t access_request_parent1, access_request_parent2;
+ access_mask_t access_masked_parent1, access_masked_parent2;
+ bool child1_is_directory, child2_is_directory;
+ bool allowed_parent1, allowed_parent2, is_dom_check;
+};
+
+static int check_access_path_walk(const struct path *const ancestor,
+ const unsigned int pos_flags,
+ void *const data)
+{
+ struct landlock_walk_state *const state = data;
+ struct landlock_id id = {
+ .type = LANDLOCK_KEY_INODE,
+ };
+
+ if (unlikely(pos_flags & VFS_WALK_POS_DISCONNECTED)) {
+ if (likely(ancestor->mnt->mnt_flags & MNT_INTERNAL)) {
+ /*
+ * Stops and allows access when reaching disconnected
+ * root directories that are part of internal
+ * filesystems (e.g. nsfs, which is reachable through
+ * /proc/<pid>/ns/<namespace>).
+ */
+ state->allowed_parent1 = true;
+ state->allowed_parent2 = true;
+ return VFS_WALK_STOP;
+ }
+ /*
+ * The old loop never visited the mountpoints a mount
+ * crossing lands on: don't match them against rules. Other
+ * disconnected roots keep being matched below, as before.
+ */
+ if (pos_flags & VFS_WALK_POS_MOUNTPOINT)
+ return VFS_WALK_CONTINUE;
+ }
+
+ /*
+ * If at least all accesses allowed on the destination are already
+ * allowed on the source, respectively if there is at least as much as
+ * restrictions on the destination than on the source, then we can
+ * safely refer files from the source to the destination without
+ * risking a privilege escalation. This also applies in the case of
+ * RENAME_EXCHANGE, which implies checks on both direction. This is
+ * crucial for standalone multilayered security policies. Furthermore,
+ * this helps avoid policy writers to shoot themselves in the foot.
+ */
+ if (unlikely(state->is_dom_check &&
+ no_more_access(state->layer_masks_parent1,
+ state->layer_masks_child1,
+ state->child1_is_directory,
+ state->layer_masks_parent2,
+ state->layer_masks_child2,
+ state->child2_is_directory))) {
+ /*
+ * Now, downgrades the remaining checks from domain handled
+ * accesses to requested accesses.
+ */
+ state->is_dom_check = false;
+ state->access_masked_parent1 = state->access_request_parent1;
+ state->access_masked_parent2 = state->access_request_parent2;
+
+ state->allowed_parent1 =
+ state->allowed_parent1 ||
+ scope_to_request(state->access_masked_parent1,
+ state->layer_masks_parent1);
+ state->allowed_parent2 =
+ state->allowed_parent2 ||
+ scope_to_request(state->access_masked_parent2,
+ state->layer_masks_parent2);
+
+ /* Stops when all accesses are granted. */
+ if (state->allowed_parent1 && state->allowed_parent2)
+ return VFS_WALK_STOP;
+ }
+
+ if (get_inode_id(ancestor->dentry, &id)) {
+ state->allowed_parent1 =
+ state->allowed_parent1 ||
+ unmask_layers_fs(state->domain, id,
+ state->access_masked_parent1,
+ state->layer_masks_parent1,
+ ancestor->dentry);
+ state->allowed_parent2 =
+ state->allowed_parent2 ||
+ unmask_layers_fs(state->domain, id,
+ state->access_masked_parent2,
+ state->layer_masks_parent2,
+ ancestor->dentry);
+ }
+
+ /* Stops when a rule from each layer grants access. */
+ if (state->allowed_parent1 && state->allowed_parent2)
+ return VFS_WALK_STOP;
+
+ return VFS_WALK_CONTINUE;
+}
+
/**
* is_access_to_paths_allowed - Check accesses for requests with a common path
*
@@ -803,16 +904,16 @@ is_access_to_paths_allowed(const struct landlock_domain *const domain,
struct landlock_request *const log_request_parent2,
struct dentry *const dentry_child2)
{
- bool allowed_parent1 = false, allowed_parent2 = false, is_dom_check,
- child1_is_directory = true, child2_is_directory = true;
- struct path walker_path;
- struct landlock_id id = {
- .type = LANDLOCK_KEY_INODE,
- };
- access_mask_t access_masked_parent1, access_masked_parent2;
struct layer_masks _layer_masks_child1, _layer_masks_child2;
- struct layer_masks *layer_masks_child1 = NULL,
- *layer_masks_child2 = NULL;
+ struct landlock_walk_state state = {
+ .domain = domain,
+ .layer_masks_parent1 = layer_masks_parent1,
+ .layer_masks_parent2 = layer_masks_parent2,
+ .access_request_parent1 = access_request_parent1,
+ .access_request_parent2 = access_request_parent2,
+ .child1_is_directory = true,
+ .child2_is_directory = true,
+ };
if (!access_request_parent1 && !access_request_parent2)
return true;
@@ -826,37 +927,39 @@ is_access_to_paths_allowed(const struct landlock_domain *const domain,
if (WARN_ON_ONCE(!layer_masks_parent1))
return false;
- allowed_parent1 = is_layer_masks_allowed(layer_masks_parent1);
+ state.allowed_parent1 = is_layer_masks_allowed(layer_masks_parent1);
if (unlikely(layer_masks_parent2)) {
if (WARN_ON_ONCE(!dentry_child1))
return false;
- allowed_parent2 = is_layer_masks_allowed(layer_masks_parent2);
+ state.allowed_parent2 =
+ is_layer_masks_allowed(layer_masks_parent2);
/*
* For a double request, first check for potential privilege
* escalation by looking at domain handled accesses (which are
* a superset of the meaningful requested accesses).
*/
- access_masked_parent1 = access_masked_parent2 =
+ state.access_masked_parent1 =
landlock_union_access_masks(domain).fs;
- is_dom_check = true;
+ state.access_masked_parent2 = state.access_masked_parent1;
+ state.is_dom_check = true;
} else {
if (WARN_ON_ONCE(dentry_child1 || dentry_child2))
return false;
/* For a simple request, only check for requested accesses. */
- access_masked_parent1 = access_request_parent1;
- access_masked_parent2 = access_request_parent2;
+ state.access_masked_parent1 = access_request_parent1;
+ state.access_masked_parent2 = access_request_parent2;
/*
* Simple requests have no parent2 to check, so parent2 is
* trivially allowed. This must be set explicitly because the
- * get_inode_id() gate in the pathwalk loop may prevent
+ * get_inode_id() gate in the walk callback may prevent
* landlock_unmask_layers() from being called (which would
* otherwise return true for NULL masks as a side effect).
*/
- allowed_parent2 = true;
- is_dom_check = false;
+ state.allowed_parent2 = true;
+ state.is_dom_check = false;
}
if (unlikely(dentry_child1)) {
@@ -872,8 +975,8 @@ is_access_to_paths_allowed(const struct landlock_domain *const domain,
if (handled && get_inode_id(dentry_child1, &id))
unmask_layers_fs(domain, id, handled,
&_layer_masks_child1, dentry_child1);
- layer_masks_child1 = &_layer_masks_child1;
- child1_is_directory = d_is_dir(dentry_child1);
+ state.layer_masks_child1 = &_layer_masks_child1;
+ state.child1_is_directory = d_is_dir(dentry_child1);
}
if (unlikely(dentry_child2)) {
struct landlock_id id = {
@@ -888,118 +991,16 @@ is_access_to_paths_allowed(const struct landlock_domain *const domain,
if (handled && get_inode_id(dentry_child2, &id))
unmask_layers_fs(domain, id, handled,
&_layer_masks_child2, dentry_child2);
- layer_masks_child2 = &_layer_masks_child2;
- child2_is_directory = d_is_dir(dentry_child2);
+ state.layer_masks_child2 = &_layer_masks_child2;
+ state.child2_is_directory = d_is_dir(dentry_child2);
}
- walker_path = *path;
- path_get(&walker_path);
/*
* We need to walk through all the hierarchy to not miss any relevant
- * restriction.
+ * restriction. Reaching the real root without a grant from each
+ * layer denies access.
*/
- while (true) {
- /*
- * If at least all accesses allowed on the destination are
- * already allowed on the source, respectively if there is at
- * least as much as restrictions on the destination than on the
- * source, then we can safely refer files from the source to
- * the destination without risking a privilege escalation.
- * This also applies in the case of RENAME_EXCHANGE, which
- * implies checks on both direction. This is crucial for
- * standalone multilayered security policies. Furthermore,
- * this helps avoid policy writers to shoot themselves in the
- * foot.
- */
- if (unlikely(is_dom_check &&
- no_more_access(
- layer_masks_parent1, layer_masks_child1,
- child1_is_directory, layer_masks_parent2,
- layer_masks_child2,
- child2_is_directory))) {
- /*
- * Now, downgrades the remaining checks from domain
- * handled accesses to requested accesses.
- */
- is_dom_check = false;
- access_masked_parent1 = access_request_parent1;
- access_masked_parent2 = access_request_parent2;
-
- allowed_parent1 =
- allowed_parent1 ||
- scope_to_request(access_masked_parent1,
- layer_masks_parent1);
- allowed_parent2 =
- allowed_parent2 ||
- scope_to_request(access_masked_parent2,
- layer_masks_parent2);
-
- /* Stops when all accesses are granted. */
- if (allowed_parent1 && allowed_parent2)
- break;
- }
-
- if (get_inode_id(walker_path.dentry, &id)) {
- allowed_parent1 =
- allowed_parent1 ||
- unmask_layers_fs(domain, id,
- access_masked_parent1,
- layer_masks_parent1,
- walker_path.dentry);
- allowed_parent2 =
- allowed_parent2 ||
- unmask_layers_fs(domain, id,
- access_masked_parent2,
- layer_masks_parent2,
- walker_path.dentry);
- }
-
- /* Stops when a rule from each layer grants access. */
- if (allowed_parent1 && allowed_parent2)
- break;
-
-jump_up:
- if (walker_path.dentry == walker_path.mnt->mnt_root) {
- if (follow_up(&walker_path)) {
- /* Ignores hidden mount points. */
- goto jump_up;
- } else {
- /*
- * Stops at the real root. Denies access
- * because not all layers have granted access.
- */
- break;
- }
- }
-
- if (unlikely(IS_ROOT(walker_path.dentry))) {
- if (likely(walker_path.mnt->mnt_flags & MNT_INTERNAL)) {
- /*
- * Stops and allows access when reaching disconnected root
- * directories that are part of internal filesystems (e.g. nsfs,
- * which is reachable through /proc/<pid>/ns/<namespace>).
- */
- allowed_parent1 = true;
- allowed_parent2 = true;
- break;
- }
-
- /*
- * We reached a disconnected root directory from a bind mount.
- * Let's continue the walk with the mount point we missed.
- */
- dput(walker_path.dentry);
- walker_path.dentry = walker_path.mnt->mnt_root;
- dget(walker_path.dentry);
- } else {
- struct dentry *const parent_dentry =
- dget_parent(walker_path.dentry);
-
- dput(walker_path.dentry);
- walker_path.dentry = parent_dentry;
- }
- }
- path_put(&walker_path);
+ vfs_walk_ancestors(path, check_access_path_walk, &state, 0);
/*
* Check CONFIG_SECURITY_LANDLOCK_LOG to enable elision of
@@ -1007,24 +1008,24 @@ is_access_to_paths_allowed(const struct landlock_domain *const domain,
* dead code elimination.
*/
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
- if (!allowed_parent1 && log_request_parent1) {
+ if (!state.allowed_parent1 && log_request_parent1) {
log_request_parent1->type = LANDLOCK_REQUEST_FS_ACCESS;
log_request_parent1->audit.type = LSM_AUDIT_DATA_PATH;
log_request_parent1->audit.u.path = *path;
- log_request_parent1->access = access_masked_parent1;
+ log_request_parent1->access = state.access_masked_parent1;
log_request_parent1->layer_masks = layer_masks_parent1;
}
- if (!allowed_parent2 && log_request_parent2) {
+ if (!state.allowed_parent2 && log_request_parent2) {
log_request_parent2->type = LANDLOCK_REQUEST_FS_ACCESS;
log_request_parent2->audit.type = LSM_AUDIT_DATA_PATH;
log_request_parent2->audit.u.path = *path;
- log_request_parent2->access = access_masked_parent2;
+ log_request_parent2->access = state.access_masked_parent2;
log_request_parent2->layer_masks = layer_masks_parent2;
}
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
- return allowed_parent1 && allowed_parent2;
+ return state.allowed_parent1 && state.allowed_parent2;
}
static int current_check_access_path(const struct path *const path,
diff --git a/tools/testing/selftests/bpf/prog_tests/path_ancestors.c b/tools/testing/selftests/bpf/prog_tests/path_ancestors.c
new file mode 100644
index 0000000000000..ce1ded844c3a5
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/path_ancestors.c
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Justin Suess */
+
+#include <sys/stat.h>
+#include <sys/xattr.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <test_progs.h>
+#include "path_ancestors.skel.h"
+
+void test_path_ancestors(void)
+{
+ char base[] = "/tmp/path_ancestors_XXXXXX";
+ struct path_ancestors *skel = NULL;
+ char suba[280], subb[280];
+ bool xattr_works;
+ int err;
+
+ if (!ASSERT_OK_PTR(mkdtemp(base), "mkdtemp"))
+ return;
+ snprintf(suba, sizeof(suba), "%s/a", base);
+ snprintf(subb, sizeof(subb), "%s/a/b", base);
+ if (!ASSERT_OK(mkdir(suba, 0755), "mkdir_a"))
+ goto out_rm;
+
+ /* Read back by the program at the escalated position (== base). */
+ err = setxattr(base, "user.walk", "hello", 6, 0);
+ xattr_works = !err;
+ if (err && errno != EOPNOTSUPP && !ASSERT_OK(err, "setxattr"))
+ goto out_rm;
+
+ skel = path_ancestors__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "open_and_load"))
+ goto out_rm;
+ skel->bss->monitored_pid = getpid();
+ if (!ASSERT_OK(path_ancestors__attach(skel), "attach"))
+ goto out;
+
+ /* mkdir b: the hook sees dir == suba, whose ancestry is walked. */
+ if (!ASSERT_OK(mkdir(subb, 0755), "mkdir_b"))
+ goto out;
+
+ ASSERT_EQ(skel->bss->test_err, 0, "test_err");
+ ASSERT_EQ(skel->bss->escalate_err, 0, "escalate_err");
+ /* suba, base, /tmp, / at least; equality across modes is the point. */
+ ASSERT_GE(skel->bss->rcu_count, 3, "rcu_count");
+ ASSERT_EQ(skel->bss->ref_count, skel->bss->rcu_count, "ref_vs_rcu");
+ ASSERT_EQ(skel->bss->rcu_ns_count, skel->bss->rcu_count,
+ "nonsleepable_vs_rcu");
+ /*
+ * The escalated position is walked twice: once lockless, then again
+ * as the resumed referenced iteration's first position.
+ */
+ ASSERT_EQ(skel->bss->hybrid_count, skel->bss->rcu_count + 1,
+ "hybrid_vs_rcu");
+ ASSERT_EQ(skel->bss->retry_flags, 0, "no_retry");
+ ASSERT_EQ(skel->bss->ref_flags, 0, "ref_flags");
+
+ /* The acquired second position, used after its step was taken. */
+ ASSERT_STREQ(skel->bss->second_path, base, "second_path");
+ ASSERT_EQ(skel->bss->second_len, strlen(base) + 1, "second_len");
+
+ /* The escalated position is the walk's second one: base. */
+ ASSERT_STREQ(skel->bss->escalated_path, base, "escalated_path");
+ ASSERT_EQ(skel->bss->escalated_len, strlen(base) + 1, "escalated_len");
+ if (xattr_works) {
+ ASSERT_EQ(skel->bss->xattr_ret, 6, "xattr_len");
+ ASSERT_STREQ(skel->bss->xattr_value, "hello", "xattr_value");
+ }
+
+out:
+ path_ancestors__destroy(skel);
+out_rm:
+ rmdir(subb);
+ rmdir(suba);
+ rmdir(base);
+}
diff --git a/tools/testing/selftests/bpf/progs/path_ancestors.c b/tools/testing/selftests/bpf/progs/path_ancestors.c
new file mode 100644
index 0000000000000..50ce0ce163ddd
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/path_ancestors.c
@@ -0,0 +1,134 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Justin Suess */
+
+#include "vmlinux.h"
+#include <errno.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_kfuncs.h"
+
+char _license[] SEC("license") = "GPL";
+
+__u32 monitored_pid;
+
+int rcu_count; /* positions seen by the pure lockless walk */
+int rcu_ns_count; /* ditto, from the non-sleepable program */
+int ref_count; /* positions seen by the pure referenced walk */
+int hybrid_count; /* positions seen by the lockless+escalate walk */
+int retry_flags; /* BPF_PATH_ANCESTORS_RETRY observations */
+int ref_flags; /* pos flags seen by the referenced walk */
+int second_len; /* d_path length of the walk's second position */
+int xattr_ret; /* xattr read at the escalated position */
+int escalated_len; /* d_path length of the escalated position */
+int escalate_err; /* bpf_path_ancestors_legitimize() result */
+int test_err;
+char second_path[256];
+char escalated_path[256];
+char xattr_value[16];
+
+static bool monitored(void)
+{
+ return (bpf_get_current_pid_tgid() >> 32) == monitored_pid;
+}
+
+/*
+ * Lockless walk from a non-sleepable program: the RCU critical section is
+ * implicit, no bpf_rcu_read_lock() needed.
+ */
+SEC("lsm/path_mkdir")
+int BPF_PROG(rcu_nonsleepable, const struct path *dir, struct dentry *dentry,
+ umode_t mode)
+{
+ struct bpf_iter_path_ancestors_rcu rit;
+
+ if (!monitored())
+ return 0;
+
+ bpf_iter_path_ancestors_rcu_new(&rit, (struct path *)dir, 0);
+ while (bpf_iter_path_ancestors_rcu_next(&rit))
+ rcu_ns_count++;
+ retry_flags |= bpf_path_ancestors_rcu_pos_flags(&rit);
+ bpf_iter_path_ancestors_rcu_destroy(&rit);
+ return 0;
+}
+
+SEC("lsm.s/path_mkdir")
+int BPF_PROG(walk_modes, const struct path *dir, struct dentry *dentry,
+ umode_t mode)
+{
+ struct bpf_iter_path_ancestors_rcu rit;
+ struct bpf_iter_path_ancestors it;
+ struct bpf_dynptr value_ptr;
+ struct path *pos;
+
+ if (!monitored())
+ return 0;
+
+ /*
+ * Mode 1: pure lockless, under an explicit RCU critical section.
+ * Positions are borrowed, so nothing is released here.
+ */
+ bpf_rcu_read_lock();
+ bpf_iter_path_ancestors_rcu_new(&rit, (struct path *)dir, 0);
+ while (bpf_iter_path_ancestors_rcu_next(&rit))
+ rcu_count++;
+ retry_flags |= bpf_path_ancestors_rcu_pos_flags(&rit);
+ bpf_iter_path_ancestors_rcu_destroy(&rit);
+ bpf_rcu_read_unlock();
+
+ /*
+ * Mode 2: pure referenced. Every position comes acquired, so it
+ * stays valid for sleepable work and past the step that yielded it.
+ */
+ bpf_iter_path_ancestors_new(&it, (struct path *)dir, 0);
+ while ((pos = bpf_iter_path_ancestors_next(&it))) {
+ ref_count++;
+ ref_flags |= bpf_path_ancestors_pos_flags(&it);
+ if (ref_count == 2)
+ second_len = bpf_path_d_path(pos, second_path,
+ sizeof(second_path));
+ bpf_path_put(pos);
+ }
+ bpf_iter_path_ancestors_destroy(&it);
+
+ /*
+ * Mode 3: hybrid. Walk lockless to the second position, then hand
+ * that position over to a referenced iteration which resumes there.
+ */
+ bpf_rcu_read_lock();
+ bpf_iter_path_ancestors_rcu_new(&rit, (struct path *)dir, 0);
+ while (bpf_iter_path_ancestors_rcu_next(&rit)) {
+ hybrid_count++;
+ if (hybrid_count == 2)
+ break;
+ }
+ escalate_err = bpf_path_ancestors_legitimize(&it, &rit);
+ retry_flags |= bpf_path_ancestors_rcu_pos_flags(&rit);
+ bpf_iter_path_ancestors_rcu_destroy(&rit);
+ bpf_rcu_read_unlock();
+
+ if (escalate_err)
+ test_err = 1;
+
+ /*
+ * Out of the RCU critical section. The resumed iteration's first
+ * position is the escalated one, kept alive by the reference the
+ * iteration hands out, so sleepable work can run on it.
+ */
+ while ((pos = bpf_iter_path_ancestors_next(&it))) {
+ hybrid_count++;
+ if (hybrid_count == 3) {
+ escalated_len = bpf_path_d_path(pos, escalated_path,
+ sizeof(escalated_path));
+ bpf_dynptr_from_mem(xattr_value, sizeof(xattr_value),
+ 0, &value_ptr);
+ /* A trusted path's dentry is trusted, never NULL. */
+ xattr_ret = bpf_get_dentry_xattr(pos->dentry,
+ "user.walk",
+ &value_ptr);
+ }
+ bpf_path_put(pos);
+ }
+ bpf_iter_path_ancestors_destroy(&it);
+ return 0;
+}
|
| 19/3 | 2026/10/06 00:34 | llm | kmsan-evaluator |
1mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 22383 output: 30 thoughts: 8054 Thoughts: **KMSAN Evaluation for BPF Path Ancestor Iterators** Okay, let's break this down. My goal is to determine if this patch series necessitates a KMSAN fuzzing session. The key is to see if any of these changes introduce or expose risks related to uninitialized memory detectable *only* by KMSAN. If KASAN, LOCKDEP, or standard bug detectors would catch something, it's a no-go for KMSAN. These new BPF path ancestor iterators are the focus. First, I see several functions added related to these iterators: `bpf_iter_path_ancestors_new`, `_next`, `_destroy`, `_legitimize`, etc. The code introduces structures `bpf_iter_path_ancestors`, `bpf_iter_path_ancestors_rcu`, and `bpf_path_ancestors_kern`. I spot potential issues right away. `bpf_iter_path_ancestors` and `bpf_iter_path_ancestors_rcu` contain `__u64 __opaque[5];`. The `bpf_path_ancestors_kern` contains a `vfs_ancestor_walk` struct, which is concerning. Let's dive into `bpf_path_ancestors_new`. I see a `memset` being used, but only if some flags are set. The critical path is when flags are zero. This calls `vfs_walk_start`, where the `vfs_ancestor_walk` struct is initialized. Crucially, in the non-RCU case (flags are zero), `aw->seq` and `aw->m_seq` are not initialized *explicitly* in `vfs_walk_start`. This is a big red flag. However, there is a build-time check to ensure all these structs are the same size so we can treat them interchangeably. I start tracking down the usages of `aw->seq` and `aw->m_seq` to see if there's a read before write. I check `vfs_walk_step_ref` and `vfs_walk_next`. It looks like `vfs_walk_step_ref` *doesn't* read those uninitialized fields. `vfs_walk_next` *does* check these values, but only within the RCU path (when the flags are set). Then I look at `bpf_path_ancestors_legitimize`, which is where an uninitialized iterator (`it__uninit`) from BPF is "legitimized" with a RCU iterator. This function contains `memset(kit, 0, sizeof(*kit));`. Phew, that should zero out everything, resolving any uninitialized memory concerns *within the legitimization*. I check the BPF verifier and see that there are checks in place to handle the `__uninit` suffix for iterator arguments. So, the verifier will handle BPF-level initialization/uninitialization. But, I must keep checking! Specifically, does `bpf_path_ancestors_new` memset it's struct? Only when `flags` is nonzero. When `flags == 0` (non-RCU mode): It *does not* `memset`, and so `aw->seq` and `aw->m_seq` *are not* initialized. Going back to `vfs_walk_start`, I see `aw->seq` (offset 16 bytes) and `aw->m_seq` (offset 20 bytes). These remain uninitialized in the non-RCU mode. The patch includes `kmalloc_obj(*held)` and `*held = *pos`. I have to check the implications there. `struct path` doesn't have padding, and so these look fine. Okay, let's consider a Landlock usage of `vfs_walk_ancestors`. In `security/landlock/fs.c`, Landlock calls `vfs_walk_ancestors`. What if the `vfs_ancestor_walk` struct is initialized on the stack, *and* the `VFS_WALK_RCU` flag *isn't* set? It looks like `vfs_walk_start` *will* leave `aw->seq` and `aw->m_seq` uninitialized! In the Landlock case, `flags = 0`. This is the same vulnerability I spotted earlier! I need to trace the stack usages of aw to check whether these uninitialized fields are used. Then I checked `vfs_walk_step_ref`, `vfs_walk_next`, `vfs_walk_end`, and `vfs_walk_handover`. The only |
| 20/3 | 2026/10/06 00:35 | tool | grepper |
0mArgs:{
"Expression": "choose_mountpoint_rcu",
"PathPrefixes": [
"fs/"
]
}Results: {
"Output": "fs/namei.c=1486=EXPORT_SYMBOL(follow_up);\nfs/namei.c-1487-\nfs/namei.c:1488:static bool choose_mountpoint_rcu(struct mount *m, const struct path *root,\nfs/namei.c-1489-\t\t\t\t struct path *path, unsigned *seqp)\n--\nfs/namei.c=1508=static bool choose_mountpoint(struct mount *m, const struct path *root,\n--\nfs/namei.c-1516-\nfs/namei.c:1517:\t\tfound = choose_mountpoint_rcu(m, root, path, \u0026seq);\nfs/namei.c-1518-\t\tif (unlikely(!found)) {\n--\nfs/namei.c=2130=static __always_inline const char *step_into(struct nameidata *nd, int flags,\n--\nfs/namei.c-2160- * (path_connected(), nameidata updates, ...) on top\nfs/namei.c:2161: * @root: boundary as for choose_mountpoint_rcu(); if zero'ed, walk all the\nfs/namei.c-2162- * way to the global root\n--\nfs/namei.c=2173=static struct dentry *__path_walk_parent_rcu(struct path *path,\n--\nfs/namei.c-2183-\nfs/namei.c:2184:\t\tif (!choose_mountpoint_rcu(real_mount(path-\u003emnt),\nfs/namei.c-2185-\t\t\t\t\t root, \u0026mounted, \u0026seq))\n"
} |
| 21/3 | 2026/10/06 00:35 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 31926 output: 35 thoughts: 29 Thoughts: Okay, let's dive into this. Here's my take on those thoughts, as if they're my own: **Analyzing `choose_mountpoint_rcu` in `fs/namei.c`** Right, so I'm thinking, let's get into the nitty-gritty of how the kernel handles mountpoints, particularly from the perspective of RCU. It's time to zero in on `choose_mountpoint_rcu`, specifically around line 1488 within `fs/namei.c`. I know this function is crucial for resolving mountpoint lookups in an RCU-safe manner. I need to refresh my understanding of how it fits into the broader picture of namespace traversal and how it deals with potential races that come with concurrent access. I want to pay special attention to the RCU usage; am I seeing the expected read-side critical sections? What precautions are in place to ensure data consistency as the name lookup progresses? I'm anticipating that this is where the core logic will be housed to safely handle potentially changing mountpoint structures under RCU. Let's see how it's implemented. |
| 22/3 | 2026/10/06 00:35 | tool | read-file |
0mArgs:{
"File": "fs/namei.c",
"FirstLine": 1485,
"LineCount": 40
}Results: {
"Contents": "1485:\t}\n1486:\tEXPORT_SYMBOL(follow_up);\n1487:\t\n1488:\tstatic bool choose_mountpoint_rcu(struct mount *m, const struct path *root,\n1489:\t\t\t\t\t struct path *path, unsigned *seqp)\n1490:\t{\n1491:\t\twhile (mnt_has_parent(m)) {\n1492:\t\t\tstruct dentry *mountpoint = m-\u003emnt_mountpoint;\n1493:\t\n1494:\t\t\tm = m-\u003emnt_parent;\n1495:\t\t\tif (unlikely(root-\u003edentry == mountpoint \u0026\u0026\n1496:\t\t\t\t root-\u003emnt == \u0026m-\u003emnt))\n1497:\t\t\t\tbreak;\n1498:\t\t\tif (mountpoint != m-\u003emnt.mnt_root) {\n1499:\t\t\t\tpath-\u003emnt = \u0026m-\u003emnt;\n1500:\t\t\t\tpath-\u003edentry = mountpoint;\n1501:\t\t\t\t*seqp = read_seqcount_begin(\u0026mountpoint-\u003ed_seq);\n1502:\t\t\t\treturn true;\n1503:\t\t\t}\n1504:\t\t}\n1505:\t\treturn false;\n1506:\t}\n1507:\t\n1508:\tstatic bool choose_mountpoint(struct mount *m, const struct path *root,\n1509:\t\t\t\t struct path *path)\n1510:\t{\n1511:\t\tbool found;\n1512:\t\n1513:\t\trcu_read_lock();\n1514:\t\twhile (1) {\n1515:\t\t\tunsigned seq, mseq = read_seqbegin(\u0026mount_lock);\n1516:\t\n1517:\t\t\tfound = choose_mountpoint_rcu(m, root, path, \u0026seq);\n1518:\t\t\tif (unlikely(!found)) {\n1519:\t\t\t\tif (!read_seqretry(\u0026mount_lock, mseq))\n1520:\t\t\t\t\tbreak;\n1521:\t\t\t} else {\n1522:\t\t\t\tif (likely(__legitimize_path(path, seq, mseq)))\n1523:\t\t\t\t\tbreak;\n1524:\t\t\t\trcu_read_unlock();\n"
} |
| 23/3 | 2026/10/06 00:35 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 32811 output: 39 thoughts: 1216 Thoughts: **Analyzing Initialization Patterns in Kernel Structures** Okay, so I'm diving into this kernel code, specifically focusing on initialization patterns. My initial thought is to trace how the `struct path` is initialized, particularly its members `mnt` and `dentry`. Looking at `choose_mountpoint_rcu`, it seems like it's the point where `path->mnt`, `path->dentry`, and the `seqp` are set up. Then, in `__path_walk_parent_rcu`, there's a straightforward assignment of a `mounted` path and sequence counter (`seq`). Seems clean. Now, let's move on to `__path_walk_parent`. This is interesting; the function explicitly handles the case where the current `path`'s `dentry` points to the root of the mount. If this happens, a `choose_mountpoint` function is called to generate a `new_path`. The old `path` is then released, and the `new_path` is assigned to it. The important thing to understand is that the `choose_mountpoint` is the function responsible for proper initialization. The code also manages the `LOOKUP_NO_XDEV` and `LOOKUP_BENEATH` flags which could trigger specific checks in mount operations. Next, I'm examining `mnt_undo_legitimize` in `fs/namespace.c`. This part deals with mount reference counting and cleanup. Critically, it operates on an *existing* `struct mount *mnt`. It's either adjusting the reference count using `mnt_add_count` or, in the case where the reference count drops to zero, adding the mount to a `delayed_mntput_list` for later disposal. I'm not seeing any new allocations or uninitialized structures here; it's all about managing the lifecycle of existing mounts. Alright, now I'm shifting gears to the BPF verifier changes in `kernel/bpf/verifier.c`. The `BTF_TYPE_SAFE_TRUSTED(struct path)` definition is a clue as to what's considered safe. The verifier tracks a `struct dentry *dentry` member. I see the inclusion of the `struct path` in the `BTF_TYPE_EMIT`. It seems BPF is aware of `struct path`. I need to understand more about iterator initialization for BPF. I'm noting the `is_kfunc_arg_iter_init`, `is_kfunc_arg_iter`, `is_iter_new_kfunc` and `btf_check_iter_arg` functions. The `is_kfunc_arg_iter_init` function is key. It's looking for arguments that are marked as `__uninit`. These functions are used to determine if a kernel function initializes an iterator for BPF programs. The verifier will use the `is_iter_reg_valid_uninit` function to check that an uninitialized iterator state is valid. I'm going to carefully review the implementation of `is_iter_reg_valid_uninit`, `process_iter_arg`, and related code to get a complete picture of how the BPF verifier ensures correct initialization and usage of iterators on the stack. My main concern is ensuring that we're catching any potential uninitialized reads or memory corruption within the context of BPF programs, especially when dealing with iterators. |
| 24/3 | 2026/10/06 00:36 | tool | grepper |
0mArgs:{
"Expression": "is_iter_reg_valid_uninit",
"PathPrefixes": [
"kernel/bpf/verifier.c"
]
}Results: {
"Output": "kernel/bpf/verifier.c=1040=static int unmark_stack_slots_iter(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-1067-\nkernel/bpf/verifier.c:1068:static bool is_iter_reg_valid_uninit(struct bpf_verifier_env *env,\nkernel/bpf/verifier.c-1069-\t\t\t\t struct bpf_reg_state *reg, int nr_slots)\n--\nkernel/bpf/verifier.c=8350=static int process_iter_arg(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\n--\nkernel/bpf/verifier.c-8398-\t\t/* expects a pointer to uninit iter state */\nkernel/bpf/verifier.c:8399:\t\tif (!is_iter_reg_valid_uninit(env, reg, nr_slots)) {\nkernel/bpf/verifier.c-8400-\t\t\tverbose(env, \"expected uninitialized iter_%s as %s\\n\",\n"
} |
| 25/3 | 2026/10/06 00:36 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 35076 output: 37 thoughts: 26 Thoughts: Okay, let's dive into this. Here's my take on those lines in `kernel/bpf/verifier.c` – specifically, around lines 1040 to 1080. I'm going to walk through this as if it's the thought process: **Diving into the BPF Verifier: A Quick Look** Alright, the instruction wants me to zoom in on that section of the verifier. Now, with my experience, I know that section is likely handling some crucial aspect of BPF program analysis. Probably related to either register state tracking, instruction validation, or maybe even dependency checks between instructions – given the approximate line numbers, it's very likely to be something related to program safety or resource limits. I'll need to confirm, but I can anticipate the direction of my thought process. Let's see if this is about branch or loop analysis, stack frame validation, or even the interaction of different BPF instruction types. I'll be looking for any indications of register state manipulation, checks on instruction offsets, and how the code handles different instruction classes and their interactions within the context of the larger program. Let's see the details. |
| 26/3 | 2026/10/06 00:36 | tool | read-file |
0mArgs:{
"File": "kernel/bpf/verifier.c",
"FirstLine": 1040,
"LineCount": 50
}Results: {
"Contents": "1040:\tstatic int unmark_stack_slots_iter(struct bpf_verifier_env *env,\n1041:\t\t\t\t\t struct bpf_reg_state *reg, int nr_slots)\n1042:\t{\n1043:\t\tstruct bpf_func_state *state = bpf_func(env, reg);\n1044:\t\tint spi, i, j;\n1045:\t\n1046:\t\tspi = iter_get_spi(env, reg, nr_slots);\n1047:\t\tif (spi \u003c 0)\n1048:\t\t\treturn spi;\n1049:\t\n1050:\t\tfor (i = 0; i \u003c nr_slots; i++) {\n1051:\t\t\tstruct bpf_stack_state *slot = bpf_stack_slot(state, spi - i);\n1052:\t\t\tstruct bpf_reg_state *st = \u0026slot-\u003espilled_ptr;\n1053:\t\n1054:\t\t\tif (i == 0)\n1055:\t\t\t\tWARN_ON_ONCE(release_reference(env, st-\u003eid));\n1056:\t\n1057:\t\t\tbpf_mark_reg_not_init(env, st);\n1058:\t\n1059:\t\t\tfor (j = 0; j \u003c BPF_REG_SIZE; j++)\n1060:\t\t\t\tslot-\u003eslot_type[j] = STACK_INVALID;\n1061:\t\n1062:\t\t\tmark_stack_slot_scratched(env, spi - i);\n1063:\t\t}\n1064:\t\n1065:\t\treturn 0;\n1066:\t}\n1067:\t\n1068:\tstatic bool is_iter_reg_valid_uninit(struct bpf_verifier_env *env,\n1069:\t\t\t\t\t struct bpf_reg_state *reg, int nr_slots)\n1070:\t{\n1071:\t\tstruct bpf_func_state *state = bpf_func(env, reg);\n1072:\t\tint spi, i, j;\n1073:\t\n1074:\t\t/* For -ERANGE (i.e. spi not falling into allocated stack slots), we\n1075:\t\t * will do check_mem_access to check and update stack bounds later, so\n1076:\t\t * return true for that case.\n1077:\t\t */\n1078:\t\tspi = iter_get_spi(env, reg, nr_slots);\n1079:\t\tif (spi == -ERANGE)\n1080:\t\t\treturn true;\n1081:\t\tif (spi \u003c 0)\n1082:\t\t\treturn false;\n1083:\t\n1084:\t\tfor (i = 0; i \u003c nr_slots; i++) {\n1085:\t\t\tstruct bpf_stack_state *slot = bpf_stack_slot(state, spi - i);\n1086:\t\n1087:\t\t\tfor (j = 0; j \u003c BPF_REG_SIZE; j++)\n1088:\t\t\t\tif (slot-\u003eslot_type[j] == STACK_ITER)\n1089:\t\t\t\t\treturn false;\n"
} |
| 27/3 | 2026/10/06 00:36 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 36215 output: 37 thoughts: 20 Thoughts: Here's a summary of my thought process, tailored for an expert like you: **Diving into `process_iter_arg`** Okay, time to zero in on `process_iter_arg`. Specifically, I'm focusing my attention around line 8400. This is where the core logic of that function should be, right where the iteration argument is handled. I need to refresh my understanding of *exactly* how the iteration arguments are being passed, parsed, and applied within this function. I'm looking for the potential for misuse and any missed edge cases here; I know this is a critical spot for iteration-related bugs. I also need to be ready to recognize any areas for potential optimization. |
| 28/3 | 2026/10/06 00:36 | tool | read-file |
0mArgs:{
"File": "kernel/bpf/verifier.c",
"FirstLine": 8390,
"LineCount": 60
}Results: {
"Contents": "8390:\t\t * Whether this argument is the iterator the call initializes, rather\n8391:\t\t * than an initialized one it operates on.\n8392:\t\t */\n8393:\t\tinit = is_iter_new_kfunc(meta) ||\n8394:\t\t is_kfunc_arg_iter_init(meta, arg,\n8395:\t\t\t\t\t \u0026btf_params(meta-\u003efunc_proto)[arg]);\n8396:\t\n8397:\t\tif (init) {\n8398:\t\t\t/* expects a pointer to uninit iter state */\n8399:\t\t\tif (!is_iter_reg_valid_uninit(env, reg, nr_slots)) {\n8400:\t\t\t\tverbose(env, \"expected uninitialized iter_%s as %s\\n\",\n8401:\t\t\t\t\titer_type_str(meta-\u003ebtf, btf_id), reg_arg_name(env, argno));\n8402:\t\t\t\tbpf_diag_res(\n8403:\t\t\t\t\tenv, insn_idx, \"iterator is already initialized\",\n8404:\t\t\t\t\t\"Iterator creation requires an uninitialized iterator stack object, but this stack range already contains iterator state.\",\n8405:\t\t\t\t\t\"Use a fresh iterator stack slot, or destroy the existing iterator before reusing the slot.\");\n8406:\t\t\t\treturn -EINVAL;\n8407:\t\t\t}\n8408:\t\n8409:\t\t\tfor (i = 0; i \u003c nr_slots * 8; i += BPF_REG_SIZE) {\n8410:\t\t\t\terr = check_mem_access(env, insn_idx, reg, argno,\n8411:\t\t\t\t\t\t i, BPF_DW, BPF_WRITE, -1, false, false);\n8412:\t\t\t\tif (err)\n8413:\t\t\t\t\treturn err;\n8414:\t\t\t}\n8415:\t\n8416:\t\t\terr = mark_stack_slots_iter(env, meta, reg, insn_idx, meta-\u003ebtf, btf_id, nr_slots);\n8417:\t\t\tif (err)\n8418:\t\t\t\treturn err;\n8419:\t\t} else {\n8420:\t\t\t/* iter_next() or iter_destroy(), as well as any kfunc\n8421:\t\t\t * accepting iter argument, expect initialized iter state\n8422:\t\t\t */\n8423:\t\t\terr = is_iter_reg_valid_init(env, reg, meta-\u003ebtf, btf_id, nr_slots);\n8424:\t\t\tswitch (err) {\n8425:\t\t\tcase 0:\n8426:\t\t\t\tbreak;\n8427:\t\t\tcase -EINVAL:\n8428:\t\t\t\tverbose(env, \"expected an initialized iter_%s as %s\\n\",\n8429:\t\t\t\t\titer_type_str(meta-\u003ebtf, btf_id), reg_arg_name(env, argno));\n8430:\t\t\t\tbpf_diag_res(\n8431:\t\t\t\t\tenv, insn_idx, \"uninitialized iterator use\",\n8432:\t\t\t\t\t\"This iterator operation requires an initialized iterator state object, but the stack range does not contain a live iterator on this path.\",\n8433:\t\t\t\t\t\"Call the matching iterator new kfunc on every path before calling next or destroy, and do not destroy the iterator before this use.\");\n8434:\t\t\t\treturn err;\n8435:\t\t\tcase -EPROTO:\n8436:\t\t\t\tverbose(env, \"expected an RCU CS when using %s\\n\", meta-\u003efunc_name);\n8437:\t\t\t\tbpf_diag_ctx_required(\n8438:\t\t\t\t\tenv, insn_idx, meta-\u003efunc_name, BPF_DIAG_CONTEXT_RCU,\n8439:\t\t\t\t\t\"Wrap iterator use in bpf_rcu_read_lock() and bpf_rcu_read_unlock(), keeping all exit paths balanced.\");\n8440:\t\t\t\treturn err;\n8441:\t\t\tdefault:\n8442:\t\t\t\treturn err;\n8443:\t\t\t}\n8444:\t\n8445:\t\t\tspi = iter_get_spi(env, reg, nr_slots);\n8446:\t\t\tif (spi \u003c 0)\n8447:\t\t\t\treturn spi;\n8448:\t\n8449:\t\t\tmark_stack_slots_scratched(env, spi, nr_slots);\n"
} |
| 29/3 | 2026/10/06 00:36 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 37524 output: 32 thoughts: 1934 Thoughts: **Analyzing BPF Iterator Initialization and Potential KMSAN Issues** Okay, let's break this down. My initial focus was on ensuring proper initialization of the `bpf_iter_path_ancestors` iterator used within the BPF kfunc `bpf_path_ancestors_legitimize`. We see the iterator is initialized via `mark_stack_slots_iter` and that `kit` (which points to `it`) is memset to zero within `bpf_path_ancestors_legitimize` when it takes over from `rcu_it`. This seemed safe. However, I circled back to `bpf_path_ancestors_new`. I realized that when `flags` is zero, as is common, `kit` (which is a local variable on the BPF stack) isn't explicitly initialized. Its `step` is set to 0, but `vfs_walk_start` is then called. Inside `vfs_walk_start`, `aw->pos`, `aw->flags` and `aw->pos_flags` get initialized. But if we're *not* using RCU mode (`flags & VFS_WALK_RCU` is false), `aw->m_seq` and `aw->seq` are left uninitialized. Also any padding bytes could also be uninitialized. This is concerning, potentially leading to reads of uninitialized memory. The core question then became: are those uninitialized fields ever *read*? After carefully tracing through the function calls – `vfs_walk_step_ref`, `vfs_walk_next`, `vfs_walk_end`, and `bpf_path_ancestors_pos_flags` – I found no instances where `aw->seq` or `aw->m_seq` are read when `VFS_WALK_RCU` is not set. The same applies for any possible padding bytes. Only `step` and `aw.pos_flags` are ever read. This is important because KMSAN, as I understand, only triggers on actual *reads* of uninitialized memory or writes to user space. If a variable is uninitialized but never accessed, KMSAN shouldn't complain. Also, the `it` itself never leaves the kernel, so no copy-to-user issues exist. Furthermore, I verified that the BPF verifier explicitly prevents BPF programs from directly reading stack slots marked as `STACK_ITER`, further reducing the possibility of a direct read of the uninitialized data by a BPF program. So, while the uninitialized stack slot is technically a problem, the current code path doesn't expose it to any operation where KMSAN would warn. |
| 30/3 | 2026/10/06 00:36 | tool | grepper |
0mArgs:{
"Expression": "STACK_ITER",
"PathPrefixes": [
"kernel/bpf/verifier.c"
]
}Results: {
"Output": "kernel/bpf/verifier.c=997=static int mark_stack_slots_iter(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-1031-\t\tfor (j = 0; j \u003c BPF_REG_SIZE; j++)\nkernel/bpf/verifier.c:1032:\t\t\tslot-\u003eslot_type[j] = STACK_ITER;\nkernel/bpf/verifier.c-1033-\n--\nkernel/bpf/verifier.c=1068=static bool is_iter_reg_valid_uninit(struct bpf_verifier_env *env,\n--\nkernel/bpf/verifier.c-1087-\t\tfor (j = 0; j \u003c BPF_REG_SIZE; j++)\nkernel/bpf/verifier.c:1088:\t\t\tif (slot-\u003eslot_type[j] == STACK_ITER)\nkernel/bpf/verifier.c-1089-\t\t\t\treturn false;\n--\nkernel/bpf/verifier.c=1095=static int is_iter_reg_valid_init(struct bpf_verifier_env *env, struct bpf_reg_state *reg,\n--\nkernel/bpf/verifier.c-1119-\t\tfor (j = 0; j \u003c BPF_REG_SIZE; j++)\nkernel/bpf/verifier.c:1120:\t\t\tif (slot-\u003eslot_type[j] != STACK_ITER)\nkernel/bpf/verifier.c-1121-\t\t\t\treturn -EINVAL;\n--\nkernel/bpf/verifier.c=1252=static int is_irq_flag_reg_valid_init(struct bpf_verifier_env *env, struct bpf_reg_state *reg)\n--\nkernel/bpf/verifier.c-1277- * - dynptr state (STACK_DYNPTR);\nkernel/bpf/verifier.c:1278: * - iter state (STACK_ITER).\nkernel/bpf/verifier.c-1279- * - irq flag state (STACK_IRQ_FLAG)\n--\nkernel/bpf/verifier.c=1281=static bool is_stack_slot_special(const struct bpf_stack_state *stack)\n--\nkernel/bpf/verifier.c-1287-\tcase STACK_DYNPTR:\nkernel/bpf/verifier.c:1288:\tcase STACK_ITER:\nkernel/bpf/verifier.c-1289-\tcase STACK_IRQ_FLAG:\n--\nkernel/bpf/verifier.c=4011=static void bpf_diag_stack_read_invalid(struct bpf_verifier_env *env, int off, int i, int size,\n--\nkernel/bpf/verifier.c-4032-\t\tbreak;\nkernel/bpf/verifier.c:4033:\tcase STACK_ITER:\nkernel/bpf/verifier.c-4034-\t\tkind = \"iterator\";\n--\nkernel/bpf/verifier.c=10786=static void invalidate_rcu_protected_refs(struct bpf_verifier_env *env)\n--\nkernel/bpf/verifier.c-10790-\tstruct bpf_reg_state *reg;\nkernel/bpf/verifier.c:10791:\tu32 clear_mask = (1 \u003c\u003c STACK_SPILL) | (1 \u003c\u003c STACK_ITER);\nkernel/bpf/verifier.c-10792-\n"
} |
| 31/3 | 2026/10/06 00:36 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 40938 output: 29 thoughts: 1216 Thoughts: **BPF Verifier and Stack Iteration: A Deep Dive** Okay, so the BPF verifier is definitely blocking any attempts to directly read from `STACK_ITER` slots. That's a given. But my mind is now on `vfs_walk_handover`. I need to figure out how it interacts with this restriction. Let me take a closer look at `vfs_walk_handover`: the function's code is fresh in my mind. Okay, I see `from->seq` and `from->m_seq`. It's reading from these variables, and `from` is an RCU walk. Now, how are these initialized? Ah, right! When `from` is started: `aw->m_seq = read_seqbegin(&mount_lock); aw->seq = raw_seqcount_begin(&aw->pos.dentry->d_seq);` Both `m_seq` and `seq` get initialized. And when `from` steps, which is where things get interesting: `aw->pos.dentry = parent; aw->seq = next_seq;`. Inside `__path_walk_parent_rcu`, if it crosses a mount, `*seqp = seq;` and `*next_seqp = read_seqcount_begin(&parent->d_seq);`. So `aw->seq` seems to always get updated and be valid! Now, the tricky scenarios. What if `bpf_path_ancestors_legitimize` is called when `rcu_kit` is brand new, before the first step? Well, before the first step, `rcu_kit->step` is zero. `vfs_walk_handover` uses `from->pos`, `from->m_seq`, and `from->seq`. But `vfs_walk_start` initializes `aw->pos`, `aw->m_seq`, and `aw->seq`. So both `from->m_seq` and `from->seq` are properly initialized! What if `bpf_path_ancestors_legitimize` is called when `rcu_kit` is already in another state? I immediately see the safety mechanism: `if (rcu_kit->step) return -ENOENT;`. If `rcu_kit->step != 0` (failed or at the end of the walk), it returns `-ENOENT`. Then, let me see how `kit` is initialized; `memset(kit, 0, sizeof(*kit)); kit->step = 1;`. So `kit` is cleanly initialized. That's good. Alright, what about `bpf_iter_path_ancestors_next`? The code uses `kmalloc_obj(*held)`. That requires immediate attention! What is `kmalloc_obj(*held)`? I'm going to need to check the kernel definition of that function, so I can see what is happening here. |
| 32/3 | 2026/10/06 00:36 | tool | grepper |
0mArgs:{
"Expression": "#define kmalloc_obj",
"PathPrefixes": [
"include/"
]
}Results: {
"Output": "include/linux/slab.h=1073=void *kmalloc_nolock(size_t size, gfp_t gfp_flags, int node);\n--\ninclude/linux/slab.h-1123- */\ninclude/linux/slab.h:1124:#define kmalloc_obj(VAR_OR_TYPE, ...) \\\ninclude/linux/slab.h-1125-\t__alloc_objs(kmalloc, default_gfp(__VA_ARGS__), typeof(VAR_OR_TYPE), 1)\n--\ninclude/linux/slab.h-1135- */\ninclude/linux/slab.h:1136:#define kmalloc_objs(VAR_OR_TYPE, COUNT, ...) \\\ninclude/linux/slab.h-1137-\t__alloc_objs(kmalloc, default_gfp(__VA_ARGS__), typeof(VAR_OR_TYPE), COUNT)\n"
} |
| 33/3 | 2026/10/06 00:36 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 43050 output: 31 thoughts: 109 Thoughts: **Analyzing the Path Structure Memory Allocation**
Okay, so `kmalloc_obj(*held)` is allocating uninitialized memory. It's doing this specifically for the size of whatever `*held` is, which in this case is a `struct path`. It's using the default GFP flags, which I know means it's likely using `GFP_KERNEL`. This is standard practice, but it's always good to be explicit about these things.
Now, the code proceeds to copy the contents of `*pos` into `*held` with `*held = *pos;`. This is where I want to be *absolutely* certain about what's happening memory-wise. So, let's nail down the size of that `struct path`. I'll recall the definition: `struct path { struct vfsmount *mnt; struct dentry *dentry; };`. That looks like two pointers, `mnt` and `dentry`. Okay, pointers are usually 8 bytes on 64-bit systems, and I'll bet it's the same here. Therefore it's going to copy 16 bytes.
Just to be completely thorough, I'm going to double-check that `struct path` definition in the `include/linux/path.h` header file. It's always best to be precise, especially when memory allocation and structure sizes are involved.
|
| 34/3 | 2026/10/06 00:36 | tool | grepper |
0mArgs:{
"Expression": "struct path \\{",
"PathPrefixes": [
"include/linux/path.h"
]
}Results: {
"Output": "include/linux/path.h=6=struct vfsmount;\ninclude/linux/path.h-7-\ninclude/linux/path.h:8:struct path {\ninclude/linux/path.h-9-\tstruct vfsmount *mnt;\n"
} |
| 35/3 | 2026/10/06 00:36 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 43556 output: 33 thoughts: 18 Thoughts: Okay, here's my interpretation of what's going through my mind as I approach that section of `include/linux/path.h`, presented as a first-person summary: **Diving into path.h** Alright, time to get my bearings. The task is to understand the core structures related to file paths in the kernel. Let's start with `include/linux/path.h`, and, based on the context, I'm going to focus my attention around line 8. Typically that early in a header file means we're dealing with definitions or the very basic structures that everything else is built upon. Expecting either data structures or very early function prototypes. I bet we'll see the fundamental `path` struct itself, or maybe some key macros. Time to go. |
| 36/3 | 2026/10/06 00:36 | tool | read-file |
0mArgs:{
"File": "include/linux/path.h",
"FirstLine": 6,
"LineCount": 15
}Results: {
"Contents": " 6:\tstruct vfsmount;\n 7:\t\n 8:\tstruct path {\n 9:\t\tstruct vfsmount *mnt;\n 10:\t\tstruct dentry *dentry;\n 11:\t} __randomize_layout;\n 12:\t\n 13:\textern void path_get(const struct path *);\n 14:\textern void path_put(const struct path *);\n 15:\t\n 16:\tstatic inline int path_equal(const struct path *path1, const struct path *path2)\n 17:\t{\n 18:\t\treturn path1-\u003emnt == path2-\u003emnt \u0026\u0026 path1-\u003edentry == path2-\u003edentry;\n 19:\t}\n 20:\t\n"
} |
| 37/3 | 2026/10/06 00:36 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 43969 output: 31 thoughts: 1011 Thoughts: **Analyzing Potential User Space Information Leaks in `struct path`** Okay, let's break this down. My initial thought is that the `struct path` definition is pretty straightforward – just `mnt` and `dentry` pointers. The compiler shouldn't be introducing any unexpected padding that would lead to information leaks when copying the structure, especially with the layout randomization disabled. So, copying the entire struct with `*held = *pos;` seems safe in that respect, and the subsequent refcount increment with `path_get(held)` appears standard. The `bpf_path_put` function looks correct: `path_put` to decrement the reference count and `kfree` to release the memory. However, the real worry is: Is *any* of this data ending up in user space? I need to verify that `struct path`, or related structures like `vfs_ancestor_walk` or `bpf_iter_path_ancestors`, aren't being inadvertently exposed. Good news, it doesn't appear they are. Ah, but then I remember the `tools/testing/selftests/bpf/prog_tests/path_ancestors.c` example! This calls `bpf_path_d_path(pos, second_path, sizeof(second_path));`. That's a preexisting kfunc or helper. I need to keep that in mind. Now, Landlock is using `vfs_walk_ancestors`. Let me see how it's implemented in `security/landlock/fs.c`. It looks like they've refactored a loop using `vfs_walk_ancestors`, replacing the old `while (true)` loop with this. The important part is making sure no uninitialized data or kernel-internal state makes its way to user space. Okay, let's examine the `struct landlock_walk_state state`. It's local, on the stack, and initialized with designated initializers. All unspecified members, which would include any pointers, are implicitly initialized to zero or NULL. That's a good start. I can see the structure is filled. Importantly, the code explicitly sets fields like `allowed_parent1`, `allowed_parent2`, and `access_masked_parent1` *before* calling `vfs_walk_ancestors`. Digging deeper into `check_access_path_walk`, the `struct landlock_id id` is initialized, and even the `.ino` field is initialized, inside `get_inode_id`. I should check that `get_inode_id` function to make sure it's doing the right thing. It's a key part of the Landlock implementation, and this seems like the point where something *could* be missed if the code isn't thorough. |
| 38/3 | 2026/10/06 00:37 | tool | grepper |
0mArgs:{
"Expression": "get_inode_id",
"PathPrefixes": [
"security/landlock/"
]
}Results: {
"Output": "security/landlock/fs.c=329=int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,\n--\nsecurity/landlock/fs.c-380-/**\nsecurity/landlock/fs.c:381: * get_inode_id - Look up the Landlock object for a dentry\nsecurity/landlock/fs.c-382- * @dentry: The dentry to look up.\n--\nsecurity/landlock/fs.c-396- */\nsecurity/landlock/fs.c:397:static bool get_inode_id(const struct dentry *const dentry,\nsecurity/landlock/fs.c-398-\t\t\t struct landlock_id *id)\n--\nsecurity/landlock/fs.c=764=static int check_access_path_walk(const struct path *const ancestor,\n--\nsecurity/landlock/fs.c-832-\nsecurity/landlock/fs.c:833:\tif (get_inode_id(ancestor-\u003edentry, \u0026id)) {\nsecurity/landlock/fs.c-834-\t\tstate-\u003eallowed_parent1 =\n--\nsecurity/landlock/fs.c=896=is_access_to_paths_allowed(const struct landlock_domain *const domain,\n--\nsecurity/landlock/fs.c-956-\t\t * trivially allowed. This must be set explicitly because the\nsecurity/landlock/fs.c:957:\t\t * get_inode_id() gate in the walk callback may prevent\nsecurity/landlock/fs.c-958-\t\t * landlock_unmask_layers() from being called (which would\n--\nsecurity/landlock/fs.c-974-\t\t\t\t\t\t LANDLOCK_KEY_INODE);\nsecurity/landlock/fs.c:975:\t\tif (handled \u0026\u0026 get_inode_id(dentry_child1, \u0026id))\nsecurity/landlock/fs.c-976-\t\t\tunmask_layers_fs(domain, id, handled,\n--\nsecurity/landlock/fs.c-990-\t\t\t\t\t\t LANDLOCK_KEY_INODE);\nsecurity/landlock/fs.c:991:\t\tif (handled \u0026\u0026 get_inode_id(dentry_child2, \u0026id))\nsecurity/landlock/fs.c-992-\t\t\tunmask_layers_fs(domain, id, handled,\n--\nsecurity/landlock/fs.c=1124=static bool collect_domain_accesses(const struct landlock_domain *const domain,\n--\nsecurity/landlock/fs.c-1150-\t\t/* Gets all layers allowing all domain accesses. */\nsecurity/landlock/fs.c:1151:\t\tif (get_inode_id(dir, \u0026id) \u0026\u0026\nsecurity/landlock/fs.c-1152-\t\t unmask_layers_fs(domain, id, access_masked_dom,\n"
} |
| 39/3 | 2026/10/06 00:37 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 46333 output: 557 thoughts: 1819 Thoughts: **Analysis of Potential Uninitialized Memory Issues** Okay, let's break this down. My initial concern was centered around potential uninitialized variables, specifically within `__path_walk_parent_rcu`. However, after reviewing the code, it's clear that all variables are initialized within the relevant code paths. The `choose_mountpoint_rcu` function, if it returns true, correctly sets `mounted` and `seq` before assigning them to `path` and `seqp`. The subsequent variable assignments all use those initialized values. The `vfs_walk_no_root` variable is also correctly initialized. I then considered potential issues arising from the interaction with `BTF_TYPE_SAFE_TRUSTED(struct path)`. While this construct indicates a trusted path structure, it doesn't expose uninitialized memory because `dentry` member is initialized in the code. I investigated if the BPF program might have issues when returning or copying paths to userspace. After careful consideration, I'm satisfied that this is not a vulnerability. The kernel manages the path struct, ensuring that no uninitialized memory could be leaked. Now, let's systematically address the four criteria for KMSAN detection: 1. **Kernel structures sent to user space:** There are no structures that are copied to user space in this patch. 2. **Conditional logic based on uninitialized data:** There is no such logic found. All conditional checks rely on initialized fields. 3. **Allocation/initialization of complex data structures:** The allocation and initialization of path structures using `kmalloc_obj` and `memset` in `bpf_iter_path_ancestors_next` and in `bpf_path_ancestors_new` and `bpf_path_ancestors_legitimize` are safe and don't lead to uninitialized memory being read. Additionally, stack-allocated structs in `landlock/fs.c` are properly initialized. 4. **Bounds checks exposing uninitialized memory:** There are no instances where the logic allows access to uninitialized bytes of existing buffers. To cover all the bases, I've considered other potential bug types: * **Reference counting issues:** `dget`, `dput`, etc. that could lead to UAF, refcount underflow/leak - KASAN and the refcount detector would catch these. * **RCU synchronization issues:** race conditions during RCU path walking - KASAN (if UAF occurs) or normal kernel testing / PROVE\_RCU / lockdep would catch these. * **Locking / deadlocks:** `mount_lock`, `delayed_mntput` - LOCKDEP would find. * **Verifier correctness:** Reference leaking in BPF programs - BPF verifier would detect these. In conclusion, none of the possible bugs are related to uninitialized memory bugs or kernel-to-user information leaks that *only* KMSAN would detect. The code seems robust and properly initialized. Therefore, `NeedsKMSAN=false`. Let's double-check again, just to be thorough... Yep, we are good to go. |
| 40/3 | 2026/10/06 00:37 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch series introduces stepwise VFS path ancestor walking infrastructure (vfs_walk_ancestors) used by Landlock and exposes BPF open-coded iterators (bpf_iter_path_ancestors and bpf_iter_path_ancestors_rcu) along with associated kfuncs.\n\nEvaluating the changes for uninitialized memory risks vs. KASAN/LOCKDEP:\n1. Memory initialization:\n - In `bpf_iter_path_ancestors_next()`, `held = kmalloc_obj(*held)` allocates a `struct path` which is immediately fully populated by `*held = *pos` (a 16-byte struct with two pointer fields and no padding).\n - In `bpf_path_ancestors_legitimize()`, the destination iterator `kit` is immediately zeroed using `memset(kit, 0, sizeof(*kit))` before any operations.\n - In `vfs_walk_start()`, when `VFS_WALK_RCU` is set, `m_seq` and `seq` are explicitly sampled via `read_seqbegin()` and `raw_seqcount_begin()`. In non-RCU mode, branches that read `seq` or `m_seq` are strictly guarded by `aw-\u003eflags \u0026 VFS_WALK_RCU` and never execute.\n - In `security/landlock/fs.c`, the new `struct landlock_walk_state state` is stack-allocated with designated initializers (zeroing unmentioned fields) and explicitly initializes all remaining fields before being passed to `vfs_walk_ancestors()`.\n2. Kernel-to-user info leaks:\n - No kernel data structures or iterator states are copied to userspace via `copy_to_user`, netlink, ioctl, or socket buffers.\n3. KASAN vs KMSAN applicability:\n - The primary risks of this patch involve reference counting (e.g. `dget`/`dput`, `path_get`/`path_put`, `mnt_undo_legitimize`), lockless RCU concurrency races, or BPF verifier reference tracking. Any memory corruption, use-after-free, or double-free resulting from reference count errors is detected by KASAN and standard refcount tracking, while locking issues are caught by LOCKDEP.\n - There are no uninitialized memory usages or information leaks that would require a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch series introduces stepwise VFS path ancestor walking infrastructure (vfs_walk_ancestors) used by Landlock and exposes BPF open-coded iterators (bpf_iter_path_ancestors and bpf_iter_path_ancestors_rcu) along with associated kfuncs.\n\nEvaluating the changes for uninitialized memory risks vs. KASAN/LOCKDEP:\n1. Memory initialization:\n - In `bpf_iter_path_ancestors_next()`, `held = kmalloc_obj(*held)` allocates a `struct path` which is immediately fully populated by `*held = *pos` (a 16-byte struct with two pointer fields and no padding).\n - In `bpf_path_ancestors_legitimize()`, the destination iterator `kit` is immediately zeroed using `memset(kit, 0, sizeof(*kit))` before any operations.\n - In `vfs_walk_start()`, when `VFS_WALK_RCU` is set, `m_seq` and `seq` are explicitly sampled via `read_seqbegin()` and `raw_seqcount_begin()`. In non-RCU mode, branches that read `seq` or `m_seq` are strictly guarded by `aw-\u003eflags \u0026 VFS_WALK_RCU` and never execute.\n - In `security/landlock/fs.c`, the new `struct landlock_walk_state state` is stack-allocated with designated initializers (zeroing unmentioned fields) and explicitly initializes all remaining fields before being passed to `vfs_walk_ancestors()`.\n2. Kernel-to-user info leaks:\n - No kernel data structures or iterator states are copied to userspace via `copy_to_user`, netlink, ioctl, or socket buffers.\n3. KASAN vs KMSAN applicability:\n - The primary risks of this patch involve reference counting (e.g. `dget`/`dput`, `path_get`/`path_put`, `mnt_undo_legitimize`), lockless RCU concurrency races, or BPF verifier reference tracking. Any memory corruption, use-after-free, or double-free resulting from reference count errors is detected by KASAN and standard refcount tracking, while locking issues are caught by LOCKDEP.\n - There are no uninitialized memory usages or information leaks that would require a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|