[PATCH] block: blk-mq: fix UAF in blk_mq_tagset_busy_iter

l1za0.sec
2026-04-30T04:28:32Z

From: Haocheng Yu <l1za0.sec@gmail.com>

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 <l1za0.sec@gmail.com>
---
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)={<r2=>0xffffffffffffffff})
r3 = syz_open_dev$dri(&(0x7f0000000100), 0xfffffffffffffffc, 0xc8503)
r4 = syz_open_dev$ndb(&(0x7f0000000000), 0x0, 0xc0400)
socketpair$nbd(0x1, 0x1, 0x0, &(0x7f0000000080)={<r5=>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)={<r10=>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