diff options
| author | Josef Bacik <josef@toxicpanda.com> | 2026-10-04 13:54:57 +0000 |
|---|---|---|
| committer | Christian Brauner <brauner@kernel.org> | 2026-10-29 14:12:33 +0100 |
| commit | e227cdac03dffa896e02ed7260f9149c4c382715 (patch) | |
| tree | 30cc181b7f6ee380412a368076cbbf9c4c4eabb8 | |
| parent | d4a952a954c81f123cd32f2ebba0c6c9fdd41a05 (diff) | |
| download | linux-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.c | 10 |
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; |
