Use-after-free (race condition) in ublk cancel/reset paths

HIGH
torvalds/linux
Commit: f7700a4415af
Affected: v7.0-rc6 and earlier
2026-05-08 23:14 UTC

Description

The commit fixes a race leading to a use-after-free between ublk_reset_ch_dev() and ublk_cancel_cmd() in the userspace block (ublk) subsystem. The root cause is concurrent access to io->cmd and io->flags: without proper synchronization, ublk_reset_ch_dev() can clear io->cmd via ublk_queue_reinit() while ublk_cancel_cmd() may concurrently read a stale pointer and pass it to io_uring_cmd_done(), causing use-after-free. The fix synchronizes the two paths by: - Reading and clearing io->cmd under cancel_lock in ublk_cancel_cmd(), saving the value to a local variable and calling io_uring_cmd_done() on that local copy outside the lock. - Holding cancel_lock while performing ublk_queue_reinit() in ublk_reset_ch_dev() so that io->cmd and io->flags are cleared atomically with respect to ublk_cancel_cmd(). This removes the window where a freed or stale pointer could be used when completing the IO command and closes a potential memory safety vulnerability in the IO cancel/reset path.

Commit Details

Author: Ming Lei

Date: 2026-05-08 12:37 UTC

Message:

ublk: fix use-after-free in ublk_cancel_cmd() When ublk_reset_ch_dev() clears io->cmd via ublk_queue_reinit() concurrently with ublk_cancel_cmd(), ublk_cancel_cmd() can read a stale pointer and pass it to io_uring_cmd_done(), causing a use-after-free. Fix by synchronizing the two paths with ubq->cancel_lock: - ublk_cancel_cmd(): read and clear io->cmd under cancel_lock, then call io_uring_cmd_done() on the saved local copy outside the lock. - ublk_reset_ch_dev(): hold cancel_lock across ublk_queue_reinit() so that io->cmd and io->flags are cleared atomically with respect to ublk_cancel_cmd(). Fixes: 216c8f5ef0f2 ("ublk: replace monitor with cancelable uring_cmd") Signed-off-by: Ming Lei <tom.leiming@gmail.com> Link: https://patch.msgid.link/20260508123746.242018-1-tom.leiming@gmail.com Signed-off-by: Jens Axboe <axboe@kernel.dk>

Triage Assessment

Vulnerability Type: Use-after-free

Confidence: HIGH

Reasoning:

The commit fixes a use-after-free race between ublk_reset_ch_dev() and ublk_cancel_cmd() by synchronizing access to io->cmd and io->flags with a cancel_lock, preventing reading a stale pointer and a subsequent use-after-free when completing an io_uring command. This is a memory-safety vulnerability that could be exploited in some scenarios, so it counts as a security fix.

Verification Assessment

Vulnerability Type: Use-after-free (race condition) in ublk cancel/reset paths

Confidence: HIGH

Affected Versions: v7.0-rc6 and earlier

Code Diff

diff --git a/drivers/block/ublk_drv.c b/drivers/block/ublk_drv.c index 57ec900f0ce0f6..6d13f1481de053 100644 --- a/drivers/block/ublk_drv.c +++ b/drivers/block/ublk_drv.c @@ -2411,8 +2411,14 @@ static void ublk_reset_ch_dev(struct ublk_device *ub) { int i; - for (i = 0; i < ub->dev_info.nr_hw_queues; i++) - ublk_queue_reinit(ub, ublk_get_queue(ub, i)); + for (i = 0; i < ub->dev_info.nr_hw_queues; i++) { + struct ublk_queue *ubq = ublk_get_queue(ub, i); + + /* Sync with ublk_cancel_cmd() */ + spin_lock(&ubq->cancel_lock); + ublk_queue_reinit(ub, ubq); + spin_unlock(&ubq->cancel_lock); + } /* set to NULL, otherwise new tasks cannot mmap io_cmd_buf */ ub->mm = NULL; @@ -2753,6 +2759,7 @@ static void ublk_cancel_cmd(struct ublk_queue *ubq, unsigned tag, { struct ublk_io *io = &ubq->ios[tag]; struct ublk_device *ub = ubq->dev; + struct io_uring_cmd *cmd = NULL; struct request *req; bool done; @@ -2775,12 +2782,15 @@ static void ublk_cancel_cmd(struct ublk_queue *ubq, unsigned tag, spin_lock(&ubq->cancel_lock); done = !!(io->flags & UBLK_IO_FLAG_CANCELED); - if (!done) + if (!done) { io->flags |= UBLK_IO_FLAG_CANCELED; + cmd = io->cmd; + io->cmd = NULL; + } spin_unlock(&ubq->cancel_lock); - if (!done) - io_uring_cmd_done(io->cmd, UBLK_IO_RES_ABORT, issue_flags); + if (!done && cmd) + io_uring_cmd_done(cmd, UBLK_IO_RES_ABORT, issue_flags); } /*
← Back to Alerts View on GitHub →