summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZhao Gongyi <zhaogongyi@bytedance.com>2026-10-23 19:12:50 +0800
committerChristian Brauner <brauner@kernel.org>2026-10-30 15:54:20 +0100
commitc206cdfee6c037b829178bb7e17e6e37ddc9aa35 (patch)
treed4b8116ca1388c096655be72cd497233c943998d
parent53d72818f9e2aeb03a6053544ee6efa4eb2a3988 (diff)
downloadlinux-c206cdfee6c037b829178bb7e17e6e37ddc9aa35.tar.gz
linux-c206cdfee6c037b829178bb7e17e6e37ddc9aa35.zip
filelock: keep superblock active through delegation wait
A caller may drop its path after try_break_deleg() returns a delegated inode but before break_deleg_wait() finishes. The extra inode reference held in struct delegated_inode does not keep the superblock active. After a lazy unmount, closing the delegation holder can release the last mount reference and shut down the superblock while another task waits for the delegation break. The waiter's final iput() can then enter the filesystem's eviction code after its superblock-private state was freed. This has been observed as a KASAN use-after-free in shmem_evict_inode(). When try_break_deleg() saves a delegated inode, take an active reference to its superblock while the caller still pins the mount. Keep that reference through break_deleg_wait() and the final iput(), then release it with deactivate_super(). This protects every caller of the helpers, including the mknod, mkdir, symlink and rmdir paths that drop their path before waiting. Fixes: e12d203b8c88 ("vfs: allow mkdir to wait for delegation break on parent") Fixes: 4fa76319cd0c ("vfs: allow rmdir to wait for delegation break on parent") Fixes: c826229c6a82 ("vfs: make vfs_create break delegations on parent directory") Fixes: e8960c1b2ee9 ("vfs: make vfs_mknod break delegations on parent directory") Fixes: 92bf53577f01 ("vfs: make vfs_symlink break delegations on parent dir") Link: https://lore.kernel.org/r/20261016093421.90725-1-zhaogongyi@bytedance.com Cc: stable@vger.kernel.org Signed-off-by: Zhao Gongyi <zhaogongyi@bytedance.com> Link: https://patch.msgid.link/20261023111250.17696-1-zhaogongyi@bytedance.com Reviewed-by: Jeff Layton <jlayton@kernel.org> Reviewed-by: Jan Kara <jack@suse.cz> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
-rw-r--r--include/linux/filelock.h13
1 files changed, 9 insertions, 4 deletions
diff --git a/include/linux/filelock.h b/include/linux/filelock.h
index ec11cc6b4c58..e909db319067 100644
--- a/include/linux/filelock.h
+++ b/include/linux/filelock.h
@@ -530,8 +530,8 @@ static inline bool is_delegated(struct delegated_inode *di)
*
* Break delegations in a non-blocking fashion. If there are
* outstanding delegations and @di is set, then an extra reference
- * will be taken on @inode and @di->di_inode will be populated so
- * that it may be waited upon.
+ * will be taken on @inode and its superblock, and @di->di_inode will
+ * be populated so that it may be waited upon.
*
* Returns 0 if there is no need to wait or an error. If -EWOULDBLOCK
* is returned, then @di will be populated (if non-NULL).
@@ -545,16 +545,21 @@ static inline int try_break_deleg(struct inode *inode, unsigned int flags,
if (ret == -EWOULDBLOCK && di) {
di->di_inode = inode;
ihold(inode);
+ /* The caller may drop its mount before break_deleg_wait(). */
+ atomic_inc(&inode->i_sb->s_active);
}
return ret;
}
static inline int break_deleg_wait(struct delegated_inode *di)
{
+ struct inode *inode = di->di_inode;
+ struct super_block *sb = inode->i_sb;
int ret;
- ret = break_deleg(di->di_inode, 0);
- iput(di->di_inode);
+ ret = break_deleg(inode, 0);
+ iput(inode);
+ deactivate_super(sb);
di->di_inode = NULL;
return ret;
}