]> 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>
Thu, 9 Jun 2022 08:29:35 +0000 (10:29 +0200)
[ Upstream commit 577a942df3de2666f6947bdd3a5c9e8d30073424 ]

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>
Signed-off-by: Sasha Levin <sashal@kernel.org>
drivers/scsi/lpfc/lpfc_els.c

index 872a26376ccbb84faf6cc6c5804ed5aeea6102cf..46a01a51b2073765c0d9a27e0a5d2c1d6301fa11 100644 (file)
@@ -1532,10 +1532,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;
@@ -1578,10 +1581,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;
@@ -1983,6 +1989,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;
@@ -2071,19 +2078,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 *)
@@ -2294,6 +2303,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;
@@ -2370,14 +2380,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
@@ -2749,6 +2763,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;
@@ -2815,13 +2830,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,