All of lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "M Nikhil" <nikh1092@linux.ibm.com>,
	linux-s390@vger.kernel.org, linux-scsi@vger.kernel.org
Cc: "Heiko Carstens" <hca@linux.ibm.com>,
	"Vasily Gorbik" <gor@linux.ibm.com>,
	"Alexander Gordeev" <agordeev@linux.ibm.com>,
	"Christian Borntraeger" <borntraeger@linux.ibm.com>
Subject: Re: [PATCH v14 2/2] zfcp: Use DMA streaming API to map I/O
Date: Wed, 28 Oct 2026 05:45:35 +0000	[thread overview]
Message-ID: <sashiko-outbox-187541@kernel.org> (raw)
In-Reply-To: <20261028051446.3163392-3-nikh1092@linux.ibm.com>

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Critical] scsi: zfcp: premature DMA unmap and UAF on adapter shutdown in SCSI EH
- [Critical] scsi: zfcp: use-after-free and DMA leak on host reset with ERP_FAILED
- [High] scsi: zfcp: stale QDIO SBALE DMA address on abort and status read error paths
- [High] scsi: zfcp: race between zfcp_fsf_reqid_check() and zfcp_scsi_forget_cmnds() on restored req->data
--

commit 62d32e0c324a9864b32b7819374bcb22949814d1
Author: M Nikhil <nikh1092@linux.ibm.com>

zfcp: Use DMA streaming API to map I/O

This commit converts zFCP I/O buffer mappings (QTCB, SRB, CT/ELS, and SCSI
I/O) from direct physical address translations to the DMA streaming API using
dma_map_sgtable() and dma_unmap_sgtable(). It also updates the SCSI error
handling and QDIO submission paths to manage DMA mapping lifetimes and SBAL
cleanup.

> diff --git a/drivers/s390/scsi/zfcp_fsf.c b/drivers/s390/scsi/zfcp_fsf.c
> index 12b3d2d0a2c87..05f3b226b1634 100644
> --- a/drivers/s390/scsi/zfcp_fsf.c
> +++ b/drivers/s390/scsi/zfcp_fsf.c
[ ... ]
> @@ -954,17 +1066,29 @@ int zfcp_fsf_status_read(struct zfcp_qdio *qdio)
>  	memset(sr_buf, 0, sizeof(*sr_buf));
>  	req->data = sr_buf;
>  
> -	zfcp_qdio_fill_next(qdio, &req->qdio_req, sr_buf, sizeof(*sr_buf));
> +	sg_init_one(&req->metadata_sg, sr_buf, sizeof(*sr_buf));
> +	req->metadata_sgt.sgl = &req->metadata_sg;
> +	req->metadata_sgt.orig_nents = 1;
> +
> +	retval = zfcp_fsf_req_map_sg(req, &req->metadata_sgt, DMA_FROM_DEVICE);
> +	if (unlikely(retval))
> +		goto failed_dma_map;
> +
> +	zfcp_qdio_fill_next(qdio, &req->qdio_req,
> +			    sg_dma_address(req->metadata_sgt.sgl),
> +			    sg_dma_len(req->metadata_sgt.sgl));
>  	zfcp_qdio_set_sbale_last(qdio, &req->qdio_req);
>  
>  	retval = zfcp_fsf_req_send(req);
>  	if (retval)
>  		goto failed_req_send;
>  	/* NOTE: DO NOT TOUCH req PAST THIS POINT! */
>  
>  	goto out;
>  
>  failed_req_send:
> +	zfcp_fsf_req_unmap_metadata(req);
> +failed_dma_map:
>  	req->data = NULL;
>  	mempool_free(virt_to_page(sr_buf), adapter->pool.sr_data);
>  failed_buf:
>  	zfcp_dbf_hba_fsf_uss("fssr__1", req);
>  	zfcp_fsf_req_free(req);
>  out:
>  	spin_unlock_irq(&qdio->req_q_lock);
>  	return retval;
>  }
[ ... ]
> @@ -1076,6 +1200,7 @@ struct zfcp_fsf_req *zfcp_fsf_abort_fcp_cmnd(struct scsi_cmnd *scmnd)
>  	}
>  
>  out_error_free:
> +	zfcp_fsf_req_unmap_metadata(req);
>  	zfcp_fsf_req_free(req);
>  	req = NULL;
>  out:

[Severity: High]
Can the error paths in zfcp_fsf_abort_fcp_cmnd() and zfcp_fsf_status_read()
leave stale DMA addresses in the QDIO request queue SBALs?

In zfcp_fsf_abort_fcp_cmnd(), zfcp_fsf_req_create() allocates and maps
req->qtcb and calls zfcp_qdio_req_init() with has_qtcb = true, which writes
element[0] and element[1] (with qtcb_dma_handle and qtcb_len) of
qdio->req_q[qdio->req_q_idx]:

drivers/s390/scsi/zfcp_qdio.h:zfcp_qdio_req_init() {
	...
	q_req->sbale_curr = 1;
	sbale->eflags = 0;
	sbale->sflags = SBAL_SFLAGS0_COMMAND | sbtype;

	if (unlikely(!has_qtcb))
		return;
	sbale++;
	sbale->addr = u64_to_dma64(qtcb_dma_handle);
	sbale->length = qtcb_len;
}

If the LUN is blocked (!(atomic_read(&zfcp_sdev->status) &
ZFCP_STATUS_COMMON_UNBLOCKED)), zfcp_fsf_abort_fcp_cmnd() jumps to
out_error_free and unmaps and frees req->qtcb without calling
zfcp_qdio_zero_sbals() and without advancing qdio->req_q_idx.

If the next caller to use qdio->req_q[qdio->req_q_idx] is
zfcp_fsf_status_read(), its zfcp_fsf_req_create() calls zfcp_qdio_req_init()
with has_qtcb = false, which sets q_req->sbale_curr = 1, initializes
element[0], and returns early without clearing element[1].
zfcp_fsf_status_read() then calls zfcp_qdio_fill_next(), which increments
q_req->sbale_curr to 2 and writes the status read buffer DMA address into
element[2]. The SRB SBAL is then submitted to the adapter with element[1]
still holding the unmapped and freed QTCB DMA address.

Similarly, in zfcp_fsf_status_read(), if mempool_alloc() (goto failed_buf) or
zfcp_fsf_req_map_sg() (goto failed_dma_map) fails after zfcp_fsf_req_create()
initialized element[0], zfcp_qdio_zero_sbals() is not called before freeing
req. Should both out_error_free in zfcp_fsf_abort_fcp_cmnd() and failed_buf
in zfcp_fsf_status_read() call zfcp_qdio_zero_sbals()?

> diff --git a/drivers/s390/scsi/zfcp_scsi.c b/drivers/s390/scsi/zfcp_scsi.c
> index ea4b968352f4d..99ea8c499bd1a 100644
> --- a/drivers/s390/scsi/zfcp_scsi.c
> +++ b/drivers/s390/scsi/zfcp_scsi.c
> @@ -163,6 +163,70 @@ static int zfcp_scsi_sdev_init(struct scsi_device *sdev)
[ ... ]
> +static int zfcp_scsi_abort_failed(struct zfcp_adapter *adapter,
> +				  struct scsi_cmnd *scpnt, u64 old_reqid)
> +{
> +	struct zfcp_fsf_req *old_req;
> +	unsigned long flags;
> +
> +	write_lock_irqsave(&adapter->abort_lock, flags);
> +	old_req = zfcp_reqlist_find(adapter->req_list, old_reqid);
> +	if (old_req) {
> +		old_req->data = scpnt;
> +		write_unlock_irqrestore(&adapter->abort_lock, flags);
> +		return FAILED;
> +	}
> +	write_unlock_irqrestore(&adapter->abort_lock, flags);
> +
> +	zfcp_scsi_unmap_cmnd(adapter, scpnt);
> +	return FAILED;
> +}

[Severity: High]
When zfcp_scsi_abort_failed() restores old_req->data = scpnt so that a later
LUN or target reset will unmap and detach scpnt in zfcp_scsi_forget_cmnds(),
can a concurrent completion in zfcp_fsf_reqid_check() race with
zfcp_scsi_forget_cmnds()?

On the QDIO completion path, zfcp_fsf_reqid_check() removes fsf_req from
adapter->req_list before acquiring adapter->abort_lock in
zfcp_fsf_fcp_cmnd_handler():

drivers/s390/scsi/zfcp_fsf.c:zfcp_fsf_reqid_check() {
	...
	fsf_req = zfcp_reqlist_find_rm(adapter->req_list, req_id);
	...
	zfcp_fsf_req_complete(fsf_req);
	  -> zfcp_fsf_fcp_cmnd_handler(fsf_req)
	       read_lock_irqsave(&req->adapter->abort_lock, flags);
}

If CPU1 removes old_req in zfcp_reqlist_find_rm() just before CPU2 runs
zfcp_scsi_forget_cmnds() at the end of zfcp_scsi_task_mgmt_function():

drivers/s390/scsi/zfcp_scsi.c:zfcp_scsi_forget_cmnds() {
	...
	write_lock_irqsave(&adapter->abort_lock, flags);
	zfcp_reqlist_apply_for_all(adapter->req_list, zfcp_scsi_forget_cmnd,
				   &filter);
	write_unlock_irqrestore(&adapter->abort_lock, flags);
}

CPU2 will not find old_req in adapter->req_list, so zfcp_scsi_forget_cmnd()
never unmaps scpnt or clears old_req->data. zfcp_scsi_task_mgmt_function()
then returns SUCCESS to scsi_eh, which either reuses scpnt for scsi_eh_tur()
(where scsi_eh_prep_cmnd() zeroes scpnt->sdb and sets scpnt->sc_data_direction
= DMA_NONE) or finishes and frees scpnt.

When CPU1 subsequently enters zfcp_fsf_fcp_cmnd_handler() and acquires
adapter->abort_lock, it sees req->data == scpnt still non-NULL, skips
unmapping the zeroed scpnt->sdb (leaking the DMA mapping), and calls
scsi_done(scpnt) on a command that is either mid-TUR or already finished.

> @@ -198,18 +284,57 @@ static int zfcp_scsi_eh_abort_handler(struct scsi_cmnd *scpnt)
>  		zfcp_erp_wait(adapter);
>  		ret = fc_block_scsi_eh(scpnt);
>  		if (ret) {
> +			/*
> +			 * fc_block_scsi_eh returned FAST_IO_FAIL because
> +			 * fast_io_fail_tmo fired.  This may originate from a
> +			 * per-port reopen (fc_timeout_fail_rport_io ->
> +			 * zfcp_scsi_terminate_rport_io ->
> +			 * zfcp_erp_port_forced_reopen) that closes the port
> +			 * asynchronously without shutting QDIO down or
> +			 * calling zfcp_fsf_req_dismiss_all().
> +			 *
> +			 * Only unmap scpnt's DMA buffers if QDIO is already
> +			 * down.  That is the exact precondition enforced by
> +			 * zfcp_fsf_req_dismiss_all() (BUG_ON QDIOUP) and
> +			 * guarantees the hardware is no longer using them.
> +			 *
> +			 * If QDIO is still up the hardware may still be
> +			 * writing into the buffers; we must not unmap now.
> +			 * Return FAILED so scsi_eh escalates instead of
> +			 * finishing scpnt.  zfcp_scsi_abort_failed() hands
> +			 * the unmap back to the request's completion or
> +			 * dismissal, or to the TMF path (forget_cmnds), so
> +			 * the mapping is not leaked.
> +			 */
>  			zfcp_dbf_scsi_abort("abrt_bl", scpnt, NULL);
> -			return ret;
> +			if (atomic_read(&adapter->status) &
> +			    ZFCP_STATUS_ADAPTER_QDIOUP)
> +				return zfcp_scsi_abort_failed(adapter, scpnt,
> +							      old_reqid);
> +			retval = ret; /* FAST_IO_FAIL, QDIO is down */
> +			goto out_unmap;
>  		}
>  		if (!(atomic_read(&adapter->status) &
>  		      ZFCP_STATUS_COMMON_RUNNING)) {
> +			/*
> +			 * Adapter is gone; ERP shut down QDIO and dismissed
> +			 * all requests before clearing RUNNING.  Safe to
> +			 * unmap now.
> +			 */
>  			zfcp_dbf_scsi_abort("abrt_ru", scpnt, NULL);
> -			return SUCCESS;
> +			retval = SUCCESS;
> +			goto out_unmap;
>  		}
>  	}
[ ... ]
> @@ -305,6 +479,21 @@ static int zfcp_scsi_task_mgmt_function(struct scsi_device *sdev, u8 tm_flags)
>  		ret = fc_block_rport(rport);
>  		if (ret) {
>  			zfcp_dbf_scsi_devreset("fiof", sdev, tm_flags, NULL);
> +			/*
> +			 * QDIO still up: the FCP channel may still be using
> +			 * the buffers of outstanding commands.  Do not let
> +			 * scsi_eh finish them; escalate to host reset instead.
> +			 */
> +			if (atomic_read(&adapter->status) &
> +			    ZFCP_STATUS_ADAPTER_QDIOUP)
> +				return FAILED;
> +			/*
> +			 * QDIO down: the hardware is done with the buffers.
> +			 * Detach and unmap the commands so that a later
> +			 * completion or dismissal of their requests cannot
> +			 * touch a finished scpnt.
> +			 */
> +			zfcp_scsi_forget_cmnds(zfcp_sdev, tm_flags);
>  			return ret;
>  		}
>  
>  		if (!(atomic_read(&adapter->status) &
>  		      ZFCP_STATUS_COMMON_RUNNING)) {
>  			zfcp_dbf_scsi_devreset("nres", sdev, tm_flags, NULL);
>  			return SUCCESS;
>  		}

[Severity: Critical]
Is it safe to assume that QDIO has been shut down and all requests have been
dismissed whenever ZFCP_STATUS_COMMON_RUNNING is clear?

Looking at zfcp_erp_adapter_shutdown() -> zfcp_erp_adapter_reopen() in
drivers/s390/scsi/zfcp_erp.c:

drivers/s390/scsi/zfcp_erp.c:zfcp_erp_adapter_shutdown() {
	int flags = ZFCP_STATUS_COMMON_RUNNING | ZFCP_STATUS_COMMON_ERP_FAILED;
	zfcp_erp_adapter_reopen(adapter, clear | flags, dbftag);
	  -> zfcp_erp_adapter_block(adapter, clear | flags);
	  -> zfcp_scsi_schedule_rports_block(adapter);
	  -> write_lock_irqsave(&adapter->erp_lock, ...);
	  -> zfcp_erp_action_enqueue(..., ZFCP_ERP_ACTION_REOPEN_ADAPTER, ...);
}

zfcp_erp_adapter_block() clears ZFCP_STATUS_COMMON_RUNNING synchronously
before the ERP action is enqueued, while zfcp_qdio_close() and
zfcp_fsf_req_dismiss_all() run asynchronously later on the ERP thread in
zfcp_erp_adapter_strategy_close().

This appears to cause a few problems in the error handlers:

1. In zfcp_scsi_eh_abort_handler() (at "abrt_ru"), if
   zfcp_erp_adapter_shutdown() runs concurrently (for example, between
   zfcp_erp_adapter_block() and zfcp_erp_action_enqueue() so zfcp_erp_wait()
   returns immediately, or right after zfcp_erp_wait() returns),
   ZFCP_STATUS_COMMON_RUNNING is already clear while QDIO is still active.
   Jumping to out_unmap will unmap scpnt's DMA buffers and return SUCCESS
   while the FCP channel may still be performing DMA.

2. If zfcp_scsi_eh_abort_handler() fails and zfcp_scsi_abort_failed()
   restores old_req->data = scpnt, scsi_eh escalates to
   zfcp_scsi_task_mgmt_function(). At "nres", observing
   !(atomic_read(&adapter->status) & ZFCP_STATUS_COMMON_RUNNING) returns
   SUCCESS without checking ZFCP_STATUS_ADAPTER_QDIOUP and without calling
   zfcp_scsi_forget_cmnds(). scsi_eh then finishes scpnt while old_req is
   still on adapter->req_list with old_req->data == scpnt. When
   zfcp_fsf_req_dismiss_all() or normal completion later runs
   zfcp_fsf_fcp_cmnd_handler(), it will unmap and call scsi_done() on the
   already-finished scpnt.

3. In zfcp_qdio_close() in drivers/s390/scsi/zfcp_qdio.c,
   ZFCP_STATUS_ADAPTER_QDIOUP is cleared before calling tasklet_disable(),
   qdio_stop_irq(), and qdio_shutdown(). Can the lockless checks of
   ZFCP_STATUS_ADAPTER_QDIOUP in "abrt_bl" and "fiof" observe QDIOUP cleared
   and unmap scpnt before qdio_shutdown() has actually stopped hardware DMA?

[Severity: Critical]
In addition, when zfcp_scsi_eh_abort_handler() (at "abrt_bl" or "abrt_ar")
and zfcp_scsi_task_mgmt_function() (at "fiof" or "reqf") return FAILED
because ZFCP_STATUS_ADAPTER_QDIOUP is still set while old_req->data == scpnt
remains on adapter->req_list, what happens if scsi_eh escalates to
zfcp_scsi_eh_host_reset_handler() after a link-down event?

When a local link-down event occurs (zfcp_fsf_status_read_link_down() ->
zfcp_fsf_link_down_info_eval(), or FSF_PROT_LINK_DOWN in
zfcp_fsf_protstatus_eval()), zfcp_erp_set_adapter_status(adapter,
ZFCP_STATUS_COMMON_ERP_FAILED) sets ZFCP_STATUS_COMMON_ERP_FAILED on
adapter->status while leaving ZFCP_STATUS_ADAPTER_QDIOUP set.

When fast_io_fail_tmo fires on the blocked rport and both abort and
LUN/target reset return FAILED because QDIOUP is set, scsi_eh calls
zfcp_scsi_eh_host_reset_handler():

drivers/s390/scsi/zfcp_scsi.c:zfcp_scsi_eh_host_reset_handler() {
	...
	zfcp_erp_adapter_reopen(adapter, 0, "schrh_1");
	zfcp_erp_wait(adapter);
	fc_ret = fc_block_scsi_eh(scpnt);
	if (fc_ret)
		ret = fc_ret;
	...
	return ret;
}

Because zfcp_erp_adapter_reopen() is called with clear = 0,
zfcp_erp_action_enqueue() -> zfcp_erp_handle_failed() sees
ZFCP_STATUS_COMMON_ERP_FAILED still set on adapter->status and suppresses
ZFCP_ERP_ACTION_REOPEN_ADAPTER (need = 0). As a result, zfcp_qdio_close() and
zfcp_fsf_req_dismiss_all() are never executed, QDIO stays up, and old_req
remains in adapter->req_list with old_req->data == scpnt.

zfcp_scsi_eh_host_reset_handler() then returns FAST_IO_FAIL (or SUCCESS) to
scsi_eh_host_reset(), which finishes and frees scpnt. When the link later
comes back up (FSF_STATUS_READ_LINK_UP) or the adapter is shut down,
zfcp_fsf_req_dismiss_all() will invoke zfcp_fsf_fcp_cmnd_handler(old_req)
with req->data pointing to the freed scpnt, causing a use-after-free in
zfcp_fsf_req_unmap_sg() and scsi_done().

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261028051446.3163392-1-nikh1092@linux.ibm.com?part=2

      reply	other threads:[~2026-10-28  5:45 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-28  5:14 [PATCH v14 0/2] zfcp: Use DMA streaming API to map I/O M Nikhil
2026-10-28  5:14 ` [PATCH v14 1/2] zfcp: Replace sg-list with sg_table in zfcp_fsf_ct_els structure M Nikhil
2026-10-28  5:20   ` sashiko-bot
2026-10-28  5:14 ` [PATCH v14 2/2] zfcp: Use DMA streaming API to map I/O M Nikhil
2026-10-28  5:45   ` sashiko-bot [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=sashiko-outbox-187541@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=agordeev@linux.ibm.com \
    --cc=borntraeger@linux.ibm.com \
    --cc=gor@linux.ibm.com \
    --cc=hca@linux.ibm.com \
    --cc=linux-s390@vger.kernel.org \
    --cc=linux-scsi@vger.kernel.org \
    --cc=nikh1092@linux.ibm.com \
    --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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.