From 9ddee6b7521d9ceb9bdd3c48d5b119c34b0444f1 Mon Sep 17 00:00:00 2001 From: Yun Zhou Date: Tue, 30 Jun 2026 11:10:21 +0800 Subject: [PATCH] nbd: fix circular lock dependency in nbd_disconnect_and_put Move flush_workqueue() out of the config_lock critical section in nbd_disconnect_and_put() to break a circular lock dependency. The lockdep splat shows: config_lock -> (wq_completion)nbd0-recv from nbd_disconnect_and_put() holding config_lock then calling flush_workqueue() which waits for recv_work to complete. (work_completion)(&args->work) -> config_lock from recv_work() -> nbd_config_put() -> refcount_dec_and_mutex_lock() which may acquire config_lock when the last reference is dropped. Fix by splitting the config_lock region: first hold config_lock to perform nbd_disconnect(), sock_shutdown(), and clear NBD_RT_BOUND (to prevent nbd_genl_reconfigure from queueing new recv_work during the window), then release config_lock before flush_workqueue(), and re-acquire it for nbd_clear_que(). This is safe because: - sock_shutdown() ensures recv_work will observe errors and exit - NBD_RT_BOUND cleared prevents concurrent reconfigure from reconnecting - flush_workqueue() guarantees all recv_work has completed before the second config_lock section clears the queue Fixes: e2daec488c57 ("nbd: Fix hungtask when nbd_config_put") Reported-by: syzbot+3add0454d5a2619b8e80@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=3add0454d5a2619b8e80 Signed-off-by: Yun Zhou --- drivers/block/nbd.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/block/nbd.c b/drivers/block/nbd.c index 8f10762e90ef7..a1ac6246336c1 100644 --- a/drivers/block/nbd.c +++ b/drivers/block/nbd.c @@ -2294,14 +2294,23 @@ static void nbd_disconnect_and_put(struct nbd_device *nbd) nbd_disconnect(nbd); sock_shutdown(nbd); wake_up(&nbd->config->conn_wait); + /* + * Clear NBD_RT_BOUND before releasing config_lock so that + * nbd_genl_reconfigure() won't queue new recv_work between + * here and flush_workqueue(). + */ + nbd->task_setup = NULL; + clear_bit(NBD_RT_BOUND, &nbd->config->runtime_flags); + mutex_unlock(&nbd->config_lock); + /* * Make sure recv thread has finished, we can safely call nbd_clear_que() * to cancel the inflight I/Os. */ flush_workqueue(nbd->recv_workq); + + mutex_lock(&nbd->config_lock); nbd_clear_que(nbd); - nbd->task_setup = NULL; - clear_bit(NBD_RT_BOUND, &nbd->config->runtime_flags); mutex_unlock(&nbd->config_lock); if (test_and_clear_bit(NBD_RT_HAS_CONFIG_REF,