mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Hyunchul Lee <hyc.lee@gmail.com>
To: Matthias Goergens <matthias.goergens@gmail.com>
Cc: Namjae Jeon <linkinjeon@kernel.org>,
	ntfs@lists.linux.dev, linux-kernel@vger.kernel.org
Subject: Re: [PATCH v2 4/6] ntfs: do not map a vcn as a hole when its runlist lookup failed
Date: Mon, 28 Sep 2026 10:09:16 +0900	[thread overview]
Message-ID: <arm-PN-hKgF2U_Qr@hyunchul-PC02> (raw)
In-Reply-To: <20260927050831.2739166-4-matthias.goergens@gmail.com>

On Sun, Sep 27, 2026 at 01:08:23PM +0800, Matthias Goergens wrote:
> ntfs_attr_vcn_to_rl() retries ntfs_map_runlist_nolock() for any lcn up
> to LCN_RL_NOT_MAPPED, which includes LCN_ENOENT, but turns a failed
> retry into an error only for LCN_RL_NOT_MAPPED.  For LCN_ENOENT the
> error is dropped and the read path maps the range as a hole.
> 
> An LCN_ENOENT below allocated_size comes from a base extent with a
> highest_vcn of 0, which ntfs_mapping_pairs_decompress() takes to map the
> whole attribute, so the runlist ends after its last mapping pair.  If
> the pairs end early, the retry finds the same extent and fails with
> -ENOENT.  On a crafted volume with 4 KiB clusters, a 64-cluster file
> whose mapping pairs stop after 16 clusters reads 48 clusters of zeros,
> with no error.
> 
> The same layout gets a crafted $MFT past the check from "ntfs: fail the
> mount when $MFT needs its own extent records".  With 512-byte clusters
> and $MFT's mapping pairs ending at vcn 4, an unpatched kernel hangs on
> the folio lock reading records 0-3.  With the check alone, the -EIO is
> dropped, records 2 and 3 read as zeros and the mount carries on until
> check_mft_mirror() finds the zeroed record 2.
> 
> Fail the lookup whenever the retry leaves @vcn unmapped, -ENOENT
> included.  At or beyond allocated_size nothing is mapped, so do not
> retry there: the runlist ends with LCN_ENOENT, or with LCN_RL_NOT_MAPPED
> when only the last extent is mapped, as after a write into it, and with
> clusters smaller than a page every read of a file's last folio looks up
> such vcns.
> 
> A failed expansion in ntfs_non_resident_attr_expand() or
> ntfs_attrlist_repack() truncates the runlist under the runlist lock but
> restores allocated_size only after dropping it.  A lookup in between
> would now fail, so restore allocated_size under the lock in both.
> 
> The crafted file now fails from vcn 16 on with -EIO, and the crafted
> volume fails to mount with the check's message.
> 
> Fixes: 495e90fa3348 ("ntfs: update attrib operations")
> Cc: stable@vger.kernel.org
> Signed-off-by: Matthias Goergens <matthias.goergens@gmail.com>
> ---
>  fs/ntfs/attrib.c   | 38 +++++++++++++++++++++++++++++++-------
>  fs/ntfs/attrlist.c |  8 ++++++--
>  2 files changed, 37 insertions(+), 9 deletions(-)
> 
> diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c
> index eab4d8d32132f..30d3d2eb5ef3c 100644
> --- a/fs/ntfs/attrib.c
> +++ b/fs/ntfs/attrib.c
> @@ -344,6 +344,23 @@ struct runlist_element *ntfs_attr_vcn_to_rl(struct ntfs_inode *ni, s64 vcn, s64
>  		rl++;
>  	*lcn = ntfs_rl_vcn_to_lcn(rl, vcn);
>  
> +	/*
> +	 * Nothing is mapped at or beyond the allocated size: the runlist ends
> +	 * there with LCN_ENOENT, or with LCN_RL_NOT_MAPPED if only a later
> +	 * extent has been mapped.  Return that end as it is.  Below the
> +	 * allocated size, an unmapped vcn is worth a retry.
> +	 */
> +	if (*lcn <= LCN_RL_NOT_MAPPED && !is_retry) {
> +		unsigned long flags;
> +		s64 allocated_vcn;
> +
> +		read_lock_irqsave(&ni->size_lock, flags);
> +		allocated_vcn = ntfs_bytes_to_cluster(ni->vol, ni->allocated_size);
> +		read_unlock_irqrestore(&ni->size_lock, flags);
> +		if (vcn >= allocated_vcn)
> +			return rl;
> +	}
> +

Could you merge the above if statement with the one below? Both
statments use the same condition.

>  	if (*lcn <= LCN_RL_NOT_MAPPED && is_retry == false) {
>  		is_retry = true;
>  		err = ntfs_map_runlist_nolock(ni, vcn, NULL);
> @@ -354,11 +371,14 @@ struct runlist_element *ntfs_attr_vcn_to_rl(struct ntfs_inode *ni, s64 vcn, s64
>  	}
>  
>  	/*
> -	 * The runlist fragment containing @vcn could not be mapped, e.g.
> -	 * because the extent mft record holding it is corrupt.  Do not hand
> -	 * LCN_RL_NOT_MAPPED back to callers, which would treat it as a hole.
> +	 * Neither the runlist nor the retry mapped @vcn, which lies below the
> +	 * allocated size, e.g. because the extent mft record holding it is
> +	 * corrupt or because the mapping pairs end too soon.
> +	 * ntfs_map_runlist_nolock() reports the latter as -ENOENT, as @vcn
> +	 * lies past the extent it found.  Callers would treat
> +	 * LCN_RL_NOT_MAPPED or LCN_ENOENT here as a hole, so fail instead.
>  	 */
> -	if (*lcn == LCN_RL_NOT_MAPPED)
> +	if (*lcn <= LCN_RL_NOT_MAPPED)
>  		return ERR_PTR(err == -ENOMEM ? -ENOMEM : -EIO);
>  
>  	return rl;
> @@ -4703,11 +4723,17 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz
>  	if (err2)
>  		ntfs_debug("Leaking clusters");
>  
> -	/* Now, truncate the runlist itself. */
> +	/*
> +	 * Now, truncate the runlist itself.  Restore allocated_size before
> +	 * dropping the lock: ntfs_attr_vcn_to_rl() fails a lookup below the
> +	 * allocated size that falls past the end of the runlist.
> +	 */
>  	if (ni != locked_ni)
>  		down_write(&ni->runlist.lock);
>  	err2 = ntfs_rl_truncate_nolock(vol, &ni->runlist,
>  			ntfs_bytes_to_cluster(vol, org_alloc_size));
> +	if (!err2)
> +		ni->allocated_size = org_alloc_size;

We should protect it with ni->size_lock.

>  	if (ni != locked_ni)
>  		up_write(&ni->runlist.lock);
>  	if (err2) {
> @@ -4719,8 +4745,6 @@ static int ntfs_non_resident_attr_expand(struct ntfs_inode *ni, const s64 newsiz
>  		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 (ni != locked_ni)
>  			down_read(&ni->runlist.lock);
> diff --git a/fs/ntfs/attrlist.c b/fs/ntfs/attrlist.c
> index 1bbd2bc62c582..3660e7fd24b13 100644
> --- a/fs/ntfs/attrlist.c
> +++ b/fs/ntfs/attrlist.c
> @@ -168,14 +168,18 @@ static int ntfs_attrlist_repack(struct inode *attr_vi,
>  	return 0;
>  
>  restore_old_runlist:
> +	/*
> +	 * Restore allocated_size before dropping the runlist lock:
> +	 * ntfs_attr_vcn_to_rl() fails a lookup below the allocated size that
> +	 * falls past the end of the runlist.
> +	 */
>  	down_write(&attr_ni->runlist.lock);
>  	attr_ni->runlist.rl = old_rl;
>  	attr_ni->runlist.count = old_rl_count;
> -	up_write(&attr_ni->runlist.lock);
> -
>  	write_lock_irqsave(&attr_ni->size_lock, flags);
>  	attr_ni->allocated_size = old_alloc_size;
>  	write_unlock_irqrestore(&attr_ni->size_lock, flags);
> +	up_write(&attr_ni->runlist.lock);
>  
>  	restore_err = ntfs_attr_update_mapping_pairs_locked(
>  			attr_ni, 0, locked_ni);
> -- 
> 2.55.0
> 

-- 
Thanks,
Hyunchul

  reply	other threads:[~2026-09-28  1:09 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-22 15:39 [PATCH] ntfs: fail the mount when $MFT needs its own extent records Matthias Goergens
2026-09-23  1:16 ` Hyunchul Lee
2026-09-27  5:08   ` [PATCH v2 0/6] ntfs: fix the $MFT bootstrap hang and reads of unmapped runlist ranges Matthias Goergens
2026-09-27  5:08     ` [PATCH v2 1/6] ntfs: do not map an unmappable runlist fragment as a hole Matthias Goergens
2026-09-27 23:03       ` liubaolin
2026-09-27  5:08     ` [PATCH v2 2/6] ntfs: do not turn an unmappable runlist fragment into delalloc on write Matthias Goergens
2026-09-28  0:21       ` Hyunchul Lee
2026-09-28  1:46         ` Hyunchul Lee
2026-09-27  5:08     ` [PATCH v2 3/6] ntfs: fail the mount when $MFT needs its own extent records Matthias Goergens
2026-09-27  5:08     ` [PATCH v2 4/6] ntfs: do not map a vcn as a hole when its runlist lookup failed Matthias Goergens
2026-09-28  1:09       ` Hyunchul Lee [this message]
2026-09-27  5:08     ` [PATCH v2 5/6] ntfs: fail the mount when $MFT's data size exceeds its allocation Matthias Goergens
2026-09-27  5:08     ` [PATCH v2 6/6] ntfs: reject non-resident attributes whose sizes exceed their allocation Matthias Goergens
2026-09-28  1:42       ` Hyunchul Lee

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=arm-PN-hKgF2U_Qr@hyunchul-PC02 \
    --to=hyc.lee@gmail.com \
    --cc=linkinjeon@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=matthias.goergens@gmail.com \
    --cc=ntfs@lists.linux.dev \
    /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®