[PATCH] ntfs: serialize read iomap lookup with mrec_lock

From: Cen Zhang

Date: Thu Jun 25 2026 - 21:49:39 EST


Take the base inode's mrec_lock in the shared read/seek iomap begin
wrapper so both resident and non-resident lookups run under the same
serialization already used by the write-side metadata path.

The buggy scenario involves two paths, with each column showing the order
within that path:

read/seek iomap lookup path:
1. Enter __ntfs_read_iomap_begin().
2. Enter ntfs_attr_lookup() / ntfs_external_attr_find() on
base_ni->attr_list.
3. Keep raw attr_list_entry cursors while mapping the extent.

attr-list mutation path:
1. Enter ntfs_attrlist_entry_add() or ntfs_attrlist_entry_rm().
2. Publish the replacement base_ni->attr_list.
3. kvfree() the old buffer.

Validation reproduced this kernel report:
BUG: KASAN: slab-use-after-free in ntfs_attr_lookup+0x24b2/0x2bf0

Call Trace:
<TASK>
dump_stack_lvl+0x66/0xa0
print_report+0xce/0x630
? ntfs_attr_lookup+0x24b2/0x2bf0
? srso_alias_return_thunk+0x5/0xfbef5
? __virt_addr_valid+0x20d/0x410
? ntfs_attr_lookup+0x24b2/0x2bf0
kasan_report+0xe0/0x110
? ntfs_attr_lookup+0x24b2/0x2bf0
ntfs_attr_lookup+0x24b2/0x2bf0
? ntfs_attr_lookup+0x9/0x2bf0
? srso_alias_return_thunk+0x5/0xfbef5
? 0xffffffffc00000da
? __pfx_ntfs_attr_lookup+0x10/0x10
ntfs_attr_map_whole_runlist+0x488/0x8b0
? __pfx_ntfs_attr_map_whole_runlist+0x10/0x10
? __pfx_do_raw_spin_trylock+0x10/0x10
ntfs_attr_vcn_to_rl+0x13a/0x1d0
? filemap_fault+0xf03/0x2080
ntfs_read_iomap_begin_non_resident+0x142/0x920
? __pfx_ntfs_read_iomap_begin_non_resident+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
? __pfx_ntfs_read_iomap_begin+0x10/0x10
iomap_iter+0x6de/0x11e0
? srso_alias_return_thunk+0x5/0xfbef5
? prepare_alloc_pages+0x1c4/0x4d0
iomap_readahead+0x239/0x910
? folios_put_refs+0x1ed/0x4f0
? __pfx_iomap_readahead+0x10/0x10
? __pfx_folios_put_refs+0x10/0x10
? lock_release+0x1e0/0x280
? srso_alias_return_thunk+0x5/0xfbef5
? xas_store+0x871/0x1650
? xas_find_conflict+0x64b/0xb90
? srso_alias_return_thunk+0x5/0xfbef5
? lock_release+0x1e0/0x280
ntfs_readahead+0x193/0x1d0
? __pfx_ntfs_readahead+0x10/0x10
? __filemap_add_folio+0x869/0xa60
? srso_alias_return_thunk+0x5/0xfbef5
? lock_acquire+0x2b8/0x2f0
? __pfx___filemap_add_folio+0x10/0x10
read_pages+0x18f/0x890
? srso_alias_return_thunk+0x5/0xfbef5
? folio_alloc_noprof+0x69/0xb0
? __pfx_read_pages+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
page_cache_ra_unbounded+0x389/0x6c0
do_page_cache_ra+0x109/0x170
filemap_fault+0xf03/0x2080
? __pfx_filemap_fault+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
? lock_acquire+0x2b8/0x2f0
? lockdep_init_map_type+0x5c/0x230
__do_fault+0xf0/0x260
__handle_mm_fault+0xdcf/0x1ca0
? __pfx___handle_mm_fault+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
? srso_alias_return_thunk+0x5/0xfbef5
? lock_release+0x1e0/0x280
handle_mm_fault+0x19c/0x470
do_user_addr_fault+0x23b/0x9c0
exc_page_fault+0x5c/0xc0
asm_exc_page_fault+0x26/0x30

Allocated by task 511:
kasan_save_stack+0x33/0x60
kasan_save_track+0x14/0x30
__kasan_kmalloc+0xaa/0xb0
__kvmalloc_node_noprof+0x353/0x920
ntfs_attrlist_entry_rm+0x28d/0x510
ntfs_attr_record_rm+0x4dd/0xda0
ntfs_delete+0x50a/0xf90
ntfs_unlink+0x212/0x4d0
vfs_unlink+0x259/0xb00
filename_unlinkat+0x2db/0x5f0
__x64_sys_unlink+0x46/0x70
do_syscall_64+0x115/0x6a0
entry_SYSCALL_64_after_hwframe+0x77/0x7f

Freed by task 511:
kasan_save_stack+0x33/0x60
kasan_save_track+0x14/0x30
kasan_save_free_info+0x3b/0x60
__kasan_slab_free+0x5f/0x80
kfree+0x307/0x580
ntfs_attrlist_entry_add+0xc1b/0x1110
ntfs_resident_attr_record_add+0x6a8/0xa10
ntfs_attr_add+0x682/0xcd0
__ntfs_link+0x815/0xde0
ntfs_link+0x223/0x3d0
vfs_link+0x465/0xcf0
filename_linkat+0x316/0x540
__x64_sys_link+0x81/0xb0
do_syscall_64+0x115/0x6a0
entry_SYSCALL_64_after_hwframe+0x77/0x7f

Fixes: b041ca562526 ("ntfs: update iomap and address space operations")
Assisted-by: Codex:gpt-5.5
Signed-off-by: Cen Zhang <zzzccc427@xxxxxxxxx>
---
fs/ntfs/iomap.c | 25 ++++++++++++++++++++-----
1 file changed, 20 insertions(+), 5 deletions(-)

diff --git a/fs/ntfs/iomap.c b/fs/ntfs/iomap.c
index 52eecf5cb256..5f15a0432f0c 100644
--- a/fs/ntfs/iomap.c
+++ b/fs/ntfs/iomap.c
@@ -263,11 +263,26 @@ static int __ntfs_read_iomap_begin(struct inode *inode, loff_t offset, loff_t le
unsigned int flags, struct iomap *iomap, struct iomap *srcmap,
bool need_unwritten)
{
- if (NInoNonResident(NTFS_I(inode)))
- return ntfs_read_iomap_begin_non_resident(inode, offset, length,
- flags, iomap, need_unwritten);
- return ntfs_read_iomap_begin_resident(inode, offset, length,
- flags, iomap);
+ struct ntfs_inode *ni = NTFS_I(inode);
+ struct ntfs_inode *base_ni = NInoAttr(ni) ? ni->ext.base_ntfs_ino : ni;
+ bool lock_mrec = NInoAttrList(base_ni);
+ int err;
+
+ if (lock_mrec)
+ mutex_lock(&base_ni->mrec_lock);
+
+ if (NInoNonResident(ni))
+ err = ntfs_read_iomap_begin_non_resident(inode, offset, length,
+ flags, iomap,
+ need_unwritten);
+ else
+ err = ntfs_read_iomap_begin_resident(inode, offset, length,
+ flags, iomap);
+
+ if (lock_mrec)
+ mutex_unlock(&base_ni->mrec_lock);
+
+ return err;
}

static int ntfs_read_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
--
2.43.0