Commit 298a8f9c authored by Wang Shilong's avatar Wang Shilong Committed by Chris Mason

Btrfs: fix NULL pointer crash when running balance and scrub concurrently

While running balance, scrub, fsstress concurrently we hit the
following kernel crash:

[56561.448845] BTRFS info (device sde): relocating block group 11005853696 flags 132
[56561.524077] BUG: unable to handle kernel NULL pointer dereference at 0000000000000078
[56561.524237] IP: [<ffffffffa038956d>] scrub_chunk.isra.12+0xdd/0x130 [btrfs]
[56561.524297] PGD 9be28067 PUD 7f3dd067 PMD 0
[56561.524325] Oops: 0000 [#1] SMP
[....]
[56561.527237] Call Trace:
[56561.527309]  [<ffffffffa038980e>] scrub_enumerate_chunks+0x24e/0x490 [btrfs]
[56561.527392]  [<ffffffff810abe00>] ? abort_exclusive_wait+0x50/0xb0
[56561.527476]  [<ffffffffa038add4>] btrfs_scrub_dev+0x1a4/0x530 [btrfs]
[56561.527561]  [<ffffffffa0368107>] btrfs_ioctl+0x13f7/0x2a90 [btrfs]
[56561.527639]  [<ffffffff811c82f0>] do_vfs_ioctl+0x2e0/0x4c0
[56561.527712]  [<ffffffff8109c384>] ? vtime_account_user+0x54/0x60
[56561.527788]  [<ffffffff810f768c>] ? __audit_syscall_entry+0x9c/0xf0
[56561.527870]  [<ffffffff811c8551>] SyS_ioctl+0x81/0xa0
[56561.527941]  [<ffffffff815707f7>] tracesys+0xdd/0xe2
[...]
[56561.528304] RIP  [<ffffffffa038956d>] scrub_chunk.isra.12+0xdd/0x130 [btrfs]
[56561.528395]  RSP <ffff88004c0f5be8>
[56561.528454] CR2: 0000000000000078

This is because in btrfs_relocate_chunk(), we will free @bdev directly while
scrub may still hold extent mapping, and may access freed memory.

Fix this problem by wrapping freeing @bdev work into free_extent_map() which
is based on reference count.
Reported-by: default avatarQu Wenruo <quwenruo@cn.fujitsu.com>
Signed-off-by: default avatarWang Shilong <wangsl.fnst@cn.fujitsu.com>
Signed-off-by: default avatarMiao Xie <miaox@cn.fujitsu.com>
Signed-off-by: default avatarChris Mason <clm@fb.com>
parent ced96edc
...@@ -75,6 +75,8 @@ void free_extent_map(struct extent_map *em) ...@@ -75,6 +75,8 @@ void free_extent_map(struct extent_map *em)
if (atomic_dec_and_test(&em->refs)) { if (atomic_dec_and_test(&em->refs)) {
WARN_ON(extent_map_in_tree(em)); WARN_ON(extent_map_in_tree(em));
WARN_ON(!list_empty(&em->list)); WARN_ON(!list_empty(&em->list));
if (test_bit(EXTENT_FLAG_FS_MAPPING, &em->flags))
kfree(em->bdev);
kmem_cache_free(extent_map_cache, em); kmem_cache_free(extent_map_cache, em);
} }
} }
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#define EXTENT_FLAG_PREALLOC 3 /* pre-allocated extent */ #define EXTENT_FLAG_PREALLOC 3 /* pre-allocated extent */
#define EXTENT_FLAG_LOGGING 4 /* Logging this extent */ #define EXTENT_FLAG_LOGGING 4 /* Logging this extent */
#define EXTENT_FLAG_FILLING 5 /* Filling in a preallocated extent */ #define EXTENT_FLAG_FILLING 5 /* Filling in a preallocated extent */
#define EXTENT_FLAG_FS_MAPPING 6 /* filesystem extent mapping type */
struct extent_map { struct extent_map {
struct rb_node rb_node; struct rb_node rb_node;
......
...@@ -2543,9 +2543,6 @@ static int btrfs_relocate_chunk(struct btrfs_root *root, ...@@ -2543,9 +2543,6 @@ static int btrfs_relocate_chunk(struct btrfs_root *root,
remove_extent_mapping(em_tree, em); remove_extent_mapping(em_tree, em);
write_unlock(&em_tree->lock); write_unlock(&em_tree->lock);
kfree(map);
em->bdev = NULL;
/* once for the tree */ /* once for the tree */
free_extent_map(em); free_extent_map(em);
/* once for us */ /* once for us */
...@@ -4301,9 +4298,11 @@ static int __btrfs_alloc_chunk(struct btrfs_trans_handle *trans, ...@@ -4301,9 +4298,11 @@ static int __btrfs_alloc_chunk(struct btrfs_trans_handle *trans,
em = alloc_extent_map(); em = alloc_extent_map();
if (!em) { if (!em) {
kfree(map);
ret = -ENOMEM; ret = -ENOMEM;
goto error; goto error;
} }
set_bit(EXTENT_FLAG_FS_MAPPING, &em->flags);
em->bdev = (struct block_device *)map; em->bdev = (struct block_device *)map;
em->start = start; em->start = start;
em->len = num_bytes; em->len = num_bytes;
...@@ -4346,7 +4345,6 @@ static int __btrfs_alloc_chunk(struct btrfs_trans_handle *trans, ...@@ -4346,7 +4345,6 @@ static int __btrfs_alloc_chunk(struct btrfs_trans_handle *trans,
/* One for the tree reference */ /* One for the tree reference */
free_extent_map(em); free_extent_map(em);
error: error:
kfree(map);
kfree(devices_info); kfree(devices_info);
return ret; return ret;
} }
...@@ -4558,7 +4556,6 @@ void btrfs_mapping_tree_free(struct btrfs_mapping_tree *tree) ...@@ -4558,7 +4556,6 @@ void btrfs_mapping_tree_free(struct btrfs_mapping_tree *tree)
write_unlock(&tree->map_tree.lock); write_unlock(&tree->map_tree.lock);
if (!em) if (!em)
break; break;
kfree(em->bdev);
/* once for us */ /* once for us */
free_extent_map(em); free_extent_map(em);
/* once for the tree */ /* once for the tree */
...@@ -5822,6 +5819,7 @@ static int read_one_chunk(struct btrfs_root *root, struct btrfs_key *key, ...@@ -5822,6 +5819,7 @@ static int read_one_chunk(struct btrfs_root *root, struct btrfs_key *key,
return -ENOMEM; return -ENOMEM;
} }
set_bit(EXTENT_FLAG_FS_MAPPING, &em->flags);
em->bdev = (struct block_device *)map; em->bdev = (struct block_device *)map;
em->start = logical; em->start = logical;
em->len = length; em->len = length;
...@@ -5846,7 +5844,6 @@ static int read_one_chunk(struct btrfs_root *root, struct btrfs_key *key, ...@@ -5846,7 +5844,6 @@ static int read_one_chunk(struct btrfs_root *root, struct btrfs_key *key,
map->stripes[i].dev = btrfs_find_device(root->fs_info, devid, map->stripes[i].dev = btrfs_find_device(root->fs_info, devid,
uuid, NULL); uuid, NULL);
if (!map->stripes[i].dev && !btrfs_test_opt(root, DEGRADED)) { if (!map->stripes[i].dev && !btrfs_test_opt(root, DEGRADED)) {
kfree(map);
free_extent_map(em); free_extent_map(em);
return -EIO; return -EIO;
} }
...@@ -5854,7 +5851,6 @@ static int read_one_chunk(struct btrfs_root *root, struct btrfs_key *key, ...@@ -5854,7 +5851,6 @@ static int read_one_chunk(struct btrfs_root *root, struct btrfs_key *key,
map->stripes[i].dev = map->stripes[i].dev =
add_missing_dev(root, devid, uuid); add_missing_dev(root, devid, uuid);
if (!map->stripes[i].dev) { if (!map->stripes[i].dev) {
kfree(map);
free_extent_map(em); free_extent_map(em);
return -EIO; return -EIO;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment