Re: [syzbot] possible deadlock in __loop_clr_fd (3)

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 2021/11/11 9:55, Dan Schatzberg wrote:
>> . Can you somehow call destroy_workqueue() without holding a lock (e.g.
>> breaking &lo->lo_mutex => (wq_completion)loop$M dependency chain by
>> making sure that below change is safe) ?
> 
> It's really not clear to me - the lo_mutex protects a lot of entry
> points (ioctls and others) and it's hard to tell if the observed state
> will be sane if they can race in the middle of __loop_clr_fd.
> 

I'm not familiar with the block layer, but I think it is clear.
We check lo_state with lo_mutex held.
The loop functions fail if lo_state is not what each function expected.

Is blk_mq_freeze_queue() for waiting for "loop_queue_work() from loop_queue_rq()" to
complete and for blocking further loop_queue_rq() until blk_mq_unfreeze_queue() ?
If yes, we need to call blk_mq_freeze_queue() before destroy_workqueue().
But I think lo_state is helping us, like a patch shown below.



>From e36f23c081e0b8ed08239f4e3fbc954b4d7d3feb Mon Sep 17 00:00:00 2001
From: Tetsuo Handa <penguin-kernel@xxxxxxxxxxxxxxxxxxx>
Date: Thu, 11 Nov 2021 23:55:43 +0900
Subject: [PATCH] loop: destroy workqueue without holding locks

syzbot is reporting circular locking problem at __loop_clr_fd() [1], for
commit 87579e9b7d8dc36e ("loop: use worker per cgroup instead of kworker")
is calling destroy_workqueue() with lo->lo_mutex held.

Since all functions where lo->lo_state matters are already checking
lo->lo_state with lo->lo_mutex held (in order to avoid racing with e.g.
ioctl(LOOP_CTL_REMOVE)), and __loop_clr_fd() can be called from either
ioctl(LOOP_CLR_FD) or close(), lo->lo_state == Lo_rundown is considered
as an exclusive lock for __loop_clr_fd(). Therefore, it is safe to
temporarily drop lo->lo_mutex when calling destroy_workqueue().

Link: https://syzkaller.appspot.com/bug?extid=63614029dfb79abd4383 [1]
Reported-by: syzbot <syzbot+63614029dfb79abd4383@xxxxxxxxxxxxxxxxxxxxxxxxx>
Signed-off-by: Tetsuo Handa <penguin-kernel@xxxxxxxxxxxxxxxxxxx>
---
 drivers/block/loop.c | 34 +++++++++++++++++++++++++---------
 1 file changed, 25 insertions(+), 9 deletions(-)

diff --git a/drivers/block/loop.c b/drivers/block/loop.c
index a154cab6cd98..b98ec1c2d950 100644
--- a/drivers/block/loop.c
+++ b/drivers/block/loop.c
@@ -1104,16 +1104,20 @@ static int __loop_clr_fd(struct loop_device *lo, bool release)
 	 */
 
 	mutex_lock(&lo->lo_mutex);
-	if (WARN_ON_ONCE(lo->lo_state != Lo_rundown)) {
-		err = -ENXIO;
-		goto out_unlock;
-	}
+	/*
+	 * Since this function is called upon "ioctl(LOOP_CLR_FD)" xor "close()
+	 * after ioctl(LOOP_CLR_FD)", it is a sign of something going wrong if
+	 * lo->lo_state has changed while waiting for lo->lo_mutex.
+	 */
+	BUG_ON(lo->lo_state != Lo_rundown);
 
+	/*
+	 * Since ioctl(LOOP_CLR_FD) depends on lo->lo_state == Lo_bound, it is
+	 * a sign of something going wrong if lo->lo_backing_file was not
+	 * assigned by ioctl(LOOP_SET_FD) or ioctl(LOOP_CONFIGURE).
+	 */
 	filp = lo->lo_backing_file;
-	if (filp == NULL) {
-		err = -EINVAL;
-		goto out_unlock;
-	}
+	BUG_ON(!filp);
 
 	if (test_bit(QUEUE_FLAG_WC, &lo->lo_queue->queue_flags))
 		blk_queue_write_cache(lo->lo_queue, false, false);
@@ -1121,7 +1125,20 @@ static int __loop_clr_fd(struct loop_device *lo, bool release)
 	/* freeze request queue during the transition */
 	blk_mq_freeze_queue(lo->lo_queue);
 
+	/*
+	 * To avoid circular locking dependency, call destroy_workqueue()
+	 * without holding lo->lo_mutex.
+	 */
+	mutex_unlock(&lo->lo_mutex);
 	destroy_workqueue(lo->workqueue);
+	mutex_lock(&lo->lo_mutex);
+
+	/*
+	 * As explained above, lo->lo_state cannot be changed while waiting for
+	 * lo->lo_mutex.
+	 */
+	BUG_ON(lo->lo_state != Lo_rundown);
+
 	spin_lock_irq(&lo->lo_work_lock);
 	list_for_each_entry_safe(worker, pos, &lo->idle_worker_list,
 				idle_list) {
@@ -1156,7 +1173,6 @@ static int __loop_clr_fd(struct loop_device *lo, bool release)
 	partscan = lo->lo_flags & LO_FLAGS_PARTSCAN;
 	lo_number = lo->lo_number;
 	disk_force_media_change(lo->lo_disk, DISK_EVENT_MEDIA_CHANGE);
-out_unlock:
 	mutex_unlock(&lo->lo_mutex);
 	if (partscan) {
 		/*
-- 
2.18.4





[Index of Archives]     [Linux RAID]     [Linux SCSI]     [Linux ATA RAID]     [IDE]     [Linux Wireless]     [Linux Kernel]     [ATH6KL]     [Linux Bluetooth]     [Linux Netdev]     [Kernel Newbies]     [Security]     [Git]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Device Mapper]

  Powered by Linux