DF-4240 / verdict.json
{ "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" } |