* [DRAFT][PATCH] scsi: scsi_transport_fc: widen FPIN pname walker counter to u32
@ 2026-05-18 14:09 Michael Bommarito
2026-05-18 14:09 ` [PATCH] " Michael Bommarito
2026-05-18 14:37 ` [PATCH v2] " Michael Bommarito
0 siblings, 2 replies; 10+ messages in thread
From: Michael Bommarito @ 2026-05-18 14:09 UTC (permalink / raw)
To: Martin K . Petersen, James E . J . Bottomley
Cc: Nilesh Javali, Himanshu Madhani, Shyam Sundar, James Smart,
Hannes Reinecke, John Meneghini, Bryan Gurney, Justin Tee,
Christoph Hellwig, Keith Busch, Kees Cook, linux-scsi, linux-nvme,
linux-hardening, linux-kernel
drivers/scsi/scsi_transport_fc.c::fc_fpin_li_stats_update() and
fc_fpin_peer_congn_stats_update() walk the on-wire pname_list[]
with a u8 loop counter against the 32-bit __be32 pname_count
field, and never bound pname_count by the descriptor body the
TLV walker already validated.
The two functions are reached on every host running lpfc or
qla2xxx as soon as the fabric controller (well-known S_ID
0xFFFFFD on an FC fabric) emits an FPIN ELS for that initiator;
no host-side capability is required, the fabric is the source.
A pname_count of 256 leaves the u8 condition i < 256 true for
every value i can take, so the walker never terminates: it
takes fc_host->rport_lock once per iteration via
fc_find_rport_by_wwpn() and never releases the calling thread.
Impact: a fabric-side FPIN sender (the elected fabric controller,
a co-tenant N_Port that spoofs S_ID 0xFFFFFD after FLOGI, or a
compromised switch supervisor) can hang the FC ELS receive
thread of an lpfc or qla2xxx initiator indefinitely by emitting
one FPIN ELS frame whose Link-Integrity or Peer-Congestion
descriptor sets pname_count to 256, blocking subsequent FPIN,
RSCN, and multipath-health processing on that HBA function
until reboot.
Switch i to u32 in both walkers and clamp pname_count against
the per-descriptor available bytes (desc_len minus the offset
of pname_list[]) before the loop. This refuses a malformed
descriptor that claims more entries than its TLV body can hold,
and is the minimum scope that covers both walkers.
I reproduced this on a KASAN-enabled x86_64 mainline kernel at
f0db6484b6ea via an out-of-tree module that allocates a real
Scsi_Host through the FC transport API (fc_attach_transport(),
scsi_host_alloc(), scsi_add_host()), builds a 2096-byte FPIN
payload with pname_count == 256, and calls the exported
fc_host_fpin_rcv() from a kernel thread. Without the patch, a
bounded watchdog timer fires three seconds into the call with
the kthread still inside fc_find_rport_by_wwpn() (offset
0x14b/0x2b0 in [scsi_transport_fc]) under
fc_host_fpin_rcv()+0x4e8. The patched-kernel A/B run, the
legitimate pname_count <= 4 regression run, and the checkpatch
and get_maintainer outputs are pending the final patch draft
and will be captured before send. A reproducer is available
off-list on request.
Two in-tree forwarders reach this code: lpfc passes the full
hardware-reported payload_len with no software clamp
(drivers/scsi/lpfc/lpfc_els.c:10830); qla2xxx clamps
total_bytes to sizeof(item->iocb.iocb) == 64 in
qla27xx_copy_fpin_pkt (drivers/scsi/qla2xxx/qla_isr.c
:1170-1171), so qla2xxx delivers at most 64 bytes of FPIN
payload, but the walker bug fires regardless because the inner
walker never consults desc_len before reading pname_list[i].
qedf, bnx2fc, sw-fcoe and bfa do not forward FPIN ELS to
fc_host_fpin_rcv() in mainline.
The in-flight v10 "fc_els: use 'union fc_tlv_desc'" series from
Hannes Reinecke and John Meneghini (linux-scsi mid
20250926000200.837025-2-jmeneghi@redhat.com) touches the same
file but only changes the descriptor pointer type; the u8 i
counter is preserved verbatim in v10. Can rebase on top of
that series if it lands first, or land this fix standalone
against current mainline.
Fixes: 3dcfe0de5a97 ("scsi: fc: Parse FPIN packets and update statistics")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH] scsi: scsi_transport_fc: widen FPIN pname walker counter to u32
2026-05-18 14:09 [DRAFT][PATCH] scsi: scsi_transport_fc: widen FPIN pname walker counter to u32 Michael Bommarito
@ 2026-05-18 14:09 ` Michael Bommarito
2026-05-19 7:21 ` Christoph Hellwig
2026-05-18 14:37 ` [PATCH v2] " Michael Bommarito
1 sibling, 1 reply; 10+ messages in thread
From: Michael Bommarito @ 2026-05-18 14:09 UTC (permalink / raw)
To: Martin K . Petersen, James E . J . Bottomley
Cc: Nilesh Javali, Himanshu Madhani, Shyam Sundar, James Smart,
Hannes Reinecke, John Meneghini, Bryan Gurney, Justin Tee,
Christoph Hellwig, Keith Busch, Kees Cook, linux-scsi, linux-nvme,
linux-hardening, linux-kernel
drivers/scsi/scsi_transport_fc.c::fc_fpin_li_stats_update() and
fc_fpin_peer_congn_stats_update() walked the on-wire
pname_list[] with a u8 loop counter against the 32-bit __be32
pname_count field, and never bounded pname_count by the
descriptor body the TLV walker already validated.
A fabric-side FPIN sender (the elected fabric controller, a
co-tenant N_Port that spoofs S_ID 0xFFFFFD after FLOGI, or a
compromised switch supervisor) could hang the FC ELS receive
thread of an lpfc or qla2xxx initiator indefinitely by
emitting one FPIN ELS frame whose Link-Integrity or
Peer-Congestion descriptor sets pname_count to 256, because
the u8 counter rolls over and the loop condition i <
pname_count stays true for every value i can take.
Widen the loop counter to u32 in both walkers and clamp
pname_count against the per-descriptor available bytes
(desc_len minus the fixed body that precedes pname_list[])
before iterating. A malformed descriptor that claims more
entries than its TLV body can hold is rejected by the clamp.
Fixes: 3dcfe0de5a97 ("scsi: fc: Parse FPIN packets and update statistics")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
---
drivers/scsi/scsi_transport_fc.c | 81 ++++++++++++++++++++------------
1 file changed, 51 insertions(+), 30 deletions(-)
diff --git a/drivers/scsi/scsi_transport_fc.c b/drivers/scsi/scsi_transport_fc.c
index dce95e361daf0..cef0c85693fd4 100644
--- a/drivers/scsi/scsi_transport_fc.c
+++ b/drivers/scsi/scsi_transport_fc.c
@@ -747,7 +747,7 @@ fc_cn_stats_update(u16 event_type, struct fc_fpin_stats *stats)
static void
fc_fpin_li_stats_update(struct Scsi_Host *shost, struct fc_tlv_desc *tlv)
{
- u8 i;
+ u32 i, pname_count, max_count, desc_len;
struct fc_rport *rport = NULL;
struct fc_rport *attach_rport = NULL;
struct fc_host_attrs *fc_host = shost_to_fc_host(shost);
@@ -764,20 +764,34 @@ fc_fpin_li_stats_update(struct Scsi_Host *shost, struct fc_tlv_desc *tlv)
fc_li_stats_update(event_type, &attach_rport->fpin_stats);
}
- if (be32_to_cpu(li_desc->pname_count) > 0) {
- for (i = 0;
- i < be32_to_cpu(li_desc->pname_count);
- i++) {
- wwpn = be64_to_cpu(li_desc->pname_list[i]);
- rport = fc_find_rport_by_wwpn(shost, wwpn);
- if (rport &&
- (rport->roles & FC_PORT_ROLE_FCP_TARGET ||
- rport->roles & FC_PORT_ROLE_NVME_TARGET)) {
- if (rport == attach_rport)
- continue;
- fc_li_stats_update(event_type,
- &rport->fpin_stats);
- }
+ /*
+ * Clamp pname_count to the number of pname_list entries
+ * the descriptor body can actually hold. desc_len
+ * excludes the desc_tag/desc_len header (FC_TLV_DESC_HDR_SZ),
+ * so the bytes available for pname_list[] are
+ * desc_len - sizeof(fixed fields before pname_list[]).
+ */
+ pname_count = be32_to_cpu(li_desc->pname_count);
+ desc_len = be32_to_cpu(li_desc->desc_len);
+ if (desc_len < sizeof(*li_desc) - FC_TLV_DESC_HDR_SZ)
+ max_count = 0;
+ else
+ max_count = (desc_len -
+ (sizeof(*li_desc) - FC_TLV_DESC_HDR_SZ)) /
+ sizeof(li_desc->pname_list[0]);
+ if (pname_count > max_count)
+ pname_count = max_count;
+
+ for (i = 0; i < pname_count; i++) {
+ wwpn = be64_to_cpu(li_desc->pname_list[i]);
+ rport = fc_find_rport_by_wwpn(shost, wwpn);
+ if (rport &&
+ (rport->roles & FC_PORT_ROLE_FCP_TARGET ||
+ rport->roles & FC_PORT_ROLE_NVME_TARGET)) {
+ if (rport == attach_rport)
+ continue;
+ fc_li_stats_update(event_type,
+ &rport->fpin_stats);
}
}
@@ -827,7 +841,7 @@ static void
fc_fpin_peer_congn_stats_update(struct Scsi_Host *shost,
struct fc_tlv_desc *tlv)
{
- u8 i;
+ u32 i, pname_count, max_count, desc_len;
struct fc_rport *rport = NULL;
struct fc_rport *attach_rport = NULL;
struct fc_fn_peer_congn_desc *pc_desc =
@@ -844,20 +858,27 @@ fc_fpin_peer_congn_stats_update(struct Scsi_Host *shost,
fc_cn_stats_update(event_type, &attach_rport->fpin_stats);
}
- if (be32_to_cpu(pc_desc->pname_count) > 0) {
- for (i = 0;
- i < be32_to_cpu(pc_desc->pname_count);
- i++) {
- wwpn = be64_to_cpu(pc_desc->pname_list[i]);
- rport = fc_find_rport_by_wwpn(shost, wwpn);
- if (rport &&
- (rport->roles & FC_PORT_ROLE_FCP_TARGET ||
- rport->roles & FC_PORT_ROLE_NVME_TARGET)) {
- if (rport == attach_rport)
- continue;
- fc_cn_stats_update(event_type,
- &rport->fpin_stats);
- }
+ pname_count = be32_to_cpu(pc_desc->pname_count);
+ desc_len = be32_to_cpu(pc_desc->desc_len);
+ if (desc_len < sizeof(*pc_desc) - FC_TLV_DESC_HDR_SZ)
+ max_count = 0;
+ else
+ max_count = (desc_len -
+ (sizeof(*pc_desc) - FC_TLV_DESC_HDR_SZ)) /
+ sizeof(pc_desc->pname_list[0]);
+ if (pname_count > max_count)
+ pname_count = max_count;
+
+ for (i = 0; i < pname_count; i++) {
+ wwpn = be64_to_cpu(pc_desc->pname_list[i]);
+ rport = fc_find_rport_by_wwpn(shost, wwpn);
+ if (rport &&
+ (rport->roles & FC_PORT_ROLE_FCP_TARGET ||
+ rport->roles & FC_PORT_ROLE_NVME_TARGET)) {
+ if (rport == attach_rport)
+ continue;
+ fc_cn_stats_update(event_type,
+ &rport->fpin_stats);
}
}
}
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v2] scsi: scsi_transport_fc: widen FPIN pname walker counter to u32
2026-05-18 14:09 [DRAFT][PATCH] scsi: scsi_transport_fc: widen FPIN pname walker counter to u32 Michael Bommarito
2026-05-18 14:09 ` [PATCH] " Michael Bommarito
@ 2026-05-18 14:37 ` Michael Bommarito
2026-05-19 7:22 ` Christoph Hellwig
2026-05-19 19:06 ` [PATCH v3] " Michael Bommarito
1 sibling, 2 replies; 10+ messages in thread
From: Michael Bommarito @ 2026-05-18 14:37 UTC (permalink / raw)
To: Martin K . Petersen, James E . J . Bottomley
Cc: Nilesh Javali, Himanshu Madhani, Shyam Sundar, James Smart,
Hannes Reinecke, John Meneghini, Bryan Gurney, Justin Tee,
Christoph Hellwig, Keith Busch, Kees Cook, linux-scsi, linux-nvme,
linux-hardening, linux-kernel
drivers/scsi/scsi_transport_fc.c::fc_fpin_li_stats_update() and
fc_fpin_peer_congn_stats_update() walked the on-wire
pname_list[] with a u8 loop counter against the 32-bit __be32
pname_count field, and never bounded pname_count by the
descriptor body the TLV walker already validated.
A fabric-side FPIN sender (the elected fabric controller, a
co-tenant N_Port that spoofs S_ID 0xFFFFFD after FLOGI, or a
compromised switch supervisor) could hang the FC ELS receive
thread of an lpfc or qla2xxx initiator indefinitely by
emitting one FPIN ELS frame whose Link-Integrity or
Peer-Congestion descriptor sets pname_count to 256, because
the u8 counter rolls over and the loop condition i <
pname_count stays true for every value i can take.
Widen the loop counter to u32 in both walkers and clamp
pname_count against the per-descriptor available bytes
(desc_len minus the fixed body that precedes pname_list[])
before iterating. A malformed descriptor that claims more
entries than its TLV body can hold is rejected by the clamp.
Fixes: 3dcfe0de5a97 ("scsi: fc: Parse FPIN packets and update statistics")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
---
v2: drop the redundant cover letter shipped with v1. A
single-patch send should not carry a cover; the lead
belongs in the commit message, which the patch below
already has. The v1 cover also carried stale drafting-
time envelope markers that should have been stripped
before send. Apologies for the noise; please ignore the
v1 cover at
https://lore.kernel.org/linux-hardening/20260518140945.2751273-1-michael.bommarito@gmail.com/
The patch hunks below are byte-identical to v1's 0001.
drivers/scsi/scsi_transport_fc.c | 81 ++++++++++++++++++++------------
1 file changed, 51 insertions(+), 30 deletions(-)
diff --git a/drivers/scsi/scsi_transport_fc.c b/drivers/scsi/scsi_transport_fc.c
index dce95e361daf0..cef0c85693fd4 100644
--- a/drivers/scsi/scsi_transport_fc.c
+++ b/drivers/scsi/scsi_transport_fc.c
@@ -747,7 +747,7 @@ fc_cn_stats_update(u16 event_type, struct fc_fpin_stats *stats)
static void
fc_fpin_li_stats_update(struct Scsi_Host *shost, struct fc_tlv_desc *tlv)
{
- u8 i;
+ u32 i, pname_count, max_count, desc_len;
struct fc_rport *rport = NULL;
struct fc_rport *attach_rport = NULL;
struct fc_host_attrs *fc_host = shost_to_fc_host(shost);
@@ -764,20 +764,34 @@ fc_fpin_li_stats_update(struct Scsi_Host *shost, struct fc_tlv_desc *tlv)
fc_li_stats_update(event_type, &attach_rport->fpin_stats);
}
- if (be32_to_cpu(li_desc->pname_count) > 0) {
- for (i = 0;
- i < be32_to_cpu(li_desc->pname_count);
- i++) {
- wwpn = be64_to_cpu(li_desc->pname_list[i]);
- rport = fc_find_rport_by_wwpn(shost, wwpn);
- if (rport &&
- (rport->roles & FC_PORT_ROLE_FCP_TARGET ||
- rport->roles & FC_PORT_ROLE_NVME_TARGET)) {
- if (rport == attach_rport)
- continue;
- fc_li_stats_update(event_type,
- &rport->fpin_stats);
- }
+ /*
+ * Clamp pname_count to the number of pname_list entries
+ * the descriptor body can actually hold. desc_len
+ * excludes the desc_tag/desc_len header (FC_TLV_DESC_HDR_SZ),
+ * so the bytes available for pname_list[] are
+ * desc_len - sizeof(fixed fields before pname_list[]).
+ */
+ pname_count = be32_to_cpu(li_desc->pname_count);
+ desc_len = be32_to_cpu(li_desc->desc_len);
+ if (desc_len < sizeof(*li_desc) - FC_TLV_DESC_HDR_SZ)
+ max_count = 0;
+ else
+ max_count = (desc_len -
+ (sizeof(*li_desc) - FC_TLV_DESC_HDR_SZ)) /
+ sizeof(li_desc->pname_list[0]);
+ if (pname_count > max_count)
+ pname_count = max_count;
+
+ for (i = 0; i < pname_count; i++) {
+ wwpn = be64_to_cpu(li_desc->pname_list[i]);
+ rport = fc_find_rport_by_wwpn(shost, wwpn);
+ if (rport &&
+ (rport->roles & FC_PORT_ROLE_FCP_TARGET ||
+ rport->roles & FC_PORT_ROLE_NVME_TARGET)) {
+ if (rport == attach_rport)
+ continue;
+ fc_li_stats_update(event_type,
+ &rport->fpin_stats);
}
}
@@ -827,7 +841,7 @@ static void
fc_fpin_peer_congn_stats_update(struct Scsi_Host *shost,
struct fc_tlv_desc *tlv)
{
- u8 i;
+ u32 i, pname_count, max_count, desc_len;
struct fc_rport *rport = NULL;
struct fc_rport *attach_rport = NULL;
struct fc_fn_peer_congn_desc *pc_desc =
@@ -844,20 +858,27 @@ fc_fpin_peer_congn_stats_update(struct Scsi_Host *shost,
fc_cn_stats_update(event_type, &attach_rport->fpin_stats);
}
- if (be32_to_cpu(pc_desc->pname_count) > 0) {
- for (i = 0;
- i < be32_to_cpu(pc_desc->pname_count);
- i++) {
- wwpn = be64_to_cpu(pc_desc->pname_list[i]);
- rport = fc_find_rport_by_wwpn(shost, wwpn);
- if (rport &&
- (rport->roles & FC_PORT_ROLE_FCP_TARGET ||
- rport->roles & FC_PORT_ROLE_NVME_TARGET)) {
- if (rport == attach_rport)
- continue;
- fc_cn_stats_update(event_type,
- &rport->fpin_stats);
- }
+ pname_count = be32_to_cpu(pc_desc->pname_count);
+ desc_len = be32_to_cpu(pc_desc->desc_len);
+ if (desc_len < sizeof(*pc_desc) - FC_TLV_DESC_HDR_SZ)
+ max_count = 0;
+ else
+ max_count = (desc_len -
+ (sizeof(*pc_desc) - FC_TLV_DESC_HDR_SZ)) /
+ sizeof(pc_desc->pname_list[0]);
+ if (pname_count > max_count)
+ pname_count = max_count;
+
+ for (i = 0; i < pname_count; i++) {
+ wwpn = be64_to_cpu(pc_desc->pname_list[i]);
+ rport = fc_find_rport_by_wwpn(shost, wwpn);
+ if (rport &&
+ (rport->roles & FC_PORT_ROLE_FCP_TARGET ||
+ rport->roles & FC_PORT_ROLE_NVME_TARGET)) {
+ if (rport == attach_rport)
+ continue;
+ fc_cn_stats_update(event_type,
+ &rport->fpin_stats);
}
}
}
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH] scsi: scsi_transport_fc: widen FPIN pname walker counter to u32
2026-05-18 14:09 ` [PATCH] " Michael Bommarito
@ 2026-05-19 7:21 ` Christoph Hellwig
0 siblings, 0 replies; 10+ messages in thread
From: Christoph Hellwig @ 2026-05-19 7:21 UTC (permalink / raw)
To: Michael Bommarito
Cc: Martin K . Petersen, James E . J . Bottomley, Nilesh Javali,
Himanshu Madhani, Shyam Sundar, James Smart, Hannes Reinecke,
John Meneghini, Bryan Gurney, Justin Tee, Christoph Hellwig,
Keith Busch, Kees Cook, linux-scsi, linux-nvme, linux-hardening,
linux-kernel
> + if (pname_count > max_count)
> + pname_count = max_count;
Use min/min_t here?
> + for (i = 0; i < pname_count; i++) {
> + wwpn = be64_to_cpu(li_desc->pname_list[i]);
> + rport = fc_find_rport_by_wwpn(shost, wwpn);
> + if (rport &&
> + (rport->roles & FC_PORT_ROLE_FCP_TARGET ||
> + rport->roles & FC_PORT_ROLE_NVME_TARGET)) {
> + if (rport == attach_rport)
> + continue;
> + fc_li_stats_update(event_type,
> + &rport->fpin_stats);
> }
> + pname_count = be32_to_cpu(pc_desc->pname_count);
> + desc_len = be32_to_cpu(pc_desc->desc_len);
> + if (desc_len < sizeof(*pc_desc) - FC_TLV_DESC_HDR_SZ)
> + max_count = 0;
> + else
> + max_count = (desc_len -
> + (sizeof(*pc_desc) - FC_TLV_DESC_HDR_SZ)) /
> + sizeof(pc_desc->pname_list[0]);
> + if (pname_count > max_count)
> + pname_count = max_count;
> +
> + for (i = 0; i < pname_count; i++) {
> + wwpn = be64_to_cpu(pc_desc->pname_list[i]);
> + rport = fc_find_rport_by_wwpn(shost, wwpn);
> + if (rport &&
> + (rport->roles & FC_PORT_ROLE_FCP_TARGET ||
> + rport->roles & FC_PORT_ROLE_NVME_TARGET)) {
> + if (rport == attach_rport)
> + continue;
> + fc_cn_stats_update(event_type,
> + &rport->fpin_stats);
> }
> }
Am I missing something or is this code entirely duplicate except
for the different stats updates helper? Maybe use the chance
to factor it into a common helper in a follow on patch?
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2] scsi: scsi_transport_fc: widen FPIN pname walker counter to u32
2026-05-18 14:37 ` [PATCH v2] " Michael Bommarito
@ 2026-05-19 7:22 ` Christoph Hellwig
2026-05-19 19:06 ` [PATCH v3] " Michael Bommarito
1 sibling, 0 replies; 10+ messages in thread
From: Christoph Hellwig @ 2026-05-19 7:22 UTC (permalink / raw)
To: Michael Bommarito
Cc: Martin K . Petersen, James E . J . Bottomley, Nilesh Javali,
Himanshu Madhani, Shyam Sundar, James Smart, Hannes Reinecke,
John Meneghini, Bryan Gurney, Justin Tee, Christoph Hellwig,
Keith Busch, Kees Cook, linux-scsi, linux-nvme, linux-hardening,
linux-kernel
> v2: drop the redundant cover letter shipped with v1. A
> single-patch send should not carry a cover; the lead
> belongs in the commit message, which the patch below
> already has. The v1 cover also carried stale drafting-
> time envelope markers that should have been stripped
> before send. Apologies for the noise; please ignore the
> v1 cover at
> https://lore.kernel.org/linux-hardening/20260518140945.2751273-1-michael.bommarito@gmail.com/
> The patch hunks below are byte-identical to v1's 0001.
Cover letters for single patches are not required, but totally fine if
they add value. I don't think you needed one here, but it also wasn't
actually harmful.
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v3] scsi: scsi_transport_fc: widen FPIN pname walker counter to u32
2026-05-18 14:37 ` [PATCH v2] " Michael Bommarito
2026-05-19 7:22 ` Christoph Hellwig
@ 2026-05-19 19:06 ` Michael Bommarito
2026-05-20 12:58 ` David Laight
2026-05-20 13:30 ` [PATCH v4] " Michael Bommarito
1 sibling, 2 replies; 10+ messages in thread
From: Michael Bommarito @ 2026-05-19 19:06 UTC (permalink / raw)
To: Martin K. Petersen, James E.J. Bottomley
Cc: Nilesh Javali, Himanshu Madhani, Shyam Sundar, James Smart,
Hannes Reinecke, John Meneghini, Bryan Gurney, Justin Tee,
Christoph Hellwig, Keith Busch, Kees Cook, linux-scsi, linux-nvme,
linux-hardening, linux-kernel, stable
An adjacent Fibre Channel fabric actor that can deliver an FPIN ELS
frame to an lpfc or qla2xxx Linux initiator can trigger a non-return
in the generic FC transport. This is not a local userspace or IP
network path; the attacker must be able to inject fabric traffic, for
example as a compromised switch or fabric controller, or as a same-zone
N_Port on a fabric that permits source spoofing.
The Link-Integrity and Peer-Congestion FPIN walkers used a u8 loop
counter against the 32-bit on-wire pname_count field, and did not bound
pname_count by the descriptor body already validated by the TLV walker.
A pname_count of 256 therefore wraps the counter and keeps the loop
condition true indefinitely.
Factor the shared pname_list[] walk into one helper, widen the counter
to u32, and clamp pname_count against the entries that fit in the
descriptor body before iterating.
Fixes: 3dcfe0de5a97 ("scsi: fc: Parse FPIN packets and update statistics")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
---
Changes in v3:
- State the fabric-adjacent threat model explicitly in the commit
message and clarify that this is not local userspace or IP-network
reachable.
- Use min_t(u32, ...) for the pname_count clamp, as Christoph suggested.
- Use FC_TLV_DESC_LENGTH_FROM_SZ() instead of open-coding the descriptor
body length calculation.
- Factor the duplicate LI and peer-congestion pname walker into a common
helper while preserving the LI-only host-stat update.
Changes in v2:
- Drop the redundant cover letter shipped with v1. A single-patch send
does not need one, and the v1 cover carried stale draft markers.
drivers/scsi/scsi_transport_fc.c | 77 +++++++++++++++++---------------
1 file changed, 41 insertions(+), 36 deletions(-)
diff --git a/drivers/scsi/scsi_transport_fc.c b/drivers/scsi/scsi_transport_fc.c
index dce95e361daf0..0684d8c69c3c6 100644
--- a/drivers/scsi/scsi_transport_fc.c
+++ b/drivers/scsi/scsi_transport_fc.c
@@ -737,6 +737,37 @@ fc_cn_stats_update(u16 event_type, struct fc_fpin_stats *stats)
}
}
+static void
+fc_fpin_pname_stats_update(struct Scsi_Host *shost,
+ struct fc_rport *attach_rport, u16 event_type,
+ u32 desc_len, u32 fixed_len, u32 pname_count,
+ __be64 *pname_list,
+ void (*stats_update)(u16 event_type,
+ struct fc_fpin_stats *stats))
+{
+ u32 i, max_count;
+ struct fc_rport *rport;
+ u64 wwpn;
+
+ if (desc_len < fixed_len)
+ max_count = 0;
+ else
+ max_count = (desc_len - fixed_len) / sizeof(pname_list[0]);
+ pname_count = min_t(u32, pname_count, max_count);
+
+ for (i = 0; i < pname_count; i++) {
+ wwpn = be64_to_cpu(pname_list[i]);
+ rport = fc_find_rport_by_wwpn(shost, wwpn);
+ if (rport &&
+ (rport->roles & FC_PORT_ROLE_FCP_TARGET ||
+ rport->roles & FC_PORT_ROLE_NVME_TARGET)) {
+ if (rport == attach_rport)
+ continue;
+ stats_update(event_type, &rport->fpin_stats);
+ }
+ }
+}
+
/*
* fc_fpin_li_stats_update - routine to update Link Integrity
* event statistics.
@@ -747,13 +778,11 @@ fc_cn_stats_update(u16 event_type, struct fc_fpin_stats *stats)
static void
fc_fpin_li_stats_update(struct Scsi_Host *shost, struct fc_tlv_desc *tlv)
{
- u8 i;
struct fc_rport *rport = NULL;
struct fc_rport *attach_rport = NULL;
struct fc_host_attrs *fc_host = shost_to_fc_host(shost);
struct fc_fn_li_desc *li_desc = (struct fc_fn_li_desc *)tlv;
u16 event_type = be16_to_cpu(li_desc->event_type);
- u64 wwpn;
rport = fc_find_rport_by_wwpn(shost,
be64_to_cpu(li_desc->attached_wwpn));
@@ -764,22 +793,11 @@ fc_fpin_li_stats_update(struct Scsi_Host *shost, struct fc_tlv_desc *tlv)
fc_li_stats_update(event_type, &attach_rport->fpin_stats);
}
- if (be32_to_cpu(li_desc->pname_count) > 0) {
- for (i = 0;
- i < be32_to_cpu(li_desc->pname_count);
- i++) {
- wwpn = be64_to_cpu(li_desc->pname_list[i]);
- rport = fc_find_rport_by_wwpn(shost, wwpn);
- if (rport &&
- (rport->roles & FC_PORT_ROLE_FCP_TARGET ||
- rport->roles & FC_PORT_ROLE_NVME_TARGET)) {
- if (rport == attach_rport)
- continue;
- fc_li_stats_update(event_type,
- &rport->fpin_stats);
- }
- }
- }
+ fc_fpin_pname_stats_update(shost, attach_rport, event_type,
+ be32_to_cpu(li_desc->desc_len),
+ FC_TLV_DESC_LENGTH_FROM_SZ(*li_desc),
+ be32_to_cpu(li_desc->pname_count),
+ li_desc->pname_list, fc_li_stats_update);
if (fc_host->port_name == be64_to_cpu(li_desc->attached_wwpn))
fc_li_stats_update(event_type, &fc_host->fpin_stats);
@@ -827,13 +845,11 @@ static void
fc_fpin_peer_congn_stats_update(struct Scsi_Host *shost,
struct fc_tlv_desc *tlv)
{
- u8 i;
struct fc_rport *rport = NULL;
struct fc_rport *attach_rport = NULL;
struct fc_fn_peer_congn_desc *pc_desc =
(struct fc_fn_peer_congn_desc *)tlv;
u16 event_type = be16_to_cpu(pc_desc->event_type);
- u64 wwpn;
rport = fc_find_rport_by_wwpn(shost,
be64_to_cpu(pc_desc->attached_wwpn));
@@ -844,22 +860,11 @@ fc_fpin_peer_congn_stats_update(struct Scsi_Host *shost,
fc_cn_stats_update(event_type, &attach_rport->fpin_stats);
}
- if (be32_to_cpu(pc_desc->pname_count) > 0) {
- for (i = 0;
- i < be32_to_cpu(pc_desc->pname_count);
- i++) {
- wwpn = be64_to_cpu(pc_desc->pname_list[i]);
- rport = fc_find_rport_by_wwpn(shost, wwpn);
- if (rport &&
- (rport->roles & FC_PORT_ROLE_FCP_TARGET ||
- rport->roles & FC_PORT_ROLE_NVME_TARGET)) {
- if (rport == attach_rport)
- continue;
- fc_cn_stats_update(event_type,
- &rport->fpin_stats);
- }
- }
- }
+ fc_fpin_pname_stats_update(shost, attach_rport, event_type,
+ be32_to_cpu(pc_desc->desc_len),
+ FC_TLV_DESC_LENGTH_FROM_SZ(*pc_desc),
+ be32_to_cpu(pc_desc->pname_count),
+ pc_desc->pname_list, fc_cn_stats_update);
}
/*
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v3] scsi: scsi_transport_fc: widen FPIN pname walker counter to u32
2026-05-19 19:06 ` [PATCH v3] " Michael Bommarito
@ 2026-05-20 12:58 ` David Laight
2026-05-20 13:30 ` [PATCH v4] " Michael Bommarito
1 sibling, 0 replies; 10+ messages in thread
From: David Laight @ 2026-05-20 12:58 UTC (permalink / raw)
To: Michael Bommarito
Cc: Martin K. Petersen, James E.J. Bottomley, Nilesh Javali,
Himanshu Madhani, Shyam Sundar, James Smart, Hannes Reinecke,
John Meneghini, Bryan Gurney, Justin Tee, Christoph Hellwig,
Keith Busch, Kees Cook, linux-scsi, linux-nvme, linux-hardening,
linux-kernel, stable
On Tue, 19 May 2026 15:06:15 -0400
Michael Bommarito <michael.bommarito@gmail.com> wrote:
> An adjacent Fibre Channel fabric actor that can deliver an FPIN ELS
> frame to an lpfc or qla2xxx Linux initiator can trigger a non-return
> in the generic FC transport. This is not a local userspace or IP
> network path; the attacker must be able to inject fabric traffic, for
> example as a compromised switch or fabric controller, or as a same-zone
> N_Port on a fabric that permits source spoofing.
>
> The Link-Integrity and Peer-Congestion FPIN walkers used a u8 loop
> counter against the 32-bit on-wire pname_count field, and did not bound
> pname_count by the descriptor body already validated by the TLV walker.
> A pname_count of 256 therefore wraps the counter and keeps the loop
> condition true indefinitely.
>
> Factor the shared pname_list[] walk into one helper, widen the counter
> to u32, and clamp pname_count against the entries that fit in the
> descriptor body before iterating.
>
> Fixes: 3dcfe0de5a97 ("scsi: fc: Parse FPIN packets and update statistics")
> Cc: stable@vger.kernel.org
> Assisted-by: Claude:claude-opus-4-7
> Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
> ---
> Changes in v3:
> - State the fabric-adjacent threat model explicitly in the commit
> message and clarify that this is not local userspace or IP-network
> reachable.
> - Use min_t(u32, ...) for the pname_count clamp, as Christoph suggested.
> - Use FC_TLV_DESC_LENGTH_FROM_SZ() instead of open-coding the descriptor
> body length calculation.
> - Factor the duplicate LI and peer-congestion pname walker into a common
> helper while preserving the LI-only host-stat update.
>
> Changes in v2:
> - Drop the redundant cover letter shipped with v1. A single-patch send
> does not need one, and the v1 cover carried stale draft markers.
>
> drivers/scsi/scsi_transport_fc.c | 77 +++++++++++++++++---------------
> 1 file changed, 41 insertions(+), 36 deletions(-)
>
> diff --git a/drivers/scsi/scsi_transport_fc.c b/drivers/scsi/scsi_transport_fc.c
> index dce95e361daf0..0684d8c69c3c6 100644
> --- a/drivers/scsi/scsi_transport_fc.c
> +++ b/drivers/scsi/scsi_transport_fc.c
> @@ -737,6 +737,37 @@ fc_cn_stats_update(u16 event_type, struct fc_fpin_stats *stats)
> }
> }
>
> +static void
> +fc_fpin_pname_stats_update(struct Scsi_Host *shost,
> + struct fc_rport *attach_rport, u16 event_type,
> + u32 desc_len, u32 fixed_len, u32 pname_count,
> + __be64 *pname_list,
> + void (*stats_update)(u16 event_type,
> + struct fc_fpin_stats *stats))
> +{
> + u32 i, max_count;
> + struct fc_rport *rport;
> + u64 wwpn;
> +
> + if (desc_len < fixed_len)
> + max_count = 0;
> + else
> + max_count = (desc_len - fixed_len) / sizeof(pname_list[0]);
> + pname_count = min_t(u32, pname_count, max_count);
No min_t() please, everything is unsigned so min() in fine.
The above might even more readable without the extra variable:
if (desc_len < fixed_len)
pname_count = min(pname_count, (desc_len - fixed_len) / sizeof(pname_list[0]));
If you think the line is too long s/pname_count/count/g
-- David
> +
> + for (i = 0; i < pname_count; i++) {
> + wwpn = be64_to_cpu(pname_list[i]);
> + rport = fc_find_rport_by_wwpn(shost, wwpn);
> + if (rport &&
> + (rport->roles & FC_PORT_ROLE_FCP_TARGET ||
> + rport->roles & FC_PORT_ROLE_NVME_TARGET)) {
> + if (rport == attach_rport)
> + continue;
> + stats_update(event_type, &rport->fpin_stats);
> + }
> + }
> +}
> +
> /*
> * fc_fpin_li_stats_update - routine to update Link Integrity
> * event statistics.
> @@ -747,13 +778,11 @@ fc_cn_stats_update(u16 event_type, struct fc_fpin_stats *stats)
> static void
> fc_fpin_li_stats_update(struct Scsi_Host *shost, struct fc_tlv_desc *tlv)
> {
> - u8 i;
> struct fc_rport *rport = NULL;
> struct fc_rport *attach_rport = NULL;
> struct fc_host_attrs *fc_host = shost_to_fc_host(shost);
> struct fc_fn_li_desc *li_desc = (struct fc_fn_li_desc *)tlv;
> u16 event_type = be16_to_cpu(li_desc->event_type);
> - u64 wwpn;
>
> rport = fc_find_rport_by_wwpn(shost,
> be64_to_cpu(li_desc->attached_wwpn));
> @@ -764,22 +793,11 @@ fc_fpin_li_stats_update(struct Scsi_Host *shost, struct fc_tlv_desc *tlv)
> fc_li_stats_update(event_type, &attach_rport->fpin_stats);
> }
>
> - if (be32_to_cpu(li_desc->pname_count) > 0) {
> - for (i = 0;
> - i < be32_to_cpu(li_desc->pname_count);
> - i++) {
> - wwpn = be64_to_cpu(li_desc->pname_list[i]);
> - rport = fc_find_rport_by_wwpn(shost, wwpn);
> - if (rport &&
> - (rport->roles & FC_PORT_ROLE_FCP_TARGET ||
> - rport->roles & FC_PORT_ROLE_NVME_TARGET)) {
> - if (rport == attach_rport)
> - continue;
> - fc_li_stats_update(event_type,
> - &rport->fpin_stats);
> - }
> - }
> - }
> + fc_fpin_pname_stats_update(shost, attach_rport, event_type,
> + be32_to_cpu(li_desc->desc_len),
> + FC_TLV_DESC_LENGTH_FROM_SZ(*li_desc),
> + be32_to_cpu(li_desc->pname_count),
> + li_desc->pname_list, fc_li_stats_update);
>
> if (fc_host->port_name == be64_to_cpu(li_desc->attached_wwpn))
> fc_li_stats_update(event_type, &fc_host->fpin_stats);
> @@ -827,13 +845,11 @@ static void
> fc_fpin_peer_congn_stats_update(struct Scsi_Host *shost,
> struct fc_tlv_desc *tlv)
> {
> - u8 i;
> struct fc_rport *rport = NULL;
> struct fc_rport *attach_rport = NULL;
> struct fc_fn_peer_congn_desc *pc_desc =
> (struct fc_fn_peer_congn_desc *)tlv;
> u16 event_type = be16_to_cpu(pc_desc->event_type);
> - u64 wwpn;
>
> rport = fc_find_rport_by_wwpn(shost,
> be64_to_cpu(pc_desc->attached_wwpn));
> @@ -844,22 +860,11 @@ fc_fpin_peer_congn_stats_update(struct Scsi_Host *shost,
> fc_cn_stats_update(event_type, &attach_rport->fpin_stats);
> }
>
> - if (be32_to_cpu(pc_desc->pname_count) > 0) {
> - for (i = 0;
> - i < be32_to_cpu(pc_desc->pname_count);
> - i++) {
> - wwpn = be64_to_cpu(pc_desc->pname_list[i]);
> - rport = fc_find_rport_by_wwpn(shost, wwpn);
> - if (rport &&
> - (rport->roles & FC_PORT_ROLE_FCP_TARGET ||
> - rport->roles & FC_PORT_ROLE_NVME_TARGET)) {
> - if (rport == attach_rport)
> - continue;
> - fc_cn_stats_update(event_type,
> - &rport->fpin_stats);
> - }
> - }
> - }
> + fc_fpin_pname_stats_update(shost, attach_rport, event_type,
> + be32_to_cpu(pc_desc->desc_len),
> + FC_TLV_DESC_LENGTH_FROM_SZ(*pc_desc),
> + be32_to_cpu(pc_desc->pname_count),
> + pc_desc->pname_list, fc_cn_stats_update);
> }
>
> /*
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v4] scsi: scsi_transport_fc: widen FPIN pname walker counter to u32
2026-05-19 19:06 ` [PATCH v3] " Michael Bommarito
2026-05-20 12:58 ` David Laight
@ 2026-05-20 13:30 ` Michael Bommarito
2026-05-22 9:22 ` Christoph Hellwig
2026-05-22 10:11 ` John Garry
1 sibling, 2 replies; 10+ messages in thread
From: Michael Bommarito @ 2026-05-20 13:30 UTC (permalink / raw)
To: Martin K. Petersen, James E.J. Bottomley
Cc: Nilesh Javali, Himanshu Madhani, Shyam Sundar, James Smart,
Hannes Reinecke, John Meneghini, Bryan Gurney, Justin Tee,
Christoph Hellwig, David Laight, Keith Busch, Kees Cook,
linux-scsi, linux-nvme, linux-hardening, linux-kernel, stable
An adjacent Fibre Channel fabric actor that can deliver an FPIN ELS
frame to an lpfc or qla2xxx Linux initiator can trigger a non-return
in the generic FC transport. This is not a local userspace or IP
network path; the attacker must be able to inject fabric traffic, for
example as a compromised switch or fabric controller, or as a same-zone
N_Port on a fabric that permits source spoofing.
The Link-Integrity and Peer-Congestion FPIN walkers used a u8 loop
counter against the 32-bit on-wire pname_count field, and did not bound
pname_count by the descriptor body already validated by the TLV walker.
A pname_count of 256 therefore wraps the counter and keeps the loop
condition true indefinitely.
Factor the shared pname_list[] walk into one helper, widen the counter
to u32, and clamp pname_count against the entries that fit in the
descriptor body before iterating.
Fixes: 3dcfe0de5a97 ("scsi: fc: Parse FPIN packets and update statistics")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
---
Changes in v4:
- Use min() rather than min_t(u32, ...) for the pname_count clamp and
fold away the temporary max_count variable, as David Laight suggested.
Changes in v3:
- State the fabric-adjacent threat model explicitly in the commit
message and clarify that this is not local userspace or IP-network
reachable.
- Use min_t(u32, ...) for the pname_count clamp, as Christoph suggested.
- Use FC_TLV_DESC_LENGTH_FROM_SZ() instead of open-coding the descriptor
body length calculation.
- Factor the duplicate LI and peer-congestion pname walker into a common
helper while preserving the LI-only host-stat update.
Changes in v2:
- Drop the redundant cover letter shipped with v1. A single-patch send
does not need one, and the v1 cover carried stale draft markers.
drivers/scsi/scsi_transport_fc.c | 77 +++++++++++++++++---------------
1 file changed, 41 insertions(+), 36 deletions(-)
diff --git a/drivers/scsi/scsi_transport_fc.c b/drivers/scsi/scsi_transport_fc.c
index dce95e361daf0..173ed6373f04b 100644
--- a/drivers/scsi/scsi_transport_fc.c
+++ b/drivers/scsi/scsi_transport_fc.c
@@ -737,6 +737,37 @@ fc_cn_stats_update(u16 event_type, struct fc_fpin_stats *stats)
}
}
+static void
+fc_fpin_pname_stats_update(struct Scsi_Host *shost,
+ struct fc_rport *attach_rport, u16 event_type,
+ u32 desc_len, u32 fixed_len, u32 pname_count,
+ __be64 *pname_list,
+ void (*stats_update)(u16 event_type,
+ struct fc_fpin_stats *stats))
+{
+ u32 i;
+ struct fc_rport *rport;
+ u64 wwpn;
+
+ if (desc_len < fixed_len)
+ pname_count = 0;
+ else
+ pname_count = min(pname_count, (desc_len - fixed_len) /
+ sizeof(pname_list[0]));
+
+ for (i = 0; i < pname_count; i++) {
+ wwpn = be64_to_cpu(pname_list[i]);
+ rport = fc_find_rport_by_wwpn(shost, wwpn);
+ if (rport &&
+ (rport->roles & FC_PORT_ROLE_FCP_TARGET ||
+ rport->roles & FC_PORT_ROLE_NVME_TARGET)) {
+ if (rport == attach_rport)
+ continue;
+ stats_update(event_type, &rport->fpin_stats);
+ }
+ }
+}
+
/*
* fc_fpin_li_stats_update - routine to update Link Integrity
* event statistics.
@@ -747,13 +778,11 @@ fc_cn_stats_update(u16 event_type, struct fc_fpin_stats *stats)
static void
fc_fpin_li_stats_update(struct Scsi_Host *shost, struct fc_tlv_desc *tlv)
{
- u8 i;
struct fc_rport *rport = NULL;
struct fc_rport *attach_rport = NULL;
struct fc_host_attrs *fc_host = shost_to_fc_host(shost);
struct fc_fn_li_desc *li_desc = (struct fc_fn_li_desc *)tlv;
u16 event_type = be16_to_cpu(li_desc->event_type);
- u64 wwpn;
rport = fc_find_rport_by_wwpn(shost,
be64_to_cpu(li_desc->attached_wwpn));
@@ -764,22 +793,11 @@ fc_fpin_li_stats_update(struct Scsi_Host *shost, struct fc_tlv_desc *tlv)
fc_li_stats_update(event_type, &attach_rport->fpin_stats);
}
- if (be32_to_cpu(li_desc->pname_count) > 0) {
- for (i = 0;
- i < be32_to_cpu(li_desc->pname_count);
- i++) {
- wwpn = be64_to_cpu(li_desc->pname_list[i]);
- rport = fc_find_rport_by_wwpn(shost, wwpn);
- if (rport &&
- (rport->roles & FC_PORT_ROLE_FCP_TARGET ||
- rport->roles & FC_PORT_ROLE_NVME_TARGET)) {
- if (rport == attach_rport)
- continue;
- fc_li_stats_update(event_type,
- &rport->fpin_stats);
- }
- }
- }
+ fc_fpin_pname_stats_update(shost, attach_rport, event_type,
+ be32_to_cpu(li_desc->desc_len),
+ FC_TLV_DESC_LENGTH_FROM_SZ(*li_desc),
+ be32_to_cpu(li_desc->pname_count),
+ li_desc->pname_list, fc_li_stats_update);
if (fc_host->port_name == be64_to_cpu(li_desc->attached_wwpn))
fc_li_stats_update(event_type, &fc_host->fpin_stats);
@@ -827,13 +845,11 @@ static void
fc_fpin_peer_congn_stats_update(struct Scsi_Host *shost,
struct fc_tlv_desc *tlv)
{
- u8 i;
struct fc_rport *rport = NULL;
struct fc_rport *attach_rport = NULL;
struct fc_fn_peer_congn_desc *pc_desc =
(struct fc_fn_peer_congn_desc *)tlv;
u16 event_type = be16_to_cpu(pc_desc->event_type);
- u64 wwpn;
rport = fc_find_rport_by_wwpn(shost,
be64_to_cpu(pc_desc->attached_wwpn));
@@ -844,22 +860,11 @@ fc_fpin_peer_congn_stats_update(struct Scsi_Host *shost,
fc_cn_stats_update(event_type, &attach_rport->fpin_stats);
}
- if (be32_to_cpu(pc_desc->pname_count) > 0) {
- for (i = 0;
- i < be32_to_cpu(pc_desc->pname_count);
- i++) {
- wwpn = be64_to_cpu(pc_desc->pname_list[i]);
- rport = fc_find_rport_by_wwpn(shost, wwpn);
- if (rport &&
- (rport->roles & FC_PORT_ROLE_FCP_TARGET ||
- rport->roles & FC_PORT_ROLE_NVME_TARGET)) {
- if (rport == attach_rport)
- continue;
- fc_cn_stats_update(event_type,
- &rport->fpin_stats);
- }
- }
- }
+ fc_fpin_pname_stats_update(shost, attach_rport, event_type,
+ be32_to_cpu(pc_desc->desc_len),
+ FC_TLV_DESC_LENGTH_FROM_SZ(*pc_desc),
+ be32_to_cpu(pc_desc->pname_count),
+ pc_desc->pname_list, fc_cn_stats_update);
}
/*
--
2.53.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v4] scsi: scsi_transport_fc: widen FPIN pname walker counter to u32
2026-05-20 13:30 ` [PATCH v4] " Michael Bommarito
@ 2026-05-22 9:22 ` Christoph Hellwig
2026-05-22 10:11 ` John Garry
1 sibling, 0 replies; 10+ messages in thread
From: Christoph Hellwig @ 2026-05-22 9:22 UTC (permalink / raw)
To: Michael Bommarito
Cc: Martin K. Petersen, James E.J. Bottomley, Nilesh Javali,
Himanshu Madhani, Shyam Sundar, James Smart, Hannes Reinecke,
John Meneghini, Bryan Gurney, Justin Tee, Christoph Hellwig,
David Laight, Keith Busch, Kees Cook, linux-scsi, linux-nvme,
linux-hardening, linux-kernel, stable
Looks good:
Reviewed-by: Christoph Hellwig <hch@lst.de>
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v4] scsi: scsi_transport_fc: widen FPIN pname walker counter to u32
2026-05-20 13:30 ` [PATCH v4] " Michael Bommarito
2026-05-22 9:22 ` Christoph Hellwig
@ 2026-05-22 10:11 ` John Garry
1 sibling, 0 replies; 10+ messages in thread
From: John Garry @ 2026-05-22 10:11 UTC (permalink / raw)
To: Michael Bommarito, Martin K. Petersen, James E.J. Bottomley
Cc: Nilesh Javali, Himanshu Madhani, Shyam Sundar, James Smart,
Hannes Reinecke, John Meneghini, Bryan Gurney, Justin Tee,
Christoph Hellwig, David Laight, Keith Busch, Kees Cook,
linux-scsi, linux-nvme, linux-hardening, linux-kernel, stable
On 20/05/2026 14:30, Michael Bommarito wrote:
> An adjacent Fibre Channel fabric actor that can deliver an FPIN ELS
> frame to an lpfc or qla2xxx Linux initiator can trigger a non-return
> in the generic FC transport. This is not a local userspace or IP
> network path; the attacker must be able to inject fabric traffic, for
> example as a compromised switch or fabric controller, or as a same-zone
> N_Port on a fabric that permits source spoofing.
>
> The Link-Integrity and Peer-Congestion FPIN walkers used a u8 loop
> counter against the 32-bit on-wire pname_count field, and did not bound
> pname_count by the descriptor body already validated by the TLV walker.
> A pname_count of 256 therefore wraps the counter and keeps the loop
> condition true indefinitely.
>
> Factor the shared pname_list[] walk into one helper, widen the counter
> to u32, and clamp pname_count against the entries that fit in the
> descriptor body before iterating.
>
> Fixes: 3dcfe0de5a97 ("scsi: fc: Parse FPIN packets and update statistics")
> Cc:stable@vger.kernel.org
> Assisted-by: Claude:claude-opus-4-7
> Signed-off-by: Michael Bommarito<michael.bommarito@gmail.com>
Regardless of nitpick below:
Reviewed-by: John Garry <john.g.garry@oracle.com>
> ---
> Changes in v4:
> - Use min() rather than min_t(u32, ...) for the pname_count clamp and
> fold away the temporary max_count variable, as David Laight suggested.
>
> Changes in v3:
> - State the fabric-adjacent threat model explicitly in the commit
> message and clarify that this is not local userspace or IP-network
> reachable.
> - Use min_t(u32, ...) for the pname_count clamp, as Christoph suggested.
> - Use FC_TLV_DESC_LENGTH_FROM_SZ() instead of open-coding the descriptor
> body length calculation.
> - Factor the duplicate LI and peer-congestion pname walker into a common
> helper while preserving the LI-only host-stat update.
>
> Changes in v2:
> - Drop the redundant cover letter shipped with v1. A single-patch send
> does not need one, and the v1 cover carried stale draft markers.
>
> drivers/scsi/scsi_transport_fc.c | 77 +++++++++++++++++---------------
> 1 file changed, 41 insertions(+), 36 deletions(-)
>
> diff --git a/drivers/scsi/scsi_transport_fc.c b/drivers/scsi/scsi_transport_fc.c
> index dce95e361daf0..173ed6373f04b 100644
> --- a/drivers/scsi/scsi_transport_fc.c
> +++ b/drivers/scsi/scsi_transport_fc.c
> @@ -737,6 +737,37 @@ fc_cn_stats_update(u16 event_type, struct fc_fpin_stats *stats)
> }
> }
>
> +static void
> +fc_fpin_pname_stats_update(struct Scsi_Host *shost,
> + struct fc_rport *attach_rport, u16 event_type,
> + u32 desc_len, u32 fixed_len, u32 pname_count,
> + __be64 *pname_list,
> + void (*stats_update)(u16 event_type,
> + struct fc_fpin_stats *stats))
> +{
> + u32 i;
> + struct fc_rport *rport;
> + u64 wwpn;
> +
> + if (desc_len < fixed_len)
> + pname_count = 0;
you could return directly here to avoid extra indentation in else leg
> + else
> + pname_count = min(pname_count, (desc_len - fixed_len) /
> + sizeof(pname_list[0]));
> +
> + for (i = 0; i < pname_count; i++) {
> + wwpn = be64_to_cpu(pname_list[i]);
> + rport = fc_find_rport_by_wwpn(shost, wwpn);
> + if (rport &&
> + (rport->roles & FC_PORT_ROLE_FCP_TARGET ||
> + rport->roles & FC_PORT_ROLE_NVME_TARGET)) {
> + if (rport == attach_rport)
> + continue;
> + stats_update(event_type, &rport->fpin_stats);
> + }
> + }
> +}
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-05-22 10:11 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-18 14:09 [DRAFT][PATCH] scsi: scsi_transport_fc: widen FPIN pname walker counter to u32 Michael Bommarito
2026-05-18 14:09 ` [PATCH] " Michael Bommarito
2026-05-19 7:21 ` Christoph Hellwig
2026-05-18 14:37 ` [PATCH v2] " Michael Bommarito
2026-05-19 7:22 ` Christoph Hellwig
2026-05-19 19:06 ` [PATCH v3] " Michael Bommarito
2026-05-20 12:58 ` David Laight
2026-05-20 13:30 ` [PATCH v4] " Michael Bommarito
2026-05-22 9:22 ` Christoph Hellwig
2026-05-22 10:11 ` John Garry
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox