block/blk-mq.c | 13 +++++-------- block/blk.h | 2 +- block/genhd.c | 8 +++++--- 3 files changed, 11 insertions(+), 12 deletions(-)
The lockdep maps taken for a queue freeze are released based on
q->mq_freeze_disk_dead and q->mq_freeze_queue_dying, which are sampled
when the outermost freeze starts. __blk_mq_unfreeze_queue() drops
mq_freeze_lock, and only afterwards the callers release the maps. In
that window the depth is already zero, so a new freezer can rewrite the
flags, e.g. del_gendisk() on a disk that owns its queue, which sets
DYING and GD_DEAD first. The old owner then skips the release and
returns to userspace with the maps still held:
WARNING: lock held when returning to user space!
syz.0.452/6787 is leaving the kernel with locks still held!
2 locks held by syz.0.452/6787:
#0: (&q->io_lockdep_map){++++}-{0:0}, at: blk_mq_freeze_queue include/linux/blk-mq.h:956 [inline]
#0: (&q->io_lockdep_map){++++}-{0:0}, at: blk_throtl_init+0x1f0/0x420 block/blk-throttle.c:1332
#1: (&q->q_lockdep_map){+.+.}-{0:0}, at: blk_mq_freeze_queue include/linux/blk-mq.h:956 [inline]
#1: (&q->q_lockdep_map){+.+.}-{0:0}, at: blk_throtl_init+0x1f0/0x420 block/blk-throttle.c:1332
It is triggered by writing a blk-throttle limit while a null_blk device
is being removed through configfs.
Release the maps from __blk_mq_unfreeze_queue() while still holding
mq_freeze_lock, where the flags can't change. del_gendisk() then only
needs the explicit release when the queue is not unfrozen there.
Reported-by: syzbot+1eb53c83ab8b0a0302a6@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=1eb53c83ab8b0a0302a6
Fixes: 6f491a8d4b92 ("block: track disk DEAD state automatically for modeling queue freeze lockdep")
Fixes: f6661b1d0525 ("block: track queue dying state automatically for modeling queue freeze lockdep")
Signed-off-by: Nguyen Ngoc Thang <ngocthang2710.1999@gmail.com>
Reproduced in QEMU (KASAN + lockdep). The window is tiny, so I widened it
with a 300ms msleep() after mq_freeze_lock is dropped in
blk_mq_unfreeze_queue_nomemrestore() for nullb1, while threads write
"<maj:min> 1" to blkio.throttle.write_iops_device and the main thread
toggles null_blk power via configfs. Unpatched: the warning above (2 locks
held) within ~10s. Patched, with the same delay after the unfreeze: no
warning. Only the null_blk path was tested.
---
block/blk-mq.c | 13 +++++--------
block/blk.h | 2 +-
block/genhd.c | 8 +++++---
3 files changed, 11 insertions(+), 12 deletions(-)
diff --git a/block/blk-mq.c b/block/blk-mq.c
index a26a11c73ee3..da52e3ab5c7c 100644
--- a/block/blk-mq.c
+++ b/block/blk-mq.c
@@ -208,10 +208,8 @@ void blk_mq_freeze_queue_nomemsave(struct request_queue *q)
}
EXPORT_SYMBOL_GPL(blk_mq_freeze_queue_nomemsave);
-bool __blk_mq_unfreeze_queue(struct request_queue *q, bool force_atomic)
+void __blk_mq_unfreeze_queue(struct request_queue *q, bool force_atomic)
{
- bool unfreeze;
-
mutex_lock(&q->mq_freeze_lock);
if (force_atomic)
q->q_usage_counter.data->force_atomic = true;
@@ -221,16 +219,15 @@ bool __blk_mq_unfreeze_queue(struct request_queue *q, bool force_atomic)
percpu_ref_resurrect(&q->q_usage_counter);
wake_up_all(&q->mq_freeze_wq);
}
- unfreeze = blk_unfreeze_check_owner(q);
+ /* release under the lock: a new freezer may rewrite the lockdep flags */
+ if (blk_unfreeze_check_owner(q))
+ blk_unfreeze_release_lock(q);
mutex_unlock(&q->mq_freeze_lock);
-
- return unfreeze;
}
void blk_mq_unfreeze_queue_nomemrestore(struct request_queue *q)
{
- if (__blk_mq_unfreeze_queue(q, false))
- blk_unfreeze_release_lock(q);
+ __blk_mq_unfreeze_queue(q, false);
}
EXPORT_SYMBOL_GPL(blk_mq_unfreeze_queue_nomemrestore);
diff --git a/block/blk.h b/block/blk.h
index 50abfd932886..16388de6c541 100644
--- a/block/blk.h
+++ b/block/blk.h
@@ -55,7 +55,7 @@ const char *blk_status_to_tag(blk_status_t status);
blk_status_t tag_to_blk_status(const char *tag);
enum req_op str_to_blk_op(const char *op);
-bool __blk_mq_unfreeze_queue(struct request_queue *q, bool force_atomic);
+void __blk_mq_unfreeze_queue(struct request_queue *q, bool force_atomic);
bool blk_queue_start_drain(struct request_queue *q);
bool __blk_freeze_queue_start(struct request_queue *q,
struct task_struct *owner);
diff --git a/block/genhd.c b/block/genhd.c
index 10ca8b4d6eea..97dd290909c0 100644
--- a/block/genhd.c
+++ b/block/genhd.c
@@ -772,11 +772,13 @@ static void __del_gendisk(struct gendisk *disk)
* If the disk does not own the queue, allow using passthrough requests
* again. Else leave the queue frozen to fail all I/O.
*/
- if (!test_bit(GD_OWNS_QUEUE, &disk->state))
+ if (!test_bit(GD_OWNS_QUEUE, &disk->state)) {
__blk_mq_unfreeze_queue(q, true);
- else if (queue_is_mq(q))
- blk_mq_exit_queue(q);
+ return;
+ }
+ if (queue_is_mq(q))
+ blk_mq_exit_queue(q);
if (start_drain)
blk_unfreeze_release_lock(q);
}
--
2.43.0
© 2016 - 2026 Red Hat, Inc.