block: fix queue freeze vs limits lock order in sysfs store methods
authorChristoph Hellwig <hch@lst.de>
Fri, 10 Jan 2025 05:47:14 +0000 (06:47 +0100)
committerJens Axboe <axboe@kernel.dk>
Fri, 10 Jan 2025 14:29:24 +0000 (07:29 -0700)
queue_attr_store() always freezes a device queue before calling the
attribute store operation. For attributes that control queue limits, the
store operation will also lock the queue limits with a call to
queue_limits_start_update(). However, some drivers (e.g. SCSI sd) may
need to issue commands to a device to obtain limit values from the
hardware with the queue limits locked. This creates a potential ABBA
deadlock situation if a user attempts to modify a limit (thus freezing
the device queue) while the device driver starts a revalidation of the
device queue limits.

Avoid such deadlock by not freezing the queue before calling the
->store_limit() method in struct queue_sysfs_entry and instead use the
queue_limits_commit_update_frozen helper to freeze the queue after taking
the limits lock.

This also removes taking the sysfs lock for the store_limit method as
it doesn't protect anything here, but creates even more nesting.
Hopefully it will go away from the actual sysfs methods entirely soon.

(commit log adapted from a similar patch from  Damien Le Moal)

Fixes: ff956a3be95b ("block: use queue_limits_commit_update in queue_discard_max_store")
Fixes: 0327ca9d53bf ("block: use queue_limits_commit_update in queue_max_sectors_store")
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Ming Lei <ming.lei@redhat.com>
Reviewed-by: Damien Le Moal <dlemoal@kernel.org>
Reviewed-by: Martin K. Petersen <martin.petersen@oracle.com>
Reviewed-by: Nilay Shroff <nilay@linux.ibm.com>
Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Link: https://lore.kernel.org/r/20250110054726.1499538-7-hch@lst.de
Signed-off-by: Jens Axboe <axboe@kernel.dk>
block/blk-sysfs.c

index d2aa2177e4ba5a2fba1d142892f6b3cc7576e16b..e828be777206bb818a09b7ce8901880b10ccfbde 100644 (file)
@@ -694,22 +694,24 @@ queue_attr_store(struct kobject *kobj, struct attribute *attr,
        if (entry->load_module)
                entry->load_module(disk, page, length);
 
-       mutex_lock(&q->sysfs_lock);
-       blk_mq_freeze_queue(q);
        if (entry->store_limit) {
                struct queue_limits lim = queue_limits_start_update(q);
 
                res = entry->store_limit(disk, page, length, &lim);
                if (res < 0) {
                        queue_limits_cancel_update(q);
-               } else {
-                       res = queue_limits_commit_update(q, &lim);
-                       if (!res)
-                               res = length;
+                       return res;
                }
-       } else {
-               res = entry->store(disk, page, length);
+
+               res = queue_limits_commit_update_frozen(q, &lim);
+               if (res)
+                       return res;
+               return length;
        }
+
+       mutex_lock(&q->sysfs_lock);
+       blk_mq_freeze_queue(q);
+       res = entry->store(disk, page, length);
        blk_mq_unfreeze_queue(q);
        mutex_unlock(&q->sysfs_lock);
        return res;