[PATCH 1/2] scsi: lpfc: bound EDC descriptor list by payload length

From: Michael Bommarito

Date: Thu Jul 09 2026 - 22:31:13 EST


drivers/scsi/lpfc/lpfc_els.c:lpfc_els_rcv_edc() trusts the EDC
descriptor-list length from the received frame without checking that it
fits in the actual ELS payload. An adjacent Fibre Channel fabric peer or
device can send an unsolicited EDC frame with a short payload and an
oversized descriptor-list length. The TLV walk can then read past the
receive buffer and trip a KASAN slab-out-of-bounds read in the LPFC ELS
receive path.

Impact: An adjacent Fibre Channel fabric peer or device can crash an
LPFC host via a malformed EDC ELS frame.

Pass the received payload length into lpfc_els_rcv_edc(), reject
truncated EDC headers and descriptor lists larger than the received
payload, and avoid logging a third payload word unless it is present.

Fixes: 9064aeb2df8e ("scsi: lpfc: Add EDC ELS support")
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: Codex:gpt-5-5-xhigh
Signed-off-by: Michael Bommarito <michael.bommarito@xxxxxxxxx>
---

I reproduced this with a same-translation-unit KUnit/KASAN test on
f5459048c38a. Without the patch, the malformed EDC frame triggers
BUG: KASAN: slab-out-of-bounds in lpfc_els_rcv_edc after the benign EDC
control passes. With the patch, the benign and malformed KUnit cases
both pass.
drivers/scsi/lpfc/lpfc_els.c | 41 ++++++++++++++++++++++++------------
1 file changed, 28 insertions(+), 13 deletions(-)

diff --git a/drivers/scsi/lpfc/lpfc_els.c b/drivers/scsi/lpfc/lpfc_els.c
index cee709617a313..5408b116f2d5a 100644
--- a/drivers/scsi/lpfc/lpfc_els.c
+++ b/drivers/scsi/lpfc/lpfc_els.c
@@ -9409,13 +9409,14 @@ lpfc_els_rcv_fan(struct lpfc_vport *vport, struct lpfc_iocbq *cmdiocb,
* @vport: pointer to a host virtual N_Port data structure.
* @cmdiocb: pointer to lpfc command iocb data structure.
* @ndlp: pointer to a node-list data structure.
+ * @payload_len: received EDC payload length in bytes.
*
* Return code
* 0 - Successfully processed echo iocb (currently always return 0)
**/
static int
lpfc_els_rcv_edc(struct lpfc_vport *vport, struct lpfc_iocbq *cmdiocb,
- struct lpfc_nodelist *ndlp)
+ struct lpfc_nodelist *ndlp, u32 payload_len)
{
struct lpfc_hba *phba = vport->phba;
struct fc_els_edc *edc_req;
@@ -9423,25 +9424,39 @@ lpfc_els_rcv_edc(struct lpfc_vport *vport, struct lpfc_iocbq *cmdiocb,
uint8_t *payload;
uint32_t *ptr, dtag;
const char *dtag_nm;
- int desc_cnt = 0, bytes_remain;
+ int desc_cnt = 0;
+ u32 bytes_remain, desc_len, word2 = 0;
struct fc_diag_lnkflt_desc *plnkflt;

payload = cmdiocb->cmd_dmabuf->virt;

+ /* No signal support unless there is a congestion descriptor */
+ phba->cgn_reg_signal = EDC_CG_SIG_NOTSUPPORTED;
+ phba->cgn_sig_freq = 0;
+ phba->cgn_reg_fpin = LPFC_CGN_FPIN_ALARM | LPFC_CGN_FPIN_WARN;
+
+ if (payload_len < sizeof(*edc_req))
+ goto out;
+
edc_req = (struct fc_els_edc *)payload;
- bytes_remain = be32_to_cpu(edc_req->desc_len);
+ desc_len = be32_to_cpu(edc_req->desc_len);
+ if (desc_len > payload_len - sizeof(*edc_req)) {
+ lpfc_printf_log(phba, KERN_WARNING,
+ LOG_ELS | LOG_CGN_MGMT | LOG_LDS_EVENT,
+ "6468 Bad EDC descriptor list length %u: %u\n",
+ desc_len, payload_len);
+ goto out;
+ }
+ bytes_remain = desc_len;

ptr = (uint32_t *)payload;
+ if (payload_len >= 3 * sizeof(*ptr))
+ word2 = be32_to_cpu(*(ptr + 2));
lpfc_printf_vlog(vport, KERN_INFO,
LOG_ELS | LOG_CGN_MGMT | LOG_LDS_EVENT,
- "3319 Rcv EDC payload len %d: x%x x%x x%x\n",
+ "3319 Rcv EDC payload len %u: x%x x%x x%x\n",
bytes_remain, be32_to_cpu(*ptr),
- be32_to_cpu(*(ptr + 1)), be32_to_cpu(*(ptr + 2)));
-
- /* No signal support unless there is a congestion descriptor */
- phba->cgn_reg_signal = EDC_CG_SIG_NOTSUPPORTED;
- phba->cgn_sig_freq = 0;
- phba->cgn_reg_fpin = LPFC_CGN_FPIN_ALARM | LPFC_CGN_FPIN_WARN;
+ be32_to_cpu(*(ptr + 1)), word2);

if (bytes_remain <= 0)
goto out;
@@ -9471,7 +9486,7 @@ lpfc_els_rcv_edc(struct lpfc_vport *vport, struct lpfc_iocbq *cmdiocb,
lpfc_printf_log(phba, KERN_WARNING,
LOG_ELS | LOG_CGN_MGMT | LOG_LDS_EVENT,
"6465 Truncated Link Fault Diagnostic "
- "descriptor[%d]: %d vs 0x%zx 0x%zx\n",
+ "descriptor[%d]: %u vs 0x%zx 0x%zx\n",
desc_cnt, bytes_remain,
FC_TLV_DESC_SZ_FROM_LENGTH(tlv),
sizeof(struct fc_diag_lnkflt_desc));
@@ -9497,7 +9512,7 @@ lpfc_els_rcv_edc(struct lpfc_vport *vport, struct lpfc_iocbq *cmdiocb,
lpfc_printf_log(
phba, KERN_WARNING, LOG_CGN_MGMT,
"6466 Truncated cgn signal Diagnostic "
- "descriptor[%d]: %d vs 0x%zx 0x%zx\n",
+ "descriptor[%d]: %u vs 0x%zx 0x%zx\n",
desc_cnt, bytes_remain,
FC_TLV_DESC_SZ_FROM_LENGTH(tlv),
sizeof(struct fc_diag_cg_sig_desc));
@@ -10815,7 +10830,7 @@ lpfc_els_unsol_buffer(struct lpfc_hba *phba, struct lpfc_sli_ring *pring,
/* There are no replies, so no rjt codes */
break;
case ELS_CMD_EDC:
- lpfc_els_rcv_edc(vport, elsiocb, ndlp);
+ lpfc_els_rcv_edc(vport, elsiocb, ndlp, payload_len);
break;
case ELS_CMD_RDF:
phba->fc_stat.elsRcvRDF++;