btrfs: tree-checker: Verify block_group_item
PATCH v3 1/1 btrfs: tree-checker: Verify block_group_item
As reported in https://bugzilla.kernel.org/show_bug.cgi?id=199849,
a crafted image with invalid block group items could make free space cache code to cause panic.
We could early detect such invalid block group item by checking:
1) Item size
Fixed value.
2) Block group size (key.offset)
We have a up limit on block group item (10G)
3) Chunk objectid
Fixed value.
4) Type
Only 4 valid type values, DATA, METADATA, SYSTEM and DATA|METADATA. No more than 1 bit set for profile type.
5) Used space
No more than block group size.
This should allow btrfs to detect and refuse to mount the crafted image.
---
changelog:
v2:
Format change for if() code block, added one new line for each block. Use "hweight64() > 1" to detect profile error, replacing the bloated !(flags & MASK == 0 || hweight64() == 1) condition.
v3:
Allow special case for mixed block groups (type == (DATA | METADATA)). Use constant define for max data chunk size.
--- fs/btrfs/tree-checker.c | 101 ++++++++++++++++++++++++++++++++++++++++ fs/btrfs/volumes.c | 2 +- fs/btrfs/volumes.h | 2 + 3 files changed, 104 insertions(+), 1 deletion(-)
fs/btrfs/tree-checker.c +101 -0
@@ -19,6 +19,7 @@#include "tree-checker.h" #include "disk-io.h" #include "compression.h" +#include "volumes.h" /* * Error message should follow the following format:
@@ -353,6 +354,103 @@ static int check_dir_item(struct btrfs_fs_info *fs_info,return 0 ; } +__printf(4, 5) +__cold +static void block_group_err (const struct btrfs_fs_info *fs_info, +const struct extent_buffer *eb,int slot, +const char *fmt, ...) +{ +struct btrfs_key key; +struct va_format vaf; +va_list args; + +btrfs_item_key_to_cpu (eb, &key, slot); +va_start (args, fmt); + + vaf.fmt = fmt; + vaf.va = &args; + +btrfs_crit (fs_info, +"corrupt %s: root=%llu block=%llu slot=%d bg_start=%llu bg_len=%llu, %pV" , +btrfs_header_level (eb) ==0 ?"leaf" :"node" , +btrfs_header_owner (eb),btrfs_header_bytenr (eb), slot, + key.objectid, key.offset, &vaf); +va_end (args); +} + +static int check_block_group_item (struct btrfs_fs_info *fs_info, +struct extent_buffer *leaf, +struct btrfs_key *key,int slot) +{ +struct btrfs_block_group_item bgi; +u32 item_size =btrfs_item_size_nr (leaf, slot); +u64 flags; +u64 type_flags; + +/* +* Here we don't really care about unalignment since extent allocator +* can handle it. +* We care more about the size, as if one block group is larger than +* maximum size, it's must be some obvious corruption +*/ +if (key->offset >BTRFS_MAX_DATA_CHUNK_SIZE || key->offset ==0 ) { +block_group_err (fs_info, leaf, slot, +"invalid block group size, have %llu expect (0, %llu]" , + key->offset,10ULL *SZ_1G ); +return -EUCLEAN ; + } + +if (item_size !=sizeof (bgi)) { +block_group_err (fs_info, leaf, slot, +"invalid item size, have %u expect %lu" , + item_size,sizeof (bgi)); +return -EUCLEAN ; + } + +read_extent_buffer (leaf, &bgi,btrfs_item_ptr_offset (leaf, slot), +sizeof (bgi)); +if (btrfs_block_group_chunk_objectid (&bgi) != +BTRFS_FIRST_CHUNK_TREE_OBJECTID ) { +block_group_err (fs_info, leaf, slot, +"invalid block group chunk objectid, have %llu expect %llu" , +btrfs_block_group_chunk_objectid (&bgi), +BTRFS_FIRST_CHUNK_TREE_OBJECTID ); +return -EUCLEAN ; + } + +if (btrfs_block_group_used (&bgi) > key->offset) { +block_group_err (fs_info, leaf, slot, +"invalid block group used, have %llu expect [0, %llu)" , +btrfs_block_group_used (&bgi), key->offset); +return -EUCLEAN ; + } + + flags =btrfs_block_group_flags (&bgi); +if (hweight64 (flags &BTRFS_BLOCK_GROUP_PROFILE_MASK ) >1 ) { +block_group_err (fs_info, leaf, slot, +"invalid profile flags, have 0x%llx (%lu bits set) expect no more than 1 bit set" , + flags &BTRFS_BLOCK_GROUP_PROFILE_MASK , +hweight64 (flags &BTRFS_BLOCK_GROUP_PROFILE_MASK )); +return -EUCLEAN ; + } + + type_flags = flags &BTRFS_BLOCK_GROUP_TYPE_MASK ; +if (type_flags !=BTRFS_BLOCK_GROUP_DATA && + type_flags !=BTRFS_BLOCK_GROUP_METADATA && + type_flags !=BTRFS_BLOCK_GROUP_SYSTEM && + type_flags != (BTRFS_BLOCK_GROUP_METADATA | +BTRFS_BLOCK_GROUP_DATA )) { +block_group_err (fs_info, leaf, slot, +"invalid type flags, have 0x%llx (%lu bits set) expect either 0x%llx, 0x%llx, 0x%llu or 0x%llx" , + type_flags,hweight64 (type_flags), +BTRFS_BLOCK_GROUP_DATA ,BTRFS_BLOCK_GROUP_METADATA , +BTRFS_BLOCK_GROUP_SYSTEM , +BTRFS_BLOCK_GROUP_METADATA |BTRFS_BLOCK_GROUP_DATA ); +return -EUCLEAN ; + } +return 0 ; +} +/* * Common point to switch the item-specific validation. */
@@ -374,6 +472,9 @@ static int check_leaf_item(struct btrfs_fs_info *fs_info,case BTRFS_XATTR_ITEM_KEY : ret =check_dir_item (fs_info, leaf, key, slot);break ; +case BTRFS_BLOCK_GROUP_ITEM_KEY : + ret =check_block_group_item (fs_info, leaf, key, slot); +break ; }return ret; }
fs/btrfs/volumes.c +1 -1
@@ -4690,7 +4690,7 @@ static int __btrfs_alloc_chunk(struct btrfs_trans_handle *trans,if (type &BTRFS_BLOCK_GROUP_DATA ) { max_stripe_size =SZ_1G ; - max_chunk_size =10 * max_stripe_size; + max_chunk_size =BTRFS_MAX_DATA_CHUNK_SIZE ;if (!devs_max) devs_max =BTRFS_MAX_DEVS (info); }else if (type &BTRFS_BLOCK_GROUP_METADATA ) {
fs/btrfs/volumes.h +2 -0
@@ -11,6 +11,8 @@#include <linux/btrfs.h> #include "async-thread.h" +#define BTRFS_MAX_DATA_CHUNK_SIZE (10ULL * SZ_1G) +extern struct mutex uuid_mutex;#define BTRFS_STRIPE_LEN SZ_64K
Signature · 1 line
2.18.0