mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Hyunchul Lee <hyc.lee@gmail.com>
To: Cen Zhang <zzzccc427@gmail.com>
Cc: Namjae Jeon <linkinjeon@kernel.org>,
	linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org,
	baijiaju1990@gmail.com
Subject: Re: [PATCH v3] ntfs: serialize attribute-list replacement with lookups
Date: Wed, 1 Jul 2026 17:15:25 +0900	[thread overview]
Message-ID: <akTMnTvQBvOIrNr6@hyunchul-PC02> (raw)
In-Reply-To: <20260629045753.2087750-1-zzzccc427@gmail.com>

Hi Cen,

Thank you for the patch, but it has conflicts and needs to be rebased onto
ntfs-for-7.2-rc1 from the repository below:

git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/ntfs.git

I left a few comments below.

On Mon, Jun 29, 2026 at 12:57:53PM +0800, Cen Zhang wrote:
> ntfs_external_attr_find() walks base_ni->attr_list with raw
> attr_list_entry cursors stored in ctx->al_entry and local variables.  Other
> metadata paths can replace base_ni->attr_list and free the old buffer while
> a lookup still carries one of those cursors.
> 
> The buggy scenario involves two paths, with each column showing the order
> within that path:
> 
>   attribute lookup path:              attribute-list mutation path:
>   1. enters ntfs_attr_lookup()        1. enters an attr-list add/remove
>   2. reads base_ni->attr_list         2. builds a replacement buffer
>   3. keeps ctx->al_entry/al_entry     3. publishes base_ni->attr_list
>      pointing into that buffer        4. frees the previous buffer
>   4. continues the attribute search
>   5. dereferences the saved cursor
> 
> 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
> 
> Add a per-ntfs_inode attr_list_lock and couple it to
> ntfs_attr_search_ctx.  Search contexts that may carry attr-list cursors
> take the read side for their full lifetime, so ctx->al_entry remains
> protected until ntfs_attr_put_search_ctx().  Attribute-list mutation paths
> use the write side while deriving offsets from the old list and publishing
> the replacement list.  Contexts used under runlist.lock use an explicit
> NONE mode so the ordering stays:
> 
>   mrec_lock => attr_list_lock => runlist.lock
> 
> Fixes: 495e90fa3348 ("ntfs: update attrib operations")
> Assisted-by: Codex:gpt-5.5
> Signed-off-by: Cen Zhang <zzzccc427@gmail.com>
> ---
> v3:
> - Keep attr_list_lock tied to ntfs_attr_search_ctx lifetime instead of
>   taking it only around ntfs_attr_lookup().
> - Add nolock helpers for paths that already hold attr_list_lock before
>   runlist.lock, preserving the mrec_lock => attr_list_lock => runlist.lock
>   order.
> - Protect the remaining direct attr_list walker in
>   ntfs_inode_attach_all_extents().
> 
>  fs/ntfs/aops.c     |   3 +
>  fs/ntfs/attrib.c   | 287 ++++++++++++++++++++++++++++++++++++---------
>  fs/ntfs/attrib.h   |  27 ++++-
>  fs/ntfs/attrlist.c |  30 +++--
>  fs/ntfs/compress.c |   6 +-
>  fs/ntfs/dir.c      |   8 +-
>  fs/ntfs/ea.c       |   2 +-
>  fs/ntfs/file.c     |  13 +-
>  fs/ntfs/index.c    |   5 +-
>  fs/ntfs/inode.c    |  88 +++++++++++---
>  fs/ntfs/inode.h    |   7 ++
>  fs/ntfs/iomap.c    |  16 ++-
>  fs/ntfs/mft.c      |  63 +++++++---
>  fs/ntfs/namei.c    |   6 +-
>  fs/ntfs/super.c    |   6 +-
>  15 files changed, 445 insertions(+), 122 deletions(-)
> 
> diff --git a/fs/ntfs/aops.c b/fs/ntfs/aops.c
> index 1fbf832ad1654..981777ff24279 100644
> --- a/fs/ntfs/aops.c
> +++ b/fs/ntfs/aops.c
> @@ -131,6 +131,7 @@ static sector_t ntfs_bmap(struct address_space *mapping, sector_t block)
>  	s64 lcn;
>  	unsigned long blocksize, flags;
>  	struct ntfs_inode *ni = NTFS_I(mapping->host);
> +	struct ntfs_inode *attr_lock_ni;
>  	struct ntfs_volume *vol = ni->vol;
>  	unsigned int delta;
>  	unsigned char blocksize_bits;
> @@ -160,10 +161,12 @@ static sector_t ntfs_bmap(struct address_space *mapping, sector_t block)
>  	 */
>  	if (unlikely(ofs >= size || (ofs + blocksize > size && size < i_size)))
>  		goto hole;
> +	attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_READ);
>  	down_read(&ni->runlist.lock);
>  	lcn = ntfs_attr_vcn_to_lcn_nolock(ni, ntfs_bytes_to_cluster(vol, ofs),
>  			false);
>  	up_read(&ni->runlist.lock);
> +	ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_READ);
>  	if (unlikely(lcn < LCN_HOLE)) {
>  		/*
>  		 * Step down to an integer to avoid gcc doing a long long
> diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c
> index 97b660eaa00c1..21662be1594ff 100644
> --- a/fs/ntfs/attrib.c
> +++ b/fs/ntfs/attrib.c
> @@ -107,7 +107,7 @@ int ntfs_map_runlist_nolock(struct ntfs_inode *ni, s64 vcn, struct ntfs_attr_sea
>  		m = map_mft_record(base_ni);
>  		if (IS_ERR(m))
>  			return PTR_ERR(m);
> -		ctx = ntfs_attr_get_search_ctx(base_ni, m);
> +		ctx = ntfs_attr_get_search_ctx(base_ni, m, NTFS_ATTR_CTX_LOCK_NONE);
>  		if (unlikely(!ctx)) {
>  			err = -ENOMEM;
>  			goto err_out;
> @@ -299,14 +299,17 @@ int ntfs_map_runlist_nolock(struct ntfs_inode *ni, s64 vcn, struct ntfs_attr_sea
>   */
>  int ntfs_map_runlist(struct ntfs_inode *ni, s64 vcn)
>  {
> +	struct ntfs_inode *attr_lock_ni;
>  	int err = 0;
>  
> +	attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_READ);
>  	down_write(&ni->runlist.lock);
>  	/* Make sure someone else didn't do the work while we were sleeping. */
>  	if (likely(ntfs_rl_vcn_to_lcn(ni->runlist.rl, vcn) <=
>  			LCN_RL_NOT_MAPPED))
>  		err = ntfs_map_runlist_nolock(ni, vcn, NULL);
>  	up_write(&ni->runlist.lock);
> +	ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_READ);
>  	return err;
>  }
>  
> @@ -317,7 +320,7 @@ struct runlist_element *ntfs_attr_vcn_to_rl(struct ntfs_inode *ni, s64 vcn, s64
>  	bool is_retry = false;
>  
>  	if (!rl) {
> -		err = ntfs_attr_map_whole_runlist(ni);
> +		err = ntfs_attr_map_whole_runlist_nolock(ni);
>  		if (err)
>  			return ERR_PTR(-ENOENT);
>  		rl = ni->runlist.rl;
> @@ -1436,6 +1439,81 @@ int ntfs_attr_lookup(const __le32 type, const __le16 *name,
>  			val, val_len, ctx);
>  }
>  
> +static struct ntfs_inode *ntfs_attr_search_lock_inode(struct ntfs_inode *ni)
> +{
> +	if (!ni)
> +		return NULL;
> +	if (NInoAttr(ni)) {
> +		if (ni->type == AT_ATTRIBUTE_LIST)
> +			return NULL;
> +		return ni->ext.base_ntfs_ino;
> +	}
> +	return ni;
> +}
> +
> +struct ntfs_inode *ntfs_attr_list_lock(struct ntfs_inode *ni,
> +				       enum ntfs_attr_search_ctx_lock_mode lock_mode)
> +{
> +	struct ntfs_inode *lock_ni;
> +
> +	if (lock_mode == NTFS_ATTR_CTX_LOCK_NONE)
> +		return NULL;

For the NONE case, it would be better to call rwsem_assert_held().

> +
> +	lock_ni = ntfs_attr_search_lock_inode(ni);
> +	if (!lock_ni)
> +		return NULL;
> +
> +	if (lock_mode == NTFS_ATTR_CTX_LOCK_WRITE)
> +		down_write(&lock_ni->attr_list_lock);
> +	else
> +		down_read(&lock_ni->attr_list_lock);
> +
> +	return lock_ni;
> +}
> +
> +void ntfs_attr_list_unlock(struct ntfs_inode *lock_ni,
> +			   enum ntfs_attr_search_ctx_lock_mode lock_mode)
> +{
> +	if (!lock_ni || lock_mode == NTFS_ATTR_CTX_LOCK_NONE)
> +		return;
> +
> +	if (lock_mode == NTFS_ATTR_CTX_LOCK_WRITE)
> +		up_write(&lock_ni->attr_list_lock);
> +	else
> +		up_read(&lock_ni->attr_list_lock);
> +}
> +
> +void ntfs_attr_lock_search_ctx(struct ntfs_attr_search_ctx *ctx,
> +			       struct ntfs_inode *ni,
> +			       enum ntfs_attr_search_ctx_lock_mode lock_mode)
> +{
> +	struct ntfs_inode *lock_ni;
> +
> +	ctx->attr_list_lock_ni = NULL;
> +	ctx->attr_list_lock_mode = NTFS_ATTR_CTX_LOCK_NONE;
> +
> +	if (lock_mode == NTFS_ATTR_CTX_LOCK_NONE)
> +		return;

As mentioned above, a call to rwsem_assert_held() would be better.

> +
> +	lock_ni = ntfs_attr_list_lock(ni, lock_mode);
> +	if (!lock_ni)
> +		return;
> +
> +	ctx->attr_list_lock_ni = lock_ni;
> +	ctx->attr_list_lock_mode = lock_mode;
> +}
> +
> +void ntfs_attr_unlock_search_ctx(struct ntfs_attr_search_ctx *ctx)
> +{
> +	if (!ctx->attr_list_lock_ni)
> +		return;
> +
> +	ntfs_attr_list_unlock(ctx->attr_list_lock_ni, ctx->attr_list_lock_mode);
> +
> +	ctx->attr_list_lock_ni = NULL;
> +	ctx->attr_list_lock_mode = NTFS_ATTR_CTX_LOCK_NONE;
> +}
> +
>  /**
>   * ntfs_attr_init_search_ctx - initialize an attribute search context
>   * @ctx:        attribute search context to initialize
> @@ -1466,6 +1544,8 @@ static bool ntfs_attr_init_search_ctx(struct ntfs_attr_search_ctx *ctx,
>  	ctx->base_mrec = NULL;
>  	ctx->base_attr = NULL;
>  	ctx->mapped_base_mrec = false;
> +	ctx->attr_list_lock_ni = NULL;
> +	ctx->attr_list_lock_mode = NTFS_ATTR_CTX_LOCK_NONE;
>  	return true;
>  }
>  
> @@ -1481,6 +1561,9 @@ static bool ntfs_attr_init_search_ctx(struct ntfs_attr_search_ctx *ctx,
>   */
>  void ntfs_attr_reinit_search_ctx(struct ntfs_attr_search_ctx *ctx)
>  {
> +	struct ntfs_inode *attr_list_lock_ni = ctx->attr_list_lock_ni;
> +	enum ntfs_attr_search_ctx_lock_mode attr_list_lock_mode =
> +		ctx->attr_list_lock_mode;
>  	bool mapped_mrec;
>  
>  	if (likely(!ctx->base_ntfs_ino)) {
> @@ -1502,6 +1585,8 @@ void ntfs_attr_reinit_search_ctx(struct ntfs_attr_search_ctx *ctx)
>  	mapped_mrec = ctx->mapped_base_mrec;
>  	ntfs_attr_init_search_ctx(ctx, ctx->base_ntfs_ino, ctx->base_mrec);
>  	ctx->mapped_mrec = mapped_mrec;
> +	ctx->attr_list_lock_ni = attr_list_lock_ni;
> +	ctx->attr_list_lock_mode = attr_list_lock_mode;
>  }
>  
>  /*
> @@ -1513,7 +1598,8 @@ void ntfs_attr_reinit_search_ctx(struct ntfs_attr_search_ctx *ctx)
>   * and return it. Return NULL if allocation failed.
>   */
>  struct ntfs_attr_search_ctx *ntfs_attr_get_search_ctx(struct ntfs_inode *ni,
> -		struct mft_record *mrec)
> +		struct mft_record *mrec,
> +		enum ntfs_attr_search_ctx_lock_mode lock_mode)
>  {
>  	struct ntfs_attr_search_ctx *ctx;
>  	bool init;
> @@ -1524,6 +1610,8 @@ struct ntfs_attr_search_ctx *ntfs_attr_get_search_ctx(struct ntfs_inode *ni,
>  		if (init == false) {
>  			kmem_cache_free(ntfs_attr_ctx_cache, ctx);
>  			ctx = NULL;
> +		} else {
> +			ntfs_attr_lock_search_ctx(ctx, ni, lock_mode);
>  		}
>  	}
>  
> @@ -1539,6 +1627,8 @@ struct ntfs_attr_search_ctx *ntfs_attr_get_search_ctx(struct ntfs_inode *ni,
>   */
>  void ntfs_attr_put_search_ctx(struct ntfs_attr_search_ctx *ctx)
>  {
> +	ntfs_attr_unlock_search_ctx(ctx);
> +
>  	if (ctx->mapped_mrec)
>  		unmap_mft_record(ctx->ntfs_ino);
>  
> @@ -1808,7 +1898,7 @@ int ntfs_attr_make_non_resident(struct ntfs_inode *ni, const u32 data_size)
>  		ctx = NULL;
>  		goto err_out;
>  	}
> -	ctx = ntfs_attr_get_search_ctx(base_ni, m);
> +	ctx = ntfs_attr_get_search_ctx(base_ni, m, NTFS_ATTR_CTX_LOCK_READ);
>  	if (unlikely(!ctx)) {
>  		err = -ENOMEM;
>  		goto err_out;
> @@ -2147,7 +2237,7 @@ int ntfs_attr_set_initialized_size(struct ntfs_inode *ni, loff_t new_size)
>  	if (!NInoNonResident(ni))
>  		return -EINVAL;
>  
> -	ctx = ntfs_attr_get_search_ctx(ni, NULL);
> +	ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
>  	if (!ctx)
>  		return -ENOMEM;
>  
> @@ -2247,7 +2337,7 @@ int ntfs_resident_attr_record_add(struct ntfs_inode *ni, __le32 type,
>  	}
>  
>  	/* Locate place where record should be. */
> -	ctx = ntfs_attr_get_search_ctx(ni, NULL);
> +	ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_NONE);
>  	if (!ctx) {
>  		ntfs_error(ni->vol->sb, "%s: Failed to get search context",
>  				__func__);
> @@ -2369,7 +2459,7 @@ static int ntfs_non_resident_attr_record_add(struct ntfs_inode *ni, __le32 type,
>  	}
>  
>  	/* Locate place where record should be. */
> -	ctx = ntfs_attr_get_search_ctx(ni, NULL);
> +	ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_NONE);
>  	if (!ctx) {
>  		pr_err("%s: Failed to get search context\n", __func__);
>  		return -ENOMEM;
> @@ -2479,6 +2569,7 @@ static int ntfs_non_resident_attr_record_add(struct ntfs_inode *ni, __le32 type,
>  int ntfs_attr_record_rm(struct ntfs_attr_search_ctx *ctx)
>  {
>  	struct ntfs_inode *base_ni, *ni;
> +	u8 *old_al = NULL;
>  	__le32 type;
>  	int err;
>  
> @@ -2517,9 +2608,11 @@ int ntfs_attr_record_rm(struct ntfs_attr_search_ctx *ctx)
>  	/* Post $ATTRIBUTE_LIST delete setup. */
>  	if (type == AT_ATTRIBUTE_LIST) {
>  		if (NInoAttrList(base_ni) && base_ni->attr_list)
> -			kvfree(base_ni->attr_list);
> +			old_al = base_ni->attr_list;
>  		base_ni->attr_list = NULL;
> +		base_ni->attr_list_size = 0;
>  		NInoClearAttrList(base_ni);
> +		kvfree(old_al);
>  	}
>  
>  	/* Free MFT record, if it doesn't contain attributes. */
> @@ -2944,7 +3037,7 @@ int ntfs_attr_open(struct ntfs_inode *ni, const __le32 type,
>  		newname = name;
>  	}
>  
> -	ctx = ntfs_attr_get_search_ctx(base_ni, NULL);
> +	ctx = ntfs_attr_get_search_ctx(base_ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
>  	if (!ctx) {
>  		err = -ENOMEM;
>  		pr_err("%s: Failed to get search context\n", __func__);
> @@ -3096,7 +3189,7 @@ void ntfs_attr_close(struct ntfs_inode *ni)
>   * will map the runlist fragments from each of the extents thus giving access
>   * to the entirety of the disk allocation of an attribute.
>   */
> -int ntfs_attr_map_whole_runlist(struct ntfs_inode *ni)
> +int ntfs_attr_map_whole_runlist_nolock(struct ntfs_inode *ni)
>  {
>  	s64 next_vcn, last_vcn, highest_vcn;
>  	struct ntfs_attr_search_ctx *ctx;
> @@ -3119,7 +3212,7 @@ int ntfs_attr_map_whole_runlist(struct ntfs_inode *ni)
>  	else
>  		base_ni = ni;
>  
> -	ctx = ntfs_attr_get_search_ctx(base_ni, NULL);
> +	ctx = ntfs_attr_get_search_ctx(base_ni, NULL, NTFS_ATTR_CTX_LOCK_NONE);
>  	if (!ctx) {
>  		ntfs_error(sb, "%s: Failed to get search context", __func__);
>  		return -ENOMEM;
> @@ -3211,6 +3304,18 @@ int ntfs_attr_map_whole_runlist(struct ntfs_inode *ni)
>  	return err;
>  }
>  
> +int ntfs_attr_map_whole_runlist(struct ntfs_inode *ni)
> +{
> +	struct ntfs_inode *attr_lock_ni;
> +	int err;
> +
> +	attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_READ);
> +	err = ntfs_attr_map_whole_runlist_nolock(ni);
> +	ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_READ);
> +
> +	return err;
> +}
> +
>  /*
>   * ntfs_attr_record_move_to - move attribute record to target inode
>   * @ctx:	attribute search context describing the attribute record
> @@ -3245,7 +3350,7 @@ int ntfs_attr_record_move_to(struct ntfs_attr_search_ctx *ctx, struct ntfs_inode
>  
>  	/* Find place in MFT record where attribute will be moved. */
>  	a = ctx->attr;
> -	nctx = ntfs_attr_get_search_ctx(ni, NULL);
> +	nctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_NONE);
>  	if (!nctx) {
>  		ntfs_error(sb, "%s: Failed to get search context", __func__);
>  		return -ENOMEM;
> @@ -3332,7 +3437,7 @@ int ntfs_attr_record_move_away(struct ntfs_attr_search_ctx *ctx, int extra)
>  		return -EINVAL;
>  	}
>  
> -	err = ntfs_inode_attach_all_extents(ctx->ntfs_ino);
> +	err = ntfs_inode_attach_all_extents_nolock(ctx->ntfs_ino);
>  	if (err) {
>  		ntfs_error(sb, "Couldn't attach extents, inode=%llu",
>  			(unsigned long long)base_ni->mft_no);
> @@ -3431,9 +3536,8 @@ static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni,
>  		if ((le32_to_cpu(a->length) -
>  		     le16_to_cpu(a->data.non_resident.mapping_pairs_offset) == 8) &&
>  		    !(le32_to_cpu(m->bytes_allocated) - le32_to_cpu(m->bytes_in_use))) {
> -
>  			if (!NInoAttrList(base_ni)) {
> -				err = ntfs_inode_add_attrlist(base_ni);
> +				err = ntfs_inode_add_attrlist_nolock(base_ni);
>  				if (err)
>  					goto out;
>  				err = -EAGAIN;
> @@ -3534,7 +3638,8 @@ static int ntfs_attr_update_meta(struct attr_record *a, struct ntfs_inode *ni,
>   * call to this function. Vice-versa @na->compressed_size will be calculated and
>   * set to correct value during this function.
>   */
> -int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn)
> +static int __ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn,
> +					    enum ntfs_attr_search_ctx_lock_mode lock_mode)

All callers pass NONE, so is the lock_mode still necessary?

>  {
>  	struct ntfs_attr_search_ctx *ctx;
>  	struct ntfs_inode *base_ni;
> @@ -3566,7 +3671,7 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn)
>  	else
>  		base_ni = ni;
>  
> -	ctx = ntfs_attr_get_search_ctx(base_ni, NULL);
> +	ctx = ntfs_attr_get_search_ctx(base_ni, NULL, lock_mode);
>  	if (!ctx) {
>  		ntfs_error(sb, "%s: Failed to get search context", __func__);
>  		return -ENOMEM;
> @@ -3666,8 +3771,8 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn)
>  			 */
>  			if (ni->type == AT_ATTRIBUTE_LIST) {
>  				ntfs_attr_put_search_ctx(ctx);
> -				if (ntfs_inode_free_space(base_ni, mp_size -
> -							cur_max_mp_size)) {
> +				if (ntfs_inode_free_space_nolock(base_ni, mp_size -
> +								cur_max_mp_size)) {
>  					ntfs_debug("Attribute list is too big. Defragment the volume\n");
>  					return -ENOSPC;
>  				}
> @@ -3679,7 +3784,7 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn)
>  			/* Add attribute list if it isn't present, and retry. */
>  			if (!NInoAttrList(base_ni)) {
>  				ntfs_attr_put_search_ctx(ctx);
> -				if (ntfs_inode_add_attrlist(base_ni)) {
> +				if (ntfs_inode_add_attrlist_nolock(base_ni)) {
>  					ntfs_error(sb, "Can not add attrlist");
>  					return -EIO;
>  				}
> @@ -3883,6 +3988,24 @@ int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn)
>  	return err;
>  }
>  
> +int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn)
> +{
> +	struct ntfs_inode *attr_lock_ni;
> +	int err;
> +
> +	attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_WRITE);
> +	err = __ntfs_attr_update_mapping_pairs(ni, from_vcn, NTFS_ATTR_CTX_LOCK_NONE);
> +	ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> +
> +	return err;
> +}
> +
> +int ntfs_attr_update_mapping_pairs_nolock(struct ntfs_inode *ni, s64 from_vcn)
> +{
> +	return __ntfs_attr_update_mapping_pairs(ni, from_vcn,
> +			NTFS_ATTR_CTX_LOCK_NONE);
> +}
> +
>  /*
>   * ntfs_attr_make_resident - convert a non-resident to a resident attribute
>   * @ni:		open ntfs attribute to make resident
> @@ -3940,7 +4063,7 @@ static int ntfs_attr_make_resident(struct ntfs_inode *ni, struct ntfs_attr_searc
>  	}
>  
>  	/* Read and cache the whole runlist if not already done. */
> -	err = ntfs_attr_map_whole_runlist(ni);
> +	err = ntfs_attr_map_whole_runlist_nolock(ni);
>  	if (err)
>  		return err;
>  
> @@ -4077,7 +4200,7 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz
>  			return err;
>  		}
>  
> -		ctx = ntfs_attr_get_search_ctx(ni, NULL);
> +		ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
>  		if (!ctx) {
>  			ntfs_error(vol->sb, "%s: Failed to get search context", __func__);
>  			return -ENOMEM;
> @@ -4125,7 +4248,7 @@ static int ntfs_non_resident_attr_shrink(struct ntfs_inode *ni, const s64 newsiz
>  	}
>  
>  	/* Get the first attribute record. */
> -	ctx = ntfs_attr_get_search_ctx(base_ni, NULL);
> +	ctx = ntfs_attr_get_search_ctx(base_ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
>  	if (!ctx) {
>  		ntfs_error(vol->sb, "%s: Failed to get search context", __func__);
>  		return -ENOMEM;
> @@ -4237,7 +4360,8 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz
>  	 * clusters if there is a change.
>  	 */
>  	if (ntfs_bytes_to_cluster(vol, ni->allocated_size) < first_free_vcn) {
> -		err = ntfs_attr_map_whole_runlist(ni);
> +		err = need_lock ? ntfs_attr_map_whole_runlist(ni) :
> +				ntfs_attr_map_whole_runlist_nolock(ni);
>  		if (err) {
>  			ntfs_error(sb, "ntfs_attr_map_whole_runlist failed");
>  			return err;
> @@ -4356,14 +4480,15 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz
>  
>  		/* Prepare to mapping pairs update. */
>  		ni->allocated_size = ntfs_cluster_to_bytes(vol, first_free_vcn);
> -		err = ntfs_attr_update_mapping_pairs(ni, 0);
> +		err = need_lock ? ntfs_attr_update_mapping_pairs(ni, 0) :
> +				ntfs_attr_update_mapping_pairs_nolock(ni, 0);
>  		if (err) {
>  			ntfs_debug("Mapping pairs update failed");
>  			goto rollback;
>  		}
>  	}
>  
> -	ctx = ntfs_attr_get_search_ctx(base_ni, NULL);
> +	ctx = ntfs_attr_get_search_ctx(base_ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
>  	if (!ctx) {
>  		err = -ENOMEM;
>  		if (ni->allocated_size == org_alloc_size)
> @@ -4414,16 +4539,22 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz
>  		kvfree(ni->runlist.rl);
>  		ni->runlist.rl = NULL;
>  		ntfs_error(sb, "Couldn't truncate runlist. Rollback failed");
> -	} else {
> -		/* Prepare to mapping pairs update. */
> -		ni->allocated_size = org_alloc_size;
> -		/* Restore mapping pairs. */
> -		if (need_lock)
> -			down_read(&ni->runlist.lock);
> -		if (ntfs_attr_update_mapping_pairs(ni, 0))
> -			ntfs_error(sb, "Failed to restore old mapping pairs");
> -		if (need_lock)
> -			up_read(&ni->runlist.lock);
> +		} else {
> +			/* Prepare to mapping pairs update. */
> +			ni->allocated_size = org_alloc_size;
> +			/* Restore mapping pairs. */
> +			if (need_lock) {
> +				struct ntfs_inode *attr_lock_ni;
> +
> +				attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_WRITE);
> +				down_read(&ni->runlist.lock);
> +				if (ntfs_attr_update_mapping_pairs_nolock(ni, 0))
> +					ntfs_error(sb, "Failed to restore old mapping pairs");
> +				up_read(&ni->runlist.lock);
> +				ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> +			} else if (ntfs_attr_update_mapping_pairs_nolock(ni, 0)) {
> +				ntfs_error(sb, "Failed to restore old mapping pairs");
> +			}
>  
>  		if (NInoSparse(ni) || NInoCompressed(ni)) {
>  			ni->itype.compressed.size =  org_compressed_size;
> @@ -4469,7 +4600,7 @@ static int ntfs_resident_attr_resize(struct ntfs_inode *attr_ni, const s64 newsi
>  		base_ni = attr_ni;
>  
>  	/* Get the attribute record that needs modification. */
> -	ctx = ntfs_attr_get_search_ctx(base_ni, NULL);
> +	ctx = ntfs_attr_get_search_ctx(base_ni, NULL, NTFS_ATTR_CTX_LOCK_READ);

attr_list_lock can be acquired recursively. this function calls
ntfs_attr_make_non_resident(), which also calls
ntfs_attr_get_search_ctx().

>  	if (!ctx) {
>  		ntfs_error(sb, "%s: Failed to get search context", __func__);
>  		return -ENOMEM;
> @@ -4698,12 +4829,16 @@ int __ntfs_attr_truncate_vfs(struct ntfs_inode *ni, const s64 newsize,
>  
>  	if (NInoNonResident(ni)) {
>  		if (newsize > i_size) {
> +			struct ntfs_inode *attr_lock_ni;
> +
> +			attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  			down_write(&ni->runlist.lock);
>  			err = ntfs_non_resident_attr_expand(ni, newsize, 0,
>  							    NVolDisableSparse(ni->vol) ?
>  							    HOLES_NO : HOLES_OK,
>  							    false);
>  			up_write(&ni->runlist.lock);
> +			ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  		} else
>  			err = ntfs_non_resident_attr_shrink(ni, newsize);
>  	} else
> @@ -4833,14 +4968,17 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start,
>  	int err = 0;
>  	size_t new_rl_count;
>  
> -	err = ntfs_attr_map_whole_runlist(ni);
> +	err = ntfs_attr_map_whole_runlist_nolock(ni);
>  	if (err)
>  		return err;
>  
> -	if (NInoAttr(ni))
> -		ctx = ntfs_attr_get_search_ctx(ni->ext.base_ntfs_ino, NULL);
> -	else
> -		ctx = ntfs_attr_get_search_ctx(ni, NULL);
> +	if (NInoAttr(ni)) {
> +		struct ntfs_inode *base_ni = ni->ext.base_ntfs_ino;
> +
> +		ctx = ntfs_attr_get_search_ctx(base_ni, NULL, NTFS_ATTR_CTX_LOCK_NONE);
> +	} else {
> +		ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_NONE);
> +	}
>  	if (!ctx) {
>  		ntfs_error(vol->sb, "%s: Failed to get search context", __func__);
>  		return -ENOMEM;
> @@ -4947,7 +5085,7 @@ int ntfs_attr_map_cluster(struct ntfs_inode *ni, s64 vcn_start, s64 *lcn_start,
>  
>  	if (update_mp) {
>  		ntfs_attr_reinit_search_ctx(ctx);
> -		err = ntfs_attr_update_mapping_pairs(ni, 0);
> +		err = ntfs_attr_update_mapping_pairs_nolock(ni, 0);
>  		if (err) {
>  			int err2;
>  
> @@ -5000,7 +5138,7 @@ int ntfs_attr_rm(struct ntfs_inode *ni)
>  		err = ntfs_attr_map_whole_runlist(ni);
>  		if (err)
>  			return err;
> -		ctx = ntfs_attr_get_search_ctx(ni, NULL);
> +		ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
>  		if (!ctx) {
>  			ntfs_error(sb, "%s: Failed to get search context", __func__);
>  			return -ENOMEM;
> @@ -5014,7 +5152,7 @@ int ntfs_attr_rm(struct ntfs_inode *ni)
>  	}
>  
>  	/* Search for attribute extents and remove them all. */
> -	ctx = ntfs_attr_get_search_ctx(base_ni, NULL);
> +	ctx = ntfs_attr_get_search_ctx(base_ni, NULL, NTFS_ATTR_CTX_LOCK_WRITE);
>  	if (!ctx) {
>  		ntfs_error(sb, "%s: Failed to get search context", __func__);
>  		return -ENOMEM;
> @@ -5046,7 +5184,7 @@ int ntfs_attr_exist(struct ntfs_inode *ni, const __le32 type, __le16 *name,
>  
>  	ntfs_debug("Entering\n");
>  
> -	ctx = ntfs_attr_get_search_ctx(ni, NULL);
> +	ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
>  	if (!ctx) {
>  		ntfs_error(ni->vol->sb, "%s: Failed to get search context",
>  				__func__);
> @@ -5155,6 +5293,7 @@ void *ntfs_attr_readall(struct ntfs_inode *ni, const __le32 type,
>  int ntfs_non_resident_attr_insert_range(struct ntfs_inode *ni, s64 start_vcn, s64 len)
>  {
>  	struct ntfs_volume *vol = ni->vol;
> +	struct ntfs_inode *attr_lock_ni;
>  	struct runlist_element *hole_rl, *rl;
>  	struct ntfs_attr_search_ctx *ctx;
>  	int ret;
> @@ -5175,16 +5314,19 @@ int ntfs_non_resident_attr_insert_range(struct ntfs_inode *ni, s64 start_vcn, s6
>  	hole_rl[1].lcn = LCN_ENOENT;
>  	hole_rl[1].length = 0;
>  
> +	attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  	down_write(&ni->runlist.lock);
> -	ret = ntfs_attr_map_whole_runlist(ni);
> +	ret = ntfs_attr_map_whole_runlist_nolock(ni);
>  	if (ret) {
>  		up_write(&ni->runlist.lock);
> +		ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  		return ret;
>  	}
>  
>  	rl = ntfs_rl_find_vcn_nolock(ni->runlist.rl, start_vcn);
>  	if (!rl) {
>  		up_write(&ni->runlist.lock);
> +		ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  		kfree(hole_rl);
>  		return -EIO;
>  	}
> @@ -5193,6 +5335,7 @@ int ntfs_non_resident_attr_insert_range(struct ntfs_inode *ni, s64 start_vcn, s6
>  				  hole_rl, 1, &new_rl_count);
>  	if (IS_ERR(rl)) {
>  		up_write(&ni->runlist.lock);
> +		ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  		kfree(hole_rl);
>  		return PTR_ERR(rl);
>  	}
> @@ -5203,12 +5346,13 @@ int ntfs_non_resident_attr_insert_range(struct ntfs_inode *ni, s64 start_vcn, s6
>  	ni->data_size += ntfs_cluster_to_bytes(vol, len);
>  	if (ntfs_cluster_to_bytes(vol, start_vcn) < ni->initialized_size)
>  		ni->initialized_size += ntfs_cluster_to_bytes(vol, len);
> -	ret = ntfs_attr_update_mapping_pairs(ni, 0);
> +	ret = ntfs_attr_update_mapping_pairs_nolock(ni, 0);
>  	up_write(&ni->runlist.lock);
> +	ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  	if (ret)
>  		return ret;
>  
> -	ctx = ntfs_attr_get_search_ctx(ni, NULL);
> +	ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
>  	if (!ctx) {
>  		ret = -ENOMEM;
>  		return ret;
> @@ -5233,6 +5377,7 @@ int ntfs_non_resident_attr_insert_range(struct ntfs_inode *ni, s64 start_vcn, s6
>  int ntfs_non_resident_attr_collapse_range(struct ntfs_inode *ni, s64 start_vcn, s64 len)
>  {
>  	struct ntfs_volume *vol = ni->vol;
> +	struct ntfs_inode *attr_lock_ni;
>  	struct runlist_element *punch_rl, *rl;
>  	struct ntfs_attr_search_ctx *ctx = NULL;
>  	s64 end_vcn;
> @@ -5247,10 +5392,12 @@ int ntfs_non_resident_attr_collapse_range(struct ntfs_inode *ni, s64 start_vcn,
>  	if (start_vcn >= end_vcn)
>  		return -EINVAL;
>  
> +	attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  	down_write(&ni->runlist.lock);
> -	ret = ntfs_attr_map_whole_runlist(ni);
> +	ret = ntfs_attr_map_whole_runlist_nolock(ni);
>  	if (ret) {
>  		up_write(&ni->runlist.lock);
> +		ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  		return ret;
>  	}
>  
> @@ -5260,6 +5407,7 @@ int ntfs_non_resident_attr_collapse_range(struct ntfs_inode *ni, s64 start_vcn,
>  	rl = ntfs_rl_find_vcn_nolock(ni->runlist.rl, start_vcn);
>  	if (!rl) {
>  		up_write(&ni->runlist.lock);
> +		ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  		return -EIO;
>  	}
>  
> @@ -5267,6 +5415,7 @@ int ntfs_non_resident_attr_collapse_range(struct ntfs_inode *ni, s64 start_vcn,
>  				    start_vcn, len, &punch_rl, &new_rl_cnt);
>  	if (IS_ERR(rl)) {
>  		up_write(&ni->runlist.lock);
> +		ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  		return PTR_ERR(rl);
>  	}
>  	ni->runlist.rl = rl;
> @@ -5288,15 +5437,17 @@ int ntfs_non_resident_attr_collapse_range(struct ntfs_inode *ni, s64 start_vcn,
>  	}
>  
>  	if (ni->allocated_size > 0) {
> -		ret = ntfs_attr_update_mapping_pairs(ni, 0);
> +		ret = ntfs_attr_update_mapping_pairs_nolock(ni, 0);
>  		if (ret) {
>  			up_write(&ni->runlist.lock);
> +			ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  			goto out_rl;
>  		}
>  	}
>  	up_write(&ni->runlist.lock);
> +	ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  
> -	ctx = ntfs_attr_get_search_ctx(ni, NULL);
> +	ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
>  	if (!ctx) {
>  		ret = -ENOMEM;
>  		goto out_rl;
> @@ -5328,6 +5479,7 @@ int ntfs_non_resident_attr_collapse_range(struct ntfs_inode *ni, s64 start_vcn,
>  int ntfs_non_resident_attr_punch_hole(struct ntfs_inode *ni, s64 start_vcn, s64 len)
>  {
>  	struct ntfs_volume *vol = ni->vol;
> +	struct ntfs_inode *attr_lock_ni;
>  	struct runlist_element *punch_rl, *rl;
>  	s64 end_vcn;
>  	int dst_cnt;
> @@ -5341,10 +5493,12 @@ int ntfs_non_resident_attr_punch_hole(struct ntfs_inode *ni, s64 start_vcn, s64
>  	if (start_vcn >= end_vcn)
>  		return -EINVAL;
>  
> +	attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  	down_write(&ni->runlist.lock);
> -	ret = ntfs_attr_map_whole_runlist(ni);
> +	ret = ntfs_attr_map_whole_runlist_nolock(ni);
>  	if (ret) {
>  		up_write(&ni->runlist.lock);
> +		ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  		return ret;
>  	}
>  
> @@ -5354,6 +5508,7 @@ int ntfs_non_resident_attr_punch_hole(struct ntfs_inode *ni, s64 start_vcn, s64
>  	rl = ntfs_rl_find_vcn_nolock(ni->runlist.rl, start_vcn);
>  	if (!rl) {
>  		up_write(&ni->runlist.lock);
> +		ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  		return -EIO;
>  	}
>  
> @@ -5361,13 +5516,15 @@ int ntfs_non_resident_attr_punch_hole(struct ntfs_inode *ni, s64 start_vcn, s64
>  				start_vcn, len, &punch_rl, &new_rl_count);
>  	if (IS_ERR(rl)) {
>  		up_write(&ni->runlist.lock);
> +		ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  		return PTR_ERR(rl);
>  	}
>  	ni->runlist.rl = rl;
>  	ni->runlist.count = new_rl_count;
>  
> -	ret = ntfs_attr_update_mapping_pairs(ni, 0);
> +	ret = ntfs_attr_update_mapping_pairs_nolock(ni, 0);
>  	up_write(&ni->runlist.lock);
> +	ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  	if (ret) {
>  		kvfree(punch_rl);
>  		return ret;
> @@ -5398,9 +5555,13 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo
>  		return -EINVAL;
>  
>  	if (NInoNonResident(ni) && !NInoFullyMapped(ni)) {
> +		struct ntfs_inode *attr_lock_ni;
> +
> +		attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_READ);
>  		down_write(&ni->runlist.lock);
> -		err = ntfs_attr_map_whole_runlist(ni);
> +		err = ntfs_attr_map_whole_runlist_nolock(ni);
>  		up_write(&ni->runlist.lock);
> +		ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_READ);
>  		if (err)
>  			return err;
>  	}
> @@ -5412,7 +5573,7 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo
>  		return PTR_ERR(mrec);
>  	}
>  
> -	ctx = ntfs_attr_get_search_ctx(ni, mrec);
> +	ctx = ntfs_attr_get_search_ctx(ni, mrec, NTFS_ATTR_CTX_LOCK_READ);
>  	if (!ctx) {
>  		err = -ENOMEM;
>  		goto out_unmap;
> @@ -5487,11 +5648,15 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo
>  			}
>  
>  			while (try_alloc_cnt > 0) {
> +				struct ntfs_inode *attr_lock_ni;
> +
>  				mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL);
> +				attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  				down_write(&ni->runlist.lock);
>  				err = ntfs_attr_map_cluster(ni, vcn, &lcn, &alloc_cnt,
>  							    try_alloc_cnt, &balloc, false, false);
>  				up_write(&ni->runlist.lock);
> +				ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  				mutex_unlock(&ni->mrec_lock);
>  				if (err)
>  					goto out;
> @@ -5517,11 +5682,15 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo
>  	/* allocate clusters outside of initialized_size */
>  	try_alloc_cnt = vcn_end - vcn;
>  	while (try_alloc_cnt > 0) {
> +		struct ntfs_inode *attr_lock_ni;
> +
>  		mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL);
> +		attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  		down_write(&ni->runlist.lock);
>  		err = ntfs_attr_map_cluster(ni, vcn, &lcn, &alloc_cnt,
>  					    try_alloc_cnt, &balloc, false, false);
>  		up_write(&ni->runlist.lock);
> +		ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  		mutex_unlock(&ni->mrec_lock);
>  		if (err || signal_pending(current))
>  			goto out;
> @@ -5532,14 +5701,18 @@ int ntfs_attr_fallocate(struct ntfs_inode *ni, loff_t start, loff_t byte_len, bo
>  	}
>  
>  	if (NInoRunlistDirty(ni)) {
> +		struct ntfs_inode *attr_lock_ni;
> +
>  		mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL);
> +		attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  		down_write(&ni->runlist.lock);
> -		err = ntfs_attr_update_mapping_pairs(ni, 0);
> +		err = ntfs_attr_update_mapping_pairs_nolock(ni, 0);
>  		if (err)
>  			ntfs_error(ni->vol->sb, "Updating mapping pairs failed");
>  		else
>  			NInoClearRunlistDirty(ni);
>  		up_write(&ni->runlist.lock);
> +		ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  		mutex_unlock(&ni->mrec_lock);
>  	}
>  	return err;
> diff --git a/fs/ntfs/attrib.h b/fs/ntfs/attrib.h
> index f7acc7986b090..b4d457b267797 100644
> --- a/fs/ntfs/attrib.h
> +++ b/fs/ntfs/attrib.h
> @@ -26,6 +26,8 @@ extern __le16 AT_UNNAMED[];
>   * @base_ntfs_ino: Base inode
>   * @mapped_base_mrec: true if @base_mrec was mapped by the search
>   * @base_attr: Base attribute record pointer
> + * @attr_list_lock_ni: Base inode whose attr_list_lock is held
> + * @attr_list_lock_mode: attr_list_lock mode held by this context
>   *
>   * Structure must be initialized to zero before the first call to one of the
>   * attribute search functions. Initialize @mrec to point to the mft record to
> @@ -39,6 +41,12 @@ extern __le16 AT_UNNAMED[];
>   * any modification of the search context, to automagically get the next
>   * matching attribute.
>   */
> +enum ntfs_attr_search_ctx_lock_mode {
> +	NTFS_ATTR_CTX_LOCK_NONE,
> +	NTFS_ATTR_CTX_LOCK_READ,
> +	NTFS_ATTR_CTX_LOCK_WRITE,
> +};
> +
>  struct ntfs_attr_search_ctx {
>  	struct mft_record *mrec;
>  	bool mapped_mrec;
> @@ -50,6 +58,8 @@ struct ntfs_attr_search_ctx {
>  	struct mft_record *base_mrec;
>  	bool mapped_base_mrec;
>  	struct attr_record *base_attr;
> +	struct ntfs_inode *attr_list_lock_ni;
> +	enum ntfs_attr_search_ctx_lock_mode attr_list_lock_mode;
>  };
>  
>  enum {                  /* ways of processing holes when expanding */
> @@ -66,6 +76,7 @@ struct runlist_element *ntfs_attr_find_vcn_nolock(struct ntfs_inode *ni,
>  		const s64 vcn, struct ntfs_attr_search_ctx *ctx);
>  struct runlist_element *__ntfs_attr_find_vcn_nolock(struct runlist *runlist,
>  		const s64 vcn);
> +int ntfs_attr_map_whole_runlist_nolock(struct ntfs_inode *ni);
>  int ntfs_attr_map_whole_runlist(struct ntfs_inode *ni);
>  int ntfs_attr_lookup(const __le32 type, const __le16 *name,
>  		const u32 name_len, const u32 ic,
> @@ -83,7 +94,16 @@ static inline s64 ntfs_attr_size(const struct attr_record *a)
>  
>  void ntfs_attr_reinit_search_ctx(struct ntfs_attr_search_ctx *ctx);
>  struct ntfs_attr_search_ctx *ntfs_attr_get_search_ctx(struct ntfs_inode *ni,
> -		struct mft_record *mrec);
> +		struct mft_record *mrec,
> +		enum ntfs_attr_search_ctx_lock_mode lock_mode);
> +struct ntfs_inode *ntfs_attr_list_lock(struct ntfs_inode *ni,
> +				       enum ntfs_attr_search_ctx_lock_mode lock_mode);
> +void ntfs_attr_list_unlock(struct ntfs_inode *lock_ni,
> +			   enum ntfs_attr_search_ctx_lock_mode lock_mode);
> +void ntfs_attr_lock_search_ctx(struct ntfs_attr_search_ctx *ctx,
> +			       struct ntfs_inode *ni,
> +			       enum ntfs_attr_search_ctx_lock_mode lock_mode);
> +void ntfs_attr_unlock_search_ctx(struct ntfs_attr_search_ctx *ctx);
>  void ntfs_attr_put_search_ctx(struct ntfs_attr_search_ctx *ctx);
>  int ntfs_attr_size_bounds_check(const struct ntfs_volume *vol,
>  		const __le32 type, const s64 size);
> @@ -129,6 +149,7 @@ int ntfs_resident_attr_record_add(struct ntfs_inode *ni, __le32 type,
>  		__le16 *name, u8 name_len, u8 *val, u32 size,
>  		__le16 flags);
>  int ntfs_attr_update_mapping_pairs(struct ntfs_inode *ni, s64 from_vcn);
> +int ntfs_attr_update_mapping_pairs_nolock(struct ntfs_inode *ni, s64 from_vcn);
>  struct runlist_element *ntfs_attr_vcn_to_rl(struct ntfs_inode *ni, s64 vcn, s64 *lcn);
>  
>  /*
> @@ -144,7 +165,9 @@ struct runlist_element *ntfs_attr_vcn_to_rl(struct ntfs_inode *ni, s64 vcn, s64
>   *	    @ni, you can simply do:
>   *
>   *	int err;
> - *	struct ntfs_attr_search_ctx *ctx = ntfs_attr_get_search_ctx(ni, NULL);
> + *	struct ntfs_attr_search_ctx *ctx;
> + *
> + *	ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
>   *	if (!ctx)
>   *		// Error code is in errno. Handle this case.
>   *	while (!(err = ntfs_attrs_walk(ctx))) {
> diff --git a/fs/ntfs/attrlist.c b/fs/ntfs/attrlist.c
> index bd501e8a628c4..56967aca8c7a2 100644
> --- a/fs/ntfs/attrlist.c
> +++ b/fs/ntfs/attrlist.c
> @@ -153,7 +153,7 @@ int ntfs_attrlist_entry_add(struct ntfs_inode *ni, struct attr_record *attr)
>  		return -ENOMEM;
>  
>  	/* Find place for the new entry. */
> -	ctx = ntfs_attr_get_search_ctx(ni, NULL);
> +	ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_WRITE);
>  	if (!ctx) {
>  		err = -ENOMEM;
>  		ntfs_error(ni->vol->sb, "Failed to get search context");
> @@ -172,7 +172,6 @@ int ntfs_attrlist_entry_add(struct ntfs_inode *ni, struct attr_record *attr)
>  		if (ctx->al_entry->lowest_vcn == attr->data.non_resident.lowest_vcn) {
>  			err = -EEXIST;
>  			ntfs_debug("Such attribute already present in the attribute list.\n");
> -			ntfs_attr_put_search_ctx(ctx);
>  			goto err_out;
>  		}
>  		/* Add new entry after this extent. */
> @@ -182,15 +181,11 @@ int ntfs_attrlist_entry_add(struct ntfs_inode *ni, struct attr_record *attr)
>  		/* Check for real errors. */
>  		if (err != -ENOENT) {
>  			ntfs_debug("Attribute lookup failed.\n");
> -			ntfs_attr_put_search_ctx(ctx);
>  			goto err_out;
>  		}
>  		/* No previous extents found. */
>  		ale = ctx->al_entry;
>  	}
> -	/* Don't need it anymore, @ctx->al_entry points to @ni->attr_list. */
> -	ntfs_attr_put_search_ctx(ctx);
> -
>  	/* Determine new entry offset. */
>  	entry_offset = ((u8 *)ale - ni->attr_list);
>  	/* Set pointer to new entry. */
> @@ -219,16 +214,22 @@ int ntfs_attrlist_entry_add(struct ntfs_inode *ni, struct attr_record *attr)
>  	old_al = ni->attr_list;
>  	ni->attr_list = new_al;
>  	ni->attr_list_size = ni->attr_list_size + entry_len;
> +	ntfs_attr_put_search_ctx(ctx);
> +	ctx = NULL;
>  
>  	err = ntfs_attrlist_update(ni);
>  	if (err) {
> +		down_write(&ni->attr_list_lock);
>  		ni->attr_list = old_al;
>  		ni->attr_list_size -= entry_len;
> +		up_write(&ni->attr_list_lock);
>  		goto err_out;
>  	}
>  	kvfree(old_al);
>  	return 0;
>  err_out:
> +	if (ctx)
> +		ntfs_attr_put_search_ctx(ctx);
>  	kvfree(new_al);
>  	return err;
>  }
> @@ -244,9 +245,12 @@ int ntfs_attrlist_entry_add(struct ntfs_inode *ni, struct attr_record *attr)
>  int ntfs_attrlist_entry_rm(struct ntfs_attr_search_ctx *ctx)
>  {
>  	u8 *new_al;
> -	int new_al_len;
> +	int err, new_al_len;
>  	struct ntfs_inode *base_ni;
>  	struct attr_list_entry *ale;
> +	enum ntfs_attr_search_ctx_lock_mode lock_mode;
> +	bool ctx_locked;
> +	u8 *old_al;
>  
>  	if (!ctx || !ctx->ntfs_ino || !ctx->al_entry) {
>  		ntfs_debug("Invalid arguments.\n");
> @@ -281,9 +285,17 @@ int ntfs_attrlist_entry_rm(struct ntfs_attr_search_ctx *ctx)
>  				ale->length), new_al_len - ((u8 *)ale - base_ni->attr_list));
>  
>  	/* Set new runlist. */
> -	kvfree(base_ni->attr_list);
> +	old_al = base_ni->attr_list;
>  	base_ni->attr_list = new_al;
>  	base_ni->attr_list_size = new_al_len;
> +	kvfree(old_al);
>  
> -	return ntfs_attrlist_update(base_ni);
> +	ctx_locked = ctx->attr_list_lock_ni;
> +	lock_mode = ctx->attr_list_lock_mode;
> +	if (ctx_locked)
> +		ntfs_attr_unlock_search_ctx(ctx);
> +	err = ntfs_attrlist_update(base_ni);
> +	if (ctx_locked)
> +		ntfs_attr_lock_search_ctx(ctx, base_ni, lock_mode);
> +	return err;
>  }
> diff --git a/fs/ntfs/compress.c b/fs/ntfs/compress.c
> index 76bd806b41edd..bac6b32069475 100644
> --- a/fs/ntfs/compress.c
> +++ b/fs/ntfs/compress.c
> @@ -1287,6 +1287,7 @@ static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages,
>  	/* more compressed zeroes, to be followed by some count */
>  	static char morezeroes[] = {0x03, 0xb0, 0x02, 0x00};
>  	struct page **pages_disk = NULL, *pg;
> +	struct ntfs_inode *attr_lock_ni;
>  	s64 bio_lcn;
>  	struct runlist_element *rlc, *rl;
>  	int i, err;
> @@ -1390,10 +1391,12 @@ static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages,
>  	}
>  
>  	bio_lcn = rlc->lcn;
> +	attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  	down_write(&ni->runlist.lock);
>  	rl = ntfs_runlists_merge(&ni->runlist, rlc, 0, &new_rl_count);
>  	if (IS_ERR(rl)) {
>  		up_write(&ni->runlist.lock);
> +		ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  		ntfs_error(vol->sb, "Failed to merge runlists");
>  		err = PTR_ERR(rl);
>  		if (ntfs_cluster_free_from_rl(vol, rlc))
> @@ -1405,8 +1408,9 @@ static int ntfs_write_cb(struct ntfs_inode *ni, loff_t pos, struct page **pages,
>  	ni->runlist.count = new_rl_count;
>  	ni->runlist.rl = rl;
>  
> -	err = ntfs_attr_update_mapping_pairs(ni, 0);
> +	err = ntfs_attr_update_mapping_pairs_nolock(ni, 0);
>  	up_write(&ni->runlist.lock);
> +	ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  	if (err) {
>  		err = -EIO;
>  		goto out;
> diff --git a/fs/ntfs/dir.c b/fs/ntfs/dir.c
> index 20f5c7074bdd1..84ca11227d060 100644
> --- a/fs/ntfs/dir.c
> +++ b/fs/ntfs/dir.c
> @@ -91,7 +91,7 @@ u64 ntfs_lookup_inode_by_name(struct ntfs_inode *dir_ni, const __le16 *uname,
>  				-PTR_ERR(m));
>  		return ERR_MREF(PTR_ERR(m));
>  	}
> -	ctx = ntfs_attr_get_search_ctx(dir_ni, m);
> +	ctx = ntfs_attr_get_search_ctx(dir_ni, m, NTFS_ATTR_CTX_LOCK_READ);
>  	if (unlikely(!ctx)) {
>  		err = -ENOMEM;
>  		goto err_out;
> @@ -854,7 +854,7 @@ static int ntfs_readdir(struct file *file, struct dir_context *actor)
>  		file->private_data = private;
>  	}
>  
> -	ctx = ntfs_attr_get_search_ctx(ndir, NULL);
> +	ctx = ntfs_attr_get_search_ctx(ndir, NULL, NTFS_ATTR_CTX_LOCK_READ);
>  	if (!ctx) {
>  		err = -ENOMEM;
>  		goto out;
> @@ -1061,7 +1061,7 @@ int ntfs_check_empty_dir(struct ntfs_inode *ni, struct mft_record *ni_mrec)
>  	if (!(ni_mrec->flags & MFT_RECORD_IS_DIRECTORY))
>  		return 0;
>  
> -	ctx = ntfs_attr_get_search_ctx(ni, NULL);
> +	ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
>  	if (!ctx) {
>  		ntfs_error(ni->vol->sb, "Failed to get search context");
>  		return -ENOMEM;
> @@ -1162,7 +1162,7 @@ static int ntfs_dir_fsync(struct file *filp, loff_t start, loff_t end,
>  	if (NVolShutdown(vol))
>  		return -EIO;
>  
> -	ctx = ntfs_attr_get_search_ctx(ni, NULL);
> +	ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
>  	if (!ctx)
>  		return -ENOMEM;
>  
> diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c
> index c4a4a3e3e5996..3cba0ff9606cb 100644
> --- a/fs/ntfs/ea.c
> +++ b/fs/ntfs/ea.c
> @@ -587,7 +587,7 @@ static int ntfs_new_attr_flags(struct ntfs_inode *ni, __le32 fattr)
>  	if (IS_ERR(m))
>  		return PTR_ERR(m);
>  
> -	ctx = ntfs_attr_get_search_ctx(ni, m);
> +	ctx = ntfs_attr_get_search_ctx(ni, m, NTFS_ATTR_CTX_LOCK_READ);
>  	if (!ctx) {
>  		err = -ENOMEM;
>  		goto err_out;
> diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c
> index e8bea22b81a75..038ed723c4bff 100644
> --- a/fs/ntfs/file.c
> +++ b/fs/ntfs/file.c
> @@ -22,6 +22,7 @@
>  #include "ea.h"
>  #include "iomap.h"
>  #include "bitmap.h"
> +#include "attrib.h"
>  
>  #include <linux/filelock.h>
>  
> @@ -78,6 +79,7 @@ static int ntfs_trim_prealloc(struct inode *vi)
>  {
>  	struct ntfs_inode *ni = NTFS_I(vi);
>  	struct ntfs_volume *vol = ni->vol;
> +	struct ntfs_inode *attr_lock_ni;
>  	struct runlist_element *rl;
>  	s64 aligned_data_size;
>  	s64 vcn_ds, vcn_tr;
> @@ -86,6 +88,7 @@ static int ntfs_trim_prealloc(struct inode *vi)
>  
>  	inode_lock(vi);
>  	mutex_lock(&ni->mrec_lock);
> +	attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  	down_write(&ni->runlist.lock);
>  
>  	aligned_data_size = round_up(ni->data_size, vol->cluster_size);
> @@ -110,7 +113,7 @@ static int ntfs_trim_prealloc(struct inode *vi)
>  			ntfs_error(vol->sb, "Preallocated block rollback failed");
>  		} else {
>  			ni->allocated_size = ntfs_cluster_to_bytes(vol, vcn_tr);
> -			err = ntfs_attr_update_mapping_pairs(ni, 0);
> +			err = ntfs_attr_update_mapping_pairs_nolock(ni, 0);
>  			if (err)
>  				ntfs_error(vol->sb,
>  					   "Failed to rollback mapping pairs for prealloc");
> @@ -119,6 +122,7 @@ static int ntfs_trim_prealloc(struct inode *vi)
>  
>  out_unlock:
>  	up_write(&ni->runlist.lock);
> +	ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  	mutex_unlock(&ni->mrec_lock);
>  	inode_unlock(vi);
>  
> @@ -176,7 +180,7 @@ static int ntfs_file_fsync(struct file *filp, loff_t start, loff_t end,
>  		ret = __ntfs_write_inode(vi, 1);
>  	write_inode_now(vi, !datasync);
>  
> -	ctx = ntfs_attr_get_search_ctx(ni, NULL);
> +	ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
>  	if (!ctx)
>  		return -ENOMEM;
>  
> @@ -1028,6 +1032,7 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t offset, loff_t le
>  	struct inode *vi = file_inode(file);
>  	struct ntfs_inode *ni = NTFS_I(vi);
>  	struct ntfs_volume *vol = ni->vol;
> +	struct ntfs_inode *attr_lock_ni;
>  	int err = 0;
>  	loff_t old_size;
>  	bool map_locked = false;
> @@ -1042,9 +1047,11 @@ static long ntfs_fallocate(struct file *file, int mode, loff_t offset, loff_t le
>  		return -ENOSPC;
>  
>  	if (NInoNonResident(ni) && !NInoFullyMapped(ni)) {
> +		attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_READ);
>  		down_write(&ni->runlist.lock);
> -		err = ntfs_attr_map_whole_runlist(ni);
> +		err = ntfs_attr_map_whole_runlist_nolock(ni);
>  		up_write(&ni->runlist.lock);
> +		ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_READ);
>  		if (err)
>  			return err;
>  	}
> diff --git a/fs/ntfs/index.c b/fs/ntfs/index.c
> index a547bdcfa4561..f6d31465c7b93 100644
> --- a/fs/ntfs/index.c
> +++ b/fs/ntfs/index.c
> @@ -509,7 +509,7 @@ static struct index_root *ntfs_ir_lookup(struct ntfs_inode *ni, __le16 *name,
>  	struct index_root *ir = NULL;
>  
>  	ntfs_debug("Entering\n");
> -	*ctx = ntfs_attr_get_search_ctx(ni, NULL);
> +	*ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_WRITE);
>  	if (!*ctx) {
>  		ntfs_error(ni->vol->sb, "%s, Failed to get search context", __func__);
>  		return NULL;
> @@ -1240,7 +1240,8 @@ static int ntfs_ir_reparent(struct ntfs_index_context *icx)
>  		 * When there is no space to build a non-resident
>  		 * index, we may have to move the root to an extent
>  		 */
> -		if ((ret == -ENOSPC) && (ctx->al_entry || !ntfs_inode_add_attrlist(icx->idx_ni))) {
> +		if ((ret == -ENOSPC) &&
> +		    (ctx->al_entry || !ntfs_inode_add_attrlist_nolock(icx->idx_ni))) {
>  			ntfs_attr_put_search_ctx(ctx);
>  			ctx = NULL;
>  			ir = ntfs_ir_lookup(icx->idx_ni, icx->name, icx->name_len, &ctx);
> diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c
> index 16890d411194d..1cbeb6a68e915 100644
> --- a/fs/ntfs/inode.c
> +++ b/fs/ntfs/inode.c
> @@ -326,7 +326,7 @@ static int ntfs_non_resident_dealloc_clusters(struct ntfs_inode *ni)
>  	struct ntfs_attr_search_ctx *actx;
>  	int err = 0;
>  
> -	actx = ntfs_attr_get_search_ctx(ni, NULL);
> +	actx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
>  	if (!actx)
>  		return -ENOMEM;
>  	WARN_ON(actx->mrec->link_count != 0);
> @@ -475,6 +475,7 @@ void __ntfs_init_inode(struct super_block *sb, struct ntfs_inode *ni)
>  	ni->folio = NULL;
>  	ni->folio_ofs = 0;
>  	ni->mrec = NULL;
> +	init_rwsem(&ni->attr_list_lock);
>  	ni->attr_list_size = 0;
>  	ni->attr_list = NULL;
>  	ni->itype.index.block_size = 0;
> @@ -711,7 +712,7 @@ static int ntfs_read_locked_inode(struct inode *vi)
>  		goto err_out;
>  	}
>  
> -	ctx = ntfs_attr_get_search_ctx(ni, m);
> +	ctx = ntfs_attr_get_search_ctx(ni, m, NTFS_ATTR_CTX_LOCK_READ);
>  	if (!ctx) {
>  		err = -ENOMEM;
>  		goto unm_err_out;
> @@ -1265,7 +1266,7 @@ static int ntfs_read_locked_attr_inode(struct inode *base_vi, struct inode *vi)
>  		err = PTR_ERR(m);
>  		goto err_out;
>  	}
> -	ctx = ntfs_attr_get_search_ctx(base_ni, m);
> +	ctx = ntfs_attr_get_search_ctx(base_ni, m, NTFS_ATTR_CTX_LOCK_READ);
>  	if (!ctx) {
>  		err = -ENOMEM;
>  		goto unm_err_out;
> @@ -1506,7 +1507,7 @@ static int ntfs_read_locked_index_inode(struct inode *base_vi, struct inode *vi)
>  		err = PTR_ERR(m);
>  		goto err_out;
>  	}
> -	ctx = ntfs_attr_get_search_ctx(base_ni, m);
> +	ctx = ntfs_attr_get_search_ctx(base_ni, m, NTFS_ATTR_CTX_LOCK_READ);
>  	if (!ctx) {
>  		err = -ENOMEM;
>  		goto unm_err_out;
> @@ -1897,7 +1898,7 @@ int ntfs_read_inode_mount(struct inode *vi)
>  	/* Provides read_folio() for map_mft_record(). */
>  	vi->i_mapping->a_ops = &ntfs_mft_aops;
>  
> -	ctx = ntfs_attr_get_search_ctx(ni, m);
> +	ctx = ntfs_attr_get_search_ctx(ni, m, NTFS_ATTR_CTX_LOCK_READ);
>  	if (!ctx) {
>  		err = -ENOMEM;
>  		goto err_out;
> @@ -2447,7 +2448,7 @@ static int ntfs_inode_sync_standard_information(struct inode *vi, struct mft_rec
>  	bool modified = false;
>  
>  	/* Update the access times in the standard information attribute. */
> -	ctx = ntfs_attr_get_search_ctx(ni, m);
> +	ctx = ntfs_attr_get_search_ctx(ni, m, NTFS_ATTR_CTX_LOCK_READ);
>  	if (unlikely(!ctx))
>  		return -ENOMEM;
>  	err = ntfs_attr_lookup(AT_STANDARD_INFORMATION, NULL, 0,
> @@ -2548,7 +2549,7 @@ int ntfs_inode_sync_filename(struct ntfs_inode *ni)
>  
>  	ntfs_debug("Entering for inode %llu\n", ni->mft_no);
>  
> -	ctx = ntfs_attr_get_search_ctx(ni, NULL);
> +	ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
>  	if (!ctx)
>  		return -ENOMEM;
>  
> @@ -2740,11 +2741,15 @@ int __ntfs_write_inode(struct inode *vi, int sync)
>  	}
>  
>  	if (NInoNonResident(ni) && NInoRunlistDirty(ni)) {
> +		struct ntfs_inode *attr_lock_ni;
> +
> +		attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  		down_write(&ni->runlist.lock);
> -		err = ntfs_attr_update_mapping_pairs(ni, 0);
> +		err = ntfs_attr_update_mapping_pairs_nolock(ni, 0);
>  		if (!err)
>  			NInoClearRunlistDirty(ni);
>  		up_write(&ni->runlist.lock);
> +		ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  	}
>  
>  	err = ntfs_inode_sync_standard_information(vi, m);
> @@ -2963,7 +2968,7 @@ static struct ntfs_inode *ntfs_extent_inode_open(struct ntfs_inode *base_ni,
>   *
>   * Return 0 on success and error.
>   */
> -int ntfs_inode_attach_all_extents(struct ntfs_inode *ni)
> +int ntfs_inode_attach_all_extents_nolock(struct ntfs_inode *ni)
>  {
>  	struct attr_list_entry *ale;
>  	u64 prev_attached = 0;
> @@ -3003,13 +3008,25 @@ int ntfs_inode_attach_all_extents(struct ntfs_inode *ni)
>  	return 0;
>  }
>  
> +int ntfs_inode_attach_all_extents(struct ntfs_inode *ni)
> +{
> +	struct ntfs_inode *attr_lock_ni;
> +	int err;
> +
> +	attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_READ);
> +	err = ntfs_inode_attach_all_extents_nolock(ni);
> +	ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_READ);
> +
> +	return err;
> +}
> +
>  /*
>   * ntfs_inode_add_attrlist - add attribute list to inode and fill it
>   * @ni: opened ntfs inode to which add attribute list
>   *
>   * Return 0 on success or error.
>   */
> -int ntfs_inode_add_attrlist(struct ntfs_inode *ni)
> +int ntfs_inode_add_attrlist_nolock(struct ntfs_inode *ni)
>  {
>  	int err;
>  	struct ntfs_attr_search_ctx *ctx;
> @@ -3034,7 +3051,7 @@ int ntfs_inode_add_attrlist(struct ntfs_inode *ni)
>  		return -EIO;
>  
>  	/* Form attribute list. */
> -	ctx = ntfs_attr_get_search_ctx(ni, ni_mrec);
> +	ctx = ntfs_attr_get_search_ctx(ni, ni_mrec, NTFS_ATTR_CTX_LOCK_NONE);
>  	if (!ctx) {
>  		err = -ENOMEM;
>  		goto err_out;
> @@ -3092,7 +3109,6 @@ int ntfs_inode_add_attrlist(struct ntfs_inode *ni)
>  		goto put_err_out;
>  	}
>  
> -	/* Set in-memory attribute list. */
>  	ni->attr_list = al;
>  	ni->attr_list_size = al_len;
>  	NInoSetAttrList(ni);
> @@ -3102,7 +3118,7 @@ int ntfs_inode_add_attrlist(struct ntfs_inode *ni)
>  	/* Free space if there is not enough it for $ATTRIBUTE_LIST. */
>  	if (le32_to_cpu(ni_mrec->bytes_allocated) -
>  			le32_to_cpu(ni_mrec->bytes_in_use) < attr_al_len) {
> -		if (ntfs_inode_free_space(ni, (int)attr_al_len)) {
> +		if (ntfs_inode_free_space_nolock(ni, (int)attr_al_len)) {
>  			/* Failed to free space. */
>  			err = -ENOSPC;
>  			ntfs_error(ni->vol->sb, "Failed to free space for attrlist");
> @@ -3129,6 +3145,7 @@ int ntfs_inode_add_attrlist(struct ntfs_inode *ni)
>  remove_attrlist_record:
>  	/* Prevent ntfs_attr_recorm_rm from freeing attribute list. */
>  	ni->attr_list = NULL;
> +	ni->attr_list_size = 0;
>  	NInoClearAttrList(ni);
>  	/* Remove $ATTRIBUTE_LIST record. */
>  	ntfs_attr_reinit_search_ctx(ctx);
> @@ -3182,6 +3199,18 @@ int ntfs_inode_add_attrlist(struct ntfs_inode *ni)
>  	return err;
>  }
>  
> +int ntfs_inode_add_attrlist(struct ntfs_inode *ni)
> +{
> +	struct ntfs_inode *attr_lock_ni;
> +	int err;
> +
> +	attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_WRITE);
> +	err = ntfs_inode_add_attrlist_nolock(ni);
> +	ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> +
> +	return err;
> +}
> +
>  /*
>   * ntfs_inode_close - close an ntfs inode and free all associated memory
>   * @ni:		ntfs inode to close
> @@ -3325,7 +3354,8 @@ static int ntfs_attr_position(__le32 type, struct ntfs_attr_search_ctx *ctx)
>   *
>   * Return 0 on success or error.
>   */
> -int ntfs_inode_free_space(struct ntfs_inode *ni, int size)
> +static int __ntfs_inode_free_space(struct ntfs_inode *ni, int size,
> +				   enum ntfs_attr_search_ctx_lock_mode lock_mode)
>  {
>  	struct ntfs_attr_search_ctx *ctx;
>  	int freed, err;
> @@ -3349,7 +3379,7 @@ int ntfs_inode_free_space(struct ntfs_inode *ni, int size)
>  	if (size <= freed)
>  		return 0;
>  
> -	ctx = ntfs_attr_get_search_ctx(ni, NULL);
> +	ctx = ntfs_attr_get_search_ctx(ni, NULL, lock_mode);
>  	if (!ctx) {
>  		ntfs_error(sb, "%s, Failed to get search context", __func__);
>  		return -ENOMEM;
> @@ -3435,6 +3465,23 @@ int ntfs_inode_free_space(struct ntfs_inode *ni, int size)
>  	return err;
>  }
>  
> +int ntfs_inode_free_space_nolock(struct ntfs_inode *ni, int size)
> +{
> +	return __ntfs_inode_free_space(ni, size, NTFS_ATTR_CTX_LOCK_NONE);
> +}
> +
> +int ntfs_inode_free_space(struct ntfs_inode *ni, int size)
> +{
> +	struct ntfs_inode *attr_lock_ni;
> +	int err;
> +
> +	attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_WRITE);
> +	err = __ntfs_inode_free_space(ni, size, NTFS_ATTR_CTX_LOCK_NONE);
> +	ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
> +
> +	return err;
> +}
> +
>  s64 ntfs_inode_attr_pread(struct inode *vi, s64 pos, s64 count, u8 *buf)
>  {
>  	struct address_space *mapping = vi->i_mapping;
> @@ -3459,10 +3506,11 @@ s64 ntfs_inode_attr_pread(struct inode *vi, s64 pos, s64 count, u8 *buf)
>  		count = isize - pos;
>  
>  	if (!NInoNonResident(ni)) {
> +		struct ntfs_inode *base_ni = ni->ext.base_ntfs_ino;
>  		struct ntfs_attr_search_ctx *ctx;
>  		u8 *attr;
>  
> -		ctx = ntfs_attr_get_search_ctx(ni->ext.base_ntfs_ino, NULL);
> +		ctx = ntfs_attr_get_search_ctx(base_ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
>  		if (!ctx) {
>  			ntfs_error(vi->i_sb, "Failed to get attr search ctx");
>  			err = -ENOMEM;
> @@ -3650,6 +3698,8 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi,
>  
>  		if (sync) {
>  			struct ntfs_volume *vol = ni->vol;
> +			struct ntfs_inode *attr_lock_ni;
> +			enum ntfs_attr_search_ctx_lock_mode mode;
>  			s64 lcn, lcn_count;
>  			unsigned int lcn_folio_off = 0;
>  			struct bio *bio;
> @@ -3659,13 +3709,16 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi,
>  
>  			lcn_count = max_t(s64, 1, ntfs_bytes_to_cluster(vol, attr_len));
>  			vcn = ntfs_pidx_to_cluster(vol, folio->index);
> +			mode = NTFS_ATTR_CTX_LOCK_READ;
>  
>  			do {
> +				attr_lock_ni = ntfs_attr_list_lock(ni, mode);
>  				down_write(&ni->runlist.lock);
>  				rl = ntfs_attr_vcn_to_rl(ni, vcn, &lcn);
>  				if (IS_ERR(rl)) {
>  					ret = PTR_ERR(rl);
>  					up_write(&ni->runlist.lock);
> +					ntfs_attr_list_unlock(attr_lock_ni, mode);
>  					goto err_unlock_folio;
>  				}
>  
> @@ -3677,6 +3730,7 @@ static s64 __ntfs_inode_non_resident_attr_pwrite(struct inode *vi,
>  					lcn_count = 0;
>  				}
>  				up_write(&ni->runlist.lock);
> +				ntfs_attr_list_unlock(attr_lock_ni, mode);
>  
>  				if (vol->cluster_size_bits > PAGE_SHIFT) {
>  					lcn_folio_off = folio->index << PAGE_SHIFT;
> @@ -3737,7 +3791,7 @@ s64 ntfs_inode_attr_pwrite(struct inode *vi, s64 pos, s64 count, u8 *buf, bool s
>  
>  	WARN_ON(!NInoAttr(ni));
>  
> -	ctx = ntfs_attr_get_search_ctx(ni->ext.base_ntfs_ino, NULL);
> +	ctx = ntfs_attr_get_search_ctx(ni->ext.base_ntfs_ino, NULL, NTFS_ATTR_CTX_LOCK_READ);
>  	if (!ctx) {
>  		ntfs_error(vi->i_sb, "Failed to get attr search ctx");
>  		return -ENOMEM;
> diff --git a/fs/ntfs/inode.h b/fs/ntfs/inode.h
> index 67942b97fac6d..43783e77c4f0e 100644
> --- a/fs/ntfs/inode.h
> +++ b/fs/ntfs/inode.h
> @@ -10,6 +10,8 @@
>  #ifndef _LINUX_NTFS_INODE_H
>  #define _LINUX_NTFS_INODE_H
>  
> +#include <linux/rwsem.h>
> +
>  #include "debug.h"
>  
>  enum ntfs_inode_mutex_lock_class {
> @@ -66,6 +68,7 @@ enum ntfs_inode_mutex_lock_class {
>   * Attribute list support (only for use by the attribute lookup
>   * functions). Setup during read_inode for all inodes with attribute
>   * lists. Only valid if NI_AttrList is set in state.
> + * @attr_list_lock: Serializes attribute list pointer updates with lookups.
>   * @attr_list_size: Length of attribute list value in bytes.
>   * @attr_list: Attribute list value itself.
>   *
> @@ -118,6 +121,7 @@ struct ntfs_inode {
>  	int folio_ofs;
>  	s64 mft_lcn[2];
>  	unsigned int mft_lcn_count;
> +	struct rw_semaphore attr_list_lock;
>  	u32 attr_list_size;
>  	u8 *attr_list;
>  	union {
> @@ -334,9 +338,12 @@ int ntfs_getattr(struct mnt_idmap *idmap, const struct path *path,
>  
>  int ntfs_get_block_mft_record(struct ntfs_inode *mft_ni, struct ntfs_inode *ni);
>  int __ntfs_write_inode(struct inode *vi, int sync);
> +int ntfs_inode_attach_all_extents_nolock(struct ntfs_inode *ni);
>  int ntfs_inode_attach_all_extents(struct ntfs_inode *ni);
> +int ntfs_inode_add_attrlist_nolock(struct ntfs_inode *ni);
>  int ntfs_inode_add_attrlist(struct ntfs_inode *ni);
>  void ntfs_destroy_ext_inode(struct ntfs_inode *ni);
> +int ntfs_inode_free_space_nolock(struct ntfs_inode *ni, int size);
>  int ntfs_inode_free_space(struct ntfs_inode *ni, int size);
>  s64 ntfs_inode_attr_pread(struct inode *vi, s64 pos, s64 count, u8 *buf);
>  s64 ntfs_inode_attr_pwrite(struct inode *vi, s64 pos, s64 count, u8 *buf,
> diff --git a/fs/ntfs/iomap.c b/fs/ntfs/iomap.c
> index dc7d8c893a699..f2bed2e0c8c8b 100644
> --- a/fs/ntfs/iomap.c
> +++ b/fs/ntfs/iomap.c
> @@ -96,7 +96,7 @@ static int ntfs_read_iomap_begin_resident(struct inode *inode, loff_t offset, lo
>  	else
>  		base_ni = ni;
>  
> -	ctx = ntfs_attr_get_search_ctx(base_ni, NULL);
> +	ctx = ntfs_attr_get_search_ctx(base_ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
>  	if (!ctx) {
>  		err = -ENOMEM;
>  		goto out;
> @@ -196,6 +196,7 @@ static int ntfs_read_iomap_begin_non_resident(struct inode *inode, loff_t offset
>  		bool need_unwritten)
>  {
>  	struct ntfs_inode *ni = NTFS_I(inode);
> +	struct ntfs_inode *attr_lock_ni;
>  	s64 vcn;
>  	s64 lcn;
>  	struct runlist_element *rl;
> @@ -206,20 +207,24 @@ static int ntfs_read_iomap_begin_non_resident(struct inode *inode, loff_t offset
>  	vcn = ntfs_bytes_to_cluster(vol, offset);
>  	vcn_ofs = ntfs_bytes_to_cluster_off(vol, offset);
>  
> +	attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_READ);
>  	down_write(&ni->runlist.lock);
>  	rl = ntfs_attr_vcn_to_rl(ni, vcn, &lcn);
>  	if (IS_ERR(rl)) {
>  		up_write(&ni->runlist.lock);
> +		ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_READ);
>  		return PTR_ERR(rl);
>  	}
>  
>  	if (flags & IOMAP_REPORT) {
>  		if (lcn < LCN_HOLE) {
>  			up_write(&ni->runlist.lock);
> +			ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_READ);
>  			return -ENOENT;
>  		}
>  	} else if (lcn < LCN_ENOENT) {
>  		up_write(&ni->runlist.lock);
> +		ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_READ);
>  		return -EINVAL;
>  	}
>  
> @@ -247,6 +252,7 @@ static int ntfs_read_iomap_begin_non_resident(struct inode *inode, loff_t offset
>  				"runlist(vcn : %lld, length : %lld, lcn : %lld) is corrupted\n",
>  				rl->vcn, rl->length, rl->lcn);
>  		up_write(&ni->runlist.lock);
> +		ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_READ);
>  		return -EIO;
>  	}
>  
> @@ -255,6 +261,7 @@ static int ntfs_read_iomap_begin_non_resident(struct inode *inode, loff_t offset
>  	else
>  		iomap->length = length;
>  	up_write(&ni->runlist.lock);
> +	ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_READ);
>  
>  	if (!(flags & IOMAP_ZERO) &&
>  			iomap->type == IOMAP_MAPPED &&
> @@ -572,6 +579,7 @@ static int ntfs_write_da_iomap_begin_non_resident(struct inode *inode,
>  {
>  	struct ntfs_inode *ni = NTFS_I(inode);
>  	struct ntfs_volume *vol = ni->vol;
> +	struct ntfs_inode *attr_lock_ni;
>  	loff_t vcn_ofs, rl_length;
>  	s64 vcn, start_lcn, lcn_count;
>  	bool balloc = false, update_mp;
> @@ -584,11 +592,13 @@ static int ntfs_write_da_iomap_begin_non_resident(struct inode *inode,
>  
>  	update_mp = ntfs_iomap_flags & (NTFS_IOMAP_FLAGS_DIO | NTFS_IOMAP_FLAGS_MKWRITE) ||
>  			NInoAttr(ni) || ni->mft_no < FILE_first_user;
> +	attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  	down_write(&ni->runlist.lock);
>  	err = ntfs_attr_map_cluster(ni, vcn, &start_lcn, &lcn_count,
>  			max_clu_count, &balloc, update_mp,
>  			ntfs_iomap_flags & NTFS_IOMAP_FLAGS_WRITEBACK);
>  	up_write(&ni->runlist.lock);
> +	ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  	mutex_unlock(&ni->mrec_lock);
>  	if (err) {
>  		ni->i_dealloc_clusters = 0;
> @@ -661,7 +671,7 @@ static int ntfs_write_iomap_begin_resident(struct inode *inode, loff_t offset,
>  	char *kattr;
>  	struct page *ipage;
>  
> -	ctx = ntfs_attr_get_search_ctx(ni, NULL);
> +	ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
>  	if (!ctx) {
>  		err = -ENOMEM;
>  		goto out;
> @@ -785,7 +795,7 @@ static int ntfs_write_iomap_end_resident(struct inode *inode, loff_t pos,
>  	struct page *ipage = iomap->private;
>  
>  	mutex_lock(&ni->mrec_lock);
> -	ctx = ntfs_attr_get_search_ctx(ni, NULL);
> +	ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
>  	if (!ctx) {
>  		written = -ENOMEM;
>  		goto err_out;
> diff --git a/fs/ntfs/mft.c b/fs/ntfs/mft.c
> index 7d989267a82b9..e2b2e21abb7f5 100644
> --- a/fs/ntfs/mft.c
> +++ b/fs/ntfs/mft.c
> @@ -12,6 +12,7 @@
>  #include <linux/bio.h>
>  #include <linux/iomap.h>
>  
> +#include "attrib.h"
>  #include "bitmap.h"
>  #include "lcnalloc.h"
>  #include "mft.h"
> @@ -1093,7 +1094,7 @@ static int ntfs_mft_attr_extend(struct ntfs_inode *ni)
>  		base_ni = ni;
>  
>  	if (!NInoAttrList(base_ni)) {
> -		ret = ntfs_inode_add_attrlist(base_ni);
> +		ret = ntfs_inode_add_attrlist_nolock(base_ni);
>  		if (ret) {
>  			pr_err("Can not add attrlist\n");
>  			goto out;
> @@ -1103,7 +1104,7 @@ static int ntfs_mft_attr_extend(struct ntfs_inode *ni)
>  		}
>  	}
>  
> -	ret = ntfs_attr_update_mapping_pairs(ni, 0);
> +	ret = ntfs_attr_update_mapping_pairs_nolock(ni, 0);
>  	if (ret)
>  		pr_err("MP update failed\n");
>  
> @@ -1135,6 +1136,7 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
>  	unsigned long flags;
>  	struct folio *folio;
>  	struct ntfs_inode *mft_ni, *mftbmp_ni;
> +	struct ntfs_inode *attr_lock_ni;
>  	struct runlist_element *rl, *rl2 = NULL;
>  	struct ntfs_attr_search_ctx *ctx = NULL;
>  	struct mft_record *mrec;
> @@ -1153,6 +1155,7 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
>  	ntfs_debug("Extending mft bitmap allocation.");
>  	mft_ni = NTFS_I(vol->mft_ino);
>  	mftbmp_ni = NTFS_I(vol->mftbmp_ino);
> +	attr_lock_ni = ntfs_attr_list_lock(mftbmp_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  	/*
>  	 * Determine the last lcn of the mft bitmap.  The allocated size of the
>  	 * mft bitmap cannot be zero so we are ok to do this.
> @@ -1165,6 +1168,7 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
>  			NTFS_B_TO_CLU(vol, ll - 1), NULL);
>  	if (IS_ERR(rl) || unlikely(!rl->length || rl->lcn < 0)) {
>  		up_write(&mftbmp_ni->runlist.lock);
> +		ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  		ntfs_error(vol->sb,
>  			"Failed to determine last allocated cluster of mft bitmap attribute.");
>  		if (!IS_ERR(rl))
> @@ -1186,6 +1190,7 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
>  			ll >> PAGE_SHIFT, NULL);
>  	if (IS_ERR(folio)) {
>  		up_write(&mftbmp_ni->runlist.lock);
> +		ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  		ntfs_error(vol->sb, "Failed to read from lcn bitmap.");
>  		return PTR_ERR(folio);
>  	}
> @@ -1217,6 +1222,7 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
>  				true, false, false);
>  		if (IS_ERR(rl2)) {
>  			up_write(&mftbmp_ni->runlist.lock);
> +			ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  			ntfs_error(vol->sb,
>  					"Failed to allocate a cluster for the mft bitmap.");
>  			return PTR_ERR(rl2);
> @@ -1224,6 +1230,7 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
>  		rl = ntfs_runlists_merge(&mftbmp_ni->runlist, rl2, 0, &new_rl_count);
>  		if (IS_ERR(rl)) {
>  			up_write(&mftbmp_ni->runlist.lock);
> +			ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  			ntfs_error(vol->sb, "Failed to merge runlists for mft bitmap.");
>  			if (ntfs_cluster_free_from_rl(vol, rl2)) {
>  				ntfs_error(vol->sb, "Failed to deallocate allocated cluster.%s",
> @@ -1251,7 +1258,7 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
>  		ret = PTR_ERR(mrec);
>  		goto undo_alloc;
>  	}
> -	ctx = ntfs_attr_get_search_ctx(mft_ni, mrec);
> +	ctx = ntfs_attr_get_search_ctx(mft_ni, mrec, NTFS_ATTR_CTX_LOCK_NONE);
>  	if (unlikely(!ctx)) {
>  		ntfs_error(vol->sb, "Failed to get search context.");
>  		ret = -ENOMEM;
> @@ -1343,6 +1350,7 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
>  	ntfs_attr_put_search_ctx(ctx);
>  	unmap_mft_record(mft_ni);
>  	up_write(&mftbmp_ni->runlist.lock);
> +	ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  	ntfs_debug("Done.");
>  	return 0;
>  
> @@ -1359,6 +1367,7 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
>  		ntfs_attr_put_search_ctx(ctx);
>  		unmap_mft_record(mft_ni);
>  		up_write(&mftbmp_ni->runlist.lock);
> +		ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  		/*
>  		 * The only thing that is now wrong is ->allocated_size of the
>  		 * base attribute extent which chkdsk should be able to fix.
> @@ -1402,7 +1411,8 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
>  			NVolSetErrors(vol);
>  		}
>  		mark_mft_record_dirty(ctx->ntfs_ino);
> -	} else if (status.mp_extended && ntfs_attr_update_mapping_pairs(mftbmp_ni, 0)) {
> +	} else if (status.mp_extended &&
> +		   ntfs_attr_update_mapping_pairs_nolock(mftbmp_ni, 0)) {
>  		ntfs_error(vol->sb, "Failed to restore mapping pairs.%s", es);
>  		NVolSetErrors(vol);
>  	}
> @@ -1411,6 +1421,7 @@ static int ntfs_mft_bitmap_extend_allocation_nolock(struct ntfs_volume *vol)
>  	if (!IS_ERR(mrec))
>  		unmap_mft_record(mft_ni);
>  	up_write(&mftbmp_ni->runlist.lock);
> +	ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  	return ret;
>  }
>  
> @@ -1449,7 +1460,7 @@ static int ntfs_mft_bitmap_extend_initialized_nolock(struct ntfs_volume *vol)
>  		ntfs_error(vol->sb, "Failed to map mft record.");
>  		return PTR_ERR(mrec);
>  	}
> -	ctx = ntfs_attr_get_search_ctx(mft_ni, mrec);
> +	ctx = ntfs_attr_get_search_ctx(mft_ni, mrec, NTFS_ATTR_CTX_LOCK_READ);
>  	if (unlikely(!ctx)) {
>  		ntfs_error(vol->sb, "Failed to get search context.");
>  		ret = -ENOMEM;
> @@ -1501,7 +1512,7 @@ static int ntfs_mft_bitmap_extend_initialized_nolock(struct ntfs_volume *vol)
>  		NVolSetErrors(vol);
>  		return ret;
>  	}
> -	ctx = ntfs_attr_get_search_ctx(mft_ni, mrec);
> +	ctx = ntfs_attr_get_search_ctx(mft_ni, mrec, NTFS_ATTR_CTX_LOCK_READ);
>  	if (unlikely(!ctx)) {
>  		ntfs_error(vol->sb, "Failed to get search context.%s", es);
>  		NVolSetErrors(vol);
> @@ -1568,6 +1579,7 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
>  	s64 min_nr, nr, ll;
>  	unsigned long flags;
>  	struct ntfs_inode *mft_ni;
> +	struct ntfs_inode *attr_lock_ni;
>  	struct runlist_element *rl, *rl2;
>  	struct ntfs_attr_search_ctx *ctx = NULL;
>  	struct mft_record *mrec;
> @@ -1579,6 +1591,7 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
>  
>  	ntfs_debug("Extending mft data allocation.");
>  	mft_ni = NTFS_I(vol->mft_ino);
> +	attr_lock_ni = ntfs_attr_list_lock(mft_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  	/*
>  	 * Determine the preferred allocation location, i.e. the last lcn of
>  	 * the mft data attribute.  The allocated size of the mft data
> @@ -1592,6 +1605,7 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
>  			NTFS_B_TO_CLU(vol, ll - 1), NULL);
>  	if (IS_ERR(rl) || unlikely(!rl->length || rl->lcn < 0)) {
>  		up_write(&mft_ni->runlist.lock);
> +		ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  		ntfs_error(vol->sb,
>  			"Failed to determine last allocated cluster of mft data attribute.");
>  		if (!IS_ERR(rl))
> @@ -1622,6 +1636,7 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
>  			ntfs_warning(vol->sb,
>  				"Cannot allocate mft record because the maximum number of inodes (2^32) has already been reached.");
>  			up_write(&mft_ni->runlist.lock);
> +			ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  			return -ENOSPC;
>  		}
>  	}
> @@ -1646,6 +1661,7 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
>  			ntfs_error(vol->sb,
>  				"Failed to allocate the minimal number of clusters (%lli) for the mft data attribute.",
>  				nr);
> +			ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  			return PTR_ERR(rl2);
>  		}
>  		/*
> @@ -1661,6 +1677,7 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
>  	rl = ntfs_runlists_merge(&mft_ni->runlist, rl2, 0, &new_rl_count);
>  	if (IS_ERR(rl)) {
>  		up_write(&mft_ni->runlist.lock);
> +		ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  		ntfs_error(vol->sb, "Failed to merge runlists for mft data attribute.");
>  		if (ntfs_cluster_free_from_rl(vol, rl2)) {
>  			ntfs_error(vol->sb,
> @@ -1686,7 +1703,7 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
>  		down_write(&mft_ni->runlist.lock);
>  		goto undo_alloc;
>  	}
> -	ctx = ntfs_attr_get_search_ctx(mft_ni, mrec);
> +	ctx = ntfs_attr_get_search_ctx(mft_ni, mrec, NTFS_ATTR_CTX_LOCK_NONE);
>  	if (unlikely(!ctx)) {
>  		ntfs_error(vol->sb, "Failed to get search context.");
>  		ret = -ENOMEM;
> @@ -1782,6 +1799,7 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
>  	ntfs_attr_put_search_ctx(ctx);
>  	unmap_mft_record(mft_ni);
>  	ntfs_debug("Done.");
> +	ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  	return 0;
>  restore_undo_alloc:
>  	ntfs_attr_reinit_search_ctx(ctx);
> @@ -1795,6 +1813,7 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
>  		ntfs_attr_put_search_ctx(ctx);
>  		unmap_mft_record(mft_ni);
>  		up_write(&mft_ni->runlist.lock);
> +		ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  		/*
>  		 * The only thing that is now wrong is ->allocated_size of the
>  		 * base attribute extent which chkdsk should be able to fix.
> @@ -1814,7 +1833,7 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
>  		ntfs_error(vol->sb, "Failed to truncate mft data attribute runlist.%s", es);
>  		NVolSetErrors(vol);
>  	}
> -	if (mp_extended && ntfs_attr_update_mapping_pairs(mft_ni, 0)) {
> +	if (mp_extended && ntfs_attr_update_mapping_pairs_nolock(mft_ni, 0)) {
>  		ntfs_error(vol->sb, "Failed to restore mapping pairs.%s",
>  			   es);
>  		NVolSetErrors(vol);
> @@ -1843,6 +1862,7 @@ static int ntfs_mft_data_extend_allocation_nolock(struct ntfs_volume *vol)
>  	}
>  	if (!IS_ERR(mrec))
>  		unmap_mft_record(mft_ni);
> +	ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_WRITE);
>  	return ret;
>  }
>  
> @@ -2326,7 +2346,7 @@ int ntfs_mft_record_alloc(struct ntfs_volume *vol, const int mode,
>  		err = PTR_ERR(m);
>  		goto undo_data_init;
>  	}
> -	ctx = ntfs_attr_get_search_ctx(mft_ni, m);
> +	ctx = ntfs_attr_get_search_ctx(mft_ni, m, NTFS_ATTR_CTX_LOCK_READ);
>  	if (unlikely(!ctx)) {
>  		ntfs_error(vol->sb, "Failed to get search context.");
>  		err = -ENOMEM;
> @@ -2671,14 +2691,17 @@ int ntfs_mft_record_free(struct ntfs_volume *vol, struct ntfs_inode *ni)
>  static s64 lcn_from_index(struct ntfs_volume *vol, struct ntfs_inode *ni,
>  		unsigned long index)
>  {
> +	struct ntfs_inode *attr_lock_ni;
>  	s64 vcn;
>  	s64 lcn;
>  
>  	vcn = ntfs_pidx_to_cluster(vol, index);
>  
> +	attr_lock_ni = ntfs_attr_list_lock(ni, NTFS_ATTR_CTX_LOCK_READ);
>  	down_read(&ni->runlist.lock);
>  	lcn = ntfs_attr_vcn_to_lcn_nolock(ni, vcn, false);
>  	up_read(&ni->runlist.lock);
> +	ntfs_attr_list_unlock(attr_lock_ni, NTFS_ATTR_CTX_LOCK_READ);
>  
>  	return lcn;
>  }
> @@ -2788,14 +2811,20 @@ static int ntfs_write_mft_block(struct folio *folio, struct writeback_control *w
>  				bio = NULL;
>  			}
>  
> -			if (vol->cluster_size < folio_size(folio)) {
> -				down_write(&ni->runlist.lock);
> -				rl = ntfs_attr_vcn_to_rl(ni, vcn_off, &lcn);
> -				up_write(&ni->runlist.lock);
> -				if (IS_ERR(rl) || lcn < 0) {
> -					err = -EIO;
> -					goto unm_done;
> -				}
> +				if (vol->cluster_size < folio_size(folio)) {
> +					struct ntfs_inode *attr_lock_ni;
> +					enum ntfs_attr_search_ctx_lock_mode mode;
> +
> +					mode = NTFS_ATTR_CTX_LOCK_READ;
> +					attr_lock_ni = ntfs_attr_list_lock(ni, mode);
> +					down_write(&ni->runlist.lock);
> +					rl = ntfs_attr_vcn_to_rl(ni, vcn_off, &lcn);
> +					up_write(&ni->runlist.lock);
> +					ntfs_attr_list_unlock(attr_lock_ni, mode);
> +					if (IS_ERR(rl) || lcn < 0) {
> +						err = -EIO;
> +						goto unm_done;
> +					}
>  
>  				if (bio &&
>  				   (bio_end_sector(bio) >> (vol->cluster_size_bits - 9)) !=
> diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c
> index 96c450e62efca..6dd72071eb25c 100644
> --- a/fs/ntfs/namei.c
> +++ b/fs/ntfs/namei.c
> @@ -267,7 +267,7 @@ static struct dentry *ntfs_lookup(struct inode *dir_ino, struct dentry *dent,
>  				ctx = NULL;
>  				goto err_out;
>  			}
> -			ctx = ntfs_attr_get_search_ctx(ni, m);
> +			ctx = ntfs_attr_get_search_ctx(ni, m, NTFS_ATTR_CTX_LOCK_READ);
>  			if (unlikely(!ctx)) {
>  				err = -ENOMEM;
>  				goto err_out;
> @@ -856,7 +856,7 @@ static int ntfs_delete(struct ntfs_inode *ni, struct ntfs_inode *dir_ni,
>  	 * If filename in DOS or in WIN32 namespace, then remove DOS name first,
>  	 * only then remove WIN32 name.
>  	 */
> -	actx = ntfs_attr_get_search_ctx(ni, NULL);
> +	actx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_WRITE);
>  	if (!actx) {
>  		ntfs_error(sb, "%s, Failed to get search context", __func__);
>  		if (need_lock) {
> @@ -1618,7 +1618,7 @@ static struct dentry *ntfs_get_parent(struct dentry *child_dent)
>  	if (IS_ERR(mrec))
>  		return ERR_CAST(mrec);
>  	/* Find the first file name attribute in the mft record. */
> -	ctx = ntfs_attr_get_search_ctx(ni, mrec);
> +	ctx = ntfs_attr_get_search_ctx(ni, mrec, NTFS_ATTR_CTX_LOCK_READ);
>  	if (unlikely(!ctx)) {
>  		unmap_mft_record(ni);
>  		return ERR_PTR(-ENOMEM);
> diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c
> index 22dc7865eca79..4b558c058934a 100644
> --- a/fs/ntfs/super.c
> +++ b/fs/ntfs/super.c
> @@ -350,7 +350,7 @@ static int ntfs_write_volume_flags(struct ntfs_volume *vol, const __le16 flags)
>  	if (vol->vol_flags == flags)
>  		goto done;
>  
> -	ctx = ntfs_attr_get_search_ctx(ni, NULL);
> +	ctx = ntfs_attr_get_search_ctx(ni, NULL, NTFS_ATTR_CTX_LOCK_READ);
>  	if (!ctx) {
>  		err = -ENOMEM;
>  		goto put_unm_err_out;
> @@ -434,7 +434,7 @@ int ntfs_write_volume_label(struct ntfs_volume *vol, char *label)
>  	}
>  
>  	mutex_lock(&vol_ni->mrec_lock);
> -	ctx = ntfs_attr_get_search_ctx(vol_ni, NULL);
> +	ctx = ntfs_attr_get_search_ctx(vol_ni, NULL, NTFS_ATTR_CTX_LOCK_WRITE);
>  	if (!ctx) {
>  		ret = -ENOMEM;
>  		goto  out;
> @@ -1492,7 +1492,7 @@ static bool load_system_files(struct ntfs_volume *vol)
>  		goto volume_failed;
>  	}
>  
> -	ctx = ntfs_attr_get_search_ctx(NTFS_I(vol->vol_ino), m);
> +	ctx = ntfs_attr_get_search_ctx(NTFS_I(vol->vol_ino), m, NTFS_ATTR_CTX_LOCK_READ);
>  	if (!ctx) {
>  		ntfs_error(sb, "Failed to get attribute search context.");
>  		goto get_ctx_vol_failed;
> -- 
> 2.43.0

-- 
Thanks,
Hyunchul

  reply	other threads:[~2026-07-01  8:15 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-29  4:57 Cen Zhang
2026-07-01  8:15 ` Hyunchul Lee [this message]
2026-07-02 15:09   ` Cen Zhang

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=akTMnTvQBvOIrNr6@hyunchul-PC02 \
    --to=hyc.lee@gmail.com \
    --cc=baijiaju1990@gmail.com \
    --cc=linkinjeon@kernel.org \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=zzzccc427@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®