summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosef Bacik <josef@toxicpanda.com>2026-10-04 13:54:57 +0000
committerChristian Brauner <brauner@kernel.org>2026-10-29 14:12:33 +0100
commite227cdac03dffa896e02ed7260f9149c4c382715 (patch)
tree30cc181b7f6ee380412a368076cbbf9c4c4eabb8
parentd4a952a954c81f123cd32f2ebba0c6c9fdd41a05 (diff)
downloadlinux-e227cdac03dffa896e02ed7260f9149c4c382715.tar.gz
linux-e227cdac03dffa896e02ed7260f9149c4c382715.zip
namei: don't assert S_ISDIR on a stale rcu-walk inode
lookup_inode_permission_may_exec() asserts that it is only ever handed a directory, on the grounds that link_path_walk() has already checked d_can_lookup(). In rcu-walk that does not follow. step_into() samples ->d_inode and validates it against ->d_seq, but link_path_walk() loads ->d_flags again afterwards, and nothing orders that load against changes of the dentry: CPU0 (rcu-walk of "x/y") CPU1 step_into(x) inode = x->d_inode // regular file read_seqcount_retry(&x->d_seq) == 0 nd->inode = inode unlink("x") d_delete() // x goes negative mkdir("x") d_instantiate() // same dentry d_can_lookup(x) // true now may_lookup() VFS_BUG_ON_INODE(!S_ISDIR(nd->inode->i_mode)) With CONFIG_PREEMPT_RCU the walker can be preempted in that window, so it is not limited to a few instructions. mkdir() only gets the same dentry back if the filesystem keeps negative dentries around. tmpfs doesn't, ext4 does. Apart from the assertion the stale inode is harmless, and has been there since before the assertion was added: nd->seq no longer matches x->d_seq, so lookup_fast(), follow_dotdot_rcu() and try_to_unlazy() all fail with -ECHILD and the walk is redone in ref-walk, whatever the permission check on the old inode said. Keep the assertion for ref-walk, where the dentry is pinned and can't change type, and skip it for rcu-walk. No change for kernels without CONFIG_DEBUG_VFS. Fixes: e631df89cd5d ("fs: speed up path lookup with cheaper handling of MAY_EXEC") Signed-off-by: Josef Bacik <josef@toxicpanda.com> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
-rw-r--r--fs/namei.c10
1 files changed, 8 insertions, 2 deletions
diff --git a/fs/namei.c b/fs/namei.c
index 22c4f328b8a9..deddab4ebda3 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -683,8 +683,14 @@ EXPORT_SYMBOL(inode_permission);
static __always_inline int lookup_inode_permission_may_exec(const struct mnt_idmap *idmap,
struct inode *inode, int mask)
{
- /* Lookup already checked this to return -ENOTDIR */
- VFS_BUG_ON_INODE(!S_ISDIR(inode->i_mode), inode);
+ /*
+ * Lookup already checked this to return -ENOTDIR. In rcu-walk that
+ * check looks at ->d_flags after ->d_seq had been validated, so it can
+ * see a dentry which went negative and then became a directory while
+ * nd->inode is still the old non-directory. Any such walk fails the
+ * next ->d_seq check and gets redone in ref-walk.
+ */
+ VFS_BUG_ON_INODE(!S_ISDIR(inode->i_mode) && !(mask & MAY_NOT_BLOCK), inode);
VFS_BUG_ON((mask & ~MAY_NOT_BLOCK) != 0);
mask |= MAY_EXEC;