[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