* [PATCH v5 01/27] libmultipath: Add initial framework
2026-07-27 14:42 [PATCH v5 00/27] Native SCSI Multipath support John Garry
@ 2026-07-27 14:42 ` John Garry
2026-07-27 15:05 ` sashiko-bot
2026-07-27 14:42 ` [PATCH v5 02/27] libmultipath: Add basic gendisk support John Garry
` (25 subsequent siblings)
26 siblings, 1 reply; 71+ messages in thread
From: John Garry @ 2026-07-27 14:42 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry, Hannes Reinecke
Add initial framework for libmultipath. libmultipath is a library for
multipath-capable block drivers, such as NVMe. The main function is to
support path management, path selection, and failover handling.
Basic support to add and remove the head structure - mpath_head - is
included.
This main purpose of this structure is to manage available paths and path
selection. It is quite similar to the multipath functionality in
nvme_ns_head. It also manages the multipath gendisk.
Each path is represented by the mpath_device structure. It should hold a
pointer to the per-path gendisk and also a list element for all siblings
of paths. For NVMe, there would be a mpath_device per nvme_ns.
All the libmultipath code is more or less taken from
drivers/nvme/host/multipath.c, which was originally authored by Christoph
Hellwig <hch@lst.de>.
Signed-off-by: John Garry <john.g.garry@oracle.com>
Reviewed-by: Hannes Reinecke <hare@kernel.org>
---
include/linux/multipath.h | 28 +++++++++++++++++
lib/Kconfig | 6 ++++
lib/Makefile | 2 ++
lib/multipath.c | 66 +++++++++++++++++++++++++++++++++++++++
4 files changed, 102 insertions(+)
create mode 100644 include/linux/multipath.h
create mode 100644 lib/multipath.c
diff --git a/include/linux/multipath.h b/include/linux/multipath.h
new file mode 100644
index 0000000000000..407d985cd31f5
--- /dev/null
+++ b/include/linux/multipath.h
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#ifndef _LIBMULTIPATH_H
+#define _LIBMULTIPATH_H
+
+#include <linux/blkdev.h>
+#include <linux/srcu.h>
+
+struct mpath_device {
+ struct list_head siblings;
+ struct gendisk *disk;
+};
+
+struct mpath_head {
+ struct srcu_struct srcu;
+ struct list_head dev_list; /* list of all mpath_devs */
+ struct mutex lock;
+
+ refcount_t refcount;
+
+ struct mpath_device __rcu *current_path[MAX_NUMNODES];
+};
+
+int mpath_get_head(struct mpath_head *mpath_head);
+void mpath_put_head(struct mpath_head *mpath_head);
+int mpath_head_init(struct mpath_head *mpath_head);
+void mpath_head_uninit(struct mpath_head *mpath_head);
+
+#endif // _LIBMULTIPATH_H
diff --git a/lib/Kconfig b/lib/Kconfig
index 55748b68714e0..d0258bef374a1 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -636,3 +636,9 @@ config UNION_FIND
config MIN_HEAP
bool
+
+config LIBMULTIPATH
+ bool "MULTIPATH BLOCK DRIVER LIBRARY"
+ depends on BLOCK
+ help
+ If you say yes here then you get a multipath lib for block drivers
diff --git a/lib/Makefile b/lib/Makefile
index 7f75cc6edf94a..7ba5e13be4171 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -334,3 +334,5 @@ CONTEXT_ANALYSIS_test_context-analysis.o := y
obj-$(CONFIG_CONTEXT_ANALYSIS_TEST) += test_context-analysis.o
subdir-$(CONFIG_FORTIFY_SOURCE) += test_fortify
+
+obj-$(CONFIG_LIBMULTIPATH) += multipath.o
diff --git a/lib/multipath.c b/lib/multipath.c
new file mode 100644
index 0000000000000..9cc398d266adf
--- /dev/null
+++ b/lib/multipath.c
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2017-2018 Christoph Hellwig.
+ * Copyright (c) 2026 Oracle and/or its affiliates.
+ */
+#include <linux/module.h>
+#include <linux/multipath.h>
+
+static struct workqueue_struct *mpath_wq;
+
+int mpath_get_head(struct mpath_head *mpath_head)
+{
+ if (!refcount_inc_not_zero(&mpath_head->refcount))
+ return -ENXIO;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mpath_get_head);
+
+void mpath_put_head(struct mpath_head *mpath_head)
+{
+ refcount_t *refcount = &mpath_head->refcount;
+
+ if (refcount_dec_and_test(&mpath_head->refcount))
+ wake_up_var(refcount);
+}
+EXPORT_SYMBOL_GPL(mpath_put_head);
+
+void mpath_head_uninit(struct mpath_head *mpath_head)
+{
+ refcount_t *refcount = &mpath_head->refcount;
+
+ if (!refcount_dec_and_test(refcount))
+ wait_var_event(refcount, !refcount_read(refcount));
+ cleanup_srcu_struct(&mpath_head->srcu);
+}
+EXPORT_SYMBOL_GPL(mpath_head_uninit);
+
+int mpath_head_init(struct mpath_head *mpath_head)
+{
+ memset(mpath_head, 0, sizeof(*mpath_head));
+ INIT_LIST_HEAD(&mpath_head->dev_list);
+ mutex_init(&mpath_head->lock);
+ refcount_set(&mpath_head->refcount, 1);
+
+ return init_srcu_struct(&mpath_head->srcu);
+}
+EXPORT_SYMBOL_GPL(mpath_head_init);
+
+static int __init mpath_init(void)
+{
+ mpath_wq = alloc_workqueue("mpath-wq",
+ WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_SYSFS, 0);
+ if (!mpath_wq)
+ return -ENOMEM;
+ return 0;
+}
+
+static void __exit mpath_exit(void)
+{
+ destroy_workqueue(mpath_wq);
+}
+
+module_init(mpath_init);
+module_exit(mpath_exit);
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("libmultipath");
--
2.43.7
^ permalink raw reply related [flat|nested] 71+ messages in thread* Re: [PATCH v5 01/27] libmultipath: Add initial framework
2026-07-27 14:42 ` [PATCH v5 01/27] libmultipath: Add initial framework John Garry
@ 2026-07-27 15:05 ` sashiko-bot
2026-07-27 15:12 ` John Garry
0 siblings, 1 reply; 71+ messages in thread
From: sashiko-bot @ 2026-07-27 15:05 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Low] The static workqueue `mpath_wq` is allocated but never used to queue work.
- [Low] Missing `#include <linux/wait_bit.h>` in `lib/multipath.c` causes a build issue.
--
--- Patch [1]: [PATCH v5 01/27] libmultipath: Add initial framework ---
commit 10e613ef081274ac617a43c354384f9ddc32f1f5
Author: John Garry <john.g.garry@oracle.com>
libmultipath: Add initial framework
Add initial framework for libmultipath to support path management, path
selection, and failover handling.
> diff --git a/lib/multipath.c b/lib/multipath.c
> new file mode 100644
> index 0000000000000..9cc398d266adf
> --- /dev/null
> +++ b/lib/multipath.c
[ ... ]
> +void mpath_put_head(struct mpath_head *mpath_head)
> +{
> + refcount_t *refcount = &mpath_head->refcount;
> +
> + if (refcount_dec_and_test(&mpath_head->refcount))
> + wake_up_var(refcount);
[Severity: Low]
Does this code build without including <linux/wait_bit.h>?
It appears that the wake_up_var() macro used here, and the wait_var_event()
macro used in mpath_head_uninit(), are defined in <linux/wait_bit.h>.
This header does not seem to be included directly or implicitly through
<linux/multipath.h> or <linux/module.h>.
> +}
> +EXPORT_SYMBOL_GPL(mpath_put_head);
[ ... ]
> +static int __init mpath_init(void)
> +{
> + mpath_wq = alloc_workqueue("mpath-wq",
> + WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_SYSFS, 0);
[Severity: Low]
Is it necessary to allocate this workqueue here?
It looks like the static variable mpath_wq is allocated and initialized, but
no work is ever actually queued to it. Could this allocation be removed
until it is needed?
> + if (!mpath_wq)
> + return -ENOMEM;
> + return 0;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727144234.3802948-1-john.g.garry@oracle.com?part=1
^ permalink raw reply [flat|nested] 71+ messages in thread* Re: [PATCH v5 01/27] libmultipath: Add initial framework
2026-07-27 15:05 ` sashiko-bot
@ 2026-07-27 15:12 ` John Garry
0 siblings, 0 replies; 71+ messages in thread
From: John Garry @ 2026-07-27 15:12 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 27/07/2026 16:05, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [Low] The static workqueue `mpath_wq` is allocated but never used to queue work.
> - [Low] Missing `#include <linux/wait_bit.h>` in `lib/multipath.c` causes a build issue.
> --
>
> --- Patch [1]: [PATCH v5 01/27] libmultipath: Add initial framework ---
> commit 10e613ef081274ac617a43c354384f9ddc32f1f5
> Author: John Garry <john.g.garry@oracle.com>
>
> libmultipath: Add initial framework
>
> Add initial framework for libmultipath to support path management, path
> selection, and failover handling.
>
>> diff --git a/lib/multipath.c b/lib/multipath.c
>> new file mode 100644
>> index 0000000000000..9cc398d266adf
>> --- /dev/null
>> +++ b/lib/multipath.c
> [ ... ]
>> +void mpath_put_head(struct mpath_head *mpath_head)
>> +{
>> + refcount_t *refcount = &mpath_head->refcount;
>> +
>> + if (refcount_dec_and_test(&mpath_head->refcount))
>> + wake_up_var(refcount);
>
> [Severity: Low]
> Does this code build without including <linux/wait_bit.h>?
>
> It appears that the wake_up_var() macro used here, and the wait_var_event()
> macro used in mpath_head_uninit(), are defined in <linux/wait_bit.h>.
> This header does not seem to be included directly or implicitly through
> <linux/multipath.h> or <linux/module.h>.
yes, it compiles
>
>> +}
>> +EXPORT_SYMBOL_GPL(mpath_put_head);
> [ ... ]
>> +static int __init mpath_init(void)
>> +{
>> + mpath_wq = alloc_workqueue("mpath-wq",
>> + WQ_UNBOUND | WQ_MEM_RECLAIM | WQ_SYSFS, 0);
>
> [Severity: Low]
> Is it necessary to allocate this workqueue here?
>
> It looks like the static variable mpath_wq is allocated and initialized, but
> no work is ever actually queued to it. Could this allocation be removed
> until it is needed?
it's used later
>
>> + if (!mpath_wq)
>> + return -ENOMEM;
>> + return 0;
>> +}
>
^ permalink raw reply [flat|nested] 71+ messages in thread
* [PATCH v5 02/27] libmultipath: Add basic gendisk support
2026-07-27 14:42 [PATCH v5 00/27] Native SCSI Multipath support John Garry
2026-07-27 14:42 ` [PATCH v5 01/27] libmultipath: Add initial framework John Garry
@ 2026-07-27 14:42 ` John Garry
2026-07-27 15:04 ` sashiko-bot
2026-07-27 14:42 ` [PATCH v5 03/27] libmultipath: Add path selection support John Garry
` (24 subsequent siblings)
26 siblings, 1 reply; 71+ messages in thread
From: John Garry @ 2026-07-27 14:42 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
Add support to allocate and free a multipath gendisk.
NVMe has almost like-for-like equivalents here:
- mpath_alloc_head_disk() -> nvme_mpath_alloc_disk()
- multipath_partition_scan_work() -> nvme_partition_scan_work()
- mpath_remove_disk() -> nvme_remove_head()
- mpath_device_set_live() -> nvme_mpath_set_live()
struct mpath_head_template is introduced as a method for drivers to
provide custom multipath functionality.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
include/linux/multipath.h | 38 ++++++++++++++
lib/multipath.c | 101 ++++++++++++++++++++++++++++++++++++++
2 files changed, 139 insertions(+)
diff --git a/include/linux/multipath.h b/include/linux/multipath.h
index 407d985cd31f5..ca6e02505be43 100644
--- a/include/linux/multipath.h
+++ b/include/linux/multipath.h
@@ -5,11 +5,19 @@
#include <linux/blkdev.h>
#include <linux/srcu.h>
+extern const struct block_device_operations mpath_ops;
+
struct mpath_device {
+ struct mpath_head *mpath_head;
struct list_head siblings;
struct gendisk *disk;
};
+struct mpath_head_template {
+};
+
+#define MPATH_HEAD_DISK_LIVE 0
+
struct mpath_head {
struct srcu_struct srcu;
struct list_head dev_list; /* list of all mpath_devs */
@@ -17,12 +25,42 @@ struct mpath_head {
refcount_t refcount;
+ unsigned long flags;
+ struct gendisk *disk;
+ struct work_struct partition_scan_work;
+ struct device *parent;
+ const struct attribute_group **disk_groups;
+ const struct mpath_head_template *mpdt;
struct mpath_device __rcu *current_path[MAX_NUMNODES];
};
+static inline struct mpath_head *mpath_bd_device_to_head(struct device *dev)
+{
+ return dev_get_drvdata(dev);
+}
+
+static inline struct mpath_head *mpath_gendisk_to_head(struct gendisk *disk)
+{
+ return mpath_bd_device_to_head(disk_to_dev(disk));
+}
+
int mpath_get_head(struct mpath_head *mpath_head);
void mpath_put_head(struct mpath_head *mpath_head);
int mpath_head_init(struct mpath_head *mpath_head);
void mpath_head_uninit(struct mpath_head *mpath_head);
+void mpath_put_disk(struct mpath_head *mpath_head);
+void mpath_remove_disk(struct mpath_head *mpath_head);
+int mpath_alloc_head_disk(struct mpath_head *mpath_head,
+ struct queue_limits *lim, int numa_node);
+void mpath_device_set_live(struct mpath_device *mpath_device);
+
+static inline bool is_mpath_disk(struct gendisk *disk)
+{
+ #if IS_ENABLED(CONFIG_LIBMULTIPATH)
+ return disk->fops == &mpath_ops;
+ #else
+ return false;
+ #endif
+}
#endif // _LIBMULTIPATH_H
diff --git a/lib/multipath.c b/lib/multipath.c
index 9cc398d266adf..d2e334259f3c1 100644
--- a/lib/multipath.c
+++ b/lib/multipath.c
@@ -35,6 +35,104 @@ void mpath_head_uninit(struct mpath_head *mpath_head)
}
EXPORT_SYMBOL_GPL(mpath_head_uninit);
+static int mpath_bdev_open(struct gendisk *disk, blk_mode_t mode)
+{
+ struct mpath_head *mpath_head = disk->private_data;
+
+ return mpath_get_head(mpath_head);
+}
+
+static void mpath_bdev_release(struct gendisk *disk)
+{
+ struct mpath_head *mpath_head = disk->private_data;
+
+ mpath_put_head(mpath_head);
+}
+
+const struct block_device_operations mpath_ops = {
+ .owner = THIS_MODULE,
+ .open = mpath_bdev_open,
+ .release = mpath_bdev_release,
+};
+EXPORT_SYMBOL_GPL(mpath_ops);
+
+static void multipath_partition_scan_work(struct work_struct *work)
+{
+ struct mpath_head *mpath_head =
+ container_of(work, struct mpath_head, partition_scan_work);
+
+ if (WARN_ON_ONCE(!test_and_clear_bit(GD_SUPPRESS_PART_SCAN,
+ &mpath_head->disk->state)))
+ return;
+
+ mutex_lock(&mpath_head->disk->open_mutex);
+ bdev_disk_changed(mpath_head->disk, false);
+ mutex_unlock(&mpath_head->disk->open_mutex);
+}
+
+void mpath_remove_disk(struct mpath_head *mpath_head)
+{
+ if (test_and_clear_bit(MPATH_HEAD_DISK_LIVE, &mpath_head->flags)) {
+ struct gendisk *disk = mpath_head->disk;
+
+ del_gendisk(disk);
+ }
+}
+EXPORT_SYMBOL_GPL(mpath_remove_disk);
+
+void mpath_put_disk(struct mpath_head *mpath_head)
+{
+ if (!mpath_head->disk)
+ return;
+
+ /* make sure all pending bios are cleaned up */
+ flush_work(&mpath_head->partition_scan_work);
+ put_disk(mpath_head->disk);
+ mpath_head->disk = NULL;
+}
+EXPORT_SYMBOL_GPL(mpath_put_disk);
+
+int mpath_alloc_head_disk(struct mpath_head *mpath_head,
+ struct queue_limits *lim, int numa_node)
+{
+ if (!mpath_head->disk_groups || !mpath_head->parent ||
+ mpath_head->disk)
+ return -EINVAL;
+
+ mpath_head->disk = blk_alloc_disk(lim, numa_node);
+ if (IS_ERR(mpath_head->disk))
+ return PTR_ERR(mpath_head->disk);
+
+ mpath_head->disk->private_data = mpath_head;
+ mpath_head->disk->fops = &mpath_ops;
+
+ set_bit(GD_SUPPRESS_PART_SCAN, &mpath_head->disk->state);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mpath_alloc_head_disk);
+
+void mpath_device_set_live(struct mpath_device *mpath_device)
+{
+ struct mpath_head *mpath_head = mpath_device->mpath_head;
+ int ret;
+
+ if (!mpath_head->disk)
+ return;
+
+ if (!test_and_set_bit(MPATH_HEAD_DISK_LIVE, &mpath_head->flags)) {
+ dev_set_drvdata(disk_to_dev(mpath_head->disk), mpath_head);
+ ret = device_add_disk(mpath_head->parent, mpath_head->disk,
+ mpath_head->disk_groups);
+ if (ret) {
+ clear_bit(MPATH_HEAD_DISK_LIVE, &mpath_head->flags);
+ return;
+ }
+ queue_work(mpath_wq, &mpath_head->partition_scan_work);
+ }
+}
+EXPORT_SYMBOL_GPL(mpath_device_set_live);
+
int mpath_head_init(struct mpath_head *mpath_head)
{
memset(mpath_head, 0, sizeof(*mpath_head));
@@ -42,6 +140,9 @@ int mpath_head_init(struct mpath_head *mpath_head)
mutex_init(&mpath_head->lock);
refcount_set(&mpath_head->refcount, 1);
+ INIT_WORK(&mpath_head->partition_scan_work,
+ multipath_partition_scan_work);
+
return init_srcu_struct(&mpath_head->srcu);
}
EXPORT_SYMBOL_GPL(mpath_head_init);
--
2.43.7
^ permalink raw reply related [flat|nested] 71+ messages in thread* Re: [PATCH v5 02/27] libmultipath: Add basic gendisk support
2026-07-27 14:42 ` [PATCH v5 02/27] libmultipath: Add basic gendisk support John Garry
@ 2026-07-27 15:04 ` sashiko-bot
2026-07-27 15:15 ` John Garry
0 siblings, 1 reply; 71+ messages in thread
From: sashiko-bot @ 2026-07-27 15:04 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] mpath_alloc_head_disk() leaves mpath_head->disk as an ERR_PTR on failure, leading to a kernel panic during cleanup in mpath_put_disk().
- [High] The allocated multipath disk unconditionally uses mpath_ops, which lacks a .submit_bio implementation, causing device_add_disk to fail and rendering the device incapable of processing I/O.
--
--- Patch [2]: [PATCH v5 02/27] libmultipath: Add basic gendisk support ---
commit 15c7ca249b473955f186aec1f0490e8ccef64672
Author: John Garry <john.g.garry@oracle.com>
libmultipath: Add basic gendisk support
Add support to allocate and free a multipath gendisk.
> diff --git a/lib/multipath.c b/lib/multipath.c
> index 9cc398d266adf..d2e334259f3c1 100644
> --- a/lib/multipath.c
> +++ b/lib/multipath.c
[ ... ]
> @@ -35,6 +35,104 @@ void mpath_head_uninit(struct mpath_head *mpath_head)
> }
> EXPORT_SYMBOL_GPL(mpath_head_uninit);
>
> +static int mpath_bdev_open(struct gendisk *disk, blk_mode_t mode)
> +{
> + struct mpath_head *mpath_head = disk->private_data;
> +
> + return mpath_get_head(mpath_head);
> +}
> +
> +static void mpath_bdev_release(struct gendisk *disk)
> +{
> + struct mpath_head *mpath_head = disk->private_data;
> +
> + mpath_put_head(mpath_head);
> +}
> +
> +const struct block_device_operations mpath_ops = {
> + .owner = THIS_MODULE,
> + .open = mpath_bdev_open,
> + .release = mpath_bdev_release,
> +};
[Severity: High]
Does this block device operations struct need a .submit_bio callback?
Since device_add_disk() in the block layer strictly enforces that bio-based
disks provide a .submit_bio callback, won't device_add_disk() unconditionally
return -EINVAL when mpath_device_set_live() is called?
Also, the helper is_mpath_disk() strictly requires disk->fops == &mpath_ops,
which prevents drivers from dynamically overriding the operations struct to
provide their own .submit_bio implementation.
> +EXPORT_SYMBOL_GPL(mpath_ops);
[ ... ]
> +void mpath_put_disk(struct mpath_head *mpath_head)
> +{
> + if (!mpath_head->disk)
> + return;
> +
> + /* make sure all pending bios are cleaned up */
> + flush_work(&mpath_head->partition_scan_work);
> + put_disk(mpath_head->disk);
> + mpath_head->disk = NULL;
> +}
> +EXPORT_SYMBOL_GPL(mpath_put_disk);
> +
> +int mpath_alloc_head_disk(struct mpath_head *mpath_head,
> + struct queue_limits *lim, int numa_node)
> +{
> + if (!mpath_head->disk_groups || !mpath_head->parent ||
> + mpath_head->disk)
> + return -EINVAL;
> +
> + mpath_head->disk = blk_alloc_disk(lim, numa_node);
> + if (IS_ERR(mpath_head->disk))
> + return PTR_ERR(mpath_head->disk);
[Severity: High]
If blk_alloc_disk() fails and returns an ERR_PTR, it is assigned to
mpath_head->disk. Does this cause a crash during cleanup?
If the caller handles this failure by tearing down the object via
mpath_put_disk():
void mpath_put_disk(struct mpath_head *mpath_head)
{
if (!mpath_head->disk)
return;
...
put_disk(mpath_head->disk);
}
The (!mpath_head->disk) check will evaluate to false for an ERR_PTR,
allowing the error pointer to be passed to put_disk(), which will dereference
it. Should mpath_head->disk be cleared to NULL before returning the error?
> +
> + mpath_head->disk->private_data = mpath_head;
> + mpath_head->disk->fops = &mpath_ops;
> +
> + set_bit(GD_SUPPRESS_PART_SCAN, &mpath_head->disk->state);
> +
> + return 0;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727144234.3802948-1-john.g.garry@oracle.com?part=2
^ permalink raw reply [flat|nested] 71+ messages in thread* Re: [PATCH v5 02/27] libmultipath: Add basic gendisk support
2026-07-27 15:04 ` sashiko-bot
@ 2026-07-27 15:15 ` John Garry
0 siblings, 0 replies; 71+ messages in thread
From: John Garry @ 2026-07-27 15:15 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 27/07/2026 16:04, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [High] mpath_alloc_head_disk() leaves mpath_head->disk as an ERR_PTR on failure, leading to a kernel panic during cleanup in mpath_put_disk().
> - [High] The allocated multipath disk unconditionally uses mpath_ops, which lacks a .submit_bio implementation, causing device_add_disk to fail and rendering the device incapable of processing I/O.
> --
>
> --- Patch [2]: [PATCH v5 02/27] libmultipath: Add basic gendisk support ---
> commit 15c7ca249b473955f186aec1f0490e8ccef64672
> Author: John Garry <john.g.garry@oracle.com>
>
> libmultipath: Add basic gendisk support
>
> Add support to allocate and free a multipath gendisk.
>
>> diff --git a/lib/multipath.c b/lib/multipath.c
>> index 9cc398d266adf..d2e334259f3c1 100644
>> --- a/lib/multipath.c
>> +++ b/lib/multipath.c
>
> [ ... ]
>
>> @@ -35,6 +35,104 @@ void mpath_head_uninit(struct mpath_head *mpath_head)
>> }
>> EXPORT_SYMBOL_GPL(mpath_head_uninit);
>>
>> +static int mpath_bdev_open(struct gendisk *disk, blk_mode_t mode)
>> +{
>> + struct mpath_head *mpath_head = disk->private_data;
>> +
>> + return mpath_get_head(mpath_head);
>> +}
>> +
>> +static void mpath_bdev_release(struct gendisk *disk)
>> +{
>> + struct mpath_head *mpath_head = disk->private_data;
>> +
>> + mpath_put_head(mpath_head);
>> +}
>> +
>> +const struct block_device_operations mpath_ops = {
>> + .owner = THIS_MODULE,
>> + .open = mpath_bdev_open,
>> + .release = mpath_bdev_release,
>> +};
>
> [Severity: High]
> Does this block device operations struct need a .submit_bio callback?
>
> Since device_add_disk() in the block layer strictly enforces that bio-based
> disks provide a .submit_bio callback, won't device_add_disk() unconditionally
> return -EINVAL when mpath_device_set_live() is called?
>
> Also, the helper is_mpath_disk() strictly requires disk->fops == &mpath_ops,
> which prevents drivers from dynamically overriding the operations struct to
> provide their own .submit_bio implementation.
>
>> +EXPORT_SYMBOL_GPL(mpath_ops);
>
> [ ... ]
>
>> +void mpath_put_disk(struct mpath_head *mpath_head)
>> +{
>> + if (!mpath_head->disk)
>> + return;
>> +
>> + /* make sure all pending bios are cleaned up */
>> + flush_work(&mpath_head->partition_scan_work);
>> + put_disk(mpath_head->disk);
>> + mpath_head->disk = NULL;
>> +}
>> +EXPORT_SYMBOL_GPL(mpath_put_disk);
>> +
>> +int mpath_alloc_head_disk(struct mpath_head *mpath_head,
>> + struct queue_limits *lim, int numa_node)
>> +{
>> + if (!mpath_head->disk_groups || !mpath_head->parent ||
>> + mpath_head->disk)
>> + return -EINVAL;
>> +
>> + mpath_head->disk = blk_alloc_disk(lim, numa_node);
>> + if (IS_ERR(mpath_head->disk))
>> + return PTR_ERR(mpath_head->disk);
>
> [Severity: High]
> If blk_alloc_disk() fails and returns an ERR_PTR, it is assigned to
> mpath_head->disk. Does this cause a crash during cleanup?
That's not an issue, but I can clear it
>
> If the caller handles this failure by tearing down the object via
> mpath_put_disk():
>
> void mpath_put_disk(struct mpath_head *mpath_head)
> {
> if (!mpath_head->disk)
> return;
> ...
> put_disk(mpath_head->disk);
> }
>
> The (!mpath_head->disk) check will evaluate to false for an ERR_PTR,
> allowing the error pointer to be passed to put_disk(), which will dereference
> it. Should mpath_head->disk be cleared to NULL before returning the error?
>
>> +
>> + mpath_head->disk->private_data = mpath_head;
>> + mpath_head->disk->fops = &mpath_ops;
>> +
>> + set_bit(GD_SUPPRESS_PART_SCAN, &mpath_head->disk->state);
>> +
>> + return 0;
>> +}
>
^ permalink raw reply [flat|nested] 71+ messages in thread
* [PATCH v5 03/27] libmultipath: Add path selection support
2026-07-27 14:42 [PATCH v5 00/27] Native SCSI Multipath support John Garry
2026-07-27 14:42 ` [PATCH v5 01/27] libmultipath: Add initial framework John Garry
2026-07-27 14:42 ` [PATCH v5 02/27] libmultipath: Add basic gendisk support John Garry
@ 2026-07-27 14:42 ` John Garry
2026-07-27 15:03 ` sashiko-bot
2026-07-27 14:42 ` [PATCH v5 04/27] libmultipath: Add bio handling John Garry
` (23 subsequent siblings)
26 siblings, 1 reply; 71+ messages in thread
From: John Garry @ 2026-07-27 14:42 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
From: John Garry <john.garry@linux.dev>
Add code for path selection.
NVMe ANA is abstracted into enum mpath_access_state. The motivation here is
so that SCSI ALUA can be used. Callbacks .is_disabled and .is_optimized
are added to get the path access state.
Path selection modes round-robin, NUMA, and queue-depth are added, same
as NVMe supports.
NVMe has almost like-for-like equivalents here:
- __mpath_find_path() -> __nvme_find_path()
- mpath_find_path() -> nvme_find_path()
and similar for all introduced callee functions.
Functions mpath_set_iopolicy() and mpath_get_iopolicy() are added for
setting default iopolicy.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
include/linux/multipath.h | 33 +++++
lib/multipath.c | 253 +++++++++++++++++++++++++++++++++++++-
2 files changed, 285 insertions(+), 1 deletion(-)
diff --git a/include/linux/multipath.h b/include/linux/multipath.h
index ca6e02505be43..9bbab0aa8961a 100644
--- a/include/linux/multipath.h
+++ b/include/linux/multipath.h
@@ -7,13 +7,31 @@
extern const struct block_device_operations mpath_ops;
+enum mpath_iopolicy_e {
+ MPATH_IOPOLICY_NUMA,
+ MPATH_IOPOLICY_RR,
+ MPATH_IOPOLICY_QD,
+};
+
+enum mpath_access_state {
+ MPATH_STATE_OPTIMIZED,
+ MPATH_STATE_NONOPTIMIZED,
+ MPATH_STATE_OTHER
+};
+
struct mpath_device {
struct mpath_head *mpath_head;
struct list_head siblings;
struct gendisk *disk;
+ int numa_node;
+ atomic_t *nr_active;
+ enum mpath_access_state access_state;
};
struct mpath_head_template {
+ bool (*is_disabled)(struct mpath_device *);
+ bool (*is_optimized)(struct mpath_device *);
+ const struct attribute_group **device_groups;
};
#define MPATH_HEAD_DISK_LIVE 0
@@ -25,6 +43,7 @@ struct mpath_head {
refcount_t refcount;
+ enum mpath_iopolicy_e *iopolicy;
unsigned long flags;
struct gendisk *disk;
struct work_struct partition_scan_work;
@@ -44,6 +63,14 @@ static inline struct mpath_head *mpath_gendisk_to_head(struct gendisk *disk)
return mpath_bd_device_to_head(disk_to_dev(disk));
}
+static inline enum mpath_iopolicy_e mpath_read_iopolicy(
+ struct mpath_head *mpath_head)
+{
+ return READ_ONCE(*mpath_head->iopolicy);
+}
+void mpath_synchronize(struct mpath_head *mpath_head);
+int mpath_set_iopolicy(const char *str, enum mpath_iopolicy_e *iopolicy);
+int mpath_get_iopolicy(char *buf, int iopolicy);
int mpath_get_head(struct mpath_head *mpath_head);
void mpath_put_head(struct mpath_head *mpath_head);
int mpath_head_init(struct mpath_head *mpath_head);
@@ -63,4 +90,10 @@ static inline bool is_mpath_disk(struct gendisk *disk)
return false;
#endif
}
+
+static inline bool mpath_qd_iopolicy(enum mpath_iopolicy_e *iopolicy)
+{
+ return READ_ONCE(*iopolicy) == MPATH_IOPOLICY_QD;
+}
+
#endif // _LIBMULTIPATH_H
diff --git a/lib/multipath.c b/lib/multipath.c
index d2e334259f3c1..2b22c74760868 100644
--- a/lib/multipath.c
+++ b/lib/multipath.c
@@ -6,8 +6,245 @@
#include <linux/module.h>
#include <linux/multipath.h>
+static struct mpath_device *mpath_find_path(struct mpath_head *mpath_head);
+
static struct workqueue_struct *mpath_wq;
+static const char * const mpath_iopolicy_names[] = {
+ [MPATH_IOPOLICY_NUMA] = "numa",
+ [MPATH_IOPOLICY_RR] = "round-robin",
+ [MPATH_IOPOLICY_QD] = "queue-depth",
+};
+
+static int mpath_iopolicy_parse(const char *str)
+{
+ return __sysfs_match_string(mpath_iopolicy_names,
+ ARRAY_SIZE(mpath_iopolicy_names), str);
+}
+
+int mpath_set_iopolicy(const char *str, enum mpath_iopolicy_e *iopolicy)
+{
+ int policy;
+
+ if (!str)
+ return -EINVAL;
+ policy = mpath_iopolicy_parse(str);
+ if (policy < 0)
+ return policy;
+ WRITE_ONCE(*iopolicy, policy);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mpath_set_iopolicy);
+
+int mpath_get_iopolicy(char *buf, int iopolicy)
+{
+ return sprintf(buf, "%s\n", mpath_iopolicy_names[iopolicy]);
+}
+EXPORT_SYMBOL_GPL(mpath_get_iopolicy);
+
+
+void mpath_synchronize(struct mpath_head *mpath_head)
+{
+ synchronize_srcu(&mpath_head->srcu);
+}
+EXPORT_SYMBOL_GPL(mpath_synchronize);
+
+static bool mpath_path_is_disabled(struct mpath_head *mpath_head,
+ struct mpath_device *mpath_device)
+{
+ return mpath_head->mpdt->is_disabled(mpath_device);
+}
+
+static struct mpath_device *__mpath_find_path(struct mpath_head *mpath_head,
+ int node)
+{
+ int found_distance = INT_MAX, fallback_distance = INT_MAX, distance;
+ struct mpath_device *found = NULL, *fallback = NULL, *mpath_device;
+
+ list_for_each_entry_srcu(mpath_device, &mpath_head->dev_list, siblings,
+ srcu_read_lock_held(&mpath_head->srcu)) {
+ if (mpath_path_is_disabled(mpath_head, mpath_device))
+ continue;
+
+ if (mpath_device->numa_node != NUMA_NO_NODE &&
+ (mpath_read_iopolicy(mpath_head) ==
+ MPATH_IOPOLICY_NUMA))
+ distance = node_distance(node,
+ mpath_device->numa_node);
+ else
+ distance = LOCAL_DISTANCE;
+
+ switch(mpath_device->access_state) {
+ case MPATH_STATE_OPTIMIZED:
+ if (distance < found_distance) {
+ found_distance = distance;
+ found = mpath_device;
+ }
+ break;
+ case MPATH_STATE_NONOPTIMIZED:
+ if (distance < fallback_distance) {
+ fallback_distance = distance;
+ fallback = mpath_device;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (!found)
+ found = fallback;
+
+ if (found)
+ rcu_assign_pointer(mpath_head->current_path[node], found);
+
+ return found;
+}
+
+static struct mpath_device *mpath_next_dev(struct mpath_head *mpath_head,
+ struct mpath_device *mpath_dev)
+{
+ mpath_dev = list_next_or_null_rcu(&mpath_head->dev_list,
+ &mpath_dev->siblings, struct mpath_device,
+ siblings);
+
+ if (mpath_dev)
+ return mpath_dev;
+ return list_first_or_null_rcu(&mpath_head->dev_list,
+ struct mpath_device, siblings);
+}
+
+static struct mpath_device *mpath_round_robin_path(
+ struct mpath_head *mpath_head)
+{
+ struct mpath_device *mpath_device, *found = NULL;
+ int node = numa_node_id();
+ enum mpath_access_state access_state_old;
+ struct mpath_device *old =
+ srcu_dereference(mpath_head->current_path[node],
+ &mpath_head->srcu);
+
+ if (unlikely(!old))
+ return __mpath_find_path(mpath_head, node);
+
+ if (list_is_singular(&mpath_head->dev_list)) {
+ if (mpath_path_is_disabled(mpath_head, old))
+ return NULL;
+ return old;
+ }
+
+ for (mpath_device = mpath_next_dev(mpath_head, old);
+ mpath_device && mpath_device != old;
+ mpath_device = mpath_next_dev(mpath_head, mpath_device)) {
+
+ if (mpath_path_is_disabled(mpath_head, mpath_device))
+ continue;
+ if (mpath_device->access_state == MPATH_STATE_OPTIMIZED) {
+ found = mpath_device;
+ goto out;
+ }
+ if (mpath_device->access_state == MPATH_STATE_NONOPTIMIZED)
+ found = mpath_device;
+ }
+
+ /*
+ * The loop above skips the current path for round-robin semantics.
+ * Fall back to the current path if either:
+ * - no other optimized path found and current is optimized,
+ * - no other usable path found and current is usable.
+ */
+ access_state_old = old->access_state;
+ if (!mpath_path_is_disabled(mpath_head, old) &&
+ (access_state_old == MPATH_STATE_OPTIMIZED ||
+ (!found && access_state_old == MPATH_STATE_NONOPTIMIZED)))
+ return old;
+
+ if (!found)
+ return NULL;
+out:
+ rcu_assign_pointer(mpath_head->current_path[node], found);
+
+ return found;
+}
+
+static struct mpath_device *mpath_queue_depth_path(
+ struct mpath_head *mpath_head)
+{
+ struct mpath_device *best_opt = NULL, *mpath_device;
+ struct mpath_device *best_nonopt = NULL;
+ unsigned int min_depth_opt = UINT_MAX, min_depth_nonopt = UINT_MAX;
+ unsigned int depth;
+
+ list_for_each_entry_srcu(mpath_device, &mpath_head->dev_list, siblings,
+ srcu_read_lock_held(&mpath_head->srcu)) {
+
+ if (mpath_path_is_disabled(mpath_head, mpath_device))
+ continue;
+
+ depth = atomic_read(mpath_device->nr_active);
+
+ switch (mpath_device->access_state) {
+ case MPATH_STATE_OPTIMIZED:
+ if (depth < min_depth_opt) {
+ min_depth_opt = depth;
+ best_opt = mpath_device;
+ }
+ break;
+ case MPATH_STATE_NONOPTIMIZED:
+ if (depth < min_depth_nonopt) {
+ min_depth_nonopt = depth;
+ best_nonopt = mpath_device;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (min_depth_opt == 0)
+ return best_opt;
+ }
+
+ return best_opt ? best_opt : best_nonopt;
+}
+
+static inline bool mpath_path_is_optimized(struct mpath_head *mpath_head,
+ struct mpath_device *mpath_device)
+{
+ return mpath_head->mpdt->is_optimized(mpath_device);
+}
+
+static struct mpath_device *mpath_numa_path(struct mpath_head *mpath_head)
+{
+ int node = numa_node_id();
+ struct mpath_device *mpath_device;
+
+ mpath_device = srcu_dereference(mpath_head->current_path[node],
+ &mpath_head->srcu);
+ if (unlikely(!mpath_device))
+ return __mpath_find_path(mpath_head, node);
+ if (unlikely(mpath_path_is_disabled(mpath_head, mpath_device)))
+ return __mpath_find_path(mpath_head, node);
+ if (unlikely(!mpath_path_is_optimized(mpath_head, mpath_device)))
+ return __mpath_find_path(mpath_head, node);
+ return mpath_device;
+}
+
+__maybe_unused
+static struct mpath_device *mpath_find_path(struct mpath_head *mpath_head)
+{
+ enum mpath_iopolicy_e iopolicy = mpath_read_iopolicy(mpath_head);
+
+ switch (iopolicy) {
+ case MPATH_IOPOLICY_QD:
+ return mpath_queue_depth_path(mpath_head);
+ case MPATH_IOPOLICY_RR:
+ return mpath_round_robin_path(mpath_head);
+ default:
+ return mpath_numa_path(mpath_head);
+ }
+}
+
int mpath_get_head(struct mpath_head *mpath_head)
{
if (!refcount_inc_not_zero(&mpath_head->refcount))
@@ -75,6 +312,7 @@ void mpath_remove_disk(struct mpath_head *mpath_head)
if (test_and_clear_bit(MPATH_HEAD_DISK_LIVE, &mpath_head->flags)) {
struct gendisk *disk = mpath_head->disk;
+ mpath_synchronize(mpath_head);
del_gendisk(disk);
}
}
@@ -96,7 +334,7 @@ int mpath_alloc_head_disk(struct mpath_head *mpath_head,
struct queue_limits *lim, int numa_node)
{
if (!mpath_head->disk_groups || !mpath_head->parent ||
- mpath_head->disk)
+ !mpath_head->iopolicy || mpath_head->disk)
return -EINVAL;
mpath_head->disk = blk_alloc_disk(lim, numa_node);
@@ -130,6 +368,19 @@ void mpath_device_set_live(struct mpath_device *mpath_device)
}
queue_work(mpath_wq, &mpath_head->partition_scan_work);
}
+
+ mutex_lock(&mpath_head->lock);
+ if (mpath_path_is_optimized(mpath_head, mpath_device)) {
+ int node, srcu_idx;
+
+ srcu_idx = srcu_read_lock(&mpath_head->srcu);
+ for_each_online_node(node)
+ __mpath_find_path(mpath_head, node);
+ srcu_read_unlock(&mpath_head->srcu, srcu_idx);
+ }
+ mutex_unlock(&mpath_head->lock);
+
+ mpath_synchronize(mpath_head);
}
EXPORT_SYMBOL_GPL(mpath_device_set_live);
--
2.43.7
^ permalink raw reply related [flat|nested] 71+ messages in thread* Re: [PATCH v5 03/27] libmultipath: Add path selection support
2026-07-27 14:42 ` [PATCH v5 03/27] libmultipath: Add path selection support John Garry
@ 2026-07-27 15:03 ` sashiko-bot
2026-07-27 15:20 ` John Garry
0 siblings, 1 reply; 71+ messages in thread
From: sashiko-bot @ 2026-07-27 15:03 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Round-robin path selection can enter an infinite loop under the SRCU read lock if the current path is concurrently removed.
- [High] If `blk_alloc_disk()` fails, an `ERR_PTR` is left in `mpath_head->disk`, which will cause a kernel panic during cleanup.
- [High] Missing requeue list flushes in `mpath_device_set_live` and `mpath_remove_disk` cause IO stalls and bio leaks.
- [High] `mpath_round_robin_path` incorrectly returns a deleted path when the path list becomes singular.
--
--- Patch [3]: [PATCH v5 03/27] libmultipath: Add path selection support ---
commit 88605e74a8a44a0f658b820940be9c7cdbbebc25
Author: John Garry <john.garry@linux.dev>
libmultipath: Add path selection support
This commit adds path selection support for NVMe ANA, abstracting it into
enum mpath_access_state. It introduces round-robin, NUMA, and queue-depth
selection modes along with iopolicy configuration functions.
> diff --git a/lib/multipath.c b/lib/multipath.c
> index d2e334259f3c1..2b22c74760868 100644
> --- a/lib/multipath.c
> +++ b/lib/multipath.c
[ ... ]
> +static struct mpath_device *mpath_round_robin_path(
> + struct mpath_head *mpath_head)
> +{
> + struct mpath_device *mpath_device, *found = NULL;
> + int node = numa_node_id();
> + enum mpath_access_state access_state_old;
> + struct mpath_device *old =
> + srcu_dereference(mpath_head->current_path[node],
> + &mpath_head->srcu);
> +
> + if (unlikely(!old))
> + return __mpath_find_path(mpath_head, node);
> +
> + if (list_is_singular(&mpath_head->dev_list)) {
> + if (mpath_path_is_disabled(mpath_head, old))
> + return NULL;
> + return old;
> + }
[Severity: High]
If the cached path (old) was concurrently unlinked from the list, is it
possible that list_is_singular() evaluates to true because there is exactly
one other valid path remaining? If so, does this mistakenly return the
unlinked old path (or NULL) instead of evaluating the single remaining valid
path?
> +
> + for (mpath_device = mpath_next_dev(mpath_head, old);
> + mpath_device && mpath_device != old;
> + mpath_device = mpath_next_dev(mpath_head, mpath_device)) {
[Severity: High]
If the currently cached path (old) is concurrently removed via
list_del_rcu(), it is unlinked from dev_list. Since mpath_next_dev()
iterates through the list, will this loop fail to encounter the unlinked
old pointer? If all remaining paths are non-optimized or disabled, could
this result in an infinite loop inside an SRCU read-side critical section?
> +
> + if (mpath_path_is_disabled(mpath_head, mpath_device))
> + continue;
[ ... ]
> @@ -75,6 +312,7 @@ void mpath_remove_disk(struct mpath_head *mpath_head)
> if (test_and_clear_bit(MPATH_HEAD_DISK_LIVE, &mpath_head->flags)) {
> struct gendisk *disk = mpath_head->disk;
>
> + mpath_synchronize(mpath_head);
> del_gendisk(disk);
> }
[Severity: High]
When the disk is removed, are there potentially stranded bios left in the
mpath_head->requeue_list? Without a call to mpath_schedule_requeue_work()
here to flush the list, do these queued bios leak?
> }
[ ... ]
> @@ -96,7 +334,7 @@ int mpath_alloc_head_disk(struct mpath_head *mpath_head,
> struct queue_limits *lim, int numa_node)
> {
> if (!mpath_head->disk_groups || !mpath_head->parent ||
> - mpath_head->disk)
> + !mpath_head->iopolicy || mpath_head->disk)
> return -EINVAL;
>
> mpath_head->disk = blk_alloc_disk(lim, numa_node);
[Severity: High]
If blk_alloc_disk() fails, does it leave an ERR_PTR in mpath_head->disk?
If the error pointer is not cleared here, would the cleanup path in
mpath_put_disk() mistakenly evaluate "if (!mpath_head->disk)" as false
and attempt to call put_disk() on the error pointer?
[ ... ]
> @@ -130,6 +368,19 @@ void mpath_device_set_live(struct mpath_device *mpath_device)
> }
> queue_work(mpath_wq, &mpath_head->partition_scan_work);
> }
> +
> + mutex_lock(&mpath_head->lock);
> + if (mpath_path_is_optimized(mpath_head, mpath_device)) {
> + int node, srcu_idx;
> +
> + srcu_idx = srcu_read_lock(&mpath_head->srcu);
> + for_each_online_node(node)
> + __mpath_find_path(mpath_head, node);
> + srcu_read_unlock(&mpath_head->srcu, srcu_idx);
> + }
> + mutex_unlock(&mpath_head->lock);
> +
> + mpath_synchronize(mpath_head);
> }
[Severity: High]
When a new path becomes available and transitions to live, does this function
need to call mpath_schedule_requeue_work()? If bios were held in the
requeue_list while no paths were available, could omitting this flush lead
to permanent I/O stalls?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727144234.3802948-1-john.g.garry@oracle.com?part=3
^ permalink raw reply [flat|nested] 71+ messages in thread* Re: [PATCH v5 03/27] libmultipath: Add path selection support
2026-07-27 15:03 ` sashiko-bot
@ 2026-07-27 15:20 ` John Garry
0 siblings, 0 replies; 71+ messages in thread
From: John Garry @ 2026-07-27 15:20 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 27/07/2026 16:03, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
> - [High] Round-robin path selection can enter an infinite loop under the SRCU read lock if the current path is concurrently removed.
> - [High] If `blk_alloc_disk()` fails, an `ERR_PTR` is left in `mpath_head->disk`, which will cause a kernel panic during cleanup.
> - [High] Missing requeue list flushes in `mpath_device_set_live` and `mpath_remove_disk` cause IO stalls and bio leaks.
> - [High] `mpath_round_robin_path` incorrectly returns a deleted path when the path list becomes singular.
> --
>
> --- Patch [3]: [PATCH v5 03/27] libmultipath: Add path selection support ---
> commit 88605e74a8a44a0f658b820940be9c7cdbbebc25
> Author: John Garry <john.garry@linux.dev>
>
> libmultipath: Add path selection support
>
> This commit adds path selection support for NVMe ANA, abstracting it into
> enum mpath_access_state. It introduces round-robin, NUMA, and queue-depth
> selection modes along with iopolicy configuration functions.
>
>> diff --git a/lib/multipath.c b/lib/multipath.c
>> index d2e334259f3c1..2b22c74760868 100644
>> --- a/lib/multipath.c
>> +++ b/lib/multipath.c
>
> [ ... ]
>
>> +static struct mpath_device *mpath_round_robin_path(
>> + struct mpath_head *mpath_head)
>> +{
>> + struct mpath_device *mpath_device, *found = NULL;
>> + int node = numa_node_id();
>> + enum mpath_access_state access_state_old;
>> + struct mpath_device *old =
>> + srcu_dereference(mpath_head->current_path[node],
>> + &mpath_head->srcu);
>> +
>> + if (unlikely(!old))
>> + return __mpath_find_path(mpath_head, node);
>> +
>> + if (list_is_singular(&mpath_head->dev_list)) {
>> + if (mpath_path_is_disabled(mpath_head, old))
>> + return NULL;
>> + return old;
>> + }
>
> [Severity: High]
> If the cached path (old) was concurrently unlinked from the list, is it
> possible that list_is_singular() evaluates to true because there is exactly
> one other valid path remaining? If so, does this mistakenly return the
> unlinked old path (or NULL) instead of evaluating the single remaining valid
> path?
>
>> +
>> + for (mpath_device = mpath_next_dev(mpath_head, old);
>> + mpath_device && mpath_device != old;
>> + mpath_device = mpath_next_dev(mpath_head, mpath_device)) {
>
> [Severity: High]
> If the currently cached path (old) is concurrently removed via
> list_del_rcu(), it is unlinked from dev_list. Since mpath_next_dev()
> iterates through the list, will this loop fail to encounter the unlinked
> old pointer? If all remaining paths are non-optimized or disabled, could
> this result in an infinite loop inside an SRCU read-side critical section?
locking is added later, which is being ignored
>
>> +
>> + if (mpath_path_is_disabled(mpath_head, mpath_device))
>> + continue;
>
> [ ... ]
>
>> @@ -75,6 +312,7 @@ void mpath_remove_disk(struct mpath_head *mpath_head)
>> if (test_and_clear_bit(MPATH_HEAD_DISK_LIVE, &mpath_head->flags)) {
>> struct gendisk *disk = mpath_head->disk;
>>
>> + mpath_synchronize(mpath_head);
>> del_gendisk(disk);
>> }
>
> [Severity: High]
> When the disk is removed, are there potentially stranded bios left in the
> mpath_head->requeue_list? Without a call to mpath_schedule_requeue_work()
> here to flush the list, do these queued bios leak?
that is done elsewhere
>
>> }
>
> [ ... ]
>
>> @@ -96,7 +334,7 @@ int mpath_alloc_head_disk(struct mpath_head *mpath_head,
>> struct queue_limits *lim, int numa_node)
>> {
>> if (!mpath_head->disk_groups || !mpath_head->parent ||
>> - mpath_head->disk)
>> + !mpath_head->iopolicy || mpath_head->disk)
>> return -EINVAL;
>>
>> mpath_head->disk = blk_alloc_disk(lim, numa_node);
>
> [Severity: High]
> If blk_alloc_disk() fails, does it leave an ERR_PTR in mpath_head->disk?
> If the error pointer is not cleared here, would the cleanup path in
> mpath_put_disk() mistakenly evaluate "if (!mpath_head->disk)" as false
> and attempt to call put_disk() on the error pointer?
already commented on
>
> [ ... ]
>
>> @@ -130,6 +368,19 @@ void mpath_device_set_live(struct mpath_device *mpath_device)
>> }
>> queue_work(mpath_wq, &mpath_head->partition_scan_work);
>> }
>> +
>> + mutex_lock(&mpath_head->lock);
>> + if (mpath_path_is_optimized(mpath_head, mpath_device)) {
>> + int node, srcu_idx;
>> +
>> + srcu_idx = srcu_read_lock(&mpath_head->srcu);
>> + for_each_online_node(node)
>> + __mpath_find_path(mpath_head, node);
>> + srcu_read_unlock(&mpath_head->srcu, srcu_idx);
>> + }
>> + mutex_unlock(&mpath_head->lock);
>> +
>> + mpath_synchronize(mpath_head);
>> }
>
> [Severity: High]
> When a new path becomes available and transitions to live, does this function
> need to call mpath_schedule_requeue_work()? If bios were held in the
> requeue_list while no paths were available, could omitting this flush lead
> to permanent I/O stalls?
I don't think so
>
^ permalink raw reply [flat|nested] 71+ messages in thread
* [PATCH v5 04/27] libmultipath: Add bio handling
2026-07-27 14:42 [PATCH v5 00/27] Native SCSI Multipath support John Garry
` (2 preceding siblings ...)
2026-07-27 14:42 ` [PATCH v5 03/27] libmultipath: Add path selection support John Garry
@ 2026-07-27 14:42 ` John Garry
2026-07-27 14:42 ` [PATCH v5 05/27] libmultipath: Add support for mpath_device management John Garry
` (22 subsequent siblings)
26 siblings, 0 replies; 71+ messages in thread
From: John Garry @ 2026-07-27 14:42 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
Add support to submit a bio per-path. In addition, for failover, add
support to requeue a failed bio.
NVMe has almost like-for-like equivalents here:
- nvme_available_path() -> mpath_available_path()
- nvme_requeue_work() -> mpath_requeue_work()
- nvme_ns_head_submit_bio() -> mpath_bdev_submit_bio()
For failover, a driver may want to re-submit a bio, so add support to
clone a bio prior to submission.
A bio which is submitted to a per-path device has flag REQ_MPATH set,
same as what is done for NVMe with REQ_NVME_MPATH.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
include/linux/multipath.h | 22 ++++++++
lib/multipath.c | 108 +++++++++++++++++++++++++++++++++++++-
2 files changed, 128 insertions(+), 2 deletions(-)
diff --git a/include/linux/multipath.h b/include/linux/multipath.h
index 9bbab0aa8961a..374f0531f7e07 100644
--- a/include/linux/multipath.h
+++ b/include/linux/multipath.h
@@ -3,6 +3,7 @@
#define _LIBMULTIPATH_H
#include <linux/blkdev.h>
+#include <linux/blk-mq.h>
#include <linux/srcu.h>
extern const struct block_device_operations mpath_ops;
@@ -29,8 +30,10 @@ struct mpath_device {
};
struct mpath_head_template {
+ bool (*available_path)(struct mpath_device *);
bool (*is_disabled)(struct mpath_device *);
bool (*is_optimized)(struct mpath_device *);
+ struct bio *(*clone_bio)(struct bio *);
const struct attribute_group **device_groups;
};
@@ -44,6 +47,14 @@ struct mpath_head {
refcount_t refcount;
enum mpath_iopolicy_e *iopolicy;
+
+ struct bio_list requeue_list; /* list for requeing bio */
+ spinlock_t requeue_lock;
+ struct work_struct requeue_work; /* work struct for requeue */
+
+ atomic_long_t requeue_no_usable_path_cnt;
+ atomic_long_t fail_no_avail_path_cnt;
+
unsigned long flags;
struct gendisk *disk;
struct work_struct partition_scan_work;
@@ -53,6 +64,13 @@ struct mpath_head {
struct mpath_device __rcu *current_path[MAX_NUMNODES];
};
+#define REQ_MPATH REQ_DRV
+
+static inline bool is_mpath_request(struct request *req)
+{
+ return req->cmd_flags & REQ_MPATH;
+}
+
static inline struct mpath_head *mpath_bd_device_to_head(struct device *dev)
{
return dev_get_drvdata(dev);
@@ -96,4 +114,8 @@ static inline bool mpath_qd_iopolicy(enum mpath_iopolicy_e *iopolicy)
return READ_ONCE(*iopolicy) == MPATH_IOPOLICY_QD;
}
+static inline void mpath_schedule_requeue_work(struct mpath_head *mpath_head)
+{
+ kblockd_schedule_work(&mpath_head->requeue_work);
+}
#endif // _LIBMULTIPATH_H
diff --git a/lib/multipath.c b/lib/multipath.c
index 2b22c74760868..db5f83ebe5d7f 100644
--- a/lib/multipath.c
+++ b/lib/multipath.c
@@ -5,6 +5,7 @@
*/
#include <linux/module.h>
#include <linux/multipath.h>
+#include <trace/events/block.h>
static struct mpath_device *mpath_find_path(struct mpath_head *mpath_head);
@@ -43,7 +44,6 @@ int mpath_get_iopolicy(char *buf, int iopolicy)
}
EXPORT_SYMBOL_GPL(mpath_get_iopolicy);
-
void mpath_synchronize(struct mpath_head *mpath_head)
{
synchronize_srcu(&mpath_head->srcu);
@@ -230,7 +230,6 @@ static struct mpath_device *mpath_numa_path(struct mpath_head *mpath_head)
return mpath_device;
}
-__maybe_unused
static struct mpath_device *mpath_find_path(struct mpath_head *mpath_head)
{
enum mpath_iopolicy_e iopolicy = mpath_read_iopolicy(mpath_head);
@@ -245,6 +244,81 @@ static struct mpath_device *mpath_find_path(struct mpath_head *mpath_head)
}
}
+static bool mpath_available_path(struct mpath_head *mpath_head)
+{
+ struct mpath_device *mpath_device;
+
+ if (!test_bit(MPATH_HEAD_DISK_LIVE, &mpath_head->flags))
+ return false;
+
+ list_for_each_entry_srcu(mpath_device, &mpath_head->dev_list, siblings,
+ srcu_read_lock_held(&mpath_head->srcu)) {
+ if (mpath_head->mpdt->available_path(mpath_device))
+ return true;
+ }
+
+ return false;
+}
+
+static void mpath_bdev_submit_bio(struct bio *bio)
+{
+ struct mpath_head *mpath_head = bio->bi_bdev->bd_disk->private_data;
+ struct device *dev = mpath_head->parent;
+ struct mpath_device *mpath_device;
+ int srcu_idx;
+
+ /*
+ * The mpath_device might be going away and the bio might be moved to a
+ * different queue in failover, so we need to use the bio_split
+ * pool from the original queue to allocate the bvecs from.
+ */
+ bio = bio_split_to_limits(bio);
+ if (!bio)
+ return;
+
+ srcu_idx = srcu_read_lock(&mpath_head->srcu);
+ mpath_device = mpath_find_path(mpath_head);
+
+ if (likely(mpath_device)) {
+ if (mpath_head->mpdt->clone_bio) {
+ struct bio *orig = bio;
+
+ bio = mpath_head->mpdt->clone_bio(bio);
+ if (!bio) {
+ bio_io_error(orig);
+ goto out;
+ }
+ }
+
+ bio_set_dev(bio, mpath_device->disk->part0);
+ /*
+ * Use BIO_REMAPPED to skip bio_check_eod() when this bio
+ * enters submit_bio_noacct() for the per-path device. The EOD
+ * check already passed on the multipath head.
+ */
+ bio_set_flag(bio, BIO_REMAPPED);
+ bio->bi_opf |= REQ_MPATH;
+ trace_block_bio_remap(bio, disk_devt(mpath_head->disk),
+ bio->bi_iter.bi_sector);
+ submit_bio_noacct(bio);
+ } else if (mpath_available_path(mpath_head)) {
+ dev_warn_ratelimited(dev, "no usable path - requeuing I/O\n");
+
+ spin_lock_irq(&mpath_head->requeue_lock);
+ bio_list_add(&mpath_head->requeue_list, bio);
+ spin_unlock_irq(&mpath_head->requeue_lock);
+ atomic_long_inc(&mpath_head->requeue_no_usable_path_cnt);
+ } else {
+ dev_warn_ratelimited(dev, "no available path - failing I/O\n");
+
+ bio_io_error(bio);
+ atomic_long_inc(&mpath_head->fail_no_avail_path_cnt);
+ }
+
+out:
+ srcu_read_unlock(&mpath_head->srcu, srcu_idx);
+}
+
int mpath_get_head(struct mpath_head *mpath_head)
{
if (!refcount_inc_not_zero(&mpath_head->refcount))
@@ -290,6 +364,7 @@ const struct block_device_operations mpath_ops = {
.owner = THIS_MODULE,
.open = mpath_bdev_open,
.release = mpath_bdev_release,
+ .submit_bio = mpath_bdev_submit_bio,
};
EXPORT_SYMBOL_GPL(mpath_ops);
@@ -307,12 +382,35 @@ static void multipath_partition_scan_work(struct work_struct *work)
mutex_unlock(&mpath_head->disk->open_mutex);
}
+static void mpath_requeue_work(struct work_struct *work)
+{
+ struct mpath_head *mpath_head =
+ container_of(work, struct mpath_head, requeue_work);
+ struct bio *bio, *next;
+
+ spin_lock_irq(&mpath_head->requeue_lock);
+ next = bio_list_get(&mpath_head->requeue_list);
+ spin_unlock_irq(&mpath_head->requeue_lock);
+
+ while ((bio = next) != NULL) {
+ next = bio->bi_next;
+ bio->bi_next = NULL;
+ submit_bio_noacct(bio);
+ }
+}
+
void mpath_remove_disk(struct mpath_head *mpath_head)
{
if (test_and_clear_bit(MPATH_HEAD_DISK_LIVE, &mpath_head->flags)) {
struct gendisk *disk = mpath_head->disk;
+ /*
+ * requeue I/O after MPATH_HEAD_DISK_LIVE has been cleared
+ * to allow multipath to fail all I/O.
+ */
mpath_synchronize(mpath_head);
+ mpath_schedule_requeue_work(mpath_head);
+
del_gendisk(disk);
}
}
@@ -324,6 +422,8 @@ void mpath_put_disk(struct mpath_head *mpath_head)
return;
/* make sure all pending bios are cleaned up */
+ kblockd_schedule_work(&mpath_head->requeue_work);
+ flush_work(&mpath_head->requeue_work);
flush_work(&mpath_head->partition_scan_work);
put_disk(mpath_head->disk);
mpath_head->disk = NULL;
@@ -381,6 +481,7 @@ void mpath_device_set_live(struct mpath_device *mpath_device)
mutex_unlock(&mpath_head->lock);
mpath_synchronize(mpath_head);
+ mpath_schedule_requeue_work(mpath_head);
}
EXPORT_SYMBOL_GPL(mpath_device_set_live);
@@ -393,6 +494,9 @@ int mpath_head_init(struct mpath_head *mpath_head)
INIT_WORK(&mpath_head->partition_scan_work,
multipath_partition_scan_work);
+ INIT_WORK(&mpath_head->requeue_work, mpath_requeue_work);
+ spin_lock_init(&mpath_head->requeue_lock);
+ bio_list_init(&mpath_head->requeue_list);
return init_srcu_struct(&mpath_head->srcu);
}
--
2.43.7
^ permalink raw reply related [flat|nested] 71+ messages in thread* [PATCH v5 05/27] libmultipath: Add support for mpath_device management
2026-07-27 14:42 [PATCH v5 00/27] Native SCSI Multipath support John Garry
` (3 preceding siblings ...)
2026-07-27 14:42 ` [PATCH v5 04/27] libmultipath: Add bio handling John Garry
@ 2026-07-27 14:42 ` John Garry
2026-07-27 15:03 ` sashiko-bot
2026-07-27 14:42 ` [PATCH v5 06/27] libmultipath: Add delayed removal support John Garry
` (21 subsequent siblings)
26 siblings, 1 reply; 71+ messages in thread
From: John Garry @ 2026-07-27 14:42 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
Add support to add or remove a mpath_device as a path.
NVMe has almost like-for-like equivalents here:
- nvme_mpath_clear_current_path() -> mpath_clear_current_path()
- nvme_mpath_add_sysfs_link() -> mpath_add_sysfs_link()
- nvme_mpath_remove_sysfs_link() -> mpath_remove_sysfs_link()
- nvme_mpath_revalidate_paths() -> mpath_revalidate_paths()
The functionality in mpath_clear_paths() and mpath_synchronize() have the
same pattern which is frequently used in the NVMe code.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
include/linux/multipath.h | 12 +++
lib/multipath.c | 154 ++++++++++++++++++++++++++++++++++++++
2 files changed, 166 insertions(+)
diff --git a/include/linux/multipath.h b/include/linux/multipath.h
index 374f0531f7e07..033ee8d54bbe9 100644
--- a/include/linux/multipath.h
+++ b/include/linux/multipath.h
@@ -20,10 +20,13 @@ enum mpath_access_state {
MPATH_STATE_OTHER
};
+#define MPATH_DEVICE_SYSFS_ATTR_LINK 0
+
struct mpath_device {
struct mpath_head *mpath_head;
struct list_head siblings;
struct gendisk *disk;
+ unsigned long flags;
int numa_node;
atomic_t *nr_active;
enum mpath_access_state access_state;
@@ -89,6 +92,15 @@ static inline enum mpath_iopolicy_e mpath_read_iopolicy(
void mpath_synchronize(struct mpath_head *mpath_head);
int mpath_set_iopolicy(const char *str, enum mpath_iopolicy_e *iopolicy);
int mpath_get_iopolicy(char *buf, int iopolicy);
+bool mpath_clear_current_path(struct mpath_device *mpath_device);
+int mpath_add_device(struct mpath_device *mpath_device, struct gendisk *disk,
+ int numa_node, atomic_t *nr_active);
+bool mpath_delete_device(struct mpath_device *mpath_device);
+bool mpath_head_devices_empty(struct mpath_head *mpath_head);
+void mpath_clear_paths(struct mpath_head *mpath_head);
+void mpath_revalidate_paths(struct mpath_head *mpath_head);
+void mpath_add_sysfs_link(struct mpath_head *mpath_head);
+void mpath_remove_sysfs_link(struct mpath_device *mpath_device);
int mpath_get_head(struct mpath_head *mpath_head);
void mpath_put_head(struct mpath_head *mpath_head);
int mpath_head_init(struct mpath_head *mpath_head);
diff --git a/lib/multipath.c b/lib/multipath.c
index db5f83ebe5d7f..f70049b6cede9 100644
--- a/lib/multipath.c
+++ b/lib/multipath.c
@@ -50,6 +50,87 @@ void mpath_synchronize(struct mpath_head *mpath_head)
}
EXPORT_SYMBOL_GPL(mpath_synchronize);
+int mpath_add_device(struct mpath_device *mpath_device, struct gendisk *disk,
+ int numa_node, atomic_t *nr_active)
+{
+ struct mpath_head *mpath_head = mpath_device->mpath_head;
+
+ if (!disk || !nr_active)
+ return -EINVAL;
+
+ mpath_device->disk = disk;
+ mpath_device->numa_node = numa_node;
+ mpath_device->nr_active = nr_active;
+ mutex_lock(&mpath_head->lock);
+ list_add_tail_rcu(&mpath_device->siblings, &mpath_head->dev_list);
+ mutex_unlock(&mpath_head->lock);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(mpath_add_device);
+
+bool mpath_delete_device(struct mpath_device *mpath_device)
+{
+ bool empty;
+
+ mutex_lock(&mpath_device->mpath_head->lock);
+ list_del_rcu(&mpath_device->siblings);
+ empty = list_empty(&mpath_device->mpath_head->dev_list);
+ mutex_unlock(&mpath_device->mpath_head->lock);
+
+ mpath_synchronize(mpath_device->mpath_head);
+
+ return empty;
+}
+EXPORT_SYMBOL_GPL(mpath_delete_device);
+
+bool mpath_head_devices_empty(struct mpath_head *mpath_head)
+{
+ bool empty;
+
+ mutex_lock(&mpath_head->lock);
+ empty = list_empty(&mpath_head->dev_list);
+ mutex_unlock(&mpath_head->lock);
+
+ return empty;
+}
+EXPORT_SYMBOL_GPL(mpath_head_devices_empty);
+
+bool mpath_clear_current_path(struct mpath_device *mpath_device)
+{
+ struct mpath_head *mpath_head = mpath_device->mpath_head;
+ bool changed = false;
+ int node;
+
+ for_each_node(node) {
+ if (mpath_device ==
+ rcu_access_pointer(mpath_head->current_path[node])) {
+ rcu_assign_pointer(mpath_head->current_path[node],
+ NULL);
+ changed = true;
+ }
+ }
+
+ return changed;
+}
+EXPORT_SYMBOL_GPL(mpath_clear_current_path);
+
+void mpath_clear_paths(struct mpath_head *mpath_head)
+{
+ int node;
+
+ for_each_node(node)
+ rcu_assign_pointer(mpath_head->current_path[node], NULL);
+}
+EXPORT_SYMBOL_GPL(mpath_clear_paths);
+
+void mpath_revalidate_paths(struct mpath_head *mpath_head)
+{
+ mpath_clear_paths(mpath_head);
+ mpath_schedule_requeue_work(mpath_head);
+}
+EXPORT_SYMBOL_GPL(mpath_revalidate_paths);
+
static bool mpath_path_is_disabled(struct mpath_head *mpath_head,
struct mpath_device *mpath_device)
{
@@ -469,6 +550,8 @@ void mpath_device_set_live(struct mpath_device *mpath_device)
queue_work(mpath_wq, &mpath_head->partition_scan_work);
}
+ mpath_add_sysfs_link(mpath_head);
+
mutex_lock(&mpath_head->lock);
if (mpath_path_is_optimized(mpath_head, mpath_device)) {
int node, srcu_idx;
@@ -485,6 +568,77 @@ void mpath_device_set_live(struct mpath_device *mpath_device)
}
EXPORT_SYMBOL_GPL(mpath_device_set_live);
+void mpath_add_sysfs_link(struct mpath_head *mpath_head)
+{
+ struct device *target;
+ struct device *source;
+ int rc, srcu_idx;
+ struct kobject *mpath_gd_kobj;
+ struct mpath_device *mpath_device;
+
+ /*
+ * Ensure head disk node is already added otherwise we may get invalid
+ * kobj for head disk node
+ */
+ if (!test_bit(GD_ADDED, &mpath_head->disk->state))
+ return;
+
+ mpath_gd_kobj = &disk_to_dev(mpath_head->disk)->kobj;
+ srcu_idx = srcu_read_lock(&mpath_head->srcu);
+
+ list_for_each_entry_srcu(mpath_device, &mpath_head->dev_list, siblings,
+ srcu_read_lock_held(&mpath_head->srcu)) {
+ if (!test_bit(GD_ADDED, &mpath_device->disk->state))
+ continue;
+
+ if (test_and_set_bit(MPATH_DEVICE_SYSFS_ATTR_LINK,
+ &mpath_device->flags))
+ continue;
+
+ target = disk_to_dev(mpath_device->disk);
+ source = disk_to_dev(mpath_head->disk);
+ /*
+ * Create sysfs link from head gendisk kobject @kobj to the
+ * ns path gendisk kobject @target->kobj.
+ */
+ rc = sysfs_add_link_to_group(mpath_gd_kobj, "multipath",
+ &target->kobj, dev_name(target));
+
+ if (unlikely(rc)) {
+ dev_err(disk_to_dev(mpath_head->disk),
+ "failed to create link to %s rc=%d\n",
+ dev_name(target), rc);
+ clear_bit(MPATH_DEVICE_SYSFS_ATTR_LINK,
+ &mpath_device->flags);
+ } else {
+ dev_info(source, "Created multipath sysfs link to %s\n",
+ mpath_device->disk->disk_name);
+ }
+ }
+
+ srcu_read_unlock(&mpath_head->srcu, srcu_idx);
+}
+EXPORT_SYMBOL_GPL(mpath_add_sysfs_link);
+
+void mpath_remove_sysfs_link(struct mpath_device *mpath_device)
+{
+ struct device *target;
+ struct kobject *mpath_gd_kobj;
+ struct mpath_head *mpath_head = mpath_device->mpath_head;
+
+ if (!test_bit(MPATH_DEVICE_SYSFS_ATTR_LINK, &mpath_device->flags))
+ return;
+
+ target = disk_to_dev(mpath_device->disk);
+ mpath_gd_kobj = &disk_to_dev(mpath_head->disk)->kobj;
+
+ sysfs_remove_link_from_group(mpath_gd_kobj, "multipath",
+ dev_name(target));
+
+ clear_bit(MPATH_DEVICE_SYSFS_ATTR_LINK, &mpath_device->flags);
+}
+EXPORT_SYMBOL_GPL(mpath_remove_sysfs_link);
+
int mpath_head_init(struct mpath_head *mpath_head)
{
memset(mpath_head, 0, sizeof(*mpath_head));
--
2.43.7
^ permalink raw reply related [flat|nested] 71+ messages in thread* Re: [PATCH v5 05/27] libmultipath: Add support for mpath_device management
2026-07-27 14:42 ` [PATCH v5 05/27] libmultipath: Add support for mpath_device management John Garry
@ 2026-07-27 15:03 ` sashiko-bot
2026-07-27 15:23 ` John Garry
0 siblings, 1 reply; 71+ messages in thread
From: sashiko-bot @ 2026-07-27 15:03 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] `mpath_delete_device()` calls `mpath_synchronize()` without first removing the device from `current_path`, risking a use-after-free if callers rely on it for full synchronization.
--
--- Patch [5]: [PATCH v5 05/27] libmultipath: Add support for mpath_device management ---
commit 036ecbe8e851ac2a476516f8e8a72ff07e94750c
Author: John Garry <john.g.garry@oracle.com>
libmultipath: Add support for mpath_device management
This commit adds support to add or remove an mpath_device as a path.
It introduces equivalents to NVMe mpath functions like clearing, adding,
and removing sysfs links, as well as revalidating paths.
> diff --git a/lib/multipath.c b/lib/multipath.c
> index db5f83ebe5d7f..f70049b6cede9 100644
> --- a/lib/multipath.c
> +++ b/lib/multipath.c
[ ... ]
> +bool mpath_delete_device(struct mpath_device *mpath_device)
> +{
> + bool empty;
> +
> + mutex_lock(&mpath_device->mpath_head->lock);
> + list_del_rcu(&mpath_device->siblings);
> + empty = list_empty(&mpath_device->mpath_head->dev_list);
> + mutex_unlock(&mpath_device->mpath_head->lock);
> +
> + mpath_synchronize(mpath_device->mpath_head);
[Severity: Medium]
Does this code leave the device accessible to new RCU readers via the
current_path array?
By calling mpath_synchronize() here without first removing the device from
current_path (for example, by calling mpath_clear_current_path() before
synchronization), new RCU readers could still find and access the device.
If callers assume mpath_delete_device() provides full synchronization and
subsequently free the device, could this lead to a use-after-free?
> +
> + return empty;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727144234.3802948-1-john.g.garry@oracle.com?part=5
^ permalink raw reply [flat|nested] 71+ messages in thread* Re: [PATCH v5 05/27] libmultipath: Add support for mpath_device management
2026-07-27 15:03 ` sashiko-bot
@ 2026-07-27 15:23 ` John Garry
0 siblings, 0 replies; 71+ messages in thread
From: John Garry @ 2026-07-27 15:23 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 27/07/2026 16:03, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Medium] `mpath_delete_device()` calls `mpath_synchronize()` without first removing the device from `current_path`, risking a use-after-free if callers rely on it for full synchronization.
> --
>
> --- Patch [5]: [PATCH v5 05/27] libmultipath: Add support for mpath_device management ---
> commit 036ecbe8e851ac2a476516f8e8a72ff07e94750c
> Author: John Garry <john.g.garry@oracle.com>
>
> libmultipath: Add support for mpath_device management
>
> This commit adds support to add or remove an mpath_device as a path.
> It introduces equivalents to NVMe mpath functions like clearing, adding,
> and removing sysfs links, as well as revalidating paths.
>
>> diff --git a/lib/multipath.c b/lib/multipath.c
>> index db5f83ebe5d7f..f70049b6cede9 100644
>> --- a/lib/multipath.c
>> +++ b/lib/multipath.c
>
> [ ... ]
>
>> +bool mpath_delete_device(struct mpath_device *mpath_device)
>> +{
>> + bool empty;
>> +
>> + mutex_lock(&mpath_device->mpath_head->lock);
>> + list_del_rcu(&mpath_device->siblings);
>> + empty = list_empty(&mpath_device->mpath_head->dev_list);
>> + mutex_unlock(&mpath_device->mpath_head->lock);
>> +
>> + mpath_synchronize(mpath_device->mpath_head);
>
> [Severity: Medium]
> Does this code leave the device accessible to new RCU readers via the
> current_path array?
>
it is removed in sd_mpath_remove() -> mpath_clear_current_path()
> By calling mpath_synchronize() here without first removing the device from
> current_path (for example, by calling mpath_clear_current_path() before
> synchronization), new RCU readers could still find and access the device.
> If callers assume mpath_delete_device() provides full synchronization and
> subsequently free the device, could this lead to a use-after-free?
>
>> +
>> + return empty;
>> +}
>
^ permalink raw reply [flat|nested] 71+ messages in thread
* [PATCH v5 06/27] libmultipath: Add delayed removal support
2026-07-27 14:42 [PATCH v5 00/27] Native SCSI Multipath support John Garry
` (4 preceding siblings ...)
2026-07-27 14:42 ` [PATCH v5 05/27] libmultipath: Add support for mpath_device management John Garry
@ 2026-07-27 14:42 ` John Garry
2026-07-27 15:00 ` sashiko-bot
2026-07-27 14:42 ` [PATCH v5 07/27] libmultipath: Add sysfs helpers John Garry
` (20 subsequent siblings)
26 siblings, 1 reply; 71+ messages in thread
From: John Garry @ 2026-07-27 14:42 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
From: John Garry <john.garry@linux.dev>
Add support for delayed removal, same as exists for NVMe.
The purpose of this feature is to keep the multipath disk and cdev present
for intermittent periods of no available path.
Helpers mpath_delayed_removal_secs_show() and
mpath_delayed_removal_secs_store() may be used in the driver sysfs code.
The driver is responsible for supplying the removal work callback for
the delayed work.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
include/linux/multipath.h | 18 ++++++++
lib/multipath.c | 92 ++++++++++++++++++++++++++++++++++++++-
2 files changed, 109 insertions(+), 1 deletion(-)
diff --git a/include/linux/multipath.h b/include/linux/multipath.h
index 033ee8d54bbe9..66b6ad9c2219a 100644
--- a/include/linux/multipath.h
+++ b/include/linux/multipath.h
@@ -34,6 +34,7 @@ struct mpath_device {
struct mpath_head_template {
bool (*available_path)(struct mpath_device *);
+ void (*remove_head)(struct mpath_head *);
bool (*is_disabled)(struct mpath_device *);
bool (*is_optimized)(struct mpath_device *);
struct bio *(*clone_bio)(struct bio *);
@@ -41,6 +42,7 @@ struct mpath_head_template {
};
#define MPATH_HEAD_DISK_LIVE 0
+#define MPATH_HEAD_QUEUE_IF_NO_PATH 1
struct mpath_head {
struct srcu_struct srcu;
@@ -58,6 +60,10 @@ struct mpath_head {
atomic_long_t requeue_no_usable_path_cnt;
atomic_long_t fail_no_avail_path_cnt;
+ struct delayed_work remove_work;
+ unsigned int delayed_removal_secs;
+ struct module *drv_module;
+
unsigned long flags;
struct gendisk *disk;
struct work_struct partition_scan_work;
@@ -111,6 +117,11 @@ void mpath_remove_disk(struct mpath_head *mpath_head);
int mpath_alloc_head_disk(struct mpath_head *mpath_head,
struct queue_limits *lim, int numa_node);
void mpath_device_set_live(struct mpath_device *mpath_device);
+bool mpath_can_remove_head(struct mpath_head *mpath_head);
+ssize_t mpath_delayed_removal_secs_show(struct mpath_head *mpath_head,
+ char *buf);
+ssize_t mpath_delayed_removal_secs_store(struct mpath_head *mpath_head,
+ const char *buf, size_t count);
static inline bool is_mpath_disk(struct gendisk *disk)
{
@@ -126,6 +137,13 @@ static inline bool mpath_qd_iopolicy(enum mpath_iopolicy_e *iopolicy)
return READ_ONCE(*iopolicy) == MPATH_IOPOLICY_QD;
}
+static inline bool mpath_head_queue_if_no_path(struct mpath_head *mpath_head)
+{
+ if (test_bit(MPATH_HEAD_QUEUE_IF_NO_PATH, &mpath_head->flags))
+ return true;
+ return false;
+}
+
static inline void mpath_schedule_requeue_work(struct mpath_head *mpath_head)
{
kblockd_schedule_work(&mpath_head->requeue_work);
diff --git a/lib/multipath.c b/lib/multipath.c
index f70049b6cede9..ede06e5e901c8 100644
--- a/lib/multipath.c
+++ b/lib/multipath.c
@@ -65,6 +65,9 @@ int mpath_add_device(struct mpath_device *mpath_device, struct gendisk *disk,
list_add_tail_rcu(&mpath_device->siblings, &mpath_head->dev_list);
mutex_unlock(&mpath_head->lock);
+ if (cancel_delayed_work(&mpath_head->remove_work))
+ module_put(mpath_head->drv_module);
+
return 0;
}
EXPORT_SYMBOL_GPL(mpath_add_device);
@@ -338,7 +341,17 @@ static bool mpath_available_path(struct mpath_head *mpath_head)
return true;
}
- return false;
+ /*
+ * If "mpath_head->delayed_removal_secs" is set (i.e., non-zero), do
+ * not immediately fail I/O. Instead, requeue the I/O for the configured
+ * duration, anticipating that if there's a transient link failure then
+ * it may recover within this time window. This parameter is exported to
+ * userspace via sysfs, and its default value is zero. It is internally
+ * mapped to MPATH_HEAD_QUEUE_IF_NO_PATH. When delayed_removal_secs is
+ * non-zero, this flag is set to true. When zero, the flag is cleared.
+ */
+ return mpath_head_queue_if_no_path(mpath_head);
+
}
static void mpath_bdev_submit_bio(struct bio *bio)
@@ -480,6 +493,39 @@ static void mpath_requeue_work(struct work_struct *work)
}
}
+bool mpath_can_remove_head(struct mpath_head *mpath_head)
+{
+ bool remove = false;
+
+ mutex_lock(&mpath_head->lock);
+ /*
+ * Ensure that no one could remove this module while the head
+ * remove work is pending.
+ */
+ if (mpath_head_queue_if_no_path(mpath_head) &&
+ try_module_get(mpath_head->drv_module)) {
+ unsigned long delay = mpath_head->delayed_removal_secs * HZ;
+
+ mod_delayed_work(mpath_wq, &mpath_head->remove_work, delay);
+ } else {
+ remove = true;
+ }
+
+ mutex_unlock(&mpath_head->lock);
+ return remove;
+}
+EXPORT_SYMBOL_GPL(mpath_can_remove_head);
+
+static void mpath_remove_head_work(struct work_struct *work)
+{
+ struct mpath_head *mpath_head = container_of(to_delayed_work(work),
+ struct mpath_head, remove_work);
+ struct module *drv_module = mpath_head->drv_module;
+
+ mpath_head->mpdt->remove_head(mpath_head);
+ module_put(drv_module);
+}
+
void mpath_remove_disk(struct mpath_head *mpath_head)
{
if (test_and_clear_bit(MPATH_HEAD_DISK_LIVE, &mpath_head->flags)) {
@@ -525,6 +571,9 @@ int mpath_alloc_head_disk(struct mpath_head *mpath_head,
mpath_head->disk->private_data = mpath_head;
mpath_head->disk->fops = &mpath_ops;
+ INIT_DELAYED_WORK(&mpath_head->remove_work, mpath_remove_head_work);
+ mpath_head->delayed_removal_secs = 0;
+
set_bit(GD_SUPPRESS_PART_SCAN, &mpath_head->disk->state);
return 0;
@@ -568,6 +617,47 @@ void mpath_device_set_live(struct mpath_device *mpath_device)
}
EXPORT_SYMBOL_GPL(mpath_device_set_live);
+ssize_t mpath_delayed_removal_secs_show(struct mpath_head *mpath_head,
+ char *buf)
+{
+ int ret;
+
+ mutex_lock(&mpath_head->lock);
+ ret = sysfs_emit(buf, "%u\n", mpath_head->delayed_removal_secs);
+ mutex_unlock(&mpath_head->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(mpath_delayed_removal_secs_show);
+
+ssize_t mpath_delayed_removal_secs_store(struct mpath_head *mpath_head,
+ const char *buf, size_t count)
+{
+ unsigned int sec;
+ ssize_t ret;
+
+ ret = kstrtouint(buf, 0, &sec);
+ if (ret < 0)
+ return ret;
+
+ mutex_lock(&mpath_head->lock);
+ mpath_head->delayed_removal_secs = sec;
+ if (sec)
+ set_bit(MPATH_HEAD_QUEUE_IF_NO_PATH, &mpath_head->flags);
+ else
+ clear_bit(MPATH_HEAD_QUEUE_IF_NO_PATH, &mpath_head->flags);
+ mutex_unlock(&mpath_head->lock);
+
+ /*
+ * Ensure that update to MPATH_HEAD_QUEUE_IF_NO_PATH is seen
+ * by its reader.
+ */
+ mpath_synchronize(mpath_head);
+
+ return count;
+}
+EXPORT_SYMBOL_GPL(mpath_delayed_removal_secs_store);
+
void mpath_add_sysfs_link(struct mpath_head *mpath_head)
{
struct device *target;
--
2.43.7
^ permalink raw reply related [flat|nested] 71+ messages in thread* Re: [PATCH v5 06/27] libmultipath: Add delayed removal support
2026-07-27 14:42 ` [PATCH v5 06/27] libmultipath: Add delayed removal support John Garry
@ 2026-07-27 15:00 ` sashiko-bot
2026-07-27 15:25 ` John Garry
0 siblings, 1 reply; 71+ messages in thread
From: sashiko-bot @ 2026-07-27 15:00 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] Missing synchronization between path addition and delayed removal causes Use-After-Free.
- [High] Missing cancellation of `remove_work` during head teardown causes Use-After-Free.
- [High] Disabling delayed removal via sysfs leaves queued I/Os stuck indefinitely.
- [Medium] Module reference leak in `mpath_can_remove_head()` when delayed work is already pending.
- [Medium] 32-bit integer overflow in delayed removal seconds calculation.
--
--- Patch [6]: [PATCH v5 06/27] libmultipath: Add delayed removal support ---
commit c3a14a8d55791e59aaf6def4186888f9333f0985
Author: John Garry <john.garry@linux.dev>
libmultipath: Add delayed removal support
This patch adds support for delayed removal of multipath devices, similar to
NVMe. It introduces a configurable delayed removal window to retain the disk
and cdev during transient path loss.
> diff --git a/lib/multipath.c b/lib/multipath.c
> index f70049b6cede9..ede06e5e901c8 100644
> --- a/lib/multipath.c
> +++ b/lib/multipath.c
> @@ -65,6 +65,9 @@ int mpath_add_device(struct mpath_device *mpath_device, struct gendisk *disk,
> list_add_tail_rcu(&mpath_device->siblings, &mpath_head->dev_list);
> mutex_unlock(&mpath_head->lock);
>
> + if (cancel_delayed_work(&mpath_head->remove_work))
> + module_put(mpath_head->drv_module);
> +
[Severity: High]
If mpath_remove_head_work() is already executing when we call
cancel_delayed_work(), the cancellation will fail and return false. By
dropping the lock before this check, is it possible for mpath_remove_head_work()
to unconditionally tear down the mpath_head without realizing a new path was
just added?
This might leave the newly added path with dangling pointers to the freed
multipath head. Should we hold the lock or verify the execution state of the
work item?
> return 0;
> }
[ ... ]
> @@ -480,6 +493,39 @@ static void mpath_requeue_work(struct work_struct *work)
> }
> }
>
> +bool mpath_can_remove_head(struct mpath_head *mpath_head)
> +{
> + bool remove = false;
> +
> + mutex_lock(&mpath_head->lock);
> + /*
> + * Ensure that no one could remove this module while the head
> + * remove work is pending.
> + */
> + if (mpath_head_queue_if_no_path(mpath_head) &&
> + try_module_get(mpath_head->drv_module)) {
[Severity: Medium]
If the delayed work is already pending from a previous path failure,
mod_delayed_work() will update the timer and return true, but it will not
queue a new execution of the work item.
Since try_module_get() unconditionally increments the reference count before
this, will subsequent calls to mpath_can_remove_head() leak module references
since mpath_remove_head_work() only executes once?
> + unsigned long delay = mpath_head->delayed_removal_secs * HZ;
[Severity: Medium]
Could this multiplication overflow on 32-bit systems? Both
mpath_head->delayed_removal_secs (unsigned int) and HZ are 32-bit types, so the
multiplication is evaluated in 32-bit arithmetic before being promoted to
unsigned long.
If a user writes a large value to the sysfs attribute, could this result in an
unintended short delay or immediate removal?
> +
> + mod_delayed_work(mpath_wq, &mpath_head->remove_work, delay);
> + } else {
> + remove = true;
> + }
> +
> + mutex_unlock(&mpath_head->lock);
> + return remove;
> +}
> +EXPORT_SYMBOL_GPL(mpath_can_remove_head);
[ ... ]
> @@ -525,6 +571,9 @@ int mpath_alloc_head_disk(struct mpath_head *mpath_head,
> mpath_head->disk->private_data = mpath_head;
> mpath_head->disk->fops = &mpath_ops;
>
> + INIT_DELAYED_WORK(&mpath_head->remove_work, mpath_remove_head_work);
[Severity: High]
Since this work is initialized here, do we need to ensure it is cancelled
during head teardown?
Looking at mpath_put_disk(), it flushes requeue_work and partition_scan_work
but does not appear to call cancel_delayed_work_sync() for remove_work. Could
this leave the delayed work active in the workqueue after mpath_head is freed?
> + mpath_head->delayed_removal_secs = 0;
> +
> set_bit(GD_SUPPRESS_PART_SCAN, &mpath_head->disk->state);
>
> return 0;
[ ... ]
> +ssize_t mpath_delayed_removal_secs_store(struct mpath_head *mpath_head,
> + const char *buf, size_t count)
> +{
> + unsigned int sec;
> + ssize_t ret;
> +
> + ret = kstrtouint(buf, 0, &sec);
> + if (ret < 0)
> + return ret;
> +
> + mutex_lock(&mpath_head->lock);
> + mpath_head->delayed_removal_secs = sec;
> + if (sec)
> + set_bit(MPATH_HEAD_QUEUE_IF_NO_PATH, &mpath_head->flags);
> + else
> + clear_bit(MPATH_HEAD_QUEUE_IF_NO_PATH, &mpath_head->flags);
> + mutex_unlock(&mpath_head->lock);
[Severity: High]
When delayed removal is disabled by writing 0, the MPATH_HEAD_QUEUE_IF_NO_PATH
flag is cleared. However, does this leave bios already queued in requeue_list
stuck indefinitely?
Should we call mpath_schedule_requeue_work() here to ensure previously queued
I/Os are flushed and failed?
> +
> + /*
> + * Ensure that update to MPATH_HEAD_QUEUE_IF_NO_PATH is seen
> + * by its reader.
> + */
> + mpath_synchronize(mpath_head);
> +
> + return count;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727144234.3802948-1-john.g.garry@oracle.com?part=6
^ permalink raw reply [flat|nested] 71+ messages in thread* Re: [PATCH v5 06/27] libmultipath: Add delayed removal support
2026-07-27 15:00 ` sashiko-bot
@ 2026-07-27 15:25 ` John Garry
0 siblings, 0 replies; 71+ messages in thread
From: John Garry @ 2026-07-27 15:25 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 27/07/2026 16:00, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
> - [High] Missing synchronization between path addition and delayed removal causes Use-After-Free.
> - [High] Missing cancellation of `remove_work` during head teardown causes Use-After-Free.
> - [High] Disabling delayed removal via sysfs leaves queued I/Os stuck indefinitely.
> - [Medium] Module reference leak in `mpath_can_remove_head()` when delayed work is already pending.
> - [Medium] 32-bit integer overflow in delayed removal seconds calculation.
> --
>
> --- Patch [6]: [PATCH v5 06/27] libmultipath: Add delayed removal support ---
> commit c3a14a8d55791e59aaf6def4186888f9333f0985
> Author: John Garry <john.garry@linux.dev>
>
> libmultipath: Add delayed removal support
>
> This patch adds support for delayed removal of multipath devices, similar to
> NVMe. It introduces a configurable delayed removal window to retain the disk
> and cdev during transient path loss.
>
>> diff --git a/lib/multipath.c b/lib/multipath.c
>> index f70049b6cede9..ede06e5e901c8 100644
>> --- a/lib/multipath.c
>> +++ b/lib/multipath.c
>> @@ -65,6 +65,9 @@ int mpath_add_device(struct mpath_device *mpath_device, struct gendisk *disk,
>> list_add_tail_rcu(&mpath_device->siblings, &mpath_head->dev_list);
>> mutex_unlock(&mpath_head->lock);
>>
>> + if (cancel_delayed_work(&mpath_head->remove_work))
>> + module_put(mpath_head->drv_module);
>> +
>
> [Severity: High]
> If mpath_remove_head_work() is already executing when we call
> cancel_delayed_work(), the cancellation will fail and return false. By
> dropping the lock before this check, is it possible for mpath_remove_head_work()
> to unconditionally tear down the mpath_head without realizing a new path was
> just added?
>
> This might leave the newly added path with dangling pointers to the freed
> multipath head. Should we hold the lock or verify the execution state of the
> work item?
>
>> return 0;
>> }
>
> [ ... ]
>
>> @@ -480,6 +493,39 @@ static void mpath_requeue_work(struct work_struct *work)
>> }
>> }
>>
>> +bool mpath_can_remove_head(struct mpath_head *mpath_head)
>> +{
>> + bool remove = false;
>> +
>> + mutex_lock(&mpath_head->lock);
>> + /*
>> + * Ensure that no one could remove this module while the head
>> + * remove work is pending.
>> + */
>> + if (mpath_head_queue_if_no_path(mpath_head) &&
>> + try_module_get(mpath_head->drv_module)) {
>
> [Severity: Medium]
> If the delayed work is already pending from a previous path failure,
> mod_delayed_work() will update the timer and return true, but it will not
> queue a new execution of the work item.
>
> Since try_module_get() unconditionally increments the reference count before
> this, will subsequent calls to mpath_can_remove_head() leak module references
> since mpath_remove_head_work() only executes once?
>
>> + unsigned long delay = mpath_head->delayed_removal_secs * HZ;
>
> [Severity: Medium]
> Could this multiplication overflow on 32-bit systems? Both
> mpath_head->delayed_removal_secs (unsigned int) and HZ are 32-bit types, so the
> multiplication is evaluated in 32-bit arithmetic before being promoted to
> unsigned long.
>
> If a user writes a large value to the sysfs attribute, could this result in an
> unintended short delay or immediate removal?
>
>> +
>> + mod_delayed_work(mpath_wq, &mpath_head->remove_work, delay);
>> + } else {
>> + remove = true;
>> + }
>> +
>> + mutex_unlock(&mpath_head->lock);
>> + return remove;
>> +}
>> +EXPORT_SYMBOL_GPL(mpath_can_remove_head);
>
> [ ... ]
>
>> @@ -525,6 +571,9 @@ int mpath_alloc_head_disk(struct mpath_head *mpath_head,
>> mpath_head->disk->private_data = mpath_head;
>> mpath_head->disk->fops = &mpath_ops;
>>
>> + INIT_DELAYED_WORK(&mpath_head->remove_work, mpath_remove_head_work);
>
> [Severity: High]
> Since this work is initialized here, do we need to ensure it is cancelled
> during head teardown?
No, check sd_mpath_remove_head() which checks for new paths added
>
> Looking at mpath_put_disk(), it flushes requeue_work and partition_scan_work
> but does not appear to call cancel_delayed_work_sync() for remove_work. Could
> this leave the delayed work active in the workqueue after mpath_head is freed?
>
>> + mpath_head->delayed_removal_secs = 0;
>> +
>> set_bit(GD_SUPPRESS_PART_SCAN, &mpath_head->disk->state);
>>
>> return 0;
>
> [ ... ]
>
>> +ssize_t mpath_delayed_removal_secs_store(struct mpath_head *mpath_head,
>> + const char *buf, size_t count)
>> +{
>> + unsigned int sec;
>> + ssize_t ret;
>> +
>> + ret = kstrtouint(buf, 0, &sec);
>> + if (ret < 0)
>> + return ret;
>> +
>> + mutex_lock(&mpath_head->lock);
>> + mpath_head->delayed_removal_secs = sec;
>> + if (sec)
>> + set_bit(MPATH_HEAD_QUEUE_IF_NO_PATH, &mpath_head->flags);
>> + else
>> + clear_bit(MPATH_HEAD_QUEUE_IF_NO_PATH, &mpath_head->flags);
>> + mutex_unlock(&mpath_head->lock);
>
> [Severity: High]
> When delayed removal is disabled by writing 0, the MPATH_HEAD_QUEUE_IF_NO_PATH
> flag is cleared. However, does this leave bios already queued in requeue_list
> stuck indefinitely?
How is this even related or responsible bios already queued in requeue_list?
>
> Should we call mpath_schedule_requeue_work() here to ensure previously queued
> I/Os are flushed and failed?
>
>> +
>> + /*
>> + * Ensure that update to MPATH_HEAD_QUEUE_IF_NO_PATH is seen
>> + * by its reader.
>> + */
>> + mpath_synchronize(mpath_head);
>> +
>> + return count;
>> +}
>
^ permalink raw reply [flat|nested] 71+ messages in thread
* [PATCH v5 07/27] libmultipath: Add sysfs helpers
2026-07-27 14:42 [PATCH v5 00/27] Native SCSI Multipath support John Garry
` (5 preceding siblings ...)
2026-07-27 14:42 ` [PATCH v5 06/27] libmultipath: Add delayed removal support John Garry
@ 2026-07-27 14:42 ` John Garry
2026-07-27 15:03 ` sashiko-bot
2026-07-27 14:42 ` [PATCH v5 08/27] libmultipath: Add support for block device IOCTL John Garry
` (19 subsequent siblings)
26 siblings, 1 reply; 71+ messages in thread
From: John Garry @ 2026-07-27 14:42 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
Add helpers for driver sysfs code for the following functionality:
- get/set iopolicy with mpath_iopolicy_store() and mpath_iopolicy_show()
- show device path per NUMA node
- "multipath" attribute group, equivalent to nvme_ns_mpath_attr_group
- device groups attribute array, similar to nvme_ns_attr_groups but not
containing NVMe members.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
include/linux/multipath.h | 5 +++
lib/multipath.c | 94 +++++++++++++++++++++++++++++++++++++++
2 files changed, 99 insertions(+)
diff --git a/include/linux/multipath.h b/include/linux/multipath.h
index 66b6ad9c2219a..7a3708ff37254 100644
--- a/include/linux/multipath.h
+++ b/include/linux/multipath.h
@@ -7,6 +7,8 @@
#include <linux/srcu.h>
extern const struct block_device_operations mpath_ops;
+extern const struct attribute_group mpath_attr_group;
+extern const struct attribute_group *mpath_device_groups[];
enum mpath_iopolicy_e {
MPATH_IOPOLICY_NUMA,
@@ -118,6 +120,9 @@ int mpath_alloc_head_disk(struct mpath_head *mpath_head,
struct queue_limits *lim, int numa_node);
void mpath_device_set_live(struct mpath_device *mpath_device);
bool mpath_can_remove_head(struct mpath_head *mpath_head);
+ssize_t mpath_numa_nodes_show(struct mpath_device *mpath_device, char *buf);
+ssize_t mpath_iopolicy_show(enum mpath_iopolicy_e *iopolicy, char *buf);
+bool mpath_iopolicy_store(enum mpath_iopolicy_e *iopolicy, const char *buf);
ssize_t mpath_delayed_removal_secs_show(struct mpath_head *mpath_head,
char *buf);
ssize_t mpath_delayed_removal_secs_store(struct mpath_head *mpath_head,
diff --git a/lib/multipath.c b/lib/multipath.c
index ede06e5e901c8..c78d46a446cf3 100644
--- a/lib/multipath.c
+++ b/lib/multipath.c
@@ -617,6 +617,100 @@ void mpath_device_set_live(struct mpath_device *mpath_device)
}
EXPORT_SYMBOL_GPL(mpath_device_set_live);
+static struct attribute dummy_attr = {
+ .name = "dummy",
+};
+
+static struct attribute *mpath_attrs[] = {
+ &dummy_attr,
+ NULL
+};
+
+static bool multipath_sysfs_group_visible(struct kobject *kobj)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct gendisk *disk = dev_to_disk(dev);
+
+ return is_mpath_disk(disk);
+}
+DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(multipath_sysfs)
+
+const struct attribute_group mpath_attr_group = {
+ .name = "multipath",
+ .attrs = mpath_attrs,
+ .is_visible = SYSFS_GROUP_VISIBLE(multipath_sysfs),
+};
+EXPORT_SYMBOL_GPL(mpath_attr_group);
+
+const struct attribute_group *mpath_device_groups[] = {
+ &mpath_attr_group,
+ NULL
+};
+EXPORT_SYMBOL_GPL(mpath_device_groups);
+
+ssize_t mpath_iopolicy_show(enum mpath_iopolicy_e *iopolicy, char *buf)
+{
+ return sysfs_emit(buf, "%s\n",
+ mpath_iopolicy_names[READ_ONCE(*iopolicy)]);
+}
+EXPORT_SYMBOL_GPL(mpath_iopolicy_show);
+
+static void mpath_iopolicy_update(enum mpath_iopolicy_e *iopolicy,
+ int new)
+{
+ int old = READ_ONCE(*iopolicy);
+
+ if (old == new)
+ return;
+
+ WRITE_ONCE(*iopolicy, new);
+
+ pr_info("iopolicy changed from %s to %s\n",
+ mpath_iopolicy_names[old],
+ mpath_iopolicy_names[new]);
+}
+
+bool mpath_iopolicy_store(enum mpath_iopolicy_e *iopolicy, const char *buf)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(mpath_iopolicy_names); i++) {
+ if (sysfs_streq(buf, mpath_iopolicy_names[i])) {
+ mpath_iopolicy_update(iopolicy, i);
+ return true;
+ }
+ }
+
+ return false;
+}
+EXPORT_SYMBOL_GPL(mpath_iopolicy_store);
+
+ssize_t mpath_numa_nodes_show(struct mpath_device *mpath_device, char *buf)
+{
+ struct mpath_head *mpath_head = mpath_device->mpath_head;
+ int node, srcu_idx;
+ nodemask_t numa_nodes;
+ struct mpath_device *current_mpath_dev;
+
+ if (mpath_read_iopolicy(mpath_head) != MPATH_IOPOLICY_NUMA)
+ return 0;
+
+ nodes_clear(numa_nodes);
+
+ srcu_idx = srcu_read_lock(&mpath_head->srcu);
+ for_each_node(node) {
+ current_mpath_dev =
+ srcu_dereference(mpath_head->current_path[node],
+ &mpath_head->srcu);
+ if (current_mpath_dev == mpath_device)
+ node_set(node, numa_nodes);
+ }
+ srcu_read_unlock(&mpath_head->srcu, srcu_idx);
+
+ return sysfs_emit(buf, "%*pbl\n", nodemask_pr_args(&numa_nodes));
+}
+EXPORT_SYMBOL_GPL(mpath_numa_nodes_show);
+
ssize_t mpath_delayed_removal_secs_show(struct mpath_head *mpath_head,
char *buf)
{
--
2.43.7
^ permalink raw reply related [flat|nested] 71+ messages in thread* Re: [PATCH v5 07/27] libmultipath: Add sysfs helpers
2026-07-27 14:42 ` [PATCH v5 07/27] libmultipath: Add sysfs helpers John Garry
@ 2026-07-27 15:03 ` sashiko-bot
2026-07-27 15:33 ` John Garry
0 siblings, 1 reply; 71+ messages in thread
From: sashiko-bot @ 2026-07-27 15:03 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] mpath_alloc_head_disk leaves mpath_head->disk set to an ERR_PTR on blk_alloc_disk failure, leading to a crash in mpath_put_disk.
- [Medium] Updating the multipath iopolicy via mpath_iopolicy_store() does not clear the existing current_path arrays, resulting in stale path selection.
--
--- Patch [7]: [PATCH v5 07/27] libmultipath: Add sysfs helpers ---
commit fdada4b0515eb9327fe00efd638199926cadf8fd
Author: John Garry <john.g.garry@oracle.com>
libmultipath: Add sysfs helpers
Add helpers for driver sysfs code for the following functionality:
- get/set iopolicy with mpath_iopolicy_store() and mpath_iopolicy_show()
- show device path per NUMA node
- "multipath" attribute group, equivalent to nvme_ns_mpath_attr_group
- device groups attribute array, similar to nvme_ns_attr_groups but not
containing NVMe members.
> diff --git a/lib/multipath.c b/lib/multipath.c
> index ede06e5e901c8..c78d46a446cf3 100644
> --- a/lib/multipath.c
> +++ b/lib/multipath.c
[ ... ]
> +bool mpath_iopolicy_store(enum mpath_iopolicy_e *iopolicy, const char *buf)
> +{
> + int i;
> +
> + for (i = 0; i < ARRAY_SIZE(mpath_iopolicy_names); i++) {
> + if (sysfs_streq(buf, mpath_iopolicy_names[i])) {
> + mpath_iopolicy_update(iopolicy, i);
> + return true;
> + }
> + }
> +
> + return false;
> +}
[Severity: Medium]
When updating the multipath iopolicy via mpath_iopolicy_store(), does this
leave stale paths in the current_path arrays?
If the policy is changed (e.g., from round-robin to NUMA), the policy enum is
updated but the cached current_path is not cleared. When mpath_numa_path()
subsequently executes, it reads the stale current_path. As long as the stale
path is MPATH_STATE_OPTIMIZED, the NUMA selector might accept it without ever
evaluating node_distance to find the true optimal NUMA path:
lib/multipath.c:mpath_numa_path()
mpath_device = srcu_dereference(mpath_head->current_path[node],
&mpath_head->srcu);
if (unlikely(!mpath_device))
return __mpath_find_path(mpath_head, node);
if (unlikely(mpath_path_is_disabled(mpath_head, mpath_device)))
return __mpath_find_path(mpath_head, node);
if (unlikely(!mpath_path_is_optimized(mpath_head, mpath_device)))
return __mpath_find_path(mpath_head, node);
return mpath_device;
Outside of the diff provided in this patch, there is another issue in the
broader multipath code to consider.
[Severity: High]
If blk_alloc_disk() fails in mpath_alloc_head_disk() (e.g., due to ENOMEM),
mpath_head->disk is assigned an ERR_PTR but is not reset to NULL before
returning the error:
lib/multipath.c:mpath_alloc_head_disk()
mpath_head->disk = blk_alloc_disk(lim, numa_node);
if (IS_ERR(mpath_head->disk))
return PTR_ERR(mpath_head->disk);
If the caller handles this failure by invoking mpath_put_disk(), the cleanup
function assumes any non-NULL value is valid and calls put_disk() on the
ERR_PTR, which could lead to a crash:
lib/multipath.c:mpath_put_disk()
if (!mpath_head->disk)
return;
/* make sure all pending bios are cleaned up */
kblockd_schedule_work(&mpath_head->requeue_work);
flush_work(&mpath_head->requeue_work);
flush_work(&mpath_head->partition_scan_work);
put_disk(mpath_head->disk);
Should mpath_head->disk be set to NULL on the blk_alloc_disk() error path?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727144234.3802948-1-john.g.garry@oracle.com?part=7
^ permalink raw reply [flat|nested] 71+ messages in thread* Re: [PATCH v5 07/27] libmultipath: Add sysfs helpers
2026-07-27 15:03 ` sashiko-bot
@ 2026-07-27 15:33 ` John Garry
0 siblings, 0 replies; 71+ messages in thread
From: John Garry @ 2026-07-27 15:33 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 27/07/2026 16:03, sashiko-bot@kernel.org wrote:
>> +bool mpath_iopolicy_store(enum mpath_iopolicy_e *iopolicy, const char *buf)
>> +{
>> + int i;
>> +
>> + for (i = 0; i < ARRAY_SIZE(mpath_iopolicy_names); i++) {
>> + if (sysfs_streq(buf, mpath_iopolicy_names[i])) {
>> + mpath_iopolicy_update(iopolicy, i);
>> + return true;
>> + }
>> + }
>> +
>> + return false;
>> +}
> [Severity: Medium]
> When updating the multipath iopolicy via mpath_iopolicy_store(), does this
> leave stale paths in the current_path arrays?
>
> If the policy is changed (e.g., from round-robin to NUMA), the policy enum is
> updated but the cached current_path is not cleared. When mpath_numa_path()
It is the job of the driver to do any path clearing or similar - see
scsi_mpath_device_iopolicy_store()
^ permalink raw reply [flat|nested] 71+ messages in thread
* [PATCH v5 08/27] libmultipath: Add support for block device IOCTL
2026-07-27 14:42 [PATCH v5 00/27] Native SCSI Multipath support John Garry
` (6 preceding siblings ...)
2026-07-27 14:42 ` [PATCH v5 07/27] libmultipath: Add sysfs helpers John Garry
@ 2026-07-27 14:42 ` John Garry
2026-07-27 15:08 ` sashiko-bot
2026-07-27 14:42 ` [PATCH v5 09/27] libmultipath: Add mpath_bdev_getgeo() John Garry
` (18 subsequent siblings)
26 siblings, 1 reply; 71+ messages in thread
From: John Garry @ 2026-07-27 14:42 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
From: John Garry <john.garry@linux.dev>
Add mpath_bdev_ioctl() as a multipath block device IOCTL handler. This
handler calls into the mpath_device bdev fops handler.
The .compat_ioctl handler is given the standard handler.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
lib/multipath.c | 36 ++++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/lib/multipath.c b/lib/multipath.c
index c78d46a446cf3..af90cf38a79d6 100644
--- a/lib/multipath.c
+++ b/lib/multipath.c
@@ -454,11 +454,47 @@ static void mpath_bdev_release(struct gendisk *disk)
mpath_put_head(mpath_head);
}
+static int mpath_bdev_ioctl(struct block_device *bdev, blk_mode_t mode,
+ unsigned int cmd, unsigned long arg)
+{
+ struct gendisk *disk = bdev->bd_disk;
+ struct mpath_head *mpath_head = mpath_gendisk_to_head(disk);
+ struct mpath_device *mpath_device;
+ int srcu_idx, err;
+
+ if (bdev_is_partition(bdev) && !capable(CAP_SYS_RAWIO))
+ return -ENOIOCTLCMD;
+
+ srcu_idx = srcu_read_lock(&mpath_head->srcu);
+ mpath_device = mpath_find_path(mpath_head);
+ if (!mpath_device) {
+ err = -EWOULDBLOCK;
+ goto out_unlock;
+ }
+
+ if (!mpath_device->disk->fops->ioctl) {
+ err = -EOPNOTSUPP;
+ goto out_unlock;
+ }
+
+ err = mpath_device->disk->fops->ioctl(
+ mpath_device->disk->part0, mode, cmd, arg);
+out_unlock:
+ srcu_read_unlock(&mpath_head->srcu, srcu_idx);
+ return err;
+}
+
const struct block_device_operations mpath_ops = {
.owner = THIS_MODULE,
.open = mpath_bdev_open,
.release = mpath_bdev_release,
.submit_bio = mpath_bdev_submit_bio,
+ .ioctl = mpath_bdev_ioctl,
+ /*
+ * Both NVMe and SCSI use generic blkdev_compat_ptr_ioctl, so would
+ * avoid their custom compat_ioctl implementation.
+ */
+ .compat_ioctl = blkdev_compat_ptr_ioctl,
};
EXPORT_SYMBOL_GPL(mpath_ops);
--
2.43.7
^ permalink raw reply related [flat|nested] 71+ messages in thread* Re: [PATCH v5 08/27] libmultipath: Add support for block device IOCTL
2026-07-27 14:42 ` [PATCH v5 08/27] libmultipath: Add support for block device IOCTL John Garry
@ 2026-07-27 15:08 ` sashiko-bot
2026-07-27 15:31 ` John Garry
0 siblings, 1 reply; 71+ messages in thread
From: sashiko-bot @ 2026-07-27 15:08 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] Holding the SRCU read lock across the underlying ioctl call causes an AB-BA deadlock if the ioctl triggers a synchronous path removal.
- [High] Forwarding ioctls via part0 strips partition context, bypassing partition-specific capability checks in underlying drivers.
- [Medium] Returning -EOPNOTSUPP instead of -ENOTTY for unimplemented underlying ioctls breaks userspace fallback mechanics.
--
--- Patch [8]: [PATCH v5 08/27] libmultipath: Add support for block device IOCTL ---
commit c97bc7070dfdd2d761dd1464c23d043908adfc68
Author: John Garry <john.garry@linux.dev>
libmultipath: Add support for block device IOCTL
Add mpath_bdev_ioctl() as a multipath block device IOCTL handler. This
handler calls into the mpath_device bdev fops handler. The .compat_ioctl
handler is given the standard handler.
> diff --git a/lib/multipath.c b/lib/multipath.c
> index c78d46a446cf3..af90cf38a79d6 100644
> --- a/lib/multipath.c
> +++ b/lib/multipath.c
[ ... ]
> @@ -454,11 +454,47 @@ static void mpath_bdev_release(struct gendisk *disk)
> mpath_put_head(mpath_head);
> }
>
> +static int mpath_bdev_ioctl(struct block_device *bdev, blk_mode_t mode,
> + unsigned int cmd, unsigned long arg)
> +{
> + struct gendisk *disk = bdev->bd_disk;
> + struct mpath_head *mpath_head = mpath_gendisk_to_head(disk);
> + struct mpath_device *mpath_device;
> + int srcu_idx, err;
> +
> + if (bdev_is_partition(bdev) && !capable(CAP_SYS_RAWIO))
> + return -ENOIOCTLCMD;
> +
> + srcu_idx = srcu_read_lock(&mpath_head->srcu);
> + mpath_device = mpath_find_path(mpath_head);
> + if (!mpath_device) {
> + err = -EWOULDBLOCK;
> + goto out_unlock;
> + }
> +
> + if (!mpath_device->disk->fops->ioctl) {
> + err = -EOPNOTSUPP;
> + goto out_unlock;
> + }
[Severity: Medium]
Should this return -ENOTTY instead?
Returning -EOPNOTSUPP for unimplemented ioctls in mpath_bdev_ioctl() can break
userspace fallback mechanisms in tools like smartctl or nvme-cli that
explicitly check for -ENOTTY to determine if an ioctl is supported on the
block device.
> +
> + err = mpath_device->disk->fops->ioctl(
> + mpath_device->disk->part0, mode, cmd, arg);
[Severity: High]
Does forwarding ioctls to part0 in mpath_bdev_ioctl() bypass
partition-specific security checks?
If an ioctl is invoked on a multipath partition, passing
mpath_device->disk->part0 strips the partition context. Underlying drivers
like nvme rely on bdev_is_partition() to enforce stricter security on
partitions (e.g. requiring CAP_SYS_ADMIN). Because part0 represents the whole
disk, this could allow users with CAP_SYS_RAWIO to issue unrestricted
passthrough commands.
[Severity: High]
Could holding the srcu read lock during the underlying ioctl cause a deadlock?
In mpath_bdev_ioctl(), if the ioctl triggers a synchronous path removal
(like an nvme namespace management command), the underlying driver might
attempt to remove the namespace. If mpath_delete_device() calls
synchronize_srcu(&mpath_head->srcu) during removal, the removal thread will
block waiting for the srcu read lock to be released, while the ioctl thread
holds the lock and waits for the removal to complete.
> +out_unlock:
> + srcu_read_unlock(&mpath_head->srcu, srcu_idx);
> + return err;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727144234.3802948-1-john.g.garry@oracle.com?part=8
^ permalink raw reply [flat|nested] 71+ messages in thread* Re: [PATCH v5 08/27] libmultipath: Add support for block device IOCTL
2026-07-27 15:08 ` sashiko-bot
@ 2026-07-27 15:31 ` John Garry
0 siblings, 0 replies; 71+ messages in thread
From: John Garry @ 2026-07-27 15:31 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 27/07/2026 16:08, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
> - [High] Holding the SRCU read lock across the underlying ioctl call causes an AB-BA deadlock if the ioctl triggers a synchronous path removal.
> - [High] Forwarding ioctls via part0 strips partition context, bypassing partition-specific capability checks in underlying drivers.
> - [Medium] Returning -EOPNOTSUPP instead of -ENOTTY for unimplemented underlying ioctls breaks userspace fallback mechanics.
> --
>
> --- Patch [8]: [PATCH v5 08/27] libmultipath: Add support for block device IOCTL ---
> commit c97bc7070dfdd2d761dd1464c23d043908adfc68
> Author: John Garry <john.garry@linux.dev>
>
> libmultipath: Add support for block device IOCTL
>
> Add mpath_bdev_ioctl() as a multipath block device IOCTL handler. This
> handler calls into the mpath_device bdev fops handler. The .compat_ioctl
> handler is given the standard handler.
>
>> diff --git a/lib/multipath.c b/lib/multipath.c
>> index c78d46a446cf3..af90cf38a79d6 100644
>> --- a/lib/multipath.c
>> +++ b/lib/multipath.c
>
> [ ... ]
>
>> @@ -454,11 +454,47 @@ static void mpath_bdev_release(struct gendisk *disk)
>> mpath_put_head(mpath_head);
>> }
>>
>> +static int mpath_bdev_ioctl(struct block_device *bdev, blk_mode_t mode,
>> + unsigned int cmd, unsigned long arg)
>> +{
>> + struct gendisk *disk = bdev->bd_disk;
>> + struct mpath_head *mpath_head = mpath_gendisk_to_head(disk);
>> + struct mpath_device *mpath_device;
>> + int srcu_idx, err;
>> +
>> + if (bdev_is_partition(bdev) && !capable(CAP_SYS_RAWIO))
>> + return -ENOIOCTLCMD;
>> +
>> + srcu_idx = srcu_read_lock(&mpath_head->srcu);
>> + mpath_device = mpath_find_path(mpath_head);
>> + if (!mpath_device) {
>> + err = -EWOULDBLOCK;
>> + goto out_unlock;
>> + }
>> +
>> + if (!mpath_device->disk->fops->ioctl) {
>> + err = -EOPNOTSUPP;
>> + goto out_unlock;
>> + }
>
> [Severity: Medium]
> Should this return -ENOTTY instead?
will fix
>
> Returning -EOPNOTSUPP for unimplemented ioctls in mpath_bdev_ioctl() can break
> userspace fallback mechanisms in tools like smartctl or nvme-cli that
> explicitly check for -ENOTTY to determine if an ioctl is supported on the
> block device.
>
>> +
>> + err = mpath_device->disk->fops->ioctl(
>> + mpath_device->disk->part0, mode, cmd, arg);
>
> [Severity: High]
> Does forwarding ioctls to part0 in mpath_bdev_ioctl() bypass
> partition-specific security checks?
I added that check above
>
> If an ioctl is invoked on a multipath partition, passing
> mpath_device->disk->part0 strips the partition context. Underlying drivers
> like nvme rely on bdev_is_partition() to enforce stricter security on
> partitions (e.g. requiring CAP_SYS_ADMIN). Because part0 represents the whole
> disk, this could allow users with CAP_SYS_RAWIO to issue unrestricted
> passthrough commands.
>
> [Severity: High]
> Could holding the srcu read lock during the underlying ioctl cause a deadlock?
>
> In mpath_bdev_ioctl(), if the ioctl triggers a synchronous path removal
> (like an nvme namespace management command), the underlying driver might
we don't support NVMe yet
> attempt to remove the namespace. If mpath_delete_device() calls
> synchronize_srcu(&mpath_head->srcu) during removal, the removal thread will
> block waiting for the srcu read lock to be released, while the ioctl thread
> holds the lock and waits for the removal to complete.
>
>> +out_unlock:
>> + srcu_read_unlock(&mpath_head->srcu, srcu_idx);
>> + return err;
>> +}
>
^ permalink raw reply [flat|nested] 71+ messages in thread
* [PATCH v5 09/27] libmultipath: Add mpath_bdev_getgeo()
2026-07-27 14:42 [PATCH v5 00/27] Native SCSI Multipath support John Garry
` (7 preceding siblings ...)
2026-07-27 14:42 ` [PATCH v5 08/27] libmultipath: Add support for block device IOCTL John Garry
@ 2026-07-27 14:42 ` John Garry
2026-07-27 14:42 ` [PATCH v5 10/27] libmultipath: Add mpath_bdev_get_unique_id() John Garry
` (17 subsequent siblings)
26 siblings, 0 replies; 71+ messages in thread
From: John Garry @ 2026-07-27 14:42 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
From: John Garry <john.garry@linux.dev>
Add mpath_bdev_getgeo() as a multipath block device .getgeo handler.
Here we just redirect into the selected mpath_device disk fops->getgeo
handler.
Signed-off-by: John Garry <john.g.garry@oracle.com>=
---
lib/multipath.c | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/lib/multipath.c b/lib/multipath.c
index af90cf38a79d6..76b844d42813e 100644
--- a/lib/multipath.c
+++ b/lib/multipath.c
@@ -484,6 +484,26 @@ static int mpath_bdev_ioctl(struct block_device *bdev, blk_mode_t mode,
return err;
}
+static int mpath_bdev_getgeo(struct gendisk *disk, struct hd_geometry *geo)
+{
+ struct mpath_head *mpath_head = mpath_gendisk_to_head(disk);
+ int srcu_idx, ret = -EWOULDBLOCK;
+ struct mpath_device *mpath_device;
+
+ srcu_idx = srcu_read_lock(&mpath_head->srcu);
+ mpath_device = mpath_find_path(mpath_head);
+ if (mpath_device) {
+ if (mpath_device->disk->fops->getgeo)
+ ret = mpath_device->disk->fops->getgeo(
+ mpath_device->disk, geo);
+ else
+ ret = -ENOTTY; /* See blkdev_getgeo */
+ }
+ srcu_read_unlock(&mpath_head->srcu, srcu_idx);
+
+ return ret;
+}
+
const struct block_device_operations mpath_ops = {
.owner = THIS_MODULE,
.open = mpath_bdev_open,
@@ -495,6 +515,7 @@ const struct block_device_operations mpath_ops = {
* avoid their custom compat_ioctl implementation.
*/
.compat_ioctl = blkdev_compat_ptr_ioctl,
+ .getgeo = mpath_bdev_getgeo,
};
EXPORT_SYMBOL_GPL(mpath_ops);
--
2.43.7
^ permalink raw reply related [flat|nested] 71+ messages in thread* [PATCH v5 10/27] libmultipath: Add mpath_bdev_get_unique_id()
2026-07-27 14:42 [PATCH v5 00/27] Native SCSI Multipath support John Garry
` (8 preceding siblings ...)
2026-07-27 14:42 ` [PATCH v5 09/27] libmultipath: Add mpath_bdev_getgeo() John Garry
@ 2026-07-27 14:42 ` John Garry
2026-07-27 14:42 ` [PATCH v5 11/27] scsi-multipath: introduce basic SCSI device support John Garry
` (16 subsequent siblings)
26 siblings, 0 replies; 71+ messages in thread
From: John Garry @ 2026-07-27 14:42 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
From: John Garry <john.garry@linux.dev>
Add mpath_bdev_get_unique_id() as a multipath block device .get_unique_id
handler.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
lib/multipath.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/lib/multipath.c b/lib/multipath.c
index 76b844d42813e..ac3816ae6b0a1 100644
--- a/lib/multipath.c
+++ b/lib/multipath.c
@@ -454,6 +454,27 @@ static void mpath_bdev_release(struct gendisk *disk)
mpath_put_head(mpath_head);
}
+static int mpath_bdev_get_unique_id(struct gendisk *disk, u8 id[16],
+ enum blk_unique_id type)
+{
+ struct mpath_head *mpath_head = mpath_gendisk_to_head(disk);
+ int srcu_idx, ret = -EWOULDBLOCK;
+ struct mpath_device *mpath_device;
+
+ srcu_idx = srcu_read_lock(&mpath_head->srcu);
+ mpath_device = mpath_find_path(mpath_head);
+ if (mpath_device) {
+ if (mpath_device->disk->fops->get_unique_id)
+ ret = mpath_device->disk->fops->get_unique_id(
+ mpath_device->disk, id, type);
+ else
+ ret = 0; /* referencing __dm_get_unique_id() */
+ }
+ srcu_read_unlock(&mpath_head->srcu, srcu_idx);
+
+ return ret;
+}
+
static int mpath_bdev_ioctl(struct block_device *bdev, blk_mode_t mode,
unsigned int cmd, unsigned long arg)
{
@@ -515,6 +536,7 @@ const struct block_device_operations mpath_ops = {
* avoid their custom compat_ioctl implementation.
*/
.compat_ioctl = blkdev_compat_ptr_ioctl,
+ .get_unique_id = mpath_bdev_get_unique_id,
.getgeo = mpath_bdev_getgeo,
};
EXPORT_SYMBOL_GPL(mpath_ops);
--
2.43.7
^ permalink raw reply related [flat|nested] 71+ messages in thread* [PATCH v5 11/27] scsi-multipath: introduce basic SCSI device support
2026-07-27 14:42 [PATCH v5 00/27] Native SCSI Multipath support John Garry
` (9 preceding siblings ...)
2026-07-27 14:42 ` [PATCH v5 10/27] libmultipath: Add mpath_bdev_get_unique_id() John Garry
@ 2026-07-27 14:42 ` John Garry
2026-07-27 18:59 ` sashiko-bot
2026-07-27 14:42 ` [PATCH v5 12/27] scsi-multipath: introduce scsi_device head structure John Garry
` (15 subsequent siblings)
26 siblings, 1 reply; 71+ messages in thread
From: John Garry @ 2026-07-27 14:42 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
For a scsi_device to support multipath, introduce structure
scsi_mpath_device to hold multipath-specific details.
Like NS structure for NVME, scsi_mpath_device holds the mpath_device
structure to device management and path selection.
A module param are introduced to enable multipath - the following modes
are available:
- on
- off
- always
SCSI multipath will only be available until the following conditions:
- scsi_multipath enabled and implicit ALUA supported and unique ID
available in VPD page 83.
- scsi_multipath always mode and unique ID available in VPD page 83
The scsi_device structure contains a pointer to scsi_mpath_device; having
this pointer set or unset indicates whether multipath is enabled or
disabled for the scsi_device.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/Kconfig | 10 +++
drivers/scsi/Makefile | 1 +
drivers/scsi/scsi.c | 8 +-
drivers/scsi/scsi_multipath.c | 146 ++++++++++++++++++++++++++++++++++
drivers/scsi/scsi_scan.c | 4 +
drivers/scsi/scsi_sysfs.c | 2 +
include/scsi/scsi_device.h | 2 +
include/scsi/scsi_multipath.h | 55 +++++++++++++
8 files changed, 227 insertions(+), 1 deletion(-)
create mode 100644 drivers/scsi/scsi_multipath.c
create mode 100644 include/scsi/scsi_multipath.h
diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig
index c3042393af234..d6c31df454825 100644
--- a/drivers/scsi/Kconfig
+++ b/drivers/scsi/Kconfig
@@ -76,6 +76,16 @@ config SCSI_LIB_KUNIT_TEST
If unsure say N.
+config SCSI_MULTIPATH
+ bool "SCSI multipath support (EXPERIMENTAL)"
+ depends on SCSI_MOD
+ select LIBMULTIPATH
+ help
+ This option enables support for native SCSI multipath support for
+ SCSI host.
+
+ If unsure say N.
+
comment "SCSI support type (disk, tape, CD-ROM)"
depends on SCSI
diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile
index 842c254bb2269..04f9c2e51fcbd 100644
--- a/drivers/scsi/Makefile
+++ b/drivers/scsi/Makefile
@@ -168,6 +168,7 @@ scsi_mod-y += scsi_trace.o scsi_logging.o
scsi_mod-$(CONFIG_PM) += scsi_pm.o
scsi_mod-$(CONFIG_SCSI_DH) += scsi_dh.o
scsi_mod-$(CONFIG_BLK_DEV_BSG) += scsi_bsg.o
+scsi_mod-$(CONFIG_SCSI_MULTIPATH) += scsi_multipath.o
hv_storvsc-y := storvsc_drv.o
diff --git a/drivers/scsi/scsi.c b/drivers/scsi/scsi.c
index 76cdad063f7bc..70aa1dbeacebf 100644
--- a/drivers/scsi/scsi.c
+++ b/drivers/scsi/scsi.c
@@ -64,6 +64,7 @@
#include <scsi/scsi_driver.h>
#include <scsi/scsi_eh.h>
#include <scsi/scsi_host.h>
+#include <scsi/scsi_multipath.h>
#include <scsi/scsi_tcq.h>
#include "scsi_priv.h"
@@ -1042,12 +1043,16 @@ static int __init init_scsi(void)
error = scsi_sysfs_register();
if (error)
goto cleanup_sysctl;
+ error = scsi_multipath_init();
+ if (error)
+ goto cleanup_sysfs;
scsi_netlink_init();
printk(KERN_NOTICE "SCSI subsystem initialized\n");
return 0;
-
+cleanup_sysfs:
+ scsi_sysfs_unregister();
cleanup_sysctl:
scsi_exit_sysctl();
cleanup_hosts:
@@ -1066,6 +1071,7 @@ static int __init init_scsi(void)
static void __exit exit_scsi(void)
{
scsi_netlink_exit();
+ scsi_multipath_exit();
scsi_sysfs_unregister();
scsi_exit_sysctl();
scsi_exit_hosts();
diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
new file mode 100644
index 0000000000000..947f3e2103e6b
--- /dev/null
+++ b/drivers/scsi/scsi_multipath.c
@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 Oracle Corp
+ *
+ */
+
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_driver.h>
+#include <scsi/scsi_proto.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_multipath.h>
+
+#include "scsi_priv.h"
+
+#define TPGS_MODE_IMPLICIT 0x1
+
+enum {
+ SCSI_MULTIPATH_OFF,
+ SCSI_MULTIPATH_ON,
+ SCSI_MULTIPATH_ALWAYS,
+};
+
+static const char *scsi_multipath_modes[] = {
+ [SCSI_MULTIPATH_OFF] = "off",
+ [SCSI_MULTIPATH_ON] = "on",
+ [SCSI_MULTIPATH_ALWAYS] = "always",
+};
+
+static int scsi_multipath = SCSI_MULTIPATH_OFF;
+
+static int scsi_multipath_param_set(const char *val, const struct kernel_param *kp)
+{
+ int mode;
+
+ if (!val)
+ return -EINVAL;
+ mode = __sysfs_match_string(scsi_multipath_modes,
+ ARRAY_SIZE(scsi_multipath_modes), val);
+
+ if (mode < 0)
+ return mode;
+ scsi_multipath = mode;
+ return 0;
+}
+
+static int scsi_multipath_param_get(char *buf, const struct kernel_param *kp)
+{
+ return sprintf(buf, "%s\n", scsi_multipath_modes[scsi_multipath]);
+}
+
+static const struct kernel_param_ops multipath_param_ops = {
+ .set = scsi_multipath_param_set,
+ .get = scsi_multipath_param_get,
+};
+
+module_param_cb(multipath, &multipath_param_ops, &scsi_multipath, 0444);
+MODULE_PARM_DESC(multipath, "turn on native multipath support, options: on, off, always");
+
+static int scsi_mpath_unique_lun_id(struct scsi_device *sdev)
+{
+ struct scsi_mpath_device *scsi_mpath_dev = sdev->scsi_mpath_dev;
+ int ret;
+
+ ret = scsi_vpd_lun_id(sdev, scsi_mpath_dev->device_id_str,
+ SCSI_MPATH_DEVICE_ID_LEN);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int scsi_multipath_sdev_init(struct scsi_device *sdev)
+{
+ struct Scsi_Host *shost = sdev->host;
+ struct scsi_mpath_device *scsi_mpath_dev;
+ struct mpath_device *mpath_device;
+
+ scsi_mpath_dev = kzalloc(sizeof(*scsi_mpath_dev), GFP_KERNEL);
+ if (!scsi_mpath_dev)
+ return -ENOMEM;
+ scsi_mpath_dev->sdev = sdev;
+ sdev->scsi_mpath_dev = scsi_mpath_dev;
+
+ mpath_device = &scsi_mpath_dev->mpath_device;
+ mpath_device->numa_node = dev_to_node(shost->dma_dev);
+ mpath_device->access_state = MPATH_STATE_OPTIMIZED;
+
+ return 0;
+}
+
+static void scsi_multipath_sdev_uninit(struct scsi_device *sdev)
+{
+ kfree(sdev->scsi_mpath_dev);
+ sdev->scsi_mpath_dev = NULL;
+}
+
+int scsi_mpath_dev_alloc(struct scsi_device *sdev)
+{
+ int ret;
+
+ if (scsi_multipath == SCSI_MULTIPATH_OFF)
+ return 0;
+
+ if (!(scsi_device_tpgs(sdev) & TPGS_MODE_IMPLICIT) &&
+ (scsi_multipath != SCSI_MULTIPATH_ALWAYS)) {
+ sdev_printk(KERN_DEBUG, sdev, "IMPLICIT TPGS are required for multipath support\n");
+ return 0;
+ }
+
+ ret = scsi_multipath_sdev_init(sdev);
+ if (ret)
+ return ret;
+
+ ret = scsi_mpath_unique_lun_id(sdev);
+ if (ret < 0)
+ goto out_uninit;
+
+ return 0;
+
+out_uninit:
+ scsi_multipath_sdev_uninit(sdev);
+ return 0;
+}
+
+void scsi_mpath_dev_release(struct scsi_device *sdev)
+{
+ struct scsi_mpath_device *scsi_mpath_dev = sdev->scsi_mpath_dev;
+
+ if (!scsi_mpath_dev)
+ return;
+
+ scsi_multipath_sdev_uninit(sdev);
+}
+
+int __init scsi_multipath_init(void)
+{
+ return 0;
+}
+
+void __exit scsi_multipath_exit(void)
+{
+}
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("scsi_multipath");
diff --git a/drivers/scsi/scsi_scan.c b/drivers/scsi/scsi_scan.c
index e27da038603a2..3f1ac302719c2 100644
--- a/drivers/scsi/scsi_scan.c
+++ b/drivers/scsi/scsi_scan.c
@@ -46,6 +46,7 @@
#include <scsi/scsi_transport.h>
#include <scsi/scsi_dh.h>
#include <scsi/scsi_eh.h>
+#include <scsi/scsi_multipath.h>
#include "scsi_priv.h"
#include "scsi_logging.h"
@@ -1130,6 +1131,9 @@ static int scsi_add_lun(struct scsi_device *sdev, unsigned char *inq_result,
sdev->max_queue_depth = sdev->queue_depth;
WARN_ON_ONCE(sdev->max_queue_depth > sdev->budget_map.depth);
+ if (scsi_mpath_dev_alloc(sdev))
+ return SCSI_SCAN_NO_RESPONSE;
+
/*
* Ok, the device is now all set up, we can
* register it and tell the rest of the kernel
diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c
index dfc3559e7e04f..2f80d703ce640 100644
--- a/drivers/scsi/scsi_sysfs.c
+++ b/drivers/scsi/scsi_sysfs.c
@@ -23,6 +23,7 @@
#include <scsi/scsi_transport.h>
#include <scsi/scsi_driver.h>
#include <scsi/scsi_devinfo.h>
+#include <scsi/scsi_multipath.h>
#include "scsi_priv.h"
#include "scsi_logging.h"
@@ -455,6 +456,7 @@ static void scsi_device_dev_release(struct device *dev)
might_sleep();
scsi_dh_release_device(sdev);
+ scsi_mpath_dev_release(sdev);
parent = sdev->sdev_gendev.parent;
diff --git a/include/scsi/scsi_device.h b/include/scsi/scsi_device.h
index 029f5115b2ea0..40a435e66b34e 100644
--- a/include/scsi/scsi_device.h
+++ b/include/scsi/scsi_device.h
@@ -280,6 +280,8 @@ struct scsi_device {
struct device sdev_gendev,
sdev_dev;
+ struct scsi_mpath_device *scsi_mpath_dev;
+
struct work_struct requeue_work;
struct scsi_device_handler *handler;
diff --git a/include/scsi/scsi_multipath.h b/include/scsi/scsi_multipath.h
new file mode 100644
index 0000000000000..d3d410dafd17a
--- /dev/null
+++ b/include/scsi/scsi_multipath.h
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _SCSI_SCSI_MULTIPATH_H
+#define _SCSI_SCSI_MULTIPATH_H
+
+#include <linux/list.h>
+#include <linux/types.h>
+#include <linux/rcupdate.h>
+#include <linux/workqueue.h>
+#include <linux/mutex.h>
+#include <linux/blk-mq.h>
+#include <linux/multipath.h>
+#include <scsi/scsi.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_dbg.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_devinfo.h>
+#include <scsi/scsi_driver.h>
+
+#ifdef CONFIG_SCSI_MULTIPATH
+#define SCSI_MPATH_DEVICE_ID_LEN 256
+
+struct scsi_mpath_device {
+ struct mpath_device mpath_device;
+ struct scsi_device *sdev;
+
+ char device_id_str[SCSI_MPATH_DEVICE_ID_LEN];
+};
+#define to_scsi_mpath_device(d) \
+ container_of(d, struct scsi_mpath_device, mpath_device)
+
+int scsi_mpath_dev_alloc(struct scsi_device *sdev);
+void scsi_mpath_dev_release(struct scsi_device *sdev);
+int scsi_multipath_init(void);
+void scsi_multipath_exit(void);
+#else /* CONFIG_SCSI_MULTIPATH */
+
+struct scsi_mpath_device {
+};
+
+static inline int scsi_mpath_dev_alloc(struct scsi_device *sdev)
+{
+ return 0;
+}
+static inline void scsi_mpath_dev_release(struct scsi_device *sdev)
+{
+}
+static inline int scsi_multipath_init(void)
+{
+ return 0;
+}
+static inline void scsi_multipath_exit(void)
+{
+}
+#endif /* CONFIG_SCSI_MULTIPATH */
+#endif /* _SCSI_SCSI_MULTIPATH_H */
--
2.43.7
^ permalink raw reply related [flat|nested] 71+ messages in thread* Re: [PATCH v5 11/27] scsi-multipath: introduce basic SCSI device support
2026-07-27 14:42 ` [PATCH v5 11/27] scsi-multipath: introduce basic SCSI device support John Garry
@ 2026-07-27 18:59 ` sashiko-bot
0 siblings, 0 replies; 71+ messages in thread
From: sashiko-bot @ 2026-07-27 18:59 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] scsi_mpath_unique_lun_id incorrectly accepts a 0-length LUN ID as a valid unique identifier.
--
--- Patch [11]: [PATCH v5 11/27] scsi-multipath: introduce basic SCSI device support ---
commit b27d35f732d009c9f3684249b66e5403b7b120c1
Author: John Garry <john.g.garry@oracle.com>
scsi-multipath: introduce basic SCSI device support
This commit introduces the scsi_mpath_device structure to hold
multipath-specific details for SCSI devices. It also adds a module
parameter to control multipath modes (on, off, always).
> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
> new file mode 100644
> index 0000000000000..947f3e2103e6b
> --- /dev/null
> +++ b/drivers/scsi/scsi_multipath.c
[ ... ]
> +static int scsi_mpath_unique_lun_id(struct scsi_device *sdev)
> +{
> + struct scsi_mpath_device *scsi_mpath_dev = sdev->scsi_mpath_dev;
> + int ret;
> +
> + ret = scsi_vpd_lun_id(sdev, scsi_mpath_dev->device_id_str,
> + SCSI_MPATH_DEVICE_ID_LEN);
> + if (ret < 0)
> + return ret;
[Severity: High]
If a SCSI device returns a 0-length designator in VPD page 83,
scsi_vpd_lun_id() will return 0. Does checking only for (ret < 0)
incorrectly accept a 0-length LUN ID as a valid unique identifier?
If multiple devices lack a unique ID and return 0, could they all be
assigned an empty string and incorrectly aggregated into the same
multipath group? This might risk routing I/Os intended for one device
to a completely different device.
> +
> + return 0;
> +}
> +
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727144234.3802948-1-john.g.garry@oracle.com?part=11
^ permalink raw reply [flat|nested] 71+ messages in thread
* [PATCH v5 12/27] scsi-multipath: introduce scsi_device head structure
2026-07-27 14:42 [PATCH v5 00/27] Native SCSI Multipath support John Garry
` (10 preceding siblings ...)
2026-07-27 14:42 ` [PATCH v5 11/27] scsi-multipath: introduce basic SCSI device support John Garry
@ 2026-07-27 14:42 ` John Garry
2026-07-27 15:15 ` sashiko-bot
2026-07-27 14:42 ` [PATCH v5 13/27] scsi-multipath: provide sysfs link from to scsi_device John Garry
` (14 subsequent siblings)
26 siblings, 1 reply; 71+ messages in thread
From: John Garry @ 2026-07-27 14:42 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
From: John Garry <john.garry@linux.dev>
Introduce a scsi_device head structure - scsi_mpath_head - to manage
multipathing for a scsi_device. This is similar to nvme_ns_head structure.
There is no reference in scsi_mpath_head to any disk, as this would be
managed by the scsi_disk driver.
A list of scsi_mpath_head structures is managed to lookup for matching
multipathed scsi_device's. Matching is done through the scsi_device
unique id.
A new class for multipathed devices is added, scsi_mpath_device_class.
The purpose of this class is for managing the scsi_mpath_head.dev member.
The naming for the scsi_device structure is in form H:C:I:L,
where H is host, C is channel, I is ID, and L is lun.
However, for a multipathed scsi_device, all the naming members may be
different between member scsi_device's. As such, just use a simple
single-number naming index for each scsi_mpath_head.
The sysfs device folder will have links to the scsi_device's so, it will
be possible to lookup the member scsi_device's.
An example sysfs entry is as follows:
# ls -l /sys/class/scsi_mpath_device/scsi_mpath_device0/
total 0
drwxr-xr-x 2 root root 0 Apr 13 15:48 power
lrwxrwxrwx 1 root root 0 Apr 13 15:48 subsystem -> ../../../../class/scsi_mpath_device
-rw-r--r-- 1 root root 4096 Apr 13 15:48 uevent
-r--r--r-- 1 root root 4096 Apr 13 15:48 vpd_id
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/scsi_multipath.c | 199 +++++++++++++++++++++++++++++++++-
drivers/scsi/scsi_sysfs.c | 3 +
include/scsi/scsi_multipath.h | 31 ++++++
3 files changed, 231 insertions(+), 2 deletions(-)
diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
index 947f3e2103e6b..5d19ab6a89eb5 100644
--- a/drivers/scsi/scsi_multipath.c
+++ b/drivers/scsi/scsi_multipath.c
@@ -29,6 +29,10 @@ static const char *scsi_multipath_modes[] = {
static int scsi_multipath = SCSI_MULTIPATH_OFF;
+static LIST_HEAD(scsi_mpath_heads_list);
+static DEFINE_MUTEX(scsi_mpath_heads_lock);
+static DEFINE_IDA(scsi_multipath_dev_ida);
+
static int scsi_multipath_param_set(const char *val, const struct kernel_param *kp)
{
int mode;
@@ -70,6 +74,55 @@ static int scsi_mpath_unique_lun_id(struct scsi_device *sdev)
return 0;
}
+static void scsi_mpath_head_release(struct device *dev)
+{
+ struct scsi_mpath_head *scsi_mpath_head =
+ container_of(dev, struct scsi_mpath_head, dev);
+ struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
+
+ ida_free(&scsi_multipath_dev_ida, scsi_mpath_head->index);
+ ida_destroy(&scsi_mpath_head->ida);
+ mpath_head_uninit(mpath_head);
+ kfree(scsi_mpath_head);
+}
+
+static ssize_t scsi_mpath_device_vpd_id_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct scsi_mpath_head *scsi_mpath_head =
+ container_of(dev, struct scsi_mpath_head, dev);
+
+ return sysfs_emit(buf, "%s\n", scsi_mpath_head->vpd_id);
+}
+static DEVICE_ATTR(vpd_id, S_IRUGO, scsi_mpath_device_vpd_id_show, NULL);
+
+static struct attribute *scsi_mpath_device_attrs[] = {
+ &dev_attr_vpd_id.attr,
+ NULL
+};
+
+static const struct attribute_group scsi_mpath_device_attrs_group = {
+ .attrs = scsi_mpath_device_attrs,
+};
+
+static bool scsi_multipath_sysfs_group_visible(struct kobject *kobj)
+{
+ return true;
+}
+DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(scsi_multipath_sysfs)
+
+static const struct attribute_group *scsi_mpath_device_groups[] = {
+ &scsi_mpath_device_attrs_group,
+ NULL
+};
+
+static const struct class scsi_mpath_device_class = {
+ .name = "scsi_mpath_device",
+ .dev_groups = scsi_mpath_device_groups,
+ .dev_release = scsi_mpath_head_release,
+};
+
static int scsi_multipath_sdev_init(struct scsi_device *sdev)
{
struct Scsi_Host *shost = sdev->host;
@@ -89,6 +142,74 @@ static int scsi_multipath_sdev_init(struct scsi_device *sdev)
return 0;
}
+static struct mpath_head_template smpdt = {
+};
+
+static struct scsi_mpath_head *scsi_mpath_alloc_head(char *vpd_id)
+{
+ struct scsi_mpath_head *scsi_mpath_head;
+ int ret;
+
+ scsi_mpath_head = kzalloc(sizeof(*scsi_mpath_head), GFP_KERNEL);
+ if (!scsi_mpath_head)
+ return NULL;
+
+ ida_init(&scsi_mpath_head->ida);
+
+ if (mpath_head_init(&scsi_mpath_head->mpath_head))
+ goto out_free;
+ scsi_mpath_head->mpath_head.mpdt = &smpdt;
+
+ strscpy(scsi_mpath_head->vpd_id, vpd_id,
+ SCSI_MPATH_DEVICE_ID_LEN);
+
+ scsi_mpath_head->index = ida_alloc(&scsi_multipath_dev_ida, GFP_KERNEL);
+ if (scsi_mpath_head->index < 0)
+ goto out_uninit_head;
+ kref_init(&scsi_mpath_head->ref);
+
+ device_initialize(&scsi_mpath_head->dev);
+ scsi_mpath_head->dev.class = &scsi_mpath_device_class;
+ ret = dev_set_name(&scsi_mpath_head->dev, "scsi_mpath_device%d",
+ scsi_mpath_head->index);
+ if (ret) {
+ put_device(&scsi_mpath_head->dev);
+ return NULL;
+ }
+
+ ret = device_add(&scsi_mpath_head->dev);
+ if (ret) {
+ put_device(&scsi_mpath_head->dev);
+ return NULL;
+ }
+
+ return scsi_mpath_head;
+
+out_uninit_head:
+ mpath_head_uninit(&scsi_mpath_head->mpath_head);
+out_free:
+ kfree(scsi_mpath_head);
+ return NULL;
+}
+
+static struct scsi_mpath_head *scsi_mpath_find_head(
+ struct scsi_mpath_device *scsi_mpath_dev)
+{
+ struct scsi_mpath_head *scsi_mpath_head;
+
+ list_for_each_entry(scsi_mpath_head, &scsi_mpath_heads_list, entry) {
+ if (strncmp(scsi_mpath_head->vpd_id,
+ scsi_mpath_dev->device_id_str,
+ SCSI_MPATH_DEVICE_ID_LEN) == 0) {
+ if (scsi_mpath_get_head(scsi_mpath_head))
+ continue;
+ return scsi_mpath_head;
+ }
+ }
+
+ return NULL;
+}
+
static void scsi_multipath_sdev_uninit(struct scsi_device *sdev)
{
kfree(sdev->scsi_mpath_dev);
@@ -97,6 +218,7 @@ static void scsi_multipath_sdev_uninit(struct scsi_device *sdev)
int scsi_mpath_dev_alloc(struct scsi_device *sdev)
{
+ struct scsi_mpath_head *scsi_mpath_head;
int ret;
if (scsi_multipath == SCSI_MULTIPATH_OFF)
@@ -116,13 +238,51 @@ int scsi_mpath_dev_alloc(struct scsi_device *sdev)
if (ret < 0)
goto out_uninit;
- return 0;
+ mutex_lock(&scsi_mpath_heads_lock);
+ scsi_mpath_head = scsi_mpath_find_head(sdev->scsi_mpath_dev);
+ if (scsi_mpath_head)
+ goto found;
+ scsi_mpath_head =
+ scsi_mpath_alloc_head(sdev->scsi_mpath_dev->device_id_str);
+ if (!scsi_mpath_head) {
+ sdev_printk(KERN_NOTICE, sdev, "could not allocate multipath head, device multipathing disabled\n");
+ mutex_unlock(&scsi_mpath_heads_lock);
+ goto out_uninit;
+ }
+ list_add_tail(&scsi_mpath_head->entry, &scsi_mpath_heads_list);
+found:
+ mutex_unlock(&scsi_mpath_heads_lock);
+ ret = ida_alloc(&scsi_mpath_head->ida, GFP_KERNEL);
+ if (ret < 0)
+ goto out_put_head;
+ sdev->scsi_mpath_dev->index = ret;
+ sdev->scsi_mpath_dev->scsi_mpath_head = scsi_mpath_head;
+ sdev->scsi_mpath_dev->mpath_device.mpath_head =
+ &scsi_mpath_head->mpath_head;
+ return 0;
+out_put_head:
+ scsi_mpath_put_head(scsi_mpath_head);
out_uninit:
scsi_multipath_sdev_uninit(sdev);
return 0;
}
+static void scsi_mpath_remove_head(struct scsi_mpath_device *scsi_mpath_dev)
+{
+ scsi_mpath_put_head(scsi_mpath_dev->scsi_mpath_head);
+ scsi_mpath_dev->scsi_mpath_head = NULL;
+}
+
+void scsi_mpath_remove_device(struct scsi_mpath_device *scsi_mpath_dev)
+{
+ struct scsi_mpath_head *scsi_mpath_head = scsi_mpath_dev->scsi_mpath_head;
+
+ ida_free(&scsi_mpath_head->ida, scsi_mpath_dev->index);
+
+ scsi_mpath_remove_head(scsi_mpath_dev);
+}
+
void scsi_mpath_dev_release(struct scsi_device *sdev)
{
struct scsi_mpath_device *scsi_mpath_dev = sdev->scsi_mpath_dev;
@@ -133,13 +293,48 @@ void scsi_mpath_dev_release(struct scsi_device *sdev)
scsi_multipath_sdev_uninit(sdev);
}
-int __init scsi_multipath_init(void)
+int scsi_mpath_get_head(struct scsi_mpath_head *scsi_mpath_head)
{
+ if (!kref_get_unless_zero(&scsi_mpath_head->ref))
+ return -ENXIO;
return 0;
}
+EXPORT_SYMBOL_GPL(scsi_mpath_get_head);
+
+static void scsi_mpath_free_head(struct kref *ref)
+{
+ struct scsi_mpath_head *scsi_mpath_head =
+ container_of(ref, struct scsi_mpath_head, ref);
+
+ /*
+ * If we race with scsi_mpath_find_head(), then that function may
+ * find this scsi_mpath_head in the heads list; however we would fail
+ * to take a reference to this scsi_mpath_head and continue the search.
+ * As such, it is safe to call device_unregister (and free
+ * scsi_mpath_head) after we delete this head from the list.
+ */
+ mutex_lock(&scsi_mpath_heads_lock);
+ list_del_init(&scsi_mpath_head->entry);
+ mutex_unlock(&scsi_mpath_heads_lock);
+
+ device_unregister(&scsi_mpath_head->dev);
+}
+
+void scsi_mpath_put_head(struct scsi_mpath_head *scsi_mpath_head)
+{
+ kref_put(&scsi_mpath_head->ref, scsi_mpath_free_head);
+}
+EXPORT_SYMBOL_GPL(scsi_mpath_put_head);
+
+int __init scsi_multipath_init(void)
+{
+ return class_register(&scsi_mpath_device_class);
+}
void __exit scsi_multipath_exit(void)
{
+ ida_destroy(&scsi_multipath_dev_ida);
+ class_unregister(&scsi_mpath_device_class);
}
MODULE_LICENSE("GPL");
diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c
index 2f80d703ce640..799569b74b535 100644
--- a/drivers/scsi/scsi_sysfs.c
+++ b/drivers/scsi/scsi_sysfs.c
@@ -1525,6 +1525,9 @@ void __scsi_remove_device(struct scsi_device *sdev)
*/
scsi_target_reap(scsi_target(sdev));
+ if (sdev->scsi_mpath_dev)
+ scsi_mpath_remove_device(sdev->scsi_mpath_dev);
+
put_device(dev);
}
diff --git a/include/scsi/scsi_multipath.h b/include/scsi/scsi_multipath.h
index d3d410dafd17a..50298056181f9 100644
--- a/include/scsi/scsi_multipath.h
+++ b/include/scsi/scsi_multipath.h
@@ -19,12 +19,25 @@
#ifdef CONFIG_SCSI_MULTIPATH
#define SCSI_MPATH_DEVICE_ID_LEN 256
+struct scsi_mpath_head {
+ struct mpath_head mpath_head;
+ char vpd_id[SCSI_MPATH_DEVICE_ID_LEN];
+ struct list_head entry;
+ struct ida ida;
+ struct kref ref;
+ struct device dev;
+ int index;
+};
+
struct scsi_mpath_device {
struct mpath_device mpath_device;
struct scsi_device *sdev;
+ int index;
+ struct scsi_mpath_head *scsi_mpath_head;
char device_id_str[SCSI_MPATH_DEVICE_ID_LEN];
};
+
#define to_scsi_mpath_device(d) \
container_of(d, struct scsi_mpath_device, mpath_device)
@@ -32,8 +45,13 @@ int scsi_mpath_dev_alloc(struct scsi_device *sdev);
void scsi_mpath_dev_release(struct scsi_device *sdev);
int scsi_multipath_init(void);
void scsi_multipath_exit(void);
+void scsi_mpath_remove_device(struct scsi_mpath_device *scsi_mpath_dev);
+int scsi_mpath_get_head(struct scsi_mpath_head *scsi_mpath_head);
+void scsi_mpath_put_head(struct scsi_mpath_head *scsi_mpath_head);
#else /* CONFIG_SCSI_MULTIPATH */
+struct scsi_mpath_head {
+};
struct scsi_mpath_device {
};
@@ -51,5 +69,18 @@ static inline int scsi_multipath_init(void)
static inline void scsi_multipath_exit(void)
{
}
+static inline
+void scsi_mpath_remove_device(struct scsi_mpath_device *scsi_mpath_dev)
+{
+}
+static inline
+int scsi_mpath_get_head(struct scsi_mpath_head *scsi_mpath_head)
+{
+ return 0;
+}
+static inline
+void scsi_mpath_put_head(struct scsi_mpath_head *scsi_mpath_head)
+{
+}
#endif /* CONFIG_SCSI_MULTIPATH */
#endif /* _SCSI_SCSI_MULTIPATH_H */
--
2.43.7
^ permalink raw reply related [flat|nested] 71+ messages in thread* Re: [PATCH v5 12/27] scsi-multipath: introduce scsi_device head structure
2026-07-27 14:42 ` [PATCH v5 12/27] scsi-multipath: introduce scsi_device head structure John Garry
@ 2026-07-27 15:15 ` sashiko-bot
2026-07-27 15:37 ` John Garry
0 siblings, 1 reply; 71+ messages in thread
From: sashiko-bot @ 2026-07-27 15:15 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] The `smpdt` (mpath_head_template) is defined without implementing logically required callbacks, which will lead to a NULL pointer dereference.
- [High] scsi_mpath_unique_lun_id treats a 0-length Unique LUN ID as success, leading to unintended grouping of devices with empty IDs.
- [Low] The function `scsi_multipath_sysfs_group_visible` and the macro invocation `DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(scsi_multipath_sysfs)` are defined but never used.
--
--- Patch [12]: [PATCH v5 12/27] scsi-multipath: introduce scsi_device head structure ---
commit 0296ab446bccfc5a00926fcca72b8b18f69d4e30
Author: John Garry <john.garry@linux.dev>
Subject: scsi-multipath: introduce scsi_device head structure
Introduce a scsi_device head structure - scsi_mpath_head - to manage
multipathing for a scsi_device. This is similar to nvme_ns_head structure.
A list of scsi_mpath_head structures is managed to lookup for matching
multipathed scsi_device's.
> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
> index 947f3e2103e6b..5d19ab6a89eb5 100644
> --- a/drivers/scsi/scsi_multipath.c
> +++ b/drivers/scsi/scsi_multipath.c
> @@ -70,6 +74,55 @@ static int scsi_mpath_unique_lun_id(struct scsi_device *sdev)
[Severity: High]
Does this function mistakenly treat a 0-length Unique LUN ID as success?
Looking at scsi_mpath_unique_lun_id() in drivers/scsi/scsi_multipath.c:
ret = scsi_vpd_lun_id(sdev, scsi_mpath_dev->device_id_str,
SCSI_MPATH_DEVICE_ID_LEN);
if (ret < 0)
return ret;
If a SCSI device responds with a valid VPD page 0x83 but a 0-length SCSI name
string, scsi_vpd_lun_id() returns 0. Since this code only checks if (ret < 0),
it will accept 0 as success.
This could cause the multipath subsystem to incorrectly group distinct devices
with empty IDs together, as strncmp("", "", 256) will match, routing I/O
intended for one disk to another entirely separate disk.
> return 0;
> }
>
> +static void scsi_mpath_head_release(struct device *dev)
[ ... ]
> +static bool scsi_multipath_sysfs_group_visible(struct kobject *kobj)
> +{
> + return true;
> +}
> +DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(scsi_multipath_sysfs)
[Severity: Low]
Are this function and macro invocation needed?
They appear to be defined but never used to gate an attribute group in this
patch.
> +
> +static const struct attribute_group *scsi_mpath_device_groups[] = {
[ ... ]
> @@ -89,6 +142,74 @@ static int scsi_multipath_sdev_init(struct scsi_device *sdev)
> return 0;
> }
>
> +static struct mpath_head_template smpdt = {
> +};
[Severity: High]
Can this empty template cause a NULL pointer dereference?
Because smpdt is defined as an empty struct, all its function pointers are
NULL. The multipath core unconditionally invokes template callbacks (such as
is_disabled and is_optimized) without checking for NULL.
When a multipath SCSI device is configured and processed, invoking these
callbacks could trigger a kernel panic.
> +
> +static struct scsi_mpath_head *scsi_mpath_alloc_head(char *vpd_id)
> +{
> + struct scsi_mpath_head *scsi_mpath_head;
[ ... ]
> + if (mpath_head_init(&scsi_mpath_head->mpath_head))
> + goto out_free;
> + scsi_mpath_head->mpath_head.mpdt = &smpdt;
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727144234.3802948-1-john.g.garry@oracle.com?part=12
^ permalink raw reply [flat|nested] 71+ messages in thread* Re: [PATCH v5 12/27] scsi-multipath: introduce scsi_device head structure
2026-07-27 15:15 ` sashiko-bot
@ 2026-07-27 15:37 ` John Garry
0 siblings, 0 replies; 71+ messages in thread
From: John Garry @ 2026-07-27 15:37 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 27/07/2026 16:15, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
> - [High] The `smpdt` (mpath_head_template) is defined without implementing logically required callbacks, which will lead to a NULL pointer dereference.
> - [High] scsi_mpath_unique_lun_id treats a 0-length Unique LUN ID as success, leading to unintended grouping of devices with empty IDs.
> - [Low] The function `scsi_multipath_sysfs_group_visible` and the macro invocation `DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(scsi_multipath_sysfs)` are defined but never used.
> --
>
> --- Patch [12]: [PATCH v5 12/27] scsi-multipath: introduce scsi_device head structure ---
> commit 0296ab446bccfc5a00926fcca72b8b18f69d4e30
> Author: John Garry <john.garry@linux.dev>
> Subject: scsi-multipath: introduce scsi_device head structure
>
> Introduce a scsi_device head structure - scsi_mpath_head - to manage
> multipathing for a scsi_device. This is similar to nvme_ns_head structure.
> A list of scsi_mpath_head structures is managed to lookup for matching
> multipathed scsi_device's.
>
>> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
>> index 947f3e2103e6b..5d19ab6a89eb5 100644
>> --- a/drivers/scsi/scsi_multipath.c
>> +++ b/drivers/scsi/scsi_multipath.c
>> @@ -70,6 +74,55 @@ static int scsi_mpath_unique_lun_id(struct scsi_device *sdev)
>
> [Severity: High]
> Does this function mistakenly treat a 0-length Unique LUN ID as success?
>
> Looking at scsi_mpath_unique_lun_id() in drivers/scsi/scsi_multipath.c:
>
> ret = scsi_vpd_lun_id(sdev, scsi_mpath_dev->device_id_str,
> SCSI_MPATH_DEVICE_ID_LEN);
> if (ret < 0)
> return ret;
>
> If a SCSI device responds with a valid VPD page 0x83 but a 0-length SCSI name
> string, scsi_vpd_lun_id() returns 0. Since this code only checks if (ret < 0),
> it will accept 0 as success.
hmmm ... in checking scsi_vpd_lun_id(), I don't see how the length could
ever be zero
>
> This could cause the multipath subsystem to incorrectly group distinct devices
> with empty IDs together, as strncmp("", "", 256) will match, routing I/O
> intended for one disk to another entirely separate disk.
>
>> return 0;
>> }
>>
>> +static void scsi_mpath_head_release(struct device *dev)
> [ ... ]
>> +static bool scsi_multipath_sysfs_group_visible(struct kobject *kobj)
>> +{
>> + return true;
>> +}
>> +DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(scsi_multipath_sysfs)
>
> [Severity: Low]
> Are this function and macro invocation needed?
>
> They appear to be defined but never used to gate an attribute group in this
> patch.
used later
>
>> +
>> +static const struct attribute_group *scsi_mpath_device_groups[] = {
> [ ... ]
>> @@ -89,6 +142,74 @@ static int scsi_multipath_sdev_init(struct scsi_device *sdev)
>> return 0;
>> }
>>
>> +static struct mpath_head_template smpdt = {
>> +};
>
> [Severity: High]
> Can this empty template cause a NULL pointer dereference?
>
> Because smpdt is defined as an empty struct, all its function pointers are
> NULL. The multipath core unconditionally invokes template callbacks (such as
> is_disabled and is_optimized) without checking for NULL.
>
> When a multipath SCSI device is configured and processed, invoking these
> callbacks could trigger a kernel panic.
they are added later
>
>> +
>> +static struct scsi_mpath_head *scsi_mpath_alloc_head(char *vpd_id)
>> +{
>> + struct scsi_mpath_head *scsi_mpath_head;
> [ ... ]
>> + if (mpath_head_init(&scsi_mpath_head->mpath_head))
>> + goto out_free;
>> + scsi_mpath_head->mpath_head.mpdt = &smpdt;
>
^ permalink raw reply [flat|nested] 71+ messages in thread
* [PATCH v5 13/27] scsi-multipath: provide sysfs link from to scsi_device
2026-07-27 14:42 [PATCH v5 00/27] Native SCSI Multipath support John Garry
` (11 preceding siblings ...)
2026-07-27 14:42 ` [PATCH v5 12/27] scsi-multipath: introduce scsi_device head structure John Garry
@ 2026-07-27 14:42 ` John Garry
2026-07-27 15:07 ` sashiko-bot
2026-07-27 14:42 ` [PATCH v5 14/27] scsi-multipath: support iopolicy John Garry
` (13 subsequent siblings)
26 siblings, 1 reply; 71+ messages in thread
From: John Garry @ 2026-07-27 14:42 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, Hannes Reinecke, John Garry
From: John Garry <john.garry@linux.dev>
Provide a link in sysfs from a scsi_mpath_device to member scsi_device's.
An example is as follows:
# ls -l /sys/class/scsi_mpath_device/scsi_mpath_device0/multipath/
total 0
lrwxrwxrwx 1 root root 0 Feb 24 12:01 8:0:0:0 -> ../../../../platform/host8/session1/target8:0:0/8:0:0:0
lrwxrwxrwx 1 root root 0 Feb 24 12:01 9:0:0:0 -> ../../../../platform/host9/session2/target9:0:0/9:0:0:0
Reviewed-by: Hannes Reinecke <hare@suse.de>
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/scsi_multipath.c | 43 +++++++++++++++++++++++++++++++++++
drivers/scsi/scsi_sysfs.c | 5 ++++
include/scsi/scsi_multipath.h | 8 +++++++
3 files changed, 56 insertions(+)
diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
index 5d19ab6a89eb5..1866f282c15cc 100644
--- a/drivers/scsi/scsi_multipath.c
+++ b/drivers/scsi/scsi_multipath.c
@@ -112,11 +112,54 @@ static bool scsi_multipath_sysfs_group_visible(struct kobject *kobj)
}
DEFINE_SIMPLE_SYSFS_GROUP_VISIBLE(scsi_multipath_sysfs)
+static struct attribute dummy_attr = {
+ .name = "dummy",
+};
+
+static struct attribute *scsi_mpath_attrs[] = {
+ &dummy_attr,
+ NULL
+};
+
+static const struct attribute_group scsi_mpath_attr_group = {
+ .name = "multipath",
+ .attrs = scsi_mpath_attrs,
+ .is_visible = SYSFS_GROUP_VISIBLE(scsi_multipath_sysfs),
+};
+
static const struct attribute_group *scsi_mpath_device_groups[] = {
&scsi_mpath_device_attrs_group,
+ &scsi_mpath_attr_group,
NULL
};
+void scsi_mpath_add_sysfs_link(struct scsi_device *sdev)
+{
+ struct device *target = &sdev->sdev_gendev;
+ struct scsi_mpath_head *scsi_mpath_head =
+ sdev->scsi_mpath_dev->scsi_mpath_head;
+ struct device *source = &scsi_mpath_head->dev;
+ int error;
+
+ error = sysfs_add_link_to_group(&source->kobj, "multipath",
+ &target->kobj, dev_name(target));
+ if (error) {
+ sdev_printk(KERN_INFO, sdev, "Failed to create mpath sysfs link, error=%d\n",
+ error);
+ }
+}
+
+void scsi_mpath_remove_sysfs_link(struct scsi_device *sdev)
+{
+ struct device *target = &sdev->sdev_gendev;
+ struct scsi_mpath_head *scsi_mpath_head =
+ sdev->scsi_mpath_dev->scsi_mpath_head;
+ struct device *source = &scsi_mpath_head->dev;
+
+ sysfs_remove_link_from_group(&source->kobj, "multipath",
+ dev_name(target));
+}
+
static const struct class scsi_mpath_device_class = {
.name = "scsi_mpath_device",
.dev_groups = scsi_mpath_device_groups,
diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c
index 799569b74b535..ce51fcc002bbf 100644
--- a/drivers/scsi/scsi_sysfs.c
+++ b/drivers/scsi/scsi_sysfs.c
@@ -1443,6 +1443,9 @@ int scsi_sysfs_add_sdev(struct scsi_device *sdev)
transport_add_device(&sdev->sdev_gendev);
sdev->is_visible = 1;
+ if (sdev->scsi_mpath_dev)
+ scsi_mpath_add_sysfs_link(sdev);
+
if (IS_ENABLED(CONFIG_BLK_DEV_BSG)) {
sdev->bsg_dev = scsi_bsg_register_queue(sdev);
if (IS_ERR(sdev->bsg_dev)) {
@@ -1495,6 +1498,8 @@ void __scsi_remove_device(struct scsi_device *sdev)
if (IS_ENABLED(CONFIG_BLK_DEV_BSG) && sdev->bsg_dev)
bsg_unregister_queue(sdev->bsg_dev);
+ if (sdev->scsi_mpath_dev)
+ scsi_mpath_remove_sysfs_link(sdev);
device_unregister(&sdev->sdev_dev);
transport_remove_device(dev);
device_del(dev);
diff --git a/include/scsi/scsi_multipath.h b/include/scsi/scsi_multipath.h
index 50298056181f9..a9fd02bc42371 100644
--- a/include/scsi/scsi_multipath.h
+++ b/include/scsi/scsi_multipath.h
@@ -46,6 +46,8 @@ void scsi_mpath_dev_release(struct scsi_device *sdev);
int scsi_multipath_init(void);
void scsi_multipath_exit(void);
void scsi_mpath_remove_device(struct scsi_mpath_device *scsi_mpath_dev);
+void scsi_mpath_add_sysfs_link(struct scsi_device *sdev);
+void scsi_mpath_remove_sysfs_link(struct scsi_device *sdev);
int scsi_mpath_get_head(struct scsi_mpath_head *scsi_mpath_head);
void scsi_mpath_put_head(struct scsi_mpath_head *scsi_mpath_head);
#else /* CONFIG_SCSI_MULTIPATH */
@@ -82,5 +84,11 @@ static inline
void scsi_mpath_put_head(struct scsi_mpath_head *scsi_mpath_head)
{
}
+static inline void scsi_mpath_add_sysfs_link(struct scsi_device *sdev)
+{
+}
+static inline void scsi_mpath_remove_sysfs_link(struct scsi_device *sdev)
+{
+}
#endif /* CONFIG_SCSI_MULTIPATH */
#endif /* _SCSI_SCSI_MULTIPATH_H */
--
2.43.7
^ permalink raw reply related [flat|nested] 71+ messages in thread* Re: [PATCH v5 13/27] scsi-multipath: provide sysfs link from to scsi_device
2026-07-27 14:42 ` [PATCH v5 13/27] scsi-multipath: provide sysfs link from to scsi_device John Garry
@ 2026-07-27 15:07 ` sashiko-bot
2026-07-27 16:21 ` John Garry
0 siblings, 1 reply; 71+ messages in thread
From: sashiko-bot @ 2026-07-27 15:07 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Missing cancellation of mpath_head->requeue_work causes Use-After-Free when tearing down the multipath head.
--
--- Patch [13]: [PATCH v5 13/27] scsi-multipath: provide sysfs link from to scsi_device ---
commit 7fc63465ac8f13d95c759573374eebed7fbf19d2
Author: John Garry <john.garry@linux.dev>
scsi-multipath: provide sysfs link from to scsi_device
This commit provides a sysfs link from a scsi_mpath_device to member
scsi_devices. It adds functions to create and remove these multipath
sysfs links.
[ ... ]
> diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c
> index 799569b74b535..ce51fcc002bbf 100644
> --- a/drivers/scsi/scsi_sysfs.c
> +++ b/drivers/scsi/scsi_sysfs.c
[ ... ]
> @@ -1495,6 +1498,8 @@ void __scsi_remove_device(struct scsi_device *sdev)
>
> if (IS_ENABLED(CONFIG_BLK_DEV_BSG) && sdev->bsg_dev)
> bsg_unregister_queue(sdev->bsg_dev);
> + if (sdev->scsi_mpath_dev)
> + scsi_mpath_remove_sysfs_link(sdev);
> device_unregister(&sdev->sdev_dev);
[Severity: High]
This is a pre-existing issue, but does the teardown of the multipath head
during device removal miss cancelling mpath_head->requeue_work?
Writing to the iopolicy sysfs attribute invokes
scsi_mpath_device_iopolicy_store(), which schedules the work:
drivers/scsi/scsi_multipath.c:scsi_mpath_device_iopolicy_store() {
...
mpath_schedule_requeue_work(mpath_head);
...
}
If the SCSI device is removed concurrently (triggering
scsi_mpath_remove_device() and dropping the final reference), the release
function frees the containing structure without ensuring the work is cancelled:
drivers/scsi/scsi_multipath.c:scsi_mpath_head_release() {
...
mpath_head_uninit(mpath_head);
kfree(scsi_mpath_head);
}
Should mpath_head_uninit() in lib/multipath.c call cancel_work_sync()
to prevent a use-after-free when the requeue work executes on the freed
scsi_mpath_head memory?
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727144234.3802948-1-john.g.garry@oracle.com?part=13
^ permalink raw reply [flat|nested] 71+ messages in thread* Re: [PATCH v5 13/27] scsi-multipath: provide sysfs link from to scsi_device
2026-07-27 15:07 ` sashiko-bot
@ 2026-07-27 16:21 ` John Garry
0 siblings, 0 replies; 71+ messages in thread
From: John Garry @ 2026-07-27 16:21 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 27/07/2026 16:07, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
>
> Pre-existing issues:
> - [High] Missing cancellation of mpath_head->requeue_work causes Use-After-Free when tearing down the multipath head.
:(
> --
>
> --- Patch [13]: [PATCH v5 13/27] scsi-multipath: provide sysfs link from to scsi_device ---
> commit 7fc63465ac8f13d95c759573374eebed7fbf19d2
> Author: John Garry <john.garry@linux.dev>
>
> scsi-multipath: provide sysfs link from to scsi_device
>
> This commit provides a sysfs link from a scsi_mpath_device to member
> scsi_devices. It adds functions to create and remove these multipath
> sysfs links.
>
> [ ... ]
>
>> diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c
>> index 799569b74b535..ce51fcc002bbf 100644
>> --- a/drivers/scsi/scsi_sysfs.c
>> +++ b/drivers/scsi/scsi_sysfs.c
>
> [ ... ]
>
>> @@ -1495,6 +1498,8 @@ void __scsi_remove_device(struct scsi_device *sdev)
>>
>> if (IS_ENABLED(CONFIG_BLK_DEV_BSG) && sdev->bsg_dev)
>> bsg_unregister_queue(sdev->bsg_dev);
>> + if (sdev->scsi_mpath_dev)
>> + scsi_mpath_remove_sysfs_link(sdev);
>> device_unregister(&sdev->sdev_dev);
>
> [Severity: High]
> This is a pre-existing issue, but does the teardown of the multipath head
> during device removal miss cancelling mpath_head->requeue_work?
>
> Writing to the iopolicy sysfs attribute invokes
> scsi_mpath_device_iopolicy_store(), which schedules the work:
>
> drivers/scsi/scsi_multipath.c:scsi_mpath_device_iopolicy_store() {
> ...
> mpath_schedule_requeue_work(mpath_head);
> ...
> }
>
> If the SCSI device is removed concurrently (triggering
> scsi_mpath_remove_device() and dropping the final reference), the release
> function frees the containing structure without ensuring the work is cancelled:
>
> drivers/scsi/scsi_multipath.c:scsi_mpath_head_release() {
> ...
> mpath_head_uninit(mpath_head);
> kfree(scsi_mpath_head);
> }
>
> Should mpath_head_uninit() in lib/multipath.c call cancel_work_sync()
> to prevent a use-after-free when the requeue work executes on the freed
> scsi_mpath_head memory?
>
> [ ... ]
>
^ permalink raw reply [flat|nested] 71+ messages in thread
* [PATCH v5 14/27] scsi-multipath: support iopolicy
2026-07-27 14:42 [PATCH v5 00/27] Native SCSI Multipath support John Garry
` (12 preceding siblings ...)
2026-07-27 14:42 ` [PATCH v5 13/27] scsi-multipath: provide sysfs link from to scsi_device John Garry
@ 2026-07-27 14:42 ` John Garry
2026-07-27 15:06 ` sashiko-bot
2026-07-27 14:42 ` [PATCH v5 15/27] scsi-multipath: clone each bio John Garry
` (12 subsequent siblings)
26 siblings, 1 reply; 71+ messages in thread
From: John Garry @ 2026-07-27 14:42 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
Add support to set the multipath iopolicy.
The iopolicy member is per scsi_mpath_head structure.
A module param is added so that the default iopolicy may be set.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/scsi_multipath.c | 47 +++++++++++++++++++++++++++++++++++
include/scsi/scsi_multipath.h | 3 +++
2 files changed, 50 insertions(+)
diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
index 1866f282c15cc..1b955e19a6814 100644
--- a/drivers/scsi/scsi_multipath.c
+++ b/drivers/scsi/scsi_multipath.c
@@ -61,6 +61,23 @@ static const struct kernel_param_ops multipath_param_ops = {
module_param_cb(multipath, &multipath_param_ops, &scsi_multipath, 0444);
MODULE_PARM_DESC(multipath, "turn on native multipath support, options: on, off, always");
+static enum mpath_iopolicy_e iopolicy = MPATH_IOPOLICY_NUMA;
+
+static int scsi_mpath_set_iopolicy_param(const char *val, const struct kernel_param *kp)
+{
+ return mpath_set_iopolicy(val, &iopolicy);
+}
+
+static int scsi_mpath_get_iopolicy_param(char *buf, const struct kernel_param *kp)
+{
+ return mpath_get_iopolicy(buf, iopolicy);
+}
+
+module_param_call(multipath_iopolicy, scsi_mpath_set_iopolicy_param,
+ scsi_mpath_get_iopolicy_param, &iopolicy, 0644);
+MODULE_PARM_DESC(multipath_iopolicy,
+ "Default multipath I/O policy; 'numa' (default), 'round-robin' or 'queue-depth'");
+
static int scsi_mpath_unique_lun_id(struct scsi_device *sdev)
{
struct scsi_mpath_device *scsi_mpath_dev = sdev->scsi_mpath_dev;
@@ -97,8 +114,36 @@ static ssize_t scsi_mpath_device_vpd_id_show(struct device *dev,
}
static DEVICE_ATTR(vpd_id, S_IRUGO, scsi_mpath_device_vpd_id_show, NULL);
+static ssize_t scsi_mpath_device_iopolicy_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct scsi_mpath_head *scsi_mpath_head =
+ container_of(dev, struct scsi_mpath_head, dev);
+ struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
+
+ if (!mpath_iopolicy_store(&scsi_mpath_head->iopolicy, buf))
+ return -EINVAL;
+
+ mpath_clear_paths(mpath_head);
+ mpath_schedule_requeue_work(mpath_head);
+ return count;
+}
+
+static ssize_t scsi_mpath_device_iopolicy_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct scsi_mpath_head *scsi_mpath_head =
+ container_of(dev, struct scsi_mpath_head, dev);
+
+ return mpath_iopolicy_show(&scsi_mpath_head->iopolicy, buf);
+}
+
+static DEVICE_ATTR(iopolicy, S_IRUGO | S_IWUSR,
+ scsi_mpath_device_iopolicy_show, scsi_mpath_device_iopolicy_store);
+
static struct attribute *scsi_mpath_device_attrs[] = {
&dev_attr_vpd_id.attr,
+ &dev_attr_iopolicy.attr,
NULL
};
@@ -202,6 +247,8 @@ static struct scsi_mpath_head *scsi_mpath_alloc_head(char *vpd_id)
if (mpath_head_init(&scsi_mpath_head->mpath_head))
goto out_free;
scsi_mpath_head->mpath_head.mpdt = &smpdt;
+ scsi_mpath_head->iopolicy = iopolicy;
+ scsi_mpath_head->mpath_head.iopolicy = &scsi_mpath_head->iopolicy;
strscpy(scsi_mpath_head->vpd_id, vpd_id,
SCSI_MPATH_DEVICE_ID_LEN);
diff --git a/include/scsi/scsi_multipath.h b/include/scsi/scsi_multipath.h
index a9fd02bc42371..2dc02313b0496 100644
--- a/include/scsi/scsi_multipath.h
+++ b/include/scsi/scsi_multipath.h
@@ -25,6 +25,7 @@ struct scsi_mpath_head {
struct list_head entry;
struct ida ida;
struct kref ref;
+ enum mpath_iopolicy_e iopolicy;
struct device dev;
int index;
};
@@ -40,6 +41,8 @@ struct scsi_mpath_device {
#define to_scsi_mpath_device(d) \
container_of(d, struct scsi_mpath_device, mpath_device)
+#define to_scsi_mpath_head(d) \
+ container_of(d, struct scsi_mpath_head, mpath_head)
int scsi_mpath_dev_alloc(struct scsi_device *sdev);
void scsi_mpath_dev_release(struct scsi_device *sdev);
--
2.43.7
^ permalink raw reply related [flat|nested] 71+ messages in thread* Re: [PATCH v5 14/27] scsi-multipath: support iopolicy
2026-07-27 14:42 ` [PATCH v5 14/27] scsi-multipath: support iopolicy John Garry
@ 2026-07-27 15:06 ` sashiko-bot
2026-07-27 15:39 ` John Garry
0 siblings, 1 reply; 71+ messages in thread
From: sashiko-bot @ 2026-07-27 15:06 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Missing `cancel_work_sync()` for `requeue_work` in the device release path leads to a Use-After-Free.
--
--- Patch [14]: [PATCH v5 14/27] scsi-multipath: support iopolicy ---
commit fea581175f41f33e0cbfa8e1c00312d8a25daa1c
Author: John Garry <john.g.garry@oracle.com>
scsi-multipath: support iopolicy
Add support to set the multipath iopolicy. The iopolicy member is per
scsi_mpath_head structure. A module param is added so that the default
iopolicy may be set.
> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
> index 1866f282c15cc..1b955e19a6814 100644
> --- a/drivers/scsi/scsi_multipath.c
> +++ b/drivers/scsi/scsi_multipath.c
[ ... ]
> @@ -97,8 +114,36 @@ static ssize_t scsi_mpath_device_vpd_id_show(struct device *dev,
> }
> static DEVICE_ATTR(vpd_id, S_IRUGO, scsi_mpath_device_vpd_id_show, NULL);
>
> +static ssize_t scsi_mpath_device_iopolicy_store(struct device *dev,
> + struct device_attribute *attr, const char *buf, size_t count)
> +{
> + struct scsi_mpath_head *scsi_mpath_head =
> + container_of(dev, struct scsi_mpath_head, dev);
> + struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
> +
> + if (!mpath_iopolicy_store(&scsi_mpath_head->iopolicy, buf))
> + return -EINVAL;
> +
> + mpath_clear_paths(mpath_head);
> + mpath_schedule_requeue_work(mpath_head);
[Severity: High]
Does this code introduce a use-after-free risk during device teardown?
If a user writes to the iopolicy sysfs attribute here, mpath_schedule_requeue_work()
is called, which schedules mpath_head->requeue_work asynchronously.
If the SCSI device is subsequently unbound or removed before this work completes,
scsi_mpath_head_release() frees the scsi_mpath_head structure. It doesn't appear
to call cancel_work_sync() to ensure the scheduled work is stopped before freeing.
When the workqueue eventually executes mpath_requeue_work(), could it dereference
the freed mpath_head memory when accessing requeue_lock?
lib/multipath.c:mpath_requeue_work() {
...
spin_lock_irq(&mpath_head->requeue_lock);
...
}
> + return count;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727144234.3802948-1-john.g.garry@oracle.com?part=14
^ permalink raw reply [flat|nested] 71+ messages in thread* Re: [PATCH v5 14/27] scsi-multipath: support iopolicy
2026-07-27 15:06 ` sashiko-bot
@ 2026-07-27 15:39 ` John Garry
0 siblings, 0 replies; 71+ messages in thread
From: John Garry @ 2026-07-27 15:39 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 27/07/2026 16:06, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [High] Missing `cancel_work_sync()` for `requeue_work` in the device release path leads to a Use-After-Free.
> --
>
> --- Patch [14]: [PATCH v5 14/27] scsi-multipath: support iopolicy ---
> commit fea581175f41f33e0cbfa8e1c00312d8a25daa1c
> Author: John Garry <john.g.garry@oracle.com>
>
> scsi-multipath: support iopolicy
>
> Add support to set the multipath iopolicy. The iopolicy member is per
> scsi_mpath_head structure. A module param is added so that the default
> iopolicy may be set.
>
>> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
>> index 1866f282c15cc..1b955e19a6814 100644
>> --- a/drivers/scsi/scsi_multipath.c
>> +++ b/drivers/scsi/scsi_multipath.c
> [ ... ]
>> @@ -97,8 +114,36 @@ static ssize_t scsi_mpath_device_vpd_id_show(struct device *dev,
>> }
>> static DEVICE_ATTR(vpd_id, S_IRUGO, scsi_mpath_device_vpd_id_show, NULL);
>>
>> +static ssize_t scsi_mpath_device_iopolicy_store(struct device *dev,
>> + struct device_attribute *attr, const char *buf, size_t count)
>> +{
>> + struct scsi_mpath_head *scsi_mpath_head =
>> + container_of(dev, struct scsi_mpath_head, dev);
>> + struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
>> +
>> + if (!mpath_iopolicy_store(&scsi_mpath_head->iopolicy, buf))
>> + return -EINVAL;
>> +
>> + mpath_clear_paths(mpath_head);
>> + mpath_schedule_requeue_work(mpath_head);
>
> [Severity: High]
> Does this code introduce a use-after-free risk during device teardown?
>
> If a user writes to the iopolicy sysfs attribute here, mpath_schedule_requeue_work()
> is called, which schedules mpath_head->requeue_work asynchronously.
>
> If the SCSI device is subsequently unbound or removed before this work completes,
> scsi_mpath_head_release() frees the scsi_mpath_head structure. It doesn't appear
> to call cancel_work_sync() to ensure the scheduled work is stopped before freeing.
>
> When the workqueue eventually executes mpath_requeue_work(), could it dereference
> the freed mpath_head memory when accessing requeue_lock?
I'll check it
>
> lib/multipath.c:mpath_requeue_work() {
> ...
> spin_lock_irq(&mpath_head->requeue_lock);
> ...
> }
>
>> + return count;
>> +}
>
^ permalink raw reply [flat|nested] 71+ messages in thread
* [PATCH v5 15/27] scsi-multipath: clone each bio
2026-07-27 14:42 [PATCH v5 00/27] Native SCSI Multipath support John Garry
` (13 preceding siblings ...)
2026-07-27 14:42 ` [PATCH v5 14/27] scsi-multipath: support iopolicy John Garry
@ 2026-07-27 14:42 ` John Garry
2026-07-27 15:21 ` sashiko-bot
2026-07-27 14:42 ` [PATCH v5 16/27] scsi-multipath: clear path when device is blocked John Garry
` (11 subsequent siblings)
26 siblings, 1 reply; 71+ messages in thread
From: John Garry @ 2026-07-27 14:42 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
From: John Garry <john.garry@linux.dev>
For failover handling, we will take the approach to resubmit each
bio.
However, unlike NVMe, for SCSI there is no guarantee that any bio submitted
is either all or none completed.
As such, for SCSI, for failover handling we will take the approach to
just re-submit the original bio. For this, clone and submit each bio.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/scsi_multipath.c | 33 +++++++++++++++++++++++++++++++++
include/scsi/scsi_multipath.h | 1 +
2 files changed, 34 insertions(+)
diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
index 1b955e19a6814..5999c59bf7297 100644
--- a/drivers/scsi/scsi_multipath.c
+++ b/drivers/scsi/scsi_multipath.c
@@ -97,6 +97,7 @@ static void scsi_mpath_head_release(struct device *dev)
container_of(dev, struct scsi_mpath_head, dev);
struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
+ bioset_exit(&scsi_mpath_head->bio_pool);
ida_free(&scsi_multipath_dev_ida, scsi_mpath_head->index);
ida_destroy(&scsi_mpath_head->ida);
mpath_head_uninit(mpath_head);
@@ -230,7 +231,34 @@ static int scsi_multipath_sdev_init(struct scsi_device *sdev)
return 0;
}
+static void scsi_mpath_clone_end_io(struct bio *clone)
+{
+ struct bio *master_bio = clone->bi_private;
+
+ master_bio->bi_status = clone->bi_status;
+ bio_put(clone);
+ bio_endio(master_bio);
+}
+
+static struct bio *scsi_mpath_clone_bio(struct bio *bio)
+{
+ struct mpath_head *mpath_head = bio->bi_bdev->bd_disk->private_data;
+ struct scsi_mpath_head *scsi_mpath_head = to_scsi_mpath_head(mpath_head);
+ struct bio *clone;
+
+ clone = bio_alloc_clone(bio->bi_bdev, bio, GFP_NOIO,
+ &scsi_mpath_head->bio_pool);
+ if (!clone)
+ return NULL;
+
+ clone->bi_end_io = scsi_mpath_clone_end_io;
+ clone->bi_private = bio;
+
+ return clone;
+}
+
static struct mpath_head_template smpdt = {
+ .clone_bio = scsi_mpath_clone_bio,
};
static struct scsi_mpath_head *scsi_mpath_alloc_head(char *vpd_id)
@@ -256,6 +284,9 @@ static struct scsi_mpath_head *scsi_mpath_alloc_head(char *vpd_id)
scsi_mpath_head->index = ida_alloc(&scsi_multipath_dev_ida, GFP_KERNEL);
if (scsi_mpath_head->index < 0)
goto out_uninit_head;
+ if (bioset_init(&scsi_mpath_head->bio_pool, BIO_POOL_SIZE,
+ 0, BIOSET_PERCPU_CACHE))
+ goto out_ida_free;
kref_init(&scsi_mpath_head->ref);
device_initialize(&scsi_mpath_head->dev);
@@ -275,6 +306,8 @@ static struct scsi_mpath_head *scsi_mpath_alloc_head(char *vpd_id)
return scsi_mpath_head;
+out_ida_free:
+ ida_free(&scsi_multipath_dev_ida, scsi_mpath_head->index);
out_uninit_head:
mpath_head_uninit(&scsi_mpath_head->mpath_head);
out_free:
diff --git a/include/scsi/scsi_multipath.h b/include/scsi/scsi_multipath.h
index 2dc02313b0496..5cc37acaa664f 100644
--- a/include/scsi/scsi_multipath.h
+++ b/include/scsi/scsi_multipath.h
@@ -26,6 +26,7 @@ struct scsi_mpath_head {
struct ida ida;
struct kref ref;
enum mpath_iopolicy_e iopolicy;
+ struct bio_set bio_pool;
struct device dev;
int index;
};
--
2.43.7
^ permalink raw reply related [flat|nested] 71+ messages in thread* Re: [PATCH v5 15/27] scsi-multipath: clone each bio
2026-07-27 14:42 ` [PATCH v5 15/27] scsi-multipath: clone each bio John Garry
@ 2026-07-27 15:21 ` sashiko-bot
2026-07-27 15:40 ` John Garry
0 siblings, 1 reply; 71+ messages in thread
From: sashiko-bot @ 2026-07-27 15:21 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The `smpdt` template in `scsi_multipath.c` fails to implement logically required callbacks from `struct mpath_head_template`, which will cause a NULL pointer dereference when the framework attempts to evaluate path states.
--
--- Patch [15]: [PATCH v5 15/27] scsi-multipath: clone each bio ---
commit d3a7e352069146b0f42f27f5c63fadf38b17548f
Author: John Garry <john.garry@linux.dev>
scsi-multipath: clone each bio
For failover handling, this patch resubmits the original bio by cloning and
submitting each bio. This ensures each bio can be safely resubmitted for
SCSI, which unlike NVMe, has no guarantee that any bio submitted is either
all or none completed.
> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
> index 1b955e19a6814..5999c59bf7297 100644
> --- a/drivers/scsi/scsi_multipath.c
> +++ b/drivers/scsi/scsi_multipath.c
[ ... ]
> @@ -230,7 +231,34 @@ static int scsi_multipath_sdev_init(struct scsi_device *sdev)
> return 0;
> }
>
> +static void scsi_mpath_clone_end_io(struct bio *clone)
> +{
> + struct bio *master_bio = clone->bi_private;
> +
> + master_bio->bi_status = clone->bi_status;
> + bio_put(clone);
> + bio_endio(master_bio);
> +}
> +
> +static struct bio *scsi_mpath_clone_bio(struct bio *bio)
> +{
[ ... ]
> +}
> +
> static struct mpath_head_template smpdt = {
> + .clone_bio = scsi_mpath_clone_bio,
[Severity: Medium]
Is it safe to leave callbacks like is_disabled, is_optimized, and
remove_head uninitialized in this template?
Looking at the libmultipath framework, it appears to unconditionally invoke
these callbacks. For example, mpath_path_is_disabled() does this:
lib/multipath.c:mpath_path_is_disabled() {
...
return mpath_head->mpdt->is_disabled(mpath_device);
}
Could this lead to a null pointer dereference when the framework attempts
to evaluate the path state or remove the multipath head, once the device
is fully hooked up?
> };
>
> static struct scsi_mpath_head *scsi_mpath_alloc_head(char *vpd_id)
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727144234.3802948-1-john.g.garry@oracle.com?part=15
^ permalink raw reply [flat|nested] 71+ messages in thread* Re: [PATCH v5 15/27] scsi-multipath: clone each bio
2026-07-27 15:21 ` sashiko-bot
@ 2026-07-27 15:40 ` John Garry
0 siblings, 0 replies; 71+ messages in thread
From: John Garry @ 2026-07-27 15:40 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 27/07/2026 16:21, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Medium] The `smpdt` template in `scsi_multipath.c` fails to implement logically required callbacks from `struct mpath_head_template`, which will cause a NULL pointer dereference when the framework attempts to evaluate path states.
> --
>
> --- Patch [15]: [PATCH v5 15/27] scsi-multipath: clone each bio ---
> commit d3a7e352069146b0f42f27f5c63fadf38b17548f
> Author: John Garry <john.garry@linux.dev>
>
> scsi-multipath: clone each bio
>
> For failover handling, this patch resubmits the original bio by cloning and
> submitting each bio. This ensures each bio can be safely resubmitted for
> SCSI, which unlike NVMe, has no guarantee that any bio submitted is either
> all or none completed.
>
>> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
>> index 1b955e19a6814..5999c59bf7297 100644
>> --- a/drivers/scsi/scsi_multipath.c
>> +++ b/drivers/scsi/scsi_multipath.c
>
> [ ... ]
>
>> @@ -230,7 +231,34 @@ static int scsi_multipath_sdev_init(struct scsi_device *sdev)
>> return 0;
>> }
>>
>> +static void scsi_mpath_clone_end_io(struct bio *clone)
>> +{
>> + struct bio *master_bio = clone->bi_private;
>> +
>> + master_bio->bi_status = clone->bi_status;
>> + bio_put(clone);
>> + bio_endio(master_bio);
>> +}
>> +
>> +static struct bio *scsi_mpath_clone_bio(struct bio *bio)
>> +{
> [ ... ]
>> +}
>> +
>> static struct mpath_head_template smpdt = {
>> + .clone_bio = scsi_mpath_clone_bio,
>
> [Severity: Medium]
> Is it safe to leave callbacks like is_disabled, is_optimized, and
> remove_head uninitialized in this template?
they are set later
>
> Looking at the libmultipath framework, it appears to unconditionally invoke
> these callbacks. For example, mpath_path_is_disabled() does this:
>
> lib/multipath.c:mpath_path_is_disabled() {
> ...
> return mpath_head->mpdt->is_disabled(mpath_device);
> }
>
> Could this lead to a null pointer dereference when the framework attempts
> to evaluate the path state or remove the multipath head, once the device
> is fully hooked up?
>
>> };
>>
>> static struct scsi_mpath_head *scsi_mpath_alloc_head(char *vpd_id)
>
^ permalink raw reply [flat|nested] 71+ messages in thread
* [PATCH v5 16/27] scsi-multipath: clear path when device is blocked
2026-07-27 14:42 [PATCH v5 00/27] Native SCSI Multipath support John Garry
` (14 preceding siblings ...)
2026-07-27 14:42 ` [PATCH v5 15/27] scsi-multipath: clone each bio John Garry
@ 2026-07-27 14:42 ` John Garry
2026-07-27 15:14 ` sashiko-bot
2026-07-27 14:42 ` [PATCH v5 17/27] scsi-multipath: revalidate paths upon device unblock John Garry
` (10 subsequent siblings)
26 siblings, 1 reply; 71+ messages in thread
From: John Garry @ 2026-07-27 14:42 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
From: John Garry <john.garry@linux.dev>
Add scsi_mpath_dev_clear_path() to clear a device path when it becomes
blocked.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/scsi_lib.c | 3 +++
drivers/scsi/scsi_multipath.c | 10 ++++++++++
include/scsi/scsi_multipath.h | 5 +++++
3 files changed, 18 insertions(+)
diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
index 22e2e3223440d..e2452392671f5 100644
--- a/drivers/scsi/scsi_lib.c
+++ b/drivers/scsi/scsi_lib.c
@@ -35,6 +35,7 @@
#include <scsi/scsi_host.h>
#include <scsi/scsi_transport.h> /* scsi_init_limits() */
#include <scsi/scsi_dh.h>
+#include <scsi/scsi_multipath.h>
#include <trace/events/scsi.h>
@@ -3016,6 +3017,8 @@ static void scsi_device_block(struct scsi_device *sdev, void *data)
scsi_stop_queue(sdev);
mutex_unlock(&sdev->state_mutex);
+ if ((err == 0) && sdev->scsi_mpath_dev)
+ scsi_mpath_dev_clear_path(sdev->scsi_mpath_dev);
WARN_ONCE(err, "%s: failed to block %s in state %d\n",
__func__, dev_name(&sdev->sdev_gendev), state);
diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
index 5999c59bf7297..801d95652fc6b 100644
--- a/drivers/scsi/scsi_multipath.c
+++ b/drivers/scsi/scsi_multipath.c
@@ -231,6 +231,16 @@ static int scsi_multipath_sdev_init(struct scsi_device *sdev)
return 0;
}
+void scsi_mpath_dev_clear_path(struct scsi_mpath_device *scsi_mpath_dev)
+{
+ struct mpath_device *mpath_device = &scsi_mpath_dev->mpath_device;
+ struct scsi_mpath_head *scsi_mpath_head = scsi_mpath_dev->scsi_mpath_head;
+ struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
+
+ if (mpath_clear_current_path(mpath_device))
+ mpath_synchronize(mpath_head);
+}
+
static void scsi_mpath_clone_end_io(struct bio *clone)
{
struct bio *master_bio = clone->bi_private;
diff --git a/include/scsi/scsi_multipath.h b/include/scsi/scsi_multipath.h
index 5cc37acaa664f..8f58c44d96a77 100644
--- a/include/scsi/scsi_multipath.h
+++ b/include/scsi/scsi_multipath.h
@@ -54,6 +54,7 @@ void scsi_mpath_add_sysfs_link(struct scsi_device *sdev);
void scsi_mpath_remove_sysfs_link(struct scsi_device *sdev);
int scsi_mpath_get_head(struct scsi_mpath_head *scsi_mpath_head);
void scsi_mpath_put_head(struct scsi_mpath_head *scsi_mpath_head);
+void scsi_mpath_dev_clear_path(struct scsi_mpath_device *scsi_mpath_dev);
#else /* CONFIG_SCSI_MULTIPATH */
struct scsi_mpath_head {
@@ -88,6 +89,10 @@ static inline
void scsi_mpath_put_head(struct scsi_mpath_head *scsi_mpath_head)
{
}
+static inline
+void scsi_mpath_dev_clear_path(struct scsi_mpath_device *scsi_mpath_dev)
+{
+}
static inline void scsi_mpath_add_sysfs_link(struct scsi_device *sdev)
{
}
--
2.43.7
^ permalink raw reply related [flat|nested] 71+ messages in thread* Re: [PATCH v5 16/27] scsi-multipath: clear path when device is blocked
2026-07-27 14:42 ` [PATCH v5 16/27] scsi-multipath: clear path when device is blocked John Garry
@ 2026-07-27 15:14 ` sashiko-bot
2026-07-27 15:44 ` John Garry
0 siblings, 1 reply; 71+ messages in thread
From: sashiko-bot @ 2026-07-27 15:14 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] A Time-of-Check to Time-of-Use (TOCTOU) race condition in `scsi_device_block()` due to lockless access to `sdev->scsi_mpath_dev` leads to NULL pointer dereferences and Use-After-Free vulnerabilities during device initialization, teardown, and initialization failure.
--
--- Patch [16]: [PATCH v5 16/27] scsi-multipath: clear path when device is blocked ---
commit 029c1109c722c76acac1333821c2190171087cb6
Author: John Garry <john.garry@linux.dev>
scsi-multipath: clear path when device is blocked
Add scsi_mpath_dev_clear_path() to clear a device path when it becomes
blocked.
Signed-off-by: John Garry <john.g.garry@oracle.com>
> diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
> index daeb3693fe559..a1ec11050090c 100644
> --- a/drivers/scsi/scsi_lib.c
> +++ b/drivers/scsi/scsi_lib.c
[ ... ]
> @@ -3008,6 +3009,8 @@ static void scsi_device_block(struct scsi_device *sdev, void *data)
> scsi_stop_queue(sdev);
>
> mutex_unlock(&sdev->state_mutex);
> + if ((err == 0) && sdev->scsi_mpath_dev)
> + scsi_mpath_dev_clear_path(sdev->scsi_mpath_dev);
[Severity: High]
Since sdev->scsi_mpath_dev is read locklessly after dropping state_mutex,
can this race with device teardown or initialization failures?
If a concurrent teardown or error path calls scsi_multipath_sdev_uninit()
and frees sdev->scsi_mpath_dev, could this result in a use-after-free when
evaluating or passing the pointer to scsi_mpath_dev_clear_path()?
> WARN_ONCE(err, "%s: failed to block %s in state %d\n",
> __func__, dev_name(&sdev->sdev_gendev), state);
[ ... ]
> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
> index 5999c59bf7297..801d95652fc6b 100644
> --- a/drivers/scsi/scsi_multipath.c
> +++ b/drivers/scsi/scsi_multipath.c
[ ... ]
> @@ -231,6 +231,16 @@ static int scsi_multipath_sdev_init(struct scsi_device *sdev)
> return 0;
> }
>
> +void scsi_mpath_dev_clear_path(struct scsi_mpath_device *scsi_mpath_dev)
> +{
> + struct mpath_device *mpath_device = &scsi_mpath_dev->mpath_device;
> + struct scsi_mpath_head *scsi_mpath_head = scsi_mpath_dev->scsi_mpath_head;
> + struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
[Severity: High]
Could scsi_mpath_dev->scsi_mpath_head be NULL here?
Looking at scsi_multipath_sdev_init(), sdev->scsi_mpath_dev is published
globally before the inner scsi_mpath_head structure is fully initialized:
scsi_multipath_sdev_init() {
...
scsi_mpath_dev->sdev = sdev;
sdev->scsi_mpath_dev = scsi_mpath_dev;
...
}
If a concurrent scsi_device_block() call sees the non-NULL wrapper, would
this dereference a NULL scsi_mpath_head?
> +
> + if (mpath_clear_current_path(mpath_device))
> + mpath_synchronize(mpath_head);
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727144234.3802948-1-john.g.garry@oracle.com?part=16
^ permalink raw reply [flat|nested] 71+ messages in thread* Re: [PATCH v5 16/27] scsi-multipath: clear path when device is blocked
2026-07-27 15:14 ` sashiko-bot
@ 2026-07-27 15:44 ` John Garry
0 siblings, 0 replies; 71+ messages in thread
From: John Garry @ 2026-07-27 15:44 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 27/07/2026 16:14, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [High] A Time-of-Check to Time-of-Use (TOCTOU) race condition in `scsi_device_block()` due to lockless access to `sdev->scsi_mpath_dev` leads to NULL pointer dereferences and Use-After-Free vulnerabilities during device initialization, teardown, and initialization failure.
> --
>
> --- Patch [16]: [PATCH v5 16/27] scsi-multipath: clear path when device is blocked ---
> commit 029c1109c722c76acac1333821c2190171087cb6
> Author: John Garry <john.garry@linux.dev>
>
> scsi-multipath: clear path when device is blocked
>
> Add scsi_mpath_dev_clear_path() to clear a device path when it becomes
> blocked.
>
> Signed-off-by: John Garry <john.g.garry@oracle.com>
>
>> diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
>> index daeb3693fe559..a1ec11050090c 100644
>> --- a/drivers/scsi/scsi_lib.c
>> +++ b/drivers/scsi/scsi_lib.c
>
> [ ... ]
>
>> @@ -3008,6 +3009,8 @@ static void scsi_device_block(struct scsi_device *sdev, void *data)
>> scsi_stop_queue(sdev);
>>
>> mutex_unlock(&sdev->state_mutex);
>> + if ((err == 0) && sdev->scsi_mpath_dev)
>> + scsi_mpath_dev_clear_path(sdev->scsi_mpath_dev);
>
> [Severity: High]
> Since sdev->scsi_mpath_dev is read locklessly after dropping state_mutex,
> can this race with device teardown or initialization failures?
>
> If a concurrent teardown or error path calls scsi_multipath_sdev_uninit()
> and frees sdev->scsi_mpath_dev, could this result in a use-after-free when
> evaluating or passing the pointer to scsi_mpath_dev_clear_path()?
scsi_multipath_sdev_uninit is called late in the sdev teardown - at this
point scsi_device_block() should not be called
>
>> WARN_ONCE(err, "%s: failed to block %s in state %d\n",
>> __func__, dev_name(&sdev->sdev_gendev), state);
>
> [ ... ]
>
>> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
>> index 5999c59bf7297..801d95652fc6b 100644
>> --- a/drivers/scsi/scsi_multipath.c
>> +++ b/drivers/scsi/scsi_multipath.c
>
> [ ... ]
>
>> @@ -231,6 +231,16 @@ static int scsi_multipath_sdev_init(struct scsi_device *sdev)
>> return 0;
>> }
>>
>> +void scsi_mpath_dev_clear_path(struct scsi_mpath_device *scsi_mpath_dev)
>> +{
>> + struct mpath_device *mpath_device = &scsi_mpath_dev->mpath_device;
>> + struct scsi_mpath_head *scsi_mpath_head = scsi_mpath_dev->scsi_mpath_head;
>> + struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
>
> [Severity: High]
> Could scsi_mpath_dev->scsi_mpath_head be NULL here?
>
> Looking at scsi_multipath_sdev_init(), sdev->scsi_mpath_dev is published
> globally before the inner scsi_mpath_head structure is fully initialized:
>
> scsi_multipath_sdev_init() {
> ...
> scsi_mpath_dev->sdev = sdev;
> sdev->scsi_mpath_dev = scsi_mpath_dev;
> ...
> }
>
> If a concurrent scsi_device_block() call sees the non-NULL wrapper, would
> this dereference a NULL scsi_mpath_head?
why would we see scsi_device_block() called for a device before we even
call scsi_sysfs_add_sdev()?
>
>> +
>> + if (mpath_clear_current_path(mpath_device))
>> + mpath_synchronize(mpath_head);
>> +}
>
^ permalink raw reply [flat|nested] 71+ messages in thread
* [PATCH v5 17/27] scsi-multipath: revalidate paths upon device unblock
2026-07-27 14:42 [PATCH v5 00/27] Native SCSI Multipath support John Garry
` (15 preceding siblings ...)
2026-07-27 14:42 ` [PATCH v5 16/27] scsi-multipath: clear path when device is blocked John Garry
@ 2026-07-27 14:42 ` John Garry
2026-07-27 15:17 ` sashiko-bot
2026-07-27 14:42 ` [PATCH v5 18/27] scsi-multipath: failover handling John Garry
` (9 subsequent siblings)
26 siblings, 1 reply; 71+ messages in thread
From: John Garry @ 2026-07-27 14:42 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
From: John Garry <john.garry@linux.dev>
When a scsi_device is unblocked, we need revalidate paths which means that
we clear paths and requeue any queued bios.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/scsi_lib.c | 2 ++
drivers/scsi/scsi_multipath.c | 8 ++++++++
include/scsi/scsi_multipath.h | 5 +++++
3 files changed, 15 insertions(+)
diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
index e2452392671f5..41d201b7e7def 100644
--- a/drivers/scsi/scsi_lib.c
+++ b/drivers/scsi/scsi_lib.c
@@ -3100,6 +3100,8 @@ static int scsi_internal_device_unblock(struct scsi_device *sdev,
mutex_lock(&sdev->state_mutex);
ret = scsi_internal_device_unblock_nowait(sdev, new_state);
mutex_unlock(&sdev->state_mutex);
+ if ((ret == 0) && sdev->scsi_mpath_dev)
+ scsi_mpath_revalidate_paths(sdev->scsi_mpath_dev);
return ret;
}
diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
index 801d95652fc6b..582deeb403119 100644
--- a/drivers/scsi/scsi_multipath.c
+++ b/drivers/scsi/scsi_multipath.c
@@ -231,6 +231,14 @@ static int scsi_multipath_sdev_init(struct scsi_device *sdev)
return 0;
}
+void scsi_mpath_revalidate_paths(struct scsi_mpath_device *scsi_mpath_dev)
+{
+ struct scsi_mpath_head *scsi_mpath_head = scsi_mpath_dev->scsi_mpath_head;
+ struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
+
+ mpath_revalidate_paths(mpath_head);
+}
+
void scsi_mpath_dev_clear_path(struct scsi_mpath_device *scsi_mpath_dev)
{
struct mpath_device *mpath_device = &scsi_mpath_dev->mpath_device;
diff --git a/include/scsi/scsi_multipath.h b/include/scsi/scsi_multipath.h
index 8f58c44d96a77..cc77e63c43da3 100644
--- a/include/scsi/scsi_multipath.h
+++ b/include/scsi/scsi_multipath.h
@@ -55,6 +55,7 @@ void scsi_mpath_remove_sysfs_link(struct scsi_device *sdev);
int scsi_mpath_get_head(struct scsi_mpath_head *scsi_mpath_head);
void scsi_mpath_put_head(struct scsi_mpath_head *scsi_mpath_head);
void scsi_mpath_dev_clear_path(struct scsi_mpath_device *scsi_mpath_dev);
+void scsi_mpath_revalidate_paths(struct scsi_mpath_device *scsi_mpath_dev);
#else /* CONFIG_SCSI_MULTIPATH */
struct scsi_mpath_head {
@@ -93,6 +94,10 @@ static inline
void scsi_mpath_dev_clear_path(struct scsi_mpath_device *scsi_mpath_dev)
{
}
+static inline
+void scsi_mpath_revalidate_paths(struct scsi_mpath_device *scsi_mpath_dev)
+{
+}
static inline void scsi_mpath_add_sysfs_link(struct scsi_device *sdev)
{
}
--
2.43.7
^ permalink raw reply related [flat|nested] 71+ messages in thread* Re: [PATCH v5 17/27] scsi-multipath: revalidate paths upon device unblock
2026-07-27 14:42 ` [PATCH v5 17/27] scsi-multipath: revalidate paths upon device unblock John Garry
@ 2026-07-27 15:17 ` sashiko-bot
2026-07-27 16:05 ` John Garry
0 siblings, 1 reply; 71+ messages in thread
From: sashiko-bot @ 2026-07-27 15:17 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Race condition in `scsi_internal_device_unblock` and `scsi_device_block` leading to NULL pointer dereference.
- [High] Concurrent execution of `scsi_mpath_dev_alloc` and transport block/unblock leads to Use-After-Free and NULL pointer dereference.
--
--- Patch [17]: [PATCH v5 17/27] scsi-multipath: revalidate paths upon device unblock ---
commit d7b8727956ce248cf872dc90fa72742ade627f54
Author: John Garry <john.garry@linux.dev>
scsi-multipath: revalidate paths upon device unblock
When a scsi_device is unblocked, this patch clears paths and requeues
any queued bios to revalidate the paths.
> diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
> --- a/drivers/scsi/scsi_lib.c
> +++ b/drivers/scsi/scsi_lib.c
[ ... ]
> @@ -3092,6 +3092,8 @@ static int scsi_internal_device_unblock(struct scsi_device *sdev,
> mutex_lock(&sdev->state_mutex);
> ret = scsi_internal_device_unblock_nowait(sdev, new_state);
> mutex_unlock(&sdev->state_mutex);
> + if ((ret == 0) && sdev->scsi_mpath_dev)
> + scsi_mpath_revalidate_paths(sdev->scsi_mpath_dev);
>
> return ret;
> }
[Severity: High]
Can this lockless check of sdev->scsi_mpath_dev lead to a use-after-free or
NULL pointer dereference?
During device discovery, scsi_alloc_sdev() publishes the scsi_device to
shost->__devices. After this, scsi_mpath_dev_alloc() assigns
sdev->scsi_mpath_dev but can fail and jump to out_uninit where it frees
the structure:
drivers/scsi/scsi_multipath.c:scsi_mpath_dev_alloc() {
...
ret = scsi_multipath_sdev_init(sdev);
if (ret)
return ret;
ret = scsi_mpath_unique_lun_id(sdev);
if (ret < 0)
goto out_uninit;
...
}
If an asynchronous transport event triggers scsi_internal_device_unblock()
during this window, could it read a freed sdev->scsi_mpath_dev, or one where
scsi_mpath_head is not yet initialized?
Could a concurrent device removal also trigger a similar race?
If __scsi_remove_device() executes after the mutex_unlock() but before
sdev->scsi_mpath_dev is evaluated, it could set the device state to
SDEV_CANCEL and call scsi_mpath_remove_device(). This sets
scsi_mpath_dev->scsi_mpath_head to NULL.
(Note: An earlier patch in this series appears to introduce an identical
lockless pattern in scsi_device_block() checking sdev->scsi_mpath_dev.)
> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
> --- a/drivers/scsi/scsi_multipath.c
> +++ b/drivers/scsi/scsi_multipath.c
[ ... ]
> @@ -231,6 +231,14 @@ static int scsi_multipath_sdev_init(struct scsi_device *sdev)
> return 0;
> }
>
> +void scsi_mpath_revalidate_paths(struct scsi_mpath_device *scsi_mpath_dev)
> +{
> + struct scsi_mpath_head *scsi_mpath_head = scsi_mpath_dev->scsi_mpath_head;
> + struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
> +
> + mpath_revalidate_paths(mpath_head);
> +}
[Severity: High]
Does this code safely handle a NULL scsi_mpath_head?
If the race mentioned above occurs and a concurrent teardown clears
scsi_mpath_head to NULL, would this assignment dereference the NULL pointer
when attempting to resolve mpath_head?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727144234.3802948-1-john.g.garry@oracle.com?part=17
^ permalink raw reply [flat|nested] 71+ messages in thread* Re: [PATCH v5 17/27] scsi-multipath: revalidate paths upon device unblock
2026-07-27 15:17 ` sashiko-bot
@ 2026-07-27 16:05 ` John Garry
0 siblings, 0 replies; 71+ messages in thread
From: John Garry @ 2026-07-27 16:05 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 27/07/2026 16:17, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [High] Race condition in `scsi_internal_device_unblock` and `scsi_device_block` leading to NULL pointer dereference.
> - [High] Concurrent execution of `scsi_mpath_dev_alloc` and transport block/unblock leads to Use-After-Free and NULL pointer dereference.
> --
>
> --- Patch [17]: [PATCH v5 17/27] scsi-multipath: revalidate paths upon device unblock ---
> commit d7b8727956ce248cf872dc90fa72742ade627f54
> Author: John Garry <john.garry@linux.dev>
>
> scsi-multipath: revalidate paths upon device unblock
>
> When a scsi_device is unblocked, this patch clears paths and requeues
> any queued bios to revalidate the paths.
>
>> diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
>> --- a/drivers/scsi/scsi_lib.c
>> +++ b/drivers/scsi/scsi_lib.c
> [ ... ]
>> @@ -3092,6 +3092,8 @@ static int scsi_internal_device_unblock(struct scsi_device *sdev,
>> mutex_lock(&sdev->state_mutex);
>> ret = scsi_internal_device_unblock_nowait(sdev, new_state);
>> mutex_unlock(&sdev->state_mutex);
>> + if ((ret == 0) && sdev->scsi_mpath_dev)
>> + scsi_mpath_revalidate_paths(sdev->scsi_mpath_dev);
>>
>> return ret;
>> }
>
> [Severity: High]
> Can this lockless check of sdev->scsi_mpath_dev lead to a use-after-free or
> NULL pointer dereference?
Last time you complained about having locking. Now you complain about no
locking.
>
> During device discovery, scsi_alloc_sdev() publishes the scsi_device to
> shost->__devices. After this, scsi_mpath_dev_alloc() assigns
> sdev->scsi_mpath_dev but can fail and jump to out_uninit where it frees
> the structure:
>
> drivers/scsi/scsi_multipath.c:scsi_mpath_dev_alloc() {
> ...
> ret = scsi_multipath_sdev_init(sdev);
> if (ret)
> return ret;
>
> ret = scsi_mpath_unique_lun_id(sdev);
> if (ret < 0)
> goto out_uninit;
> ...
> }
>
> If an asynchronous transport event triggers scsi_internal_device_unblock()
> during this window, could it read a freed sdev->scsi_mpath_dev, or one where
> scsi_mpath_head is not yet initialized?
Give a proper example of when this can happen and not just mention
"asynchronous transport event"
>
> Could a concurrent device removal also trigger a similar race?
>
> If __scsi_remove_device() executes after the mutex_unlock() but before
> sdev->scsi_mpath_dev is evaluated
Again, this should not happen as __scsi_remove_device() should not be
called before sdev->scsi_mpath_dev is evaluated
>, it could set the device state to
> SDEV_CANCEL and call scsi_mpath_remove_device(). This sets
> scsi_mpath_dev->scsi_mpath_head to NULL.
>
> (Note: An earlier patch in this series appears to introduce an identical
> lockless pattern in scsi_device_block() checking sdev->scsi_mpath_dev.)
Who is calling scsi_internal_device_unblock() and __scsi_remove_device()
in parallel?
>
>> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
>> --- a/drivers/scsi/scsi_multipath.c
>> +++ b/drivers/scsi/scsi_multipath.c
> [ ... ]
>> @@ -231,6 +231,14 @@ static int scsi_multipath_sdev_init(struct scsi_device *sdev)
>> return 0;
>> }
>>
>> +void scsi_mpath_revalidate_paths(struct scsi_mpath_device *scsi_mpath_dev)
>> +{
>> + struct scsi_mpath_head *scsi_mpath_head = scsi_mpath_dev->scsi_mpath_head;
>> + struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
>> +
>> + mpath_revalidate_paths(mpath_head);
>> +}
>
> [Severity: High]
> Does this code safely handle a NULL scsi_mpath_head?
>
> If the race mentioned above occurs and a concurrent teardown clears
> scsi_mpath_head to NULL, would this assignment dereference the NULL pointer
> when attempting to resolve mpath_head?
>
^ permalink raw reply [flat|nested] 71+ messages in thread
* [PATCH v5 18/27] scsi-multipath: failover handling
2026-07-27 14:42 [PATCH v5 00/27] Native SCSI Multipath support John Garry
` (16 preceding siblings ...)
2026-07-27 14:42 ` [PATCH v5 17/27] scsi-multipath: revalidate paths upon device unblock John Garry
@ 2026-07-27 14:42 ` John Garry
2026-07-27 14:42 ` [PATCH v5 19/27] scsi-multipath: provide callbacks for path state John Garry
` (8 subsequent siblings)
26 siblings, 0 replies; 71+ messages in thread
From: John Garry @ 2026-07-27 14:42 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
From: John Garry <john.garry@linux.dev>
Failover occurs when the scsi_cmnd has failed and it is discovered that the
target scsi_device has transport down.
For a scsi command which suffers failover, requeue the master bio of each
bio attached to its request.
A bio which for which failover occurs is handled in
scsi_mpath_clone_end_io(). Failover is detected for blk_path_error()
occuring, same as how dm-mpath detects this.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/scsi_multipath.c | 39 +++++++++++++++++++++++++++++++++++
1 file changed, 39 insertions(+)
diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
index 582deeb403119..ec611f26d7960 100644
--- a/drivers/scsi/scsi_multipath.c
+++ b/drivers/scsi/scsi_multipath.c
@@ -249,10 +249,49 @@ void scsi_mpath_dev_clear_path(struct scsi_mpath_device *scsi_mpath_dev)
mpath_synchronize(mpath_head);
}
+static inline void bio_list_add_clone(struct bio_list *bl,
+ struct bio *clone)
+{
+ struct bio *master_bio = clone->bi_private;
+
+ if (bl->tail)
+ bl->tail->bi_next = master_bio;
+ else
+ bl->head = master_bio;
+ bl->tail = master_bio;
+ bio_put(clone);
+}
+
static void scsi_mpath_clone_end_io(struct bio *clone)
{
struct bio *master_bio = clone->bi_private;
+ if (clone->bi_status && blk_path_error(clone->bi_status)) {
+ struct block_device *bi_bdev = clone->bi_bdev;
+ struct request_queue *q = bi_bdev->bd_queue;
+ struct scsi_device *sdev = scsi_device_from_queue(q);
+ struct scsi_mpath_device *scsi_mpath_dev;
+ struct mpath_device *mpath_device;
+ struct mpath_head *mpath_head;
+ unsigned long flags;
+
+ if (!sdev)
+ goto end_bio;
+
+ scsi_mpath_dev = sdev->scsi_mpath_dev;
+ mpath_device = &scsi_mpath_dev->mpath_device;
+ mpath_head = mpath_device->mpath_head;
+
+ spin_lock_irqsave(&mpath_head->requeue_lock, flags);
+ bio_list_add_clone(&mpath_head->requeue_list, clone);
+ spin_unlock_irqrestore(&mpath_head->requeue_lock, flags);
+
+ mpath_schedule_requeue_work(mpath_head);
+ put_device(&sdev->sdev_gendev);
+ return;
+ }
+
+end_bio:
master_bio->bi_status = clone->bi_status;
bio_put(clone);
bio_endio(master_bio);
--
2.43.7
^ permalink raw reply related [flat|nested] 71+ messages in thread* [PATCH v5 19/27] scsi-multipath: provide callbacks for path state
2026-07-27 14:42 [PATCH v5 00/27] Native SCSI Multipath support John Garry
` (17 preceding siblings ...)
2026-07-27 14:42 ` [PATCH v5 18/27] scsi-multipath: failover handling John Garry
@ 2026-07-27 14:42 ` John Garry
2026-07-27 15:24 ` sashiko-bot
2026-07-27 14:42 ` [PATCH v5 20/27] scsi-multipath: add scsi_mpath_{start,end}_request() John Garry
` (7 subsequent siblings)
26 siblings, 1 reply; 71+ messages in thread
From: John Garry @ 2026-07-27 14:42 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
Provide callbacks for .is_disabled, .is_optimized, and .available_path.
These all use scsi_device.sdev_state and scsi_device.access_state.
Member scsi_device.access_state will be driven by ALUA. Currently
only device handlers support this, and in future we will have core
SCSI support for implicit ALUA (not relying on device handlers).
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/scsi_multipath.c | 49 +++++++++++++++++++++++++++++++++++
1 file changed, 49 insertions(+)
diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
index ec611f26d7960..d79e1b7b90051 100644
--- a/drivers/scsi/scsi_multipath.c
+++ b/drivers/scsi/scsi_multipath.c
@@ -314,7 +314,56 @@ static struct bio *scsi_mpath_clone_bio(struct bio *bio)
return clone;
}
+static bool scsi_mpath_is_disabled(struct mpath_device *mpath_device)
+{
+ struct scsi_mpath_device *scsi_mpath_dev =
+ to_scsi_mpath_device(mpath_device);
+ struct scsi_device *sdev = scsi_mpath_dev->sdev;
+ unsigned char access_state = READ_ONCE(sdev->access_state) &
+ SCSI_ACCESS_STATE_MASK;
+
+ if (READ_ONCE(sdev->sdev_state) != SDEV_RUNNING)
+ return true;
+
+ if (access_state == SCSI_ACCESS_STATE_OPTIMAL ||
+ access_state == SCSI_ACCESS_STATE_ACTIVE)
+ return false;
+
+ return true;
+}
+
+static bool scsi_mpath_is_optimized(struct mpath_device *mpath_device)
+{
+ struct scsi_mpath_device *scsi_mpath_dev =
+ to_scsi_mpath_device(mpath_device);
+ struct scsi_device *sdev = scsi_mpath_dev->sdev;
+ unsigned char access_state = READ_ONCE(sdev->access_state) &
+ SCSI_ACCESS_STATE_MASK;
+
+ if (READ_ONCE(sdev->sdev_state) != SDEV_RUNNING)
+ return false;
+
+ return access_state == SCSI_ACCESS_STATE_OPTIMAL;
+}
+
+static bool scsi_mpath_available_path(struct mpath_device *mpath_device)
+{
+ struct scsi_mpath_device *scsi_mpath_dev =
+ to_scsi_mpath_device(mpath_device);
+ struct scsi_device *sdev = scsi_mpath_dev->sdev;
+ enum scsi_device_state sdev_state = READ_ONCE(sdev->sdev_state);
+
+ if (sdev_state == SDEV_RUNNING || sdev_state == SDEV_QUIESCE ||
+ sdev_state == SDEV_BLOCK || sdev_state == SDEV_CREATED_BLOCK)
+ return true;
+
+ return false;
+}
+
static struct mpath_head_template smpdt = {
+ .is_disabled = scsi_mpath_is_disabled,
+ .is_optimized = scsi_mpath_is_optimized,
+ .available_path = scsi_mpath_available_path,
.clone_bio = scsi_mpath_clone_bio,
};
--
2.43.7
^ permalink raw reply related [flat|nested] 71+ messages in thread* Re: [PATCH v5 19/27] scsi-multipath: provide callbacks for path state
2026-07-27 14:42 ` [PATCH v5 19/27] scsi-multipath: provide callbacks for path state John Garry
@ 2026-07-27 15:24 ` sashiko-bot
2026-07-27 16:07 ` John Garry
0 siblings, 1 reply; 71+ messages in thread
From: sashiko-bot @ 2026-07-27 15:24 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] scsi-multipath fails to kick the multipath head's requeue list when underlying paths transition back to a usable state, causing pending I/Os to hang indefinitely.
- [High] Core path selectors ignore the newly added `.is_optimized` callback, breaking ALUA priorities for round-robin and queue-depth I/O policies.
- [High] LBA-dependent ALUA paths are incorrectly treated as disabled, leading to complete I/O failure for LBA-dependent arrays.
--
--- Patch [19]: [PATCH v5 19/27] scsi-multipath: provide callbacks for path state ---
commit c99fff5c406ddb30334df26d8cb6dcc7c86e7a13
Author: John Garry <john.g.garry@oracle.com>
scsi-multipath: provide callbacks for path state
Provide callbacks for .is_disabled, .is_optimized, and .available_path.
These all use scsi_device.sdev_state and scsi_device.access_state.
Member scsi_device.access_state will be driven by ALUA.
> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
> index ec611f26d7960..d79e1b7b90051 100644
> --- a/drivers/scsi/scsi_multipath.c
> +++ b/drivers/scsi/scsi_multipath.c
> @@ -314,7 +314,56 @@ static struct bio *scsi_mpath_clone_bio(struct bio *bio)
[ ... ]
> +static bool scsi_mpath_is_disabled(struct mpath_device *mpath_device)
> +{
> + struct scsi_mpath_device *scsi_mpath_dev =
> + to_scsi_mpath_device(mpath_device);
> + struct scsi_device *sdev = scsi_mpath_dev->sdev;
> + unsigned char access_state = READ_ONCE(sdev->access_state) &
> + SCSI_ACCESS_STATE_MASK;
> +
> + if (READ_ONCE(sdev->sdev_state) != SDEV_RUNNING)
> + return true;
> +
> + if (access_state == SCSI_ACCESS_STATE_OPTIMAL ||
> + access_state == SCSI_ACCESS_STATE_ACTIVE)
> + return false;
[Severity: High]
Does this code inadvertently exclude SCSI_ACCESS_STATE_LBA?
If a path is in SCSI_ACCESS_STATE_LBA, it is considered a valid, usable active
path by ALUA. By returning true for any state other than OPTIMAL or ACTIVE,
won't this cause complete I/O failure if all available paths are
LBA-dependent?
> +
> + return true;
> +}
[Severity: High]
If a path's state changes to become usable again (for example, transitioning
from SDEV_QUIESCE back to SDEV_RUNNING in scsi_device_resume(), or when the
ALUA state changes to SCSI_ACCESS_STATE_OPTIMAL asynchronously), how is the
multipath head notified?
Without a corresponding transition hook in the SCSI core or ALUA handlers
to invoke mpath_schedule_requeue_work(), won't pending I/Os on the
requeue_list hang indefinitely instead of being retried?
> +
> +static bool scsi_mpath_is_optimized(struct mpath_device *mpath_device)
> +{
> + struct scsi_mpath_device *scsi_mpath_dev =
> + to_scsi_mpath_device(mpath_device);
> + struct scsi_device *sdev = scsi_mpath_dev->sdev;
> + unsigned char access_state = READ_ONCE(sdev->access_state) &
> + SCSI_ACCESS_STATE_MASK;
> +
> + if (READ_ONCE(sdev->sdev_state) != SDEV_RUNNING)
> + return false;
> +
> + return access_state == SCSI_ACCESS_STATE_OPTIMAL;
> +}
[Severity: High]
Are core path selectors actually invoking this new callback?
Looking at the core selection algorithms in lib/multipath.c, such as
__mpath_find_path() or mpath_round_robin_path(), it appears they check the
static field mpath_device->access_state instead of invoking the
is_optimized() callback.
Because scsi_multipath_sdev_init() hardcodes mpath_device->access_state to
MPATH_STATE_OPTIMIZED, won't all SCSI paths be incorrectly treated as optimized
by the core logic, bypassing ALUA path weighting for round-robin and
queue-depth policies?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727144234.3802948-1-john.g.garry@oracle.com?part=19
^ permalink raw reply [flat|nested] 71+ messages in thread* Re: [PATCH v5 19/27] scsi-multipath: provide callbacks for path state
2026-07-27 15:24 ` sashiko-bot
@ 2026-07-27 16:07 ` John Garry
0 siblings, 0 replies; 71+ messages in thread
From: John Garry @ 2026-07-27 16:07 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 27/07/2026 16:24, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
> - [High] scsi-multipath fails to kick the multipath head's requeue list when underlying paths transition back to a usable state, causing pending I/Os to hang indefinitely.
> - [High] Core path selectors ignore the newly added `.is_optimized` callback, breaking ALUA priorities for round-robin and queue-depth I/O policies.
> - [High] LBA-dependent ALUA paths are incorrectly treated as disabled, leading to complete I/O failure for LBA-dependent arrays.
> --
>
> --- Patch [19]: [PATCH v5 19/27] scsi-multipath: provide callbacks for path state ---
> commit c99fff5c406ddb30334df26d8cb6dcc7c86e7a13
> Author: John Garry <john.g.garry@oracle.com>
>
> scsi-multipath: provide callbacks for path state
>
> Provide callbacks for .is_disabled, .is_optimized, and .available_path.
> These all use scsi_device.sdev_state and scsi_device.access_state.
> Member scsi_device.access_state will be driven by ALUA.
>
>> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
>> index ec611f26d7960..d79e1b7b90051 100644
>> --- a/drivers/scsi/scsi_multipath.c
>> +++ b/drivers/scsi/scsi_multipath.c
>> @@ -314,7 +314,56 @@ static struct bio *scsi_mpath_clone_bio(struct bio *bio)
>
> [ ... ]
>
>> +static bool scsi_mpath_is_disabled(struct mpath_device *mpath_device)
>> +{
>> + struct scsi_mpath_device *scsi_mpath_dev =
>> + to_scsi_mpath_device(mpath_device);
>> + struct scsi_device *sdev = scsi_mpath_dev->sdev;
>> + unsigned char access_state = READ_ONCE(sdev->access_state) &
>> + SCSI_ACCESS_STATE_MASK;
>> +
>> + if (READ_ONCE(sdev->sdev_state) != SDEV_RUNNING)
>> + return true;
>> +
>> + if (access_state == SCSI_ACCESS_STATE_OPTIMAL ||
>> + access_state == SCSI_ACCESS_STATE_ACTIVE)
>> + return false;
>
> [Severity: High]
> Does this code inadvertently exclude SCSI_ACCESS_STATE_LBA?
>
> If a path is in SCSI_ACCESS_STATE_LBA, it is considered a valid, usable active
> path by ALUA. By returning true for any state other than OPTIMAL or ACTIVE,
> won't this cause complete I/O failure if all available paths are
> LBA-dependent?
We don't fully support ALUA yet
>
>> +
>> + return true;
>> +}
>
> [Severity: High]
> If a path's state changes to become usable again (for example, transitioning
> from SDEV_QUIESCE back to SDEV_RUNNING in scsi_device_resume(), or when the
> ALUA state changes to SCSI_ACCESS_STATE_OPTIMAL asynchronously), how is the
> multipath head notified?
>
> Without a corresponding transition hook in the SCSI core or ALUA handlers
> to invoke mpath_schedule_requeue_work(), won't pending I/Os on the
> requeue_list hang indefinitely instead of being retried?
>
>> +
>> +static bool scsi_mpath_is_optimized(struct mpath_device *mpath_device)
>> +{
>> + struct scsi_mpath_device *scsi_mpath_dev =
>> + to_scsi_mpath_device(mpath_device);
>> + struct scsi_device *sdev = scsi_mpath_dev->sdev;
>> + unsigned char access_state = READ_ONCE(sdev->access_state) &
>> + SCSI_ACCESS_STATE_MASK;
>> +
>> + if (READ_ONCE(sdev->sdev_state) != SDEV_RUNNING)
>> + return false;
>> +
>> + return access_state == SCSI_ACCESS_STATE_OPTIMAL;
>> +}
>
> [Severity: High]
> Are core path selectors actually invoking this new callback?
It's ok as we will make it work properly when ALUA is supported
>
> Looking at the core selection algorithms in lib/multipath.c, such as
> __mpath_find_path() or mpath_round_robin_path(), it appears they check the
> static field mpath_device->access_state instead of invoking the
> is_optimized() callback.
>
> Because scsi_multipath_sdev_init() hardcodes mpath_device->access_state to
> MPATH_STATE_OPTIMIZED, won't all SCSI paths be incorrectly treated as optimized
> by the core logic, bypassing ALUA path weighting for round-robin and
> queue-depth policies?
>
^ permalink raw reply [flat|nested] 71+ messages in thread
* [PATCH v5 20/27] scsi-multipath: add scsi_mpath_{start,end}_request()
2026-07-27 14:42 [PATCH v5 00/27] Native SCSI Multipath support John Garry
` (18 preceding siblings ...)
2026-07-27 14:42 ` [PATCH v5 19/27] scsi-multipath: provide callbacks for path state John Garry
@ 2026-07-27 14:42 ` John Garry
2026-07-27 15:25 ` sashiko-bot
2026-07-27 14:42 ` [PATCH v5 21/27] scsi-multipath: add delayed disk removal support John Garry
` (6 subsequent siblings)
26 siblings, 1 reply; 71+ messages in thread
From: John Garry @ 2026-07-27 14:42 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
From: John Garry <john.garry@linux.dev>
Add scsi_mpath_{start,end}_request() to handle updating private multipath
request data, like nvme_mpath_{start,end}_request().
New member Scsi_Host.mpath_nr_active is added. This is required for queue
depth multipath iopolicy.
For NVMe, this count is per controller. The reason is that many NSes may
be connected to a controller, so congestion should be judged at
controller level.
SCSI has no definition of a controller, but SCSI host is a comparable
concept.
Indeed, many SCSI disks may be connected to the same SCSI host, so it
makes sense to count number of active requests at this point. However,
for a transport like iSCSI Initiator over TCP/IP, we have a separate SCSI
host per SCSI device (so there the count would be same at SCSI device
level).
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/scsi_lib.c | 8 ++++-
drivers/scsi/scsi_multipath.c | 62 +++++++++++++++++++++++++++++++++++
include/scsi/scsi_cmnd.h | 9 ++++-
include/scsi/scsi_host.h | 4 +++
include/scsi/scsi_multipath.h | 11 +++++++
5 files changed, 92 insertions(+), 2 deletions(-)
diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
index 41d201b7e7def..cdcef98e56eac 100644
--- a/drivers/scsi/scsi_lib.c
+++ b/drivers/scsi/scsi_lib.c
@@ -669,7 +669,10 @@ static bool scsi_end_request(struct request *req, blk_status_t error,
struct scsi_device *sdev = cmd->device;
struct request_queue *q = sdev->request_queue;
- if (blk_update_request(req, error, bytes))
+ if (is_mpath_request(req)) {
+ if (scsi_mpath_end_request(req, error, bytes))
+ return true;
+ } else if (blk_update_request(req, error, bytes))
return true;
if (q->limits.features & BLK_FEAT_ADD_RANDOM)
@@ -1917,6 +1920,9 @@ static blk_status_t scsi_queue_rq(struct blk_mq_hw_ctx *hctx,
memset(cmd->sense_buffer, 0, SCSI_SENSE_BUFFERSIZE);
cmd->submitter = SUBMITTED_BY_BLOCK_LAYER;
+ if (is_mpath_request(req))
+ scsi_mpath_start_request(req);
+
blk_mq_start_request(req);
if (blk_mq_is_reserved_rq(req)) {
reason = shost->hostt->queue_reserved_command(shost, cmd);
diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
index d79e1b7b90051..eae5864ce5826 100644
--- a/drivers/scsi/scsi_multipath.c
+++ b/drivers/scsi/scsi_multipath.c
@@ -555,6 +555,68 @@ void scsi_mpath_put_head(struct scsi_mpath_head *scsi_mpath_head)
}
EXPORT_SYMBOL_GPL(scsi_mpath_put_head);
+void scsi_mpath_start_request(struct request *req)
+{
+ struct scsi_cmnd *scmd = blk_mq_rq_to_pdu(req);
+ struct scsi_device *sdev = scmd->device;
+ struct scsi_mpath_device *scsi_mpath_dev = sdev->scsi_mpath_dev;
+ struct scsi_mpath_head *scsi_mpath_head =
+ scsi_mpath_dev->scsi_mpath_head;
+ struct bio *clone = req->bio, *master;
+ struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
+ struct gendisk *disk = mpath_head->disk;
+
+ if (mpath_qd_iopolicy(&scsi_mpath_head->iopolicy) &&
+ !(scmd->flags & SCMD_MPATH_CNT_ACTIVE)) {
+ struct Scsi_Host *shost = sdev->host;
+
+ atomic_inc(&shost->mpath_nr_active);
+ scmd->flags |= SCMD_MPATH_CNT_ACTIVE;
+ }
+
+ if (blk_rq_is_passthrough(req) || !blk_queue_io_stat(disk->queue) ||
+ (scmd->flags & SCMD_MPATH_IO_STATS) || !clone)
+ return;
+
+ master = clone->bi_private;
+
+ scmd->flags |= SCMD_MPATH_IO_STATS;
+ scmd->start_time = bdev_start_io_acct(master->bi_bdev, req_op(req),
+ jiffies);
+ scmd->start_bytes = blk_rq_bytes(req);
+}
+
+bool scsi_mpath_end_request(struct request *req, blk_status_t error,
+ unsigned int nr_bytes)
+{
+ struct scsi_cmnd *scmd = blk_mq_rq_to_pdu(req);
+ struct scsi_device *sdev = scmd->device;
+ struct block_device *bi_bdev = NULL;
+
+ if (scmd->flags & SCMD_MPATH_IO_STATS) {
+ struct bio *clone = req->bio, *master = clone->bi_private;
+
+ bi_bdev = master->bi_bdev;
+ }
+
+ if (blk_update_request(req, error, nr_bytes))
+ return true;
+
+ if (scmd->flags & SCMD_MPATH_CNT_ACTIVE) {
+ struct Scsi_Host *shost = sdev->host;
+
+ atomic_dec_if_positive(&shost->mpath_nr_active);
+ }
+
+ if (!(scmd->flags & SCMD_MPATH_IO_STATS))
+ return false;
+ bdev_end_io_acct(bi_bdev, req_op(req),
+ scmd->start_bytes >> SECTOR_SHIFT,
+ scmd->start_time);
+
+ return false;
+}
+
int __init scsi_multipath_init(void)
{
return class_register(&scsi_mpath_device_class);
diff --git a/include/scsi/scsi_cmnd.h b/include/scsi/scsi_cmnd.h
index 8ecfb94049db5..52227e2cd9749 100644
--- a/include/scsi/scsi_cmnd.h
+++ b/include/scsi/scsi_cmnd.h
@@ -58,8 +58,11 @@ struct scsi_pointer {
*/
#define SCMD_FORCE_EH_SUCCESS (1 << 3)
#define SCMD_FAIL_IF_RECOVERING (1 << 4)
+#define SCMD_MPATH_IO_STATS (1 << 5)
+#define SCMD_MPATH_CNT_ACTIVE (1 << 6)
/* flags preserved across unprep / reprep */
-#define SCMD_PRESERVED_FLAGS (SCMD_INITIALIZED | SCMD_FAIL_IF_RECOVERING)
+#define SCMD_PRESERVED_FLAGS (SCMD_INITIALIZED | SCMD_FAIL_IF_RECOVERING | \
+ SCMD_MPATH_IO_STATS | SCMD_MPATH_CNT_ACTIVE)
/* for scmd->state */
#define SCMD_STATE_COMPLETE 0
@@ -139,6 +142,10 @@ struct scsi_cmnd {
* to release this memory. (The memory
* obtained by scsi_malloc is guaranteed
* to be at an address < 16Mb). */
+ #ifdef CONFIG_SCSI_MULTIPATH
+ unsigned long start_time;
+ unsigned int start_bytes;
+ #endif
int result; /* Status code from lower level driver */
};
diff --git a/include/scsi/scsi_host.h b/include/scsi/scsi_host.h
index 7e2011830ba4b..cf4fff3ea1a8e 100644
--- a/include/scsi/scsi_host.h
+++ b/include/scsi/scsi_host.h
@@ -753,6 +753,10 @@ struct Scsi_Host {
/* Delay for runtime autosuspend */
int rpm_autosuspend_delay;
+ #ifdef CONFIG_SCSI_MULTIPATH
+ atomic_t mpath_nr_active;
+ #endif
+
/*
* We should ensure that this is aligned, both for better performance
* and also because some compilers (m68k) don't automatically force
diff --git a/include/scsi/scsi_multipath.h b/include/scsi/scsi_multipath.h
index cc77e63c43da3..c3398e9f03f16 100644
--- a/include/scsi/scsi_multipath.h
+++ b/include/scsi/scsi_multipath.h
@@ -56,6 +56,9 @@ int scsi_mpath_get_head(struct scsi_mpath_head *scsi_mpath_head);
void scsi_mpath_put_head(struct scsi_mpath_head *scsi_mpath_head);
void scsi_mpath_dev_clear_path(struct scsi_mpath_device *scsi_mpath_dev);
void scsi_mpath_revalidate_paths(struct scsi_mpath_device *scsi_mpath_dev);
+void scsi_mpath_start_request(struct request *req);
+bool scsi_mpath_end_request(struct request *req, blk_status_t error,
+ unsigned int nr_bytes);
#else /* CONFIG_SCSI_MULTIPATH */
struct scsi_mpath_head {
@@ -98,6 +101,14 @@ static inline
void scsi_mpath_revalidate_paths(struct scsi_mpath_device *scsi_mpath_dev)
{
}
+static inline void scsi_mpath_start_request(struct request *req)
+{
+}
+static inline bool scsi_mpath_end_request(struct request *req, blk_status_t error,
+ unsigned int nr_bytes)
+{
+ return false;
+}
static inline void scsi_mpath_add_sysfs_link(struct scsi_device *sdev)
{
}
--
2.43.7
^ permalink raw reply related [flat|nested] 71+ messages in thread* Re: [PATCH v5 20/27] scsi-multipath: add scsi_mpath_{start,end}_request()
2026-07-27 14:42 ` [PATCH v5 20/27] scsi-multipath: add scsi_mpath_{start,end}_request() John Garry
@ 2026-07-27 15:25 ` sashiko-bot
2026-07-27 16:18 ` John Garry
0 siblings, 1 reply; 71+ messages in thread
From: sashiko-bot @ 2026-07-27 15:25 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Critical] Synchronous dispatch errors and request cancellations bypass `scsi_mpath_end_request()`, permanently leaking resources and causing NULL pointer dereferences on recycled tags.
- [Critical] The dummy implementation of `scsi_mpath_end_request()` skips `blk_update_request()`, silently dropping bio completions if CONFIG_SCSI_MULTIPATH is disabled.
--
--- Patch [20]: [PATCH v5 20/27] scsi-multipath: add scsi_mpath_{start,end}_request() ---
commit 375ed243ce77c97b0b6e6eb569248a68ab42aec6
Author: John Garry <john.garry@linux.dev>
scsi-multipath: add scsi_mpath_{start,end}_request()
This commit introduces scsi_mpath_start_request() and scsi_mpath_end_request()
to update private multipath request data. It also adds mpath_nr_active to
Scsi_Host to support queue depth multipath iopolicies.
> diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
> index 6a2d7dbfbe8c3..36e5c5543754f 100644
> --- a/drivers/scsi/scsi_lib.c
> +++ b/drivers/scsi/scsi_lib.c
> @@ -669,7 +669,10 @@ static bool scsi_end_request(struct request *req, blk_status_t error,
> struct scsi_device *sdev = cmd->device;
> struct request_queue *q = sdev->request_queue;
>
> - if (blk_update_request(req, error, bytes))
> + if (is_mpath_request(req)) {
> + if (scsi_mpath_end_request(req, error, bytes))
> + return true;
> + } else if (blk_update_request(req, error, bytes))
> return true;
[Severity: Critical]
If CONFIG_SCSI_MULTIPATH is disabled, the dummy stub for
scsi_mpath_end_request() unconditionally returns false.
If a request has REQ_MPATH set (which aliases REQ_DRV and can be set by
other subsystems like Device Mapper), won't the else if branch be completely
skipped?
Could this result in blk_update_request() never being called, leaking bios
and hanging the submitting task waiting for completion?
> [ ... ]
> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
> index d79e1b7b90051..eae5864ce5826 100644
> --- a/drivers/scsi/scsi_multipath.c
> +++ b/drivers/scsi/scsi_multipath.c
> [ ... ]
> +bool scsi_mpath_end_request(struct request *req, blk_status_t error,
> + unsigned int nr_bytes)
> +{
> + struct scsi_cmnd *scmd = blk_mq_rq_to_pdu(req);
> + struct scsi_device *sdev = scmd->device;
> + struct block_device *bi_bdev = NULL;
> +
> + if (scmd->flags & SCMD_MPATH_IO_STATS) {
> + struct bio *clone = req->bio, *master = clone->bi_private;
> +
> + bi_bdev = master->bi_bdev;
> + }
[Severity: Critical]
If a request is requeued, SCMD_MPATH_IO_STATS and SCMD_MPATH_CNT_ACTIVE are
preserved by SCMD_PRESERVED_FLAGS. If the request later fails an early state
check in scsi_queue_rq() (e.g., the device goes offline), blk-mq ends it
directly via blk_mq_end_request(), bypassing scsi_end_request().
When the tag is reused for a dataless request, scsi_mpath_start_request()
returns early without clearing the flags. Will this cause
scsi_mpath_end_request() to read the leaked SCMD_MPATH_IO_STATS flag and
dereference req->bio which is NULL?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727144234.3802948-1-john.g.garry@oracle.com?part=20
^ permalink raw reply [flat|nested] 71+ messages in thread* Re: [PATCH v5 20/27] scsi-multipath: add scsi_mpath_{start,end}_request()
2026-07-27 15:25 ` sashiko-bot
@ 2026-07-27 16:18 ` John Garry
0 siblings, 0 replies; 71+ messages in thread
From: John Garry @ 2026-07-27 16:18 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 27/07/2026 16:25, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [Critical] Synchronous dispatch errors and request cancellations bypass `scsi_mpath_end_request()`, permanently leaking resources and causing NULL pointer dereferences on recycled tags.
> - [Critical] The dummy implementation of `scsi_mpath_end_request()` skips `blk_update_request()`, silently dropping bio completions if CONFIG_SCSI_MULTIPATH is disabled.
> --
>
> --- Patch [20]: [PATCH v5 20/27] scsi-multipath: add scsi_mpath_{start,end}_request() ---
> commit 375ed243ce77c97b0b6e6eb569248a68ab42aec6
> Author: John Garry <john.garry@linux.dev>
> scsi-multipath: add scsi_mpath_{start,end}_request()
>
> This commit introduces scsi_mpath_start_request() and scsi_mpath_end_request()
> to update private multipath request data. It also adds mpath_nr_active to
> Scsi_Host to support queue depth multipath iopolicies.
>
>> diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
>> index 6a2d7dbfbe8c3..36e5c5543754f 100644
>> --- a/drivers/scsi/scsi_lib.c
>> +++ b/drivers/scsi/scsi_lib.c
>> @@ -669,7 +669,10 @@ static bool scsi_end_request(struct request *req, blk_status_t error,
>> struct scsi_device *sdev = cmd->device;
>> struct request_queue *q = sdev->request_queue;
>>
>> - if (blk_update_request(req, error, bytes))
>> + if (is_mpath_request(req)) {
>> + if (scsi_mpath_end_request(req, error, bytes))
>> + return true;
>> + } else if (blk_update_request(req, error, bytes))
>> return true;
>
> [Severity: Critical]
> If CONFIG_SCSI_MULTIPATH is disabled, the dummy stub for
> scsi_mpath_end_request() unconditionally returns false.
>
> If a request has REQ_MPATH set (which aliases REQ_DRV and can be set by
> other subsystems like Device Mapper), won't the else if branch be completely
> skipped?
Nobody else should set it, but I can check this
>
> Could this result in blk_update_request() never being called, leaking bios
> and hanging the submitting task waiting for completion?
>
>> [ ... ]
>> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
>> index d79e1b7b90051..eae5864ce5826 100644
>> --- a/drivers/scsi/scsi_multipath.c
>> +++ b/drivers/scsi/scsi_multipath.c
>> [ ... ]
>> +bool scsi_mpath_end_request(struct request *req, blk_status_t error,
>> + unsigned int nr_bytes)
>> +{
>> + struct scsi_cmnd *scmd = blk_mq_rq_to_pdu(req);
>> + struct scsi_device *sdev = scmd->device;
>> + struct block_device *bi_bdev = NULL;
>> +
>> + if (scmd->flags & SCMD_MPATH_IO_STATS) {
>> + struct bio *clone = req->bio, *master = clone->bi_private;
>> +
>> + bi_bdev = master->bi_bdev;
>> + }
>
> [Severity: Critical]
> If a request is requeued, SCMD_MPATH_IO_STATS and SCMD_MPATH_CNT_ACTIVE are
> preserved by SCMD_PRESERVED_FLAGS.
Yes, as sashiko suggested.
> If the request later fails an early state
> check in scsi_queue_rq() (e.g., the device goes offline), blk-mq ends it
> directly via blk_mq_end_request(), bypassing scsi_end_request().
>
> When the tag is reused for a dataless request, scsi_mpath_start_request()
> returns early without clearing the flags. Will this cause
> scsi_mpath_end_request() to read the leaked SCMD_MPATH_IO_STATS flag and
> dereference req->bio which is NULL?
>
ok, I can clear those flags
^ permalink raw reply [flat|nested] 71+ messages in thread
* [PATCH v5 21/27] scsi-multipath: add delayed disk removal support
2026-07-27 14:42 [PATCH v5 00/27] Native SCSI Multipath support John Garry
` (19 preceding siblings ...)
2026-07-27 14:42 ` [PATCH v5 20/27] scsi-multipath: add scsi_mpath_{start,end}_request() John Garry
@ 2026-07-27 14:42 ` John Garry
2026-07-27 15:23 ` sashiko-bot
2026-07-27 14:42 ` [PATCH v5 22/27] scsi: sd: add multipath disk class John Garry
` (5 subsequent siblings)
26 siblings, 1 reply; 71+ messages in thread
From: John Garry @ 2026-07-27 14:42 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
Add support in core code for delayed disk removal support. In this, the
callback calls into the scsi_driver to do the necessary removal work.
The scsi_disk driver (sd) must ensure that the scsi_mpath_device does not
go away while the delayed removal work is active, i.e. it must keep a
reference.
No reference to the scsi_disk multipath structures are kept outside that
driver, so that driver needs to provide a scsi_driver.mpath_remove_head
callback to do the necessary work.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/scsi_multipath.c | 27 +++++++++++++++++++++++++++
include/scsi/scsi_driver.h | 4 ++++
2 files changed, 31 insertions(+)
diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
index eae5864ce5826..ed9c83189942a 100644
--- a/drivers/scsi/scsi_multipath.c
+++ b/drivers/scsi/scsi_multipath.c
@@ -360,7 +360,34 @@ static bool scsi_mpath_available_path(struct mpath_device *mpath_device)
return false;
}
+static int scsi_mpath_remove_head_drv(struct device_driver *drv, void *data)
+{
+ struct scsi_mpath_head *scsi_mpath_head = data;
+ struct scsi_driver *scsi_driver = to_scsi_driver(drv);
+
+ if (scsi_driver->mpath_remove_head)
+ scsi_driver->mpath_remove_head(scsi_mpath_head);
+
+ return 0;
+}
+
+static void scsi_mpath_remove_head_work(struct mpath_head *mpath_head)
+{
+ struct scsi_mpath_head *scsi_mpath_head = to_scsi_mpath_head(mpath_head);
+
+ /*
+ * scsi_mpath_remove_head_drv() could drop the final reference to
+ * scsi_mpath_head, meaning that we pass a free'd pointer further
+ * drivers - take a reference so that won't happen.
+ */
+ scsi_mpath_get_head(scsi_mpath_head);
+ bus_for_each_drv(&scsi_bus_type, NULL, scsi_mpath_head,
+ scsi_mpath_remove_head_drv);
+ scsi_mpath_put_head(scsi_mpath_head);
+}
+
static struct mpath_head_template smpdt = {
+ .remove_head = scsi_mpath_remove_head_work,
.is_disabled = scsi_mpath_is_disabled,
.is_optimized = scsi_mpath_is_optimized,
.available_path = scsi_mpath_available_path,
diff --git a/include/scsi/scsi_driver.h b/include/scsi/scsi_driver.h
index 249cea724abd1..d92b63d357f2a 100644
--- a/include/scsi/scsi_driver.h
+++ b/include/scsi/scsi_driver.h
@@ -8,6 +8,7 @@
struct module;
struct request;
+struct scsi_mpath_head;
struct scsi_driver {
struct device_driver gendrv;
@@ -22,6 +23,9 @@ struct scsi_driver {
int (*done)(struct scsi_cmnd *);
int (*eh_action)(struct scsi_cmnd *, int);
void (*eh_reset)(struct scsi_cmnd *);
+ #ifdef CONFIG_SCSI_MULTIPATH
+ void (*mpath_remove_head)(struct scsi_mpath_head *);
+ #endif
};
#define to_scsi_driver(drv) \
container_of((drv), struct scsi_driver, gendrv)
--
2.43.7
^ permalink raw reply related [flat|nested] 71+ messages in thread* Re: [PATCH v5 21/27] scsi-multipath: add delayed disk removal support
2026-07-27 14:42 ` [PATCH v5 21/27] scsi-multipath: add delayed disk removal support John Garry
@ 2026-07-27 15:23 ` sashiko-bot
2026-07-27 16:20 ` John Garry
0 siblings, 1 reply; 71+ messages in thread
From: sashiko-bot @ 2026-07-27 15:23 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The delayed_work struct for removing the multipath head is never initialized for SCSI.
--
--- Patch [21]: [PATCH v5 21/27] scsi-multipath: add delayed disk removal support ---
commit 75d6c7bf920e841ac7f62e67cc466430605858b8
Author: John Garry <john.g.garry@oracle.com>
scsi-multipath: add delayed disk removal support
Add support in core code for delayed disk removal support. In this, the
callback calls into the scsi_driver to do the necessary removal work.
> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
> index eae5864ce5826..ed9c83189942a 100644
> --- a/drivers/scsi/scsi_multipath.c
> +++ b/drivers/scsi/scsi_multipath.c
[ ... ]
> @@ -360,7 +360,34 @@ static bool scsi_mpath_available_path(struct mpath_device *mpath_device)
[ ... ]
> static struct mpath_head_template smpdt = {
> + .remove_head = scsi_mpath_remove_head_work,
[Severity: High]
Does enabling this feature lead to an uninitialized work struct regression?
When delayed removal is triggered, it calls mod_delayed_work() on
mpath_head->remove_work. However, the initialization path for SCSI multipath
heads appears to omit INIT_DELAYED_WORK() for this work struct.
In drivers/scsi/scsi_multipath.c:scsi_mpath_alloc_head():
if (mpath_head_init(&scsi_mpath_head->mpath_head))
goto out_free;
mpath_head_init() zeroes the structure but does not initialize remove_work.
Could this lead to a panic in the workqueue core when operating on an
uninitialized work struct?
> .is_disabled = scsi_mpath_is_disabled,
> .is_optimized = scsi_mpath_is_optimized,
> .available_path = scsi_mpath_available_path,
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727144234.3802948-1-john.g.garry@oracle.com?part=21
^ permalink raw reply [flat|nested] 71+ messages in thread* Re: [PATCH v5 21/27] scsi-multipath: add delayed disk removal support
2026-07-27 15:23 ` sashiko-bot
@ 2026-07-27 16:20 ` John Garry
0 siblings, 0 replies; 71+ messages in thread
From: John Garry @ 2026-07-27 16:20 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 27/07/2026 16:23, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [High] The delayed_work struct for removing the multipath head is never initialized for SCSI.
> --
>
> --- Patch [21]: [PATCH v5 21/27] scsi-multipath: add delayed disk removal support ---
> commit 75d6c7bf920e841ac7f62e67cc466430605858b8
> Author: John Garry <john.g.garry@oracle.com>
>
> scsi-multipath: add delayed disk removal support
>
> Add support in core code for delayed disk removal support. In this, the
> callback calls into the scsi_driver to do the necessary removal work.
>
>> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
>> index eae5864ce5826..ed9c83189942a 100644
>> --- a/drivers/scsi/scsi_multipath.c
>> +++ b/drivers/scsi/scsi_multipath.c
> [ ... ]
>> @@ -360,7 +360,34 @@ static bool scsi_mpath_available_path(struct mpath_device *mpath_device)
> [ ... ]
>> static struct mpath_head_template smpdt = {
>> + .remove_head = scsi_mpath_remove_head_work,
>
> [Severity: High]
> Does enabling this feature lead to an uninitialized work struct regression?
>
> When delayed removal is triggered, it calls mod_delayed_work() on
> mpath_head->remove_work. However, the initialization path for SCSI multipath
> heads appears to omit INIT_DELAYED_WORK() for this work struct.
it's done in mpath_alloc_head_disk()
>
> In drivers/scsi/scsi_multipath.c:scsi_mpath_alloc_head():
>
> if (mpath_head_init(&scsi_mpath_head->mpath_head))
> goto out_free;
>
> mpath_head_init() zeroes the structure but does not initialize remove_work.
>
> Could this lead to a panic in the workqueue core when operating on an
> uninitialized work struct?
>
>> .is_disabled = scsi_mpath_is_disabled,
>> .is_optimized = scsi_mpath_is_optimized,
>> .available_path = scsi_mpath_available_path,
>
> [ ... ]
>
^ permalink raw reply [flat|nested] 71+ messages in thread
* [PATCH v5 22/27] scsi: sd: add multipath disk class
2026-07-27 14:42 [PATCH v5 00/27] Native SCSI Multipath support John Garry
` (20 preceding siblings ...)
2026-07-27 14:42 ` [PATCH v5 21/27] scsi-multipath: add delayed disk removal support John Garry
@ 2026-07-27 14:42 ` John Garry
2026-07-27 15:14 ` sashiko-bot
2026-07-27 14:42 ` [PATCH v5 23/27] scsi: sd: add multipath disk attr groups John Garry
` (4 subsequent siblings)
26 siblings, 1 reply; 71+ messages in thread
From: John Garry @ 2026-07-27 14:42 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
Add a new class, sd_mpath_disk_class, which is the multipath version of
the scsi_disk class.
Structure sd_mpath_disk is introduced to manage the multipath gendisk.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/sd.c | 43 ++++++++++++++++++++++++++++++++++++++++++-
drivers/scsi/sd.h | 3 +++
2 files changed, 45 insertions(+), 1 deletion(-)
diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
index 599e75f333343..242a15bc2c5bb 100644
--- a/drivers/scsi/sd.c
+++ b/drivers/scsi/sd.c
@@ -70,6 +70,7 @@
#include <scsi/scsi_ioctl.h>
#include <scsi/scsicam.h>
#include <scsi/scsi_common.h>
+#include <scsi/scsi_multipath.h>
#include "sd.h"
#include "scsi_priv.h"
@@ -113,6 +114,39 @@ static mempool_t *sd_page_pool;
static mempool_t *sd_large_page_pool;
static atomic_t sd_large_page_pool_users = ATOMIC_INIT(0);
static struct lock_class_key sd_bio_compl_lkclass;
+#ifdef CONFIG_SCSI_MULTIPATH
+struct sd_mpath_disk {
+ struct scsi_mpath_head *scsi_mpath_head;
+};
+
+static void sd_mpath_disk_release(struct device *dev)
+{
+}
+
+static const struct class sd_mpath_disk_class = {
+ .name = "scsi_mpath_disk",
+ .dev_release = sd_mpath_disk_release,
+};
+
+static int sd_mpath_class_register(void)
+{
+ return class_register(&sd_mpath_disk_class);
+}
+
+static void sd_mpath_class_unregister(void)
+{
+ class_unregister(&sd_mpath_disk_class);
+}
+#else /* CONFIG_SCSI_MULTIPATH */
+static int sd_mpath_class_register(void)
+{
+ return 0;
+}
+
+static void sd_mpath_class_unregister(void)
+{
+}
+#endif
static const char *sd_cache_types[] = {
"write through", "none", "write back",
@@ -4453,11 +4487,15 @@ static int __init init_sd(void)
if (err)
goto err_out;
+ err = sd_mpath_class_register();
+ if (err)
+ goto err_out_class;
+
sd_page_pool = mempool_create_page_pool(SD_MEMPOOL_SIZE, 0);
if (!sd_page_pool) {
printk(KERN_ERR "sd: can't init discard page pool\n");
err = -ENOMEM;
- goto err_out_class;
+ goto err_out_mpath_class;
}
err = scsi_register_driver(&sd_template);
@@ -4468,6 +4506,8 @@ static int __init init_sd(void)
err_out_driver:
mempool_destroy(sd_page_pool);
+err_out_mpath_class:
+ sd_mpath_class_unregister();
err_out_class:
class_unregister(&sd_disk_class);
err_out:
@@ -4493,6 +4533,7 @@ static void __exit exit_sd(void)
mempool_destroy(sd_large_page_pool);
class_unregister(&sd_disk_class);
+ sd_mpath_class_unregister();
for (i = 0; i < SD_MAJORS; i++)
unregister_blkdev(sd_major(i), "sd");
diff --git a/drivers/scsi/sd.h b/drivers/scsi/sd.h
index 574af82430169..22438173ea2f8 100644
--- a/drivers/scsi/sd.h
+++ b/drivers/scsi/sd.h
@@ -83,6 +83,9 @@ struct zoned_disk_info {
struct scsi_disk {
struct scsi_device *device;
+#ifdef CONFIG_SCSI_MULTIPATH
+ struct sd_mpath_disk *sd_mpath_disk;
+#endif
/*
* disk_dev is used to show attributes in /sys/class/scsi_disk/,
--
2.43.7
^ permalink raw reply related [flat|nested] 71+ messages in thread* Re: [PATCH v5 22/27] scsi: sd: add multipath disk class
2026-07-27 14:42 ` [PATCH v5 22/27] scsi: sd: add multipath disk class John Garry
@ 2026-07-27 15:14 ` sashiko-bot
2026-07-27 16:21 ` John Garry
0 siblings, 1 reply; 71+ messages in thread
From: sashiko-bot @ 2026-07-27 15:14 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Empty device release function `sd_mpath_disk_release` violates kobject API rules, masking a future memory leak or Use-After-Free.
--
--- Patch [22]: [PATCH v5 22/27] scsi: sd: add multipath disk class ---
commit f2a48f7b23ab293ee7178e9c9e97a979e61dbc55
Author: John Garry <john.g.garry@oracle.com>
scsi: sd: add multipath disk class
This commit adds a new class, sd_mpath_disk_class, which acts as the
multipath version of the scsi_disk class. It also introduces the
sd_mpath_disk structure to manage the multipath gendisk.
> diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
> index 599e75f333343..242a15bc2c5bb 100644
> --- a/drivers/scsi/sd.c
> +++ b/drivers/scsi/sd.c
[ ... ]
> @@ -113,6 +114,39 @@ static mempool_t *sd_page_pool;
> static mempool_t *sd_large_page_pool;
> static atomic_t sd_large_page_pool_users = ATOMIC_INIT(0);
> static struct lock_class_key sd_bio_compl_lkclass;
> +#ifdef CONFIG_SCSI_MULTIPATH
> +struct sd_mpath_disk {
> + struct scsi_mpath_head *scsi_mpath_head;
> +};
> +
[Severity: Medium]
Does this empty release function violate the device lifecycle rules?
The kernel core API strictly forbids empty device release functions (often
referred to as 'mocking kobjects'). An empty release callback prevents the
proper freeing of the structure containing the device, which forces the
memory to either be leaked when the reference count drops to zero, or freed
synchronously elsewhere leading to a Use-After-Free.
> +static void sd_mpath_disk_release(struct device *dev)
> +{
> +}
> +
> +static const struct class sd_mpath_disk_class = {
> + .name = "scsi_mpath_disk",
> + .dev_release = sd_mpath_disk_release,
> +};
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727144234.3802948-1-john.g.garry@oracle.com?part=22
^ permalink raw reply [flat|nested] 71+ messages in thread* Re: [PATCH v5 22/27] scsi: sd: add multipath disk class
2026-07-27 15:14 ` sashiko-bot
@ 2026-07-27 16:21 ` John Garry
0 siblings, 0 replies; 71+ messages in thread
From: John Garry @ 2026-07-27 16:21 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 27/07/2026 16:14, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Medium] Empty device release function `sd_mpath_disk_release` violates kobject API rules, masking a future memory leak or Use-After-Free.
> --
>
> --- Patch [22]: [PATCH v5 22/27] scsi: sd: add multipath disk class ---
> commit f2a48f7b23ab293ee7178e9c9e97a979e61dbc55
> Author: John Garry <john.g.garry@oracle.com>
>
> scsi: sd: add multipath disk class
>
> This commit adds a new class, sd_mpath_disk_class, which acts as the
> multipath version of the scsi_disk class. It also introduces the
> sd_mpath_disk structure to manage the multipath gendisk.
>
>> diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
>> index 599e75f333343..242a15bc2c5bb 100644
>> --- a/drivers/scsi/sd.c
>> +++ b/drivers/scsi/sd.c
>
> [ ... ]
>
>> @@ -113,6 +114,39 @@ static mempool_t *sd_page_pool;
>> static mempool_t *sd_large_page_pool;
>> static atomic_t sd_large_page_pool_users = ATOMIC_INIT(0);
>> static struct lock_class_key sd_bio_compl_lkclass;
>> +#ifdef CONFIG_SCSI_MULTIPATH
>> +struct sd_mpath_disk {
>> + struct scsi_mpath_head *scsi_mpath_head;
>> +};
>> +
>
> [Severity: Medium]
> Does this empty release function violate the device lifecycle rules?
it's added later
>
> The kernel core API strictly forbids empty device release functions (often
> referred to as 'mocking kobjects'). An empty release callback prevents the
> proper freeing of the structure containing the device, which forces the
> memory to either be leaked when the reference count drops to zero, or freed
> synchronously elsewhere leading to a Use-After-Free.
>
>> +static void sd_mpath_disk_release(struct device *dev)
>> +{
>> +}
>> +
>> +static const struct class sd_mpath_disk_class = {
>> + .name = "scsi_mpath_disk",
>> + .dev_release = sd_mpath_disk_release,
>> +};
>
> [ ... ]
>
^ permalink raw reply [flat|nested] 71+ messages in thread
* [PATCH v5 23/27] scsi: sd: add multipath disk attr groups
2026-07-27 14:42 [PATCH v5 00/27] Native SCSI Multipath support John Garry
` (21 preceding siblings ...)
2026-07-27 14:42 ` [PATCH v5 22/27] scsi: sd: add multipath disk class John Garry
@ 2026-07-27 14:42 ` John Garry
2026-07-27 15:20 ` sashiko-bot
2026-07-27 14:42 ` [PATCH v5 24/27] scsi: sd: support multipath disk John Garry
` (3 subsequent siblings)
26 siblings, 1 reply; 71+ messages in thread
From: John Garry @ 2026-07-27 14:42 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
From: John Garry <john.garry@linux.dev>
Set multipath disk attr groups, which includes delayed disk removal and
everything from mpath_attr_group.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/sd.c | 36 ++++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
index 242a15bc2c5bb..36c745cbb2619 100644
--- a/drivers/scsi/sd.c
+++ b/drivers/scsi/sd.c
@@ -4006,6 +4006,42 @@ static int sd_format_disk_name(char *prefix, int index, char *buf, int buflen)
return 0;
}
+static ssize_t sd_mpath_device_delayed_removal_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct mpath_head *mpath_head = mpath_bd_device_to_head(dev);
+
+ return mpath_delayed_removal_secs_store(mpath_head, buf, count);
+}
+
+static ssize_t sd_mpath_device_delayed_removal_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mpath_head *mpath_head = mpath_bd_device_to_head(dev);
+
+ return mpath_delayed_removal_secs_show(mpath_head, buf);
+}
+
+static DEVICE_ATTR(delayed_removal_secs, S_IRUGO | S_IWUSR,
+ sd_mpath_device_delayed_removal_show,
+ sd_mpath_device_delayed_removal_store);
+
+static struct attribute *sd_mpath_disk_attrs[] = {
+ &dev_attr_delayed_removal_secs.attr,
+ NULL
+};
+
+static const struct attribute_group sd_mpath_disk_attr_group = {
+ .attrs = sd_mpath_disk_attrs,
+};
+
+__maybe_unused
+static const struct attribute_group *sd_mpath_disk_attr_groups[] = {
+ &sd_mpath_disk_attr_group,
+ &mpath_attr_group,
+ NULL
+};
+
/**
* sd_probe - called during driver initialization and whenever a
* new scsi device is attached to the system. It is called once
--
2.43.7
^ permalink raw reply related [flat|nested] 71+ messages in thread* Re: [PATCH v5 23/27] scsi: sd: add multipath disk attr groups
2026-07-27 14:42 ` [PATCH v5 23/27] scsi: sd: add multipath disk attr groups John Garry
@ 2026-07-27 15:20 ` sashiko-bot
2026-07-27 16:22 ` John Garry
0 siblings, 1 reply; 71+ messages in thread
From: sashiko-bot @ 2026-07-27 15:20 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The newly defined multipath disk attribute groups are completely unreferenced and never hooked up to the multipath structures, rendering the feature inactive.
--
--- Patch [23]: [PATCH v5 23/27] scsi: sd: add multipath disk attr groups ---
commit 54820f0d02e2f6d8f0530b7358f2376bde300894
Author: John Garry <john.garry@linux.dev>
scsi: sd: add multipath disk attr groups
This commit adds sysfs attribute groups for multipath disks in the SCSI
subsystem. It defines attributes for delayed disk removal and includes
everything from the existing mpath attribute group.
> diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
> index 242a15bc2c5bb..36c745cbb2619 100644
> --- a/drivers/scsi/sd.c
> +++ b/drivers/scsi/sd.c
[ ... ]
> +static const struct attribute_group sd_mpath_disk_attr_group = {
> + .attrs = sd_mpath_disk_attrs,
> +};
> +
> +__maybe_unused
> +static const struct attribute_group *sd_mpath_disk_attr_groups[] = {
> + &sd_mpath_disk_attr_group,
> + &mpath_attr_group,
> + NULL
> +};
[Severity: Medium]
Are these newly defined multipath disk attribute groups missing a registration
step?
They are defined here and explicitly marked with __maybe_unused, but do not
appear to be assigned to any registration structure or hooked up to the active
multipath structures. Does this leave the new feature inactive and the
attributes unexposed to userspace?
> +
> /**
> * sd_probe - called during driver initialization and whenever a
> * new scsi device is attached to the system. It is called once
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727144234.3802948-1-john.g.garry@oracle.com?part=23
^ permalink raw reply [flat|nested] 71+ messages in thread* Re: [PATCH v5 23/27] scsi: sd: add multipath disk attr groups
2026-07-27 15:20 ` sashiko-bot
@ 2026-07-27 16:22 ` John Garry
0 siblings, 0 replies; 71+ messages in thread
From: John Garry @ 2026-07-27 16:22 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 27/07/2026 16:20, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Medium] The newly defined multipath disk attribute groups are completely unreferenced and never hooked up to the multipath structures, rendering the feature inactive.
> --
>
> --- Patch [23]: [PATCH v5 23/27] scsi: sd: add multipath disk attr groups ---
> commit 54820f0d02e2f6d8f0530b7358f2376bde300894
> Author: John Garry <john.garry@linux.dev>
>
> scsi: sd: add multipath disk attr groups
>
> This commit adds sysfs attribute groups for multipath disks in the SCSI
> subsystem. It defines attributes for delayed disk removal and includes
> everything from the existing mpath attribute group.
>
>> diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
>> index 242a15bc2c5bb..36c745cbb2619 100644
>> --- a/drivers/scsi/sd.c
>> +++ b/drivers/scsi/sd.c
>
> [ ... ]
>
>> +static const struct attribute_group sd_mpath_disk_attr_group = {
>> + .attrs = sd_mpath_disk_attrs,
>> +};
>> +
>> +__maybe_unused
>> +static const struct attribute_group *sd_mpath_disk_attr_groups[] = {
>> + &sd_mpath_disk_attr_group,
>> + &mpath_attr_group,
>> + NULL
>> +};
>
> [Severity: Medium]
> Are these newly defined multipath disk attribute groups missing a registration
> step?
they are referenced later
>
> They are defined here and explicitly marked with __maybe_unused, but do not
> appear to be assigned to any registration structure or hooked up to the active
> multipath structures. Does this leave the new feature inactive and the
> attributes unexposed to userspace?
>
>> +
>> /**
>> * sd_probe - called during driver initialization and whenever a
>> * new scsi device is attached to the system. It is called once
>
^ permalink raw reply [flat|nested] 71+ messages in thread
* [PATCH v5 24/27] scsi: sd: support multipath disk
2026-07-27 14:42 [PATCH v5 00/27] Native SCSI Multipath support John Garry
` (22 preceding siblings ...)
2026-07-27 14:42 ` [PATCH v5 23/27] scsi: sd: add multipath disk attr groups John Garry
@ 2026-07-27 14:42 ` John Garry
2026-07-27 15:20 ` sashiko-bot
2026-07-27 14:42 ` [PATCH v5 25/27] scsi: sd: add mpath_dev file John Garry
` (2 subsequent siblings)
26 siblings, 1 reply; 71+ messages in thread
From: John Garry @ 2026-07-27 14:42 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
From: John Garry <john.garry@linux.dev>
Add support to attach a multipath disk.
We still allocate the gendisk per path, and this is required for the
per-path submission. However, those gendisks are marked as hidden. Those
disks are named sdX:Y, where X is the multipath disk index and Y is the
per-path index.
A global list of sd_mpath_disks is kept for matching scsi_device's.
The multipath gendisk has the name and disk->major/minor set to minic a
scsi_disk.
The following is an example of relevant scsi_disk and block sysfs
directories:
$ ls -l /sys/block/ | grep sdc
lrwxrwxrwx 1 root root 0 Feb 24 16:01 sdc -> ../devices/virtual/scsi_mpath_disk/0/sdc
lrwxrwxrwx 1 root root 0 Feb 24 16:01 sdc:0 -> ../devices/platform/host8/session1/target8:0:0/8:0:0:0/block/sdc:0
lrwxrwxrwx 1 root root 0 Feb 24 16:02 sdc:1 -> ../devices/platform/host9/session2/target9:0:0/9:0:0:0/block/sdc:1
$ ls -l /sys/class/scsi_mpath_disk/scsi_mpath_disk0/
total 0
drwxr-xr-x 2 root root 0 Feb 24 16:03 power
drwxr-xr-x 11 root root 0 Feb 24 16:01 sdc
lrwxrwxrwx 1 root root 0 Feb 24 16:01 subsystem -> ../../../../class/scsi_mpath_disk
-rw-r--r-- 1 root root 4096 Feb 24 16:01 uevent
$ ls -l /sys/class/scsi_mpath_disk/scsi_mpath_disk0/sdc/multipath/
total 0
lrwxrwxrwx 1 root root 0 Feb 24 16:20 sdc:0 -> ../../../../../platform/host8/session1/target8:0:0/8:0:0:0/block/sdc:0
lrwxrwxrwx 1 root root 0 Feb 24 16:20 sdc:1 -> ../../../../../platform/host9/session2/target9:0:0/9:0:0:0/block/sdc:1
$ ls -l /dev/sdc*
brw-rw---- 1 root disk 8, 32 Feb 24 16:01 /dev/sdc
brw-rw---- 1 root disk 8, 33 Feb 24 16:01 /dev/sdc1
brw-rw---- 1 root disk 8, 34 Feb 24 16:01 /dev/sdc2
$ lsblk /dev/sdc
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sdc 8:32 0 600M 0 disk
|-sdc1 8:33 0 9M 0 part
`-sdc2 8:34 0 568M 0 part
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/sd.c | 425 +++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 405 insertions(+), 20 deletions(-)
diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
index 36c745cbb2619..f044b2dfaa9fd 100644
--- a/drivers/scsi/sd.c
+++ b/drivers/scsi/sd.c
@@ -115,12 +115,30 @@ static mempool_t *sd_large_page_pool;
static atomic_t sd_large_page_pool_users = ATOMIC_INIT(0);
static struct lock_class_key sd_bio_compl_lkclass;
#ifdef CONFIG_SCSI_MULTIPATH
+static LIST_HEAD(sd_mpath_disks_list);
+static DEFINE_MUTEX(sd_mpath_disks_lock);
+
struct sd_mpath_disk {
+ struct device dev;
+ int disk_index;
+ int disk_count;
+ struct list_head entry;
struct scsi_mpath_head *scsi_mpath_head;
};
static void sd_mpath_disk_release(struct device *dev)
{
+ struct sd_mpath_disk *sd_mpath_disk =
+ container_of(dev, struct sd_mpath_disk, dev);
+ struct scsi_mpath_head *scsi_mpath_head =
+ sd_mpath_disk->scsi_mpath_head;
+ struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
+
+ mpath_put_disk(mpath_head);
+ ida_free(&sd_index_ida, sd_mpath_disk->disk_index);
+ scsi_mpath_put_head(scsi_mpath_head);
+
+ kfree(sd_mpath_disk);
}
static const struct class sd_mpath_disk_class = {
@@ -817,7 +835,8 @@ static void scsi_disk_release(struct device *dev)
{
struct scsi_disk *sdkp = to_scsi_disk(dev);
- ida_free(&sd_index_ida, sdkp->index);
+ if (sdkp->index >= 0)
+ ida_free(&sd_index_ida, sdkp->index);
put_device(&sdkp->device->sdev_gendev);
free_opal_dev(sdkp->opal_dev);
@@ -4006,6 +4025,90 @@ static int sd_format_disk_name(char *prefix, int index, char *buf, int buflen)
return 0;
}
+#ifdef CONFIG_SCSI_MULTIPATH
+static int sd_mpath_revalidate_head(struct scsi_disk *sdkp)
+{
+ struct sd_mpath_disk *sd_mpath_disk = sdkp->sd_mpath_disk;
+ struct scsi_mpath_head *scsi_mpath_head = sd_mpath_disk->scsi_mpath_head;
+ struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
+ struct gendisk *disk = mpath_head->disk;
+ struct queue_limits *sdkp_lim = &sdkp->disk->queue->limits;
+ struct queue_limits lim;
+ unsigned int memflags;
+ int ret;
+
+ lim = queue_limits_start_update(disk->queue);
+ memflags = blk_mq_freeze_queue(disk->queue);
+
+ lim.logical_block_size = sdkp_lim->logical_block_size;
+ lim.physical_block_size = sdkp_lim->physical_block_size;
+ lim.io_min = sdkp_lim->io_min;
+ lim.io_opt = sdkp_lim->io_opt;
+
+ queue_limits_stack_bdev(&lim, sdkp->disk->part0, 0,
+ disk->disk_name);
+
+ /* TODO: setup integrity and zoned limits */
+ lim.max_write_streams = sdkp_lim->max_write_streams;
+ lim.write_stream_granularity = sdkp_lim->write_stream_granularity;
+ ret = queue_limits_commit_update(disk->queue, &lim);
+
+ set_capacity_and_notify(disk, get_capacity(sdkp->disk));
+
+ mpath_revalidate_paths(mpath_head);
+
+ blk_mq_unfreeze_queue(disk->queue, memflags);
+
+ return ret;
+}
+
+static int sd_mpath_get_disk(struct sd_mpath_disk *sd_mpath_disk)
+{
+ if (!get_device(&sd_mpath_disk->dev))
+ return -ENXIO;
+ return 0;
+}
+
+static void sd_mpath_put_disk(struct sd_mpath_disk *sd_mpath_disk)
+{
+ put_device(&sd_mpath_disk->dev);
+}
+
+static struct sd_mpath_disk *sd_mpath_find_disk(
+ struct scsi_mpath_head *scsi_mpath_head)
+{
+ struct sd_mpath_disk *sd_mpath_disk;
+ int ret;
+
+ list_for_each_entry(sd_mpath_disk, &sd_mpath_disks_list, entry) {
+ ret = sd_mpath_get_disk(sd_mpath_disk);
+ if (ret)
+ continue;
+
+ if (sd_mpath_disk->scsi_mpath_head == scsi_mpath_head)
+ return sd_mpath_disk;
+
+ sd_mpath_put_disk(sd_mpath_disk);
+ }
+
+ return NULL;
+}
+
+static int sd_mpath_add_disk(struct scsi_disk *sdkp)
+{
+ struct scsi_device *sdp = sdkp->device;
+ struct scsi_mpath_device *scsi_mpath_dev = sdp->scsi_mpath_dev;
+ struct mpath_device *mpath_device = &scsi_mpath_dev->mpath_device;
+ int ret;
+
+ ret = mpath_add_device(mpath_device, sdkp->disk,
+ dev_to_node(sdp->host->dma_dev), &sdp->host->mpath_nr_active);
+ if (ret)
+ return ret;
+ mpath_device_set_live(mpath_device);
+ return 0;
+}
+
static ssize_t sd_mpath_device_delayed_removal_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
@@ -4035,13 +4138,259 @@ static const struct attribute_group sd_mpath_disk_attr_group = {
.attrs = sd_mpath_disk_attrs,
};
-__maybe_unused
static const struct attribute_group *sd_mpath_disk_attr_groups[] = {
&sd_mpath_disk_attr_group,
&mpath_attr_group,
NULL
};
+static int sd_mpath_probe(struct scsi_disk *sdkp)
+{
+ struct scsi_device *sdp = sdkp->device;
+ struct scsi_mpath_device *scsi_mpath_dev = sdp->scsi_mpath_dev;
+ struct device *dma_dev = sdp->host->dma_dev;
+ struct scsi_mpath_head *scsi_mpath_head =
+ scsi_mpath_dev->scsi_mpath_head;
+ struct sd_mpath_disk *sd_mpath_disk;
+ struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
+ char disk_name[DISK_NAME_LEN - 2];
+ struct queue_limits lim;
+ struct gendisk *disk;
+ int error;
+
+ /*
+ * sd_mpath_disks_list is kept locked if no disk found.
+ * Otherwise an extra reference is taken.
+ */
+ mutex_lock(&sd_mpath_disks_lock);
+ sd_mpath_disk = sd_mpath_find_disk(scsi_mpath_head);
+ if (sd_mpath_disk) {
+ error = sized_strscpy(disk_name, mpath_head->disk->disk_name,
+ sizeof(disk_name));
+ if (error < 0) {
+ /*
+ * Should not happen as would fail for the same when
+ * allocating the sd_mpath_disk
+ */
+ sd_mpath_put_disk(sd_mpath_disk);
+ mutex_unlock(&sd_mpath_disks_lock);
+ return error;
+ }
+ sd_mpath_disk->disk_count++;
+ mutex_unlock(&sd_mpath_disks_lock);
+
+ goto found;
+ }
+
+ sd_mpath_disk = kzalloc(sizeof(*sd_mpath_disk), GFP_KERNEL);
+ if (!sd_mpath_disk) {
+ error = -ENOMEM;
+ goto out_unlock;
+ }
+
+ sd_mpath_disk->scsi_mpath_head = scsi_mpath_head;
+
+ blk_set_stacking_limits(&lim);
+ lim.dma_alignment = 3;
+ lim.features |= BLK_FEAT_IO_STAT | BLK_FEAT_NOWAIT |
+ BLK_FEAT_POLL | BLK_FEAT_ATOMIC_WRITES;
+
+ mpath_head->parent = &sd_mpath_disk->dev;
+ mpath_head->drv_module = THIS_MODULE;
+ mpath_head->disk_groups = sd_mpath_disk_attr_groups;
+ error = mpath_alloc_head_disk(mpath_head, &lim,
+ dev_to_node(dma_dev));
+ if (error)
+ goto out_free_disk;
+ disk = mpath_head->disk;
+
+ error = ida_alloc(&sd_index_ida, GFP_KERNEL);
+ if (error < 0) {
+ sdev_printk(KERN_WARNING, sdp, "sd_mpath_probe: memory exhausted.\n");
+ goto out_put_disk;
+ }
+ sd_mpath_disk->disk_index = error;
+ error = sd_format_disk_name("sd", sd_mpath_disk->disk_index,
+ disk->disk_name, DISK_NAME_LEN);
+ if (error)
+ goto out_free_index;
+
+ error = sized_strscpy(disk_name, mpath_head->disk->disk_name,
+ sizeof(disk_name));
+ if (error < 0)
+ goto out_free_index;
+
+ device_initialize(&sd_mpath_disk->dev);
+ sd_mpath_disk->dev.class = &sd_mpath_disk_class;
+
+ /* undone in sd_mpath_disk_release() */
+ scsi_mpath_get_head(scsi_mpath_head);
+
+ error = dev_set_name(&sd_mpath_disk->dev, "scsi_mpath_disk%d",
+ scsi_mpath_head->index);
+ if (error) {
+ put_device(&sd_mpath_disk->dev);
+ goto out_unlock;
+ }
+
+ error = device_add(&sd_mpath_disk->dev);
+ if (error) {
+ put_device(&sd_mpath_disk->dev);
+ goto out_unlock;
+ }
+
+ list_add_tail(&sd_mpath_disk->entry, &sd_mpath_disks_list);
+ disk->major = sd_major((sd_mpath_disk->disk_index & 0xf0) >> 4);
+ disk->first_minor = ((sd_mpath_disk->disk_index & 0xf) << 4) |
+ (sd_mpath_disk->disk_index & 0xfff00);
+ disk->minors = SD_MINORS;
+
+ sd_mpath_disk->disk_count = 1;
+ mutex_unlock(&sd_mpath_disks_lock);
+found:
+ sdkp->sd_mpath_disk = sd_mpath_disk;
+ sdkp->disk->flags |= GENHD_FL_HIDDEN;
+ snprintf(sdkp->disk->disk_name, DISK_NAME_LEN, "%s:%d",
+ disk_name, scsi_mpath_dev->index);
+
+ sdkp->index = -1;
+ return 0;
+
+out_free_index:
+ ida_free(&sd_index_ida, sd_mpath_disk->disk_index);
+out_put_disk:
+ mpath_put_disk(mpath_head);
+out_free_disk:
+ kfree(sd_mpath_disk);
+out_unlock:
+ mutex_unlock(&sd_mpath_disks_lock);
+ return error;
+}
+
+static void sd_mpath_remove(struct scsi_disk *sdkp)
+{
+ struct sd_mpath_disk *sd_mpath_disk = sdkp->sd_mpath_disk;
+ struct scsi_device *sdp = sdkp->device;
+ struct scsi_mpath_device *scsi_mpath_dev = sdp->scsi_mpath_dev;
+ struct mpath_device *mpath_device = &scsi_mpath_dev->mpath_device;
+ struct scsi_mpath_head *scsi_mpath_head = sd_mpath_disk->scsi_mpath_head;
+ struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
+ bool remove = false;
+
+ mpath_synchronize(mpath_head);
+
+ if (mpath_clear_current_path(mpath_device))
+ mpath_synchronize(mpath_head);
+
+ mpath_delete_device(mpath_device);
+
+ mutex_lock(&sd_mpath_disks_lock);
+ sd_mpath_disk->disk_count--;
+ if (!sd_mpath_disk->disk_count && mpath_can_remove_head(mpath_head)) {
+ list_del_init(&sd_mpath_disk->entry);
+ remove = true;
+ }
+ mutex_unlock(&sd_mpath_disks_lock);
+ mpath_remove_sysfs_link(mpath_device);
+ mpath_device->disk = NULL;
+
+ if (remove) {
+ device_del(&sd_mpath_disk->dev);
+ mpath_remove_disk(mpath_head);
+ }
+ sd_mpath_put_disk(sd_mpath_disk);
+}
+
+static void sd_mpath_remove_head(struct scsi_mpath_head *scsi_mpath_head)
+{
+ struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
+ struct sd_mpath_disk *sd_mpath_disk;
+ struct device *dev = &scsi_mpath_head->dev;
+
+ mutex_lock(&sd_mpath_disks_lock);
+ sd_mpath_disk = sd_mpath_find_disk(scsi_mpath_head);
+ if (!sd_mpath_disk) {
+ dev_warn(dev, "could not find mpath disk\n");
+ mutex_unlock(&sd_mpath_disks_lock);
+ return;
+ }
+
+ if (sd_mpath_disk->disk_count) {
+ dev_dbg(dev, "non-zero multipath disk count in removal\n");
+ sd_mpath_put_disk(sd_mpath_disk);
+ mutex_unlock(&sd_mpath_disks_lock);
+ return;
+ }
+
+ list_del_init(&sd_mpath_disk->entry);
+ mutex_unlock(&sd_mpath_disks_lock);
+
+ device_del(&sd_mpath_disk->dev);
+ mpath_remove_disk(mpath_head);
+ sd_mpath_put_disk(sd_mpath_disk);
+}
+
+/*
+ * Always calls for a failed probe, so we need to handle that some structures
+ * have not been setup.
+ */
+static void sd_mpath_fail_probe(struct scsi_disk *sdkp)
+{
+ struct sd_mpath_disk *sd_mpath_disk = sdkp->sd_mpath_disk;
+ struct scsi_mpath_device *scsi_mpath_dev;
+ struct mpath_device *mpath_device;
+ struct scsi_device *sdp = sdkp->device;
+ struct scsi_mpath_head *scsi_mpath_head;
+ struct mpath_head *mpath_head;
+ bool remove = false;
+
+ if (!sd_mpath_disk)
+ return;
+
+ scsi_mpath_dev = sdp->scsi_mpath_dev;
+ mpath_device = &scsi_mpath_dev->mpath_device;
+ scsi_mpath_head = sd_mpath_disk->scsi_mpath_head;
+ mpath_head = &scsi_mpath_head->mpath_head;
+
+ mutex_lock(&sd_mpath_disks_lock);
+ sd_mpath_disk->disk_count--;
+ if (!sd_mpath_disk->disk_count) {
+ list_del_init(&sd_mpath_disk->entry);
+ remove = true;
+ }
+ mutex_unlock(&sd_mpath_disks_lock);
+ mpath_device->disk = NULL;
+
+ if (remove) {
+ device_del(&sd_mpath_disk->dev);
+ mpath_remove_disk(mpath_head);
+ }
+ sd_mpath_put_disk(sd_mpath_disk);
+}
+
+#else /* CONFIG_SCSI_MULTIPATH */
+static int sd_mpath_probe(struct scsi_disk *sdkp)
+{
+ return 0;
+}
+static void sd_mpath_remove(struct scsi_disk *sdkp)
+{
+ return;
+}
+static void sd_mpath_fail_probe(struct scsi_disk *sdkp)
+{
+
+}
+static int sd_mpath_revalidate_head(struct scsi_disk *sdkp)
+{
+ return 0;
+}
+static int sd_mpath_add_disk(struct scsi_disk *sdkp)
+{
+ return 0;
+}
+#endif
+
/**
* sd_probe - called during driver initialization and whenever a
* new scsi device is attached to the system. It is called once
@@ -4065,7 +4414,7 @@ static int sd_probe(struct scsi_device *sdp)
struct device *dev = &sdp->sdev_gendev;
struct scsi_disk *sdkp;
struct gendisk *gd;
- int index;
+ int index = -1;
int error;
scsi_autopm_get_device(sdp);
@@ -4094,22 +4443,33 @@ static int sd_probe(struct scsi_device *sdp)
&sd_bio_compl_lkclass);
if (!gd)
goto out_free;
+ sdkp->disk = gd;
+ sdkp->device = sdp;
- index = ida_alloc(&sd_index_ida, GFP_KERNEL);
- if (index < 0) {
- sdev_printk(KERN_WARNING, sdp, "sd_probe: memory exhausted.\n");
- goto out_put;
- }
+ if (sdp->scsi_mpath_dev) {
+ error = sd_mpath_probe(sdkp);
+ if (error)
+ goto out_put;
+ } else {
+ index = ida_alloc(&sd_index_ida, GFP_KERNEL);
+ if (index < 0) {
+ sdev_printk(KERN_WARNING, sdp, "sd_probe: memory exhausted.\n");
+ goto out_put;
+ }
- error = sd_format_disk_name("sd", index, gd->disk_name, DISK_NAME_LEN);
- if (error) {
- sdev_printk(KERN_WARNING, sdp, "SCSI disk (sd) name length exceeded.\n");
- goto out_free_index;
+ error = sd_format_disk_name("sd", index, gd->disk_name,
+ DISK_NAME_LEN);
+ if (error) {
+ sdev_printk(KERN_WARNING, sdp, "SCSI disk (sd) name length exceeded.\n");
+ goto out_free_index;
+ }
+ sdkp->index = index;
+
+ gd->major = sd_major((index & 0xf0) >> 4);
+ gd->first_minor = ((index & 0xf) << 4) | (index & 0xfff00);
+ gd->minors = SD_MINORS;
}
- sdkp->device = sdp;
- sdkp->disk = gd;
- sdkp->index = index;
sdkp->max_retries = SD_MAX_RETRIES;
atomic_set(&sdkp->openers, 0);
atomic_set(&sdkp->device->ioerr_cnt, 0);
@@ -4129,6 +4489,7 @@ static int sd_probe(struct scsi_device *sdp)
error = device_add(&sdkp->disk_dev);
if (error) {
+ sd_mpath_fail_probe(sdkp);
put_device(&sdkp->disk_dev);
put_disk(gd);
goto out;
@@ -4136,10 +4497,6 @@ static int sd_probe(struct scsi_device *sdp)
dev_set_drvdata(dev, sdkp);
- gd->major = sd_major((index & 0xf0) >> 4);
- gd->first_minor = ((index & 0xf) << 4) | (index & 0xfff00);
- gd->minors = SD_MINORS;
-
gd->fops = &sd_fops;
gd->private_data = sdkp;
@@ -4158,11 +4515,18 @@ static int sd_probe(struct scsi_device *sdp)
sd_revalidate_disk(gd);
if (sdp->sector_size > PAGE_SIZE) {
if (sd_large_pool_create()) {
+ sd_mpath_fail_probe(sdkp);
error = -ENOMEM;
goto out_free_index;
}
}
+ if (sdp->scsi_mpath_dev) {
+ error = sd_mpath_revalidate_head(sdkp);
+ if (error)
+ sdev_printk(KERN_WARNING, sdp, "could not revalidate multipath limits\n");
+ }
+
if (sdp->removable) {
gd->flags |= GENHD_FL_REMOVABLE;
gd->events |= DISK_EVENT_MEDIA_CHANGE;
@@ -4177,6 +4541,7 @@ static int sd_probe(struct scsi_device *sdp)
error = device_add_disk(dev, gd, NULL);
if (error) {
+ sd_mpath_fail_probe(sdkp);
device_unregister(&sdkp->disk_dev);
put_disk(gd);
if (sdp->sector_size > PAGE_SIZE)
@@ -4184,6 +4549,19 @@ static int sd_probe(struct scsi_device *sdp)
goto out;
}
+ if (sdp->scsi_mpath_dev) {
+ error = sd_mpath_add_disk(sdkp);
+ if (error) {
+ sd_mpath_fail_probe(sdkp);
+ device_del(&sdkp->disk_dev);
+ del_gendisk(sdkp->disk);
+
+ put_disk(sdkp->disk);
+ if (sdp->sector_size > PAGE_SIZE)
+ sd_large_pool_destroy();
+ goto out;
+ }
+ }
if (sdkp->security) {
sdkp->opal_dev = init_opal_dev(sdkp, &sd_sec_submit);
if (sdkp->opal_dev)
@@ -4197,7 +4575,8 @@ static int sd_probe(struct scsi_device *sdp)
return 0;
out_free_index:
- ida_free(&sd_index_ida, index);
+ if (index >= 0)
+ ida_free(&sd_index_ida, index);
out_put:
put_disk(gd);
out_free:
@@ -4325,6 +4704,9 @@ static void sd_remove(struct scsi_device *sdp)
struct device *dev = &sdp->sdev_gendev;
struct scsi_disk *sdkp = dev_get_drvdata(dev);
+ if (sdp->scsi_mpath_dev)
+ sd_mpath_remove(sdkp);
+
scsi_autopm_get_device(sdkp->device);
device_del(&sdkp->disk_dev);
@@ -4493,6 +4875,9 @@ static struct scsi_driver sd_template = {
.resume = sd_resume,
.init_command = sd_init_command,
.uninit_command = sd_uninit_command,
+#ifdef CONFIG_SCSI_MULTIPATH
+ .mpath_remove_head = sd_mpath_remove_head,
+#endif
.done = sd_done,
.eh_action = sd_eh_action,
.eh_reset = sd_eh_reset,
--
2.43.7
^ permalink raw reply related [flat|nested] 71+ messages in thread* Re: [PATCH v5 24/27] scsi: sd: support multipath disk
2026-07-27 14:42 ` [PATCH v5 24/27] scsi: sd: support multipath disk John Garry
@ 2026-07-27 15:20 ` sashiko-bot
2026-07-27 16:33 ` John Garry
0 siblings, 1 reply; 71+ messages in thread
From: sashiko-bot @ 2026-07-27 15:20 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [High] Concurrent path removal and addition causes a TOCTOU race that overwrites and frees the shared `mpath_head->disk`.
- [High] Uncancelled `remove_work` triggers a Use-After-Free if a re-attached multipath disk fails probing.
- [Medium] Missing `put_device()` on the `sd_mpath_add_disk()` error path permanently leaks `struct scsi_disk`.
Pre-existing issues:
- [High] Direct `kfree(sdkp)` in the `sd_large_pool_create()` error path causes a use-after-free because the device is already registered in sysfs.
--
--- Patch [24]: [PATCH v5 24/27] scsi: sd: support multipath disk ---
commit 6b75fe218921318a27f170eca853c70058afe60d
Author: John Garry <john.garry@linux.dev>
Subject: scsi: sd: support multipath disk
Add support to attach a multipath disk.
We still allocate the gendisk per path, and this is required for the
per-path submission. However, those gendisks are marked as hidden. Those
disks are named sdX:Y, where X is the multipath disk index and Y is the
per-path index.
> diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
> index 36c745cbb2619..f044b2dfaa9fd 100644
> --- a/drivers/scsi/sd.c
> +++ b/drivers/scsi/sd.c
[ ... ]
> @@ -4035,13 +4138,259 @@ static const struct attribute_group sd_mpath_disk_attr_group = {
> +static int sd_mpath_probe(struct scsi_disk *sdkp)
> +{
[ ... ]
> + mpath_head->parent = &sd_mpath_disk->dev;
> + mpath_head->drv_module = THIS_MODULE;
> + mpath_head->disk_groups = sd_mpath_disk_attr_groups;
> + error = mpath_alloc_head_disk(mpath_head, &lim,
> + dev_to_node(dma_dev));
> + if (error)
> + goto out_free_disk;
> + disk = mpath_head->disk;
[ ... ]
> +static void sd_mpath_remove(struct scsi_disk *sdkp)
> +{
[ ... ]
> + mutex_lock(&sd_mpath_disks_lock);
> + sd_mpath_disk->disk_count--;
> + if (!sd_mpath_disk->disk_count && mpath_can_remove_head(mpath_head)) {
> + list_del_init(&sd_mpath_disk->entry);
> + remove = true;
> + }
> + mutex_unlock(&sd_mpath_disks_lock);
> + mpath_remove_sysfs_link(mpath_device);
> + mpath_device->disk = NULL;
> +
> + if (remove) {
> + device_del(&sd_mpath_disk->dev);
> + mpath_remove_disk(mpath_head);
[Severity: High]
Does this create a TOCTOU race that overwrites and frees the shared
mpath_head->disk?
If sd_mpath_remove() and sd_mpath_probe() run concurrently for the same
scsi_mpath_head (e.g., during hotplug/hot-unplug events):
The removal path drops the sd_mpath_disks_lock before tearing down
mpath_head->disk here. A concurrent probe can then allocate and assign a new
disk to mpath_head->disk in sd_mpath_probe().
When the removal thread resumes, it deletes and frees the new disk while the
probe thread proceeds to use it, causing a use-after-free.
> + }
> + sd_mpath_put_disk(sd_mpath_disk);
> +}
[ ... ]
> +static void sd_mpath_fail_probe(struct scsi_disk *sdkp)
> +{
[ ... ]
> + mutex_lock(&sd_mpath_disks_lock);
> + sd_mpath_disk->disk_count--;
> + if (!sd_mpath_disk->disk_count) {
> + list_del_init(&sd_mpath_disk->entry);
> + remove = true;
> + }
> + mutex_unlock(&sd_mpath_disks_lock);
> + mpath_device->disk = NULL;
> +
> + if (remove) {
> + device_del(&sd_mpath_disk->dev);
> + mpath_remove_disk(mpath_head);
> + }
> + sd_mpath_put_disk(sd_mpath_disk);
[Severity: High]
Can an uncancelled remove_work trigger a use-after-free here if a re-attached
multipath disk fails probing?
A delayed removal is scheduled when the last path is removed. A new path is
then rapidly probed, incrementing the disk count, but fails midway through
sd_probe().
This cleanup function decrements the count back to 0 and frees sd_mpath_disk
and scsi_mpath_head, but it does NOT cancel the pending remove_work (which
would normally be cancelled later in sd_mpath_add_disk()). The scheduled work
will subsequently execute on freed memory.
> +}
[ ... ]
> @@ -4158,11 +4515,18 @@ static int sd_probe(struct scsi_device *sdp)
> sd_revalidate_disk(gd);
> if (sdp->sector_size > PAGE_SIZE) {
> if (sd_large_pool_create()) {
> + sd_mpath_fail_probe(sdkp);
> error = -ENOMEM;
> goto out_free_index;
[Severity: High]
This isn't a bug introduced by this patch, but does the direct kfree(sdkp) in
the sd_large_pool_create() error path cause a use-after-free because the
device is already registered in sysfs?
After device_add(&sdkp->disk_dev) succeeds earlier in sd_probe(), the device
is exposed to sysfs. If sd_large_pool_create() fails, the code branches to
out_free_index which directly calls kfree(sdkp) without unregistering the
sysfs device, leaving dangling pointers in the sysfs tree.
> }
> }
[ ... ]
> @@ -4184,6 +4549,19 @@ static int sd_probe(struct scsi_device *sdp)
> goto out;
> }
>
> + if (sdp->scsi_mpath_dev) {
> + error = sd_mpath_add_disk(sdkp);
> + if (error) {
> + sd_mpath_fail_probe(sdkp);
> + device_del(&sdkp->disk_dev);
> + del_gendisk(sdkp->disk);
> +
> + put_disk(sdkp->disk);
> + if (sdp->sector_size > PAGE_SIZE)
> + sd_large_pool_destroy();
> + goto out;
[Severity: Medium]
Will this error path leak the struct scsi_disk?
A failure in sd_mpath_add_disk() triggers the error handling path which calls
device_del() but misses the corresponding put_device().
Because the kobject reference is never dropped, scsi_disk_release() is never
invoked, permanently leaking the memory.
> + }
> + }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727144234.3802948-1-john.g.garry@oracle.com?part=24
^ permalink raw reply [flat|nested] 71+ messages in thread* Re: [PATCH v5 24/27] scsi: sd: support multipath disk
2026-07-27 15:20 ` sashiko-bot
@ 2026-07-27 16:33 ` John Garry
0 siblings, 0 replies; 71+ messages in thread
From: John Garry @ 2026-07-27 16:33 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 27/07/2026 16:20, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
>
> New issues:
> - [High] Concurrent path removal and addition causes a TOCTOU race that overwrites and frees the shared `mpath_head->disk`.
> - [High] Uncancelled `remove_work` triggers a Use-After-Free if a re-attached multipath disk fails probing.
> - [Medium] Missing `put_device()` on the `sd_mpath_add_disk()` error path permanently leaks `struct scsi_disk`.
>
> Pre-existing issues:
> - [High] Direct `kfree(sdkp)` in the `sd_large_pool_create()` error path causes a use-after-free because the device is already registered in sysfs.
> --
>
> --- Patch [24]: [PATCH v5 24/27] scsi: sd: support multipath disk ---
> commit 6b75fe218921318a27f170eca853c70058afe60d
> Author: John Garry <john.garry@linux.dev>
> Subject: scsi: sd: support multipath disk
>
> Add support to attach a multipath disk.
>
> We still allocate the gendisk per path, and this is required for the
> per-path submission. However, those gendisks are marked as hidden. Those
> disks are named sdX:Y, where X is the multipath disk index and Y is the
> per-path index.
>
>> diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
>> index 36c745cbb2619..f044b2dfaa9fd 100644
>> --- a/drivers/scsi/sd.c
>> +++ b/drivers/scsi/sd.c
>
> [ ... ]
>
>> @@ -4035,13 +4138,259 @@ static const struct attribute_group sd_mpath_disk_attr_group = {
>> +static int sd_mpath_probe(struct scsi_disk *sdkp)
>> +{
> [ ... ]
>> + mpath_head->parent = &sd_mpath_disk->dev;
>> + mpath_head->drv_module = THIS_MODULE;
>> + mpath_head->disk_groups = sd_mpath_disk_attr_groups;
>> + error = mpath_alloc_head_disk(mpath_head, &lim,
>> + dev_to_node(dma_dev));
>> + if (error)
>> + goto out_free_disk;
>> + disk = mpath_head->disk;
>
> [ ... ]
>
>> +static void sd_mpath_remove(struct scsi_disk *sdkp)
>> +{
> [ ... ]
>> + mutex_lock(&sd_mpath_disks_lock);
>> + sd_mpath_disk->disk_count--;
>> + if (!sd_mpath_disk->disk_count && mpath_can_remove_head(mpath_head)) {
>> + list_del_init(&sd_mpath_disk->entry);
>> + remove = true;
>> + }
>> + mutex_unlock(&sd_mpath_disks_lock);
>> + mpath_remove_sysfs_link(mpath_device);
>> + mpath_device->disk = NULL;
>> +
>> + if (remove) {
>> + device_del(&sd_mpath_disk->dev);
>> + mpath_remove_disk(mpath_head);
>
> [Severity: High]
> Does this create a TOCTOU race that overwrites and frees the shared
> mpath_head->disk?
>
> If sd_mpath_remove() and sd_mpath_probe() run concurrently for the same
> scsi_mpath_head (e.g., during hotplug/hot-unplug events):
If sd_mpath_probe() finds mpath_head->mpath_disk is non-NULL in
mpath_alloc_head_disk(), it will error. Only after
sd_mpath_disk_release() -> mpath_put_disk() is mpath_head->disk set to NULL.
>
> The removal path drops the sd_mpath_disks_lock before tearing down
> mpath_head->disk here. A concurrent probe can then allocate and assign a new
> disk to mpath_head->disk in sd_mpath_probe().
>
> When the removal thread resumes, it deletes and frees the new disk while the
> probe thread proceeds to use it, causing a use-after-free.
>
>> + }
>> + sd_mpath_put_disk(sd_mpath_disk);
>> +}
>
> [ ... ]
>
>> +static void sd_mpath_fail_probe(struct scsi_disk *sdkp)
>> +{
> [ ... ]
>> + mutex_lock(&sd_mpath_disks_lock);
>> + sd_mpath_disk->disk_count--;
>> + if (!sd_mpath_disk->disk_count) {
>> + list_del_init(&sd_mpath_disk->entry);
>> + remove = true;
>> + }
>> + mutex_unlock(&sd_mpath_disks_lock);
>> + mpath_device->disk = NULL;
>> +
>> + if (remove) {
>> + device_del(&sd_mpath_disk->dev);
>> + mpath_remove_disk(mpath_head);
>> + }
>> + sd_mpath_put_disk(sd_mpath_disk);
>
> [Severity: High]
> Can an uncancelled remove_work trigger a use-after-free here if a re-attached
> multipath disk fails probing?
>
> A delayed removal is scheduled when the last path is removed. A new path is
> then rapidly probed, incrementing the disk count, but fails midway through
> sd_probe().
>
> This cleanup function decrements the count back to 0 and frees sd_mpath_disk
> and scsi_mpath_head, but it does NOT cancel the pending remove_work (which
> would normally be cancelled later in sd_mpath_add_disk()). The scheduled work
> will subsequently execute on freed memory.
I'll fix it
>
>> +}
>
> [ ... ]
>
>> @@ -4158,11 +4515,18 @@ static int sd_probe(struct scsi_device *sdp)
>> sd_revalidate_disk(gd);
>> if (sdp->sector_size > PAGE_SIZE) {
>> if (sd_large_pool_create()) {
>> + sd_mpath_fail_probe(sdkp);
>> error = -ENOMEM;
>> goto out_free_index;
>
> [Severity: High]
> This isn't a bug introduced by this patch, but does the direct kfree(sdkp) in
> the sd_large_pool_create() error path cause a use-after-free because the
> device is already registered in sysfs?
>
> After device_add(&sdkp->disk_dev) succeeds earlier in sd_probe(), the device
> is exposed to sysfs. If sd_large_pool_create() fails, the code branches to
> out_free_index which directly calls kfree(sdkp) without unregistering the
> sysfs device, leaving dangling pointers in the sysfs tree.
>
>> }
>> }
>
> [ ... ]
>
>> @@ -4184,6 +4549,19 @@ static int sd_probe(struct scsi_device *sdp)
>> goto out;
>> }
>>
>> + if (sdp->scsi_mpath_dev) {
>> + error = sd_mpath_add_disk(sdkp);
>> + if (error) {
>> + sd_mpath_fail_probe(sdkp);
>> + device_del(&sdkp->disk_dev);
>> + del_gendisk(sdkp->disk);
>> +
>> + put_disk(sdkp->disk);
>> + if (sdp->sector_size > PAGE_SIZE)
>> + sd_large_pool_destroy();
>> + goto out;
>
> [Severity: Medium]
> Will this error path leak the struct scsi_disk?
>
> A failure in sd_mpath_add_disk() triggers the error handling path which calls
> device_del() but misses the corresponding put_device().
>
> Because the kobject reference is never dropped, scsi_disk_release() is never
> invoked, permanently leaking the memory.
this all needs to be fixed in mkp- scsi treee
>
>> + }
>> + }
>
^ permalink raw reply [flat|nested] 71+ messages in thread
* [PATCH v5 25/27] scsi: sd: add mpath_dev file
2026-07-27 14:42 [PATCH v5 00/27] Native SCSI Multipath support John Garry
` (23 preceding siblings ...)
2026-07-27 14:42 ` [PATCH v5 24/27] scsi: sd: support multipath disk John Garry
@ 2026-07-27 14:42 ` John Garry
2026-07-27 15:33 ` sashiko-bot
2026-07-27 14:42 ` [PATCH v5 26/27] scsi: sd: add mpath_numa_nodes dev attribute John Garry
2026-07-27 14:42 ` [PATCH v5 27/27] scsi: sd: add mpath_queue_depth " John Garry
26 siblings, 1 reply; 71+ messages in thread
From: John Garry @ 2026-07-27 14:42 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
Add a mpath_dev file so that the multipath disk can be looked up from
per-path scsi_disk directory.
The following is an example of this usage:
$ ls -l /dev/sdc
brw-rw---- 1 root disk 8, 32 Feb 24 16:08 /dev/sdc
$ cat /sys/class/scsi_mpath_disk/scsi_mpath_disk0/sdc/multipath/sdc:0/mpath_dev
8:32
This can be used by a util like lsscsi, which would find that the gendisk
for the per-path scsi_disk is missing.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/sd.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 70 insertions(+), 1 deletion(-)
diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
index f044b2dfaa9fd..0d73b03a3daee 100644
--- a/drivers/scsi/sd.c
+++ b/drivers/scsi/sd.c
@@ -4109,6 +4109,73 @@ static int sd_mpath_add_disk(struct scsi_disk *sdkp)
return 0;
}
+static ssize_t sd_mpath_dev_show(struct device *dev,
+ struct device_attribute *attr, char *page)
+{
+ struct gendisk *gd = dev_to_disk(dev);
+ struct scsi_disk *sdkp = gd->private_data;
+ struct scsi_device *sdev = sdkp->device;
+ struct scsi_mpath_device *scsi_mpath_dev = sdev->scsi_mpath_dev;
+ struct scsi_mpath_head *scsi_mpath_head =
+ scsi_mpath_dev->scsi_mpath_head;
+ struct mpath_head *mpath_head = &scsi_mpath_head->mpath_head;
+ struct sd_mpath_disk *sd_mpath_disk;
+ struct device *disk_dev;
+ struct gendisk *disk;
+ char devt_str[13] = "";
+
+ /*
+ * This attr is for the per-path scsi_disk block device. We need to
+ * handle the scenario that the multipath disk has been deleted and
+ * the per-path scsi_disk (and its attr files) is still present.
+ */
+ mutex_lock(&sd_mpath_disks_lock);
+ sd_mpath_disk = sd_mpath_find_disk(scsi_mpath_head);
+ if (!sd_mpath_disk)
+ goto out;
+ if (!sd_mpath_disk->disk_count)
+ goto out_put_disk;
+ disk = mpath_head->disk;
+ disk_dev = disk_to_dev(disk);
+ format_dev_t(devt_str, disk_dev->devt);
+out_put_disk:
+ sd_mpath_put_disk(sd_mpath_disk);
+out:
+ mutex_unlock(&sd_mpath_disks_lock);
+ return sysfs_emit(page, "%s\n", devt_str);
+}
+static DEVICE_ATTR(mpath_dev, 0444, sd_mpath_dev_show, NULL);
+
+static struct attribute *sd_mpath_dev_attrs[] = {
+ &dev_attr_mpath_dev.attr,
+ NULL
+};
+
+static umode_t sd_mpath_dev_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr, int i)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct gendisk *gd = dev_to_disk(dev);
+ struct scsi_disk *sdkp = gd->private_data;
+ struct scsi_device *sdev = sdkp->device;
+ struct scsi_mpath_device *scsi_mpath_device = sdev->scsi_mpath_dev;
+
+ if (!scsi_mpath_device)
+ return 0;
+
+ return attr->mode;
+}
+
+static const struct attribute_group sd_mpath_dev_attr_group = {
+ .is_visible = sd_mpath_dev_attr_is_visible,
+ .attrs = sd_mpath_dev_attrs,
+};
+
+static const struct attribute_group *sd_mpath_dev_groups[] = {
+ &sd_mpath_dev_attr_group,
+ NULL
+};
+
static ssize_t sd_mpath_device_delayed_removal_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
@@ -4389,6 +4456,8 @@ static int sd_mpath_add_disk(struct scsi_disk *sdkp)
{
return 0;
}
+
+#define sd_mpath_dev_groups NULL
#endif
/**
@@ -4539,7 +4608,7 @@ static int sd_probe(struct scsi_device *sdp)
sdp->host->rpm_autosuspend_delay);
}
- error = device_add_disk(dev, gd, NULL);
+ error = device_add_disk(dev, gd, sd_mpath_dev_groups);
if (error) {
sd_mpath_fail_probe(sdkp);
device_unregister(&sdkp->disk_dev);
--
2.43.7
^ permalink raw reply related [flat|nested] 71+ messages in thread* Re: [PATCH v5 25/27] scsi: sd: add mpath_dev file
2026-07-27 14:42 ` [PATCH v5 25/27] scsi: sd: add mpath_dev file John Garry
@ 2026-07-27 15:33 ` sashiko-bot
2026-07-27 16:24 ` John Garry
0 siblings, 1 reply; 71+ messages in thread
From: sashiko-bot @ 2026-07-27 15:33 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The `mpath_dev` sysfs attribute is exposed to userspace before the multipath disk's device number is initialized, leading to reads returning `0:0`.
--
--- Patch [25]: [PATCH v5 25/27] scsi: sd: add mpath_dev file ---
commit 947fd6f1a6cab1fe0824d619f628abe9ed07adac
Author: John Garry <john.g.garry@oracle.com>
scsi: sd: add mpath_dev file
Add a mpath_dev file so that the multipath disk can be looked up from
per-path scsi_disk directory.
> diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
> index f044b2dfaa9fd..0d73b03a3daee 100644
> --- a/drivers/scsi/sd.c
> +++ b/drivers/scsi/sd.c
> @@ -4109,6 +4109,73 @@ static int sd_mpath_add_disk(struct scsi_disk *sdkp)
[ ... ]
> +static ssize_t sd_mpath_dev_show(struct device *dev,
> + struct device_attribute *attr, char *page)
> +{
[ ... ]
> + disk = mpath_head->disk;
> + disk_dev = disk_to_dev(disk);
> + format_dev_t(devt_str, disk_dev->devt);
[Severity: Medium]
Will this return 0:0 if userspace reads the attribute before the multipath
disk's device number is fully initialized?
Since device_add_disk() on the underlying path exposes these sysfs attributes
before the multipath disk is fully live, disk_dev->devt might not yet be set
by the block core.
Would it be safer to calculate this from disk->major and disk->first_minor,
which are already initialized?
> +out_put_disk:
> + sd_mpath_put_disk(sd_mpath_disk);
[ ... ]
> @@ -4539,7 +4608,7 @@ static int sd_probe(struct scsi_device *sdp)
> sdp->host->rpm_autosuspend_delay);
> }
>
> - error = device_add_disk(dev, gd, NULL);
> + error = device_add_disk(dev, gd, sd_mpath_dev_groups);
[Severity: Medium]
Does exposing the mpath_dev sysfs attribute here allow userspace tools like
udev to read it before the multipath disk is fully initialized?
If read immediately, before the multipath disk's devt is set, it may return
an invalid 0:0 device number and cause userspace misbehavior.
> if (error) {
> sd_mpath_fail_probe(sdkp);
> device_unregister(&sdkp->disk_dev);
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727144234.3802948-1-john.g.garry@oracle.com?part=25
^ permalink raw reply [flat|nested] 71+ messages in thread* Re: [PATCH v5 25/27] scsi: sd: add mpath_dev file
2026-07-27 15:33 ` sashiko-bot
@ 2026-07-27 16:24 ` John Garry
0 siblings, 0 replies; 71+ messages in thread
From: John Garry @ 2026-07-27 16:24 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 27/07/2026 16:33, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Medium] The `mpath_dev` sysfs attribute is exposed to userspace before the multipath disk's device number is initialized, leading to reads returning `0:0`.
> --
>
> --- Patch [25]: [PATCH v5 25/27] scsi: sd: add mpath_dev file ---
> commit 947fd6f1a6cab1fe0824d619f628abe9ed07adac
> Author: John Garry <john.g.garry@oracle.com>
>
> scsi: sd: add mpath_dev file
>
> Add a mpath_dev file so that the multipath disk can be looked up from
> per-path scsi_disk directory.
>
>> diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
>> index f044b2dfaa9fd..0d73b03a3daee 100644
>> --- a/drivers/scsi/sd.c
>> +++ b/drivers/scsi/sd.c
>> @@ -4109,6 +4109,73 @@ static int sd_mpath_add_disk(struct scsi_disk *sdkp)
> [ ... ]
>> +static ssize_t sd_mpath_dev_show(struct device *dev,
>> + struct device_attribute *attr, char *page)
>> +{
> [ ... ]
>> + disk = mpath_head->disk;
>> + disk_dev = disk_to_dev(disk);
>> + format_dev_t(devt_str, disk_dev->devt);
>
> [Severity: Medium]
> Will this return 0:0 if userspace reads the attribute before the multipath
> disk's device number is fully initialized?
>
I'll just error instead
> Since device_add_disk() on the underlying path exposes these sysfs attributes
> before the multipath disk is fully live, disk_dev->devt might not yet be set
> by the block core.
>
> Would it be safer to calculate this from disk->major and disk->first_minor,
> which are already initialized?
>
>> +out_put_disk:
>> + sd_mpath_put_disk(sd_mpath_disk);
> [ ... ]
>> @@ -4539,7 +4608,7 @@ static int sd_probe(struct scsi_device *sdp)
>> sdp->host->rpm_autosuspend_delay);
>> }
>>
>> - error = device_add_disk(dev, gd, NULL);
>> + error = device_add_disk(dev, gd, sd_mpath_dev_groups);
>
> [Severity: Medium]
> Does exposing the mpath_dev sysfs attribute here allow userspace tools like
> udev to read it before the multipath disk is fully initialized?
>
> If read immediately, before the multipath disk's devt is set, it may return
> an invalid 0:0 device number and cause userspace misbehavior.
>
>> if (error) {
>> sd_mpath_fail_probe(sdkp);
>> device_unregister(&sdkp->disk_dev);
>
^ permalink raw reply [flat|nested] 71+ messages in thread
* [PATCH v5 26/27] scsi: sd: add mpath_numa_nodes dev attribute
2026-07-27 14:42 [PATCH v5 00/27] Native SCSI Multipath support John Garry
` (24 preceding siblings ...)
2026-07-27 14:42 ` [PATCH v5 25/27] scsi: sd: add mpath_dev file John Garry
@ 2026-07-27 14:42 ` John Garry
2026-07-27 14:42 ` [PATCH v5 27/27] scsi: sd: add mpath_queue_depth " John Garry
26 siblings, 0 replies; 71+ messages in thread
From: John Garry @ 2026-07-27 14:42 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
From: John Garry <john.garry@linux.dev>
Add an attribute to show multipath NUMA node per-path (scsi_disk).
The following is an example of reading the file:
$ cat /sys/devices/platform/host8/session1/target8:0:0/8:0:0:0/block/sdc:0/numa_
mpath_numa_nodes
0-3
$ cat /sys/devices/platform/host9/session2/target9:0:0/9:0:0:0/block/sdc:1/numa_
mpath_numa_nodes
$
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/sd.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
index 0d73b03a3daee..6b6aef361ae22 100644
--- a/drivers/scsi/sd.c
+++ b/drivers/scsi/sd.c
@@ -4146,8 +4146,22 @@ static ssize_t sd_mpath_dev_show(struct device *dev,
}
static DEVICE_ATTR(mpath_dev, 0444, sd_mpath_dev_show, NULL);
+static ssize_t sd_mpath_numa_nodes_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gendisk *gd = dev_to_disk(dev);
+ struct scsi_disk *sdkp = gd->private_data;
+ struct scsi_device *sdev = sdkp->device;
+ struct scsi_mpath_device *scsi_mpath_dev = sdev->scsi_mpath_dev;
+ struct mpath_device *mpath_device = &scsi_mpath_dev->mpath_device;
+
+ return mpath_numa_nodes_show(mpath_device, buf);
+}
+static DEVICE_ATTR(mpath_numa_nodes, 0444, sd_mpath_numa_nodes_show, NULL);
+
static struct attribute *sd_mpath_dev_attrs[] = {
&dev_attr_mpath_dev.attr,
+ &dev_attr_mpath_numa_nodes.attr,
NULL
};
--
2.43.7
^ permalink raw reply related [flat|nested] 71+ messages in thread* [PATCH v5 27/27] scsi: sd: add mpath_queue_depth dev attribute
2026-07-27 14:42 [PATCH v5 00/27] Native SCSI Multipath support John Garry
` (25 preceding siblings ...)
2026-07-27 14:42 ` [PATCH v5 26/27] scsi: sd: add mpath_numa_nodes dev attribute John Garry
@ 2026-07-27 14:42 ` John Garry
2026-07-27 15:28 ` sashiko-bot
26 siblings, 1 reply; 71+ messages in thread
From: John Garry @ 2026-07-27 14:42 UTC (permalink / raw)
To: martin.petersen, james.bottomley, hare
Cc: jmeneghi, linux-nvme, linux-scsi, michael.christie, snitzer,
bmarzins, dm-devel, linux-kernel, nilay, john.garry, hch, kbusch,
sagi, John Garry
From: John Garry <john.garry@linux.dev>
Add a queue_depth file so that the multipath dynamic queue depth can be
looked up from per-path gendisk (scsi_disk) directory.
Signed-off-by: John Garry <john.g.garry@oracle.com>
---
drivers/scsi/sd.c | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
index 6b6aef361ae22..fbf9da686f25d 100644
--- a/drivers/scsi/sd.c
+++ b/drivers/scsi/sd.c
@@ -4159,9 +4159,28 @@ static ssize_t sd_mpath_numa_nodes_show(struct device *dev,
}
static DEVICE_ATTR(mpath_numa_nodes, 0444, sd_mpath_numa_nodes_show, NULL);
+static ssize_t sd_mpath_queue_depth_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct gendisk *gd = dev_to_disk(dev);
+ struct scsi_disk *sdkp = gd->private_data;
+ struct scsi_device *sdev = sdkp->device;
+ struct scsi_mpath_device *scsi_mpath_dev = sdev->scsi_mpath_dev;
+ struct scsi_mpath_head *scsi_mpath_head =
+ scsi_mpath_dev->scsi_mpath_head;
+ struct Scsi_Host *shost = sdev->host;
+
+ if (!mpath_qd_iopolicy(&scsi_mpath_head->iopolicy))
+ return 0;
+
+ return sysfs_emit(buf, "%d\n", atomic_read(&shost->mpath_nr_active));
+}
+static DEVICE_ATTR(mpath_queue_depth, 0444, sd_mpath_queue_depth_show, NULL);
+
static struct attribute *sd_mpath_dev_attrs[] = {
&dev_attr_mpath_dev.attr,
&dev_attr_mpath_numa_nodes.attr,
+ &dev_attr_mpath_queue_depth.attr,
NULL
};
--
2.43.7
^ permalink raw reply related [flat|nested] 71+ messages in thread* Re: [PATCH v5 27/27] scsi: sd: add mpath_queue_depth dev attribute
2026-07-27 14:42 ` [PATCH v5 27/27] scsi: sd: add mpath_queue_depth " John Garry
@ 2026-07-27 15:28 ` sashiko-bot
2026-07-27 15:29 ` John Garry
0 siblings, 1 reply; 71+ messages in thread
From: sashiko-bot @ 2026-07-27 15:28 UTC (permalink / raw)
To: John Garry; +Cc: linux-scsi
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The sysfs show function `sd_mpath_queue_depth_show` returns 0 when the iopolicy is not QD, causing reads to return EOF (0 bytes).
--
--- Patch [27]: [PATCH v5 27/27] scsi: sd: add mpath_queue_depth dev attribute ---
commit a1c72efbc115f45170fc3510049d2e925ee24a95
Author: John Garry <john.garry@linux.dev>
scsi: sd: add mpath_queue_depth dev attribute
Add a queue_depth file so that the multipath dynamic queue depth can be
looked up from per-path gendisk (scsi_disk) directory.
> diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
> index 6b6aef361ae22..fbf9da686f25d 100644
> --- a/drivers/scsi/sd.c
> +++ b/drivers/scsi/sd.c
> @@ -4159,9 +4159,28 @@ static ssize_t sd_mpath_numa_nodes_show(struct device *dev,
> }
> static DEVICE_ATTR(mpath_numa_nodes, 0444, sd_mpath_numa_nodes_show, NULL);
>
> +static ssize_t sd_mpath_queue_depth_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct gendisk *gd = dev_to_disk(dev);
> + struct scsi_disk *sdkp = gd->private_data;
> + struct scsi_device *sdev = sdkp->device;
> + struct scsi_mpath_device *scsi_mpath_dev = sdev->scsi_mpath_dev;
> + struct scsi_mpath_head *scsi_mpath_head =
> + scsi_mpath_dev->scsi_mpath_head;
> + struct Scsi_Host *shost = sdev->host;
> +
> + if (!mpath_qd_iopolicy(&scsi_mpath_head->iopolicy))
> + return 0;
[Severity: Medium]
Should this return a standard negative error code like -EOPNOTSUPP instead of 0?
Returning 0 from a sysfs show callback means zero bytes were written to the
output buffer, causing user-space reads to receive an immediate EOF and see a
completely empty file instead of parsing an integer or receiving an error.
> +
> + return sysfs_emit(buf, "%d\n", atomic_read(&shost->mpath_nr_active));
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260727144234.3802948-1-john.g.garry@oracle.com?part=27
^ permalink raw reply [flat|nested] 71+ messages in thread* Re: [PATCH v5 27/27] scsi: sd: add mpath_queue_depth dev attribute
2026-07-27 15:28 ` sashiko-bot
@ 2026-07-27 15:29 ` John Garry
0 siblings, 0 replies; 71+ messages in thread
From: John Garry @ 2026-07-27 15:29 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-scsi
On 27/07/2026 16:28, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Medium] The sysfs show function `sd_mpath_queue_depth_show` returns 0 when the iopolicy is not QD, causing reads to return EOF (0 bytes).
> --
>
> --- Patch [27]: [PATCH v5 27/27] scsi: sd: add mpath_queue_depth dev attribute ---
> commit a1c72efbc115f45170fc3510049d2e925ee24a95
> Author: John Garry <john.garry@linux.dev>
>
> scsi: sd: add mpath_queue_depth dev attribute
>
> Add a queue_depth file so that the multipath dynamic queue depth can be
> looked up from per-path gendisk (scsi_disk) directory.
>
>> diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
>> index 6b6aef361ae22..fbf9da686f25d 100644
>> --- a/drivers/scsi/sd.c
>> +++ b/drivers/scsi/sd.c
>> @@ -4159,9 +4159,28 @@ static ssize_t sd_mpath_numa_nodes_show(struct device *dev,
>> }
>> static DEVICE_ATTR(mpath_numa_nodes, 0444, sd_mpath_numa_nodes_show, NULL);
>>
>> +static ssize_t sd_mpath_queue_depth_show(struct device *dev,
>> + struct device_attribute *attr, char *buf)
>> +{
>> + struct gendisk *gd = dev_to_disk(dev);
>> + struct scsi_disk *sdkp = gd->private_data;
>> + struct scsi_device *sdev = sdkp->device;
>> + struct scsi_mpath_device *scsi_mpath_dev = sdev->scsi_mpath_dev;
>> + struct scsi_mpath_head *scsi_mpath_head =
>> + scsi_mpath_dev->scsi_mpath_head;
>> + struct Scsi_Host *shost = sdev->host;
>> +
>> + if (!mpath_qd_iopolicy(&scsi_mpath_head->iopolicy))
>> + return 0;
>
> [Severity: Medium]
> Should this return a standard negative error code like -EOPNOTSUPP instead of 0?
>
> Returning 0 from a sysfs show callback means zero bytes were written to the
> output buffer, causing user-space reads to receive an immediate EOF and see a
> completely empty file instead of parsing an integer or receiving an error.
No, this is same as what NVMe does
>
>> +
>> + return sysfs_emit(buf, "%d\n", atomic_read(&shost->mpath_nr_active));
>> +}
>
^ permalink raw reply [flat|nested] 71+ messages in thread