[PATCH RFC v3] mtd: block2mtd: fix circular locking dependency in block2mtd_setup
"syzbot" <[email protected]>
| Newsgroups | dev.linux.lists.syzbot |
|---|---|
| Message-ID | <[email protected]> |
A circular locking dependency was reported involving the global param_lock
and VFS locks (i_rwsem). The block2mtd driver performs a synchronous VFS
path lookup (kern_path via bdev_file_open_by_path) inside its module
parameter set callback (block2mtd_setup). This callback is invoked by the
sysfs core with param_lock held.
The dependency chain is:
1. i_rwsem -> cgroup_mutex (via cgroup_rmdir)
2. cgroup_mutex -> rtnl_mutex (via cgroup_mkdir -> cgrp_css_online)
3. rtnl_mutex -> param_lock (via mac80211_hwsim_new_radio ->
ieee80211_rate_control_ops_get)
4. param_lock -> i_rwsem (via block2mtd_setup -> kern_path)
To fix this without breaking existing userspace scripts that expect
synchronous device creation, temporarily drop the param_lock during the
device creation process. This breaks the circular locking dependency while
preserving the synchronous nature of the operation.
Since dropping param_lock allows the module to be unloaded while the
callback is still executing, use try_module_get() and module_put() to
prevent a use-after-free race condition.
To implement this safely, introduce a local mutex (block2mtd_mutex) to
protect the module's internal state (blkmtd_device_list,
block2mtd_paramline, and block2mtd_init_called) from concurrent sysfs
writes, since param_lock will no longer provide this protection during the
critical section.
The lock ordering is now block2mtd_mutex -> VFS locks (i_rwsem) ->
mtd_table_mutex, which is strictly hierarchical and free of circular
dependencies.
Fixes: f9d8c3c4236e ("block2mtd: port device access to files")
Assisted-by: Gemini:gemini-3.1-pro-preview Gemini:gemini-3-flash-preview syzbot
Reported-by: [email protected]
Closes: https://syzkaller.appspot.com/bug?extid=65459fd3b61877d717a3
Link: https://syzkaller.appspot.com/ai_job?id=282a0acf-5eb7-43ae-9083-45ec7febd075
To: "Joern Engel" <[email protected]>
To: <[email protected]>
To: "Miquel Raynal" <[email protected]>
To: "Richard Weinberger" <[email protected]>
To: "Vignesh Raghavendra" <[email protected]>
Cc: <[email protected]>
---
v3:
- Added try_module_get() and module_put() in block2mtd_setup() to prevent a use-after-free race condition during module unloading.
- Introduced block2mtd_mutex to protect internal state (blkmtd_device_list, block2mtd_paramline, and block2mtd_init_called) from concurrent access.
- Temporarily drop param_lock during device lookup in block2mtd_setup() to break the circular locking dependency.
v2:
- Added comments in block2mtd_init() and block2mtd_exit() explaining the need for block2mtd_mutex to protect against concurrent sysfs writes.
https://lore.kernel.org/all/[email protected]/T/
v1:
https://lore.kernel.org/all/[email protected]/T/
---
diff --git a/drivers/mtd/devices/block2mtd.c b/drivers/mtd/devices/block2mtd.c
index 03e80b2c4..cc7054de4 100644
--- a/drivers/mtd/devices/block2mtd.c
+++ b/drivers/mtd/devices/block2mtd.c
@@ -45,7 +45,7 @@ struct block2mtd_dev {
/* Static info about the MTD, used in cleanup_module */
static LIST_HEAD(blkmtd_device_list);
-
+static DEFINE_MUTEX(block2mtd_mutex);
static struct page *page_read(struct address_space *mapping, pgoff_t index)
{
@@ -464,28 +464,47 @@ static int block2mtd_setup2(const char *val)
static int block2mtd_setup(const char *val, const struct kernel_param *kp)
{
+ int ret = 0;
+
+ if (!try_module_get(kp->mod))
+ return -ENODEV;
+
+ /*
+ * Drop the param_lock to avoid circular locking dependency
+ * with VFS locks during device lookup.
+ */
+ kernel_param_unlock(kp->mod);
+
+ mutex_lock(&block2mtd_mutex);
+
#ifdef MODULE
- return block2mtd_setup2(val);
+ ret = block2mtd_setup2(val);
#else
/* If more parameters are later passed in via
/sys/module/block2mtd/parameters/block2mtd
and block2mtd_init() has already been called,
we can parse the argument now. */
- if (block2mtd_init_called)
- return block2mtd_setup2(val);
+ if (block2mtd_init_called) {
+ ret = block2mtd_setup2(val);
+ } else {
+ /* During early boot stage, we only save the parameters
+ here. We must parse them later: if the param passed
+ from kernel boot command line, block2mtd_setup() is
+ called so early that it is not possible to resolve
+ the device (even kmalloc() fails). Deter that work to
+ block2mtd_setup2(). */
+
+ strscpy(block2mtd_paramline, val, sizeof(block2mtd_paramline));
+ }
+#endif
- /* During early boot stage, we only save the parameters
- here. We must parse them later: if the param passed
- from kernel boot command line, block2mtd_setup() is
- called so early that it is not possible to resolve
- the device (even kmalloc() fails). Deter that work to
- block2mtd_setup2(). */
+ mutex_unlock(&block2mtd_mutex);
- strscpy(block2mtd_paramline, val, sizeof(block2mtd_paramline));
+ kernel_param_lock(kp->mod);
+ module_put(kp->mod);
- return 0;
-#endif
+ return ret;
}
@@ -497,9 +516,15 @@ static int __init block2mtd_init(void)
int ret = 0;
#ifndef MODULE
+ /*
+ * block2mtd_mutex protects block2mtd_init_called and
+ * block2mtd_paramline against concurrent sysfs writes.
+ */
+ mutex_lock(&block2mtd_mutex);
if (strlen(block2mtd_paramline))
ret = block2mtd_setup2(block2mtd_paramline);
block2mtd_init_called = 1;
+ mutex_unlock(&block2mtd_mutex);
#endif
return ret;
@@ -511,6 +536,11 @@ static void block2mtd_exit(void)
struct list_head *pos, *next;
/* Remove the MTD devices */
+ /*
+ * block2mtd_mutex protects blkmtd_device_list against
+ * concurrent sysfs writes.
+ */
+ mutex_lock(&block2mtd_mutex);
list_for_each_safe(pos, next, &blkmtd_device_list) {
struct block2mtd_dev *dev = list_entry(pos, typeof(*dev), list);
block2mtd_sync(&dev->mtd);
@@ -522,6 +552,7 @@ static void block2mtd_exit(void)
list_del(&dev->list);
block2mtd_free_device(dev);
}
+ mutex_unlock(&block2mtd_mutex);
}
late_initcall(block2mtd_init);
base-commit: 7fd2df204f342fc17d1a0bfcd474b24232fb0f32
--
This is an AI-generated patch subject to moderation.
Reply with '#syz upstream' to Sign-off the patch as a human author
and send it to the upstream kernel mailing lists.
Reply with '#syz reject' to reject it ('#syz unreject' to undo).
See https://goo.gle/syzbot-ai-patches for information about AI-generated patches.
You can comment on the patch as usual, syzbot will try to address
the comments and send a new version of the patch if necessary.
syzbot engineers can be reached at [email protected].