isp_add_wwn_entry / isp_del_wwn_entry index FCPARAM(isp, chan) with an unchecked firmware-controlled channel (raw in_vpidx) β kernel heap OOB read/write up to ~17.8 MB past isp_param; isp_del_all_wwn_entries off-by-one admits chan == nchan
Summary
Unlike all four siblings in the same file, isp_add_wwn_entry and isp_del_wwn_entry dereference FCPARAM(isp, chan) with no chan < isp_nchan check, and isp_del_all_wwn_entries gates with 'chan > nchan' instead of >=. On MULTI-ID firmware the channel arrives as the raw 8-bit in_vpidx from the firmware-generated IN24XX_ELS_RCVD notify. isp_param is only nchan in {1,2} fcparams (~68 KB each), so vpidx up to 255 lands up to ~17.8 MB past the allocation (offsets demonstrated on the guest: vpidx 52 -> 3.55 MB, 255 -> 17.77 MB). Through the OOB fcp the code reads fcp->isp_tgt_map[nphdl] and writes it plus a portdb entry containing remote-controlled data (target_mode=1, handle=nphdl, portid=s_id, port_wwn=ini) and dispatches isp_async with the OOB-derived fields. Trust boundary identical to accepted DF-2471/DF-3582/3583; legit firmware reports only valid VP indices - hence Medium. NOT runnable on the audit guest (zero QLogic HBAs); source-certain with offset demonstrator.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3812 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.8 KB | β raw | |
| VERDICT.md | β | 4.4 KB | β raw | |
| verdict.json | β | 3.4 KB | view raw | |
| manifest.json | β | 1.0 KB | view raw | |
| vpidx_oob_arith.c | β | 2.7 KB | view raw | |
| build.sh | β | 134 B | view raw | |
| build.log | β | 1.3 KB | view raw | |
| env.txt | β | 730 B | view raw |
DF-3812 β isp_add_wwn_entry / isp_del_wwn_entry index FCPARAM(chan) without any channel bounds check; chan is a raw firmware byte
Files
sys/dev/disk/isp/isp_library.c:2391-2398(isp_add_wwn_entry)sys/dev/disk/isp/isp_library.c:2517-2529(isp_del_wwn_entry)sys/dev/disk/isp/isp_library.c:2560-2584(isp_del_all_wwn_entriesβ adjacent off-by-onechan > isp->isp_nchanat :2580 admitschan == nchan)
Build / Run / Expected
Two parts:
- Offset-arithmetic demonstrator (runs anywhere, executed on the audit
guest as an unprivileged user):
cc -Wall -o vpidx vpidx_oob_arith.c && ./vpidxExpected (seebuild.log, executed on DragonFly 6.5-DEVELOPMENT):sizeof(fcparam)replica lower bound = 69,696 B;isp_paramisnchan β {1,2}fcparams (isp_pci.c:824-832), so anin_vpidxof e.g. 52 indexes 3.55 MB past the block and 255 indexes 17.77 MB past it. - In-vivo trigger (NOT executed β the guest has no QLogic HBA):
target-mode ISP FC adapter with MULTI-ID firmware receives an ELS
(PLOGI/PRLI) whose firmware-generated
IN24XX_ELS_RCVDnotify IOCB carriesin_vpidx >= isp_nchan; the driver then reads/writes kernel heap far outsideisp_param(list under "Impact").
Root cause chain
/* isp_freebsd.c:2626-2627 */ int chan = ISP_GET_VPIDX(isp, inot->in_vpidx);
/* ispmbox.h:861-862 */ #define ISP_GET_VPIDX(isp, tag) \
(ISP_CAP_MULTI_ID(isp) ? tag : 0) /* raw 8-bit fw field */
/* isp_freebsd.c:2677 */ isp_add_wwn_entry(isp, chan, wwn, nphdl, portid);
/* isp_library.c:2398 */ fcp = FCPARAM(isp, chan);
/* ispvar.h:644 */ #define FCPARAM(isp, chan) (&((fcparam *)(isp)->isp_param)[(chan)])
Every sibling in the same library validates first β isp_find_pdb_by_wwn
(isp_library.c:2304 if (chan < isp->isp_nchan)), isp_find_pdb_by_loopid
(:2327), isp_find_pdb_by_sid (:2350), isp_fc_change_role (:512) β but
isp_add_wwn_entry/isp_del_wwn_entry dereference FCPARAM(isp, chan)
unvalidated. isp_del_all_wwn_entries checks chan > isp->isp_nchan (:2580)
instead of >=, admitting chan == nchan (one-past-end fcparam).
Through the OOB fcp the function then performs, on attacker/firmware-
influenced data:
- reads fcp->isp_tgt_map[nphdl] (isp_library.c:2407; nphdl is the firmware
16-bit N-port handle, bounded only against MAX_NPORT_HANDLE=2048 relative
to the already-OOB pointer);
- writes fcp->isp_tgt_map[nphdl] = i + 1 (:2497);
- writes lp->target_mode = 1, lp->handle = nphdl, lp->portid = s_id,
lp->port_wwn = ini (:2491-2496) into fcp->portdb[...] β i.e. an 8-byte
remote-controlled WWN plus 32-bit port ID written at
chan * sizeof(fcparam) + <portdb offset> + entry*fcportdb;
- dispatches isp_async(NT_ARRIVED) with the OOB-derived fields (:2510).
Threat model
Same trust boundary as previously accepted DF-2471 (card-reported channel in isp.c async handler) and DF-3582/3583 (card-controlled fields): the field is produced by the adapter firmware/DMA, i.e. a compromised or buggy HBA (or PCIe DMA attacker) fully controls it; a remote FC peer controls the ELS traffic that generates the notification but normally only sees its own valid VP index. Legit firmware reports only configured VPs, which is why this is Medium, not High: it is a hardening hole in the kernel's trust of the card, with a multi-field OOB write primitive behind it.
Impact
Kernel heap OOB read + OOB write of remote-controlled data (WWN/portid), offset
controlled in units of ~68 KB up to ~17.8 MB past a 68β139 KB kmalloc block β
arbitrary heap corruption on target-mode FC with MULTI-ID firmware. Guest has
no QLogic hardware, so the driver path could not be exercised: classification
inconclusive (source-certain, untriggerable here).
DF-3812 β VERDICT
status: inconclusive (poc enum) β no QLogic HBA on the audit guest; the code defect is certain from source, and the index arithmetic was demonstrated by a userland replica built and run on the guest (build.log).
What was verified
- The missing guard, precisely.
isp_add_wwn_entry(isp_library.c:2391) andisp_del_wwn_entry(:2517) takechanstraight intoFCPARAM(isp, chan)(:2398, :2529) with no bound check, while their four siblings in the same file all checkchan < isp->isp_nchan(isp_find_pdb_by_wwn :2304, isp_find_pdb_by_loopid :2327, isp_find_pdb_by_sid :2350, isp_fc_change_role :512). The asymmetry is the bug β it is not plausibly intentional. - The tainted source. On the IN24XX_ELS_RCVD path,
chan = ISP_GET_VPIDX(isp, inot->in_vpidx)(isp_freebsd.c:2627) and ISP_GET_VPIDX is a pass-through of the raw 8-bit queue-entry field whenever the firmware advertises MULTI-ID (ispmbox.h:861-862, ISP_CAP_MULTI_ID at ispmbox.h:858-859). PLOGI/PRLI subcodes then callisp_add_wwn_entry(isp, chan, wwn, nphdl, portid)(isp_freebsd.c:2677); LOGO callsisp_del_wwn_entry(isp, chan, ...)(:2650); PORT_LOGOUT calls it withISP_GET_VPIDX(...)inline (:2702-2703). - The allocation geometry.
isp->isp_param = kmalloc(sizeof(fcparam) * isp->isp_nchan)(isp_pci.c:824-832);isp_nchanis 1 or 2 (isp_pci.c:662/732/749, isp.c:1105).fcparamembedsuint16_t isp_tgt_map[2048](ispvar.h:496) andfcportdb_t portdb[512](MAX_FC_TARG=512, ispvar.h:66) β β₯ ~68 KB per channel. Demonstrated on the guest (build.log): in_vpidx 52 β 3.55 MB past the block; 255 β 17.77 MB past. - The write set through the OOB fcp. isp_library.c:2407
(tgt_map read), :2497 (
fcp->isp_tgt_map[nphdl] = i + 1), :2491-2496 (portdb entry:target_mode=1,handle=nphdl,portid=s_id,port_wwn=iniβ 8-byte remote-controlled value), :2510 (async dispatch).isp_del_wwn_entrywritesfcp->isp_tgt_map[nphdl] = 0(:2546) through the same OOB pointer. - Adjacent off-by-one.
isp_del_all_wwn_entriesgates withif (chan > isp->isp_nchan)(isp_library.c:2580) βchan == nchanpasses and yields the one-past-end fcparam. Current callers pass validated chans (isp_fc_change_role :518 after :512 check; recursion bounded :2574; NT loop :660-669), so it is latent today; folded into this finding.
Why not reproduced on the guest
pciconf -l | grep -ci "isp|qlogic" = 0 on the DragonFly QEMU guest β no
QLogic HBA, no ISP attachment; the notify-IOCB path cannot be entered. Per the
task's honesty guidance the verdict is inconclusive: certain by source,
untriggerable on available hardware. uid0-privesc chain not pursued for the
same reason (no primitive instance to groom).
Consistency with the DB
Same class/trust boundary as accepted DF-2471 (isp.c async-event channel) and DF-3582/3583 (card-controlled fields). Rated Medium rather than DF-2471's Low because this path performs a multi-field OOB write of remote-controlled data (WWN, port ID) plus a map write, at fully selectable ~68 KB granularity.
Fix
--- a/sys/dev/disk/isp/isp_library.c
+++ b/sys/dev/disk/isp/isp_library.c
@@ -2395,6 +2395,11 @@ void
isp_add_wwn_entry(ispsoftc_t *isp, int chan, uint64_t ini, uint16_t nphdl, uint32_t s_id)
{
fcparam *fcp;
+
+ if (chan >= isp->isp_nchan) {
+ isp_prt(isp, ISP_LOGWARN, "%s: bad channel %d", __func__, chan);
+ return;
+ }
fcp = FCPARAM(isp, chan);
@@ -2526,6 +2531,11 @@ void
isp_del_wwn_entry(ispsoftc_t *isp, int chan, uint64_t ini, uint16_t nphdl, uint32_t s_id)
{
fcparam *fcp;
+
+ if (chan >= isp->isp_nchan) {
+ isp_prt(isp, ISP_LOGWARN, "%s: bad channel %d", __func__, chan);
+ return;
+ }
isp_notify_t nt;
@@ -2577,7 +2587,7 @@ isp_del_all_wwn_entries(ispsoftc_t *isp, int chan)
return;
}
- if (chan > isp->isp_nchan) {
+ if (chan >= isp->isp_nchan) {
return;
}
(Plus, optionally, clamp in ISP_GET_VPIDX consumers β but fixing the library functions closes every path at once.)
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
(No fix.diff for this item: documented recommendation only - see Fix above.)
Fix verification
not_testablenot_testable: baseline never reproduced (no QLogic hardware on guest)
Confirmed kernel references
Detail
Exploit chain
compromised/buggy HBA firmware (or PCIe DMA) emits IN24XX_ELS_RCVD PLOGI/PRLI notify with in_vpidx > nchan -> chan unchecked into FCPARAM -> OOB heap write of remote WWN/portid + tgt_map entry -> arbitrary heap corruption groomable toward uid0; chain not executed (no hardware)
Evidence (decisive lines)
findings/poc/DF-3812/VERDICT.md (full trace with path:line), findings/poc/DF-3812/build.log (guest-built offset demonstrator: vpidx 52 -> 3.55MB, 255 -> 17.77MB past block), findings/poc/DF-3812/vpidx_oob_arith.c, findings/poc/DF-3812/env.txt
PoC changes
n/a - pack authored fresh; demonstrator written to quantify OOB offsets since in-vivo trigger impossible
Verified recommended fix
Add chan >= isp_nchan guards to isp_add_wwn_entry/isp_del_wwn_entry and fix isp_del_all_wwn_entries' > to >= (isp_library.c)
Verdict
isp_add_wwn_entry (isp_library.c:2398) and isp_del_wwn_entry (:2529) index FCPARAM(isp, chan) with no chan < isp_nchan check, unlike all four siblings in the same file (:2304/:2327/:2350/:512); isp_del_all_wwn_entries uses > instead of >= (:2580). On MULTI-ID firmware chan is the raw 8-bit in_vpidx from the firmware-generated IN24XX_ELS_RCVD notify (isp_freebsd.c:2627 via ispmbox.h:861-862), so a vpidx beyond the 1-2 channel fcparam allocation (isp_pci.c:824-832) yields a kernel-heap OOB pointer through which the function reads isp_tgt_map[nphdl] and writes a portdb entry (target_mode=1, handle=nphdl, portid=s_id, 8-byte remote-controlled port_wwn) plus fcp->isp_tgt_map[nphdl]=i+1 - multi-field OOB write at ~68KB granularity up to ~17.8MB past the block (arithmetic demonstrated on the guest, see build.log). Not triggered in vivo: the audit guest has no QLogic HBA (pciconf grep count 0), so the driver path is unreachable there; verdict inconclusive = source-certain, hardware-absent. Trust model matches accepted DF-2471/DF-3582/DF-3583 (card-controlled fields); rated Medium because this path writes remote-controlled data OOB.
No comments yet.