diff options
author | Eryu Guan <guaneryu@gmail.com> | 2015-07-03 23:56:50 -0400 |
---|---|---|
committer | Theodore Ts'o <tytso@mit.edu> | 2015-07-03 23:56:50 -0400 |
commit | d6f123a9297496ad0b6335fe881504c4b5b2a5e5 (patch) | |
tree | d2c181f579f00f23b6476d07c72a4a7fa0184e01 | |
parent | 9705acd63b125dee8b15c705216d7186daea4625 (diff) |
ext4: be more strict when migrating to non-extent based file
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>
Cc: stable@vger.kernel.org
-rw-r--r-- | fs/ext4/migrate.c | 12 |
1 files changed, 11 insertions, 1 deletions
diff --git a/fs/ext4/migrate.c b/fs/ext4/migrate.c index b52374e42102..6d8b0c917364 100644 --- a/fs/ext4/migrate.c +++ b/fs/ext4/migrate.c | |||
@@ -620,6 +620,7 @@ int ext4_ind_migrate(struct inode *inode) | |||
620 | struct ext4_inode_info *ei = EXT4_I(inode); | 620 | struct ext4_inode_info *ei = EXT4_I(inode); |
621 | struct ext4_extent *ex; | 621 | struct ext4_extent *ex; |
622 | unsigned int i, len; | 622 | unsigned int i, len; |
623 | ext4_lblk_t end; | ||
623 | ext4_fsblk_t blk; | 624 | ext4_fsblk_t blk; |
624 | handle_t *handle; | 625 | handle_t *handle; |
625 | int ret; | 626 | int ret; |
@@ -633,6 +634,14 @@ int ext4_ind_migrate(struct inode *inode) | |||
633 | EXT4_FEATURE_RO_COMPAT_BIGALLOC)) | 634 | EXT4_FEATURE_RO_COMPAT_BIGALLOC)) |
634 | return -EOPNOTSUPP; | 635 | return -EOPNOTSUPP; |
635 | 636 | ||
637 | /* | ||
638 | * In order to get correct extent info, force all delayed allocation | ||
639 | * blocks to be allocated, otherwise delayed allocation blocks may not | ||
640 | * be reflected and bypass the checks on extent header. | ||
641 | */ | ||
642 | if (test_opt(inode->i_sb, DELALLOC)) | ||
643 | ext4_alloc_da_blocks(inode); | ||
644 | |||
636 | handle = ext4_journal_start(inode, EXT4_HT_MIGRATE, 1); | 645 | handle = ext4_journal_start(inode, EXT4_HT_MIGRATE, 1); |
637 | if (IS_ERR(handle)) | 646 | if (IS_ERR(handle)) |
638 | return PTR_ERR(handle); | 647 | return PTR_ERR(handle); |
@@ -654,7 +663,8 @@ int ext4_ind_migrate(struct inode *inode) | |||
654 | else { | 663 | else { |
655 | len = le16_to_cpu(ex->ee_len); | 664 | len = le16_to_cpu(ex->ee_len); |
656 | blk = ext4_ext_pblock(ex); | 665 | blk = ext4_ext_pblock(ex); |
657 | if (len > EXT4_NDIR_BLOCKS) { | 666 | end = le32_to_cpu(ex->ee_block) + len - 1; |
667 | if (end >= EXT4_NDIR_BLOCKS) { | ||
658 | ret = -EOPNOTSUPP; | 668 | ret = -EOPNOTSUPP; |
659 | goto errout; | 669 | goto errout; |
660 | } | 670 | } |