From: Sasha Levin <sashal@kernel.org>
To: stable@vger.kernel.org
Cc: Cen Zhang <zzzccc427@gmail.com>,
Andrew Morton <akpm@linux-foundation.org>,
Joseph Qi <joseph.qi@linux.alibaba.com>,
Mark Fasheh <mark@fasheh.com>, Joel Becker <jlbec@evilplan.org>,
Heming Zhao <heming.zhao@suse.com>,
Jia-Ju Bai <baijiaju1990@gmail.com>,
jjzuming@gmail.com, Sasha Levin <sashal@kernel.org>
Subject: [PATCH 6.12.y] ocfs2: validate move extents against a stable EOF
Date: Fri, 13 Nov 2026 10:07:58 -0500 [thread overview]
Message-ID: <20261113150736.1856589-1-sashal@kernel.org> (raw)
In-Reply-To: <20261109200821-failed-6.12-84db6619cb70@kernel.org>
From: Cen Zhang <zzzccc427@gmail.com>
[ Upstream commit 84db6619cb702c37cf848b450c21b9c9706b0775 ]
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.
Range errors now reach the ioctl's normal copyout path, as goal errors
already did. Unlike the old early rejection, a failed copyout can
therefore override -EINVAL with -EFAULT.
Link: https://lore.kernel.org/pm-ocfs2-move-extents-isize-rrw-v6-395bdee84ea81149068e@gmail.com
Fixes: 53069d4e7695 ("Ocfs2/move_extents: move/defrag extents within a certain range.")
Signed-off-by: Cen Zhang <zzzccc427@gmail.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Suggested-by: Joseph Qi <joseph.qi@linux.alibaba.com>
Reviewed-by: Joseph Qi <joseph.qi@linux.alibaba.com>
Assisted-by: LLM
Cc: Mark Fasheh <mark@fasheh.com>
Cc: Joel Becker <jlbec@evilplan.org>
Cc: Heming Zhao <heming.zhao@suse.com>
Cc: Jia-Ju Bai <baijiaju1990@gmail.com>
Cc: <jjzuming@gmail.com>
Cc: <stable@vger.kernel.org>
[ sashal: inlined the upstream range-validation helper into ocfs2_move_extents() to avoid adding a new function ]
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
fs/ocfs2/move_extents.c | 62 ++++++++++++++++++++---------------------
1 file changed, 31 insertions(+), 31 deletions(-)
diff --git a/fs/ocfs2/move_extents.c b/fs/ocfs2/move_extents.c
index decf14ea21478..279726e5f7cfe 100644
--- a/fs/ocfs2/move_extents.c
+++ b/fs/ocfs2/move_extents.c
@@ -906,8 +906,10 @@ static int __ocfs2_move_extents_range(struct buffer_head *di_bh,
static int ocfs2_move_extents(struct ocfs2_move_extents_context *context)
{
int status;
+ u64 i_size;
handle_t *handle;
struct inode *inode = context->inode;
+ struct ocfs2_move_extents *range = context->range;
struct ocfs2_dinode *di;
struct buffer_head *di_bh = NULL;
struct ocfs2_super *osb = OCFS2_SB(inode->i_sb);
@@ -932,6 +934,35 @@ static int ocfs2_move_extents(struct ocfs2_move_extents_context *context)
goto out_rw_unlock;
}
+ i_size = i_size_read(inode);
+ if (range->me_start > i_size) {
+ status = -EINVAL;
+ mlog_errno(status);
+ goto out_inode_unlock;
+ }
+
+ 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;
+
+ /* 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) {
+ mlog_errno(status);
+ goto out_inode_unlock;
+ }
+ }
+
/*
* rememer ip_xattr_sem also needs to be held if necessary
*/
@@ -1024,49 +1055,18 @@ 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;
- }
-
- if (range.me_start + range.me_len > i_size_read(inode))
- range.me_len = i_size_read(inode) - range.me_start;
-
context->range = ⦥
- /*
- * ok, the default theshold 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) {
context->auto_defrag = 1;
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
--
2.53.0
prev parent reply other threads:[~2026-11-13 15:08 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-10 1:08 FAILED: patch "[PATCH] ocfs2: validate move extents against a stable EOF" failed to apply to 6.12-stable tree Sasha Levin
2026-11-13 15:07 ` Sasha Levin [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=20261113150736.1856589-1-sashal@kernel.org \
--to=sashal@kernel.org \
--cc=akpm@linux-foundation.org \
--cc=baijiaju1990@gmail.com \
--cc=heming.zhao@suse.com \
--cc=jjzuming@gmail.com \
--cc=jlbec@evilplan.org \
--cc=joseph.qi@linux.alibaba.com \
--cc=mark@fasheh.com \
--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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.