mext_check_adjust_range() clamps the requested EXT4_IOC_MOVE_EXT range to the end of the original and donor files with: else if (orig_eof < orig_start + *len - 1) *len = orig_eof - orig_start; orig_eof and donor_eof are exclusive (the first logical block past i_size), so the range [start, start + len) runs past EOF as soon as start + len > eof. The extra "- 1" has two consequences: - A range that ends exactly one block past EOF is not clamped, so e.g. orig_start = 0, len = 2 on a one-block file walks and swaps a block beyond i_size. - A zero length makes the unsigned "start + len - 1" wrap to ULLONG_MAX when start is 0. The comparison is then always true and len is "clamped" to the full file size, so the later !*len check never fires and a request to move zero blocks moves every block of the file instead of failing with -EINVAL. Compare against start + len instead. With that, a zero length is left untouched by the clamping (eof > start in that branch) and is rejected by the existing !*len check, so no separate check is needed. The sums cannot overflow because start and len were already bounded by EXT_MAX_BLOCKS above. Extend test_mext_check_adjust_range to cover a zero length and ranges that end exactly one block past EOF. Fixes: fcf6b1b729bc ("ext4: refactor ext4_move_extents code base") Signed-off-by: Theodore Ts'o --- fs/ext4/move_extent-test.c | 21 +++++++++++++++++++++ fs/ext4/move_extent.c | 4 ++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/fs/ext4/move_extent-test.c b/fs/ext4/move_extent-test.c index e29a392eeebe..dded52fd0b5f 100644 --- a/fs/ext4/move_extent-test.c +++ b/fs/ext4/move_extent-test.c @@ -802,6 +802,27 @@ static void test_mext_check_adjust_range(struct kunit *test) ret = ext4_mext_check_adjust_range_test(orig, donor, 4, 4, &len); KUNIT_EXPECT_EQ(test, ret, -EINVAL); KUNIT_EXPECT_EQ(test, len, 0ULL); + + /* A zero length must be rejected, not wrapped to the whole file */ + len = 0; + ret = ext4_mext_check_adjust_range_test(orig, donor, 0, 0, &len); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); + KUNIT_EXPECT_EQ(test, len, 0ULL); + + /* A range ending exactly one block past EOF must be clamped */ + i_size_write(orig, MEXT_TEST_BLK_SIZE); /* orig_eof = 1 */ + i_size_write(donor, MEXT_TEST_BLK_SIZE); /* donor_eof = 1 */ + len = 2; + ret = ext4_mext_check_adjust_range_test(orig, donor, 0, 0, &len); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, len, 1ULL); + + i_size_write(orig, 4 * MEXT_TEST_BLK_SIZE); /* orig_eof = 4 */ + i_size_write(donor, 8 * MEXT_TEST_BLK_SIZE); /* donor_eof = 8 */ + len = 4; + ret = ext4_mext_check_adjust_range_test(orig, donor, 1, 1, &len); + KUNIT_EXPECT_EQ(test, ret, 0); + KUNIT_EXPECT_EQ(test, len, 3ULL); } static void test_move_extents_holes_and_unwritten(struct kunit *test) diff --git a/fs/ext4/move_extent.c b/fs/ext4/move_extent.c index 49adc348dda0..5ee51fde1819 100644 --- a/fs/ext4/move_extent.c +++ b/fs/ext4/move_extent.c @@ -545,11 +545,11 @@ static int mext_check_adjust_range(struct inode *orig_inode, donor_eof = EXT4_B_TO_LBLK(donor_inode, i_size_read(donor_inode)); if (orig_eof <= orig_start) *len = 0; - else if (orig_eof < orig_start + *len - 1) + else if (orig_eof < orig_start + *len) *len = orig_eof - orig_start; if (donor_eof <= donor_start) *len = 0; - else if (donor_eof < donor_start + *len - 1) + else if (donor_eof < donor_start + *len) *len = donor_eof - donor_start; if (!*len) { ext4_debug("ext4 move extent: len should not be 0 [ino:orig %llu, donor %llu]\n", -- 2.56.0.542.g3ef89b7020-goog