| From: Liu Bo <bo.li.liu@oracle.com> |
| Date: Fri, 3 Jun 2016 12:05:15 -0700 |
| Subject: Btrfs: add validadtion checks for chunk loading |
| |
| commit e06cd3dd7cea50e87663a88acdfdb7ac1c53a5ca upstream. |
| |
| To prevent fuzzed filesystem images from panic the whole system, |
| we need various validation checks to refuse to mount such an image |
| if btrfs finds any invalid value during loading chunks, including |
| both sys_array and regular chunks. |
| |
| Note that these checks may not be sufficient to cover all corner cases, |
| feel free to add more checks. |
| |
| Reported-by: Vegard Nossum <vegard.nossum@oracle.com> |
| Reported-by: Quentin Casasnovas <quentin.casasnovas@oracle.com> |
| Reviewed-by: David Sterba <dsterba@suse.com> |
| Signed-off-by: Liu Bo <bo.li.liu@oracle.com> |
| Signed-off-by: David Sterba <dsterba@suse.com> |
| Signed-off-by: Ben Hutchings <ben.hutchings@codethink.co.uk> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| Signed-off-by: Ben Hutchings <ben@decadent.org.uk> |
| --- |
| fs/btrfs/volumes.c | 82 +++++++++++++++++++++++++++++++++++++--------- |
| 1 file changed, 67 insertions(+), 15 deletions(-) |
| |
| --- a/fs/btrfs/volumes.c |
| +++ b/fs/btrfs/volumes.c |
| @@ -5805,27 +5805,23 @@ struct btrfs_device *btrfs_alloc_device( |
| return dev; |
| } |
| |
| -static int read_one_chunk(struct btrfs_root *root, struct btrfs_key *key, |
| - struct extent_buffer *leaf, |
| - struct btrfs_chunk *chunk) |
| +/* Return -EIO if any error, otherwise return 0. */ |
| +static int btrfs_check_chunk_valid(struct btrfs_root *root, |
| + struct extent_buffer *leaf, |
| + struct btrfs_chunk *chunk, u64 logical) |
| { |
| - struct btrfs_mapping_tree *map_tree = &root->fs_info->mapping_tree; |
| - struct map_lookup *map; |
| - struct extent_map *em; |
| - u64 logical; |
| u64 length; |
| u64 stripe_len; |
| - u64 devid; |
| - u8 uuid[BTRFS_UUID_SIZE]; |
| - int num_stripes; |
| - int ret; |
| - int i; |
| + u16 num_stripes; |
| + u16 sub_stripes; |
| + u64 type; |
| |
| - logical = key->offset; |
| length = btrfs_chunk_length(leaf, chunk); |
| stripe_len = btrfs_chunk_stripe_len(leaf, chunk); |
| num_stripes = btrfs_chunk_num_stripes(leaf, chunk); |
| - /* Validation check */ |
| + sub_stripes = btrfs_chunk_sub_stripes(leaf, chunk); |
| + type = btrfs_chunk_type(leaf, chunk); |
| + |
| if (!num_stripes) { |
| btrfs_err(root->fs_info, "invalid chunk num_stripes: %u", |
| num_stripes); |
| @@ -5836,6 +5832,11 @@ static int read_one_chunk(struct btrfs_r |
| "invalid chunk logical %llu", logical); |
| return -EIO; |
| } |
| + if (btrfs_chunk_sector_size(leaf, chunk) != root->sectorsize) { |
| + btrfs_err(root->fs_info, "invalid chunk sectorsize %u", |
| + btrfs_chunk_sector_size(leaf, chunk)); |
| + return -EIO; |
| + } |
| if (!length || !IS_ALIGNED(length, root->sectorsize)) { |
| btrfs_err(root->fs_info, |
| "invalid chunk length %llu", length); |
| @@ -5847,13 +5848,54 @@ static int read_one_chunk(struct btrfs_r |
| return -EIO; |
| } |
| if (~(BTRFS_BLOCK_GROUP_TYPE_MASK | BTRFS_BLOCK_GROUP_PROFILE_MASK) & |
| - btrfs_chunk_type(leaf, chunk)) { |
| + type) { |
| btrfs_err(root->fs_info, "unrecognized chunk type: %llu", |
| ~(BTRFS_BLOCK_GROUP_TYPE_MASK | |
| BTRFS_BLOCK_GROUP_PROFILE_MASK) & |
| btrfs_chunk_type(leaf, chunk)); |
| return -EIO; |
| } |
| + if ((type & BTRFS_BLOCK_GROUP_RAID10 && sub_stripes != 2) || |
| + (type & BTRFS_BLOCK_GROUP_RAID1 && num_stripes < 1) || |
| + (type & BTRFS_BLOCK_GROUP_RAID5 && num_stripes < 2) || |
| + (type & BTRFS_BLOCK_GROUP_RAID6 && num_stripes < 3) || |
| + (type & BTRFS_BLOCK_GROUP_DUP && num_stripes > 2) || |
| + ((type & BTRFS_BLOCK_GROUP_PROFILE_MASK) == 0 && |
| + num_stripes != 1)) { |
| + btrfs_err(root->fs_info, |
| + "invalid num_stripes:sub_stripes %u:%u for profile %llu", |
| + num_stripes, sub_stripes, |
| + type & BTRFS_BLOCK_GROUP_PROFILE_MASK); |
| + return -EIO; |
| + } |
| + |
| + return 0; |
| +} |
| + |
| +static int read_one_chunk(struct btrfs_root *root, struct btrfs_key *key, |
| + struct extent_buffer *leaf, |
| + struct btrfs_chunk *chunk) |
| +{ |
| + struct btrfs_mapping_tree *map_tree = &root->fs_info->mapping_tree; |
| + struct map_lookup *map; |
| + struct extent_map *em; |
| + u64 logical; |
| + u64 length; |
| + u64 stripe_len; |
| + u64 devid; |
| + u8 uuid[BTRFS_UUID_SIZE]; |
| + int num_stripes; |
| + int ret; |
| + int i; |
| + |
| + logical = key->offset; |
| + length = btrfs_chunk_length(leaf, chunk); |
| + stripe_len = btrfs_chunk_stripe_len(leaf, chunk); |
| + num_stripes = btrfs_chunk_num_stripes(leaf, chunk); |
| + |
| + ret = btrfs_check_chunk_valid(root, leaf, chunk, logical); |
| + if (ret) |
| + return ret; |
| |
| read_lock(&map_tree->map_tree.lock); |
| em = lookup_extent_mapping(&map_tree->map_tree, logical, 1); |
| @@ -6070,6 +6112,7 @@ int btrfs_read_sys_array(struct btrfs_ro |
| u32 array_size; |
| u32 len = 0; |
| u32 cur_offset; |
| + u64 type; |
| struct btrfs_key key; |
| |
| sb = btrfs_find_create_tree_block(root, BTRFS_SUPER_INFO_OFFSET, |
| @@ -6130,6 +6173,15 @@ int btrfs_read_sys_array(struct btrfs_ro |
| ret = -EIO; |
| break; |
| } |
| + |
| + type = btrfs_chunk_type(sb, chunk); |
| + if ((type & BTRFS_BLOCK_GROUP_SYSTEM) == 0) { |
| + btrfs_err(root->fs_info, |
| + "invalid chunk type %llu in sys_array at offset %u", |
| + type, cur_offset); |
| + ret = -EIO; |
| + break; |
| + } |
| |
| len = btrfs_chunk_item_size(num_stripes); |
| if (cur_offset + len > array_size) |