summaryrefslogtreecommitdiffstats
path: root/fs
diff options
context:
space:
mode:
authorFilipe Manana <fdmanana@suse.com>2015-06-13 01:52:57 -0400
committerChris Mason <clm@fb.com>2015-06-30 17:36:46 -0400
commitae9d8f17118551bedd797406a6768b87c2146234 (patch)
treee3c3613da9804a6d955ce03c8839a453f148f86f /fs
parentc3f4a1685bb87e59c886ee68f7967eae07d4dffa (diff)
Btrfs: fix race between caching kthread and returning inode to inode cache
While the inode cache caching kthread is calling btrfs_unpin_free_ino(), we could have a concurrent call to btrfs_return_ino() that adds a new entry to the root's free space cache of pinned inodes. This concurrent call does not acquire the fs_info->commit_root_sem before adding a new entry if the caching state is BTRFS_CACHE_FINISHED, which is a problem because the caching kthread calls btrfs_unpin_free_ino() after setting the caching state to BTRFS_CACHE_FINISHED and therefore races with the task calling btrfs_return_ino(), which is adding a new entry, while the former (caching kthread) is navigating the cache's rbtree, removing and freeing nodes from the cache's rbtree without acquiring the spinlock that protects the rbtree. This race resulted in memory corruption due to double free of struct btrfs_free_space objects because both tasks can end up doing freeing the same objects. Note that adding a new entry can result in merging it with other entries in the cache, in which case those entries are freed. This is particularly important as btrfs_free_space structures are also used for the block group free space caches. This memory corruption can be detected by a debugging kernel, which reports it with the following trace: [132408.501148] slab error in verify_redzone_free(): cache `btrfs_free_space': double free detected [132408.505075] CPU: 15 PID: 12248 Comm: btrfs-ino-cache Tainted: G W 4.1.0-rc5-btrfs-next-10+ #1 [132408.505075] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.8.1-0-g4adadbd-20150316_085822-nilsson.home.kraxel.org 04/01/2014 [132408.505075] ffff880023e7d320 ffff880163d73cd8 ffffffff8145eec7 ffffffff81095dce [132408.505075] ffff880009735d40 ffff880163d73ce8 ffffffff81154e1e ffff880163d73d68 [132408.505075] ffffffff81155733 ffffffffa054a95a ffff8801b6099f00 ffffffffa0505b5f [132408.505075] Call Trace: [132408.505075] [<ffffffff8145eec7>] dump_stack+0x4f/0x7b [132408.505075] [<ffffffff81095dce>] ? console_unlock+0x356/0x3a2 [132408.505075] [<ffffffff81154e1e>] __slab_error.isra.28+0x25/0x36 [132408.505075] [<ffffffff81155733>] __cache_free+0xe2/0x4b6 [132408.505075] [<ffffffffa054a95a>] ? __btrfs_add_free_space+0x2f0/0x343 [btrfs] [132408.505075] [<ffffffffa0505b5f>] ? btrfs_unpin_free_ino+0x8e/0x99 [btrfs] [132408.505075] [<ffffffff810f3b30>] ? time_hardirqs_off+0x15/0x28 [132408.505075] [<ffffffff81084d42>] ? trace_hardirqs_off+0xd/0xf [132408.505075] [<ffffffff811563a1>] ? kfree+0xb6/0x14e [132408.505075] [<ffffffff811563d0>] kfree+0xe5/0x14e [132408.505075] [<ffffffffa0505b5f>] btrfs_unpin_free_ino+0x8e/0x99 [btrfs] [132408.505075] [<ffffffffa0505e08>] caching_kthread+0x29e/0x2d9 [btrfs] [132408.505075] [<ffffffffa0505b6a>] ? btrfs_unpin_free_ino+0x99/0x99 [btrfs] [132408.505075] [<ffffffff8106698f>] kthread+0xef/0xf7 [132408.505075] [<ffffffff810f3b08>] ? time_hardirqs_on+0x15/0x28 [132408.505075] [<ffffffff810668a0>] ? __kthread_parkme+0xad/0xad [132408.505075] [<ffffffff814653d2>] ret_from_fork+0x42/0x70 [132408.505075] [<ffffffff810668a0>] ? __kthread_parkme+0xad/0xad [132408.505075] ffff880023e7d320: redzone 1:0x9f911029d74e35b, redzone 2:0x9f911029d74e35b. [132409.501654] slab: double free detected in cache 'btrfs_free_space', objp ffff880023e7d320 [132409.503355] ------------[ cut here ]------------ [132409.504241] kernel BUG at mm/slab.c:2571! Therefore fix this by having btrfs_unpin_free_ino() acquire the lock that protects the rbtree while doing the searches and removing entries. Fixes: 1c70d8fb4dfa ("Btrfs: fix inode caching vs tree log") Cc: stable@vger.kernel.org Signed-off-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: Chris Mason <clm@fb.com>
Diffstat (limited to 'fs')
-rw-r--r--fs/btrfs/inode-map.c15
1 files changed, 11 insertions, 4 deletions
diff --git a/fs/btrfs/inode-map.c b/fs/btrfs/inode-map.c
index 218df701e607..d4a582ac3f73 100644
--- a/fs/btrfs/inode-map.c
+++ b/fs/btrfs/inode-map.c
@@ -246,6 +246,7 @@ void btrfs_unpin_free_ino(struct btrfs_root *root)
246{ 246{
247 struct btrfs_free_space_ctl *ctl = root->free_ino_ctl; 247 struct btrfs_free_space_ctl *ctl = root->free_ino_ctl;
248 struct rb_root *rbroot = &root->free_ino_pinned->free_space_offset; 248 struct rb_root *rbroot = &root->free_ino_pinned->free_space_offset;
249 spinlock_t *rbroot_lock = &root->free_ino_pinned->tree_lock;
249 struct btrfs_free_space *info; 250 struct btrfs_free_space *info;
250 struct rb_node *n; 251 struct rb_node *n;
251 u64 count; 252 u64 count;
@@ -254,23 +255,29 @@ void btrfs_unpin_free_ino(struct btrfs_root *root)
254 return; 255 return;
255 256
256 while (1) { 257 while (1) {
258 bool add_to_ctl = true;
259
260 spin_lock(rbroot_lock);
257 n = rb_first(rbroot); 261 n = rb_first(rbroot);
258 if (!n) 262 if (!n) {
263 spin_unlock(rbroot_lock);
259 break; 264 break;
265 }
260 266
261 info = rb_entry(n, struct btrfs_free_space, offset_index); 267 info = rb_entry(n, struct btrfs_free_space, offset_index);
262 BUG_ON(info->bitmap); /* Logic error */ 268 BUG_ON(info->bitmap); /* Logic error */
263 269
264 if (info->offset > root->ino_cache_progress) 270 if (info->offset > root->ino_cache_progress)
265 goto free; 271 add_to_ctl = false;
266 else if (info->offset + info->bytes > root->ino_cache_progress) 272 else if (info->offset + info->bytes > root->ino_cache_progress)
267 count = root->ino_cache_progress - info->offset + 1; 273 count = root->ino_cache_progress - info->offset + 1;
268 else 274 else
269 count = info->bytes; 275 count = info->bytes;
270 276
271 __btrfs_add_free_space(ctl, info->offset, count);
272free:
273 rb_erase(&info->offset_index, rbroot); 277 rb_erase(&info->offset_index, rbroot);
278 spin_unlock(rbroot_lock);
279 if (add_to_ctl)
280 __btrfs_add_free_space(ctl, info->offset, count);
274 kmem_cache_free(btrfs_free_space_cachep, info); 281 kmem_cache_free(btrfs_free_space_cachep, info);
275 } 282 }
276} 283}