From: James Smart Date: Tue, 12 Apr 2022 22:19:50 +0000 (-0700) Subject: scsi: lpfc: Fix null pointer dereference after failing to issue FLOGI and PLOGI X-Git-Url: http://git.maquefel.me/?a=commitdiff_plain;h=577a942df3de2666f6947bdd3a5c9e8d30073424;p=linux.git scsi: lpfc: Fix null pointer dereference after failing to issue FLOGI and PLOGI If lpfc_issue_els_flogi() fails and returns non-zero status, the node reference count is decremented to trigger the release of the nodelist structure. However, if there is a prior registration or dev-loss-evt work pending, the node may be released prematurely. When dev-loss-evt completes, the released node is referenced causing a use-after-free null pointer dereference. Similarly, when processing non-zero ELS PLOGI completion status in lpfc_cmpl_els_plogi(), the ndlp flags are checked for a transport registration before triggering node removal. If dev-loss-evt work is pending, the node may be released prematurely and a subsequent call to lpfc_dev_loss_tmo_handler() results in a use after free ndlp dereference. Add test for pending dev-loss before decrementing the node reference count for FLOGI, PLOGI, PRLI, and ADISC handling. Link: https://lore.kernel.org/r/20220412222008.126521-9-jsmart2021@gmail.com Co-developed-by: Justin Tee Signed-off-by: Justin Tee Signed-off-by: James Smart Signed-off-by: Martin K. Petersen --- diff --git a/drivers/scsi/lpfc/lpfc_els.c b/drivers/scsi/lpfc/lpfc_els.c index 59e8c6f9d30ed..44ef87324b806 100644 --- a/drivers/scsi/lpfc/lpfc_els.c +++ b/drivers/scsi/lpfc/lpfc_els.c @@ -1534,10 +1534,13 @@ lpfc_initial_flogi(struct lpfc_vport *vport) /* Reset the Fabric flag, topology change may have happened */ vport->fc_flag &= ~FC_FABRIC; if (lpfc_issue_els_flogi(vport, ndlp, 0)) { - /* This decrement of reference count to node shall kick off - * the release of the node. + /* A node reference should be retained while registered with a + * transport or dev-loss-evt work is pending. + * Otherwise, decrement node reference to trigger release. */ - lpfc_nlp_put(ndlp); + if (!(ndlp->fc4_xpt_flags & (SCSI_XPT_REGD | NVME_XPT_REGD)) && + !(ndlp->nlp_flag & NLP_IN_DEV_LOSS)) + lpfc_nlp_put(ndlp); return 0; } return 1; @@ -1580,10 +1583,13 @@ lpfc_initial_fdisc(struct lpfc_vport *vport) } if (lpfc_issue_els_fdisc(vport, ndlp, 0)) { - /* decrement node reference count to trigger the release of - * the node. + /* A node reference should be retained while registered with a + * transport or dev-loss-evt work is pending. + * Otherwise, decrement node reference to trigger release. */ - lpfc_nlp_put(ndlp); + if (!(ndlp->fc4_xpt_flags & (SCSI_XPT_REGD | NVME_XPT_REGD)) && + !(ndlp->nlp_flag & NLP_IN_DEV_LOSS)) + lpfc_nlp_put(ndlp); return 0; } return 1; @@ -1985,6 +1991,7 @@ lpfc_cmpl_els_plogi(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb, int disc; struct serv_parm *sp = NULL; u32 ulp_status, ulp_word4, did, iotag; + bool release_node = false; /* we pass cmdiocb to state machine which needs rspiocb as well */ cmdiocb->context_un.rsp_iocb = rspiocb; @@ -2073,19 +2080,21 @@ lpfc_cmpl_els_plogi(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb, spin_unlock_irq(&ndlp->lock); goto out; } - spin_unlock_irq(&ndlp->lock); /* No PLOGI collision and the node is not registered with the * scsi or nvme transport. It is no longer an active node. Just * start the device remove process. */ if (!(ndlp->fc4_xpt_flags & (SCSI_XPT_REGD | NVME_XPT_REGD))) { - spin_lock_irq(&ndlp->lock); ndlp->nlp_flag &= ~NLP_NPR_2B_DISC; - spin_unlock_irq(&ndlp->lock); + if (!(ndlp->nlp_flag & NLP_IN_DEV_LOSS)) + release_node = true; + } + spin_unlock_irq(&ndlp->lock); + + if (release_node) lpfc_disc_state_machine(vport, ndlp, cmdiocb, NLP_EVT_DEVICE_RM); - } } else { /* Good status, call state machine */ prsp = list_entry(((struct lpfc_dmabuf *) @@ -2296,6 +2305,7 @@ lpfc_cmpl_els_prli(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb, u32 loglevel; u32 ulp_status; u32 ulp_word4; + bool release_node = false; /* we pass cmdiocb to state machine which needs rspiocb as well */ cmdiocb->context_un.rsp_iocb = rspiocb; @@ -2372,14 +2382,18 @@ lpfc_cmpl_els_prli(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb, * it is no longer an active node. Otherwise devloss * handles the final cleanup. */ + spin_lock_irq(&ndlp->lock); if (!(ndlp->fc4_xpt_flags & (SCSI_XPT_REGD | NVME_XPT_REGD)) && !ndlp->fc4_prli_sent) { - spin_lock_irq(&ndlp->lock); ndlp->nlp_flag &= ~NLP_NPR_2B_DISC; - spin_unlock_irq(&ndlp->lock); + if (!(ndlp->nlp_flag & NLP_IN_DEV_LOSS)) + release_node = true; + } + spin_unlock_irq(&ndlp->lock); + + if (release_node) lpfc_disc_state_machine(vport, ndlp, cmdiocb, NLP_EVT_DEVICE_RM); - } } else { /* Good status, call state machine. However, if another * PRLI is outstanding, don't call the state machine @@ -2751,6 +2765,7 @@ lpfc_cmpl_els_adisc(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb, struct lpfc_nodelist *ndlp; int disc; u32 ulp_status, ulp_word4, tmo; + bool release_node = false; /* we pass cmdiocb to state machine which needs rspiocb as well */ cmdiocb->context_un.rsp_iocb = rspiocb; @@ -2817,13 +2832,17 @@ lpfc_cmpl_els_adisc(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb, * transport, it is no longer an active node. Otherwise * devloss handles the final cleanup. */ + spin_lock_irq(&ndlp->lock); if (!(ndlp->fc4_xpt_flags & (SCSI_XPT_REGD | NVME_XPT_REGD))) { - spin_lock_irq(&ndlp->lock); ndlp->nlp_flag &= ~NLP_NPR_2B_DISC; - spin_unlock_irq(&ndlp->lock); + if (!(ndlp->nlp_flag & NLP_IN_DEV_LOSS)) + release_node = true; + } + spin_unlock_irq(&ndlp->lock); + + if (release_node) lpfc_disc_state_machine(vport, ndlp, cmdiocb, NLP_EVT_DEVICE_RM); - } } else /* Good status, call state machine */ lpfc_disc_state_machine(vport, ndlp, cmdiocb,