Commit 577a942d authored by James Smart's avatar James Smart Committed by Martin K. Petersen

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.comCo-developed-by: default avatarJustin Tee <justin.tee@broadcom.com>
Signed-off-by: default avatarJustin Tee <justin.tee@broadcom.com>
Signed-off-by: default avatarJames Smart <jsmart2021@gmail.com>
Signed-off-by: default avatarMartin K. Petersen <martin.petersen@oracle.com>
parent 3483a44b
...@@ -1534,10 +1534,13 @@ lpfc_initial_flogi(struct lpfc_vport *vport) ...@@ -1534,10 +1534,13 @@ lpfc_initial_flogi(struct lpfc_vport *vport)
/* Reset the Fabric flag, topology change may have happened */ /* Reset the Fabric flag, topology change may have happened */
vport->fc_flag &= ~FC_FABRIC; vport->fc_flag &= ~FC_FABRIC;
if (lpfc_issue_els_flogi(vport, ndlp, 0)) { if (lpfc_issue_els_flogi(vport, ndlp, 0)) {
/* This decrement of reference count to node shall kick off /* A node reference should be retained while registered with a
* the release of the node. * 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 0;
} }
return 1; return 1;
...@@ -1580,10 +1583,13 @@ lpfc_initial_fdisc(struct lpfc_vport *vport) ...@@ -1580,10 +1583,13 @@ lpfc_initial_fdisc(struct lpfc_vport *vport)
} }
if (lpfc_issue_els_fdisc(vport, ndlp, 0)) { if (lpfc_issue_els_fdisc(vport, ndlp, 0)) {
/* decrement node reference count to trigger the release of /* A node reference should be retained while registered with a
* the node. * 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 0;
} }
return 1; return 1;
...@@ -1985,6 +1991,7 @@ lpfc_cmpl_els_plogi(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb, ...@@ -1985,6 +1991,7 @@ lpfc_cmpl_els_plogi(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb,
int disc; int disc;
struct serv_parm *sp = NULL; struct serv_parm *sp = NULL;
u32 ulp_status, ulp_word4, did, iotag; u32 ulp_status, ulp_word4, did, iotag;
bool release_node = false;
/* we pass cmdiocb to state machine which needs rspiocb as well */ /* we pass cmdiocb to state machine which needs rspiocb as well */
cmdiocb->context_un.rsp_iocb = rspiocb; cmdiocb->context_un.rsp_iocb = rspiocb;
...@@ -2073,19 +2080,21 @@ lpfc_cmpl_els_plogi(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb, ...@@ -2073,19 +2080,21 @@ lpfc_cmpl_els_plogi(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb,
spin_unlock_irq(&ndlp->lock); spin_unlock_irq(&ndlp->lock);
goto out; goto out;
} }
spin_unlock_irq(&ndlp->lock);
/* No PLOGI collision and the node is not registered with the /* No PLOGI collision and the node is not registered with the
* scsi or nvme transport. It is no longer an active node. Just * scsi or nvme transport. It is no longer an active node. Just
* start the device remove process. * start the device remove process.
*/ */
if (!(ndlp->fc4_xpt_flags & (SCSI_XPT_REGD | NVME_XPT_REGD))) { if (!(ndlp->fc4_xpt_flags & (SCSI_XPT_REGD | NVME_XPT_REGD))) {
spin_lock_irq(&ndlp->lock);
ndlp->nlp_flag &= ~NLP_NPR_2B_DISC; 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, lpfc_disc_state_machine(vport, ndlp, cmdiocb,
NLP_EVT_DEVICE_RM); NLP_EVT_DEVICE_RM);
}
} else { } else {
/* Good status, call state machine */ /* Good status, call state machine */
prsp = list_entry(((struct lpfc_dmabuf *) prsp = list_entry(((struct lpfc_dmabuf *)
...@@ -2296,6 +2305,7 @@ lpfc_cmpl_els_prli(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb, ...@@ -2296,6 +2305,7 @@ lpfc_cmpl_els_prli(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb,
u32 loglevel; u32 loglevel;
u32 ulp_status; u32 ulp_status;
u32 ulp_word4; u32 ulp_word4;
bool release_node = false;
/* we pass cmdiocb to state machine which needs rspiocb as well */ /* we pass cmdiocb to state machine which needs rspiocb as well */
cmdiocb->context_un.rsp_iocb = rspiocb; cmdiocb->context_un.rsp_iocb = rspiocb;
...@@ -2372,14 +2382,18 @@ lpfc_cmpl_els_prli(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb, ...@@ -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 * it is no longer an active node. Otherwise devloss
* handles the final cleanup. * handles the final cleanup.
*/ */
spin_lock_irq(&ndlp->lock);
if (!(ndlp->fc4_xpt_flags & (SCSI_XPT_REGD | NVME_XPT_REGD)) && if (!(ndlp->fc4_xpt_flags & (SCSI_XPT_REGD | NVME_XPT_REGD)) &&
!ndlp->fc4_prli_sent) { !ndlp->fc4_prli_sent) {
spin_lock_irq(&ndlp->lock);
ndlp->nlp_flag &= ~NLP_NPR_2B_DISC; 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, lpfc_disc_state_machine(vport, ndlp, cmdiocb,
NLP_EVT_DEVICE_RM); NLP_EVT_DEVICE_RM);
}
} else { } else {
/* Good status, call state machine. However, if another /* Good status, call state machine. However, if another
* PRLI is outstanding, don't call the state machine * 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, ...@@ -2751,6 +2765,7 @@ lpfc_cmpl_els_adisc(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb,
struct lpfc_nodelist *ndlp; struct lpfc_nodelist *ndlp;
int disc; int disc;
u32 ulp_status, ulp_word4, tmo; u32 ulp_status, ulp_word4, tmo;
bool release_node = false;
/* we pass cmdiocb to state machine which needs rspiocb as well */ /* we pass cmdiocb to state machine which needs rspiocb as well */
cmdiocb->context_un.rsp_iocb = rspiocb; cmdiocb->context_un.rsp_iocb = rspiocb;
...@@ -2817,13 +2832,17 @@ lpfc_cmpl_els_adisc(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb, ...@@ -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 * transport, it is no longer an active node. Otherwise
* devloss handles the final cleanup. * devloss handles the final cleanup.
*/ */
spin_lock_irq(&ndlp->lock);
if (!(ndlp->fc4_xpt_flags & (SCSI_XPT_REGD | NVME_XPT_REGD))) { if (!(ndlp->fc4_xpt_flags & (SCSI_XPT_REGD | NVME_XPT_REGD))) {
spin_lock_irq(&ndlp->lock);
ndlp->nlp_flag &= ~NLP_NPR_2B_DISC; 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, lpfc_disc_state_machine(vport, ndlp, cmdiocb,
NLP_EVT_DEVICE_RM); NLP_EVT_DEVICE_RM);
}
} else } else
/* Good status, call state machine */ /* Good status, call state machine */
lpfc_disc_state_machine(vport, ndlp, cmdiocb, lpfc_disc_state_machine(vport, ndlp, cmdiocb,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment