Re: [PATCH 1/2] btrfs: Introduce mount time chunk <-> dev extent mapping check

2018-07-31 Thread Qu Wenruo


On 2018年07月16日 21:06, David Sterba wrote:
> On Mon, Jul 09, 2018 at 02:42:02PM +0800, Qu Wenruo wrote:
>> This patch will introduce chunk <-> dev extent mapping check, to protect
>> us against invalid dev extents or chunks.
>>
>> Since chunk mapping is the fundamental infrastructure of btrfs, extra
>> check at mount time could prevent a lot of unexpected behavior (BUG_ON).
>>
>> Reported-by: Xu Wen 
>> Links: https://bugzilla.kernel.org/show_bug.cgi?id=200403
> 
> Link:
> 
>> Links: https://bugzilla.kernel.org/show_bug.cgi?id=200407
>> Signed-off-by: Qu Wenruo 
>> ---
>>  fs/btrfs/disk-io.c |   7 ++
>>  fs/btrfs/volumes.c | 173 +
>>  fs/btrfs/volumes.h |   2 +
>>  3 files changed, 182 insertions(+)
>>
>> diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
>> index 205092dc9390..068ca7498e94 100644
>> --- a/fs/btrfs/disk-io.c
>> +++ b/fs/btrfs/disk-io.c
>> @@ -3075,6 +3075,13 @@ int open_ctree(struct super_block *sb,
>>  fs_info->generation = generation;
>>  fs_info->last_trans_committed = generation;
>>  
>> +ret = btrfs_verify_dev_extents(fs_info);
>> +if (ret) {
>> +btrfs_err(fs_info,
>> +  "failed to verify dev extents against chunks: %d",
>> +  ret);
>> +goto fail_block_groups;
>> +}
>>  ret = btrfs_recover_balance(fs_info);
>>  if (ret) {
>>  btrfs_err(fs_info, "failed to recover balance: %d", ret);
>> diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c
>> index e6a8e4aabc66..05e418cb37f3 100644
>> --- a/fs/btrfs/volumes.c
>> +++ b/fs/btrfs/volumes.c
>> @@ -6440,6 +6440,7 @@ static int read_one_chunk(struct btrfs_fs_info 
>> *fs_info, struct btrfs_key *key,
>>  map->stripe_len = btrfs_chunk_stripe_len(leaf, chunk);
>>  map->type = btrfs_chunk_type(leaf, chunk);
>>  map->sub_stripes = btrfs_chunk_sub_stripes(leaf, chunk);
>> +map->verified_stripes = 0;
>>  for (i = 0; i < num_stripes; i++) {
>>  map->stripes[i].physical =
>>  btrfs_stripe_offset_nr(leaf, chunk, i);
>> @@ -7295,3 +7296,175 @@ void btrfs_reset_fs_info_ptr(struct btrfs_fs_info 
>> *fs_info)
>>  fs_devices = fs_devices->seed;
>>  }
>>  }
>> +
>> +static u64 calc_stripe_length(u64 type, u64 chunk_len, int num_stripes)
>> +{
>> +switch (type & BTRFS_BLOCK_GROUP_PROFILE_MASK) {
>> +case BTRFS_BLOCK_GROUP_RAID0:
>> +return div_u64(chunk_len, num_stripes);
>> +case BTRFS_BLOCK_GROUP_RAID10:
>> +return div_u64(chunk_len * 2, num_stripes);
>> +case BTRFS_BLOCK_GROUP_RAID5:
>> +return div_u64(chunk_len, num_stripes - 1);
>> +case BTRFS_BLOCK_GROUP_RAID6:
>> +return div_u64(chunk_len, num_stripes - 2);
>> +default:
>> +return chunk_len;
>> +}
>> +}
> 
> There are already too many hardcoded values for the raid profiles,
> please don't add another one unless really necessary and use existing
> predefined constants or helpers (eg. nr_data_stripes or
> btrfs_raid_array).

OK, I'll try to reuse btrfs_raid_array in next version.

Thanks,
Qu

> 
>> +static int verify_one_dev_extent(struct btrfs_fs_info *fs_info,
>> + u64 chunk_offset, u64 devid,
>> + u64 physical_offset, u64 physical_len)
>> +{
>> +struct extent_map_tree *em_tree = _info->mapping_tree.map_tree;
>> +struct extent_map *em;
>> +struct map_lookup *map;
>> +u64 stripe_len;
>> +bool found = false;
> 
> This variable is only set and never checked.
> 
>> +int ret = 0;
>> +int i;
>> +
>> +read_lock(_tree->lock);
>> +em = lookup_extent_mapping(em_tree, chunk_offset, 1);
>> +read_unlock(_tree->lock);
>> +
>> +if (!em) {
>> +ret = -EUCLEAN;
>> +btrfs_err(fs_info,
>> +"dev extent (%llu, %llu) doesn't have corresponding chunk",
>> +  devid, physical_offset);
>> +goto out;
>> +}
>> +
>> +map = em->map_lookup;
>> +stripe_len = calc_stripe_length(map->type, em->len, map->num_stripes);
>> +if (physical_len != stripe_len) {
>> +btrfs_err(fs_info,
>> +"dev extent (%llu, %llu) length doesn't match with chunk %llu, have %llu 
>> expect %llu",
>> +  devid, physical_offset, em->start, physical_len,
>> +  stripe_len);
>> +ret = -EUCLEAN;
>> +goto out;
>> +}
>> +
>> +for (i = 0; i < map->num_stripes; i++) {
>> +if (map->stripes[i].dev->devid == devid &&
>> +map->stripes[i].physical == physical_offset) {
>> +found = true;
> 
> 2nd time set
> 
>> +if (map->verified_stripes >= map->num_stripes) {
>> +btrfs_err(fs_info,
>> +"too many dev extent for chunk %llu is detected",
>> +  em->start);
>> + 

Re: [PATCH 1/2] btrfs: Introduce mount time chunk <-> dev extent mapping check

2018-07-16 Thread David Sterba
On Mon, Jul 09, 2018 at 02:42:02PM +0800, Qu Wenruo wrote:
> This patch will introduce chunk <-> dev extent mapping check, to protect
> us against invalid dev extents or chunks.
> 
> Since chunk mapping is the fundamental infrastructure of btrfs, extra
> check at mount time could prevent a lot of unexpected behavior (BUG_ON).
> 
> Reported-by: Xu Wen 
> Links: https://bugzilla.kernel.org/show_bug.cgi?id=200403

Link:

> Links: https://bugzilla.kernel.org/show_bug.cgi?id=200407
> Signed-off-by: Qu Wenruo 
> ---
>  fs/btrfs/disk-io.c |   7 ++
>  fs/btrfs/volumes.c | 173 +
>  fs/btrfs/volumes.h |   2 +
>  3 files changed, 182 insertions(+)
> 
> diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
> index 205092dc9390..068ca7498e94 100644
> --- a/fs/btrfs/disk-io.c
> +++ b/fs/btrfs/disk-io.c
> @@ -3075,6 +3075,13 @@ int open_ctree(struct super_block *sb,
>   fs_info->generation = generation;
>   fs_info->last_trans_committed = generation;
>  
> + ret = btrfs_verify_dev_extents(fs_info);
> + if (ret) {
> + btrfs_err(fs_info,
> +   "failed to verify dev extents against chunks: %d",
> +   ret);
> + goto fail_block_groups;
> + }
>   ret = btrfs_recover_balance(fs_info);
>   if (ret) {
>   btrfs_err(fs_info, "failed to recover balance: %d", ret);
> diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c
> index e6a8e4aabc66..05e418cb37f3 100644
> --- a/fs/btrfs/volumes.c
> +++ b/fs/btrfs/volumes.c
> @@ -6440,6 +6440,7 @@ static int read_one_chunk(struct btrfs_fs_info 
> *fs_info, struct btrfs_key *key,
>   map->stripe_len = btrfs_chunk_stripe_len(leaf, chunk);
>   map->type = btrfs_chunk_type(leaf, chunk);
>   map->sub_stripes = btrfs_chunk_sub_stripes(leaf, chunk);
> + map->verified_stripes = 0;
>   for (i = 0; i < num_stripes; i++) {
>   map->stripes[i].physical =
>   btrfs_stripe_offset_nr(leaf, chunk, i);
> @@ -7295,3 +7296,175 @@ void btrfs_reset_fs_info_ptr(struct btrfs_fs_info 
> *fs_info)
>   fs_devices = fs_devices->seed;
>   }
>  }
> +
> +static u64 calc_stripe_length(u64 type, u64 chunk_len, int num_stripes)
> +{
> + switch (type & BTRFS_BLOCK_GROUP_PROFILE_MASK) {
> + case BTRFS_BLOCK_GROUP_RAID0:
> + return div_u64(chunk_len, num_stripes);
> + case BTRFS_BLOCK_GROUP_RAID10:
> + return div_u64(chunk_len * 2, num_stripes);
> + case BTRFS_BLOCK_GROUP_RAID5:
> + return div_u64(chunk_len, num_stripes - 1);
> + case BTRFS_BLOCK_GROUP_RAID6:
> + return div_u64(chunk_len, num_stripes - 2);
> + default:
> + return chunk_len;
> + }
> +}

There are already too many hardcoded values for the raid profiles,
please don't add another one unless really necessary and use existing
predefined constants or helpers (eg. nr_data_stripes or
btrfs_raid_array).

> +static int verify_one_dev_extent(struct btrfs_fs_info *fs_info,
> +  u64 chunk_offset, u64 devid,
> +  u64 physical_offset, u64 physical_len)
> +{
> + struct extent_map_tree *em_tree = _info->mapping_tree.map_tree;
> + struct extent_map *em;
> + struct map_lookup *map;
> + u64 stripe_len;
> + bool found = false;

This variable is only set and never checked.

> + int ret = 0;
> + int i;
> +
> + read_lock(_tree->lock);
> + em = lookup_extent_mapping(em_tree, chunk_offset, 1);
> + read_unlock(_tree->lock);
> +
> + if (!em) {
> + ret = -EUCLEAN;
> + btrfs_err(fs_info,
> + "dev extent (%llu, %llu) doesn't have corresponding chunk",
> +   devid, physical_offset);
> + goto out;
> + }
> +
> + map = em->map_lookup;
> + stripe_len = calc_stripe_length(map->type, em->len, map->num_stripes);
> + if (physical_len != stripe_len) {
> + btrfs_err(fs_info,
> +"dev extent (%llu, %llu) length doesn't match with chunk %llu, have %llu 
> expect %llu",
> +   devid, physical_offset, em->start, physical_len,
> +   stripe_len);
> + ret = -EUCLEAN;
> + goto out;
> + }
> +
> + for (i = 0; i < map->num_stripes; i++) {
> + if (map->stripes[i].dev->devid == devid &&
> + map->stripes[i].physical == physical_offset) {
> + found = true;

2nd time set

> + if (map->verified_stripes >= map->num_stripes) {
> + btrfs_err(fs_info,
> + "too many dev extent for chunk %llu is detected",
> +   em->start);
> + ret = -EUCLEAN;
> + goto out;
> + }
> + map->verified_stripes++;
> + break;
> 

[PATCH 1/2] btrfs: Introduce mount time chunk <-> dev extent mapping check

2018-07-09 Thread Qu Wenruo
This patch will introduce chunk <-> dev extent mapping check, to protect
us against invalid dev extents or chunks.

Since chunk mapping is the fundamental infrastructure of btrfs, extra
check at mount time could prevent a lot of unexpected behavior (BUG_ON).

Reported-by: Xu Wen 
Links: https://bugzilla.kernel.org/show_bug.cgi?id=200403
Links: https://bugzilla.kernel.org/show_bug.cgi?id=200407
Signed-off-by: Qu Wenruo 
---
 fs/btrfs/disk-io.c |   7 ++
 fs/btrfs/volumes.c | 173 +
 fs/btrfs/volumes.h |   2 +
 3 files changed, 182 insertions(+)

diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
index 205092dc9390..068ca7498e94 100644
--- a/fs/btrfs/disk-io.c
+++ b/fs/btrfs/disk-io.c
@@ -3075,6 +3075,13 @@ int open_ctree(struct super_block *sb,
fs_info->generation = generation;
fs_info->last_trans_committed = generation;
 
+   ret = btrfs_verify_dev_extents(fs_info);
+   if (ret) {
+   btrfs_err(fs_info,
+ "failed to verify dev extents against chunks: %d",
+ ret);
+   goto fail_block_groups;
+   }
ret = btrfs_recover_balance(fs_info);
if (ret) {
btrfs_err(fs_info, "failed to recover balance: %d", ret);
diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c
index e6a8e4aabc66..05e418cb37f3 100644
--- a/fs/btrfs/volumes.c
+++ b/fs/btrfs/volumes.c
@@ -6440,6 +6440,7 @@ static int read_one_chunk(struct btrfs_fs_info *fs_info, 
struct btrfs_key *key,
map->stripe_len = btrfs_chunk_stripe_len(leaf, chunk);
map->type = btrfs_chunk_type(leaf, chunk);
map->sub_stripes = btrfs_chunk_sub_stripes(leaf, chunk);
+   map->verified_stripes = 0;
for (i = 0; i < num_stripes; i++) {
map->stripes[i].physical =
btrfs_stripe_offset_nr(leaf, chunk, i);
@@ -7295,3 +7296,175 @@ void btrfs_reset_fs_info_ptr(struct btrfs_fs_info 
*fs_info)
fs_devices = fs_devices->seed;
}
 }
+
+static u64 calc_stripe_length(u64 type, u64 chunk_len, int num_stripes)
+{
+   switch (type & BTRFS_BLOCK_GROUP_PROFILE_MASK) {
+   case BTRFS_BLOCK_GROUP_RAID0:
+   return div_u64(chunk_len, num_stripes);
+   case BTRFS_BLOCK_GROUP_RAID10:
+   return div_u64(chunk_len * 2, num_stripes);
+   case BTRFS_BLOCK_GROUP_RAID5:
+   return div_u64(chunk_len, num_stripes - 1);
+   case BTRFS_BLOCK_GROUP_RAID6:
+   return div_u64(chunk_len, num_stripes - 2);
+   default:
+   return chunk_len;
+   }
+}
+static int verify_one_dev_extent(struct btrfs_fs_info *fs_info,
+u64 chunk_offset, u64 devid,
+u64 physical_offset, u64 physical_len)
+{
+   struct extent_map_tree *em_tree = _info->mapping_tree.map_tree;
+   struct extent_map *em;
+   struct map_lookup *map;
+   u64 stripe_len;
+   bool found = false;
+   int ret = 0;
+   int i;
+
+   read_lock(_tree->lock);
+   em = lookup_extent_mapping(em_tree, chunk_offset, 1);
+   read_unlock(_tree->lock);
+
+   if (!em) {
+   ret = -EUCLEAN;
+   btrfs_err(fs_info,
+   "dev extent (%llu, %llu) doesn't have corresponding chunk",
+ devid, physical_offset);
+   goto out;
+   }
+
+   map = em->map_lookup;
+   stripe_len = calc_stripe_length(map->type, em->len, map->num_stripes);
+   if (physical_len != stripe_len) {
+   btrfs_err(fs_info,
+"dev extent (%llu, %llu) length doesn't match with chunk %llu, have %llu 
expect %llu",
+ devid, physical_offset, em->start, physical_len,
+ stripe_len);
+   ret = -EUCLEAN;
+   goto out;
+   }
+
+   for (i = 0; i < map->num_stripes; i++) {
+   if (map->stripes[i].dev->devid == devid &&
+   map->stripes[i].physical == physical_offset) {
+   found = true;
+   if (map->verified_stripes >= map->num_stripes) {
+   btrfs_err(fs_info,
+   "too many dev extent for chunk %llu is detected",
+ em->start);
+   ret = -EUCLEAN;
+   goto out;
+   }
+   map->verified_stripes++;
+   break;
+   }
+   }
+out:
+   free_extent_map(em);
+   return ret;
+}
+
+static int verify_chunk_dev_extent_mapping(struct btrfs_fs_info *fs_info)
+{
+   struct extent_map_tree *em_tree = _info->mapping_tree.map_tree;
+   struct extent_map *em;
+   struct rb_node *node;
+   int ret = 0;
+
+   read_lock(_tree->lock);
+   for (node = rb_first(_tree->map); node; node = rb_next(node)) {
+