[PATCH v2 15/20] scsi: ibmvfc: unregister NVMe local port on adapter removal

From: Tyrel Datwyler

Date: Fri Sep 18 2026 - 21:38:55 EST


ibmvfc_remove() tears down the CRQ and frees all host memory but never
calls ibmvfc_nvme_unregister(). The NVMe-FC transport therefore retains
a live reference to the local port whose private pointer points into the
now-freed ibmvfc_host, leading to a use-after-free whenever the transport
subsequently touches the port (queue creation, port scanning, etc.).

ibmvfc_nvme_unregister() is already called from the NPIV logout and host
reset paths during normal operation; driver removal simply missed it.

Add the call after ibmvfc_release_crq_queue() — at which point the CRQ
is torn down so no further completions can arrive — and before
ibmvfc_free_mem(), so host memory is still valid while
nvme_fc_unregister_localport() runs and waits for the
localport_delete callback to complete.

Fixes: 86e495358096 ("scsi: ibmvfc: implement LLDD callbacks for mapping nvme-fc queues")
Signed-off-by: Tyrel Datwyler <tyreld@xxxxxxxxxxxxx>
---
drivers/scsi/ibmvscsi/ibmvfc-core.c | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)

diff --git a/drivers/scsi/ibmvscsi/ibmvfc-core.c b/drivers/scsi/ibmvscsi/ibmvfc-core.c
index 080312ff0a93..e68ed5e79e58 100644
--- a/drivers/scsi/ibmvscsi/ibmvfc-core.c
+++ b/drivers/scsi/ibmvscsi/ibmvfc-core.c
@@ -6976,6 +6976,7 @@ static int ibmvfc_probe(struct vio_dev *vdev, const struct vio_device_id *id)
static void ibmvfc_remove(struct vio_dev *vdev)
{
struct ibmvfc_host *vhost = dev_get_drvdata(&vdev->dev);
+ struct ibmvfc_target *tgt, *tgt_tmp;
LIST_HEAD(purge);
unsigned long flags;

@@ -6992,6 +6993,22 @@ static void ibmvfc_remove(struct vio_dev *vdev)
fc_remove_host(vhost->host);
scsi_remove_host(vhost->host);

+ list_for_each_entry_safe(tgt, tgt_tmp, &vhost->nvme_scrqs.targets, queue) {
+ if (tgt->nvme_remote_port)
+ ibmvfc_nvme_unregister_remoteport(tgt);
+ list_del(&tgt->queue);
+ timer_delete_sync(&tgt->timer);
+ kref_put(&tgt->kref, ibmvfc_release_tgt);
+ }
+
+ list_for_each_entry_safe(tgt, tgt_tmp, &vhost->scsi_scrqs.targets, queue) {
+ if (tgt->rport)
+ fc_remote_port_delete(tgt->rport);
+ list_del(&tgt->queue);
+ timer_delete_sync(&tgt->timer);
+ kref_put(&tgt->kref, ibmvfc_release_tgt);
+ }
+
spin_lock_irqsave(&vhost->host->host_lock, flags);
ibmvfc_purge_requests(vhost, DID_ERROR);
list_splice_init(&vhost->purge, &purge);
@@ -7000,6 +7017,8 @@ static void ibmvfc_remove(struct vio_dev *vdev)
ibmvfc_release_sub_crqs(vhost);
ibmvfc_release_crq_queue(vhost);

+ ibmvfc_nvme_unregister(vhost);
+
ibmvfc_free_mem(vhost);
spin_lock(&ibmvfc_driver_lock);
list_del(&vhost->queue);
--
2.55.0