Re: [PATCH RFC v2] mtd: block2mtd: fix circular locking dependency in block2mtd_setup

Marco Elver <[email protected]>
Newsgroups dev.linux.lists.syzbot
Message-ID <CANpmjNM4hLSAVkz4qGMOgQUXHHsvy9ceraSzzLGg7RaueCZeGg@mail.gmail.com>
On Sat, 16 May 2026 at 17:41, 'syzbot' via
syzkaller-upstream-moderation
<[email protected]> wrote:
>
> syzkaller reported a circular locking dependency 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.
>
> 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.
>
> Summary of changes:
> 1. Introduce block2mtd_mutex to protect the global blkmtd_device_list
>    and early-boot state variables.
> 2. Drop param_lock in block2mtd_setup using kernel_param_unlock(kp->mod)
>    and kernel_param_lock(kp->mod). For built-in modules, kp->mod is
>    NULL,
>    and these functions correctly unlock/lock the global param_lock.
> 3. Protect block2mtd_setup with block2mtd_mutex to ensure that
> concurrent
>    sysfs writes are serialized and the device list is safely updated.
> 4. Protect block2mtd_init and block2mtd_exit with block2mtd_mutex to
>    ensure safe access to blkmtd_device_list and block2mtd_paramline.
>
> The lock ordering is now block2mtd_mutex -> VFS locks (i_rwsem) ->
> mtd_table_mutex, which is strictly hierarchical and free of circular
> dependencies.
>
> Fixes: f9d8c3c4236ef2a1c15b01200b9d164e2c5a54c7 ("block2mtd: port device access to files")
> Assisted-by: Gemini:gemini-3.1-pro-preview Gemini:gemini-3-flash-preview
> Reported-by: [email protected]
> Link: https://syzkaller.appspot.com/bug?extid=65459fd3b61877d717a3
> Link: https://syzkaller.appspot.com/ai_job?id=c3c46711-9492-41e5-bcb4-4fb68a746c23
> To: <[email protected]>
> To: <[email protected]>
> Cc: <[email protected]>
> Cc: <[email protected]>
> Cc: <[email protected]>
> Cc: <[email protected]>
>
> ---
> v2:
> - Added comments in block2mtd_init() and block2mtd_exit() explaining the need for block2mtd_mutex to protect against concurrent sysfs writes.
>
> 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..42da6b9a5 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,43 @@ static int block2mtd_setup2(const char *val)
>
>  static int block2mtd_setup(const char *val, const struct kernel_param *kp)
>  {
> +       int ret = 0;
> +
> +       /*
> +        * 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);
>
> -       return 0;
> -#endif
> +       return ret;
>  }
>
>
> @@ -497,9 +512,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 +532,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 +548,7 @@ static void block2mtd_exit(void)
>                 list_del(&dev->list);
>                 block2mtd_free_device(dev);
>         }
> +       mutex_unlock(&block2mtd_mutex);
>  }
>
>  late_initcall(block2mtd_init);

  The deadlock resolution is correct, but dropping  param_lock
exposes a critical Use-After-Free (UAF) race condition during module
unloading.

  Because sysfs writes do not hold a module reference,  delete_module
(rmmod) can proceed concurrently with an active sysfs parameter write.

  1.  rmmod  starts, sets module state to  GOING , and calls  block2mtd_exit() .
  2.  block2mtd_exit()  acquires  block2mtd_mutex , unregisters all
active devices, clears  blkmtd_device_list , and releases
block2mtd_mutex .
  3. A concurrent sysfs write (which dropped  param_lock  to avoid the
deadlock and was blocked on  block2mtd_mutex ) now acquires the mutex.
  4. It calls  block2mtd_setup2()  $\rightarrow$  add_device() ,
registers a new MTD device, and adds it to  blkmtd_device_list .
  5. The sysfs write releases  block2mtd_mutex  and completes.
  6.  delete_module  completes and frees the module memory.

  This leaves an active MTD device registered in the kernel pointing
to freed module code ( dev->mtd._read , etc.), leading to an immediate
kernel panic on next access.

  To prevent this, we must serialize the module exit state. Introduce
a  block2mtd_exiting  flag synchronized by  block2mtd_mutex  to reject
new registrations during unload.

    diff --git a/drivers/mtd/devices/block2mtd.c
b/drivers/mtd/devices/block2mtd.c
    index f813272dad88..b12a84cf0e12 100644
    --- a/drivers/mtd/devices/block2mtd.c
    +++ b/drivers/mtd/devices/block2mtd.c
    @@ -46,6 +46,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 bool block2mtd_exiting;

     static struct page *page_read(struct address_space *mapping, pgoff_t index)
     {
    @@ -477,6 +478,11 @@ static int block2mtd_setup(const char *val,
const struct kernel_param *kp)

         mutex_lock(&block2mtd_mutex);

    +    if (block2mtd_exiting) {
    +        ret = -ENODEV;
    +        goto out_unlock;
    +    }
    +
     #ifdef MODULE
         ret = block2mtd_setup2(val);
     #else
    @@ -498,6 +504,7 @@ static int block2mtd_setup(const char *val,
const struct kernel_param *kp)
         }
     #endif

    +out_unlock:
         mutex_unlock(&block2mtd_mutex);

         kernel_param_lock(kp->mod);
    @@ -529,6 +536,7 @@ static void block2mtd_exit(void)

         /* Remove the MTD devices */
         mutex_lock(&block2mtd_mutex);
    +    block2mtd_exiting = true;
         list_for_each_safe(pos, next, &blkmtd_device_list) {
             struct block2mtd_dev *dev = list_entry(pos, typeof(*dev), list);
             block2mtd_sync(&dev->mtd);
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.