From: Jeff Layton <jlayton@kernel.org>
To: sashiko-reviews@lists.linux.dev
Cc: linux-scsi@vger.kernel.org
Subject: Re: [PATCH v3 2/2] scsi: mpt3sas: Time out delayed not-responding device blocks
Date: Mon, 09 Nov 2026 09:46:08 -0500 [thread overview]
Message-ID: <eb243163db8e99c0c2db72eceb5d1fd9990e7d7d.camel@kernel.org> (raw)
In-Reply-To: <sashiko-outbox-202261@kernel.org>
On Fri, 2026-11-06 at 20:57 +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 3 potential
> issues to consider:
> - [High] scsi: mpt3sas: use-after-free of sdev->hostdata in unblock
> helpers
> - [Medium] scsi: mpt3sas: race setting SDEV_OFFLINE outside block_lock
> on unblock failure
> - [Medium] scsi: mpt3sas: _scsih_block_io_all_device() leaves sdevs
> blocked without deadline
> --
>
> commit ba9687a8025a542963a3bc4d91c8e8b094a21e74
> Author: Jeff Layton <jlayton@kernel.org>
>
> scsi: mpt3sas: Time out delayed not-responding device blocks
>
> This commit adds a delayed work item and per-device deadline to release
> delayed not-responding sdev blocks after the firmware's device missing
> delay plus 30 seconds, preventing scans from deadlocking on sas_host->lock.
>
> > diff --git a/drivers/scsi/mpt3sas/mpt3sas_scsih.c b/drivers/scsi/mpt3sas/mpt3sas_scsih.c
> > index 60c9cf679a8a7..acdeece20140f 100644
> > --- a/drivers/scsi/mpt3sas/mpt3sas_scsih.c
> > +++ b/drivers/scsi/mpt3sas/mpt3sas_scsih.c
>
> [ ... ]
>
> > @@ -3958,7 +3989,20 @@ _scsih_ublock_io_all_device(struct MPT3SAS_ADAPTER *ioc, u8 no_turs)
> > int count = 0;
> > u8 tr_method = 0;
> > u8 tr_timeout = 30;
> > + unsigned long flags;
> >
> > + /* polling one device can outlast another's deadline */
> > + if (!no_turs && issue_scsi_cmd_to_bringup_drive) {
> > + shost_for_each_device(sdev, ioc->shost) {
> > + spin_lock_irqsave(&ioc->block_lock, flags);
> > + sas_device_priv_data = sdev->hostdata;
> > + if (sas_device_priv_data &&
> > + sas_device_priv_data->sas_target &&
> > + !sas_device_priv_data->sas_target->deleted)
> > + sas_device_priv_data->block_deadline = 0;
> > + spin_unlock_irqrestore(&ioc->block_lock, flags);
> > + }
> > + }
> >
> > shost_for_each_device(sdev, ioc->shost) {
> > sas_device_priv_data = sdev->hostdata;
> > @@ -3975,10 +4019,18 @@ _scsih_ublock_io_all_device(struct MPT3SAS_ADAPTER *ioc, u8 no_turs)
> > if ((no_turs) || (!issue_scsi_cmd_to_bringup_drive)) {
> > sdev_printk(KERN_WARNING, sdev, "device_unblocked handle(0x%04x)\n",
> > sas_device_priv_data->sas_target->handle);
> > - _scsih_internal_device_unblock(sdev, sas_device_priv_data);
> > + spin_lock_irqsave(&ioc->block_lock, flags);
> > + /* dnr_unblock_work may have released it */
> > + if (sas_device_priv_data->block)
> > + _scsih_internal_device_unblock(sdev,
> > + sas_device_priv_data);
> > + spin_unlock_irqrestore(&ioc->block_lock, flags);
> > continue;
> > }
> >
> > + /* may have been blocked since the first pass */
> > + if (!_scsih_claim_block(ioc, sdev))
> > + continue;
> > do {
> > pcie_device = mpt3sas_get_pdev_by_handle(ioc, sas_target->handle);
> > if (pcie_device && (!ioc->tm_custom_handling) &&
> > @@ -3997,11 +4049,13 @@ _scsih_ublock_io_all_device(struct MPT3SAS_ADAPTER *ioc, u8 no_turs)
> > } while ((rc == DEVICE_RETRY || rc == DEVICE_START_UNIT ||
> > rc == DEVICE_STOP_UNIT || rc == DEVICE_RETRY_UA)
> > && count++ < command_retry_count);
> > + spin_lock_irqsave(&ioc->block_lock, flags);
> > sas_device_priv_data->block = 0;
> > if (rc != DEVICE_READY)
> > sas_device_priv_data->deleted = 1;
> >
> > _scsih_internal_device_unblock(sdev, sas_device_priv_data);
> > + spin_unlock_irqrestore(&ioc->block_lock, flags);
> >
> > if (rc != DEVICE_READY) {
> > sdev_printk(KERN_WARNING, sdev, "%s: device_offlined,\n"
>
> [Severity: High]
> Can _scsih_ublock_io_all_device() and _scsih_ublock_io_device_wait() cause a
> use-after-free on sas_device_priv_data and sas_target?
>
> shost_for_each_device() only holds a reference on sdev, not sdev->hostdata.
> When _scsih_dnr_unblock_work() unblocks an sdev that was blocked during a
> concurrent sysfs scan and its probe fails in scsi_probe_and_add_lun(), or
> when a device is removed via sysfs, __scsi_remove_device() calls
> scsih_sdev_destroy(), which clears sdev->hostdata under ioc->block_lock and
> frees sas_device_priv_data:
>
> CPU0 (firmware_event_thread) CPU1 (scan / device removal)
> _scsih_ublock_io_all_device()
> sas_device_priv_data =
> sdev->hostdata;
> _scsih_claim_block(ioc, sdev)
> _scsih_wait_for_device_...()
> __scsi_remove_device(sdev)
> scsih_sdev_destroy(sdev)
> spin_lock_irqsave(&ioc->block_lock)
> sdev->hostdata = NULL;
> spin_unlock_irqrestore(&ioc->block_lock)
> kfree(sas_device_priv_data);
> spin_lock_irqsave(&ioc->block_lock)
> sas_device_priv_data->block = 0; <-- use-after-free
>
> Both functions also read sdev->hostdata and dereference
> sas_device_priv_data->sas_target at the top of their second
> shost_for_each_device() loops without holding ioc->block_lock, and in the
> no_turs branch _scsih_ublock_io_all_device() takes ioc->block_lock and
> accesses the cached sas_device_priv_data pointer without re-checking
> sdev->hostdata. Re-fetching sdev->hostdata (and sas_target) under
> ioc->block_lock before each access and after the polling loop would avoid
> touching freed hostdata.
>
Real issue. Still, worth fixing while we're in here.
> > @@ -4060,24 +4114,30 @@ _scsih_ublock_io_device_wait(struct MPT3SAS_ADAPTER *ioc, u64 sas_address,
> > u8 tr_timeout = 30;
> > u8 tr_method = 0;
> > int count = 0;
> > + unsigned long flags;
> >
> > /* moving devices from SDEV_OFFLINE to SDEV_BLOCK */
> > shost_for_each_device(sdev, ioc->shost) {
> > + spin_lock_irqsave(&ioc->block_lock, flags);
> > sas_device_priv_data = sdev->hostdata;
> > if (!sas_device_priv_data)
> > - continue;
> > + goto next;
> > sas_target = sas_device_priv_data->sas_target;
> > if (!sas_target)
> > - continue;
> > + goto next;
> > if (sas_target->sas_address != sas_address ||
> > sas_target->port != port)
> > - continue;
> > + goto next;
> > + /* polling one device can outlast another's deadline */
> > + sas_device_priv_data->block_deadline = 0;
> > if (sdev->sdev_state == SDEV_OFFLINE) {
> > sas_device_priv_data->block = 1;
> > sas_device_priv_data->deleted = 0;
> > scsi_device_set_state(sdev, SDEV_RUNNING);
> > scsi_internal_device_block_nowait(sdev);
> > }
> > +next:
> > + spin_unlock_irqrestore(&ioc->block_lock, flags);
> > }
> >
> > /* moving devices from SDEV_BLOCK to SDEV_RUNNING state */
> > @@ -4091,7 +4151,7 @@ _scsih_ublock_io_device_wait(struct MPT3SAS_ADAPTER *ioc, u64 sas_address,
> > if (sas_target->sas_address != sas_address ||
> > sas_target->port != port)
> > continue;
> > - if (!sas_device_priv_data->block)
> > + if (!_scsih_claim_block(ioc, sdev))
> > continue;
> >
> > do {
> > @@ -4129,10 +4189,12 @@ _scsih_ublock_io_device_wait(struct MPT3SAS_ADAPTER *ioc, u64 sas_address,
> > rc == DEVICE_STOP_UNIT || rc == DEVICE_RETRY_UA)
> > && count++ <= command_retry_count);
> >
> > + spin_lock_irqsave(&ioc->block_lock, flags);
> > sas_device_priv_data->block = 0;
> > if (rc != DEVICE_READY)
> > sas_device_priv_data->deleted = 1;
> > scsi_internal_device_unblock_nowait(sdev, SDEV_RUNNING);
> > + spin_unlock_irqrestore(&ioc->block_lock, flags);
> >
> > if (rc != DEVICE_READY) {
> > sdev_printk(KERN_WARNING, sdev,
>
> [Severity: Medium]
> When rc != DEVICE_READY in _scsih_ublock_io_device_wait() (and in
> _scsih_ublock_io_all_device()), can dropping ioc->block_lock before calling
> scsi_device_set_state(sdev, SDEV_OFFLINE) race with _scsih_block_io_device()?
>
> CPU0 (firmware_event_thread) CPU1 (<hardirq>)
> _scsih_ublock_io_device_wait()
> spin_lock_irqsave(&ioc->block_lock)
> sas_device_priv_data->block = 0;
> scsi_internal_device_unblock_nowait(
> sdev, SDEV_RUNNING)
> spin_unlock_irqrestore(&ioc->block_lock)
> _scsih_block_io_device()
> spin_lock_irqsave(&ioc->block_lock)
> /* sdev_state is SDEV_RUNNING */
> _scsih_internal_device_block(sdev, ...)
> sas_device_priv_data->block_deadline = ...;
> spin_unlock_irqrestore(&ioc->block_lock)
> ...
> scsi_device_set_state(sdev,
> SDEV_OFFLINE) <-- overwrites SDEV_BLOCK; queue stays stopped
>
> Because sdev->sdev_state is SDEV_RUNNING while ioc->block_lock is dropped,
> _scsih_block_io_device() does not see SDEV_OFFLINE and stops the request
> queue, sets block = 1, and arms block_deadline. CPU0 then sets
> sdev->sdev_state to SDEV_OFFLINE with the queue still stopped (so
> scsi_queue_rq() cannot fail queued I/O with BLK_STS_IOERR) and an active
> block_deadline that will later transition the offline device back to
> SDEV_RUNNING without a TUR check. Setting SDEV_OFFLINE before releasing
> ioc->block_lock would close this window.
>
> [ ... ]
>
There's a real race window here, but I think the claimed result is
wrong. Still, this is simple to fix.
> > @@ -4213,21 +4279,119 @@ _scsih_block_io_all_device(struct MPT3SAS_ADAPTER *ioc)
> > {
> > struct MPT3SAS_DEVICE *sas_device_priv_data;
> > struct scsi_device *sdev;
> > + unsigned long flags;
> >
> > shost_for_each_device(sdev, ioc->shost) {
> > + spin_lock_irqsave(&ioc->block_lock, flags);
> > sas_device_priv_data = sdev->hostdata;
> > if (!sas_device_priv_data)
> > - continue;
> > - if (sas_device_priv_data->block)
> > - continue;
> > + goto next;
> > + if (sas_device_priv_data->block) {
> > + /* the caller unblocks it; dnr_unblock_work must not */
> > + sas_device_priv_data->block_deadline = 0;
> > + goto next;
> > + }
> > if (sas_device_priv_data->ignore_delay_remove) {
> > sdev_printk(KERN_INFO, sdev,
> > "%s skip device_block for SES handle(0x%04x)\n",
> > __func__, sas_device_priv_data->sas_target->handle);
> > - continue;
> > + goto next;
> > }
> > _scsih_internal_device_block(sdev, sas_device_priv_data);
> > +next:
> > + spin_unlock_irqrestore(&ioc->block_lock, flags);
> > }
> > +}
>
> [Severity: Medium]
> Can _scsih_block_io_all_device() leave devices blocked with
> block_deadline == 0 so that neither the caller nor _scsih_dnr_unblock_work()
> unblocks them before device removal?
>
> _scsih_block_io_all_device() blocks every sdev (or clears its
> block_deadline) without checking sas_target->deleted, whereas its paired
> _scsih_ublock_io_all_device() skips targets with sas_target->deleted set.
> In addition, its caller _scsih_sas_broadcast_primitive_event() skips
> _scsih_ublock_io_all_device() altogether when host recovery starts:
>
> drivers/scsi/mpt3sas/mpt3sas_scsih.c:_scsih_sas_broadcast_primitive_event() {
> ...
> ioc->broadcast_aen_busy = 0;
> if (!ioc->shost_recovery)
> _scsih_ublock_io_all_device(ioc, 1);
> mutex_unlock(&ioc->tm_cmds.mutex);
> }
>
> After host reset, _scsih_remove_unresponding_devices() runs on
> firmware_event_thread and removes unresponding devices (taking
> sas_host->lock and shost->scan_mutex) before it reaches
> _scsih_ublock_io_all_device(ioc, 0). If a concurrent sysfs scan holds
> sas_host->lock and waits on I/O to one of these sdevs whose block_deadline
> was cleared to 0, _scsih_dnr_unblock_work() will not unblock it and the scan
> deadlock recurs.
Real issue, but this predates the set AFAICT. Still it's also worth
fixing.
There's also another issue here, I think where repeated DNRs can extend
the block indefinitely and still cause a hang. We should just bound the
timeout instead.
I'll send a v4.
--
Jeff Layton <jlayton@kernel.org>
prev parent reply other threads:[~2026-11-09 14:46 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-06 20:29 [PATCH v3 0/2] scsi: mpt3sas: Fix delayed not-responding sdev block handling Jeff Layton
2026-11-06 20:29 ` [PATCH v3 1/2] scsi: mpt3sas: Serialize interrupt-time sdev block with hostdata teardown Jeff Layton
2026-11-06 20:29 ` [PATCH v3 2/2] scsi: mpt3sas: Time out delayed not-responding device blocks Jeff Layton
2026-11-06 20:57 ` sashiko-bot
2026-11-09 14:46 ` Jeff Layton [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=eb243163db8e99c0c2db72eceb5d1fd9990e7d7d.camel@kernel.org \
--to=jlayton@kernel.org \
--cc=linux-scsi@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox