Linux s390 Architecture development
 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 v13 2/2] zfcp: Use DMA streaming API to map I/O
Date: Mon, 26 Oct 2026 11:38:31 +0000	[thread overview]
Message-ID: <sashiko-outbox-185112@kernel.org> (raw)
In-Reply-To: <20261026101301.3543163-3-nikh1092@linux.ibm.com>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Critical] scsi: zfcp: premature command finish and UAF on FAST_IO_FAIL in TMF reset
- [High] scsi: zfcp: DMA mapping leak and double map after zfcp_scsi_eh_abort_handler()
--

commit 14eb6f8d5a95f776e71a82cf2adfa01567fe0309
Author: M Nikhil <nikh1092@linux.ibm.com>

zfcp: Use DMA streaming API to map I/O

This commit converts zFCP I/O buffer mappings for QTCB, SRB, CT/ELS, and
SCSI I/O buffers from direct physical address translations to the DMA
streaming API using sg_table, dma_map_sgtable(), and dma_unmap_sgtable().

> diff --git a/drivers/s390/scsi/zfcp_scsi.c b/drivers/s390/scsi/zfcp_scsi.c
> index ea4b968352f4d..bda27153c3268 100644
> --- a/drivers/s390/scsi/zfcp_scsi.c
> +++ b/drivers/s390/scsi/zfcp_scsi.c
> @@ -179,11 +179,31 @@ static int zfcp_scsi_eh_abort_handler(struct scsi_cmnd *scpnt)
>  	write_lock_irqsave(&adapter->abort_lock, flags);
>  
>  	old_req = zfcp_reqlist_find(adapter->req_list, old_reqid);
> -	if (!old_req) {
> +	if (!old_req || !old_req->data) {
>  		write_unlock_irqrestore(&adapter->abort_lock, flags);
>  		zfcp_dbf_scsi_abort("abrt_or", scpnt, NULL);
>  		return FAILED; /* completion could be in progress */
>  	}
> +
> +	/*
> +	 * Suppress the normal completion handler's unmap by clearing
> +	 * req->data now, under the abort_lock write-side.  The actual
> +	 * DMA unmap is deferred until the hardware has stopped using
> +	 * the buffers (after wait_for_completion or after ERP has
> +	 * quiesced the adapter), so that we never call dma_unmap_sgtable
> +	 * while the FCP channel may still be writing into the pages.
> +	 *
> +	 * old_req->data is left NULL permanently and never restored.
> +	 * Restoring it while returning non-SUCCESS to scsi_eh would be
> +	 * unsafe: scsi_eh is free to call scsi_finish_command() on scpnt
> +	 * as soon as we return non-SUCCESS, freeing scpnt's scatter-gather
> +	 * tables while old_req (with data pointing at the freed scpnt) is
> +	 * still live.  Any subsequent completion or ERP dismissal of
> +	 * old_req would then dereference a freed scpnt in
> +	 * zfcp_fsf_fcp_cmnd_handler() -- use-after-free and double
> +	 * scsi_done().  Leaving data = NULL means zfcp_fsf_fcp_cmnd_handler
> +	 * returns early harmlessly if old_req completes after we return.
> +	 */
>  	old_req->data = NULL;
>  
>  	/* don't access old fsf_req after releasing the abort_lock */
> @@ -198,16 +218,55 @@ 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.  old_req->data stays NULL, so
> +			 * the eventual dismiss will skip the unmap (accepted
> +			 * DMA mapping leak for this path).
> +			 */
>  			zfcp_dbf_scsi_abort("abrt_bl", scpnt, NULL);
> -			return ret;
> +			if (atomic_read(&adapter->status) &
> +			    ZFCP_STATUS_ADAPTER_QDIOUP) {
> +				retval = FAILED;
> +				return retval;
> +			}
> +			retval = ret; /* FAST_IO_FAIL, QDIO is down */
> +			goto out_unmap;
>  		}

[Severity: Critical]
When fc_block_scsi_eh() returns FAST_IO_FAIL while
ZFCP_STATUS_ADAPTER_QDIOUP is set, zfcp_scsi_eh_abort_handler() returns
FAILED here so that scsi_eh escalates instead of finishing scpnt while the
FCP channel may still be performing DMA.

However, when scmd_eh_abort_handler() escalates to
zfcp_scsi_eh_device_reset_handler() or zfcp_scsi_eh_target_reset_handler(),
both call zfcp_scsi_task_mgmt_function():

drivers/s390/scsi/zfcp_scsi.c:zfcp_scsi_task_mgmt_function() {
	while (retry--) {
		fsf_req = zfcp_fsf_fcp_task_mgmt(sdev, tm_flags);
		if (fsf_req)
			break;

		zfcp_dbf_scsi_devreset("wait", sdev, tm_flags, NULL);
		zfcp_erp_wait(adapter);
		ret = fc_block_rport(rport);
		if (ret) {
			zfcp_dbf_scsi_devreset("fiof", sdev, tm_flags, NULL);
			return ret;
		}
}

If zfcp_fsf_fcp_task_mgmt() also returns NULL because the LUN is still
blocked, fc_block_rport() immediately returns FAST_IO_FAIL, and
zfcp_scsi_task_mgmt_function() returns FAST_IO_FAIL directly to
scsi_eh_bus_device_reset() or scsi_eh_target_reset() without checking
ZFCP_STATUS_ADAPTER_QDIOUP and without calling zfcp_scsi_forget_cmnds().

In scsi_eh_bus_device_reset() and scsi_eh_target_reset(), a FAST_IO_FAIL
return is treated the same as SUCCESS, moving all commands for that LUN or
target to done_q where scsi_eh_flush_done_q() calls scsi_finish_command()
and scsi_free_sgtables().

Won't this finish scpnt and free its scatter-gather tables and I/O pages
while QDIO is still up and the hardware may still be performing DMA?
In addition, for any other command on that LUN or target that reached
LUN/target reset without going through zfcp_scsi_eh_abort_handler() (for
example, if scsi_host_eh_past_deadline() skipped abort or
SCSI_EH_ABORT_SCHEDULED was already set on a retry), old_req->data remains
non-NULL, so a subsequent completion or ERP dismissal in
zfcp_fsf_fcp_cmnd_handler() will dereference the freed scpnt and its
scpnt->sdb.table.

>  		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;
>  		}
>  	}
>  	if (!abrt_req) {
> +		/*
> +		 * abrt_ar: all retries of zfcp_fsf_abort_fcp_cmnd() failed.
> +		 * The hardware may still be running the original I/O; do not
> +		 * unmap.  old_req->data stays NULL so any eventual completion
> +		 * or ERP dismissal of old_req is a harmless no-op.
> +		 */
>  		zfcp_dbf_scsi_abort("abrt_ar", scpnt, NULL);
>  		return FAILED;
>  	}
> @@ -219,11 +278,49 @@ static int zfcp_scsi_eh_abort_handler(struct scsi_cmnd *scpnt)
>  	else if (abrt_req->status & ZFCP_STATUS_FSFREQ_ABORTNOTNEEDED)
>  		dbf_tag = "abrt_nn";
>  	else {
> -		dbf_tag = "abrt_fa";
> -		retval = FAILED;
> +		/*
> +		 * abrt_fa: the abort FSF request itself was dismissed by ERP
> +		 * (neither ABORTSUCCEEDED nor ABORTNOTNEEDED is set).  We do
> +		 * not know whether the hardware has stopped the original I/O;
> +		 * do not unmap.  old_req->data stays NULL.
> +		 */
> +		zfcp_dbf_scsi_abort("abrt_fa", scpnt, abrt_req);
> +		zfcp_fsf_req_free(abrt_req);
> +		return FAILED;
>  	}

[ ... ]

> @@ -237,13 +334,17 @@ static void zfcp_scsi_forget_cmnd(struct zfcp_fsf_req *old_req, void *data)
>  {
>  	struct zfcp_scsi_req_filter *filter =
>  		(struct zfcp_scsi_req_filter *)data;
> +	struct scsi_cmnd *scpnt;
>  
> -	/* already aborted - prevent side-effects - or not a SCSI command */
> +	/* already aborted - prevent side-effects - or not a SCSI I/O command */
>  	if (old_req->data == NULL ||
>  	    zfcp_fsf_req_is_status_read_buffer(old_req) ||
> -	    old_req->qtcb->header.fsf_command != FSF_QTCB_FCP_CMND)
> +	    old_req->qtcb->header.fsf_command != FSF_QTCB_FCP_CMND ||
> +	    old_req->handler == zfcp_fsf_fcp_task_mgmt_handler)
>  		return;
>  
> +	scpnt = old_req->data;
> +
>  	/* (tmf_scope == FCP_TMF_TGT_RESET || tmf_scope == FCP_TMF_LUN_RESET) */
>  	if (old_req->qtcb->header.port_handle != filter->port_handle)
>  		return;
> @@ -253,6 +354,17 @@ static void zfcp_scsi_forget_cmnd(struct zfcp_fsf_req *old_req, void *data)
>  		return;
>  
>  	zfcp_dbf_scsi_nullcmnd((struct scsi_cmnd *)old_req->data, old_req);
> +
> +	if ((scsi_get_prot_op(scpnt) != SCSI_PROT_NORMAL) &&
> +	    scsi_prot_sg_count(scpnt))
> +		zfcp_fsf_req_unmap_sg(&old_req->adapter->ccw_device->dev,
> +				      &scpnt->prot_sdb->table,
> +				      scpnt->sc_data_direction);
> +
> +	zfcp_fsf_req_unmap_sg(&old_req->adapter->ccw_device->dev,
> +			      &scpnt->sdb.table,
> +			      scpnt->sc_data_direction);
> +
>  	old_req->data = NULL;
>  }

[Severity: High]
When zfcp_scsi_eh_abort_handler() clears old_req->data = NULL on entry and
then returns FAILED on the abrt_ar, abrt_fa, or abrt_bl (with QDIOUP) paths
without unmapping scpnt->sdb.table and scpnt->prot_sdb->table, how are those
DMA mappings cleaned up when SCSI error recovery escalates?

In scmd_eh_abort_handler(), a FAILED return from eh_abort_handler does not
finish scpnt; instead it calls scsi_eh_scmd_add(scpnt) to escalate recovery
to LUN reset, target reset, and host reset in scsi_unjam_host().

Because old_req->data was already set to NULL:
- If LUN reset or target reset succeeds in zfcp_scsi_task_mgmt_function(),
  zfcp_scsi_forget_cmnds() -> zfcp_scsi_forget_cmnd() sees
  old_req->data == NULL and returns early without unmapping scpnt.
- If host reset (zfcp_scsi_eh_host_reset_handler()) or adapter ERP runs
  zfcp_fsf_req_dismiss_all(), or if old_req completes before reset,
  zfcp_fsf_fcp_cmnd_handler() sees req->data == NULL and returns early
  without unmapping scpnt (and on the abrt_fa path where ERP dismissed
  abrt_req, old_req was already dismissed and freed with data == NULL).

When scsi_eh_flush_done_q() then runs after the reset succeeds, won't it
either requeue scpnt via scsi_queue_insert() and call dma_map_sgtable() a
second time on the still-mapped scpnt->sdb.table in zfcp_fsf_fcp_cmnd(), or
call scsi_finish_command() and leak the DMA mappings and SWIOTLB bounce
buffer slots?

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

  reply	other threads:[~2026-10-26 11:38 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-26 10:12 [PATCH v13 0/2] zfcp: Use DMA streaming API to map I/O M Nikhil
2026-10-26 10:13 ` [PATCH v13 1/2] zfcp: Replace sg-list with sg_table in zfcp_fsf_ct_els structure M Nikhil
2026-10-26 10:44   ` sashiko-bot
2026-10-26 10:13 ` [PATCH v13 2/2] zfcp: Use DMA streaming API to map I/O M Nikhil
2026-10-26 11:38   ` sashiko-bot [this message]
  -- strict thread matches above, loose matches on Subject: below --
2026-10-26  4:16 [PATCH v13 0/2] " M Nikhil
2026-10-26  4:16 ` [PATCH v13 2/2] " M Nikhil
2026-10-23 11:40 [PATCH v13 0/2] " M Nikhil
2026-10-23 11:40 ` [PATCH v13 2/2] " M Nikhil
2026-10-23 12:41   ` sashiko-bot
2026-10-22 17:08 [PATCH v13 0/2] " M Nikhil
2026-10-22 17:08 ` [PATCH v13 2/2] " M Nikhil
2026-10-22 18:15   ` sashiko-bot
2026-10-21  8:33 [PATCH v13 0/2] " M Nikhil
2026-10-21  8:33 ` [PATCH v13 2/2] " M Nikhil
2026-10-21  9:04   ` sashiko-bot

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-185112@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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox