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 v13 2/2] zfcp: Use DMA streaming API to map I/O
Date: Fri, 23 Oct 2026 12:41:37 +0000	[thread overview]
Message-ID: <sashiko-outbox-182788@kernel.org> (raw)
In-Reply-To: <20261023114046.4137557-3-nikh1092@linux.ibm.com>

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Critical] scsi: zfcp: scsi_cmnd UAF on abort failure in zfcp_scsi_eh_abort_handler()
- [High] scsi: zfcp: DMA mapping leak in zfcp_scsi_eh_abort_handler()
- [Medium] zfcp: 31-bit coherent_dma_mask overwritten in zfcp_set_dma_mask_for_adapter()
--

commit ee42d1a728b67714b2c6be28df7f409c25bc9e1f
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().

> diff --git a/drivers/s390/scsi/zfcp_aux.c b/drivers/s390/scsi/zfcp_aux.c
> index fea573e00f9df..20c905c68e1e6 100644
> --- a/drivers/s390/scsi/zfcp_aux.c
> +++ b/drivers/s390/scsi/zfcp_aux.c
> @@ -329,6 +329,19 @@ static void zfcp_destroy_adapter_work_queue(struct zfcp_adapter *adapter)
>  
>  }
>  
> +static int zfcp_set_dma_mask_for_adapter(struct zfcp_adapter *adapter)
> +{
> +	int ret;
> +
> +	ret = dma_set_mask_and_coherent(&adapter->ccw_device->dev,
> +					DMA_BIT_MASK(64));
> +	if (ret)
> +		dev_err(&adapter->ccw_device->dev,
> +			"Setting the DMA mask failed for the FCP device.\n");
> +
> +	return ret;
> +}

[Severity: Medium]
Should zfcp_set_dma_mask_for_adapter() call dma_set_mask() instead of
dma_set_mask_and_coherent()?

In drivers/s390/cio/css.c:css_alloc_subchannel() and
drivers/s390/cio/device.c:io_subchannel_allocate_dev(), the s390 CIO core
initializes cdev->dev.dma_mask to DMA_BIT_MASK(64) for streaming DMA while
explicitly keeping cdev->dev.coherent_dma_mask at DMA_BIT_MASK(31):

drivers/s390/cio/css.c:css_alloc_subchannel() {
	/*
	 * The physical addresses for some of the dma structures that can
	 * belong to a subchannel need to fit 31 bit width (e.g. ccw).
	 */
	ret = dma_set_coherent_mask(&sch->dev, DMA_BIT_MASK(31));
	if (ret)
		goto err;
	/*
	 * But we don't have such restrictions imposed on the stuff that
	 * is handled by the streaming API.
	 */
	ret = dma_set_mask(&sch->dev, DMA_BIT_MASK(64));
}

Calling dma_set_mask_and_coherent() overwrites cdev->dev.coherent_dma_mask
with a 64-bit mask even though zFCP only uses streaming DMA. If an online
zFCP device is detached under z/VM or KVM, zfcp_ccw_notify() returns
NOTIFY_OK on CIO_GONE and io_subchannel_sch_event() moves the struct
ccw_device to the pseudo-subchannel orphanage via ccw_device_move_to_orph().
When another CCW device (such as virtio-ccw) is later attached at the same
device number and rebound via ccw_device_recog_done() ->
ccw_device_do_unbind_bind(), coherent allocations via
ccw_device_dma_zalloc() -> __cio_gp_dma_zalloc() will use the clobbered
64-bit coherent_dma_mask, allowing dma_alloc_coherent() to return memory
above 2 GiB and truncate dma_addr to 31-bit dma32_t.

[ ... ]

> diff --git a/drivers/s390/scsi/zfcp_scsi.c b/drivers/s390/scsi/zfcp_scsi.c
> index ea4b968352f4d..32bfb7e4891ee 100644
> --- a/drivers/s390/scsi/zfcp_scsi.c
> +++ b/drivers/s390/scsi/zfcp_scsi.c
> @@ -179,11 +179,20 @@ 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 = NULL;
>  
>  	/* don't access old fsf_req after releasing the abort_lock */
> @@ -198,18 +207,34 @@ 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: the
> +			 * fast_io_fail_tmo fired.  ERP has already called
> +			 * zfcp_fsf_req_dismiss_all() (which requires QDIO
> +			 * to be down first), so the hardware is no longer
> +			 * using the buffers.  Pass FAST_IO_FAIL back to
> +			 * scsi_eh, but unmap first.
> +			 */
>  			zfcp_dbf_scsi_abort("abrt_bl", scpnt, NULL);
> -			return ret;
> +			retval = ret;
> +			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;
>  		}
>  	}
>  	if (!abrt_req) {
>  		zfcp_dbf_scsi_abort("abrt_ar", scpnt, NULL);
> -		return FAILED;
> +		retval = FAILED;
> +		goto out_unmap;
>  	}
>  
>  	wait_for_completion(&abrt_req->completion);
> @@ -224,6 +249,39 @@ static int zfcp_scsi_eh_abort_handler(struct scsi_cmnd *scpnt)
>  	}
>  	zfcp_dbf_scsi_abort(dbf_tag, scpnt, abrt_req);
>  	zfcp_fsf_req_free(abrt_req);
> +
> +out_unmap:
> +	if (retval == SUCCESS) {
> +		/*
> +		 * The hardware has stopped using the SCSI command's DMA buffers
> +		 * either the adapter confirmed the abort (abrt_ok / abrt_nn), or
> +		 * ERP has shutdown QDIO and cleared RUNNING (abrt_ru).
> +		 */
> +		if ((scsi_get_prot_op(scpnt) != SCSI_PROT_NORMAL) &&
> +		    scsi_prot_sg_count(scpnt))
> +			zfcp_fsf_req_unmap_sg(&adapter->ccw_device->dev,
> +					      &scpnt->prot_sdb->table,
> +					      scpnt->sc_data_direction);
> +
> +		zfcp_fsf_req_unmap_sg(&adapter->ccw_device->dev,
> +				      &scpnt->sdb.table,
> +				      scpnt->sc_data_direction);
> +	} else {
> +		/*
> +		 * Abort failed and adapter was not quiesced (abrt_ar, abrt_fa,
> +		 * or abrt_bl). Do not unmap DMA buffers while hardware may still
> +		 * be accessing them. Restore old_req->data so that subsequent
> +		 * SCSI EH reset escalation (zfcp_scsi_forget_cmnd) or ERP
> +		 * dismissal (zfcp_fsf_req_dismiss_all) unmaps the buffers
> +		 * once the hardware has actually stopped.
> +		 */
> +		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 retval;
>  }

[Severity: High]
Does out_unmap in zfcp_scsi_eh_abort_handler() leak the DMA mappings for
scpnt->sdb.table and scpnt->prot_sdb->table when retval != SUCCESS and
old_req was already removed from adapter->req_list?

After zfcp_scsi_eh_abort_handler() sets old_req->data = NULL and drops
adapter->abort_lock, old_req can complete or be dismissed by ERP via
zfcp_qdio_close() -> zfcp_fsf_req_dismiss_all() -> zfcp_fsf_req_complete()
while the abort handler is in its retry loop or waiting on
abrt_req->completion. Because req->data is NULL, zfcp_fsf_fcp_cmnd_handler()
returns early without unmapping scpnt's scatterlists, and old_req is removed
from adapter->req_list and freed:

drivers/s390/scsi/zfcp_fsf.c:zfcp_fsf_fcp_cmnd_handler() {
	scpnt = req->data;
	if (unlikely(!scpnt)) {
		read_unlock_irqrestore(&req->adapter->abort_lock, flags);
		return;
	}
}

If zfcp_scsi_eh_abort_handler() then reaches out_unmap with retval != SUCCESS
-- for example, when fc_block_scsi_eh() returns FAST_IO_FAIL ("abrt_bl",
where the comment notes it should unmap first, but retval is FAST_IO_FAIL so
the retval == SUCCESS branch is skipped), when all retries of
zfcp_fsf_abort_fcp_cmnd() return NULL ("abrt_ar"), or when abrt_req itself is
dismissed by ERP ("abrt_fa") -- execution enters the else branch. There,
zfcp_reqlist_find(adapter->req_list, old_reqid) returns NULL because old_req
is already gone, so neither out_unmap nor any later zfcp_scsi_forget_cmnd()
or zfcp_fsf_req_dismiss_all() will ever unmap scpnt->sdb.table or
scpnt->prot_sdb->table.

[Severity: Critical]
In the case where old_req is still in adapter->req_list when out_unmap enters
the else branch, can restoring old_req->data = scpnt cause a use-after-free
on scpnt in zfcp_fsf_fcp_cmnd_handler()?

Prior to this change, zfcp_scsi_eh_abort_handler() permanently cleared
old_req->data = NULL under adapter->abort_lock so that
zfcp_fsf_fcp_cmnd_handler() could never touch scpnt or call scsi_done(scpnt)
once abort handling started.

With old_req->data = scpnt restored on retval != SUCCESS:

1. If FAST_IO_FAIL is returned while old_req is still in adapter->req_list --
   either from fc_block_scsi_eh(scpnt) in zfcp_scsi_eh_abort_handler() (for
   example, when a single remote port's fast_io_fail_tmo fires via
   fc_timeout_fail_rport_io() -> zfcp_scsi_terminate_rport_io() ->
   zfcp_erp_port_forced_reopen(), which closes the port asynchronously
   without shutting down QDIO or calling zfcp_fsf_req_dismiss_all()), or
   during subsequent EH escalation in zfcp_scsi_task_mgmt_function() when
   fc_block_rport(rport) returns FAST_IO_FAIL before
   zfcp_scsi_forget_cmnds() is reached:

   drivers/s390/scsi/zfcp_scsi.c:zfcp_scsi_task_mgmt_function() {
		ret = fc_block_rport(rport);
		if (ret) {
			zfcp_dbf_scsi_devreset("fiof", sdev, tm_flags, NULL);
			return ret;
		}
   }

   -- SCSI EH immediately finishes scpnt via scsi_eh_finish_cmd() ->
   scsi_finish_command() -> scsi_free_sgtables(). That frees scpnt's
   scatter-gather tables while they are still DMA-mapped and releases scpnt
   for reuse while old_req->data still points to scpnt. When old_req later
   completes or is dismissed, zfcp_fsf_fcp_cmnd_handler() will dereference
   the freed or reused scpnt, unmap its freed sdb.table and prot_sdb->table,
   and call scsi_done(scpnt).

2. Even when FAST_IO_FAIL is not returned, restoring old_req->data = scpnt
   allows any late completion or ERP dismissal of old_req during SCSI EH
   escalation to execute zfcp_fsf_fcp_cmnd_handler(), modifying scpnt and
   calling scsi_done(scpnt) concurrently with SCSI EH.

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

  reply	other threads:[~2026-10-23 12:41 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-23 11:40 [PATCH v13 0/2] zfcp: Use DMA streaming API to map I/O M Nikhil
2026-10-23 11:40 ` [PATCH v13 1/2] zfcp: Replace sg-list with sg_table in zfcp_fsf_ct_els structure M Nikhil
2026-10-23 11:47   ` sashiko-bot
2026-10-23 11:40 ` [PATCH v13 2/2] zfcp: Use DMA streaming API to map I/O M Nikhil
2026-10-23 12:41   ` sashiko-bot [this message]
  -- strict thread matches above, loose matches on Subject: below --
2026-10-26 10:12 [PATCH v13 0/2] " M Nikhil
2026-10-26 10:13 ` [PATCH v13 2/2] " M Nikhil
2026-10-26 11:38   ` sashiko-bot
2026-10-26  4:16 [PATCH v13 0/2] " M Nikhil
2026-10-26  4:16 ` [PATCH v13 2/2] " M Nikhil
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-182788@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.