From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=100.103.45.18 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1794701535; b=0kgsbqFPRUyubWcn8sx69vFYw9nFBaH7NdMeoYYUZZJ66BsOZGVfeE27GFMOiU48WWlN 9vPsnwAxRjFlF4k+ue6OftN2iwLdS6kFZL8XWyKnKh79+oBIbGpa6REXYcF67KnhmlwVO UHniAeQOAZ12HL8oaKZP4W0JaZtE5Zd+knt2fTRMwRpu1Jh/HD6BgXpMa10i9jbregA42 h60MrLW+liVwbB+GqLIpHWJ9v8wWMq6GOIJdSnqm7H4SQg9HnAiIyc+XzPYVNP8E0Hw6R UnV4o0B0Ah5ve90EnVPX991t51KJyJYoF9VAlSzUtHX8hCKCMWd5J78vX/brAl3k9CQ== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1794701535; h=Received:DKIM-Signature:From:To:Cc:Subject:Date:Message-ID:X-Mailer: In-Reply-To:References:MIME-Version:Content-Transfer-Encoding; bh=hywaSbrPVzpNYOJfDLZfDZOtqgBZTTztHRmB4EsjRcQ=; b=sH3O8Y6YHaRDj9HxdB5mPHn3ELylqYHzO9RXRyK0NQ01ZrsWFyuaOT3m6hMf8vStjrS7 QdMgUvUpuGkSjZN0iAS8JieqxbdLNaDTo9ikbfXmdGS6Iu7zGLJ9pFcl2uOTT1I0neP3A HBdAZxT4gVoGF0/fT4EsciYJ/ZG6Zx/8suowLXWr08PuqCpUwwo5V9HHCoWvpzKVwyDSY waOAM7YIdIZIDyzBgK+oqlXYxASov/vw1s8M6KR/H2GyTHcjfjtUNOZCe1d8/Q78LFwQ/ 7JHfkun3kjQCPuaez3b2LU1NrYLxkkZo3GskrTFFMk6zX48CygM1067dSccdnhkw8Iw== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256 header.s=k20260515 header.b=nAJRNzhF; arc=none smtp.remote-ip=100.103.45.18 Received: from smtp.kernel.org (quasi.space.kernel.org [100.103.45.18]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id AF9C9606A323 for ; Sun, 15 Nov 2026 00:12:15 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256 header.s=k20260515 header.b=nAJRNzhF Received: by smtp.kernel.org (Postfix) with ESMTPSA id 7D8041F000C5; Sun, 15 Nov 2026 00:12:14 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1794701535; bh=hywaSbrPVzpNYOJfDLZfDZOtqgBZTTztHRmB4EsjRcQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=nAJRNzhF7KgjA+6nfbnTflPsMZIQO1zRqaOqGBWSqcCNNGZwC5TrCAH3RnKPCCjOZ 51GtOJa7X1oaN1DD/7ndVAMfrfz9SeDf2yZgB7qp/zt8t1RaCpyEENOup9+/2G4O1x 7KRkXko47BJqyBUleuObn8X3s5IXSO9QmiCXrBXYWh4WpoV4B/5aHvHTEofnQ896dV sWAKVNa+VH+c2QzMAw2Hr6OgGhbGQBtOZVNbTAKLudrUgPfe1qt1+UaS6UZJdql9hy dz6tQS6VnYw2DyRDI6zHQEY8POr2bExpn6zy5sE6ncoWuSXtKVyaA4KblodGRv3bGY B8SMgkpNmHQSw== From: Sasha Levin To: stable@vger.kernel.org Cc: Cen Zhang , Andrew Morton , Joseph Qi , Mark Fasheh , Joel Becker , Heming Zhao , Jia-Ju Bai , jjzuming@gmail.com, Sasha Levin Subject: [PATCH 6.6.y] ocfs2: validate move extents against a stable EOF Date: Sat, 14 Nov 2026 19:12:13 -0500 Message-ID: <20261115001146.468298-1-sashal@kernel.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20261109201244-failed-6.6-84db6619cb70@kernel.org> References: <20261109201244-failed-6.6-84db6619cb70@kernel.org> X-Mailing-List: stable@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Transfer-Encoding: 8bit From: Cen Zhang [ 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 Signed-off-by: Andrew Morton Suggested-by: Joseph Qi Reviewed-by: Joseph Qi Assisted-by: LLM Cc: Mark Fasheh Cc: Joel Becker Cc: Heming Zhao Cc: Jia-Ju Bai Cc: Cc: 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. [ sashal: inlined range validation into ocfs2_move_extents() instead of adding a separate helper ] Signed-off-by: Sasha Levin --- 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 fc9aa015e05ec..0fbcb9dbdab45 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