[PATCH 2/2] ntfs3: release ni_lock before dir_emit in ntfs_readdir to fix deadlocks

Yun Zhou <[email protected]> Wed, 1 Jul 2026 09:30:49 +0800
Newsgroups dev.linux.lists.ntfs3,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
ntfs_readdir() holds ni_lock while calling ntfs_read_hdr(), which calls
dir_emit() to copy directory entries to userspace.  If the user buffer
triggers a page fault, the fault handler may need mmap_lock or
mapping->invalidate_lock, creating lock-order inversions with paths
that acquire those locks before ni_lock (e.g. ntfs_fallocate, mmap).

Fix by releasing ni_lock before calling ntfs_read_hdr():

- For non-root index nodes, indx_read_ra() returns data in a kmalloc'd
  buffer (node->index) that remains valid after ni_unlock.

- For the root index header, the data lives in the main MFT record
  whose lifetime is bound to the inode.  i_rwsem shared prevents
  concurrent directory modifications, so it is safe to access after
  ni_unlock.

Reported-by: [email protected]
Reported-by: [email protected]
Reported-by: [email protected]
Closes: https://syzkaller.appspot.com/bug?extid=7736960b837908f3a81d
Closes: https://syzkaller.appspot.com/bug?extid=262a71e9d2faf8747085
Closes: https://syzkaller.appspot.com/bug?extid=cfc6e8106bec1c524f7d
Fixes: d62cf685d12e ("fs/ntfs3: hold ni_lock across readdir metadata walk")
Signed-off-by: Yun Zhou <[email protected]>
---
 fs/ntfs3/dir.c | 22 ++++++++++++++++++++--
 1 file changed, 20 insertions(+), 2 deletions(-)

diff --git a/fs/ntfs3/dir.c b/fs/ntfs3/dir.c
index 07de564a2c47..65883409689a 100644
--- a/fs/ntfs3/dir.c
+++ b/fs/ntfs3/dir.c
@@ -491,12 +491,22 @@ static int ntfs_readdir(struct file *file, struct dir_context *ctx)
 	if (pos >= sbi->record_size) {
 		bit = (pos - sbi->record_size) >> index_bits;
 	} else {
+		/*
+		 * Release ni_lock before dir_emit (which may fault into
+		 * user pages).  root->ihdr lives in the main MFT record
+		 * whose lifetime is bound to the inode, and i_rwsem shared
+		 * prevents concurrent directory modifications.
+		 */
+		ni_unlock(ni);
+
 		/*
 		 * Add each name from root in 'ctx'.
 		 */
 		err = ntfs_read_hdr(sbi, ni, &root->ihdr, 0, pos, name, ctx);
 		if (err)
-			goto out_unlock;
+			goto out;
+
+		ni_lock(ni);
 		bit = 0;
 	}
 
@@ -525,6 +535,12 @@ static int ntfs_readdir(struct file *file, struct dir_context *ctx)
 		if (err)
 			goto out_unlock;
 
+		/*
+		 * node->index is a kmalloc'd buffer, safe after ni_unlock.
+		 * Release lock before dir_emit to avoid lock-order issues.
+		 */
+		ni_unlock(ni);
+
 		/*
 		 * Add each name from index in 'ctx'.
 		 */
@@ -532,7 +548,9 @@ static int ntfs_readdir(struct file *file, struct dir_context *ctx)
 				    ((u64)bit << index_bits) + sbi->record_size,
 				    pos, name, ctx);
 		if (err)
-			goto out_unlock;
+			goto out;
+
+		ni_lock(ni);
 	}
 
 out_unlock:
-- 
2.43.0