diff options
| author | Zhao Gongyi <zhaogongyi@bytedance.com> | 2026-10-23 19:12:50 +0800 |
|---|---|---|
| committer | Christian Brauner <brauner@kernel.org> | 2026-10-30 15:54:20 +0100 |
| commit | c206cdfee6c037b829178bb7e17e6e37ddc9aa35 (patch) | |
| tree | d4b8116ca1388c096655be72cd497233c943998d | |
| parent | 53d72818f9e2aeb03a6053544ee6efa4eb2a3988 (diff) | |
| download | linux-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.h | 13 |
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; } |
