⬢ DragonFlyBSD Kernel Audit
DF-4240 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-4240",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "REPRODUCED via the production-handler KLD dispatch rig: with a device-fed channels=4 (MAX_CHAN=3, exactly what production dpt_init computes unclamped at dpt_scsi.c:1313), the production dpt_attach (0xffffffff80439500 in the shipped X86_64_GENERIC) executed the i=3 iteration out of bounds -- &sims[3]==&paths[0] stored a cam_sim* over paths[0] (type confusion confirmed: paths[0]==sims[3] value) and xpt_create_path(&paths[3]) wrote the kernel pointer 0xfffff8008d481e20 across commands_processed (0x41414141->0x8d481e20) and lost_interrupts (0x42424242->0xfffff800). With channels=8 all 10 OOB stores executed and the store at &sims[7]==&channels corrupted the loop bound itself: production dpt_attach ran away writing cam_sim*/cam_path* pointers until xpt_create_path faulted on an unmapped page 77,824 bytes past the softc (Fatal trap 12, supervisor write, page not present) -- system down in DDB. FIX VALIDATED on a rebuilt kernel via the fullinit path, which emulates the EATA config handshake (virtual HBA DMA-feeds the 0xc1 config page with MAX_CHAN=7) so the PRODUCTION dpt_init parses the device-fed data itself: dpt7: controller reported 8 channels; clamping to 3, init completes (3 channels, 16 CCBs), production dpt_attach returns 3, canaries intact (0x41414141/0x42424242 preserved), no pointer store past paths[2], runaway=0. Threat model: hostile/glitching PCI device (no unprivileged-local vector).",
  "exploit_chain": "device-fed MAX_CHAN=7 -> dpt_init channels=8 (unclamped) -> dpt_attach stores sims[3..7]/paths[3..7] OOB -> paths[0] type-confused with cam_sim*, commands_processed/lost_interrupts become a cam_path*, channels clobbered by sims[7] -> loop-bound feedback -> unbounded kernel pointer stores past the softc -> page fault (write) in production xpt_create_path -> kernel down. Impact ceiling on a real machine: attach-time kernel heap corruption with partially device-chosen pointer values.",
  "evidence": "run.log (both baseline variants, verbatim); run_channels8_fault.log (Fatal trap 12 at xpt_create_path+0x6b, fault VA 0xfffff801184f3000 = softc+77824); run_chan4_controlled.log (canaries 0x41414141->0x8d481e20 / 0x42424242->0xfffff800, paths[0]==sims[3]); fix_validation.log (clamping message, channels=3, canaries intact, attach_ret=3)",
  "kernel_refs": [
    "sys/dev/raid/dpt/dpt_scsi.c:1313",
    "sys/dev/raid/dpt/dpt_scsi.c:1462",
    "sys/dev/raid/dpt/dpt_scsi.c:1466",
    "sys/dev/raid/dpt/dpt_scsi.c:1475",
    "sys/dev/raid/dpt/dpt.h:127",
    "sys/dev/raid/dpt/dpt.h:486",
    "sys/dev/raid/dpt/dpt.h:992",
    "sys/dev/raid/dpt/dpt.h:1002"
  ],
  "poc_changes": "No runnable kernel-level seed existed (the prior seed was a userland ABI layout prover). Built the dfdpt production-handler dispatch rig from scratch: resolves production dpt_attach/dpt_init/dpt_intr from the running kernel (globals link, statics primed via sysctl with host-side prologue byte verification), fabricates post-init softc state over real busdma, populates the free dccb list via production dptallocccbs, and adds a virtual-HBA kthread emulating the EATA config handshake so the fix could be validated through production dpt_init end-to-end. Two rig bugs fixed along the way (sim-lock assert on registration; lock leak across setup) plus a stale-binary reload trap.",
  "attempts": 5,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul  2 06:02:54 UTC 2026  root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64",
  "runtime_sec": 3000,
  "guest_dirty": 0,
  "build_cmd": "cd /root/rig && make && cc -O2 -Wall -o dfdpt_ctl dfdpt_ctl.c && kldload ./dfdpt_rig.ko && ./dfdpt_ctl prime",
  "run_cmd": "./dfdpt_ctl chan8 4   (controlled)   then, on a fresh boot:   ./dfdpt_ctl chan8 8   (catastrophic, kills the kernel); fix check: ./dfdpt_ctl fullinit 8",
  "code_hash": "93d90103a5d6faf59e478ac7d74fbb3fcd04cb6fb799c747dc9e515eedf1d4ee",
  "notes": "Impact recorded as panic = demonstrated terminal kernel crash from the OOB write; the underlying class is heap corruption (the run shows a 10-store device-driven OOB write primitive plus a loop-bound feedback runaway, not merely a NULL deref). The rigs path0_holds_sim flag compares &sims[3] with &paths[0] -- the same address -- so it prints YES even post-fix; canaries + attach_ret/channels are the real discriminators (see VERDICT.md). Fix-validation kernel rebuilt on the guest with nativekernel (gcc 8.3; original world gcc120) -- compiler difference is immaterial to the clamp logic. Baseline detonations ran on the shipped #0 kernel. Guest reset clean afterwards.",
  "recommended_fix": "Clamp dpt->channels to MAX_CHANNELS after conf.MAX_CHAN+1 (dpt_scsi.c:1313).",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Validated on a rebuilt+rebooted guest kernel carrying all three dpt fixes: fullinit (production dpt_init parsing a device-fed MAX_CHAN=7 via emulated EATA handshake) prints the clamp warning, emits channels=3, production dpt_attach returns 3 with canaries intact and zero OOB stores, while the same rig on the unpatched kernel corrupted the canaries/paths[0] (channels=4) and page-faulted (channels=8).",
  "fix_evidence": "fix_validation.log"
}