diff options
author | Wang Shilong <wangsl.fnst@cn.fujitsu.com> | 2014-06-18 22:42:52 -0400 |
---|---|---|
committer | Chris Mason <clm@fb.com> | 2014-06-19 17:20:55 -0400 |
commit | 298a8f9cf17d2f2e1ffc41e5e247fa3695a8a76f (patch) | |
tree | 639c13227f07fefe4eea2a13988d3792848f67bf /fs | |
parent | ced96edc48ba455b0982c3aa64d3cc3bf2d0816a (diff) |
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: Qu Wenruo <quwenruo@cn.fujitsu.com>
Signed-off-by: Wang Shilong <wangsl.fnst@cn.fujitsu.com>
Signed-off-by: Miao Xie <miaox@cn.fujitsu.com>
Signed-off-by: Chris Mason <clm@fb.com>
Diffstat (limited to 'fs')
-rw-r--r-- | fs/btrfs/extent_map.c | 2 | ||||
-rw-r--r-- | fs/btrfs/extent_map.h | 1 | ||||
-rw-r--r-- | fs/btrfs/volumes.c | 10 |
3 files changed, 6 insertions, 7 deletions
diff --git a/fs/btrfs/extent_map.c b/fs/btrfs/extent_map.c index 1874aee69c86..225302b39afb 100644 --- a/fs/btrfs/extent_map.c +++ b/fs/btrfs/extent_map.c | |||
@@ -75,6 +75,8 @@ void free_extent_map(struct extent_map *em) | |||
75 | if (atomic_dec_and_test(&em->refs)) { | 75 | if (atomic_dec_and_test(&em->refs)) { |
76 | WARN_ON(extent_map_in_tree(em)); | 76 | WARN_ON(extent_map_in_tree(em)); |
77 | WARN_ON(!list_empty(&em->list)); | 77 | WARN_ON(!list_empty(&em->list)); |
78 | if (test_bit(EXTENT_FLAG_FS_MAPPING, &em->flags)) | ||
79 | kfree(em->bdev); | ||
78 | kmem_cache_free(extent_map_cache, em); | 80 | kmem_cache_free(extent_map_cache, em); |
79 | } | 81 | } |
80 | } | 82 | } |
diff --git a/fs/btrfs/extent_map.h b/fs/btrfs/extent_map.h index e7fd8a56a140..b2991fd8583e 100644 --- a/fs/btrfs/extent_map.h +++ b/fs/btrfs/extent_map.h | |||
@@ -15,6 +15,7 @@ | |||
15 | #define EXTENT_FLAG_PREALLOC 3 /* pre-allocated extent */ | 15 | #define EXTENT_FLAG_PREALLOC 3 /* pre-allocated extent */ |
16 | #define EXTENT_FLAG_LOGGING 4 /* Logging this extent */ | 16 | #define EXTENT_FLAG_LOGGING 4 /* Logging this extent */ |
17 | #define EXTENT_FLAG_FILLING 5 /* Filling in a preallocated extent */ | 17 | #define EXTENT_FLAG_FILLING 5 /* Filling in a preallocated extent */ |
18 | #define EXTENT_FLAG_FS_MAPPING 6 /* filesystem extent mapping type */ | ||
18 | 19 | ||
19 | struct extent_map { | 20 | struct extent_map { |
20 | struct rb_node rb_node; | 21 | struct rb_node rb_node; |
diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c index ffeed6d6326f..19c298a47a6f 100644 --- a/fs/btrfs/volumes.c +++ b/fs/btrfs/volumes.c | |||
@@ -2543,9 +2543,6 @@ static int btrfs_relocate_chunk(struct btrfs_root *root, | |||
2543 | remove_extent_mapping(em_tree, em); | 2543 | remove_extent_mapping(em_tree, em); |
2544 | write_unlock(&em_tree->lock); | 2544 | write_unlock(&em_tree->lock); |
2545 | 2545 | ||
2546 | kfree(map); | ||
2547 | em->bdev = NULL; | ||
2548 | |||
2549 | /* once for the tree */ | 2546 | /* once for the tree */ |
2550 | free_extent_map(em); | 2547 | free_extent_map(em); |
2551 | /* once for us */ | 2548 | /* once for us */ |
@@ -4301,9 +4298,11 @@ static int __btrfs_alloc_chunk(struct btrfs_trans_handle *trans, | |||
4301 | 4298 | ||
4302 | em = alloc_extent_map(); | 4299 | em = alloc_extent_map(); |
4303 | if (!em) { | 4300 | if (!em) { |
4301 | kfree(map); | ||
4304 | ret = -ENOMEM; | 4302 | ret = -ENOMEM; |
4305 | goto error; | 4303 | goto error; |
4306 | } | 4304 | } |
4305 | set_bit(EXTENT_FLAG_FS_MAPPING, &em->flags); | ||
4307 | em->bdev = (struct block_device *)map; | 4306 | em->bdev = (struct block_device *)map; |
4308 | em->start = start; | 4307 | em->start = start; |
4309 | em->len = num_bytes; | 4308 | em->len = num_bytes; |
@@ -4346,7 +4345,6 @@ error_del_extent: | |||
4346 | /* One for the tree reference */ | 4345 | /* One for the tree reference */ |
4347 | free_extent_map(em); | 4346 | free_extent_map(em); |
4348 | error: | 4347 | error: |
4349 | kfree(map); | ||
4350 | kfree(devices_info); | 4348 | kfree(devices_info); |
4351 | return ret; | 4349 | return ret; |
4352 | } | 4350 | } |
@@ -4558,7 +4556,6 @@ void btrfs_mapping_tree_free(struct btrfs_mapping_tree *tree) | |||
4558 | write_unlock(&tree->map_tree.lock); | 4556 | write_unlock(&tree->map_tree.lock); |
4559 | if (!em) | 4557 | if (!em) |
4560 | break; | 4558 | break; |
4561 | kfree(em->bdev); | ||
4562 | /* once for us */ | 4559 | /* once for us */ |
4563 | free_extent_map(em); | 4560 | free_extent_map(em); |
4564 | /* once for the tree */ | 4561 | /* once for the tree */ |
@@ -5822,6 +5819,7 @@ static int read_one_chunk(struct btrfs_root *root, struct btrfs_key *key, | |||
5822 | return -ENOMEM; | 5819 | return -ENOMEM; |
5823 | } | 5820 | } |
5824 | 5821 | ||
5822 | set_bit(EXTENT_FLAG_FS_MAPPING, &em->flags); | ||
5825 | em->bdev = (struct block_device *)map; | 5823 | em->bdev = (struct block_device *)map; |
5826 | em->start = logical; | 5824 | em->start = logical; |
5827 | em->len = length; | 5825 | em->len = length; |
@@ -5846,7 +5844,6 @@ static int read_one_chunk(struct btrfs_root *root, struct btrfs_key *key, | |||
5846 | map->stripes[i].dev = btrfs_find_device(root->fs_info, devid, | 5844 | map->stripes[i].dev = btrfs_find_device(root->fs_info, devid, |
5847 | uuid, NULL); | 5845 | uuid, NULL); |
5848 | if (!map->stripes[i].dev && !btrfs_test_opt(root, DEGRADED)) { | 5846 | if (!map->stripes[i].dev && !btrfs_test_opt(root, DEGRADED)) { |
5849 | kfree(map); | ||
5850 | free_extent_map(em); | 5847 | free_extent_map(em); |
5851 | return -EIO; | 5848 | return -EIO; |
5852 | } | 5849 | } |
@@ -5854,7 +5851,6 @@ static int read_one_chunk(struct btrfs_root *root, struct btrfs_key *key, | |||
5854 | map->stripes[i].dev = | 5851 | map->stripes[i].dev = |
5855 | add_missing_dev(root, devid, uuid); | 5852 | add_missing_dev(root, devid, uuid); |
5856 | if (!map->stripes[i].dev) { | 5853 | if (!map->stripes[i].dev) { |
5857 | kfree(map); | ||
5858 | free_extent_map(em); | 5854 | free_extent_map(em); |
5859 | return -EIO; | 5855 | return -EIO; |
5860 | } | 5856 | } |