[PATCH v9 17/29] scsi-multipath: clear path when device is blocked

sashiko-bot at kernel.org sashiko-bot at kernel.org
Sun Nov 8 13:06:28 PST 2026


Thank you for your contribution! Sashiko AI review found 2 potential
issues to consider:
- [High] scsi: multipath: UAF and NULL deref in scsi_device_block() on
  removal race
- [High] scsi: multipath: NULL deref and UAF in scsi_device_block()
  during LUN scan
--

commit 6f23ec119648804e57ce4572c175483a5109ea7a
Author: John Garry <john.garry at linux.dev>

scsi-multipath: clear path when device is blocked

This commit introduces scsi_mpath_dev_clear_path() and calls it from
scsi_device_block() to clear the active multipath device path when a SCSI
device is blocked.

> diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c
> index 36bc995e4e250..1950034f580f4 100644
> --- a/drivers/scsi/scsi_lib.c
> +++ b/drivers/scsi/scsi_lib.c
[ ... ]
> @@ -3037,6 +3038,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);

[Severity: High]
Can calling scsi_mpath_dev_clear_path() after dropping sdev->state_mutex
race with __scsi_remove_device() and cause a use-after-free or NULL
pointer dereference on the multipath head?

scsi_block_targets() iterates target devices via starget_for_each_device()
without holding shost->scan_mutex.  Once scsi_device_block() unlocks
sdev->state_mutex, __scsi_remove_device() can transition sdev->sdev_state
from SDEV_BLOCK to SDEV_DEL and tear down the multipath head while
starget_for_each_device() still holds a reference to sdev:

CPU0                                  CPU1
scsi_device_block()
  mutex_lock(&sdev->state_mutex)
  __scsi_internal_device_block_nowait()
  mutex_unlock(&sdev->state_mutex)
                                      __scsi_remove_device()
                                        mutex_lock(&sdev->state_mutex)
                                        sdev->sdev_state = SDEV_DEL
                                        mutex_unlock(&sdev->state_mutex)
                                        ...
                                        scsi_mpath_remove_device()
                                          scsi_mpath_remove_head()
                                            scsi_mpath_put_head()
                                            scsi_mpath_dev->scsi_mpath_head = NULL
  scsi_mpath_dev_clear_path()
    mpath_clear_current_path()        <-- use-after-free on mpath_head
    mpath_synchronize(mpath_head)     <-- NULL deref or use-after-free

Because scsi_mpath_remove_head() drops the reference on scsi_mpath_head
and sets scsi_mpath_dev->scsi_mpath_head to NULL while leaving
mpath_device.mpath_head dangling, mpath_clear_current_path() reads the
freed mpath_device->mpath_head and mpath_synchronize() dereferences a NULL
or freed mpath_head.

> diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c
> index 8e229357e62c5..67e9010d54782 100644
> --- a/drivers/scsi/scsi_multipath.c
> +++ b/drivers/scsi/scsi_multipath.c
> @@ -240,6 +240,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;

[Severity: High]
What prevents scsi_device_block() from calling scsi_mpath_dev_clear_path()
while scsi_mpath_dev_alloc() is still initializing or freeing
sdev->scsi_mpath_dev during LUN scanning?

sdev is already linked onto starget->devices and set to SDEV_RUNNING in
scsi_add_lun() before scsi_mpath_dev_alloc() runs.  Inside
scsi_mpath_dev_alloc(), scsi_multipath_sdev_init() publishes
sdev->scsi_mpath_dev before scsi_mpath_head and mpath_device.mpath_head
are populated, and the error path in scsi_multipath_sdev_uninit() frees
sdev->scsi_mpath_dev before clearing the pointer:

CPU0                                  CPU1
scsi_add_lun()
  scsi_mpath_dev_alloc()
    scsi_multipath_sdev_init()
      sdev->scsi_mpath_dev = ...
                                      scsi_block_targets()
                                        ...
                                        scsi_device_block()
                                          scsi_mpath_dev_clear_path()
                                            mpath_clear_current_path()
                                              mpath_head->current_path[node]
                                              <-- NULL pointer dereference
    ...
    sdev->scsi_mpath_dev->mpath_device.mpath_head = ...

If scsi_mpath_dev_alloc() fails (for example when a LUN has no VPD page 83
device ID) and calls scsi_multipath_sdev_uninit(), a concurrent
scsi_device_block() also risks a use-after-free on sdev->scsi_mpath_dev.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261108204733.557995-1-john.garry@linux.dev?part=17



More information about the Linux-nvme mailing list