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

XPT_ABORT passes the abort CCB itself to ISPCTL_ABORT_CMD - every SCSI command abort fails (upstream passes accb)

Summary

isp_action(XPT_ABORT) calls isp_control(isp, ISPCTL_ABORT_CMD, ccb) with the XPT_ABORT control CCB instead of ccb->cab.abort_ccb (accb). isp_control looks the command up via isp_find_handle() which matches by pointer against isp->isp_xflist[] - a table only isp_start() fills - so the lookup always returns ISP_HANDLE_FREE, the driver logs 'cannot find handle for command to abort' and returns -1, and the XPT_ABORT is completed CAM_UA_ABORT. Aborts never succeed on any chip generation. CAM periph error recovery (da/cd timeout ladder: retry -> abort -> BDR) loses its abort rung: any timed-out command escalates straight to device/bus reset, disrupting every LUN/target on the channel. Deterministic functional break on all isp-equipped hosts; in-kernel trigger, no memory corruption. One-word fix matching upstream FreeBSD. VERIFIED vs FreeBSD HEAD (fetched live). Guest: no QLogic HBA - not_reproduced, source-certain; live /dev/xpt0 trigger proves userland can't inject XPT_ABORT past the xptioctl whitelist (errno 45).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3657 Β· 8 files
FileTypeDescriptionSize
trig_abort.c β€” 2.6 KB view raw
build.sh β€” 92 B view raw
run.sh β€” 96 B view raw
build.log β€” 76 B view raw
run.log β€” 353 B view raw
env.txt β€” 386 B view raw
fix.diff β€” 341 B view raw
VERDICT.md β€” 3.8 KB ↓ raw
VERDICT.md
↓ download raw

DF-3657 VERDICT β€” XPT_ABORT wrong CCB

Status: not_reproduced (guest has no QLogic HBA; source-level certain)

The bug

sys/dev/disk/isp/isp_freebsd.c:4531-4557, case XPT_ABORT:

union ccb *accb = ccb->cab.abort_ccb;
switch (accb->ccb_h.func_code) {
...
case XPT_SCSI_IO:
        error = isp_control(isp, ISPCTL_ABORT_CMD, ccb);   /* BUG: ccb, not accb */

Upstream FreeBSD (sys/dev/isp/isp_freebsd.c, case XPT_ABORT) passes accb β€” verified against current FreeBSD HEAD:

case XPT_SCSI_IO:
        error = isp_control(isp, ISPCTL_ABORT_CMD, accb);

Why the abort always fails β€” full trace

  1. isp_control(ISPCTL_ABORT_CMD, xs) at isp.c:4603 takes xs and calls handle = isp_find_handle(isp, xs) (isp.c:4611).
  2. isp_find_handle() (isp_library.c:260-274) matches by pointer equality against isp->isp_xflist[i].cmd β€” the table filled only by isp_start() for commands actually queued to the firmware.
  3. The XPT_ABORT CCB is a synchronous control CCB; it was never passed to isp_start(), so it is in no isp_xflist[] slot β†’ ISP_HANDLE_FREE (0).
  4. isp.c:4612-4616: handle == 0 β†’ warning "cannot find handle for command to abort" β†’ break β†’ isp_control returns -1 (isp.c:4845).
  5. isp_freebsd.c:4542-4546: error β‡’ ccb->ccb_h.status = CAM_UA_ABORT.

Net effect: every XPT_ABORT of an XPT_SCSI_IO on isp(4) fails, on all chip generations (the IS_24XX / IS_FC / SCSI branches are all behind the failed handle lookup). The command that should have been aborted keeps running; callers must escalate to XPT_RESET_DEV / XPT_RESET_BUS.

Impact

  • CAM periph error recovery (cam_periph_timeout β†’ abort β†’ BDR ladder) degrades: the abort rung always fails, so any timed-out command forces device reset / bus reset escalation. More resets = more I/O disruption on a live bus (all LUNs/targets on the channel see the reset).
  • No memory corruption: the wrongly-passed CCB is only used for the (failing) pointer lookup; tgt/chan derived from it stay in range because they come from the (valid) abort path.
  • Callers are in-kernel CAM periphs (da/cd/...), or camcontrol abort style tools on hosts with the HBA. DragonFly userland /dev/xpt0 cannot issue XPT_ABORT at all (xptioctl whitelist, sys/bus/cam/cam_xpt.c:1028+) β€” demonstrated live in run.log (errno 45, CCB never reaches a SIM).

Why not reproduced on the guest

  • pciconf -l: zero devices with vendor 0x1077 β†’ isp(4) never attaches, no /dev/isp*, no isp CAM bus (bus0 is ATAPI). The cited path (isp_action XPT_ABORT) cannot execute at runtime on this guest.
  • Verification is therefore the source-level trace above plus the upstream FreeBSD diff β€” both conclusive.

Fix validation

fix.diff (ccb β†’ accb) is the minimal, upstream-equivalent fix. Fix build/boot validation on the guest is not_testable: with no HBA the fixed kernel cannot execute the abort path (nothing to observe); a compile check alone would prove nothing beyond syntax. The one-word change is byte-identical in effect to upstream FreeBSD's code, which has shipped for a decade.

Kernel references

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 is the upstream-equivalent one-word correction; cannot be runtime-validated on this guest (no HBA to attach, no isp bus in CAM), so fix validation is not_testable rather than fixed.

findings/poc/DF-3657/fix.diff; FreeBSD HEAD sys/dev/isp/isp_freebsd.c case XPT_ABORT passes accb (verified via raw.githubusercontent fetch 2026-09-15)
↓ fix.diffper-fix-DF-3657

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log (userland XPT_ABORT rejected pre-SIM, bus0=ATAPI, 0 QLogic devices); VERDICT.md (full path:line trace); fix.diff (one-word fix matching upstream)

PoC changes

wrote reachability trigger from scratch (seed sketch had none); documented xptioctl whitelist boundary live on guest

Verified recommended fix

Pass accb (ccb->cab.abort_ccb) to isp_control(ISPCTL_ABORT_CMD) at isp_freebsd.c:4541, matching upstream FreeBSD.

Verdict

isp_action(XPT_ABORT) passes the XPT_ABORT CCB itself (ccb) to isp_control(ISPCTL_ABORT_CMD) instead of ccb->cab.abort_ccb (accb) at isp_freebsd.c:4541; upstream FreeBSD passes accb. isp_find_handle() matches by pointer against isp_xflist[] which only holds started commands, so the lookup always fails and every XPT_ABORT of an XPT_SCSI_IO returns CAM_UA_ABORT. CAM error-recovery (periph timeout abort rung) is broken; escalation always goes to device/bus reset. Runtime reproduction impossible on this guest: zero QLogic PCI devices (vendor 0x1077), no /dev/isp*, no isp CAM bus; userland /dev/xpt0 CAMIOCOMMAND whitelist also excludes XPT_ABORT (demonstrated in run.log, errno 45). Source-level trace + upstream diff are conclusive.