dpt_init/dpt_attach: device-fed MAX_CHAN (up to 8 channels) never clamped to MAX_CHANNELS=3 β OOB writes of sims[3..7]/paths[3..7] over live softc members incl. bus_dma_tag_t/bus_dmamap_t (type confusion)
Summary
conf.MAX_CHAN is a 3-bit controller-fed field so dpt->channels ranges 1..8 while sims[]/paths[] are [MAX_CHANNELS=3] (the header even admits the 'FAT assumption'). The attach loop then performs up to 10 OOB pointer stores over paths[], commands_processed, parent_dmat, dccb_dmat, dccb_dmamap, sg_dmat; detach then bus_dma_tag_destroy()s the confused pointers - wild deref / kernel memory corruption. Layout proof compiled+run on the guest (proves &sims[3]==&paths[0] and enumerates all 10 OOB stores for MAX_CHAN=3..7). Clamp fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4240 Β· 15 files| File | Type | Description | Size | |
|---|---|---|---|---|
| dfdpt_rig.c | β | 37.5 KB | view raw | |
| dfdpt_ctl.c | β | 7.5 KB | view raw | |
| Makefile | β | 108 B | β download | |
| dpt.h | β | 38.3 KB | view raw | |
| build.sh | β | 92 B | view raw | |
| run.sh | β | 206 B | view raw | |
| build.log | β | 1.6 KB | view raw | |
| run.log | β | 3.0 KB | view raw | |
| run_chan4_controlled.log | β | 3.4 KB | view raw | |
| run_channels8_fault.log | β | 2.3 KB | view raw | |
| fix_validation.log | β | 3.7 KB | view raw | |
| fix.diff | β | 776 B | view raw | |
| verdict.json | β | 5.2 KB | view raw | |
| VERDICT.md | β | 5.0 KB | β raw | |
| env.txt | β | 789 B | view raw |
DF-4240 β VERDICT
REPRODUCED (kernel memory corruption, crash; fix validated on a rebuilt kernel).
What was proven, and how
sys/dev/raid/dpt/dpt_scsi.c is compiled into the guest's X86_64_GENERIC kernel
(nm /boot/kernel/kernel β dpt_attach @ 0xffffffff80439500, dpt_init @ β¦89f0+,
dptallocccbs @ β¦75e0, dpt_action @ β¦8010). There is no DPT HBA on the guest, so the
rig (dfdpt_rig.c) stands in for the hardware only:
- Baseline, controlled (
chan8 4,run.log+run_chan4_controlled.log): fabricate a post-dpt_initsoftc (real bus_dma, real DMA-mapped dccb array, free list built by productiondptallocccbs), setchannels = 4β exactly what productiondpt_initcomputes from a device-fedMAX_CHAN = 3(dpt_scsi.c:1313, no clamp) β and call the productiondpt_attach. Observed: -attach_ret=4: the loop ran iteration i=3 out of bounds; -&sims[3] == &paths[0](rig prints 0 bytes apart): iteration 3 stored acam_sim*intopaths[0], clobbering the path pointer installed by iteration 0 βpaths[0] == sims[3]value confirmed ("type confusion: YES"); - canarycommands_processed0x41414141 β 0x8d481e20 andlost_interrupts0x42424242 β 0xfffff800: together the kernel pointer 0xfffff8008d481e20 thatxpt_create_path(&dpt->paths[3])wrote across both counters (8-byte store at&paths[3] == &commands_processed). - Baseline, catastrophic (
chan8 8,run_channels8_fault.log): with channels=8 all ten OOB stores execute; the store at&sims[7](==&channels+max_id) corrupts the loop-bound fieldchannelswith the low 32 bits of a kernel pointer, the loop runs away, and productionxpt_create_pathfaults writing acam_path*to an unmapped page 77,824 bytes past the softc:Fatal trap 12: page fault β¦ supervisor write data, page not present β¦ xpt_create_path+0x6b: movq %rbx,(%r12)β system down in DDB. - Fix validation (
fix_validation.log): all three dpt fixes applied to the guest's /usr/src, kernel rebuilt (nativekernel, gcc 8.3) and booted. The rig'sfullinitpath emulates the EATA config handshake (virtual HBA kthread: watches HA_WCOMMAND, DMA-feeds the 0xc1 config page incl.MAX_CHAN=7, raises HA_AIRQ; the handshake emulation is race-free β see rig comments) so the productiondpt_initparses the device-fed config itself: -dpt7: controller reported 8 channels; clamping to 3(the fix) -dpt7: FW Rev. , 3 channels, 16 CCBs(production init banner β full success) - productiondpt_attachreturned 3; canaries0x41414141β0x41414141,0x42424242β0x42424242intact; slot at +176 still reads 0x4242424241414141 (the canary pair) and +184 reads 0xf00000003 (channels=3,max_id=15β the softc's own fields, no pointer store);runaway=0.
Baseline behavior gone on the patched kernel; normal 3-channel attach unaffected.
Threat model
All inputs are device-fed: conf.MAX_CHAN arrives over the EATA PIO/DMA config protocol
from the PCI card (a hostile or glitching controller β firmware bug, power blip, PCI
reset β or a passed-through/virtualized device). Attach runs once per boot/hotplug, so
the corruption is single-shot but destructive: with MAX_CHAN=3..4 it type-confuses
paths[0] (CAM later treats a cam_sim* as a cam_path*) and turns two counters into a
pointer; with MAX_CHANβ₯5 it corrupts channels/max_id, the TAILQ linkage of
dpt_softcs, and β via the loop-bound feedback β writes an unbounded stream of kernel
pointers past the softc (demonstrated page fault). Local unprivβroot is not the threat
model here; this is a hostile/failed-device kernel-corruption bug.
Notes / caveats
- The rig's
path0_holds_simdetector compares the value at&sims[3]with the value at&paths[0]β the same address β so it prints "YES" even on the patched kernel where no OOB store happened (&paths[0]legitimately holds its own path). The canaries andattach_ret/channelsare the real discriminators; VERDICT relies on those. - The chan8 baseline bypasses
dpt_init(fabricates its output), because the full handshake emulation was built for the fix-validation run; thefullinitpath closes that gap end-to-end on the patched kernel (device β MAX_CHAN β clamp). - Earlier seed evidence (
dpt_df4240_layout.c, oldrun.log) was a userland ABI layout proof only; superseded by this run.
Bottom line: the unclamped, controller-fed channel count drives production dpt_attach
into demonstrated out-of-bounds pointer stores (type confusion over live softc members
and an uncontrolled runaway write), and the one-line clamp in fix.diff removes it.
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
fixedValidated 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_validation.log
Confirmed kernel references
Detail
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 (decisive lines)
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)
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.
Verified recommended fix
Clamp dpt->channels to MAX_CHANNELS after conf.MAX_CHAN+1 (dpt_scsi.c:1313).
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).
No comments yet.