ntfs_file_fsync(), ntfs_dir_fsync() and __ntfs_write_inode() lock an
inode's mrec_lock before taking the mrec_lock of its parent directory.
ntfs_rename() takes old_ni->mrec_lock and old_dir_ni->mrec_lock
before taking new_ni->mrec_lock for an existing target, or
new_dir_ni->mrec_lock for a cross-directory rename.
This can deadlock when ntfs_file_fsync() or __ntfs_write_inode() holds
the target inode, or when ntfs_dir_fsync() holds a child target
directory, while rename() holds the parent directory and waits for the
target.
Fix this by locking the existing target inode before taking any parent
directory mrec_lock. For cross-directory renames where the target parent
is a descendant of the source parent, lock the target parent before the
source parent so the directory order matches the child-to-parent order used
by ntfs_file_fsync(), ntfs_dir_fsync(), and __ntfs_write_inode().
Reported-by: Peiyang He <
peiya...@smail.nju.edu.cn>
Closes:
https://lore.kernel.org/all/C4D296F0E9F3D66C+9397ffbc-...@smail.nju.edu.cn/
Fixes: af0db57d4293 ("ntfs: update inode operations")
Cc:
sta...@vger.kernel.org
Signed-off-by: Peiyang He <
peiya...@smail.nju.edu.cn>
Assisted-by: Codex:gpt-5.5
---
fs/ntfs/namei.c | 60 ++++++++++++++++++++++++-------------------------
1 file changed, 29 insertions(+), 31 deletions(-)
diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c
index a19626a135bd..43f5f306a4fc 100644
--- a/fs/ntfs/namei.c
+++ b/fs/ntfs/namei.c
@@ -1266,6 +1266,7 @@ static int ntfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
struct ntfs_volume *vol = NTFS_SB(sb);
struct ntfs_inode *old_ni, *new_ni = NULL;
struct ntfs_inode *old_dir_ni = NTFS_I(old_dir), *new_dir_ni = NTFS_I(new_dir);
+ bool new_dir_first = false;
if (NVolShutdown(old_dir_ni->vol))
return -EIO;
@@ -1301,36 +1302,37 @@ static int ntfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
old_inode = old_dentry->d_inode;
new_inode = new_dentry->d_inode;
old_ni = NTFS_I(old_inode);
+ if (new_inode)
+ new_ni = NTFS_I(new_inode);
if (!(vol->vol_flags & VOLUME_IS_DIRTY))
ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY);
mutex_lock_nested(&old_ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL);
- mutex_lock_nested(&old_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT);
+ if (new_ni)
+ mutex_lock_nested(&new_ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL_2);
- if (NInoBeingDeleted(old_ni) || NInoBeingDeleted(old_dir_ni)) {
+ if (old_dir == new_dir) {
+ mutex_lock_nested(&old_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT);
+ } else if (d_ancestor(old_dentry->d_parent, new_dentry->d_parent)) {
+ mutex_lock_nested(&new_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT);
+ mutex_lock_nested(&old_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT_2);
+ new_dir_first = true;
+ } else {
+ mutex_lock_nested(&old_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT);
+ mutex_lock_nested(&new_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT_2);
+ }
+
+ if (NInoBeingDeleted(old_ni) || NInoBeingDeleted(old_dir_ni) ||
+ (new_ni && NInoBeingDeleted(new_ni)) ||
+ (old_dir != new_dir && NInoBeingDeleted(new_dir_ni))) {
err = -ENOENT;
- goto unlock_old;
+ goto err_out;
}
is_dir = S_ISDIR(old_inode->i_mode);
if (new_inode) {
- new_ni = NTFS_I(new_inode);
- mutex_lock_nested(&new_ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL_2);
- if (old_dir != new_dir) {
- mutex_lock_nested(&new_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT_2);
- if (NInoBeingDeleted(new_dir_ni)) {
- err = -ENOENT;
- goto err_out;
- }
- }
-
- if (NInoBeingDeleted(new_ni)) {
- err = -ENOENT;
- goto err_out;
- }
-
if (is_dir) {
struct mft_record *ni_mrec;
@@ -1348,14 +1350,6 @@ static int ntfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
err = ntfs_delete(new_ni, new_dir_ni, uname_new, new_name_len, false);
if (err)
goto err_out;
- } else {
- if (old_dir != new_dir) {
- mutex_lock_nested(&new_dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT_2);
- if (NInoBeingDeleted(new_dir_ni)) {
- err = -ENOENT;
- goto err_out;
- }
- }
}
err = __ntfs_link(old_ni, new_dir_ni, uname_new, new_name_len);
@@ -1386,13 +1380,17 @@ static int ntfs_rename(struct mnt_idmap *idmap, struct inode *old_dir,
inode_inc_iversion(new_dir);
err_out:
- if (old_dir != new_dir)
+ if (old_dir == new_dir) {
+ mutex_unlock(&old_dir_ni->mrec_lock);
+ } else if (new_dir_first) {
+ mutex_unlock(&old_dir_ni->mrec_lock);
mutex_unlock(&new_dir_ni->mrec_lock);
- if (new_inode)
+ } else {
+ mutex_unlock(&new_dir_ni->mrec_lock);
+ mutex_unlock(&old_dir_ni->mrec_lock);
+ }
+ if (new_ni)
mutex_unlock(&new_ni->mrec_lock);
-
-unlock_old:
- mutex_unlock(&old_dir_ni->mrec_lock);
mutex_unlock(&old_ni->mrec_lock);
if (uname_new)
kmem_cache_free(ntfs_name_cache, uname_new);
base-commit: 1a3746ccbb0a97bed3c06ccde6b880013b1dddc1
--
2.43.0