Skip to content
cvekernelbtrfsuse-after-free

Linux kernel btrfs use-after-free

3 min read

Overview

Unmounting after some operations (with a crafted image) can cause a use-after-free in the btrfs_queue_work function. It can be triggered not only locally (mounting a btrfs image in a local shell) but also remotely (mounting a corrupted USB or other storage carrying a crafted btrfs image).

Target

Linux Kernel btrfs filesystem.

Linux Version Availablity
5.0.21 True

Bug type: use-after-free.

Reproduce

gcc -o poc poc_2019_19377.c
mkdir mnt
mount poc_2019_19377.img ./mnt
cp poc ./mnt/
cd mnt
./poc
cd ..
umount ./mnt

Root cause

fs/btrfs/async-thread.c:367 (link)

void btrfs_queue_work(struct btrfs_workqueue *wq,
		      struct btrfs_work *work)
{
	struct __btrfs_workqueue *dest_wq;
 
	if (test_bit(WORK_HIGH_PRIO_BIT, &work->flags) && wq->high)
		dest_wq = wq->high;
	else
		dest_wq = wq->normal;
	__btrfs_queue_work(dest_wq, work);
}

fs_info->workers (struct btrfs_workqueue *wq) is already freed.

Debugger / KASAN

Debugger view — the parameter wq is freed (wq->high causes the use-after-free):

─────────────────────────────────────────────────────────── trace ────
[#0] 0xffffffff81aeddbc → btrfs_queue_work(wq=0xffff888067cf6840, work=0xffff8880676e4d28)
[#1] 0xffffffff81a4151d → btrfs_wq_submit_bio(...)
[#2] 0xffffffff81a4171c → btree_submit_bio_hook(...)
[#3] 0xffffffff81ab130e → submit_one_bio(...)
[#4] 0xffffffff81ab1d7c → flush_write_bio(...)
[#5] 0xffffffff81ac5aeb → btree_write_cache_pages(...)
[#6] 0xffffffff81436a2c → do_writepages(...)
[#7] 0xffffffff815c10f3 → __writeback_single_inode(...)
[#8] 0xffffffff815c356d → writeback_single_inode(...)
[#9] 0xffffffff815c3955 → write_inode_now(...)

gef➤  p &wq->high
$5 = (struct __btrfs_workqueue **) 0xffff888067cf6848

On exec poc — WARNING (trimmed to the relevant frames):

[  267.165168] WARNING: CPU: 1 PID: 1045 at fs/btrfs/extent-tree.c:7046 __btrfs_free_extent.isra.71+0xa16/0x1130
[  267.165168] CPU: 1 PID: 1045 Comm: kworker/u4:5 Not tainted 5.0.21 #1
[  267.165168] Workqueue: events_unbound btrfs_async_reclaim_metadata_space
[  267.165168] Call Trace:
[  267.165168]  __btrfs_run_delayed_refs+0x1236/0x3100
[  267.165168]  btrfs_run_delayed_refs+0x1b6/0x390
[  267.165168]  flush_space+0x5fa/0xde0
[  267.165168]  btrfs_async_reclaim_metadata_space+0x451/0x1260
[  267.165168]  process_one_work+0x580/0x1210
[  267.165168]  worker_thread+0x8a/0xfc0
[  267.165168]  kthread+0x2a9/0x390
[  267.165168]  ret_from_fork+0x35/0x40
[  267.165168] ---[ end trace ae8f476daf11ea95 ]---
...
[  267.224076] BTRFS error (device loop0): unable to find ref byte nr 29376512 parent 0 root 7  owner 0 offset 0
[  267.243654] BTRFS: error (device loop0) in __btrfs_free_extent:7052: errno=-2 No such entry
[  267.246314] BTRFS info (device loop0): forced readonly

On umount — use-after-free (trimmed to the relevant frames):

[  272.825171] ==================================================================
[  272.825171] BUG: KASAN: use-after-free in btrfs_queue_work+0x2c1/0x390
[  272.825171] Read of size 8 at addr ffff888067cf6848 by task umount/1922
[  272.825171] CPU: 0 PID: 1922 Comm: umount Tainted: G        W         5.0.21 #1
[  272.825171] Call Trace:
[  272.825171]  dump_stack+0x5b/0x8b
[  272.825171]  kasan_report+0x13a/0x19b
[  272.825171]  btrfs_queue_work+0x2c1/0x390
[  272.825171]  btrfs_wq_submit_bio+0x1cd/0x240
[  272.825171]  btree_submit_bio_hook+0x18c/0x2a0
[  272.825171]  submit_one_bio+0x1be/0x320
[  272.825171]  flush_write_bio.isra.41+0x2c/0x70
[  272.825171]  btree_write_cache_pages+0x3bb/0x7f0
[  272.825171]  do_writepages+0x5c/0x130
[  272.825171]  __writeback_single_inode+0xa3/0x9a0
[  272.825171]  writeback_single_inode+0x23d/0x390
[  272.825171]  write_inode_now+0x1b5/0x280
[  272.825171]  iput+0x2ef/0x600
[  272.825171]  close_ctree+0x341/0x750
[  272.825171]  generic_shutdown_super+0x126/0x370
[  272.825171]  kill_anon_super+0x31/0x50
[  272.825171]  btrfs_kill_super+0x36/0x2b0
[  272.825171]  deactivate_locked_super+0x80/0xc0
[  272.825171]  deactivate_super+0x13c/0x150
[  272.825171]  cleanup_mnt+0x9a/0x130
[  272.825171]  task_work_run+0x11a/0x1b0
[  272.825171]  exit_to_usermode_loop+0x107/0x130
[  272.825171]  do_syscall_64+0x1e5/0x280
[  272.825171]  entry_SYSCALL_64_after_hwframe+0x44/0xa9
[  272.825171] Allocated by task 1889:
[  272.825171]  __kasan_kmalloc+0xd5/0xf0
[  272.825171]  btrfs_alloc_workqueue+0x54/0x280
[  272.825171]  open_ctree+0x2c30/0x7065
[  272.825171]  btrfs_mount_root+0xc94/0x1060
[  272.825171] Freed by task 1922:
[  272.825171]  __kasan_slab_free+0x132/0x180
[  272.825171]  kfree+0x99/0x1c0
[  272.825171]  btrfs_stop_all_workers+0x8e/0x3e0
[  272.825171]  close_ctree+0x300/0x750
[  272.825171] The buggy address belongs to the object at ffff888067cf6840
[  272.825171]  which belongs to the cache kmalloc-16 of size 16
[  272.825171] ==================================================================

Details

CVSS 7.8 (CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H), CWE-416, published 2019-11-29, last modified 2024-11-21.

Attribution

Found as part of the Best of the Best (BoB) 8th bobfuzzer team project — a five-person team that ported the JANUS file-system fuzzer. This is team work, not sole authorship.

References