Linux SCSI subsystem development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Karan Tilak Kumar" <kartilak@cisco.com>
Cc: linux-scsi@vger.kernel.org
Subject: Re: [PATCH v6 06/13] scsi: fnic: Add the NVMe/FC transport path
Date: Fri, 24 Jul 2026 18:20:47 +0000	[thread overview]
Message-ID: <20260724182048.0C5771F000E9@smtp.kernel.org> (raw)
In-Reply-To: <20260724174811.5118-7-kartilak@cisco.com>

Thank you for your contribution! Sashiko AI review found 11 potential issue(s) to consider:

New issues:
- [High] The NVMe FC transport template `nvfnic_port` omits the mandatory `.ls_req` callback, causing `nvme_fc_register_localport()` to unconditionally fail and preventing NVMe initialization.
- [High] NVMe I/O completion and ITMF handlers are completely disconnected from the CQ polling loop, causing all NVMe I/Os to hang indefinitely.
- [High] The `ls_req_timer` in `struct nvfnic_ls_req` is armed without ever being initialized.
- [High] Concurrent execution of `nvfnic_delete_tport` results in stack corruption, double free, and use-after-free of the `tport` structure.
- [High] A TOCTOU race between abort WQ enqueue and fast completions causes IO requests to permanently hang.
- [High] `tport->ls_req_list` is not initialized with `INIT_LIST_HEAD()`, causing a kernel panic when iterated.
- [High] `ls_req` is left on `tport->ls_req_list` during abort, leading to Use-After-Free and list corruption when `tport` is freed.
- [High] Timeout during local port unregistration leads to a use-after-free of the `nv_tmpl` and `fnic` structures.
- [Medium] `fnic_queue_wq_copy_desc_itmf` leaks uninitialized descriptor memory to firmware when `lun` is NULL.

Pre-existing issues:
- [High] Dropping a spinlock inside a `list_for_each_entry_safe` loop invalidates the safe iteration, leading to list corruption and kernel panic.
- [High] The driver incorrectly uses a shared struct member (`fnic->lock_flags`) for saving CPU-local IRQ state during `spin_lock_irqsave`.
--

--- Patch [6]: [PATCH v6 06/13] scsi: fnic: Add the NVMe/FC transport path ---
commit 320b83a04180e41753d3bc332a151c194da1ab86
Author: Karan Tilak Kumar <kartilak@cisco.com>
Subject: scsi: fnic: Add the NVMe/FC transport path

This commit introduces the NVMe over FC transport path for the fnic driver.
It adds logic for local-port and remote-port registration, I/O submission,
tag management, completion handling, and cleanup operations.

Closes: https://lore.kernel.org/oe-kbuild-all/202605280430.wTYAqI3A-lkp@intel.com/
Closes: https://lore.kernel.org/oe-kbuild-all/202605280619.pmobiDWp-lkp@intel.com/
Closes: https://lore.kernel.org/oe-kbuild-all/202605280519.Jd4fmgAZ-lkp@intel.com/

> diff --git a/drivers/scsi/fnic/fnic_fcs.c b/drivers/scsi/fnic/fnic_fcs.c
> --- a/drivers/scsi/fnic/fnic_fcs.c
> +++ b/drivers/scsi/fnic/fnic_fcs.c
[ ... ]
> @@ -1020,6 +1019,8 @@ void fnic_delete_fcp_tports(struct fnic *fnic)
>  		fnic_del_tport_timer_sync(fnic, tport);
>  		if (IS_FNIC_FCP_INITIATOR(fnic))
>  			fnic_fdls_remove_tport(&fnic->iport, tport, flags);
> +		else if (IS_FNIC_NVME_INITIATOR(fnic))
> +			nvfnic_delete_tport(&fnic->iport, tport, flags);

[Severity: High]
This is a pre-existing issue, but does dropping the spinlock inside the
list_for_each_entry_safe() loop corrupt the list?

In fnic_delete_fcp_tports(), the loop holds fnic_lock and calls
nvfnic_delete_tport(), which explicitly drops fnic_lock. If a concurrent
task frees the cached next pointer while the lock is dropped, the loop will
dereference freed memory when it resumes.

[ ... ]
> diff --git a/drivers/scsi/fnic/fnic_nvme.c b/drivers/scsi/fnic/fnic_nvme.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/scsi/fnic/fnic_nvme.c
[ ... ]
> +void nvfnic_fcpio_nvme_fast_cmpl_handler(struct fnic *fnic,
> +				       struct fcpio_fw_req *desc)
> +{
[ ... ]
> +	if (io_req->cmd_state == FNIC_IOREQ_ABTS_PENDING) {
> +		/*
> +		 * set the FNIC_IO_DONE so that this doesn't get
> +		 * flagged as 'out of order' if it was not aborted
> +		 */
> +		io_req->cmd_flags |= FNIC_IO_DONE;
> +		io_req->cmd_flags |= FNIC_IO_ABTS_PENDING;

[Severity: High]
Will this sequence cause an I/O request to hang permanently?

In nvfnic_fcpio_nvme_fast_cmpl_handler(), if the IO state is
FNIC_IOREQ_ABTS_PENDING, the completion is discarded. If nvfnic_fcpio_abort()
then fails to enqueue the abort (for example, if the WQ is full), it restores
the state back to FNIC_IOREQ_CMD_PENDING and returns. Since the hardware
completion was already dropped, the I/O would remain pending forever.

[ ... ]
> +void nvfnic_local_port_delete(struct nvme_fc_local_port *lport)
> +{
> +	struct fnic_iport_s *iport = (struct fnic_iport_s *) lport->private;
> +	struct fnic *fnic = iport->fnic;
> +	unsigned long flags = 0;
> +
> +	FNIC_NVME_DBG(KERN_INFO, fnic, "lport delete 0x%x\n",
> +		      iport->fcid);
> +
> +	spin_lock_irqsave(&fnic->fnic_lock, flags);

[Severity: High]
Could a timeout during unload lead to a use-after-free here?

If the wait_for_completion_timeout() in nvfnic_nvme_unload() expires, the
driver frees iport->nv_tmpl and eventually the fnic structure. However, if
the NVMe midlayer later invokes the localport_delete callback
(nvfnic_local_port_delete()), it will dereference the freed iport and fnic
memory and attempt to acquire the freed fnic_lock.

[ ... ]
> +void
> +nvfnic_cleanup_tport_io(struct fnic *fnic, struct fnic_tport_s *tport)
> +{
> +	unsigned long flags;
> +	struct nvfnic_ls_req *nvfnic_ls_req, *next;
> +	struct nvmefc_ls_req *lsreq;
> +	uint16_t oxid;
> +	LIST_HEAD(done_reqs);
> +
> +	spin_lock_irqsave(&fnic->fnic_lock, flags);
> +	list_for_each_entry_safe(nvfnic_ls_req, next,
> +				 &(tport->ls_req_list), list) {
> +		lsreq = nvfnic_ls_req->ls_req;
> +		if (!lsreq || (lsreq->private == NULL)) {
> +			FNIC_NVME_DBG(KERN_INFO, fnic,
> +				"fnic_cleanup_tport_io lsreq NULL\n");
> +			continue;
> +		}
> +		if (nvfnic_ls_req->state == FNIC_LS_REQ_CMD_ABTS_STARTED) {
> +			FNIC_NVME_DBG(KERN_INFO, fnic,
> +				"fnic_cleanup_tport_io lsreq abort started\n");
> +			continue;
> +		}

[Severity: High]
Does skipping list_del_init() for aborting requests lead to a use-after-free
of the tport?

If the request state is FNIC_LS_REQ_CMD_ABTS_STARTED, the continue statement
skips removing the request from tport->ls_req_list. Once the tport is freed,
the subsequent abort completion or timeout callback will call list_del() on
nvfnic_ls_req->list, corrupting the freed tport memory.

[ ... ]
> +void
> +nvfnic_terminate_tport_ls_reqs(struct fnic *fnic, struct fnic_tport_s *tport)
> +{
> +	struct nvmefc_ls_req *lsreq;
> +	struct nvfnic_ls_req *nvfnic_ls_req, *next;
> +	int count = 0;
> +	uint16_t oxid;
> +	LIST_HEAD(done_reqs);
> +
> +	spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags);

[Severity: High]
This is a pre-existing issue, but does using the shared fnic->lock_flags
corrupt CPU-local IRQ state?

Calling spin_lock_irqsave(&fnic->fnic_lock, fnic->lock_flags) overwrites the
shared structure member. If multiple CPUs contend for this lock, they will
overwrite each other's saved IRQ states, leading to an incorrect state
restoration upon unlock.

> +	list_for_each_entry_safe(nvfnic_ls_req, next,
> +		&(tport->ls_req_list), list) {

[Severity: High]
Will iterating this list trigger a kernel panic?

The tport structure is allocated with kzalloc(), but tport->ls_req_list is
never initialized with INIT_LIST_HEAD(). When list_for_each_entry_safe()
executes, it will dereference a NULL next pointer.

[ ... ]
> +struct
> +nvme_fc_port_template nvfnic_port = {
> +	.localport_delete = nvfnic_local_port_delete,
> +	.remoteport_delete = nvfnic_remote_port_delete,
> +	.create_queue = nvfnic_create_queue,
> +	.delete_queue = NULL,
> +	.ls_req = NULL,

[Severity: High]
Does the omitted ls_req callback cause the local port registration to always
fail?

The nvfnic_port template explicitly sets .ls_req to NULL. When
nvfnic_add_lport() calls nvme_fc_register_localport(), the NVMe transport
validates the presence of the ls_req callback and will unconditionally return
-EINVAL if it is missing. This prevents NVMe initialization entirely.

[ ... ]
> +void nvfnic_nvme_unload(struct fnic *fnic)
> +{
> +	int ret = 0;
> +	struct fnic_iport_s *iport = &fnic->iport;
> +	unsigned long flags;
> +	unsigned int time_wait =  FNIC_NVME_LPORT_REMOVE_WAIT;
> +	unsigned int time_remain;
> +	DECLARE_COMPLETION_ONSTACK(nvme_lport_unreg_done);
[ ... ]
> +			time_remain = wait_for_completion_timeout(
> +				fnic->nvme_lport_unreg_done,
> +				msecs_to_jiffies(time_wait));
> +			if (!time_remain) {
> +				FNIC_NVME_DBG(KERN_ERR, fnic,
> +					      "Local port removal timed out\n");
> +				WARN_ON(1);
> +			}
> +			iport->flags &= ~FNIC_LPORT_NVME_REGISTERED;
> +			kfree(iport->nv_tmpl);

[Severity: High]
Could a timeout here lead to a use-after-free?

If the wait_for_completion_timeout() in nvfnic_nvme_unload() expires, the
driver frees iport->nv_tmpl and eventually the fnic structure. However, if
the NVMe midlayer later invokes the localport_delete callback
(nvfnic_local_port_delete()), it will dereference the freed iport and fnic
memory and attempt to acquire the freed fnic_lock.

[ ... ]
> +void nvfnic_ls_req_abort(struct nvme_fc_local_port *lport,
> +		   struct nvme_fc_remote_port *rport,
> +		   struct nvmefc_ls_req *lsreq)
> +{
[ ... ]
> +	/* Mark the state and flags */
> +	nvfnic_ls_req->state = FNIC_LS_REQ_CMD_ABTS_PENDING;
> +	timeout = FNIC_LS_REQ_TMO_MSECS(lsreq->timeout);
> +	mod_timer(&nvfnic_ls_req->ls_req_timer,
> +		  round_jiffies(jiffies + msecs_to_jiffies(timeout)));

[Severity: High]
Could arming this timer trigger a kernel panic?

In nvfnic_ls_req_abort(), mod_timer() is called on
nvfnic_ls_req->ls_req_timer. However, timer_setup() is never called for this
timer during the request's lifecycle. Calling mod_timer() on an uninitialized
timer structure leads to list corruption.

[ ... ]
> +void nvfnic_delete_tport(struct fnic_iport_s *iport,
> +						struct fnic_tport_s *tport,
> +						unsigned long flags)
> +{
[ ... ]
> +	if (!(tport->flags & FNIC_FDLS_NVME_REGISTERED)) {
> +		FNIC_NVME_DBG(KERN_ERR, fnic,
> +			"0x%x: tport: 0x%x not registered. Freeing\n",
> +		    iport->fcid, tport->fcid);
> +		list_del(&tport->links);
> +		kfree(tport);
> +		return;
> +	}
> +
> +	tport->tport_del_done = &tm_done;

[Severity: High]
Is it possible for concurrent teardown paths to corrupt the stack and
double-free the tport?

The FNIC_FDLS_NVME_REGISTERED flag is checked but never cleared. If called
concurrently, both threads will assign their local stack address of tm_done
to tport->tport_del_done. If the second thread fails in
nvme_fc_unregister_remoteport(), it will free the tport and return, while
the first thread is still waiting on the completion using the freed tport.

> +
> +	tport->flags |= FNIC_FDLS_TPORT_DELETED;
> +	spin_unlock_irqrestore(&fnic->fnic_lock, flags);
> +	ret = nvme_fc_unregister_remoteport(tport->nv_rport);

[Severity: High]
This is a pre-existing issue, but does dropping the spinlock here corrupt the
list iteration in the caller?

In fnic_delete_fcp_tports(), a list_for_each_entry_safe() loop holds
fnic_lock and calls nvfnic_delete_tport(). By explicitly dropping fnic_lock
here, if a concurrent task frees the cached next pointer while the lock is
dropped, the loop will dereference freed memory when it resumes.

[ ... ]
> diff --git a/drivers/scsi/fnic/fnic_res.h b/drivers/scsi/fnic/fnic_res.h
> --- a/drivers/scsi/fnic/fnic_res.h
> +++ b/drivers/scsi/fnic/fnic_res.h
> @@ -126,7 +126,8 @@ static inline void fnic_queue_wq_copy_desc_itmf(struct vnic_wq_copy *wq,
>  	desc->u.itmf.tm_req = tm_req;       /* SCSI Task Management request */
>  	desc->u.itmf.t_tag = tm_id;         /* tag of fcpio to be aborted */
>  	desc->u.itmf._resvd = 0;
> -	memcpy(desc->u.itmf.lun, lun, LUN_ADDRESS);  /* LUN address */
> +	if (lun)
> +		memcpy(desc->u.itmf.lun, lun, LUN_ADDRESS);  /* LUN address */
>  	desc->u.itmf._resvd1 = 0;
>  	hton24(desc->u.itmf.d_id, d_id);    /* FC vNIC only: Target D_ID */

[Severity: Medium]
Does skipping the memcpy when lun is NULL leak uninitialized memory to the
firmware?

If lun is NULL, the 8-byte lun array in the descriptor is not zeroed. Because
vnic_wq_copy_next_desc() returns memory directly from the ring buffer, this
leaks whatever residual data was previously in the descriptor.

[ ... ]
> diff --git a/drivers/scsi/fnic/fnic_scsi.c b/drivers/scsi/fnic/fnic_scsi.c
> --- a/drivers/scsi/fnic/fnic_scsi.c
> +++ b/drivers/scsi/fnic/fnic_scsi.c

[Severity: High]
Are the newly added NVMe completion handlers unreachable from the completion
queue polling loop?

When the hardware completes an NVMe I/O, fnic_fcpio_cmpl_handler() processes
the CQ entry. Because there are no switch cases for FCPIO_NVME_ERSP_HW_CMPL
or FCPIO_NVME_ERSP_FW_CMPL, they hit the default case and are discarded.
Consequently, NVMe commands are never completed back to the midlayer, causing
I/Os to hang indefinitely.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260724174811.5118-1-kartilak@cisco.com?part=6

  reply	other threads:[~2026-07-24 18:20 UTC|newest]

Thread overview: 27+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-24 17:47 [PATCH v6 00/13] Introduce functionality for NVMe initiator Karan Tilak Kumar
2026-07-24 17:47 ` [PATCH v6 01/13] scsi: fnic: Make debug logging protocol independent Karan Tilak Kumar
2026-07-24 18:16   ` sashiko-bot
2026-07-24 17:48 ` [PATCH v6 02/13] scsi: fnic: Use fnic_num for non-SCSI identifiers Karan Tilak Kumar
2026-07-24 18:09   ` sashiko-bot
2026-07-24 17:48 ` [PATCH v6 03/13] scsi: fnic: Decode firmware role configuration Karan Tilak Kumar
2026-07-24 18:07   ` sashiko-bot
2026-07-24 17:48 ` [PATCH v6 04/13] scsi: fnic: Advertise NVMe initiator service parameters Karan Tilak Kumar
2026-07-24 18:05   ` sashiko-bot
2026-07-24 17:48 ` [PATCH v6 05/13] scsi: fnic: Add FDLS role handling for NVMe initiators Karan Tilak Kumar
2026-07-24 18:54   ` sashiko-bot
2026-07-24 17:48 ` [PATCH v6 06/13] scsi: fnic: Add the NVMe/FC transport path Karan Tilak Kumar
2026-07-24 18:20   ` sashiko-bot [this message]
2026-07-24 17:48 ` [PATCH v6 07/13] scsi: fnic: Route completions and resets by initiator role Karan Tilak Kumar
2026-07-24 18:26   ` sashiko-bot
2026-07-24 17:48 ` [PATCH v6 08/13] scsi: fnic: Handle NVMe LS frames in FDLS Karan Tilak Kumar
2026-07-24 18:17   ` sashiko-bot
2026-07-24 17:48 ` [PATCH v6 09/13] scsi: fnic: Send NVMe LS requests through FDLS Karan Tilak Kumar
2026-07-24 18:16   ` sashiko-bot
2026-07-24 17:48 ` [PATCH v6 10/13] scsi: fnic: Abort timed-out NVMe LS requests Karan Tilak Kumar
2026-07-24 18:26   ` sashiko-bot
2026-07-24 17:48 ` [PATCH v6 11/13] scsi: fnic: Track NVMe transport statistics Karan Tilak Kumar
2026-07-24 18:31   ` sashiko-bot
2026-07-24 17:48 ` [PATCH v6 12/13] scsi: fnic: Expose NVMe transport state in debugfs Karan Tilak Kumar
2026-07-24 18:25   ` sashiko-bot
2026-07-24 17:48 ` [PATCH v6 13/13] scsi: fnic: Bump up version number Karan Tilak Kumar
2026-07-24 18:18   ` 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=20260724182048.0C5771F000E9@smtp.kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=kartilak@cisco.com \
    --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