mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Joseph Qi <joseph.qi@linux.alibaba.com>
To: Cen Zhang <zzzccc427@gmail.com>
Cc: mark@fasheh.com, jlbec@evilplan.org, heming.zhao@suse.com,
	ocfs2-devel@lists.linux.dev, linux-kernel@vger.kernel.org,
	stable@vger.kernel.org, baijiaju1990@gmail.com,
	jjzuming@gmail.com
Subject: Re: [PATCH v2] ocfs2: validate move extents against a stable EOF
Date: Mon, 26 Oct 2026 17:59:59 +0800	[thread overview]
Message-ID: <87a005a4-c61f-4525-a0d1-89bfc6a8f75d@linux.alibaba.com> (raw)
In-Reply-To: <pm-ocfs2-move-extents-isize-rrw-v4-d4b80ed0748f84dc3f8e@gmail.com>



On 10/25/26 12:20 AM, Cen Zhang wrote:
> The move-extents range must be bounded by the inode size used for extent
> movement. ocfs2_ioctl_move_extents() checks and clamps the request before
> taking the local inode and cluster locks in ocfs2_move_extents(). It also
> uses separate i_size_read() calls for the range check and subtraction.
> 
> A concurrent truncate can invalidate the first check and make the later
> unsigned subtraction underflow:
> 
>   Move-extents ioctl                  Truncate
>   ------------------                  --------
>   check me_start <= i_size
>                                       take inode and cluster locks
>                                       shrink EOF below me_start
>                                       release locks
>   compare me_start + me_len with EOF
>   set me_len = EOF - me_start
>     [unsigned underflow]
>   acquire inode and cluster locks
>   select extents using the bad range
> 
> A truncate after clamping can also leave the range extending beyond EOF.
> Conversely, the cached size may be smaller than the size refreshed when
> the cluster inode lock is acquired. Early validation can then reject a
> valid start or permanently shorten the requested length and threshold.
> The me_start + me_len comparison can also wrap for a large input length.
> 
> Validate and adjust the original request once, after inode_lock(), the
> exclusive rw lock and ocfs2_inode_lock() have stabilized and refreshed
> the size. Read EOF once, reject a start beyond it, and bound the length
> by EOF minus start. Normalize the threshold using the same EOF snapshot.
> This keeps zero as the default-threshold sentinel until validation uses
> the size that will govern extent movement.
> 
> Move goal validation after range adjustment as well. Its allocation-group
> capacity check uses me_len and must see the EOF-clamped length. Both
> validation failures use the existing inode-lock unwind path before any
> extent movement or transaction starts.
> 
> Fixes: 53069d4e7695 ("Ocfs2/move_extents: move/defrag extents within a certain range.")
> Cc: stable@vger.kernel.org
> Assisted-by: LLM
> Signed-off-by: Cen Zhang <zzzccc427@gmail.com>
> ---
> Changes in v2:
> - Validate and normalize the original range only once, after inode and
>   cluster locking have stabilized and refreshed EOF.
> - Move goal validation after range adjustment because its allocation-group
>   capacity check depends on me_len.
> 
> Link to v1: https://lore.kernel.org/r/pm-ocfs2-move-extents-isize-rrw-v3-b2a7508c41108cc2d00b@gmail.com
> 
>  fs/ocfs2/move_extents.c | 68 ++++++++++++++++++++++-------------------
>  1 file changed, 37 insertions(+), 31 deletions(-)
> 
> diff --git a/fs/ocfs2/move_extents.c b/fs/ocfs2/move_extents.c
> index ad1678ee7cc4..f2682ef6bf2b 100644
> --- a/fs/ocfs2/move_extents.c
> +++ b/fs/ocfs2/move_extents.c
> @@ -907,6 +907,30 @@ out:
>  	return ret;
>  }
>  
> +static int ocfs2_validate_and_adjust_move_range(struct inode *inode,
> +						struct ocfs2_move_extents *range)
> +{
> +	u64 i_size = i_size_read(inode);
> +
> +	if (range->me_start > i_size)
> +		return -EINVAL;
> +
> +	if (range->me_len > i_size - range->me_start)
> +		range->me_len = i_size - range->me_start;
> +
> +	/*
> +	 * The default threshold for defragmentation is 1 MiB, which
> +	 * matches the maximum OCFS2 cluster size.
> +	 */
> +	if (!range->me_threshold)
> +		range->me_threshold = 1024 * 1024;
> +
> +	if (range->me_threshold > i_size)
> +		range->me_threshold = i_size;
> +
> +	return 0;
> +}
> +
>  static int ocfs2_move_extents(struct ocfs2_move_extents_context *context)
>  {
>  	int status;
> @@ -936,6 +960,19 @@ static int ocfs2_move_extents(struct ocfs2_move_extents_context *context)
>  		goto out_rw_unlock;
>  	}
>  
> +	status = ocfs2_validate_and_adjust_move_range(inode, context->range);
> +	if (status) {
> +		mlog_errno(status);
> +		goto out_inode_unlock;
> +	}
> +
> +	/* The goal check depends on the adjusted range length. */
> +	if (!context->auto_defrag) {
> +		status = ocfs2_validate_and_adjust_move_goal(inode, context->range);
> +		if (status)
> +			goto out_inode_unlock;

Seems we'd better log the failure like ocfs2_validate_and_adjust_move_range().

> +	}
> +
>  	/*
>  	 * remember ip_xattr_sem also needs to be held if necessary
>  	 */
> @@ -1028,27 +1065,8 @@ int ocfs2_ioctl_move_extents(struct file *filp, void __user *argp)
>  		goto out_free;
>  	}
>  
> -	if (range.me_start > i_size_read(inode)) {
> -		status = -EINVAL;
> -		goto out_free;
> -	}

This seems changes the return value. It returned -EINVAL without touching
the caller's buffer. It now unwinds through out_inode_unlock and falls into
the copy back and returns -EFAULT instead of -EINVAL.

Seems not a bug since the goal validation failure already behaved this way,
but worth a line in the commit log.

Thanks,
Joseph

> -
> -	if (range.me_start + range.me_len > i_size_read(inode))
> -			range.me_len = i_size_read(inode) - range.me_start;
> -
>  	context->range = &range;
>  
> -	/*
> -	 * ok, the default threshold for the defragmentation
> -	 * is 1M, since our maximum clustersize was 1M also.
> -	 * any thought?
> -	 */
> -	if (!range.me_threshold)
> -		range.me_threshold = 1024 * 1024;
> -
> -	if (range.me_threshold > i_size_read(inode))
> -		range.me_threshold = i_size_read(inode);
> -
>  	if (range.me_flags & ~(OCFS2_MOVE_EXT_FL_AUTO_DEFRAG |
>  			       OCFS2_MOVE_EXT_FL_PART_DEFRAG)) {
>  		status = -EINVAL;
> @@ -1060,23 +1078,11 @@ int ocfs2_ioctl_move_extents(struct file *filp, void __user *argp)
>  
>  		if (range.me_flags & OCFS2_MOVE_EXT_FL_PART_DEFRAG)
>  			context->partial = 1;
> -	} else {
> -		/*
> -		 * first best-effort attempt to validate and adjust the goal
> -		 * (physical address in block), while it can't guarantee later
> -		 * operation can succeed all the time since global_bitmap may
> -		 * change a bit over time.
> -		 */
> -
> -		status = ocfs2_validate_and_adjust_move_goal(inode, &range);
> -		if (status)
> -			goto out_copy;
>  	}
>  
>  	status = ocfs2_move_extents(context);
>  	if (status)
>  		mlog_errno(status);
> -out_copy:
>  	/*
>  	 * movement/defragmentation may end up being partially completed,
>  	 * that's the reason why we need to return userspace the finished


      reply	other threads:[~2026-10-26 10:00 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-24 16:20 Cen Zhang
2026-10-26  9:59 ` Joseph Qi [this message]

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=87a005a4-c61f-4525-a0d1-89bfc6a8f75d@linux.alibaba.com \
    --to=joseph.qi@linux.alibaba.com \
    --cc=baijiaju1990@gmail.com \
    --cc=heming.zhao@suse.com \
    --cc=jjzuming@gmail.com \
    --cc=jlbec@evilplan.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mark@fasheh.com \
    --cc=ocfs2-devel@lists.linux.dev \
    --cc=stable@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®