| From 5cc980619ed51084ec0f0576e0f4df80ec01b55e Mon Sep 17 00:00:00 2001 |
| From: "Ewan D. Milne" <emilne@redhat.com> |
| Date: Thu, 17 Jan 2019 11:14:44 -0500 |
| Subject: scsi: lpfc: nvme: avoid hang / use-after-free when destroying |
| localport |
| |
| [ Upstream commit 7961cba6f7d8215fa632df3d220e5154bb825249 ] |
| |
| We cannot wait on a completion object in the lpfc_nvme_lport structure in |
| the _destroy_localport() code path because the NVMe/fc transport will free |
| that structure immediately after the .localport_delete() callback. This |
| results in a use-after-free, and a hang if slub_debug=FZPU is enabled. |
| |
| Fix this by putting the completion on the stack. |
| |
| Signed-off-by: Ewan D. Milne <emilne@redhat.com> |
| Acked-by: James Smart <james.smart@broadcom.com> |
| Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| drivers/scsi/lpfc/lpfc_nvme.c | 16 +++++++++------- |
| drivers/scsi/lpfc/lpfc_nvme.h | 2 +- |
| 2 files changed, 10 insertions(+), 8 deletions(-) |
| |
| diff --git a/drivers/scsi/lpfc/lpfc_nvme.c b/drivers/scsi/lpfc/lpfc_nvme.c |
| index 918ae18ef8a82..ca62117a2d131 100644 |
| --- a/drivers/scsi/lpfc/lpfc_nvme.c |
| +++ b/drivers/scsi/lpfc/lpfc_nvme.c |
| @@ -297,7 +297,8 @@ lpfc_nvme_localport_delete(struct nvme_fc_local_port *localport) |
| lport); |
| |
| /* release any threads waiting for the unreg to complete */ |
| - complete(&lport->lport_unreg_done); |
| + if (lport->vport->localport) |
| + complete(lport->lport_unreg_cmp); |
| } |
| |
| /* lpfc_nvme_remoteport_delete |
| @@ -2556,7 +2557,8 @@ lpfc_nvme_create_localport(struct lpfc_vport *vport) |
| */ |
| void |
| lpfc_nvme_lport_unreg_wait(struct lpfc_vport *vport, |
| - struct lpfc_nvme_lport *lport) |
| + struct lpfc_nvme_lport *lport, |
| + struct completion *lport_unreg_cmp) |
| { |
| #if (IS_ENABLED(CONFIG_NVME_FC)) |
| u32 wait_tmo; |
| @@ -2568,8 +2570,7 @@ lpfc_nvme_lport_unreg_wait(struct lpfc_vport *vport, |
| */ |
| wait_tmo = msecs_to_jiffies(LPFC_NVME_WAIT_TMO * 1000); |
| while (true) { |
| - ret = wait_for_completion_timeout(&lport->lport_unreg_done, |
| - wait_tmo); |
| + ret = wait_for_completion_timeout(lport_unreg_cmp, wait_tmo); |
| if (unlikely(!ret)) { |
| lpfc_printf_vlog(vport, KERN_ERR, LOG_NVME_IOERR, |
| "6176 Lport %p Localport %p wait " |
| @@ -2603,12 +2604,12 @@ lpfc_nvme_destroy_localport(struct lpfc_vport *vport) |
| struct lpfc_nvme_lport *lport; |
| struct lpfc_nvme_ctrl_stat *cstat; |
| int ret; |
| + DECLARE_COMPLETION_ONSTACK(lport_unreg_cmp); |
| |
| if (vport->nvmei_support == 0) |
| return; |
| |
| localport = vport->localport; |
| - vport->localport = NULL; |
| lport = (struct lpfc_nvme_lport *)localport->private; |
| cstat = lport->cstat; |
| |
| @@ -2619,13 +2620,14 @@ lpfc_nvme_destroy_localport(struct lpfc_vport *vport) |
| /* lport's rport list is clear. Unregister |
| * lport and release resources. |
| */ |
| - init_completion(&lport->lport_unreg_done); |
| + lport->lport_unreg_cmp = &lport_unreg_cmp; |
| ret = nvme_fc_unregister_localport(localport); |
| |
| /* Wait for completion. This either blocks |
| * indefinitely or succeeds |
| */ |
| - lpfc_nvme_lport_unreg_wait(vport, lport); |
| + lpfc_nvme_lport_unreg_wait(vport, lport, &lport_unreg_cmp); |
| + vport->localport = NULL; |
| kfree(cstat); |
| |
| /* Regardless of the unregister upcall response, clear |
| diff --git a/drivers/scsi/lpfc/lpfc_nvme.h b/drivers/scsi/lpfc/lpfc_nvme.h |
| index cfd4719be25c3..b234d02989942 100644 |
| --- a/drivers/scsi/lpfc/lpfc_nvme.h |
| +++ b/drivers/scsi/lpfc/lpfc_nvme.h |
| @@ -50,7 +50,7 @@ struct lpfc_nvme_ctrl_stat { |
| /* Declare nvme-based local and remote port definitions. */ |
| struct lpfc_nvme_lport { |
| struct lpfc_vport *vport; |
| - struct completion lport_unreg_done; |
| + struct completion *lport_unreg_cmp; |
| /* Add stats counters here */ |
| struct lpfc_nvme_ctrl_stat *cstat; |
| atomic_t fc4NvmeLsRequests; |
| -- |
| 2.19.1 |
| |