]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
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)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 2 May 2025 05:44:24 +0000 (07:44 +0200)
commit 577a942df3de2666f6947bdd3a5c9e8d30073424 upstream.

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>
[Minor context change fixed.]
Signed-off-by: Bin Lan <bin.lan.cn@windriver.com>
Signed-off-by: He Zhe <zhe.he@windriver.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/scsi/lpfc/lpfc_els.c

index 5f44a0763f37df0e8c45a5ad20643a8260a68985..134d56bd00dafe885a99e414b147be0f7a9744cc 100644 (file)
@@ -1517,10 +1517,13 @@ lpfc_initial_flogi(struct lpfc_vport *vport)
        }
 
        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;
@@ -1563,10 +1566,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;
@@ -1967,6 +1973,7 @@ lpfc_cmpl_els_plogi(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb,
        struct lpfc_dmabuf *prsp;
        int disc;
        struct serv_parm *sp = NULL;
+       bool release_node = false;
 
        /* we pass cmdiocb to state machine which needs rspiocb as well */
        cmdiocb->context_un.rsp_iocb = rspiocb;
@@ -2047,19 +2054,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 *)
@@ -2269,6 +2278,7 @@ lpfc_cmpl_els_prli(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb,
        struct lpfc_nodelist *ndlp;
        char *mode;
        u32 loglevel;
+       bool release_node = false;
 
        /* we pass cmdiocb to state machine which needs rspiocb as well */
        cmdiocb->context_un.rsp_iocb = rspiocb;
@@ -2335,14 +2345,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
@@ -2713,6 +2727,7 @@ lpfc_cmpl_els_adisc(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb,
        IOCB_t *irsp;
        struct lpfc_nodelist *ndlp;
        int  disc;
+       bool release_node = false;
 
        /* we pass cmdiocb to state machine which needs rspiocb as well */
        cmdiocb->context_un.rsp_iocb = rspiocb;
@@ -2771,13 +2786,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,