* [PATCH] ext4: fix the logical block counter overflow in indirect migration
@ 2026-09-14 6:55 Yichong Chen
2026-09-14 7:18 ` sashiko-bot
2026-09-14 9:57 ` Jan Kara
0 siblings, 2 replies; 4+ messages in thread
From: Yichong Chen @ 2026-09-14 6:55 UTC (permalink / raw)
To: Theodore Ts'o
Cc: linux-ext4, linux-kernel, Andreas Dilger, Baokun Li, Jan Kara,
Ojaswin Mujoo, Ritesh Harjani, Zhang Yi, Aneesh Kumar K . V,
Yichong Chen
update_tind_extent_range() advances lb->curr_block, an ext4_lblk_t, by
max_entries * max_entries for every empty triple-indirect slot. One
triple-indirect block spans max_entries^3 logical blocks, which exceeds
2^32 as soon as the block size is 8K or larger (16384^3 = 2^42 with 64K
blocks), so the counter wraps while that block is walked.
A wrapped counter makes the migration store a block number that is 2^32
blocks away from the one the pointer block describes. Two ranges can then
end up with the same ee_block, which trips
BUG_ON(newext->ee_block == nearex->ee_block) in ext4_ext_insert_extent(),
and without that collision the data is still moved to the wrong logical
block while the migration reports success.
Keep the counter in 64 bit so that it cannot wrap, and refuse the
migration with -EOPNOTSUPP when a data block is found after the last
logical block an extent can describe, which only a corrupt block map can
contain.
Fixes: c14c6fd5c56a ("ext4: Add EXT4_IOC_MIGRATE ioctl")
Signed-off-by: Yichong Chen <chenyichong@uniontech.com>
---
fs/ext4/migrate.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/fs/ext4/migrate.c b/fs/ext4/migrate.c
index e06d847033a1..8043959c19ef 100644
--- a/fs/ext4/migrate.c
+++ b/fs/ext4/migrate.c
@@ -14,7 +14,7 @@
* represented by a single extent
*/
struct migrate_struct {
- ext4_lblk_t first_block, last_block, curr_block;
+ u64 first_block, last_block, curr_block;
ext4_fsblk_t first_pblock, last_pblock;
};
@@ -65,6 +65,10 @@ static int update_extent_range(handle_t *handle, struct inode *inode,
ext4_fsblk_t pblock, struct migrate_struct *lb)
{
int retval;
+
+ if (lb->curr_block > (ext4_lblk_t)-1)
+ return -EOPNOTSUPP;
+
/*
* See if we can add on to the existing range (if it exists)
*/
--
2.51.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH] ext4: fix the logical block counter overflow in indirect migration
2026-09-14 6:55 [PATCH] ext4: fix the logical block counter overflow in indirect migration Yichong Chen
@ 2026-09-14 7:18 ` sashiko-bot
2026-09-14 7:38 ` Yichong Chen
2026-09-14 9:57 ` Jan Kara
1 sibling, 1 reply; 4+ messages in thread
From: sashiko-bot @ 2026-09-14 7:18 UTC (permalink / raw)
To: Yichong Chen; +Cc: linux-ext4, tytso
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
New issues:
- [High] The bounds check for `curr_block` incorrectly allows migrating a logical block at `EXT_MAX_BLOCKS` (`0xffffffff`), which is a reserved sentinel in extent trees.
Pre-existing issues:
- [Critical] Unbounded extent length accumulation in `update_extent_range()` leads to `ee_len` overflow and data corruption.
- [Critical] If journal credit extension fails after successfully swapping inode data, `tmp_inode` is evicted with its extent tree intact, erroneously freeing the migrated blocks.
--
commit 09fb778181d3ee108e977c6ec47d3771e863f54a
Author: Yichong Chen <chenyichong@uniontech.com>
ext4: fix the logical block counter overflow in indirect migration
update_tind_extent_range() advances lb->curr_block by max_entries *
max_entries for every empty triple-indirect slot. This fixes the block
counter overflow by keeping the counter in 64-bit and refusing migration
with -EOPNOTSUPP when a data block is found after the last logical block.
> diff --git a/fs/ext4/migrate.c b/fs/ext4/migrate.c
> index c5e3fc998211a..dbf13d47aae4c 100644
> --- a/fs/ext4/migrate.c
> +++ b/fs/ext4/migrate.c
[ ... ]
> @@ -66,6 +66,10 @@ static int update_extent_range(handle_t *handle, struct inode *inode,
> ext4_fsblk_t pblock, struct migrate_struct *lb)
> {
> int retval;
> +
> + if (lb->curr_block > (ext4_lblk_t)-1)
> + return -EOPNOTSUPP;
> +
[Severity: High]
Does this bounds check correctly handle the EXT_MAX_BLOCKS sentinel?
Since EXT_MAX_BLOCKS is 0xffffffff, when lb->curr_block is exactly
0xffffffff, the condition 0xffffffff > 0xffffffff evaluates to false.
This successfully allows the logical block to be migrated and inserted into
the extent tree with ee_block = 0xffffffff, which subsequent block mapping
operations will reject with -EFSCORRUPTED.
Should this use >= to properly reject the EXT_MAX_BLOCKS sentinel?
> /*
> * See if we can add on to the existing range (if it exists)
> */
> if (lb->first_pblock &&
> (lb->last_pblock+1 == pblock) &&
> (lb->last_block+1 == lb->curr_block)) {
> lb->last_pblock = pblock;
> lb->last_block = lb->curr_block;
> lb->curr_block++;
> return 0;
> }
[Severity: Critical]
This is a pre-existing issue, but does this loop lack bounds checking against
the maximum extent length (EXT_INIT_MAX_LEN)?
Because contiguous blocks are accumulated here indefinitely, if the run of
blocks becomes > 32768, could this overflow the 16-bit ee_len field when
written in finish_range()?
fs/ext4/migrate.c:finish_range() {
...
newext.ee_len = cpu_to_le16(lb->last_block - lb->first_block + 1);
...
}
An overflow could set the MSB (0x8000), marking the extent as unwritten, or
wrap past 65535, truncating the length. This silently corrupts the migrated
file by replacing valid user data with zeros or unmapping it entirely.
[ ... ]
[Severity: Critical]
This is also a pre-existing issue, but what happens if journal credit
extension fails after successfully swapping inode data in ext4_ext_migrate()?
fs/ext4/migrate.c:ext4_ext_migrate() {
...
else {
retval = ext4_ext_swap_inode_data(handle, inode, tmp_inode);
if (retval)
free_ext_block(handle, tmp_inode);
}
retval = ext4_journal_ensure_credits(handle, 1, 0);
if (retval < 0)
goto out_stop;
...
}
If ext4_journal_ensure_credits() fails, the error path jumps to out_stop
without clearing tmp_inode's i_data array. Does this leave tmp_inode to
be evicted with its extent tree intact?
Since the extent tree now points to the original file's data blocks,
will this erroneously free the migrated blocks during tmp_inode eviction,
leading to a use-after-free and cross-file data corruption?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260914065544.3438431-1-chenyichong@uniontech.com?part=1
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH] ext4: fix the logical block counter overflow in indirect migration
2026-09-14 7:18 ` sashiko-bot
@ 2026-09-14 7:38 ` Yichong Chen
0 siblings, 0 replies; 4+ messages in thread
From: Yichong Chen @ 2026-09-14 7:38 UTC (permalink / raw)
To: sashiko-bot; +Cc: chenyichong, linux-ext4, sashiko-reviews, tytso
Hi,
Thanks for the review.
The EXT_MAX_BLOCKS point is valid. An extent may not cover that block,
because ext4_valid_extent() rejects a wrapping ee_block + ee_len, so
ext4_iget() would drop the migrated inode as corrupt. The bound has to be
>= EXT_MAX_BLOCKS, not > (ext4_lblk_t)-1. I will send a v2 with that fixed.
Thanks,
Yichong
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH] ext4: fix the logical block counter overflow in indirect migration
2026-09-14 6:55 [PATCH] ext4: fix the logical block counter overflow in indirect migration Yichong Chen
2026-09-14 7:18 ` sashiko-bot
@ 2026-09-14 9:57 ` Jan Kara
1 sibling, 0 replies; 4+ messages in thread
From: Jan Kara @ 2026-09-14 9:57 UTC (permalink / raw)
To: Yichong Chen
Cc: Theodore Ts'o, linux-ext4, linux-kernel, Andreas Dilger,
Baokun Li, Jan Kara, Ojaswin Mujoo, Ritesh Harjani, Zhang Yi,
Aneesh Kumar K . V
On Mon 14-09-26 14:55:44, Yichong Chen wrote:
> update_tind_extent_range() advances lb->curr_block, an ext4_lblk_t, by
> max_entries * max_entries for every empty triple-indirect slot. One
> triple-indirect block spans max_entries^3 logical blocks, which exceeds
> 2^32 as soon as the block size is 8K or larger (16384^3 = 2^42 with 64K
> blocks), so the counter wraps while that block is walked.
>
> A wrapped counter makes the migration store a block number that is 2^32
> blocks away from the one the pointer block describes. Two ranges can then
> end up with the same ee_block, which trips
> BUG_ON(newext->ee_block == nearex->ee_block) in ext4_ext_insert_extent(),
> and without that collision the data is still moved to the wrong logical
> block while the migration reports success.
>
> Keep the counter in 64 bit so that it cannot wrap, and refuse the
> migration with -EOPNOTSUPP when a data block is found after the last
> logical block an extent can describe, which only a corrupt block map can
> contain.
>
> Fixes: c14c6fd5c56a ("ext4: Add EXT4_IOC_MIGRATE ioctl")
> Signed-off-by: Yichong Chen <chenyichong@uniontech.com>
Yeah, I guess this is the easiest way how to deal with this. Feel free to
add:
Reviewed-by: Jan Kara <jack@suse.cz>
Honza
> ---
> fs/ext4/migrate.c | 6 +++++-
> 1 file changed, 5 insertions(+), 1 deletion(-)
>
> diff --git a/fs/ext4/migrate.c b/fs/ext4/migrate.c
> index e06d847033a1..8043959c19ef 100644
> --- a/fs/ext4/migrate.c
> +++ b/fs/ext4/migrate.c
> @@ -14,7 +14,7 @@
> * represented by a single extent
> */
> struct migrate_struct {
> - ext4_lblk_t first_block, last_block, curr_block;
> + u64 first_block, last_block, curr_block;
> ext4_fsblk_t first_pblock, last_pblock;
> };
>
> @@ -65,6 +65,10 @@ static int update_extent_range(handle_t *handle, struct inode *inode,
> ext4_fsblk_t pblock, struct migrate_struct *lb)
> {
> int retval;
> +
> + if (lb->curr_block > (ext4_lblk_t)-1)
> + return -EOPNOTSUPP;
> +
> /*
> * See if we can add on to the existing range (if it exists)
> */
> --
> 2.51.0
>
--
Jan Kara <jack@suse.com>
SUSE Labs, CR
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-09-14 9:57 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-14 6:55 [PATCH] ext4: fix the logical block counter overflow in indirect migration Yichong Chen
2026-09-14 7:18 ` sashiko-bot
2026-09-14 7:38 ` Yichong Chen
2026-09-14 9:57 ` Jan Kara
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox