Re: [PATCH v4] ext4: avoid infinite loops caused by residual data

From: Jan Kara

Date: Thu Mar 05 2026 - 10:48:25 EST


On Thu 05-03-26 22:12:03, Edward Adam Davis wrote:
> On the mkdir/mknod path, when mapping logical blocks to physical blocks,
> if inserting a new extent into the extent tree fails (in this example,
> because the file system disabled the huge file feature when marking the
> inode as dirty), ext4_ext_map_blocks() only calls ext4_free_blocks() to
> reclaim the physical block without deleting the corresponding data in
> the extent tree. This causes subsequent mkdir operations to reference
> the previously reclaimed physical block number again, even though this
> physical block is already being used by the xattr block. Therefore, a
> situation arises where both the directory and xattr are using the same
> buffer head block in memory simultaneously.
>
> The above causes ext4_xattr_block_set() to enter an infinite loop about
> "inserted" and cannot release the inode lock, ultimately leading to the
> 143s blocking problem mentioned in [1].
>
> By using ext4_ext_remove_space() to delete the inserted logical block
> and reclaim the physical block when inserting a new extent fails during
> extent block mapping, either delete both as described above, or retain
> the data completely (i.e., neither delete the physical block nor the
> data in the extent tree), residual extent data can be prevented from
> affecting subsequent logical block physical mappings.
>
> Besides the errors ENOSPC or EDQUOT, this means metadata is corrupted.
> We should strive to do as few modifications as possible to limit damage.
> So just skip freeing of allocated blocks.
>
> [1]
> INFO: task syz.0.17:5995 blocked for more than 143 seconds.
> Call Trace:
> inode_lock_nested include/linux/fs.h:1073 [inline]
> __start_dirop fs/namei.c:2923 [inline]
> start_dirop fs/namei.c:2934 [inline]
>
> Reported-by: syzbot+512459401510e2a9a39f@xxxxxxxxxxxxxxxxxxxxxxxxx
> Closes: https://syzkaller.appspot.com/bug?extid=1659aaaaa8d9d11265d7
> Tested-by: syzbot+1659aaaaa8d9d11265d7@xxxxxxxxxxxxxxxxxxxxxxxxx
> Reported-by: syzbot+1659aaaaa8d9d11265d7@xxxxxxxxxxxxxxxxxxxxxxxxx
> Closes: https://syzkaller.appspot.com/bug?extid=512459401510e2a9a39f
> Tested-by: syzbot+1659aaaaa8d9d11265d7@xxxxxxxxxxxxxxxxxxxxxxxxx
> Signed-off-by: Edward Adam Davis <eadavis@xxxxxx>

...

> @@ -4457,20 +4457,19 @@ int ext4_ext_map_blocks(handle_t *handle, struct inode *inode,
> path = ext4_ext_insert_extent(handle, inode, path, &newex, flags);
> if (IS_ERR(path)) {
> err = PTR_ERR(path);
> - if (allocated_clusters) {
> - int fb_flags = 0;
> -
> + /*
> + * Gracefully handle out of space conditions. If the filesystem
> + * is inconsistent, we'll just leak allocated blocks to avoid
> + * causing even more damage.
> + */
> + if (allocated_clusters && (err == -EDQUOT || err == -ENOSPC)) {

This is fine now...

> /*
> * free data blocks we just allocated.
> * not a good idea to call discard here directly,
> * but otherwise we'd need to call it every free().
> */
> ext4_discard_preallocations(inode);
> - if (flags & EXT4_GET_BLOCKS_DELALLOC_RESERVE)
> - fb_flags = EXT4_FREE_BLOCKS_NO_QUOT_UPDATE;
> - ext4_free_blocks(handle, inode, NULL, newblock,
> - EXT4_C2B(sbi, allocated_clusters),
> - fb_flags);
> + ext4_ext_remove_space(inode, newex.ee_block, newex.ee_block);

But this won't work because in case of errors like ENOSPC there's no extent
inserted into the tree so ext4_ext_remove_space() won't do anything. Just
don't touch the freeing code and things will be fine...

Honza

> }
> goto out;
> }
> --
> 2.43.0
>
--
Jan Kara <jack@xxxxxxxx>
SUSE Labs, CR