From: Haocheng Yu A KASAN: slab-use-after-free Read in blk_mq_tagset_busy_iter is reported by a modified Syzkaller-based kernel fuzzing tool we developed. This problem is caused by a race condition between block/blk-mq-tag.c/blk_mq_tagset_busy_iter() and block/blk-mq.c/blk_mq_realloc_tag_set_tags(). In blk_mq_realloc_tag_set_tags(), set->tags is first freed, and then new_tags is assigned to set->tags. However, this process is not protected by synchronization. Therefore, if another process reads tagset->tags in blk_mq_tagset_busy_iter() between these two steps, it will cause a use-after-free read problem. To fix this vulnerability, first save the old set->tags. After updating set->tags to new_tags, wait for the reading side to exit before releasing it. This avoids the problem of tagset->tags being directly released while blk_mq_tagset_busy_iter() is still iterating. Signed-off-by: Haocheng Yu --- The full reproducer is attached here: # {Threaded:true Repeat:true RepeatTimes:0 Procs:8 Slowdown:1 Sandbox:none SandboxArg:0 Leak:false NetInjection:true NetDevices:true NetReset:true Cgroups:true BinfmtMisc:true CloseFDs:true KCSAN:false DevlinkPCI:false NicVF:false USB:true VhciInjection:true Wifi:true IEEE802154:true Sysctl:true Swap:true UseTmpDir:true HandleSegv:true Trace:false CallComments:true LegacyOptions:{Collide:false Fault:false FaultCall:0 FaultNth:0}} r0 = syz_open_dev$ndb(&(0x7f0000000000), 0x0, 0xc0400) r1 = syz_open_dev$ndb(&(0x7f0000000000), 0x0, 0x80040) ioctl$NBD_SET_FLAGS(r1, 0xab0a, 0x9ad) socketpair$nbd(0x1, 0x1, 0x0, &(0x7f0000000080)={0xffffffffffffffff}) r3 = syz_open_dev$dri(&(0x7f0000000100), 0xfffffffffffffffc, 0xc8503) r4 = syz_open_dev$ndb(&(0x7f0000000000), 0x0, 0xc0400) socketpair$nbd(0x1, 0x1, 0x0, &(0x7f0000000080)={0xffffffffffffffff}) ioctl$NBD_SET_SOCK(r4, 0xab00, r5) r6 = syz_open_dev$ndb(&(0x7f0000000240), 0x0, 0x12100) ioctl$NBD_DO_IT(r6, 0xab03) close_range(r3, 0xffffffffffffffff, 0x0) ioctl$NBD_SET_SIZE_BLOCKS(0xffffffffffffffff, 0xab07, 0x1) ioctl$NBD_SET_SOCK(r0, 0xab00, r2) r7 = syz_open_dev$loop(&(0x7f0000000040), 0x1, 0x200) ioctl$BLKPG(r7, 0x1269, &(0x7f00000001c0)={0x1, 0x0, 0x98, &(0x7f00000000c0)={0x5, 0x2, 0x10}}) close(0x5) r8 = syz_open_dev$ndb(&(0x7f0000000000), 0x0, 0x100) r9 = syz_open_dev$ndb(&(0x7f0000000000), 0x0, 0xc0400) socketpair$nbd(0x1, 0x1, 0x0, &(0x7f0000000080)={0xffffffffffffffff}) ioctl$NBD_SET_SOCK(r9, 0xab00, r10) close(0x5) r11 = syz_open_dev$ndb(&(0x7f0000000000), 0x0, 0x100) ioctl$NBD_DO_IT(r11, 0xab03) r12 = syz_open_dev$ndb(&(0x7f0000000040), 0x0, 0x0) ioctl$NBD_CLEAR_SOCK(r12, 0xab04) ioctl$NBD_DO_IT(r8, 0xab03) socket$inet6_tcp(0xa, 0x1, 0x0) syz_open_dev$ndb(&(0x7f0000000000), 0x0, 0xc0400) block/blk-mq.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/block/blk-mq.c b/block/blk-mq.c index d626d32f6e57..4357625a512d 100644 --- a/block/blk-mq.c +++ b/block/blk-mq.c @@ -4738,6 +4738,7 @@ static int blk_mq_realloc_tag_set_tags(struct blk_mq_tag_set *set, int new_nr_hw_queues) { struct blk_mq_tags **new_tags; + struct blk_mq_tags **old_tags; int i; if (set->nr_hw_queues >= new_nr_hw_queues) @@ -4751,8 +4752,10 @@ static int blk_mq_realloc_tag_set_tags(struct blk_mq_tag_set *set, if (set->tags) memcpy(new_tags, set->tags, set->nr_hw_queues * sizeof(*set->tags)); - kfree(set->tags); + old_tags = set->tags; set->tags = new_tags; + synchronize_srcu(&set->tags_srcu); + kfree(old_tags); for (i = set->nr_hw_queues; i < new_nr_hw_queues; i++) { if (!__blk_mq_alloc_map_and_rqs(set, i)) { base-commit: 7d0a66e4bb9081d75c82ec4957c50034cb0ea449 -- 2.51.0