diff options
author | Lukas Czerner <lczerner@redhat.com> | 2012-03-21 21:22:22 -0400 |
---|---|---|
committer | Theodore Ts'o <tytso@mit.edu> | 2012-03-21 21:22:22 -0400 |
commit | 913eed83eda42012cde8a9a801c31b312545c098 (patch) | |
tree | c6d3ecf5f98fcb1da3854059dbcc51c27b078011 /fs/ext4 | |
parent | 636d7e2e3b57543636d20cd083079300ccdd569d (diff) |
ext4: fix start and len arguments handling in ext4_trim_fs()
The overflow can happen when we are calling get_group_no_and_offset()
which stores the group number in the ext4_grpblk_t type which is
actually int. However when the blocknr is big enough the group number
might be bigger than ext4_grpblk_t resulting in overflow. This will
most likely happen with FITRIM default argument len = ULLONG_MAX.
Fix this by using "end" variable instead of "start+len" as it is easier
to get right and specifically check that the end is not beyond the end
of the file system, so we are sure that the result of
get_group_no_and_offset() will not overflow. Otherwise truncate it to
the size of the file system.
Signed-off-by: Lukas Czerner <lczerner@redhat.com>
Reviewed-by: Jan Kara <jack@suse.cz>
Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
Diffstat (limited to 'fs/ext4')
-rw-r--r-- | fs/ext4/mballoc.c | 57 |
1 files changed, 30 insertions, 27 deletions
diff --git a/fs/ext4/mballoc.c b/fs/ext4/mballoc.c index 23ec6a879b35..7d5a1e3a703a 100644 --- a/fs/ext4/mballoc.c +++ b/fs/ext4/mballoc.c | |||
@@ -4914,11 +4914,11 @@ ext4_trim_all_free(struct super_block *sb, ext4_group_t group, | |||
4914 | start = (e4b.bd_info->bb_first_free > start) ? | 4914 | start = (e4b.bd_info->bb_first_free > start) ? |
4915 | e4b.bd_info->bb_first_free : start; | 4915 | e4b.bd_info->bb_first_free : start; |
4916 | 4916 | ||
4917 | while (start < max) { | 4917 | while (start <= max) { |
4918 | start = mb_find_next_zero_bit(bitmap, max, start); | 4918 | start = mb_find_next_zero_bit(bitmap, max + 1, start); |
4919 | if (start >= max) | 4919 | if (start > max) |
4920 | break; | 4920 | break; |
4921 | next = mb_find_next_bit(bitmap, max, start); | 4921 | next = mb_find_next_bit(bitmap, max + 1, start); |
4922 | 4922 | ||
4923 | if ((next - start) >= minblocks) { | 4923 | if ((next - start) >= minblocks) { |
4924 | ext4_trim_extent(sb, start, | 4924 | ext4_trim_extent(sb, start, |
@@ -4970,37 +4970,36 @@ out: | |||
4970 | int ext4_trim_fs(struct super_block *sb, struct fstrim_range *range) | 4970 | int ext4_trim_fs(struct super_block *sb, struct fstrim_range *range) |
4971 | { | 4971 | { |
4972 | struct ext4_group_info *grp; | 4972 | struct ext4_group_info *grp; |
4973 | ext4_group_t first_group, last_group; | 4973 | ext4_group_t group, first_group, last_group; |
4974 | ext4_group_t group, ngroups = ext4_get_groups_count(sb); | ||
4975 | ext4_grpblk_t cnt = 0, first_cluster, last_cluster; | 4974 | ext4_grpblk_t cnt = 0, first_cluster, last_cluster; |
4976 | uint64_t start, len, minlen, trimmed = 0; | 4975 | uint64_t start, end, minlen, trimmed = 0; |
4977 | ext4_fsblk_t first_data_blk = | 4976 | ext4_fsblk_t first_data_blk = |
4978 | le32_to_cpu(EXT4_SB(sb)->s_es->s_first_data_block); | 4977 | le32_to_cpu(EXT4_SB(sb)->s_es->s_first_data_block); |
4978 | ext4_fsblk_t max_blks = ext4_blocks_count(EXT4_SB(sb)->s_es); | ||
4979 | int ret = 0; | 4979 | int ret = 0; |
4980 | 4980 | ||
4981 | start = range->start >> sb->s_blocksize_bits; | 4981 | start = range->start >> sb->s_blocksize_bits; |
4982 | len = range->len >> sb->s_blocksize_bits; | 4982 | end = start + (range->len >> sb->s_blocksize_bits) - 1; |
4983 | minlen = range->minlen >> sb->s_blocksize_bits; | 4983 | minlen = range->minlen >> sb->s_blocksize_bits; |
4984 | 4984 | ||
4985 | if (unlikely(minlen > EXT4_CLUSTERS_PER_GROUP(sb))) | 4985 | if (unlikely(minlen > EXT4_CLUSTERS_PER_GROUP(sb)) || |
4986 | unlikely(start >= max_blks)) | ||
4986 | return -EINVAL; | 4987 | return -EINVAL; |
4987 | if (start + len <= first_data_blk) | 4988 | if (end >= max_blks) |
4989 | end = max_blks - 1; | ||
4990 | if (end <= first_data_blk) | ||
4988 | goto out; | 4991 | goto out; |
4989 | if (start < first_data_blk) { | 4992 | if (start < first_data_blk) |
4990 | len -= first_data_blk - start; | ||
4991 | start = first_data_blk; | 4993 | start = first_data_blk; |
4992 | } | ||
4993 | 4994 | ||
4994 | /* Determine first and last group to examine based on start and len */ | 4995 | /* Determine first and last group to examine based on start and end */ |
4995 | ext4_get_group_no_and_offset(sb, (ext4_fsblk_t) start, | 4996 | ext4_get_group_no_and_offset(sb, (ext4_fsblk_t) start, |
4996 | &first_group, &first_cluster); | 4997 | &first_group, &first_cluster); |
4997 | ext4_get_group_no_and_offset(sb, (ext4_fsblk_t) (start + len), | 4998 | ext4_get_group_no_and_offset(sb, (ext4_fsblk_t) end, |
4998 | &last_group, &last_cluster); | 4999 | &last_group, &last_cluster); |
4999 | last_group = (last_group > ngroups - 1) ? ngroups - 1 : last_group; | ||
5000 | last_cluster = EXT4_CLUSTERS_PER_GROUP(sb); | ||
5001 | 5000 | ||
5002 | if (first_group > last_group) | 5001 | /* end now represents the last cluster to discard in this group */ |
5003 | return -EINVAL; | 5002 | end = EXT4_CLUSTERS_PER_GROUP(sb) - 1; |
5004 | 5003 | ||
5005 | for (group = first_group; group <= last_group; group++) { | 5004 | for (group = first_group; group <= last_group; group++) { |
5006 | grp = ext4_get_group_info(sb, group); | 5005 | grp = ext4_get_group_info(sb, group); |
@@ -5012,24 +5011,28 @@ int ext4_trim_fs(struct super_block *sb, struct fstrim_range *range) | |||
5012 | } | 5011 | } |
5013 | 5012 | ||
5014 | /* | 5013 | /* |
5015 | * For all the groups except the last one, last block will | 5014 | * For all the groups except the last one, last cluster will |
5016 | * always be EXT4_BLOCKS_PER_GROUP(sb), so we only need to | 5015 | * always be EXT4_CLUSTERS_PER_GROUP(sb)-1, so we only need to |
5017 | * change it for the last group in which case start + | 5016 | * change it for the last group, note that last_cluster is |
5018 | * len < EXT4_BLOCKS_PER_GROUP(sb). | 5017 | * already computed earlier by ext4_get_group_no_and_offset() |
5019 | */ | 5018 | */ |
5020 | if (first_cluster + len < EXT4_CLUSTERS_PER_GROUP(sb)) | 5019 | if (group == last_group) |
5021 | last_cluster = first_cluster + len; | 5020 | end = last_cluster; |
5022 | len -= last_cluster - first_cluster; | ||
5023 | 5021 | ||
5024 | if (grp->bb_free >= minlen) { | 5022 | if (grp->bb_free >= minlen) { |
5025 | cnt = ext4_trim_all_free(sb, group, first_cluster, | 5023 | cnt = ext4_trim_all_free(sb, group, first_cluster, |
5026 | last_cluster, minlen); | 5024 | end, minlen); |
5027 | if (cnt < 0) { | 5025 | if (cnt < 0) { |
5028 | ret = cnt; | 5026 | ret = cnt; |
5029 | break; | 5027 | break; |
5030 | } | 5028 | } |
5031 | } | 5029 | } |
5032 | trimmed += cnt; | 5030 | trimmed += cnt; |
5031 | |||
5032 | /* | ||
5033 | * For every group except the first one, we are sure | ||
5034 | * that the first cluster to discard will be cluster #0. | ||
5035 | */ | ||
5033 | first_cluster = 0; | 5036 | first_cluster = 0; |
5034 | } | 5037 | } |
5035 | range->len = trimmed * sb->s_blocksize; | 5038 | range->len = trimmed * sb->s_blocksize; |