diff mbox

[1/2] btrfs: tree-checker: Add checker for variable length item

Message ID 20171101121450.6297-1-wqu@suse.com (mailing list archive)
State New, archived
Headers show

Commit Message

Qu Wenruo Nov. 1, 2017, 12:14 p.m. UTC
For the following types, we have items with variable length:
(With BTRFS_ prefix and _KEY suffix snipped)

DIR_ITEM
DIR_INDEX
XATTR_ITEM
INODE_REF
INODE_EXTREF
ROOT_REF
ROOT_BACKREF

They all use @name_len to indicate their name length, and XATTR_ITEM has
extra @data_len to indicate it data length.

Despite their variable length, it's also possible to have several such
structure inside one item.

This patch will add checker to ensure:

1) No structure header and its data cross item boundary
2) Except XATTR_ITEM, no structure should have non-zero @data_len

This checker is especially useful to avoid possible access beyond
boundary for fuzzed image.

Signed-off-by: Qu Wenruo <wqu@suse.com>
---
 fs/btrfs/tree-checker.c | 123 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 123 insertions(+)

Comments

Nikolay Borisov Nov. 2, 2017, 12:06 p.m. UTC | #1
On  1.11.2017 14:14, Qu Wenruo wrote:
> For the following types, we have items with variable length:
> (With BTRFS_ prefix and _KEY suffix snipped)
> 
> DIR_ITEM
> DIR_INDEX
> XATTR_ITEM
> INODE_REF
> INODE_EXTREF
> ROOT_REF
> ROOT_BACKREF
> 
> They all use @name_len to indicate their name length, and XATTR_ITEM has
> extra @data_len to indicate it data length.
> 
> Despite their variable length, it's also possible to have several such
> structure inside one item.
> 
> This patch will add checker to ensure:
> 
> 1) No structure header and its data cross item boundary
> 2) Except XATTR_ITEM, no structure should have non-zero @data_len
> 
> This checker is especially useful to avoid possible access beyond
> boundary for fuzzed image.
> > Signed-off-by: Qu Wenruo <wqu@suse.com>
> ---
>  fs/btrfs/tree-checker.c | 123 ++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 123 insertions(+)
> 
> diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c
> index 114fc5f0ecc5..f26e86fcbd74 100644
> --- a/fs/btrfs/tree-checker.c
> +++ b/fs/btrfs/tree-checker.c
> @@ -222,6 +222,120 @@ static int check_csum_item(struct btrfs_root *root, struct extent_buffer *leaf,
>  	return 0;
>  }
>  
> +static u32 get_header_size(u8 type)
> +{
> +	switch (type) {
> +	case BTRFS_DIR_ITEM_KEY:
> +	case BTRFS_DIR_INDEX_KEY:
> +	case BTRFS_XATTR_ITEM_KEY:
> +		return sizeof(struct btrfs_dir_item);
> +	case BTRFS_INODE_REF_KEY:
> +		return sizeof(struct btrfs_inode_ref);
> +	case BTRFS_INODE_EXTREF_KEY:
> +		return sizeof(struct btrfs_inode_extref);
> +	case BTRFS_ROOT_REF_KEY:
> +	case BTRFS_ROOT_BACKREF_KEY:
> +		return sizeof(struct btrfs_root_ref);
> +	}
> +	WARN_ON(1);
> +	return 0;
> +}
> +
> +static u16 get_header_namelen(struct extent_buffer *leaf, u8 type,
> +			      u32 header_offset)
> +{
> +	/*
> +	 * @header_offset is offset starts after leaf header, while the
> +	 * accessors expects offset starts from leaf header.
> +	 * Sowe need to adds LEAF_DATA_OFFSET here
> +	 */

The comment is confusing (but so are the internals of accessing an
extent buffer and all the offset magic happening behind the scenes).

You are essentially opencoding btrfs_item_ptr_offset but instead of
pointing at the first entry in the data portion, you really point to
'cur' entry and in order to make it "accessor friendly" you do the +
BTRFS_LEAF_DATA_OFFSET. I'd say just put something like :

"This is the same as btrfs_item_ptr_offset but starting at item data
pointed by header_offset". In fact I'd suggest renaming header_offset to
data_offset

> +	unsigned long leaf_offset = header_offset + BTRFS_LEAF_DATA_OFFSET;
> +
> +	switch (type) {
> +	case BTRFS_DIR_ITEM_KEY:
> +	case BTRFS_DIR_INDEX_KEY:
> +	case BTRFS_XATTR_ITEM_KEY:
> +		return btrfs_dir_name_len(leaf, (void *)leaf_offset);
> +	case BTRFS_INODE_REF_KEY:
> +		return btrfs_inode_ref_name_len(leaf, (void *)leaf_offset);
> +	case BTRFS_INODE_EXTREF_KEY:
> +		return btrfs_inode_extref_name_len(leaf, (void *)leaf_offset);
> +	case BTRFS_ROOT_REF_KEY:
> +	case BTRFS_ROOT_BACKREF_KEY:
> +		return btrfs_root_ref_name_len(leaf, (void *)leaf_offset);
> +	}
> +	WARN_ON(1);
> +	return 0;
> +}
> +
> +static u16 get_header_datalen(struct extent_buffer *leaf, u8 type,
> +			      unsigned long header_offset)
> +{
> +	/* Same as get_header_namelen */
> +	unsigned long leaf_offset = header_offset + BTRFS_LEAF_DATA_OFFSET;
> +
> +	switch (type) {
> +	case BTRFS_DIR_ITEM_KEY:
> +	case BTRFS_DIR_INDEX_KEY:
> +	case BTRFS_XATTR_ITEM_KEY:
> +		return btrfs_dir_data_len(leaf, (void *)leaf_offset);
> +	}
> +	return 0;
> +}
> +
> +/*
> + * For items with variable length, normally with namelen and tailing data.
> + * Like INODE_REF or XATTR
> + */
> +static int check_variable_length_item(struct btrfs_root *root,
> +				      struct extent_buffer *leaf,
> +				      struct btrfs_key *key, int slot)
> +{
> +	u8 type = key->type;
> +	u32 item_start = btrfs_item_offset_nr(leaf, slot);
> +	u32 item_end = btrfs_item_end_nr(leaf, slot);
> +	u32 header_size = get_header_size(type);
> +	u32 total_size;
> +	u32 cur = item_start;

nit: Rename cur to cur_offset to make it clear this is an offset.

> +
> +	while (cur < item_end) {
> +		u32 namelen;
> +		u32 datalen;
> +
> +		/* header itself should not cross item boundary */
> +		if (cur + header_size > item_end) {
> +			generic_err(root, leaf, slot,
> +				"structure header crosses item boundary, have %u expect (%u, %u]",
> +				cur + header_size, cur, item_end);
> +			return -EUCLEAN;
> +		}
> +
> +		namelen = get_header_namelen(leaf, type, cur);
> +		datalen = get_header_datalen(leaf, type, cur);
> +
> +		/* Only XATTR can own data */
> +		if (type != BTRFS_XATTR_ITEM_KEY && datalen) {
> +			generic_err(root, leaf, slot,
> +				"item has invalid data len, have %u expect 0",
> +				datalen);
> +			return -EUCLEAN;
> +		}
> +
> +		total_size = header_size + namelen + datalen;
> +
> +		/* header and name/data should not cross item boundary */
> +		if (cur + total_size > item_end) {
> +			generic_err(root, leaf, slot,
> +				"structure data crosses item boundary, have %u expect (%u, %u]",
> +				cur + total_size, cur + header_size, item_end);
> +			return -EUCLEAN;
> +		}
> +
> +		cur += total_size;
> +	}
> +	return 0;
> +}
> +
>  /*
>   * Common point to switch the item-specific validation.
>   */
> @@ -238,6 +352,15 @@ static int check_leaf_item(struct btrfs_root *root,
>  	case BTRFS_EXTENT_CSUM_KEY:
>  		ret = check_csum_item(root, leaf, key, slot);
>  		break;
> +	case BTRFS_DIR_ITEM_KEY:
> +	case BTRFS_XATTR_ITEM_KEY:
> +	case BTRFS_DIR_INDEX_KEY:
> +	case BTRFS_INODE_REF_KEY:
> +	case BTRFS_INODE_EXTREF_KEY:
> +	case BTRFS_ROOT_REF_KEY:
> +	case BTRFS_ROOT_BACKREF_KEY:
> +		ret = check_variable_length_item(root, leaf, key, slot);
> +		break;
>  	}
>  	return ret;
>  }
> 
--
To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Nikolay Borisov Nov. 2, 2017, 12:12 p.m. UTC | #2
On  1.11.2017 14:14, Qu Wenruo wrote:
> For the following types, we have items with variable length:
> (With BTRFS_ prefix and _KEY suffix snipped)
> 
> DIR_ITEM
> DIR_INDEX
> XATTR_ITEM
> INODE_REF
> INODE_EXTREF
> ROOT_REF
> ROOT_BACKREF
> 
> They all use @name_len to indicate their name length, and XATTR_ITEM has
> extra @data_len to indicate it data length.
> 
> Despite their variable length, it's also possible to have several such
> structure inside one item.
> 
> This patch will add checker to ensure:
> 
> 1) No structure header and its data cross item boundary
> 2) Except XATTR_ITEM, no structure should have non-zero @data_len
> 
> This checker is especially useful to avoid possible access beyond
> boundary for fuzzed image.
> 
> Signed-off-by: Qu Wenruo <wqu@suse.com>
> ---
>  fs/btrfs/tree-checker.c | 123 ++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 123 insertions(+)
> 
> diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c
> index 114fc5f0ecc5..f26e86fcbd74 100644
> --- a/fs/btrfs/tree-checker.c
> +++ b/fs/btrfs/tree-checker.c
> @@ -222,6 +222,120 @@ static int check_csum_item(struct btrfs_root *root, struct extent_buffer *leaf,
>  	return 0;
>  }
>  
> +static u32 get_header_size(u8 type)
> +{
> +	switch (type) {
> +	case BTRFS_DIR_ITEM_KEY:
> +	case BTRFS_DIR_INDEX_KEY:
> +	case BTRFS_XATTR_ITEM_KEY:
> +		return sizeof(struct btrfs_dir_item);
> +	case BTRFS_INODE_REF_KEY:
> +		return sizeof(struct btrfs_inode_ref);
> +	case BTRFS_INODE_EXTREF_KEY:
> +		return sizeof(struct btrfs_inode_extref);
> +	case BTRFS_ROOT_REF_KEY:
> +	case BTRFS_ROOT_BACKREF_KEY:
> +		return sizeof(struct btrfs_root_ref);
> +	}
> +	WARN_ON(1);
> +	return 0;
> +}
> +
> +static u16 get_header_namelen(struct extent_buffer *leaf, u8 type,
> +			      u32 header_offset)
> +{
> +	/*
> +	 * @header_offset is offset starts after leaf header, while the
> +	 * accessors expects offset starts from leaf header.
> +	 * Sowe need to adds LEAF_DATA_OFFSET here
> +	 */
> +	unsigned long leaf_offset = header_offset + BTRFS_LEAF_DATA_OFFSET;
> +
> +	switch (type) {
> +	case BTRFS_DIR_ITEM_KEY:
> +	case BTRFS_DIR_INDEX_KEY:
> +	case BTRFS_XATTR_ITEM_KEY:
> +		return btrfs_dir_name_len(leaf, (void *)leaf_offset);
> +	case BTRFS_INODE_REF_KEY:
> +		return btrfs_inode_ref_name_len(leaf, (void *)leaf_offset);
> +	case BTRFS_INODE_EXTREF_KEY:
> +		return btrfs_inode_extref_name_len(leaf, (void *)leaf_offset);
> +	case BTRFS_ROOT_REF_KEY:
> +	case BTRFS_ROOT_BACKREF_KEY:
> +		return btrfs_root_ref_name_len(leaf, (void *)leaf_offset);
> +	}
> +	WARN_ON(1);
> +	return 0;
> +}
> +
> +static u16 get_header_datalen(struct extent_buffer *leaf, u8 type,
> +			      unsigned long header_offset)
> +{
> +	/* Same as get_header_namelen */
> +	unsigned long leaf_offset = header_offset + BTRFS_LEAF_DATA_OFFSET;
> +
> +	switch (type) {
> +	case BTRFS_DIR_ITEM_KEY:
> +	case BTRFS_DIR_INDEX_KEY:
> +	case BTRFS_XATTR_ITEM_KEY:
> +		return btrfs_dir_data_len(leaf, (void *)leaf_offset);
> +	}
> +	return 0;
> +}
> +
> +/*
> + * For items with variable length, normally with namelen and tailing data.
> + * Like INODE_REF or XATTR
> + */
> +static int check_variable_length_item(struct btrfs_root *root,
> +				      struct extent_buffer *leaf,
> +				      struct btrfs_key *key, int slot)
> +{

One more thing - you are only validating the boundaries of such variable
length items, so make this specific. I.e rename the function to
something like:

validate_variable_boundaries
check_variable_length_item_boundary
check_item_boundaries

> +	u8 type = key->type;
> +	u32 item_start = btrfs_item_offset_nr(leaf, slot);
> +	u32 item_end = btrfs_item_end_nr(leaf, slot);
> +	u32 header_size = get_header_size(type);
> +	u32 total_size;
> +	u32 cur = item_start;
> +
> +	while (cur < item_end) {
> +		u32 namelen;
> +		u32 datalen;
> +
> +		/* header itself should not cross item boundary */
> +		if (cur + header_size > item_end) {
> +			generic_err(root, leaf, slot,
> +				"structure header crosses item boundary, have %u expect (%u, %u]",
> +				cur + header_size, cur, item_end);
> +			return -EUCLEAN;
> +		}
> +
> +		namelen = get_header_namelen(leaf, type, cur);
> +		datalen = get_header_datalen(leaf, type, cur);
> +
> +		/* Only XATTR can own data */
> +		if (type != BTRFS_XATTR_ITEM_KEY && datalen) {
> +			generic_err(root, leaf, slot,
> +				"item has invalid data len, have %u expect 0",
> +				datalen);
> +			return -EUCLEAN;
> +		}
> +
> +		total_size = header_size + namelen + datalen;
> +
> +		/* header and name/data should not cross item boundary */
> +		if (cur + total_size > item_end) {
> +			generic_err(root, leaf, slot,
> +				"structure data crosses item boundary, have %u expect (%u, %u]",
> +				cur + total_size, cur + header_size, item_end);
> +			return -EUCLEAN;
> +		}
> +
> +		cur += total_size;
> +	}
> +	return 0;
> +}
> +
>  /*
>   * Common point to switch the item-specific validation.
>   */
> @@ -238,6 +352,15 @@ static int check_leaf_item(struct btrfs_root *root,
>  	case BTRFS_EXTENT_CSUM_KEY:
>  		ret = check_csum_item(root, leaf, key, slot);
>  		break;
> +	case BTRFS_DIR_ITEM_KEY:
> +	case BTRFS_XATTR_ITEM_KEY:
> +	case BTRFS_DIR_INDEX_KEY:
> +	case BTRFS_INODE_REF_KEY:
> +	case BTRFS_INODE_EXTREF_KEY:
> +	case BTRFS_ROOT_REF_KEY:
> +	case BTRFS_ROOT_BACKREF_KEY:
> +		ret = check_variable_length_item(root, leaf, key, slot);
> +		break;
>  	}
>  	return ret;
>  }
> 
--
To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Qu Wenruo Nov. 2, 2017, 12:37 p.m. UTC | #3
On 2017年11月02日 20:06, Nikolay Borisov wrote:
> 
> 
> On  1.11.2017 14:14, Qu Wenruo wrote:
>> For the following types, we have items with variable length:
>> (With BTRFS_ prefix and _KEY suffix snipped)
>>
>> DIR_ITEM
>> DIR_INDEX
>> XATTR_ITEM
>> INODE_REF
>> INODE_EXTREF
>> ROOT_REF
>> ROOT_BACKREF
>>
>> They all use @name_len to indicate their name length, and XATTR_ITEM has
>> extra @data_len to indicate it data length.
>>
>> Despite their variable length, it's also possible to have several such
>> structure inside one item.
>>
>> This patch will add checker to ensure:
>>
>> 1) No structure header and its data cross item boundary
>> 2) Except XATTR_ITEM, no structure should have non-zero @data_len
>>
>> This checker is especially useful to avoid possible access beyond
>> boundary for fuzzed image.
>>> Signed-off-by: Qu Wenruo <wqu@suse.com>
>> ---
>>  fs/btrfs/tree-checker.c | 123 ++++++++++++++++++++++++++++++++++++++++++++++++
>>  1 file changed, 123 insertions(+)
>>
>> diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c
>> index 114fc5f0ecc5..f26e86fcbd74 100644
>> --- a/fs/btrfs/tree-checker.c
>> +++ b/fs/btrfs/tree-checker.c
>> @@ -222,6 +222,120 @@ static int check_csum_item(struct btrfs_root *root, struct extent_buffer *leaf,
>>  	return 0;
>>  }
>>  
>> +static u32 get_header_size(u8 type)
>> +{
>> +	switch (type) {
>> +	case BTRFS_DIR_ITEM_KEY:
>> +	case BTRFS_DIR_INDEX_KEY:
>> +	case BTRFS_XATTR_ITEM_KEY:
>> +		return sizeof(struct btrfs_dir_item);
>> +	case BTRFS_INODE_REF_KEY:
>> +		return sizeof(struct btrfs_inode_ref);
>> +	case BTRFS_INODE_EXTREF_KEY:
>> +		return sizeof(struct btrfs_inode_extref);
>> +	case BTRFS_ROOT_REF_KEY:
>> +	case BTRFS_ROOT_BACKREF_KEY:
>> +		return sizeof(struct btrfs_root_ref);
>> +	}
>> +	WARN_ON(1);
>> +	return 0;
>> +}
>> +
>> +static u16 get_header_namelen(struct extent_buffer *leaf, u8 type,
>> +			      u32 header_offset)
>> +{
>> +	/*
>> +	 * @header_offset is offset starts after leaf header, while the
>> +	 * accessors expects offset starts from leaf header.
>> +	 * Sowe need to adds LEAF_DATA_OFFSET here
>> +	 */
> 
> The comment is confusing (but so are the internals of accessing an
> extent buffer and all the offset magic happening behind the scenes).

The sentence inside the brackets is the point. :)

> 
> You are essentially opencoding btrfs_item_ptr_offset but instead of
> pointing at the first entry in the data portion, you really point to
> 'cur' entry and in order to make it "accessor friendly" you do the +
> BTRFS_LEAF_DATA_OFFSET. I'd say just put something like :
> 
> "This is the same as btrfs_item_ptr_offset but starting at item data
> pointed by header_offset". In fact I'd suggest renaming header_offset to
> data_offset

Only if we can ensure all offsets passed in are starting after header,
then I'm OK to rename it to "data_offset"

The "header_offset" here is to info any caller that one should pass
"normal" offset here, to avoid possible LEAF_DATA_OFFSET hassle.

Thanks,
Qu

> 
>> +	unsigned long leaf_offset = header_offset + BTRFS_LEAF_DATA_OFFSET;
>> +
>> +	switch (type) {
>> +	case BTRFS_DIR_ITEM_KEY:
>> +	case BTRFS_DIR_INDEX_KEY:
>> +	case BTRFS_XATTR_ITEM_KEY:
>> +		return btrfs_dir_name_len(leaf, (void *)leaf_offset);
>> +	case BTRFS_INODE_REF_KEY:
>> +		return btrfs_inode_ref_name_len(leaf, (void *)leaf_offset);
>> +	case BTRFS_INODE_EXTREF_KEY:
>> +		return btrfs_inode_extref_name_len(leaf, (void *)leaf_offset);
>> +	case BTRFS_ROOT_REF_KEY:
>> +	case BTRFS_ROOT_BACKREF_KEY:
>> +		return btrfs_root_ref_name_len(leaf, (void *)leaf_offset);
>> +	}
>> +	WARN_ON(1);
>> +	return 0;
>> +}
>> +
>> +static u16 get_header_datalen(struct extent_buffer *leaf, u8 type,
>> +			      unsigned long header_offset)
>> +{
>> +	/* Same as get_header_namelen */
>> +	unsigned long leaf_offset = header_offset + BTRFS_LEAF_DATA_OFFSET;
>> +
>> +	switch (type) {
>> +	case BTRFS_DIR_ITEM_KEY:
>> +	case BTRFS_DIR_INDEX_KEY:
>> +	case BTRFS_XATTR_ITEM_KEY:
>> +		return btrfs_dir_data_len(leaf, (void *)leaf_offset);
>> +	}
>> +	return 0;
>> +}
>> +
>> +/*
>> + * For items with variable length, normally with namelen and tailing data.
>> + * Like INODE_REF or XATTR
>> + */
>> +static int check_variable_length_item(struct btrfs_root *root,
>> +				      struct extent_buffer *leaf,
>> +				      struct btrfs_key *key, int slot)
>> +{
>> +	u8 type = key->type;
>> +	u32 item_start = btrfs_item_offset_nr(leaf, slot);
>> +	u32 item_end = btrfs_item_end_nr(leaf, slot);
>> +	u32 header_size = get_header_size(type);
>> +	u32 total_size;
>> +	u32 cur = item_start;
> 
> nit: Rename cur to cur_offset to make it clear this is an offset.
> 
>> +
>> +	while (cur < item_end) {
>> +		u32 namelen;
>> +		u32 datalen;
>> +
>> +		/* header itself should not cross item boundary */
>> +		if (cur + header_size > item_end) {
>> +			generic_err(root, leaf, slot,
>> +				"structure header crosses item boundary, have %u expect (%u, %u]",
>> +				cur + header_size, cur, item_end);
>> +			return -EUCLEAN;
>> +		}
>> +
>> +		namelen = get_header_namelen(leaf, type, cur);
>> +		datalen = get_header_datalen(leaf, type, cur);
>> +
>> +		/* Only XATTR can own data */
>> +		if (type != BTRFS_XATTR_ITEM_KEY && datalen) {
>> +			generic_err(root, leaf, slot,
>> +				"item has invalid data len, have %u expect 0",
>> +				datalen);
>> +			return -EUCLEAN;
>> +		}
>> +
>> +		total_size = header_size + namelen + datalen;
>> +
>> +		/* header and name/data should not cross item boundary */
>> +		if (cur + total_size > item_end) {
>> +			generic_err(root, leaf, slot,
>> +				"structure data crosses item boundary, have %u expect (%u, %u]",
>> +				cur + total_size, cur + header_size, item_end);
>> +			return -EUCLEAN;
>> +		}
>> +
>> +		cur += total_size;
>> +	}
>> +	return 0;
>> +}
>> +
>>  /*
>>   * Common point to switch the item-specific validation.
>>   */
>> @@ -238,6 +352,15 @@ static int check_leaf_item(struct btrfs_root *root,
>>  	case BTRFS_EXTENT_CSUM_KEY:
>>  		ret = check_csum_item(root, leaf, key, slot);
>>  		break;
>> +	case BTRFS_DIR_ITEM_KEY:
>> +	case BTRFS_XATTR_ITEM_KEY:
>> +	case BTRFS_DIR_INDEX_KEY:
>> +	case BTRFS_INODE_REF_KEY:
>> +	case BTRFS_INODE_EXTREF_KEY:
>> +	case BTRFS_ROOT_REF_KEY:
>> +	case BTRFS_ROOT_BACKREF_KEY:
>> +		ret = check_variable_length_item(root, leaf, key, slot);
>> +		break;
>>  	}
>>  	return ret;
>>  }
>>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>
Qu Wenruo Nov. 2, 2017, 12:48 p.m. UTC | #4
On 2017年11月02日 20:12, Nikolay Borisov wrote:
> 
> 
> On  1.11.2017 14:14, Qu Wenruo wrote:
>> For the following types, we have items with variable length:
>> (With BTRFS_ prefix and _KEY suffix snipped)
>>
>> DIR_ITEM
>> DIR_INDEX
>> XATTR_ITEM
>> INODE_REF
>> INODE_EXTREF
>> ROOT_REF
>> ROOT_BACKREF
>>
>> They all use @name_len to indicate their name length, and XATTR_ITEM has
>> extra @data_len to indicate it data length.
>>
>> Despite their variable length, it's also possible to have several such
>> structure inside one item.
>>
>> This patch will add checker to ensure:
>>
>> 1) No structure header and its data cross item boundary
>> 2) Except XATTR_ITEM, no structure should have non-zero @data_len
>>
>> This checker is especially useful to avoid possible access beyond
>> boundary for fuzzed image.
>>
>> Signed-off-by: Qu Wenruo <wqu@suse.com>
>> ---
>>  fs/btrfs/tree-checker.c | 123 ++++++++++++++++++++++++++++++++++++++++++++++++
>>  1 file changed, 123 insertions(+)
>>
>> diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c
>> index 114fc5f0ecc5..f26e86fcbd74 100644
>> --- a/fs/btrfs/tree-checker.c
>> +++ b/fs/btrfs/tree-checker.c
>> @@ -222,6 +222,120 @@ static int check_csum_item(struct btrfs_root *root, struct extent_buffer *leaf,
>>  	return 0;
>>  }
>>  
>> +static u32 get_header_size(u8 type)
>> +{
>> +	switch (type) {
>> +	case BTRFS_DIR_ITEM_KEY:
>> +	case BTRFS_DIR_INDEX_KEY:
>> +	case BTRFS_XATTR_ITEM_KEY:
>> +		return sizeof(struct btrfs_dir_item);
>> +	case BTRFS_INODE_REF_KEY:
>> +		return sizeof(struct btrfs_inode_ref);
>> +	case BTRFS_INODE_EXTREF_KEY:
>> +		return sizeof(struct btrfs_inode_extref);
>> +	case BTRFS_ROOT_REF_KEY:
>> +	case BTRFS_ROOT_BACKREF_KEY:
>> +		return sizeof(struct btrfs_root_ref);
>> +	}
>> +	WARN_ON(1);
>> +	return 0;
>> +}
>> +
>> +static u16 get_header_namelen(struct extent_buffer *leaf, u8 type,
>> +			      u32 header_offset)
>> +{
>> +	/*
>> +	 * @header_offset is offset starts after leaf header, while the
>> +	 * accessors expects offset starts from leaf header.
>> +	 * Sowe need to adds LEAF_DATA_OFFSET here
>> +	 */
>> +	unsigned long leaf_offset = header_offset + BTRFS_LEAF_DATA_OFFSET;
>> +
>> +	switch (type) {
>> +	case BTRFS_DIR_ITEM_KEY:
>> +	case BTRFS_DIR_INDEX_KEY:
>> +	case BTRFS_XATTR_ITEM_KEY:
>> +		return btrfs_dir_name_len(leaf, (void *)leaf_offset);
>> +	case BTRFS_INODE_REF_KEY:
>> +		return btrfs_inode_ref_name_len(leaf, (void *)leaf_offset);
>> +	case BTRFS_INODE_EXTREF_KEY:
>> +		return btrfs_inode_extref_name_len(leaf, (void *)leaf_offset);
>> +	case BTRFS_ROOT_REF_KEY:
>> +	case BTRFS_ROOT_BACKREF_KEY:
>> +		return btrfs_root_ref_name_len(leaf, (void *)leaf_offset);
>> +	}
>> +	WARN_ON(1);
>> +	return 0;
>> +}
>> +
>> +static u16 get_header_datalen(struct extent_buffer *leaf, u8 type,
>> +			      unsigned long header_offset)
>> +{
>> +	/* Same as get_header_namelen */
>> +	unsigned long leaf_offset = header_offset + BTRFS_LEAF_DATA_OFFSET;
>> +
>> +	switch (type) {
>> +	case BTRFS_DIR_ITEM_KEY:
>> +	case BTRFS_DIR_INDEX_KEY:
>> +	case BTRFS_XATTR_ITEM_KEY:
>> +		return btrfs_dir_data_len(leaf, (void *)leaf_offset);
>> +	}
>> +	return 0;
>> +}
>> +
>> +/*
>> + * For items with variable length, normally with namelen and tailing data.
>> + * Like INODE_REF or XATTR
>> + */
>> +static int check_variable_length_item(struct btrfs_root *root,
>> +				      struct extent_buffer *leaf,
>> +				      struct btrfs_key *key, int slot)
>> +{
> 
> One more thing - you are only validating the boundaries of such variable
> length items, so make this specific. I.e rename the function to
> something like:

If you follow the naming schema in tree-checker, you'll find that we (at
least myself) is naming these function always using "check_" prefix, so
"validate_" prefix seems less consistent.

Further more, the naming has its level"
leaf (all items boundary already checked)
|- leaf_item (hub wrapper)
   |- csum_item
   |- extent_data_item

So here check_<something>_item follows the original level according to
the naming schema.

Further more, although we're checking boundary, the truth is, we are
check the name_len/data_len *members*, so the check_<something>_item
still makes sence.

If really want to change the name, I prefer some naming can show that
we're checking several items which share the same variable length property.

So at least, none of the alternative seems to fit the schema.

Thanks,
Qu

> 
> validate_variable_boundaries
> check_variable_length_item_boundary
> check_item_boundaries
> 
>> +	u8 type = key->type;
>> +	u32 item_start = btrfs_item_offset_nr(leaf, slot);
>> +	u32 item_end = btrfs_item_end_nr(leaf, slot);
>> +	u32 header_size = get_header_size(type);
>> +	u32 total_size;
>> +	u32 cur = item_start;
>> +
>> +	while (cur < item_end) {
>> +		u32 namelen;
>> +		u32 datalen;
>> +
>> +		/* header itself should not cross item boundary */
>> +		if (cur + header_size > item_end) {
>> +			generic_err(root, leaf, slot,
>> +				"structure header crosses item boundary, have %u expect (%u, %u]",
>> +				cur + header_size, cur, item_end);
>> +			return -EUCLEAN;
>> +		}
>> +
>> +		namelen = get_header_namelen(leaf, type, cur);
>> +		datalen = get_header_datalen(leaf, type, cur);
>> +
>> +		/* Only XATTR can own data */
>> +		if (type != BTRFS_XATTR_ITEM_KEY && datalen) {
>> +			generic_err(root, leaf, slot,
>> +				"item has invalid data len, have %u expect 0",
>> +				datalen);
>> +			return -EUCLEAN;
>> +		}
>> +
>> +		total_size = header_size + namelen + datalen;
>> +
>> +		/* header and name/data should not cross item boundary */
>> +		if (cur + total_size > item_end) {
>> +			generic_err(root, leaf, slot,
>> +				"structure data crosses item boundary, have %u expect (%u, %u]",
>> +				cur + total_size, cur + header_size, item_end);
>> +			return -EUCLEAN;
>> +		}
>> +
>> +		cur += total_size;
>> +	}
>> +	return 0;
>> +}
>> +
>>  /*
>>   * Common point to switch the item-specific validation.
>>   */
>> @@ -238,6 +352,15 @@ static int check_leaf_item(struct btrfs_root *root,
>>  	case BTRFS_EXTENT_CSUM_KEY:
>>  		ret = check_csum_item(root, leaf, key, slot);
>>  		break;
>> +	case BTRFS_DIR_ITEM_KEY:
>> +	case BTRFS_XATTR_ITEM_KEY:
>> +	case BTRFS_DIR_INDEX_KEY:
>> +	case BTRFS_INODE_REF_KEY:
>> +	case BTRFS_INODE_EXTREF_KEY:
>> +	case BTRFS_ROOT_REF_KEY:
>> +	case BTRFS_ROOT_BACKREF_KEY:
>> +		ret = check_variable_length_item(root, leaf, key, slot);
>> +		break;
>>  	}
>>  	return ret;
>>  }
>>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>
Nikolay Borisov Nov. 2, 2017, 12:52 p.m. UTC | #5
On  2.11.2017 14:37, Qu Wenruo wrote:
> 
> 
> On 2017年11月02日 20:06, Nikolay Borisov wrote:
>>
>>
>> On  1.11.2017 14:14, Qu Wenruo wrote:
>>> For the following types, we have items with variable length:
>>> (With BTRFS_ prefix and _KEY suffix snipped)
>>>
>>> DIR_ITEM
>>> DIR_INDEX
>>> XATTR_ITEM
>>> INODE_REF
>>> INODE_EXTREF
>>> ROOT_REF
>>> ROOT_BACKREF
>>>
>>> They all use @name_len to indicate their name length, and XATTR_ITEM has
>>> extra @data_len to indicate it data length.
>>>
>>> Despite their variable length, it's also possible to have several such
>>> structure inside one item.
>>>
>>> This patch will add checker to ensure:
>>>
>>> 1) No structure header and its data cross item boundary
>>> 2) Except XATTR_ITEM, no structure should have non-zero @data_len
>>>
>>> This checker is especially useful to avoid possible access beyond
>>> boundary for fuzzed image.
>>>> Signed-off-by: Qu Wenruo <wqu@suse.com>
>>> ---
>>>  fs/btrfs/tree-checker.c | 123 ++++++++++++++++++++++++++++++++++++++++++++++++
>>>  1 file changed, 123 insertions(+)
>>>
>>> diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c
>>> index 114fc5f0ecc5..f26e86fcbd74 100644
>>> --- a/fs/btrfs/tree-checker.c
>>> +++ b/fs/btrfs/tree-checker.c
>>> @@ -222,6 +222,120 @@ static int check_csum_item(struct btrfs_root *root, struct extent_buffer *leaf,
>>>  	return 0;
>>>  }
>>>  
>>> +static u32 get_header_size(u8 type)
>>> +{
>>> +	switch (type) {
>>> +	case BTRFS_DIR_ITEM_KEY:
>>> +	case BTRFS_DIR_INDEX_KEY:
>>> +	case BTRFS_XATTR_ITEM_KEY:
>>> +		return sizeof(struct btrfs_dir_item);
>>> +	case BTRFS_INODE_REF_KEY:
>>> +		return sizeof(struct btrfs_inode_ref);
>>> +	case BTRFS_INODE_EXTREF_KEY:
>>> +		return sizeof(struct btrfs_inode_extref);
>>> +	case BTRFS_ROOT_REF_KEY:
>>> +	case BTRFS_ROOT_BACKREF_KEY:
>>> +		return sizeof(struct btrfs_root_ref);
>>> +	}
>>> +	WARN_ON(1);
>>> +	return 0;
>>> +}
>>> +
>>> +static u16 get_header_namelen(struct extent_buffer *leaf, u8 type,
>>> +			      u32 header_offset)
>>> +{
>>> +	/*
>>> +	 * @header_offset is offset starts after leaf header, while the
>>> +	 * accessors expects offset starts from leaf header.
>>> +	 * Sowe need to adds LEAF_DATA_OFFSET here
>>> +	 */
>>
>> The comment is confusing (but so are the internals of accessing an
>> extent buffer and all the offset magic happening behind the scenes).
> 
> The sentence inside the brackets is the point. :)
> 
>>
>> You are essentially opencoding btrfs_item_ptr_offset but instead of
>> pointing at the first entry in the data portion, you really point to
>> 'cur' entry and in order to make it "accessor friendly" you do the +
>> BTRFS_LEAF_DATA_OFFSET. I'd say just put something like :
>>
>> "This is the same as btrfs_item_ptr_offset but starting at item data
>> pointed by header_offset". In fact I'd suggest renaming header_offset to
>> data_offset
> 
> Only if we can ensure all offsets passed in are starting after header,
> then I'm OK to rename it to "data_offset"

That's one of the things which confused me. Which header:

1. There is the btrfs_leaf which is at the beginning of the of the leaf
item and in order to access a pointer inside the leaf extent_buffer via
the helpers you really want the offset which skips this header and
points to the data item hence your + BTRFS_LEAF_DATA_OFFSET code

2. (The one which I assume you meant) There is the actual struct in the
data portion of the item, which acts as a header for the variable length
data (either name or name + data).

Since this function is only ever called from check_variable_length_item
and it guarantees that the passed value always points inside the data
portion I believe this is fine, no ?


> 
> The "header_offset" here is to info any caller that one should pass
> "normal" offset here, to avoid possible LEAF_DATA_OFFSET hassle.
> 
> Thanks,
> Qu
> 
>>
>>> +	unsigned long leaf_offset = header_offset + BTRFS_LEAF_DATA_OFFSET;
>>> +
>>> +	switch (type) {
>>> +	case BTRFS_DIR_ITEM_KEY:
>>> +	case BTRFS_DIR_INDEX_KEY:
>>> +	case BTRFS_XATTR_ITEM_KEY:
>>> +		return btrfs_dir_name_len(leaf, (void *)leaf_offset);
>>> +	case BTRFS_INODE_REF_KEY:
>>> +		return btrfs_inode_ref_name_len(leaf, (void *)leaf_offset);
>>> +	case BTRFS_INODE_EXTREF_KEY:
>>> +		return btrfs_inode_extref_name_len(leaf, (void *)leaf_offset);
>>> +	case BTRFS_ROOT_REF_KEY:
>>> +	case BTRFS_ROOT_BACKREF_KEY:
>>> +		return btrfs_root_ref_name_len(leaf, (void *)leaf_offset);
>>> +	}
>>> +	WARN_ON(1);
>>> +	return 0;
>>> +}
>>> +
>>> +static u16 get_header_datalen(struct extent_buffer *leaf, u8 type,
>>> +			      unsigned long header_offset)
>>> +{
>>> +	/* Same as get_header_namelen */
>>> +	unsigned long leaf_offset = header_offset + BTRFS_LEAF_DATA_OFFSET;
>>> +
>>> +	switch (type) {
>>> +	case BTRFS_DIR_ITEM_KEY:
>>> +	case BTRFS_DIR_INDEX_KEY:
>>> +	case BTRFS_XATTR_ITEM_KEY:
>>> +		return btrfs_dir_data_len(leaf, (void *)leaf_offset);
>>> +	}
>>> +	return 0;
>>> +}
>>> +
>>> +/*
>>> + * For items with variable length, normally with namelen and tailing data.
>>> + * Like INODE_REF or XATTR
>>> + */
>>> +static int check_variable_length_item(struct btrfs_root *root,
>>> +				      struct extent_buffer *leaf,
>>> +				      struct btrfs_key *key, int slot)
>>> +{
>>> +	u8 type = key->type;
>>> +	u32 item_start = btrfs_item_offset_nr(leaf, slot);
>>> +	u32 item_end = btrfs_item_end_nr(leaf, slot);
>>> +	u32 header_size = get_header_size(type);
>>> +	u32 total_size;
>>> +	u32 cur = item_start;
>>
>> nit: Rename cur to cur_offset to make it clear this is an offset.
>>
>>> +
>>> +	while (cur < item_end) {
>>> +		u32 namelen;
>>> +		u32 datalen;
>>> +
>>> +		/* header itself should not cross item boundary */
>>> +		if (cur + header_size > item_end) {
>>> +			generic_err(root, leaf, slot,
>>> +				"structure header crosses item boundary, have %u expect (%u, %u]",
>>> +				cur + header_size, cur, item_end);
>>> +			return -EUCLEAN;
>>> +		}
>>> +
>>> +		namelen = get_header_namelen(leaf, type, cur);
>>> +		datalen = get_header_datalen(leaf, type, cur);
>>> +
>>> +		/* Only XATTR can own data */
>>> +		if (type != BTRFS_XATTR_ITEM_KEY && datalen) {
>>> +			generic_err(root, leaf, slot,
>>> +				"item has invalid data len, have %u expect 0",
>>> +				datalen);
>>> +			return -EUCLEAN;
>>> +		}
>>> +
>>> +		total_size = header_size + namelen + datalen;
>>> +
>>> +		/* header and name/data should not cross item boundary */
>>> +		if (cur + total_size > item_end) {
>>> +			generic_err(root, leaf, slot,
>>> +				"structure data crosses item boundary, have %u expect (%u, %u]",
>>> +				cur + total_size, cur + header_size, item_end);
>>> +			return -EUCLEAN;
>>> +		}
>>> +
>>> +		cur += total_size;
>>> +	}
>>> +	return 0;
>>> +}
>>> +
>>>  /*
>>>   * Common point to switch the item-specific validation.
>>>   */
>>> @@ -238,6 +352,15 @@ static int check_leaf_item(struct btrfs_root *root,
>>>  	case BTRFS_EXTENT_CSUM_KEY:
>>>  		ret = check_csum_item(root, leaf, key, slot);
>>>  		break;
>>> +	case BTRFS_DIR_ITEM_KEY:
>>> +	case BTRFS_XATTR_ITEM_KEY:
>>> +	case BTRFS_DIR_INDEX_KEY:
>>> +	case BTRFS_INODE_REF_KEY:
>>> +	case BTRFS_INODE_EXTREF_KEY:
>>> +	case BTRFS_ROOT_REF_KEY:
>>> +	case BTRFS_ROOT_BACKREF_KEY:
>>> +		ret = check_variable_length_item(root, leaf, key, slot);
>>> +		break;
>>>  	}
>>>  	return ret;
>>>  }
>>>
>> --
>> To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in
>> the body of a message to majordomo@vger.kernel.org
>> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>>
> 
--
To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Nikolay Borisov Nov. 2, 2017, 12:56 p.m. UTC | #6
On  2.11.2017 14:48, Qu Wenruo wrote:
> 
> 
> On 2017年11月02日 20:12, Nikolay Borisov wrote:
>>
>>
>> On  1.11.2017 14:14, Qu Wenruo wrote:
>>> For the following types, we have items with variable length:
>>> (With BTRFS_ prefix and _KEY suffix snipped)
>>>
>>> DIR_ITEM
>>> DIR_INDEX
>>> XATTR_ITEM
>>> INODE_REF
>>> INODE_EXTREF
>>> ROOT_REF
>>> ROOT_BACKREF
>>>
>>> They all use @name_len to indicate their name length, and XATTR_ITEM has
>>> extra @data_len to indicate it data length.
>>>
>>> Despite their variable length, it's also possible to have several such
>>> structure inside one item.
>>>
>>> This patch will add checker to ensure:
>>>
>>> 1) No structure header and its data cross item boundary
>>> 2) Except XATTR_ITEM, no structure should have non-zero @data_len
>>>
>>> This checker is especially useful to avoid possible access beyond
>>> boundary for fuzzed image.
>>>
>>> Signed-off-by: Qu Wenruo <wqu@suse.com>
>>> ---
>>>  fs/btrfs/tree-checker.c | 123 ++++++++++++++++++++++++++++++++++++++++++++++++
>>>  1 file changed, 123 insertions(+)
>>>
>>> diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c
>>> index 114fc5f0ecc5..f26e86fcbd74 100644
>>> --- a/fs/btrfs/tree-checker.c
>>> +++ b/fs/btrfs/tree-checker.c
>>> @@ -222,6 +222,120 @@ static int check_csum_item(struct btrfs_root *root, struct extent_buffer *leaf,
>>>  	return 0;
>>>  }
>>>  
>>> +static u32 get_header_size(u8 type)
>>> +{
>>> +	switch (type) {
>>> +	case BTRFS_DIR_ITEM_KEY:
>>> +	case BTRFS_DIR_INDEX_KEY:
>>> +	case BTRFS_XATTR_ITEM_KEY:
>>> +		return sizeof(struct btrfs_dir_item);
>>> +	case BTRFS_INODE_REF_KEY:
>>> +		return sizeof(struct btrfs_inode_ref);
>>> +	case BTRFS_INODE_EXTREF_KEY:
>>> +		return sizeof(struct btrfs_inode_extref);
>>> +	case BTRFS_ROOT_REF_KEY:
>>> +	case BTRFS_ROOT_BACKREF_KEY:
>>> +		return sizeof(struct btrfs_root_ref);
>>> +	}
>>> +	WARN_ON(1);
>>> +	return 0;
>>> +}
>>> +
>>> +static u16 get_header_namelen(struct extent_buffer *leaf, u8 type,
>>> +			      u32 header_offset)
>>> +{
>>> +	/*
>>> +	 * @header_offset is offset starts after leaf header, while the
>>> +	 * accessors expects offset starts from leaf header.
>>> +	 * Sowe need to adds LEAF_DATA_OFFSET here
>>> +	 */
>>> +	unsigned long leaf_offset = header_offset + BTRFS_LEAF_DATA_OFFSET;
>>> +
>>> +	switch (type) {
>>> +	case BTRFS_DIR_ITEM_KEY:
>>> +	case BTRFS_DIR_INDEX_KEY:
>>> +	case BTRFS_XATTR_ITEM_KEY:
>>> +		return btrfs_dir_name_len(leaf, (void *)leaf_offset);
>>> +	case BTRFS_INODE_REF_KEY:
>>> +		return btrfs_inode_ref_name_len(leaf, (void *)leaf_offset);
>>> +	case BTRFS_INODE_EXTREF_KEY:
>>> +		return btrfs_inode_extref_name_len(leaf, (void *)leaf_offset);
>>> +	case BTRFS_ROOT_REF_KEY:
>>> +	case BTRFS_ROOT_BACKREF_KEY:
>>> +		return btrfs_root_ref_name_len(leaf, (void *)leaf_offset);
>>> +	}
>>> +	WARN_ON(1);
>>> +	return 0;
>>> +}
>>> +
>>> +static u16 get_header_datalen(struct extent_buffer *leaf, u8 type,
>>> +			      unsigned long header_offset)
>>> +{
>>> +	/* Same as get_header_namelen */
>>> +	unsigned long leaf_offset = header_offset + BTRFS_LEAF_DATA_OFFSET;
>>> +
>>> +	switch (type) {
>>> +	case BTRFS_DIR_ITEM_KEY:
>>> +	case BTRFS_DIR_INDEX_KEY:
>>> +	case BTRFS_XATTR_ITEM_KEY:
>>> +		return btrfs_dir_data_len(leaf, (void *)leaf_offset);
>>> +	}
>>> +	return 0;
>>> +}
>>> +
>>> +/*
>>> + * For items with variable length, normally with namelen and tailing data.
>>> + * Like INODE_REF or XATTR
>>> + */
>>> +static int check_variable_length_item(struct btrfs_root *root,
>>> +				      struct extent_buffer *leaf,
>>> +				      struct btrfs_key *key, int slot)
>>> +{
>>
>> One more thing - you are only validating the boundaries of such variable
>> length items, so make this specific. I.e rename the function to
>> something like:
> 
> If you follow the naming schema in tree-checker, you'll find that we (at
> least myself) is naming these function always using "check_" prefix, so
> "validate_" prefix seems less consistent.
> 
> Further more, the naming has its level"
> leaf (all items boundary already checked)
> |- leaf_item (hub wrapper)
>    |- csum_item
>    |- extent_data_item
> 
> So here check_<something>_item follows the original level according to
> the naming schema.
> 
> Further more, although we're checking boundary, the truth is, we are
> check the name_len/data_len *members*, so the check_<something>_item
> still makes sence.
> 
> If really want to change the name, I prefer some naming can show that
> we're checking several items which share the same variable length property.
> 
> So at least, none of the alternative seems to fit the schema.

The reason why I wanted this function renamed is that we have this
function which performs only some checks and then we have the
verify_dir_item which performs different checks for the same item. So
why can't those function be turned into one ? I'm not too hung up on the
actual naming!

> 
> Thanks,
> Qu
> 
>>
>> validate_variable_boundaries
>> check_variable_length_item_boundary
>> check_item_boundaries
>>
>>> +	u8 type = key->type;
>>> +	u32 item_start = btrfs_item_offset_nr(leaf, slot);
>>> +	u32 item_end = btrfs_item_end_nr(leaf, slot);
>>> +	u32 header_size = get_header_size(type);
>>> +	u32 total_size;
>>> +	u32 cur = item_start;
>>> +
>>> +	while (cur < item_end) {
>>> +		u32 namelen;
>>> +		u32 datalen;
>>> +
>>> +		/* header itself should not cross item boundary */
>>> +		if (cur + header_size > item_end) {
>>> +			generic_err(root, leaf, slot,
>>> +				"structure header crosses item boundary, have %u expect (%u, %u]",
>>> +				cur + header_size, cur, item_end);
>>> +			return -EUCLEAN;
>>> +		}
>>> +
>>> +		namelen = get_header_namelen(leaf, type, cur);
>>> +		datalen = get_header_datalen(leaf, type, cur);
>>> +
>>> +		/* Only XATTR can own data */
>>> +		if (type != BTRFS_XATTR_ITEM_KEY && datalen) {
>>> +			generic_err(root, leaf, slot,
>>> +				"item has invalid data len, have %u expect 0",
>>> +				datalen);
>>> +			return -EUCLEAN;
>>> +		}
>>> +
>>> +		total_size = header_size + namelen + datalen;
>>> +
>>> +		/* header and name/data should not cross item boundary */
>>> +		if (cur + total_size > item_end) {
>>> +			generic_err(root, leaf, slot,
>>> +				"structure data crosses item boundary, have %u expect (%u, %u]",
>>> +				cur + total_size, cur + header_size, item_end);
>>> +			return -EUCLEAN;
>>> +		}
>>> +
>>> +		cur += total_size;
>>> +	}
>>> +	return 0;
>>> +}
>>> +
>>>  /*
>>>   * Common point to switch the item-specific validation.
>>>   */
>>> @@ -238,6 +352,15 @@ static int check_leaf_item(struct btrfs_root *root,
>>>  	case BTRFS_EXTENT_CSUM_KEY:
>>>  		ret = check_csum_item(root, leaf, key, slot);
>>>  		break;
>>> +	case BTRFS_DIR_ITEM_KEY:
>>> +	case BTRFS_XATTR_ITEM_KEY:
>>> +	case BTRFS_DIR_INDEX_KEY:
>>> +	case BTRFS_INODE_REF_KEY:
>>> +	case BTRFS_INODE_EXTREF_KEY:
>>> +	case BTRFS_ROOT_REF_KEY:
>>> +	case BTRFS_ROOT_BACKREF_KEY:
>>> +		ret = check_variable_length_item(root, leaf, key, slot);
>>> +		break;
>>>  	}
>>>  	return ret;
>>>  }
>>>
>> --
>> To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in
>> the body of a message to majordomo@vger.kernel.org
>> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>>
> 
--
To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Qu Wenruo Nov. 2, 2017, 1:35 p.m. UTC | #7
On 2017年11月02日 20:56, Nikolay Borisov wrote:
> 
> 
> On  2.11.2017 14:48, Qu Wenruo wrote:
>>
>>
>> On 2017年11月02日 20:12, Nikolay Borisov wrote:
>>>
>>>
>>> On  1.11.2017 14:14, Qu Wenruo wrote:
>>>> For the following types, we have items with variable length:
>>>> (With BTRFS_ prefix and _KEY suffix snipped)
>>>>
>>>> DIR_ITEM
>>>> DIR_INDEX
>>>> XATTR_ITEM
>>>> INODE_REF
>>>> INODE_EXTREF
>>>> ROOT_REF
>>>> ROOT_BACKREF
>>>>
>>>> They all use @name_len to indicate their name length, and XATTR_ITEM has
>>>> extra @data_len to indicate it data length.
>>>>
>>>> Despite their variable length, it's also possible to have several such
>>>> structure inside one item.
>>>>
>>>> This patch will add checker to ensure:
>>>>
>>>> 1) No structure header and its data cross item boundary
>>>> 2) Except XATTR_ITEM, no structure should have non-zero @data_len
>>>>
>>>> This checker is especially useful to avoid possible access beyond
>>>> boundary for fuzzed image.
>>>>
>>>> Signed-off-by: Qu Wenruo <wqu@suse.com>
>>>> ---
>>>>  fs/btrfs/tree-checker.c | 123 ++++++++++++++++++++++++++++++++++++++++++++++++
>>>>  1 file changed, 123 insertions(+)
>>>>
>>>> diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c
>>>> index 114fc5f0ecc5..f26e86fcbd74 100644
>>>> --- a/fs/btrfs/tree-checker.c
>>>> +++ b/fs/btrfs/tree-checker.c
>>>> @@ -222,6 +222,120 @@ static int check_csum_item(struct btrfs_root *root, struct extent_buffer *leaf,
>>>>  	return 0;
>>>>  }
>>>>  
>>>> +static u32 get_header_size(u8 type)
>>>> +{
>>>> +	switch (type) {
>>>> +	case BTRFS_DIR_ITEM_KEY:
>>>> +	case BTRFS_DIR_INDEX_KEY:
>>>> +	case BTRFS_XATTR_ITEM_KEY:
>>>> +		return sizeof(struct btrfs_dir_item);
>>>> +	case BTRFS_INODE_REF_KEY:
>>>> +		return sizeof(struct btrfs_inode_ref);
>>>> +	case BTRFS_INODE_EXTREF_KEY:
>>>> +		return sizeof(struct btrfs_inode_extref);
>>>> +	case BTRFS_ROOT_REF_KEY:
>>>> +	case BTRFS_ROOT_BACKREF_KEY:
>>>> +		return sizeof(struct btrfs_root_ref);
>>>> +	}
>>>> +	WARN_ON(1);
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static u16 get_header_namelen(struct extent_buffer *leaf, u8 type,
>>>> +			      u32 header_offset)
>>>> +{
>>>> +	/*
>>>> +	 * @header_offset is offset starts after leaf header, while the
>>>> +	 * accessors expects offset starts from leaf header.
>>>> +	 * Sowe need to adds LEAF_DATA_OFFSET here
>>>> +	 */
>>>> +	unsigned long leaf_offset = header_offset + BTRFS_LEAF_DATA_OFFSET;
>>>> +
>>>> +	switch (type) {
>>>> +	case BTRFS_DIR_ITEM_KEY:
>>>> +	case BTRFS_DIR_INDEX_KEY:
>>>> +	case BTRFS_XATTR_ITEM_KEY:
>>>> +		return btrfs_dir_name_len(leaf, (void *)leaf_offset);
>>>> +	case BTRFS_INODE_REF_KEY:
>>>> +		return btrfs_inode_ref_name_len(leaf, (void *)leaf_offset);
>>>> +	case BTRFS_INODE_EXTREF_KEY:
>>>> +		return btrfs_inode_extref_name_len(leaf, (void *)leaf_offset);
>>>> +	case BTRFS_ROOT_REF_KEY:
>>>> +	case BTRFS_ROOT_BACKREF_KEY:
>>>> +		return btrfs_root_ref_name_len(leaf, (void *)leaf_offset);
>>>> +	}
>>>> +	WARN_ON(1);
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static u16 get_header_datalen(struct extent_buffer *leaf, u8 type,
>>>> +			      unsigned long header_offset)
>>>> +{
>>>> +	/* Same as get_header_namelen */
>>>> +	unsigned long leaf_offset = header_offset + BTRFS_LEAF_DATA_OFFSET;
>>>> +
>>>> +	switch (type) {
>>>> +	case BTRFS_DIR_ITEM_KEY:
>>>> +	case BTRFS_DIR_INDEX_KEY:
>>>> +	case BTRFS_XATTR_ITEM_KEY:
>>>> +		return btrfs_dir_data_len(leaf, (void *)leaf_offset);
>>>> +	}
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +/*
>>>> + * For items with variable length, normally with namelen and tailing data.
>>>> + * Like INODE_REF or XATTR
>>>> + */
>>>> +static int check_variable_length_item(struct btrfs_root *root,
>>>> +				      struct extent_buffer *leaf,
>>>> +				      struct btrfs_key *key, int slot)
>>>> +{
>>>
>>> One more thing - you are only validating the boundaries of such variable
>>> length items, so make this specific. I.e rename the function to
>>> something like:
>>
>> If you follow the naming schema in tree-checker, you'll find that we (at
>> least myself) is naming these function always using "check_" prefix, so
>> "validate_" prefix seems less consistent.
>>
>> Further more, the naming has its level"
>> leaf (all items boundary already checked)
>> |- leaf_item (hub wrapper)
>>    |- csum_item
>>    |- extent_data_item
>>
>> So here check_<something>_item follows the original level according to
>> the naming schema.
>>
>> Further more, although we're checking boundary, the truth is, we are
>> check the name_len/data_len *members*, so the check_<something>_item
>> still makes sence.
>>
>> If really want to change the name, I prefer some naming can show that
>> we're checking several items which share the same variable length property.
>>
>> So at least, none of the alternative seems to fit the schema.
> 
> The reason why I wanted this function renamed is that we have this
> function which performs only some checks and then we have the
> verify_dir_item which performs different checks for the same item. So
> why can't those function be turned into one ? I'm not too hung up on the
> actual naming!

Well, this makes sense.

I should merge them up.
Since verify_dir_item() just do extra check on types, they can be easily
merged.
The original plan is to have check_<some extra item> under
check_variable_length_item() if needed.

Considering only type and maximum name len need to be addded, it's quite
an easy work.

I'll add them in next update. (With extra possible checks).

Thanks,
Qu

> 
>>
>> Thanks,
>> Qu
>>
>>>
>>> validate_variable_boundaries
>>> check_variable_length_item_boundary
>>> check_item_boundaries
>>>
>>>> +	u8 type = key->type;
>>>> +	u32 item_start = btrfs_item_offset_nr(leaf, slot);
>>>> +	u32 item_end = btrfs_item_end_nr(leaf, slot);
>>>> +	u32 header_size = get_header_size(type);
>>>> +	u32 total_size;
>>>> +	u32 cur = item_start;
>>>> +
>>>> +	while (cur < item_end) {
>>>> +		u32 namelen;
>>>> +		u32 datalen;
>>>> +
>>>> +		/* header itself should not cross item boundary */
>>>> +		if (cur + header_size > item_end) {
>>>> +			generic_err(root, leaf, slot,
>>>> +				"structure header crosses item boundary, have %u expect (%u, %u]",
>>>> +				cur + header_size, cur, item_end);
>>>> +			return -EUCLEAN;
>>>> +		}
>>>> +
>>>> +		namelen = get_header_namelen(leaf, type, cur);
>>>> +		datalen = get_header_datalen(leaf, type, cur);
>>>> +
>>>> +		/* Only XATTR can own data */
>>>> +		if (type != BTRFS_XATTR_ITEM_KEY && datalen) {
>>>> +			generic_err(root, leaf, slot,
>>>> +				"item has invalid data len, have %u expect 0",
>>>> +				datalen);
>>>> +			return -EUCLEAN;
>>>> +		}
>>>> +
>>>> +		total_size = header_size + namelen + datalen;
>>>> +
>>>> +		/* header and name/data should not cross item boundary */
>>>> +		if (cur + total_size > item_end) {
>>>> +			generic_err(root, leaf, slot,
>>>> +				"structure data crosses item boundary, have %u expect (%u, %u]",
>>>> +				cur + total_size, cur + header_size, item_end);
>>>> +			return -EUCLEAN;
>>>> +		}
>>>> +
>>>> +		cur += total_size;
>>>> +	}
>>>> +	return 0;
>>>> +}
>>>> +
>>>>  /*
>>>>   * Common point to switch the item-specific validation.
>>>>   */
>>>> @@ -238,6 +352,15 @@ static int check_leaf_item(struct btrfs_root *root,
>>>>  	case BTRFS_EXTENT_CSUM_KEY:
>>>>  		ret = check_csum_item(root, leaf, key, slot);
>>>>  		break;
>>>> +	case BTRFS_DIR_ITEM_KEY:
>>>> +	case BTRFS_XATTR_ITEM_KEY:
>>>> +	case BTRFS_DIR_INDEX_KEY:
>>>> +	case BTRFS_INODE_REF_KEY:
>>>> +	case BTRFS_INODE_EXTREF_KEY:
>>>> +	case BTRFS_ROOT_REF_KEY:
>>>> +	case BTRFS_ROOT_BACKREF_KEY:
>>>> +		ret = check_variable_length_item(root, leaf, key, slot);
>>>> +		break;
>>>>  	}
>>>>  	return ret;
>>>>  }
>>>>
>>> --
>>> To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in
>>> the body of a message to majordomo@vger.kernel.org
>>> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>>>
>>
diff mbox

Patch

diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c
index 114fc5f0ecc5..f26e86fcbd74 100644
--- a/fs/btrfs/tree-checker.c
+++ b/fs/btrfs/tree-checker.c
@@ -222,6 +222,120 @@  static int check_csum_item(struct btrfs_root *root, struct extent_buffer *leaf,
 	return 0;
 }
 
+static u32 get_header_size(u8 type)
+{
+	switch (type) {
+	case BTRFS_DIR_ITEM_KEY:
+	case BTRFS_DIR_INDEX_KEY:
+	case BTRFS_XATTR_ITEM_KEY:
+		return sizeof(struct btrfs_dir_item);
+	case BTRFS_INODE_REF_KEY:
+		return sizeof(struct btrfs_inode_ref);
+	case BTRFS_INODE_EXTREF_KEY:
+		return sizeof(struct btrfs_inode_extref);
+	case BTRFS_ROOT_REF_KEY:
+	case BTRFS_ROOT_BACKREF_KEY:
+		return sizeof(struct btrfs_root_ref);
+	}
+	WARN_ON(1);
+	return 0;
+}
+
+static u16 get_header_namelen(struct extent_buffer *leaf, u8 type,
+			      u32 header_offset)
+{
+	/*
+	 * @header_offset is offset starts after leaf header, while the
+	 * accessors expects offset starts from leaf header.
+	 * Sowe need to adds LEAF_DATA_OFFSET here
+	 */
+	unsigned long leaf_offset = header_offset + BTRFS_LEAF_DATA_OFFSET;
+
+	switch (type) {
+	case BTRFS_DIR_ITEM_KEY:
+	case BTRFS_DIR_INDEX_KEY:
+	case BTRFS_XATTR_ITEM_KEY:
+		return btrfs_dir_name_len(leaf, (void *)leaf_offset);
+	case BTRFS_INODE_REF_KEY:
+		return btrfs_inode_ref_name_len(leaf, (void *)leaf_offset);
+	case BTRFS_INODE_EXTREF_KEY:
+		return btrfs_inode_extref_name_len(leaf, (void *)leaf_offset);
+	case BTRFS_ROOT_REF_KEY:
+	case BTRFS_ROOT_BACKREF_KEY:
+		return btrfs_root_ref_name_len(leaf, (void *)leaf_offset);
+	}
+	WARN_ON(1);
+	return 0;
+}
+
+static u16 get_header_datalen(struct extent_buffer *leaf, u8 type,
+			      unsigned long header_offset)
+{
+	/* Same as get_header_namelen */
+	unsigned long leaf_offset = header_offset + BTRFS_LEAF_DATA_OFFSET;
+
+	switch (type) {
+	case BTRFS_DIR_ITEM_KEY:
+	case BTRFS_DIR_INDEX_KEY:
+	case BTRFS_XATTR_ITEM_KEY:
+		return btrfs_dir_data_len(leaf, (void *)leaf_offset);
+	}
+	return 0;
+}
+
+/*
+ * For items with variable length, normally with namelen and tailing data.
+ * Like INODE_REF or XATTR
+ */
+static int check_variable_length_item(struct btrfs_root *root,
+				      struct extent_buffer *leaf,
+				      struct btrfs_key *key, int slot)
+{
+	u8 type = key->type;
+	u32 item_start = btrfs_item_offset_nr(leaf, slot);
+	u32 item_end = btrfs_item_end_nr(leaf, slot);
+	u32 header_size = get_header_size(type);
+	u32 total_size;
+	u32 cur = item_start;
+
+	while (cur < item_end) {
+		u32 namelen;
+		u32 datalen;
+
+		/* header itself should not cross item boundary */
+		if (cur + header_size > item_end) {
+			generic_err(root, leaf, slot,
+				"structure header crosses item boundary, have %u expect (%u, %u]",
+				cur + header_size, cur, item_end);
+			return -EUCLEAN;
+		}
+
+		namelen = get_header_namelen(leaf, type, cur);
+		datalen = get_header_datalen(leaf, type, cur);
+
+		/* Only XATTR can own data */
+		if (type != BTRFS_XATTR_ITEM_KEY && datalen) {
+			generic_err(root, leaf, slot,
+				"item has invalid data len, have %u expect 0",
+				datalen);
+			return -EUCLEAN;
+		}
+
+		total_size = header_size + namelen + datalen;
+
+		/* header and name/data should not cross item boundary */
+		if (cur + total_size > item_end) {
+			generic_err(root, leaf, slot,
+				"structure data crosses item boundary, have %u expect (%u, %u]",
+				cur + total_size, cur + header_size, item_end);
+			return -EUCLEAN;
+		}
+
+		cur += total_size;
+	}
+	return 0;
+}
+
 /*
  * Common point to switch the item-specific validation.
  */
@@ -238,6 +352,15 @@  static int check_leaf_item(struct btrfs_root *root,
 	case BTRFS_EXTENT_CSUM_KEY:
 		ret = check_csum_item(root, leaf, key, slot);
 		break;
+	case BTRFS_DIR_ITEM_KEY:
+	case BTRFS_XATTR_ITEM_KEY:
+	case BTRFS_DIR_INDEX_KEY:
+	case BTRFS_INODE_REF_KEY:
+	case BTRFS_INODE_EXTREF_KEY:
+	case BTRFS_ROOT_REF_KEY:
+	case BTRFS_ROOT_BACKREF_KEY:
+		ret = check_variable_length_item(root, leaf, key, slot);
+		break;
 	}
 	return ret;
 }