diff mbox series

[v3] loop: reduce the loop_ctl_mutex scope

Message ID cc5c215f-4b3b-94e9-560b-a02d0e23c97c@i-love.sakura.ne.jp (mailing list archive)
State New, archived
Headers show
Series [v3] loop: reduce the loop_ctl_mutex scope | expand

Commit Message

Tetsuo Handa Aug. 29, 2021, 1:47 p.m. UTC
syzbot is reporting circular locking problem at __loop_clr_fd() [1], for
commit a160c6159d4a0cf8 ("block: add an optional probe callback to
major_names") is calling the module's probe function with major_names_lock
held.

Fortunately, since commit 990e78116d38059c ("block: loop: fix deadlock
between open and remove") stopped holding loop_ctl_mutex in lo_open(),
current role of loop_ctl_mutex is to serialize access to loop_index_idr
and loop_add()/loop_remove(); in other words, management of id for IDR.
To avoid holding loop_ctl_mutex during whole add/remove operation, use
a bool flag to indicate whether the loop device is ready for use.

loop_unregister_transfer() which is called from cleanup_cryptoloop()
currently has possibility of use-after-free access due to lack of
serialization between kfree() from loop_remove() from loop_control_remove()
and mutex_lock() from unregister_transfer_cb(). But since lo->lo_encryption
should be already NULL when this function is called due to module unload,
and commit 222013f9ac30b9ce ("cryptoloop: add a deprecation warning")
indicates that we will remove this function shortly, this patch updates
this function to emit warning instead of checking lo->lo_encryption.

Holding loop_ctl_mutex in loop_exit() is pointless, for all users must
close /dev/loop-control and /dev/loop$num (in order to drop module's
refcount to 0) before loop_exit() starts, and nobody can open
/dev/loop-control or /dev/loop$num afterwards.

Link: https://syzkaller.appspot.com/bug?id=7bb10e8b62f83e4d445cdf4c13d69e407e629558 [1]
Reported-by: syzbot <syzbot+f61766d5763f9e7a118f@syzkaller.appspotmail.com>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
---
Changes in v3:
  Don't use cmpxchg(), for kernel test robot <lkp@intel.com> reported
  that some archtectures do not support cmpxchg() on bool.
  Add data_race() annotation to loop_control_get_free().

Changes in v2:
  Don't replace loop_ctl_mutex mutex with loop_idr_spinlock spinlock.
  Don't traverse on loop_index_idr at loop_unregister_transfer().
  Don't use refcount for handling duplicated removal requests.
---
 drivers/block/loop.c | 75 +++++++++++++++++++++++++++++---------------
 drivers/block/loop.h |  1 +
 2 files changed, 50 insertions(+), 26 deletions(-)

Comments

Christoph Hellwig Aug. 30, 2021, 7:13 a.m. UTC | #1
Hi Tetsuo,

this generally looks fine to me.  The only issue is that I remember is
that cmpxchg did historically not work on bool on all architectures.
I'm not sure if this has been fixed since.
Christoph Hellwig Sept. 1, 2021, 5:36 a.m. UTC | #2
On Mon, Aug 30, 2021 at 09:13:50AM +0200, Christoph Hellwig wrote:
> Hi Tetsuo,
> 
> this generally looks fine to me.  The only issue is that I remember is
> that cmpxchg did historically not work on bool on all architectures.
> I'm not sure if this has been fixed since.

Looks like that still is an issue based on the buildbot reports on
linux-kernel.  I'd suggest to resend with idr_visible as an int.
Tetsuo Handa Sept. 1, 2021, 5:47 a.m. UTC | #3
On 2021/09/01 14:36, Christoph Hellwig wrote:
> On Mon, Aug 30, 2021 at 09:13:50AM +0200, Christoph Hellwig wrote:
>> Hi Tetsuo,
>>
>> this generally looks fine to me.  The only issue is that I remember is
>> that cmpxchg did historically not work on bool on all architectures.
>> I'm not sure if this has been fixed since.
> 
> Looks like that still is an issue based on the buildbot reports on
> linux-kernel.  I'd suggest to resend with idr_visible as an int.
> 

I sent "[PATCH v3] loop: reduce the loop_ctl_mutex scope" as not using cmpxchg().
The buildbot is reporting errors on the v2 patch.
Christoph Hellwig Sept. 1, 2021, 6:10 a.m. UTC | #4
Looks good,

Reviewed-by: Christoph Hellwig <hch@lst.de>
Jens Axboe Sept. 4, 2021, 4:16 a.m. UTC | #5
On 8/29/21 7:47 AM, Tetsuo Handa wrote:
> syzbot is reporting circular locking problem at __loop_clr_fd() [1], for
> commit a160c6159d4a0cf8 ("block: add an optional probe callback to
> major_names") is calling the module's probe function with major_names_lock
> held.
> 
> Fortunately, since commit 990e78116d38059c ("block: loop: fix deadlock
> between open and remove") stopped holding loop_ctl_mutex in lo_open(),
> current role of loop_ctl_mutex is to serialize access to loop_index_idr
> and loop_add()/loop_remove(); in other words, management of id for IDR.
> To avoid holding loop_ctl_mutex during whole add/remove operation, use
> a bool flag to indicate whether the loop device is ready for use.
> 
> loop_unregister_transfer() which is called from cleanup_cryptoloop()
> currently has possibility of use-after-free access due to lack of
> serialization between kfree() from loop_remove() from loop_control_remove()
> and mutex_lock() from unregister_transfer_cb(). But since lo->lo_encryption
> should be already NULL when this function is called due to module unload,
> and commit 222013f9ac30b9ce ("cryptoloop: add a deprecation warning")
> indicates that we will remove this function shortly, this patch updates
> this function to emit warning instead of checking lo->lo_encryption.
> 
> Holding loop_ctl_mutex in loop_exit() is pointless, for all users must
> close /dev/loop-control and /dev/loop$num (in order to drop module's
> refcount to 0) before loop_exit() starts, and nobody can open
> /dev/loop-control or /dev/loop$num afterwards.

Applied, thanks.
diff mbox series

Patch

diff --git a/drivers/block/loop.c b/drivers/block/loop.c
index f0cdff0c5fbf..478ff6650dab 100644
--- a/drivers/block/loop.c
+++ b/drivers/block/loop.c
@@ -2113,18 +2113,6 @@  int loop_register_transfer(struct loop_func_table *funcs)
 	return 0;
 }
 
-static int unregister_transfer_cb(int id, void *ptr, void *data)
-{
-	struct loop_device *lo = ptr;
-	struct loop_func_table *xfer = data;
-
-	mutex_lock(&lo->lo_mutex);
-	if (lo->lo_encryption == xfer)
-		loop_release_xfer(lo);
-	mutex_unlock(&lo->lo_mutex);
-	return 0;
-}
-
 int loop_unregister_transfer(int number)
 {
 	unsigned int n = number;
@@ -2132,9 +2120,20 @@  int loop_unregister_transfer(int number)
 
 	if (n == 0 || n >= MAX_LO_CRYPT || (xfer = xfer_funcs[n]) == NULL)
 		return -EINVAL;
+	/*
+	 * This function is called from only cleanup_cryptoloop().
+	 * Given that each loop device that has a transfer enabled holds a
+	 * reference to the module implementing it we should never get here
+	 * with a transfer that is set (unless forced module unloading is
+	 * requested). Thus, check module's refcount and warn if this is
+	 * not a clean unloading.
+	 */
+#ifdef CONFIG_MODULE_UNLOAD
+	if (xfer->owner && module_refcount(xfer->owner) != -1)
+		pr_err("Danger! Unregistering an in use transfer function.\n");
+#endif
 
 	xfer_funcs[n] = NULL;
-	idr_for_each(&loop_index_idr, &unregister_transfer_cb, xfer);
 	return 0;
 }
 
@@ -2325,8 +2324,9 @@  static int loop_add(int i)
 	} else {
 		err = idr_alloc(&loop_index_idr, lo, 0, 0, GFP_KERNEL);
 	}
+	mutex_unlock(&loop_ctl_mutex);
 	if (err < 0)
-		goto out_unlock;
+		goto out_free_dev;
 	i = err;
 
 	err = -ENOMEM;
@@ -2392,15 +2392,19 @@  static int loop_add(int i)
 	disk->private_data	= lo;
 	disk->queue		= lo->lo_queue;
 	sprintf(disk->disk_name, "loop%d", i);
+	/* Make this loop device reachable from pathname. */
 	add_disk(disk);
+	/* Show this loop device. */
+	mutex_lock(&loop_ctl_mutex);
+	lo->idr_visible = true;
 	mutex_unlock(&loop_ctl_mutex);
 	return i;
 
 out_cleanup_tags:
 	blk_mq_free_tag_set(&lo->tag_set);
 out_free_idr:
+	mutex_lock(&loop_ctl_mutex);
 	idr_remove(&loop_index_idr, i);
-out_unlock:
 	mutex_unlock(&loop_ctl_mutex);
 out_free_dev:
 	kfree(lo);
@@ -2410,9 +2414,14 @@  static int loop_add(int i)
 
 static void loop_remove(struct loop_device *lo)
 {
+	/* Make this loop device unreachable from pathname. */
 	del_gendisk(lo->lo_disk);
 	blk_cleanup_disk(lo->lo_disk);
 	blk_mq_free_tag_set(&lo->tag_set);
+	mutex_lock(&loop_ctl_mutex);
+	idr_remove(&loop_index_idr, lo->lo_number);
+	mutex_unlock(&loop_ctl_mutex);
+	/* There is no route which can find this loop device. */
 	mutex_destroy(&lo->lo_mutex);
 	kfree(lo);
 }
@@ -2436,31 +2445,40 @@  static int loop_control_remove(int idx)
 		return -EINVAL;
 	}
 		
+	/* Hide this loop device for serialization. */
 	ret = mutex_lock_killable(&loop_ctl_mutex);
 	if (ret)
 		return ret;
-
 	lo = idr_find(&loop_index_idr, idx);
-	if (!lo) {
+	if (!lo || !lo->idr_visible)
 		ret = -ENODEV;
-		goto out_unlock_ctrl;
-	}
+	else
+		lo->idr_visible = false;
+	mutex_unlock(&loop_ctl_mutex);
+	if (ret)
+		return ret;
 
+	/* Check whether this loop can be removed. */
 	ret = mutex_lock_killable(&lo->lo_mutex);
 	if (ret)
-		goto out_unlock_ctrl;
+		goto mark_visible;
 	if (lo->lo_state != Lo_unbound ||
 	    atomic_read(&lo->lo_refcnt) > 0) {
 		mutex_unlock(&lo->lo_mutex);
 		ret = -EBUSY;
-		goto out_unlock_ctrl;
+		goto mark_visible;
 	}
+	/* Mark this loop device no longer open()-able. */
 	lo->lo_state = Lo_deleting;
 	mutex_unlock(&lo->lo_mutex);
 
-	idr_remove(&loop_index_idr, lo->lo_number);
 	loop_remove(lo);
-out_unlock_ctrl:
+	return 0;
+
+mark_visible:
+	/* Show this loop device again. */
+	mutex_lock(&loop_ctl_mutex);
+	lo->idr_visible = true;
 	mutex_unlock(&loop_ctl_mutex);
 	return ret;
 }
@@ -2474,7 +2492,8 @@  static int loop_control_get_free(int idx)
 	if (ret)
 		return ret;
 	idr_for_each_entry(&loop_index_idr, lo, id) {
-		if (lo->lo_state == Lo_unbound)
+		/* Hitting a race results in creating a new loop device which is harmless. */
+		if (lo->idr_visible && data_race(lo->lo_state) == Lo_unbound)
 			goto found;
 	}
 	mutex_unlock(&loop_ctl_mutex);
@@ -2590,10 +2609,14 @@  static void __exit loop_exit(void)
 	unregister_blkdev(LOOP_MAJOR, "loop");
 	misc_deregister(&loop_misc);
 
-	mutex_lock(&loop_ctl_mutex);
+	/*
+	 * There is no need to use loop_ctl_mutex here, for nobody else can
+	 * access loop_index_idr when this module is unloading (unless forced
+	 * module unloading is requested). If this is not a clean unloading,
+	 * we have no means to avoid kernel crash.
+	 */
 	idr_for_each_entry(&loop_index_idr, lo, id)
 		loop_remove(lo);
-	mutex_unlock(&loop_ctl_mutex);
 
 	idr_destroy(&loop_index_idr);
 }
diff --git a/drivers/block/loop.h b/drivers/block/loop.h
index 1988899db63a..04c88dd6eabd 100644
--- a/drivers/block/loop.h
+++ b/drivers/block/loop.h
@@ -68,6 +68,7 @@  struct loop_device {
 	struct blk_mq_tag_set	tag_set;
 	struct gendisk		*lo_disk;
 	struct mutex		lo_mutex;
+	bool			idr_visible;
 };
 
 struct loop_cmd {