| From d6f123a9297496ad0b6335fe881504c4b5b2a5e5 Mon Sep 17 00:00:00 2001 |
| From: Eryu Guan <guaneryu@gmail.com> |
| Date: Fri, 3 Jul 2015 23:56:50 -0400 |
| Subject: ext4: be more strict when migrating to non-extent based file |
| |
| From: Eryu Guan <guaneryu@gmail.com> |
| |
| commit d6f123a9297496ad0b6335fe881504c4b5b2a5e5 upstream. |
| |
| Currently the check in ext4_ind_migrate() is not enough before doing the |
| real conversion: |
| |
| a) delayed allocated extents could bypass the check on eh->eh_entries |
| and eh->eh_depth |
| |
| This can be demonstrated by this script |
| |
| xfs_io -fc "pwrite 0 4k" -c "pwrite 8k 4k" /mnt/ext4/testfile |
| chattr -e /mnt/ext4/testfile |
| |
| where testfile has two extents but still be converted to non-extent |
| based file format. |
| |
| b) only extent length is checked but not the offset, which would result |
| in data lose (delalloc) or fs corruption (nodelalloc), because |
| non-extent based file only supports at most (12 + 2^10 + 2^20 + 2^30) |
| blocks |
| |
| This can be demostrated by |
| |
| xfs_io -fc "pwrite 5T 4k" /mnt/ext4/testfile |
| chattr -e /mnt/ext4/testfile |
| sync |
| |
| If delalloc is enabled, dmesg prints |
| EXT4-fs warning (device dm-4): ext4_block_to_path:105: block 1342177280 > max in inode 53 |
| EXT4-fs (dm-4): Delayed block allocation failed for inode 53 at logical offset 1342177280 with max blocks 1 with error 5 |
| EXT4-fs (dm-4): This should not happen!! Data will be lost |
| |
| If delalloc is disabled, e2fsck -nf shows corruption |
| Inode 53, i_size is 5497558142976, should be 4096. Fix? no |
| |
| Fix the two issues by |
| |
| a) forcing all delayed allocation blocks to be allocated before checking |
| eh->eh_depth and eh->eh_entries |
| b) limiting the last logical block of the extent is within direct map |
| |
| Signed-off-by: Eryu Guan <guaneryu@gmail.com> |
| Signed-off-by: Theodore Ts'o <tytso@mit.edu> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| fs/ext4/migrate.c | 12 +++++++++++- |
| 1 file changed, 11 insertions(+), 1 deletion(-) |
| |
| --- a/fs/ext4/migrate.c |
| +++ b/fs/ext4/migrate.c |
| @@ -616,6 +616,7 @@ int ext4_ind_migrate(struct inode *inode |
| struct ext4_inode_info *ei = EXT4_I(inode); |
| struct ext4_extent *ex; |
| unsigned int i, len; |
| + ext4_lblk_t end; |
| ext4_fsblk_t blk; |
| handle_t *handle; |
| int ret; |
| @@ -629,6 +630,14 @@ int ext4_ind_migrate(struct inode *inode |
| EXT4_FEATURE_RO_COMPAT_BIGALLOC)) |
| return -EOPNOTSUPP; |
| |
| + /* |
| + * In order to get correct extent info, force all delayed allocation |
| + * blocks to be allocated, otherwise delayed allocation blocks may not |
| + * be reflected and bypass the checks on extent header. |
| + */ |
| + if (test_opt(inode->i_sb, DELALLOC)) |
| + ext4_alloc_da_blocks(inode); |
| + |
| handle = ext4_journal_start(inode, EXT4_HT_MIGRATE, 1); |
| if (IS_ERR(handle)) |
| return PTR_ERR(handle); |
| @@ -650,7 +659,8 @@ int ext4_ind_migrate(struct inode *inode |
| else { |
| len = le16_to_cpu(ex->ee_len); |
| blk = ext4_ext_pblock(ex); |
| - if (len > EXT4_NDIR_BLOCKS) { |
| + end = le32_to_cpu(ex->ee_block) + len - 1; |
| + if (end >= EXT4_NDIR_BLOCKS) { |
| ret = -EOPNOTSUPP; |
| goto errout; |
| } |