mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-19 18:53:52 +08:00
btrfs: tree-checker: Add EXTENT_ITEM and METADATA_ITEM check
This patch introduces the ability to check extent items. This check involves: - key->objectid check Basic alignment check. - key->type check Against btrfs_extent_item::type and SKINNY_METADATA feature. - key->offset alignment check for EXTENT_ITEM - key->offset check for METADATA_ITEM - item size check Both against minimal size and stepping check. - btrfs_extent_item check Checks its flags and generation. - btrfs_extent_inline_ref checks Against 4 types inline ref. Checks bytenr alignment and tree level. - btrfs_extent_item::refs check Check against total refs found in inline refs. This check would be the most complex single item check due to its nature of inlined items. Signed-off-by: Qu Wenruo <wqu@suse.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
This commit is contained in:
parent
f11369897e
commit
f82d1c7ca8
@ -910,6 +910,250 @@ static int check_root_item(struct extent_buffer *leaf, struct btrfs_key *key,
|
||||
return 0;
|
||||
}
|
||||
|
||||
__printf(3,4)
|
||||
__cold
|
||||
static void extent_err(const struct extent_buffer *eb, int slot,
|
||||
const char *fmt, ...)
|
||||
{
|
||||
struct btrfs_key key;
|
||||
struct va_format vaf;
|
||||
va_list args;
|
||||
u64 bytenr;
|
||||
u64 len;
|
||||
|
||||
btrfs_item_key_to_cpu(eb, &key, slot);
|
||||
bytenr = key.objectid;
|
||||
if (key.type == BTRFS_METADATA_ITEM_KEY)
|
||||
len = eb->fs_info->nodesize;
|
||||
else
|
||||
len = key.offset;
|
||||
va_start(args, fmt);
|
||||
|
||||
vaf.fmt = fmt;
|
||||
vaf.va = &args;
|
||||
|
||||
btrfs_crit(eb->fs_info,
|
||||
"corrupt %s: block=%llu slot=%d extent bytenr=%llu len=%llu %pV",
|
||||
btrfs_header_level(eb) == 0 ? "leaf" : "node",
|
||||
eb->start, slot, bytenr, len, &vaf);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
static int check_extent_item(struct extent_buffer *leaf,
|
||||
struct btrfs_key *key, int slot)
|
||||
{
|
||||
struct btrfs_fs_info *fs_info = leaf->fs_info;
|
||||
struct btrfs_extent_item *ei;
|
||||
bool is_tree_block = false;
|
||||
unsigned long ptr; /* Current pointer inside inline refs */
|
||||
unsigned long end; /* Extent item end */
|
||||
const u32 item_size = btrfs_item_size_nr(leaf, slot);
|
||||
u64 flags;
|
||||
u64 generation;
|
||||
u64 total_refs; /* Total refs in btrfs_extent_item */
|
||||
u64 inline_refs = 0; /* found total inline refs */
|
||||
|
||||
if (key->type == BTRFS_METADATA_ITEM_KEY &&
|
||||
!btrfs_fs_incompat(fs_info, SKINNY_METADATA)) {
|
||||
generic_err(leaf, slot,
|
||||
"invalid key type, METADATA_ITEM type invalid when SKINNY_METADATA feature disabled");
|
||||
return -EUCLEAN;
|
||||
}
|
||||
/* key->objectid is the bytenr for both key types */
|
||||
if (!IS_ALIGNED(key->objectid, fs_info->sectorsize)) {
|
||||
generic_err(leaf, slot,
|
||||
"invalid key objectid, have %llu expect to be aligned to %u",
|
||||
key->objectid, fs_info->sectorsize);
|
||||
return -EUCLEAN;
|
||||
}
|
||||
|
||||
/* key->offset is tree level for METADATA_ITEM_KEY */
|
||||
if (key->type == BTRFS_METADATA_ITEM_KEY &&
|
||||
key->offset >= BTRFS_MAX_LEVEL) {
|
||||
extent_err(leaf, slot,
|
||||
"invalid tree level, have %llu expect [0, %u]",
|
||||
key->offset, BTRFS_MAX_LEVEL - 1);
|
||||
return -EUCLEAN;
|
||||
}
|
||||
|
||||
/*
|
||||
* EXTENT/METADATA_ITEM consists of:
|
||||
* 1) One btrfs_extent_item
|
||||
* Records the total refs, type and generation of the extent.
|
||||
*
|
||||
* 2) One btrfs_tree_block_info (for EXTENT_ITEM and tree backref only)
|
||||
* Records the first key and level of the tree block.
|
||||
*
|
||||
* 2) Zero or more btrfs_extent_inline_ref(s)
|
||||
* Each inline ref has one btrfs_extent_inline_ref shows:
|
||||
* 2.1) The ref type, one of the 4
|
||||
* TREE_BLOCK_REF Tree block only
|
||||
* SHARED_BLOCK_REF Tree block only
|
||||
* EXTENT_DATA_REF Data only
|
||||
* SHARED_DATA_REF Data only
|
||||
* 2.2) Ref type specific data
|
||||
* Either using btrfs_extent_inline_ref::offset, or specific
|
||||
* data structure.
|
||||
*/
|
||||
if (item_size < sizeof(*ei)) {
|
||||
extent_err(leaf, slot,
|
||||
"invalid item size, have %u expect [%zu, %u)",
|
||||
item_size, sizeof(*ei),
|
||||
BTRFS_LEAF_DATA_SIZE(fs_info));
|
||||
return -EUCLEAN;
|
||||
}
|
||||
end = item_size + btrfs_item_ptr_offset(leaf, slot);
|
||||
|
||||
/* Checks against extent_item */
|
||||
ei = btrfs_item_ptr(leaf, slot, struct btrfs_extent_item);
|
||||
flags = btrfs_extent_flags(leaf, ei);
|
||||
total_refs = btrfs_extent_refs(leaf, ei);
|
||||
generation = btrfs_extent_generation(leaf, ei);
|
||||
if (generation > btrfs_super_generation(fs_info->super_copy) + 1) {
|
||||
extent_err(leaf, slot,
|
||||
"invalid generation, have %llu expect (0, %llu]",
|
||||
generation,
|
||||
btrfs_super_generation(fs_info->super_copy) + 1);
|
||||
return -EUCLEAN;
|
||||
}
|
||||
if (!is_power_of_2(flags & (BTRFS_EXTENT_FLAG_DATA |
|
||||
BTRFS_EXTENT_FLAG_TREE_BLOCK))) {
|
||||
extent_err(leaf, slot,
|
||||
"invalid extent flag, have 0x%llx expect 1 bit set in 0x%llx",
|
||||
flags, BTRFS_EXTENT_FLAG_DATA |
|
||||
BTRFS_EXTENT_FLAG_TREE_BLOCK);
|
||||
return -EUCLEAN;
|
||||
}
|
||||
is_tree_block = !!(flags & BTRFS_EXTENT_FLAG_TREE_BLOCK);
|
||||
if (is_tree_block) {
|
||||
if (key->type == BTRFS_EXTENT_ITEM_KEY &&
|
||||
key->offset != fs_info->nodesize) {
|
||||
extent_err(leaf, slot,
|
||||
"invalid extent length, have %llu expect %u",
|
||||
key->offset, fs_info->nodesize);
|
||||
return -EUCLEAN;
|
||||
}
|
||||
} else {
|
||||
if (key->type != BTRFS_EXTENT_ITEM_KEY) {
|
||||
extent_err(leaf, slot,
|
||||
"invalid key type, have %u expect %u for data backref",
|
||||
key->type, BTRFS_EXTENT_ITEM_KEY);
|
||||
return -EUCLEAN;
|
||||
}
|
||||
if (!IS_ALIGNED(key->offset, fs_info->sectorsize)) {
|
||||
extent_err(leaf, slot,
|
||||
"invalid extent length, have %llu expect aligned to %u",
|
||||
key->offset, fs_info->sectorsize);
|
||||
return -EUCLEAN;
|
||||
}
|
||||
}
|
||||
ptr = (unsigned long)(struct btrfs_extent_item *)(ei + 1);
|
||||
|
||||
/* Check the special case of btrfs_tree_block_info */
|
||||
if (is_tree_block && key->type != BTRFS_METADATA_ITEM_KEY) {
|
||||
struct btrfs_tree_block_info *info;
|
||||
|
||||
info = (struct btrfs_tree_block_info *)ptr;
|
||||
if (btrfs_tree_block_level(leaf, info) >= BTRFS_MAX_LEVEL) {
|
||||
extent_err(leaf, slot,
|
||||
"invalid tree block info level, have %u expect [0, %u]",
|
||||
btrfs_tree_block_level(leaf, info),
|
||||
BTRFS_MAX_LEVEL - 1);
|
||||
return -EUCLEAN;
|
||||
}
|
||||
ptr = (unsigned long)(struct btrfs_tree_block_info *)(info + 1);
|
||||
}
|
||||
|
||||
/* Check inline refs */
|
||||
while (ptr < end) {
|
||||
struct btrfs_extent_inline_ref *iref;
|
||||
struct btrfs_extent_data_ref *dref;
|
||||
struct btrfs_shared_data_ref *sref;
|
||||
u64 dref_offset;
|
||||
u64 inline_offset;
|
||||
u8 inline_type;
|
||||
|
||||
if (ptr + sizeof(*iref) > end) {
|
||||
extent_err(leaf, slot,
|
||||
"inline ref item overflows extent item, ptr %lu iref size %zu end %lu",
|
||||
ptr, sizeof(*iref), end);
|
||||
return -EUCLEAN;
|
||||
}
|
||||
iref = (struct btrfs_extent_inline_ref *)ptr;
|
||||
inline_type = btrfs_extent_inline_ref_type(leaf, iref);
|
||||
inline_offset = btrfs_extent_inline_ref_offset(leaf, iref);
|
||||
if (ptr + btrfs_extent_inline_ref_size(inline_type) > end) {
|
||||
extent_err(leaf, slot,
|
||||
"inline ref item overflows extent item, ptr %lu iref size %u end %lu",
|
||||
ptr, inline_type, end);
|
||||
return -EUCLEAN;
|
||||
}
|
||||
|
||||
switch (inline_type) {
|
||||
/* inline_offset is subvolid of the owner, no need to check */
|
||||
case BTRFS_TREE_BLOCK_REF_KEY:
|
||||
inline_refs++;
|
||||
break;
|
||||
/* Contains parent bytenr */
|
||||
case BTRFS_SHARED_BLOCK_REF_KEY:
|
||||
if (!IS_ALIGNED(inline_offset, fs_info->sectorsize)) {
|
||||
extent_err(leaf, slot,
|
||||
"invalid tree parent bytenr, have %llu expect aligned to %u",
|
||||
inline_offset, fs_info->sectorsize);
|
||||
return -EUCLEAN;
|
||||
}
|
||||
inline_refs++;
|
||||
break;
|
||||
/*
|
||||
* Contains owner subvolid, owner key objectid, adjusted offset.
|
||||
* The only obvious corruption can happen in that offset.
|
||||
*/
|
||||
case BTRFS_EXTENT_DATA_REF_KEY:
|
||||
dref = (struct btrfs_extent_data_ref *)(&iref->offset);
|
||||
dref_offset = btrfs_extent_data_ref_offset(leaf, dref);
|
||||
if (!IS_ALIGNED(dref_offset, fs_info->sectorsize)) {
|
||||
extent_err(leaf, slot,
|
||||
"invalid data ref offset, have %llu expect aligned to %u",
|
||||
dref_offset, fs_info->sectorsize);
|
||||
return -EUCLEAN;
|
||||
}
|
||||
inline_refs += btrfs_extent_data_ref_count(leaf, dref);
|
||||
break;
|
||||
/* Contains parent bytenr and ref count */
|
||||
case BTRFS_SHARED_DATA_REF_KEY:
|
||||
sref = (struct btrfs_shared_data_ref *)(iref + 1);
|
||||
if (!IS_ALIGNED(inline_offset, fs_info->sectorsize)) {
|
||||
extent_err(leaf, slot,
|
||||
"invalid data parent bytenr, have %llu expect aligned to %u",
|
||||
inline_offset, fs_info->sectorsize);
|
||||
return -EUCLEAN;
|
||||
}
|
||||
inline_refs += btrfs_shared_data_ref_count(leaf, sref);
|
||||
break;
|
||||
default:
|
||||
extent_err(leaf, slot, "unknown inline ref type: %u",
|
||||
inline_type);
|
||||
return -EUCLEAN;
|
||||
}
|
||||
ptr += btrfs_extent_inline_ref_size(inline_type);
|
||||
}
|
||||
/* No padding is allowed */
|
||||
if (ptr != end) {
|
||||
extent_err(leaf, slot,
|
||||
"invalid extent item size, padding bytes found");
|
||||
return -EUCLEAN;
|
||||
}
|
||||
|
||||
/* Finally, check the inline refs against total refs */
|
||||
if (inline_refs > total_refs) {
|
||||
extent_err(leaf, slot,
|
||||
"invalid extent refs, have %llu expect >= inline %llu",
|
||||
total_refs, inline_refs);
|
||||
return -EUCLEAN;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Common point to switch the item-specific validation.
|
||||
*/
|
||||
@ -948,6 +1192,10 @@ static int check_leaf_item(struct extent_buffer *leaf,
|
||||
case BTRFS_ROOT_ITEM_KEY:
|
||||
ret = check_root_item(leaf, key, slot);
|
||||
break;
|
||||
case BTRFS_EXTENT_ITEM_KEY:
|
||||
case BTRFS_METADATA_ITEM_KEY:
|
||||
ret = check_extent_item(leaf, key, slot);
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user