β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-3584

isp_control() ISPCTL_RESET_DEV/ABORT_CMD index fcp->isp_dev_map[tgt] without validating tgt; the CAM glue packs bus<<16 into tgt so every channel>0 device reset reads ~108 KB out of bounds

Summary

Both ISPCTL paths perform 'hdlidx = fcp->isp_dev_map[tgt] - 1;' bounds-checking only the loaded value, never the index - unlike isp_start() which validates the target before the same array. The XPT_RESET_DEV glue ORs the bus number into the target ('tgt |= (bus << 16)'), so for a device on channel 1 of a multi-channel 24xx-class card the load is at base+151608 against a 43152-byte 2-channel allocation - 108,456 bytes out of bounds: a wild kernel read during BDR handling (panic where unmapped, garbage index otherwise). Local DoS on multi-channel ISP FC hardware: an operator-group user via pass(4) camcontrol reset, or plain CAM error recovery on a hung command on port B, deterministically performs the wild read. PROOF: index-arithmetic harness vs verbatim headers - '=> 108456 bytes past allocation end' observed. Inconclusive (no HBA). Fix: validate tgt < MAX_FC_TARG before both loads (+ drop the bus<<16 packing in the glue).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3584 Β· 9 files
FileTypeDescriptionSize
trigger_df3584.c β€” 3.8 KB view raw
build.sh β€” 214 B view raw
run.sh β€” 97 B view raw
build.log β€” 1.2 KB view raw
run.log β€” 2.5 KB view raw
VERDICT.md β€” 2.9 KB ↓ raw
verdict.json β€” 3.4 KB view raw
fix.diff β€” 890 B view raw
manifest.json β€” 1018 B view raw
VERDICT.md
↓ download raw

DF-3584 β€” Unvalidated target index into fcp->isp_dev_map[] in isp_control() (far OOB read)

WHAT sys/dev/disk/isp/isp.c:4522 (ISPCTL_RESET_DEV) and 4624 (ISPCTL_ABORT_CMD), inside isp_control():

    hdlidx = fcp->isp_dev_map[tgt] - 1;
    if (hdlidx < 0 || hdlidx >= MAX_FC_TARG) { ... }

The RESULT of the array load is bounds-checked, but `tgt` β€” the INDEX β€”
is never validated against MAX_FC_TARG (contrast isp_start(),
isp.c:4247-4254, which checks XS_TGT before the same array).

The CAM glue makes it worse: XPT_RESET_DEV packs the bus number into the
same word (isp_freebsd.c:4519-4523):

    bus = cam_sim_bus(xpt_path_sim(ccb->ccb_h.path));
    tgt = ccb->ccb_h.target_id;
    tgt |= (bus << 16);
    error = isp_control(isp, ISPCTL_RESET_DEV, bus, tgt);

so for a device on channel 1 the 24XX path reads
    offsetof(fcparam, isp_dev_map) + 2*(65536 + target_id)
  = 20536 + 131072 = >=151608 bytes into an allocation of
    nchan*sizeof(fcparam) (21576 or 43152 bytes)
β€” >= 108 KB past the end: a wild kernel read on EVERY BDR issued to a
channel>0 device (panic if the page is unmapped; garbage index otherwise,
which the post-check then mostly rejects).

REACHABILITY Any XPT_RESET_DEV reaching the driver for a channel>0 FC device: cam_periph error recovery (a hung/errored disk on port B of a dual-port 24xx/25xx, NPIV/multi-ID configurations), or camcontrol reset issued by an operator-group user via the pass(4) node. The read is deterministic; whether it faults depends on what follows the allocation in kmem.

PROOF (this pack) trigger_df3584.c performs the identical isp_dev_map[tgt] load with tgt = 0x10000 (bus 1, target 0) against a 43152-byte (2-channel) fcparam-sized allocation followed by PROT_NONE:

    read address = base + 20536 + 2*65536 = base + 151608
    => 108456 bytes past the end of the 43152-byte allocation
    FAULT (SIGSEGV/SEGV) at 0x8004c5038 => 108456 bytes past allocation end

WHY "inconclusive" No QLogic HBA (and in particular no multi-channel 24xx) in the guest, so the in-kernel path cannot be exercised. Offsets proven with the real kernel headers on the guest compiler.

FIX fix.diff β€” validate tgt before indexing (both ISPCTL_RESET_DEV and ISPCTL_ABORT_CMD), mirroring isp_start()'s check. (The tgt |= bus<<16 packing in isp_freebsd.c should also be dropped by whoever owns that file; the isp.c-side validation makes the core robust regardless.)

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.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

fix.diff adds the same MAX_FC_TARG bounds check isp_start() already performs, before the isp_dev_map[] load, in both ISPCTL_RESET_DEV and ISPCTL_ABORT_CMD. Baseline not reproducible without hardware; no patched-kernel A/B possible. Callers should additionally stop packing bus<<16 into tgt (isp_freebsd.c), but the isp.c-side check removes the memory-safety hazard regardless.

fix.diff (git-apply-able against sys/dev/disk/isp/isp.c hunks at lines 4522 and 4624)
↓ fix.diffper-fix-DF-3584

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log β€” 'read address = base + 20536 + 2*65536 = base + 151608', '=> 108456 bytes past the end of the 43152-byte allocation', 'FAULT (SIGSEGV/SEGV) at 0x8004c5038 => 108456 bytes past allocation end'; trigger_df3584.c; VERDICT.md; fix.diff (bounds-check tgt in both ISPCTL cases)

PoC changes

No seed (pass-2 new finding). Trigger written from scratch against verbatim kernel headers; one iteration needed a missing include for offsetof (captured in run.log).

Verified recommended fix

Bounds-check tgt (0 <= tgt < MAX_FC_TARG) before indexing fcp->isp_dev_map[] in ISPCTL_RESET_DEV/ISPCTL_ABORT_CMD.

Verdict

Unchecked array index confirmed at source level: isp_control() ISPCTL_RESET_DEV (sys/dev/disk/isp/isp.c:4522) and ISPCTL_ABORT_CMD (isp.c:4624) read fcp->isp_dev_map[tgt] without validating tgt against MAX_FC_TARG, unlike isp_start() (isp.c:4247-4254) which checks the same mapping. The CAM XPT_RESET_DEV glue packs the bus number into tgt (isp_freebsd.c:4519-4523 'tgt |= (bus << 16)'), so every BDR for a channel>0 FC device indexes at least 65536 entries past the array: read address base+151608 vs a 43152-byte 2-channel allocation β€” 108,456 bytes out of bounds (fault proven by the extracted-logic PoC, run.log). In-kernel impact is a wild read during BDR handling: kernel panic where unmapped, otherwise a garbage index that the existing post-check usually rejects. Not exercised on the guest (no QLogic HBA / no multi-channel card). Additionally observed while tracing the caller: DragonFly's XPT_ABORT passes the XPT_ABORT CCB itself instead of the CCB to abort (isp_freebsd.c:4541 'isp_control(isp, ISPCTL_RESET_DEV...' vs upstream's accb), making command abort always fail the handle lookup β€” a functional divergence in the neighboring file, noted for that file's audit.