Re: [PATCH] null_blk: serialize configfs attribute updates with device setup

Zizhi Wo <[email protected]>
Newsgroups org.kernel.vger.linux-block
Message-ID <[email protected]>
Hi Niklas,

在 2026/8/13 22:14, Niklas Cassel 写道:
> The attribute store methods generated with NULLB_DEVICE_ATTR() refuse to
> change the configuration of a live device by testing
> NULLB_DEV_FL_CONFIGURED, but that flag is only set by
> nullb_device_power_store() after null_add_dev() has returned, and the
> store methods take no lock at all. configfs only serializes writes to
> the same open file (buffer->mutex), so a write to any attribute can run
> concurrently with null_add_dev() and change the device configuration
> while it is being used.
> 
> null_add_dev() reads the configuration several times, e.g. dev->zoned is
> read once to set up the queue limits and once to initialize the zone
> resources:
> 
>    CPU0: echo 1 > nullb0/power         CPU1: echo 1 > nullb0/zoned
>    nullb_device_power_store()
>      mutex_lock(&lock)
>      null_add_dev()
>        if (dev->zoned) -> false
>          /* no BLK_FEAT_ZONED */       nullb_device_zoned_store()
>                                          test_bit(FL_CONFIGURED) -> 0
>                                          dev->zoned = true
>        blk_mq_alloc_disk()
>          /* queue is not zoned */
>        if (nullb->dev->zoned) -> true
>          null_register_zoned_dev()
>            blk_revalidate_disk_zones()
> 
> blk_revalidate_disk_zones() is then called for a queue that does not
> have BLK_FEAT_ZONED set, which triggers its WARN_ON_ONCE() and fails the
> device setup with -EIO:
> 
>    WARNING: CPU: 2 PID: 322 at block/blk-zoned.c:2357 blk_revalidate_disk_zones+0x4c/0x560
> 
> Clearing dev->zoned in the same window is worse: the queue is created
> with BLK_FEAT_ZONED but the zone resources are never initialized, so
> add_disk() succeeds for a zoned disk that has no zones. And a store that
> lands after the last dev->zoned test leaves dev->zoned set while
> dev->zones is still NULL, which null_process_zoned_cmd() dereferences on
> the first write.
> 
> Fix this by taking the global lock, which nullb_device_power_store()
> already holds across null_add_dev() and null_del_dev(), around both the
> NULLB_DEV_FL_CONFIGURED test and the update of the device configuration.
> The submit_queues and poll_queues apply callbacks are now called with
> that lock held, so remove the locking they did themselves.
> 
> Since the store methods can run as soon as configfs_register_subsystem()
> returns, that is, before null_init() gets to mutex_init(&lock), also
> initialize the lock statically with DEFINE_MUTEX().
> 
> Fixes: 3bf2bd20734e ("nullb: add configfs interface")
> Reported-by: [email protected]
> Closes: https://lore.kernel.org/linux-block/[email protected]/
> Signed-off-by: Niklas Cassel <[email protected]>
> ---
>   drivers/block/null_blk/main.c | 39 ++++++++++++++++-------------------
>   1 file changed, 18 insertions(+), 21 deletions(-)
> 
> diff --git a/drivers/block/null_blk/main.c b/drivers/block/null_blk/main.c
> index f8c0fd57e041..2e8f99873956 100644
> --- a/drivers/block/null_blk/main.c
> +++ b/drivers/block/null_blk/main.c
> @@ -66,7 +66,7 @@ struct nullb_page {
>   #define NULLB_PAGE_FREE (MAP_SZ - 2)
>   
>   static LIST_HEAD(nullb_list);
> -static struct mutex lock;
> +static DEFINE_MUTEX(lock);
>   static int null_major;
>   static DEFINE_IDA(nullb_indexes);
>   static struct blk_mq_tag_set tag_set;
> @@ -340,7 +340,15 @@ static ssize_t nullb_device_bool_attr_store(bool *val, const char *page,
>   	return count;
>   }
>   
> -/* The following macro should only be used with TYPE = {uint, ulong, bool}. */
> +/*
> + * The following macro should only be used with TYPE = {uint, ulong, bool}.
> + *
> + * The device configuration is modified under the global lock to serialize
> + * attribute changes against null_add_dev() and null_del_dev(): without this,
> + * an attribute could be changed while null_add_dev() is running, that is,
> + * before NULLB_DEV_FL_CONFIGURED is set, which would let null_add_dev()
> + * observe inconsistent values for the device configuration.
> + */
>   #define NULLB_DEVICE_ATTR(NAME, TYPE, APPLY)				\
>   static ssize_t								\
>   nullb_device_##NAME##_show(struct config_item *item, char *page)	\
> @@ -360,13 +368,16 @@ nullb_device_##NAME##_store(struct config_item *item, const char *page,	\
>   	ret = nullb_device_##TYPE##_attr_store(&new_value, page, count);\
>   	if (ret < 0)							\
>   		return ret;						\
> +	mutex_lock(&lock);						\
>   	if (apply_fn)							\
>   		ret = apply_fn(dev, new_value);				\
>   	else if (test_bit(NULLB_DEV_FL_CONFIGURED, &dev->flags)) 	\
>   		ret = -EBUSY;						\
> +	if (ret >= 0)							\
> +		dev->NAME = new_value;					\
> +	mutex_unlock(&lock);						\
>   	if (ret < 0)							\
>   		return ret;						\
> -	dev->NAME = new_value;						\
>   	return count;							\
>   }									\
>   CONFIGFS_ATTR(nullb_device_, NAME);
> @@ -379,6 +390,8 @@ static int nullb_update_nr_hw_queues(struct nullb_device *dev,
>   	struct blk_mq_tag_set *set;
>   	int ret, nr_hw_queues;
>   
> +	lockdep_assert_held(&lock);
> +
>   	if (!dev->nullb)
>   		return 0;
>   
> @@ -421,25 +434,13 @@ static int nullb_update_nr_hw_queues(struct nullb_device *dev,
>   static int nullb_apply_submit_queues(struct nullb_device *dev,
>   				     unsigned int submit_queues)
>   {
> -	int ret;
> -
> -	mutex_lock(&lock);
> -	ret = nullb_update_nr_hw_queues(dev, submit_queues, dev->poll_queues);
> -	mutex_unlock(&lock);
> -
> -	return ret;
> +	return nullb_update_nr_hw_queues(dev, submit_queues, dev->poll_queues);
>   }
>   
>   static int nullb_apply_poll_queues(struct nullb_device *dev,
>   				   unsigned int poll_queues)
>   {
> -	int ret;
> -
> -	mutex_lock(&lock);
> -	ret = nullb_update_nr_hw_queues(dev, dev->submit_queues, poll_queues);
> -	mutex_unlock(&lock);
> -
> -	return ret;
> +	return nullb_update_nr_hw_queues(dev, dev->submit_queues, poll_queues);
>   }
>   
>   NULLB_DEVICE_ATTR(size, ulong, NULL);
> @@ -2166,8 +2167,6 @@ static int __init null_init(void)
>   	if (ret)
>   		return ret;
>   
> -	mutex_init(&lock);
> -
>   	null_major = register_blkdev(0, "nullb");
>   	if (null_major < 0) {
>   		ret = null_major;
> @@ -2211,8 +2210,6 @@ static void __exit null_exit(void)
>   
>   	if (tag_set.ops)
>   		blk_mq_free_tag_set(&tag_set);
> -
> -	mutex_destroy(&lock);
>   }
>   
>   module_init(null_init);

Thanks for the patch. This issue has already been addressed in my 
null_blk series posted back in July:

https://lore.kernel.org/all/20260725022509.714271-1-
[email protected]/

This includes DEFINE_MUTEX and the serialization, but this series hasn't
been merged yet. It has already collected some Reviewed-by tags, and I'm
hoping it can be picked up for mainline soon.

Thanks,
Zizhi Wo
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.