[DRAFT][PATCH] scsi: fcoe: reject FIP descriptors with zero fip_dlen in CVL walker

From: Michael Bommarito

Date: Mon May 18 2026 - 10:16:44 EST


drivers/scsi/fcoe/fcoe_ctlr.c::fcoe_ctlr_recv_clr_vlink() advances
the descriptor cursor by an attacker-supplied fip_dlen without
ever requiring dlen >= sizeof(struct fip_desc) in the default
branch. The named descriptor cases (FIP_DT_MAC, FIP_DT_NAME,
FIP_DT_VN_ID) check their per-type minimum lengths, but a
FIP_DT_NON_CRITICAL descriptor (fip_dtype >= 128, which the
standard requires receivers to silently ignore) skips that check
entirely.

The function is reached on every host that has selected an FCoE
Forwarder and logged into the fabric: any L2 peer on the FCoE
control VLAN that spoofs the elected FCF source MAC, or wins
FIP election, can deliver a CVL frame; FIP frames are not
cryptographically authenticated.

A FIP CVL frame with one FIP_DT_NON_CRITICAL descriptor whose
fip_dlen == 0 leaves desc and rlen unchanged after one loop
iteration, so the loop condition rlen >= sizeof(*desc) stays
true forever and fcoe_ctlr_recv_work never returns.

Impact: an unauthenticated L2 peer on the FCoE control VLAN can
hang fcoe_ctlr_recv_work on an fcoe, qedf, or bnx2fc initiator
indefinitely by emitting one FIP CVL frame whose single
descriptor has fip_dtype == FIP_DT_NON_CRITICAL and
fip_dlen == 0, blocking every subsequent FIP frame (FCF
keepalives, FLOGI, FDISC, real CVLs) on that controller and,
once the fabric ages out the session, leaving FCoE storage on
the affected initiator unavailable until reboot.

Reject the descriptor in the default branch when fip_dlen *
FIP_BPW is less than sizeof(struct fip_desc), i.e. when the
attacker-supplied length cannot even cover the descriptor
header. This is the same lower-bound that the named cases
already apply and is the minimum scope that closes the loop.

I reproduced this on a KASAN-enabled x86_64 mainline kernel at
f0db6484b6ea via an out-of-tree module that initialises a real
struct fcoe_ctlr through fcoe_ctlr_init(FIP_MODE_FABRIC),
installs a fcoe_fcf with fcf_mac and switch_name, sets
ctlr->state = FIP_ST_ENABLED and lp->port_id, then queues a
crafted FIP CVL skb whose single descriptor has fip_dtype ==
FIP_DT_NON_CRITICAL and fip_dlen == 0, and calls the exported
fcoe_ctlr_recv() from the init thread. Without the patch, a
bounded watchdog timer fires three seconds into the call with
the workqueue still inside fcoe_ctlr_recv_work (RIP
fcoe_ctlr_recv_work+0x1161/0x34d0 in [libfcoe]). The
patched-kernel A/B run, the legitimate non-critical-descriptor
regression (fip_dlen == 1) 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.

Three driver-private walkers in qedf and fnic share the same
algorithmic invariant (qedf_fcoe_process_vlan_resp at
drivers/scsi/qedf/qedf_fip.c:90, qedf CVL walker at
qedf_fip.c:233, fnic_fcoe_process_vlan_resp at
drivers/scsi/fnic/fip.c:117). Those are companion fixes for a
separate posting; they need the same dlen lower bound in their
own walker bodies and live-evidence on qedf or fnic hardware.

Fixes: 97c8389d54b9 ("[SCSI] fcoe, libfcoe: Add support for FIP. FCoE discovery and keep-alive.")
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Michael Bommarito <michael.bommarito@xxxxxxxxx>