scsi: lpfc: Fix null pointer dereference after failing to issue FLOGI and PLOGI
authorJames Smart <jsmart2021@gmail.com>
Tue, 12 Apr 2022 22:19:50 +0000 (15:19 -0700)
committerMartin K. Petersen <martin.petersen@oracle.com>
Tue, 19 Apr 2022 02:48:44 +0000 (22:48 -0400)
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 <justin.tee@broadcom.com>
Signed-off-by: Justin Tee <justin.tee@broadcom.com>
Signed-off-by: James Smart <jsmart2021@gmail.com>
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
drivers/scsi/lpfc/lpfc_els.c

index 59e8c6f..44ef873 100644 (file)
@@ -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,