Re: [syzbot] [fs?] possible deadlock in ovl_create_object (2)
From: Chris Roy
Date: Wed Sep 16 2026 - 17:42:56 EST
On Wed, 9 Sep 2026 08:19:24 -0700, syzbot wrote:
> syzbot found the following issue on:
> ...
> possible deadlock in ovl_create_object
Follow-up to my earlier note: I reproduced this with the C reproducer.
The cycle is not an overlayfs bug by itself. block2mtd_setup() opens the
named block device (VFS path walk) while still under param_lock and the
kernfs write path. When that write arrives via splice, a pipe mutex is
held as well. That nests under already (the other way) ordered locks with
overlay sb_writers / ovl_i_mutex.
The attached patch drops param_lock and defers the open to a workqueue
(still synchronous via wait_for_completion), so the VFS walk does not
run under that stack. Local testing with the syzbot C repro: lockdep
warning before the patch, clean after.
#syz test: git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
master
Please consider the patch for linux-mtd.
On Thu, 17 Sept 2026 at 01:18, Chris Roy <iam@xxxxxxxxxxx> wrote:
>
> On Thu, 17 Sept 2026 at 01:10, syzbot
> <syzbot+7cab6a19619f1b8efc00@xxxxxxxxxxxxxxxxxxxxxxxxx> wrote:
> >
> > Hello,
> >
> > syzbot found the following issue on:
> >
> > HEAD commit: 654ae5d73c05 Merge tag 'drm-fixes-2026-09-05' of https://g..
> > git tree: upstream
> > console output: https://syzkaller.appspot.com/x/log.txt?x=11860cf9580000
> > kernel config: https://syzkaller.appspot.com/x/.config?x=8c5c3949d762a91f
> > dashboard link: https://syzkaller.appspot.com/bug?extid=7cab6a19619f1b8efc00
> > compiler: gcc (Debian 14.2.0-19) 14.2.0, GNU ld (GNU Binutils for Debian) 2.44
> >
> > Unfortunately, I don't have any reproducer for this issue yet.
>
> I am investigating this report and working on reproducing the lockdep
> cycle and identifying the root cause. I will follow up with findings
> and a proposed fix shortly.
>
> Regards,
> - Chris
>
> "But how could you live and have no story to tell?"
From c39535066f53d998c716488273e86761bb8e2ade Mon Sep 17 00:00:00 2001
From: Chris Roy <iam@xxxxxxxxxxx>
Date: Wed, 16 Sep 2026 21:09:49 +0000
Subject: [PATCH] mtd: block2mtd: defer device open out of param/sysfs write
block2mtd_setup() is called from the module-parameter write path
while param_lock is held, and from inside kernfs_fop_write_iter
(which holds the kernfs inode mutex). When that write arrives via
splice, a pipe mutex is held as well.
The setup path then opens the named block device with
bdev_file_open_by_path(), which walks the VFS. That nests inode /
overlay directory locks and sb_writers under the locks above and
creates a lockdep cycle, for example:
sb_writers -> pipe -> kernfs/param -> ovl_i_mutex -> sb_writers
syzbot reproduces it by splicing into an overlay file, splicing an
overlay path into /sys/module/block2mtd/parameters/block2mtd, then
creating a file on the overlay.
Drop param_lock and run the open on a workqueue so VFS locking is
not nested under the parameter/sysfs/pipe stack. Keep the call
synchronous with wait_for_completion(). Protect the device list and
the early-boot paramline buffer with a local mutex that is never
held across a path lookup.
Reported-by: syzbot+7cab6a19619f1b8efc00@xxxxxxxxxxxxxxxxxxxxxxxxx
Closes: https://syzkaller.appspot.com/bug?extid=7cab6a19619f1b8efc00
Signed-off-by: Chris Roy <iam@xxxxxxxxxxx>
---
drivers/mtd/devices/block2mtd.c | 103 +++++++++++++++++++++++++-------
1 file changed, 83 insertions(+), 20 deletions(-)
diff --git a/drivers/mtd/devices/block2mtd.c b/drivers/mtd/devices/block2mtd.c
index 03e80b2c4..4ab4c31a2 100644
--- a/drivers/mtd/devices/block2mtd.c
+++ b/drivers/mtd/devices/block2mtd.c
@@ -27,6 +27,8 @@
#include <linux/init.h>
#include <linux/mtd/mtd.h>
#include <linux/mutex.h>
+#include <linux/workqueue.h>
+#include <linux/completion.h>
#include <linux/mount.h>
#include <linux/slab.h>
#include <linux/major.h>
@@ -45,6 +47,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)
@@ -329,7 +332,9 @@ static struct block2mtd_dev *add_device(char *devname, int erase_size,
goto err_destroy_mutex;
}
+ mutex_lock(&block2mtd_mutex);
list_add(&dev->list, &blkmtd_device_list);
+ mutex_unlock(&block2mtd_mutex);
pr_info("mtd%d: [%s] erase_size = %dKiB [%d]\n",
dev->mtd.index,
label ? label : dev->mtd.name + strlen("block2mtd: "),
@@ -462,30 +467,77 @@ static int block2mtd_setup2(const char *val)
}
-static int block2mtd_setup(const char *val, const struct kernel_param *kp)
+
+struct block2mtd_setup_work {
+ struct work_struct work;
+ char *val;
+ struct completion done;
+ int ret;
+};
+
+static void block2mtd_setup_workfn(struct work_struct *work)
{
-#ifdef MODULE
- return 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. */
+ struct block2mtd_setup_work *w =
+ container_of(work, struct block2mtd_setup_work, work);
+
+ w->ret = block2mtd_setup2(w->val);
+ complete(&w->done);
+}
+
+/*
+ * Run device setup outside the module-parameter / kernfs write path.
+ * Those paths hold param_lock and the kernfs inode mutex (and, when the
+ * write arrives via splice, a pipe mutex). Opening a block device does
+ * VFS lookups and must not nest under that stack.
+ */
+static int block2mtd_setup_defer(const char *val)
+{
+ struct block2mtd_setup_work w = {
+ .ret = 0,
+ };
+
+ w.val = kstrdup(val, GFP_KERNEL);
+ if (!w.val)
+ return -ENOMEM;
+
+ init_completion(&w.done);
+ INIT_WORK(&w.work, block2mtd_setup_workfn);
+ schedule_work(&w.work);
+ wait_for_completion(&w.done);
+ kfree(w.val);
+ return w.ret;
+}
- if (block2mtd_init_called)
- return block2mtd_setup2(val);
+static int block2mtd_setup(const char *val, const struct kernel_param *kp)
+{
+ int ret = 0;
- /* 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(). */
+ if (!try_module_get(kp->mod))
+ return -ENODEV;
- strscpy(block2mtd_paramline, val, sizeof(block2mtd_paramline));
+ /*
+ * Drop param_lock before scheduling. The actual open runs on a
+ * workqueue so it is also outside kernfs_fop_write_iter's inode
+ * mutex (and any pipe lock from splice).
+ */
+ kernel_param_unlock(kp->mod);
- return 0;
+#ifdef MODULE
+ ret = block2mtd_setup_defer(val);
+#else
+ if (block2mtd_init_called) {
+ ret = block2mtd_setup_defer(val);
+ } else {
+ mutex_lock(&block2mtd_mutex);
+ strscpy(block2mtd_paramline, val, sizeof(block2mtd_paramline));
+ mutex_unlock(&block2mtd_mutex);
+ }
#endif
+
+ kernel_param_lock(kp->mod);
+ module_put(kp->mod);
+
+ return ret;
}
@@ -497,9 +549,18 @@ static int __init block2mtd_init(void)
int ret = 0;
#ifndef MODULE
- if (strlen(block2mtd_paramline))
- ret = block2mtd_setup2(block2mtd_paramline);
+ mutex_lock(&block2mtd_mutex);
+ if (strlen(block2mtd_paramline)) {
+ char buf[sizeof(block2mtd_paramline)];
+
+ strscpy(buf, block2mtd_paramline, sizeof(buf));
+ mutex_unlock(&block2mtd_mutex);
+ /* init context: no kernfs/param locks held */
+ ret = block2mtd_setup2(buf);
+ mutex_lock(&block2mtd_mutex);
+ }
block2mtd_init_called = 1;
+ mutex_unlock(&block2mtd_mutex);
#endif
return ret;
@@ -510,6 +571,7 @@ static void block2mtd_exit(void)
{
struct list_head *pos, *next;
+ mutex_lock(&block2mtd_mutex);
/* Remove the MTD devices */
list_for_each_safe(pos, next, &blkmtd_device_list) {
struct block2mtd_dev *dev = list_entry(pos, typeof(*dev), list);
@@ -522,6 +584,7 @@ static void block2mtd_exit(void)
list_del(&dev->list);
block2mtd_free_device(dev);
}
+ mutex_unlock(&block2mtd_mutex);
}
late_initcall(block2mtd_init);
--
2.43.0