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

From: Yun Zhou

Date: Tue Jun 30 2026 - 21:31:31 EST


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: syzbot+7736960b837908f3a81d@xxxxxxxxxxxxxxxxxxxxxxxxx
Reported-by: syzbot+262a71e9d2faf8747085@xxxxxxxxxxxxxxxxxxxxxxxxx
Reported-by: syzbot+cfc6e8106bec1c524f7d@xxxxxxxxxxxxxxxxxxxxxxxxx
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 <yun.zhou@xxxxxxxxxxxxx>
---
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