Skip to content

Commit c462789

Browse files
adam900710roxanan1996
authored andcommitted
btrfs: scrub: avoid use-after-free when chunk length is not 64K aligned
BugLink: https://bugs.launchpad.net/bugs/2059068 commit f546c42 upstream. [BUG] There is a bug report that, on a ext4-converted btrfs, scrub leads to various problems, including: - "unable to find chunk map" errors BTRFS info (device vdb): scrub: started on devid 1 BTRFS critical (device vdb): unable to find chunk map for logical 2214744064 length 4096 BTRFS critical (device vdb): unable to find chunk map for logical 2214744064 length 45056 This would lead to unrepariable errors. - Use-after-free KASAN reports: ================================================================== BUG: KASAN: slab-use-after-free in __blk_rq_map_sg+0x18f/0x7c0 Read of size 8 at addr ffff8881013c9040 by task btrfs/909 CPU: 0 PID: 909 Comm: btrfs Not tainted 6.7.0-x64v3-dbg #11 c50636e9419a8354555555245df535e380563b2b Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 2023.11-2 12/24/2023 Call Trace: <TASK> dump_stack_lvl+0x43/0x60 print_report+0xcf/0x640 kasan_report+0xa6/0xd0 __blk_rq_map_sg+0x18f/0x7c0 virtblk_prep_rq.isra.0+0x215/0x6a0 [virtio_blk 19a65eeee9ae6fcf02edfad39bb9ddee07dcdaff] virtio_queue_rqs+0xc4/0x310 [virtio_blk 19a65eeee9ae6fcf02edfad39bb9ddee07dcdaff] blk_mq_flush_plug_list.part.0+0x780/0x860 __blk_flush_plug+0x1ba/0x220 blk_finish_plug+0x3b/0x60 submit_initial_group_read+0x10a/0x290 [btrfs e57987a360bed82fe8756dcd3e0de5406ccfe965] flush_scrub_stripes+0x38e/0x430 [btrfs e57987a360bed82fe8756dcd3e0de5406ccfe965] scrub_stripe+0x82a/0xae0 [btrfs e57987a360bed82fe8756dcd3e0de5406ccfe965] scrub_chunk+0x178/0x200 [btrfs e57987a360bed82fe8756dcd3e0de5406ccfe965] scrub_enumerate_chunks+0x4bc/0xa30 [btrfs e57987a360bed82fe8756dcd3e0de5406ccfe965] btrfs_scrub_dev+0x398/0x810 [btrfs e57987a360bed82fe8756dcd3e0de5406ccfe965] btrfs_ioctl+0x4b9/0x3020 [btrfs e57987a360bed82fe8756dcd3e0de5406ccfe965] __x64_sys_ioctl+0xbd/0x100 do_syscall_64+0x5d/0xe0 entry_SYSCALL_64_after_hwframe+0x63/0x6b RIP: 0033:0x7f47e5e0952b - Crash, mostly due to above use-after-free [CAUSE] The converted fs has the following data chunk layout: item 2 key (FIRST_CHUNK_TREE CHUNK_ITEM 2214658048) itemoff 16025 itemsize 80 length 86016 owner 2 stripe_len 65536 type DATA|single For above logical bytenr 2214744064, it's at the chunk end (2214658048 + 86016 = 2214744064). This means btrfs_submit_bio() would split the bio, and trigger endio function for both of the two halves. However scrub_submit_initial_read() would only expect the endio function to be called once, not any more. This means the first endio function would already free the bbio::bio, leaving the bvec freed, thus the 2nd endio call would lead to use-after-free. [FIX] - Make sure scrub_read_endio() only updates bits in its range Since we may read less than 64K at the end of the chunk, we should not touch the bits beyond chunk boundary. - Make sure scrub_submit_initial_read() only to read the chunk range This is done by calculating the real number of sectors we need to read, and add sector-by-sector to the bio. Thankfully the scrub read repair path won't need extra fixes: - scrub_stripe_submit_repair_read() With above fixes, we won't update error bit for range beyond chunk, thus scrub_stripe_submit_repair_read() should never submit any read beyond the chunk. Reported-by: Rongrong <[email protected]> Fixes: e02ee89 ("btrfs: scrub: switch scrub_simple_mirror() to scrub_stripe infrastructure") Tested-by: Rongrong <[email protected]> Reviewed-by: Johannes Thumshirn <[email protected]> Signed-off-by: Qu Wenruo <[email protected]> Signed-off-by: David Sterba <[email protected]> [ Use min_t() to fix a compiling error due to difference types ] Signed-off-by: Greg Kroah-Hartman <[email protected]> Signed-off-by: Portia Stephens <[email protected]> Signed-off-by: Stefan Bader <[email protected]>
1 parent a05f98e commit c462789

File tree

1 file changed

+22
-7
lines changed

1 file changed

+22
-7
lines changed

fs/btrfs/scrub.c

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1077,12 +1077,22 @@ static void scrub_stripe_read_repair_worker(struct work_struct *work)
10771077
static void scrub_read_endio(struct btrfs_bio *bbio)
10781078
{
10791079
struct scrub_stripe *stripe = bbio->private;
1080+
struct bio_vec *bvec;
1081+
int sector_nr = calc_sector_number(stripe, bio_first_bvec_all(&bbio->bio));
1082+
int num_sectors;
1083+
u32 bio_size = 0;
1084+
int i;
1085+
1086+
ASSERT(sector_nr < stripe->nr_sectors);
1087+
bio_for_each_bvec_all(bvec, &bbio->bio, i)
1088+
bio_size += bvec->bv_len;
1089+
num_sectors = bio_size >> stripe->bg->fs_info->sectorsize_bits;
10801090

10811091
if (bbio->bio.bi_status) {
1082-
bitmap_set(&stripe->io_error_bitmap, 0, stripe->nr_sectors);
1083-
bitmap_set(&stripe->error_bitmap, 0, stripe->nr_sectors);
1092+
bitmap_set(&stripe->io_error_bitmap, sector_nr, num_sectors);
1093+
bitmap_set(&stripe->error_bitmap, sector_nr, num_sectors);
10841094
} else {
1085-
bitmap_clear(&stripe->io_error_bitmap, 0, stripe->nr_sectors);
1095+
bitmap_clear(&stripe->io_error_bitmap, sector_nr, num_sectors);
10861096
}
10871097
bio_put(&bbio->bio);
10881098
if (atomic_dec_and_test(&stripe->pending_io)) {
@@ -1619,6 +1629,9 @@ static void scrub_submit_initial_read(struct scrub_ctx *sctx,
16191629
{
16201630
struct btrfs_fs_info *fs_info = sctx->fs_info;
16211631
struct btrfs_bio *bbio;
1632+
unsigned int nr_sectors = min_t(u64, BTRFS_STRIPE_LEN, stripe->bg->start +
1633+
stripe->bg->length - stripe->logical) >>
1634+
fs_info->sectorsize_bits;
16221635
int mirror = stripe->mirror_num;
16231636

16241637
ASSERT(stripe->bg);
@@ -1628,14 +1641,16 @@ static void scrub_submit_initial_read(struct scrub_ctx *sctx,
16281641
bbio = btrfs_bio_alloc(SCRUB_STRIPE_PAGES, REQ_OP_READ, fs_info,
16291642
scrub_read_endio, stripe);
16301643

1631-
/* Read the whole stripe. */
16321644
bbio->bio.bi_iter.bi_sector = stripe->logical >> SECTOR_SHIFT;
1633-
for (int i = 0; i < BTRFS_STRIPE_LEN >> PAGE_SHIFT; i++) {
1645+
/* Read the whole range inside the chunk boundary. */
1646+
for (unsigned int cur = 0; cur < nr_sectors; cur++) {
1647+
struct page *page = scrub_stripe_get_page(stripe, cur);
1648+
unsigned int pgoff = scrub_stripe_get_page_offset(stripe, cur);
16341649
int ret;
16351650

1636-
ret = bio_add_page(&bbio->bio, stripe->pages[i], PAGE_SIZE, 0);
1651+
ret = bio_add_page(&bbio->bio, page, fs_info->sectorsize, pgoff);
16371652
/* We should have allocated enough bio vectors. */
1638-
ASSERT(ret == PAGE_SIZE);
1653+
ASSERT(ret == fs_info->sectorsize);
16391654
}
16401655
atomic_inc(&stripe->pending_io);
16411656

0 commit comments

Comments
 (0)