[BUG] Our fuzz testing triggered a blkg use-after-free issue: BUG: KASAN: slab-use-after-free in percpu_ref_put_many.constprop.0+0xbe/0xe0 Call Trace: ... blkcg_activate_policy+0x347/0xfa0 bfq_create_group_hierarchy+0x5b/0x140 bfq_init_queue+0xc1b/0x1470 ? mutex_init_generic+0x9f/0x100 ? elevator_alloc+0x166/0x2b0 blk_mq_init_sched+0x2b0/0x730 elevator_switch+0x188/0x450 elevator_change+0x290/0x470 elv_iosched_store+0x30a/0x3a0 ... [CAUSE] process1 process2 cgroup_rmdir ... blkcg_destroy_blkgs spin_trylock(&q->queue_lock) blkg_destroy percpu_ref_kill(&blkg->refcnt) ... blkg_free INIT_WORK(xxx, blkg_free_workfn) schedule_work spin_unlock(&q->queue_lock) ====================================schedule_work blkg_free_workfn elevator_change ... bfq_create_group_hierarchy blkcg_activate_policy spin_lock_irq(&q->queue_lock) blkg_get // get dead ref !! pinned_blkg = blkg spin_unlock_irq(&q->queue_lock) spin_lock_irq(&q->queue_lock) list_del_init(&blkg->q_node) spin_unlock_irq(&q->queue_lock) kfree(blkg) blkg_put(pinned_blkg) // UAF !! A blkg killed by blkg_destroy() stays on q->blkg_list until blkg_free_workfn() grabs queue_lock and unlinks it. blkg_get() on a dead percpu_ref does not resurrect the blkg, so the later blkg_put() hits freed memory and triggers this issue. [Fix] Replace blkg_get() with blkg_tryget(), which fails on a dead ref and lets the loop skip dying blkgs. Also hoist the ref acquisition to the top of the loop so dying blkgs are filtered out before a pd is allocated and attached. Otherwise a pd attached to an already-destroyed blkg would never called pd_offline_fn(). Fixes: 9d179b865449 ("blkcg: Fix multiple bugs in blkcg_activate_policy()") Signed-off-by: Zizhi Wo --- block/blk-cgroup.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/block/blk-cgroup.c b/block/blk-cgroup.c index 554c87bb4a86..03b6ce934848 100644 --- a/block/blk-cgroup.c +++ b/block/blk-cgroup.c @@ -1621,6 +1621,10 @@ int blkcg_activate_policy(struct gendisk *disk, const struct blkcg_policy *pol) if (blkg->pd[pol->plid]) continue; + /* a destroyed blkg may still be on q->blkg_list; skip it via tryget */ + if (!blkg_tryget(blkg)) + continue; + /* If prealloc matches, use it; otherwise try GFP_NOWAIT */ if (blkg == pinned_blkg) { pd = pd_prealloc; @@ -1637,7 +1641,6 @@ int blkcg_activate_policy(struct gendisk *disk, const struct blkcg_policy *pol) */ if (pinned_blkg) blkg_put(pinned_blkg); - blkg_get(blkg); pinned_blkg = blkg; spin_unlock_irq(&q->queue_lock); @@ -1666,6 +1669,8 @@ int blkcg_activate_policy(struct gendisk *disk, const struct blkcg_policy *pol) pd->online = true; spin_unlock(&blkg->blkcg->lock); + + blkg_put(blkg); } __set_bit(pol->plid, q->blkcg_pols); -- 2.52.0