[PATCH 14/20] scsi: ibmvfc: fix UAF and stall in NVMe LS abort callback

From: Tyrel Datwyler

Date: Wed Sep 16 2026 - 19:12:19 EST


Two problems in ibmvfc_nvme_ls_abort():

1. Use-after-free / stale pointer dereference.
ibmvfc_init_ls_abort() reads abt_evt = ls_abort->private and
immediately dereferences abt_evt->tgt. ibmvfc_ls_req_done() calls
ibmvfc_free_event() under host_lock, which returns the event slot to
the pool. If the LS completes naturally just before ls_abort is
called, abt_evt points to a freed (and potentially reused) event,
making the dereference a UAF.

Fix by taking host_lock before reading ls_abort->private and checking
evt->free (set to 1 by ibmvfc_free_event() under host_lock) to detect
whether the original LS has already completed. If so, there is
nothing to cancel and we return early. ibmvfc_get_event() is also
moved inside the lock so the validity check and event allocation are
atomic with respect to the completion path.

2. Blocking wait on timeout workqueue (same class as the FCP abort fix).
The original code called wait_for_completion() from ls_abort, which
is invoked by the NVMe-FC transport from a context that must not
block.

Fix by replacing ibmvfc_sync_nvme_completion with a dedicated async
callback ibmvfc_nvme_ls_abort_done() that logs any non-zero MAD
status, drops the target kref, and frees the event.
ibmvfc_send_event() guarantees the callback fires on both success and
failure paths, so ibmvfc_nvme_ls_abort() returns immediately after
ibmvfc_send_event().

ibmvfc_sync_nvme_completion is now unused and is removed.
ibmvfc_init_ls_abort() is updated to take the validated abt_evt pointer
directly instead of deriving it from ls_abort->private.

Fixes: 20bec08f0208 ("scsi: ibmvfc: implement nvme-fc LS abort handling callback")
Signed-off-by: Tyrel Datwyler <tyreld@xxxxxxxxxxxxx>
---
drivers/scsi/ibmvscsi/ibmvfc-nvme.c | 68 +++++++++++++++--------------
1 file changed, 35 insertions(+), 33 deletions(-)

diff --git a/drivers/scsi/ibmvscsi/ibmvfc-nvme.c b/drivers/scsi/ibmvscsi/ibmvfc-nvme.c
index 52e2621a4342..7e18b79ae4cc 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc-nvme.c
+++ b/drivers/scsi/ibmvscsi/ibmvfc-nvme.c
@@ -164,21 +164,12 @@ static int ibmvfc_nvme_ls_req(struct nvme_fc_local_port *lport,
return 0;
}

-static void ibmvfc_sync_nvme_completion(struct ibmvfc_event *evt)
+static void ibmvfc_init_ls_abort(struct ibmvfc_event *evt,
+ struct ibmvfc_event *abt_evt)
{
- /* copy the response back */
- if (evt->sync_iu)
- *evt->sync_iu = *evt->xfer_iu;
-
- complete(&evt->comp);
-}
-
-static void ibmvfc_init_ls_abort(struct ibmvfc_event *evt, struct nvmefc_ls_req *ls_abort)
-{
- struct ibmvfc_tmf *tmf;
- struct ibmvfc_event *abt_evt = ls_abort->private;
struct ibmvfc_target *tgt = abt_evt->tgt;
struct ibmvfc_host *vhost = evt->vhost;
+ struct ibmvfc_tmf *tmf;

tmf = &evt->iu.tmf;
memset(tmf, 0, sizeof(*tmf));
@@ -192,8 +183,18 @@ static void ibmvfc_init_ls_abort(struct ibmvfc_event *evt, struct nvmefc_ls_req
tmf->cancel_key = cpu_to_be32((u64)abt_evt);
tmf->my_cancel_key = cpu_to_be32((u64)evt);
tmf->assoc_id = cpu_to_be64(tgt->assoc_id);
+}
+
+static void ibmvfc_nvme_ls_abort_done(struct ibmvfc_event *evt)
+{
+ u16 status = be16_to_cpu(evt->xfer_iu->mad_common.status);
+
+ if (status)
+ ibmvfc_dbg(evt->vhost, "ls_abort: cancel MAD failed with rc=%x\n",
+ status);

- init_completion(&evt->comp);
+ kref_put(&evt->tgt->kref, ibmvfc_release_tgt);
+ ibmvfc_free_event(evt);
}

static void ibmvfc_nvme_ls_abort(struct nvme_fc_local_port *lport,
@@ -202,34 +203,35 @@ static void ibmvfc_nvme_ls_abort(struct nvme_fc_local_port *lport,
{
struct ibmvfc_host *vhost = lport->private;
struct ibmvfc_target *tgt = rport->private;
- struct ibmvfc_event *evt;
- union ibmvfc_iu rsp;
+ struct ibmvfc_event *evt, *abt_evt;
unsigned long flags;
- u16 status = IBMVFC_MAD_CRQ_ERROR;
+
+ spin_lock_irqsave(&vhost->host->host_lock, flags);
+
+ /*
+ * If the original LS has already completed naturally, abt_evt will
+ * have been freed back to the pool (evt->free set to 1 under
+ * host_lock by ibmvfc_free_event()). Nothing left to cancel.
+ */
+ abt_evt = ls_abort->private;
+ if (!abt_evt || atomic_read(&abt_evt->free)) {
+ spin_unlock_irqrestore(&vhost->host->host_lock, flags);
+ return;
+ }

evt = ibmvfc_get_event(&vhost->crq);
- if (!vhost->logged_in || !evt)
+ if (!vhost->logged_in || !evt) {
+ spin_unlock_irqrestore(&vhost->host->host_lock, flags);
return;
+ }

- spin_lock_irqsave(&vhost->host->host_lock, flags);
kref_get(&tgt->kref);
- ibmvfc_init_event(evt, ibmvfc_sync_nvme_completion, IBMVFC_MAD_FORMAT);
- ibmvfc_init_ls_abort(evt, ls_abort);
- evt->sync_iu = &rsp;
-
- if (ibmvfc_send_event(evt, vhost, default_timeout))
- goto out;
-
- spin_unlock_irqrestore(&vhost->host->host_lock, flags);
+ ibmvfc_init_event(evt, ibmvfc_nvme_ls_abort_done, IBMVFC_MAD_FORMAT);
+ ibmvfc_init_ls_abort(evt, abt_evt);
+ evt->tgt = tgt;

- wait_for_completion(&evt->comp);
- status = be16_to_cpu(rsp.mad_common.status);
- spin_lock_irqsave(&vhost->host->host_lock, flags);
- ibmvfc_free_event(evt);
-out:
+ ibmvfc_send_event(evt, vhost, default_timeout);
spin_unlock_irqrestore(&vhost->host->host_lock, flags);
- ibmvfc_dbg(vhost, "ls_abort: cancel failed with rc=%x\n", status);
- kref_put(&tgt->kref, ibmvfc_release_tgt);
}

static void ibmvfc_nvme_done(struct ibmvfc_event *evt)
--
2.55.0