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

DUMPDMA ioctl channel-bound off-by-one: dump_db() indexes ir[OHCI_DMA_IRCH] one past the array, type-confuses the fwohci_softc tail and walks it as a descriptor ring (panic / console heap leak); dump_dma() MMIO-reads past the 2KB register file

Summary

ch=0x44 (<= OHCI_MAX_DMA_CH passes the check) makes dbch = &ir[0x20] one past the array - the phantom dbch type-confuses the softc tail (ndb aliases maxrec always non-zero, top aliases a live dma_map kernel pointer) so dump_db ALWAYS walks it as a ring and print_db dumps whatever it lands on to the console (kernel-heap log leak when it does not fault first); sibling dump_dma OREADs at 0x800-0x810 past the 2KB register file. Guest-verified via the harness: Fatal trap 12 supervisor read va=0x10 at dump_db+0x58. One-character bound fix (< instead of <=).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5303 Β· 13 files
FileTypeDescriptionSize
df5302.c β€” 8.4 KB view raw
Makefile β€” 568 B ↓ download
build.sh β€” 140 B view raw
run.sh β€” 158 B view raw
build.log β€” 1.1 KB view raw
run.log β€” 1.3 KB view raw
panic.txt β€” 1.1 KB view raw
panic.first.txt β€” 1002 B view raw
fix.diff β€” 320 B view raw
env.txt β€” 260 B view raw
VERDICT.md β€” 4.3 KB ↓ raw
verdict.json β€” 3.6 KB view raw
manifest.json β€” 1.1 KB view raw
VERDICT.md
↓ download raw

DF-5303 VERDICT

Reproduced: YES β€” panic inside the production dump_db() via the OOB ir[32] index; static chain from the ioctl is airtight; fix correct by inspection (end-to-end ioctl validation not possible without FireWire hardware).

Narrative

The DUMPDMA debug ioctl validates the channel number with *dmach <= OHCI_MAX_DMA_CH (fwohci.c:372). OHCI_MAX_DMA_CH (0x44, fwohcireg.h:88) is a count (4 async + 32 IT + 32 IR), so the last valid index is 0x43; ch == 0x44 is accepted and both helpers run with it:

  • dump_dma(sc, 0x44) computes off = OHCI_IRCTL(0x20) = 0x800 and performs OREAD()s at 0x800..0x810 β€” past the end of the 2 KB OHCI register block (struct ohci_registers spans 0x000-0x7ff, fwohcireg.h:220-328).
  • dump_db(sc, 0x44) computes dbch = &sc->ir[0x20] β€” one past the ir[OHCI_DMA_IRCH] array (fwohci.c:2261-2263). Because ir[] is not the last member of fwohci_softc, the phantom fwohci_dbch overlaps the softc tail: ndb = maxrec (non-zero after reset: speed+8, fwohci.c:521), db_trq.stqh_first = sid_buf, top = sid_dma.dma_map (a live bus_dmamap_t), etc. dump_db() therefore always enters its descriptor walk (fwohci.c:2275-2303) with dbch->top pointing at bus_dmamap_impl internals instead of an fwohcidb_tr, and print_db() formats whatever memory it lands on (fwohci.c:2347-2402) β€” kernel heap contents printed to the console/log when it does not fault first.

Verification

Guest has no FireWire controller, so fwohci_ioctl()'s devclass_get_softc(firewire_devclass, unit) cannot resolve a softc (fwohci.c:344-347) β€” the ioctl entry itself is unreachable on this VM. The harness (shared with DF-5302; kern.df5302_mode=2) calls the production dump_db() directly with ch = OHCI_MAX_DMA_CH and the softc tail fields set to their post-fwohci_init() shapes (maxrec=10, sid_dma.dma_map=(void *)0x10 to make the first hop fault at a recognizable address):

DF5303: phantom dbch->ndb(=maxrec)=10 phantom->top(=sid_dma.dma_map)=0x10
Fatal trap 12: page fault while in kernel mode
fault virtual address  = 0x10
fault code             = supervisor read data, page not present
Stopped at  dump_db+0x58:   movq    (%rax),%r9     <- STAILQ_NEXT(pp, link)

This proves the OOB index executes and that the walk dereferences the aliased tail fields unvalidated. panic.first.txt records an earlier run where the phantom ndb aliased to 0 and the fault landed in the device_printf(fc->dev, "No DB is attached...") branch β€” the same OOB index, faulting one branch earlier.

Two harness iterations were needed to characterize the alias correctly (STAILQ_HEAD is 16 bytes, so top lands on sid_dma.dma_map, not sid_dma.v_addr/dma_tag) β€” the intermediate non-panicking runs are part of attempts.

Exploitability

Preconditions: open /dev/fwN.M (operator-group on classic layouts β€” same preconditions as DF-2300 documented for this driver) on a machine with a FireWire controller, then ioctl(fd, DUMPDMA, &(u_int32_t){0x44}). Impact: kernel panic (DoS) or, when the walk survives, console/log disclosure of kernel heap memory through print_db() output. Not an arbitrary-read primitive (walk shape is fixed by the aliased fields). Severity Low.

Fix validation

fix.diff: <= β†’ < at fwohci.c:372. End-to-end re-run of the ioctl is impossible on this guest (hardware-gated as above), so fix_status is not_testable; the change is provably correct by inspection (valid channel range 0..0x43), it was compiled into the fixed-module build of the DF-5302 validation cycle (applied at fwohci.c:372, see DF-5302 fix_build.log), and dump_db/dump_dma themselves are only reachable through this check.

Bottom line

Genuine off-by-one OOB array indexing on a user-reachable ioctl path with a demonstrated wild-pointer walk in the production function; Low severity (panic/console-leak ceiling, FireWire hardware required).

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 reproduced→ patch + rebuild →patched clean

Baseline walk panic reproduced. The one-character bound fix (<= -> <) is provably correct by inspection (valid indices 0..0x43) and compiled cleanly in the fixed-module build of the DF-5302 cycle (applied at fwohci.c:372), but the ioctl entry cannot be driven end-to-end on this guest because fwohci_ioctl's devclass_get_softc(firewire_devclass, unit) needs a real FireWire device - hence not_testable rather than fixed.

fix.diff; DF-5302/fix_build.log (patch applied at fwohci.c:372 during the combined fixed build).
↓ fix.diffper-fix-DF-5303

Confirmed kernel references

Detail

Exploit chain

operator-group user on a machine with a FireWire controller: open /dev/fwN.M -> ioctl(DUMPDMA, ch=0x44) -> dump_dma reads MMIO past the register file + dump_db walks a phantom ring built from softc-tail fields -> kernel panic (DoS) or print_db leaks kernel heap contents to console/log.

Evidence (decisive lines)

panic.txt: 'fault virtual address = 0x10 / supervisor read data / Stopped at dump_db+0x58: movq (%rax),%r9'; panic.first.txt (earlier alias shape, fault in the No-DB device_printf branch); run.log; VERDICT.md has the phantom-field alias table.

PoC changes

No seed PoC. Case 2 of the DF-5302 harness (kern.df5302_mode=2): calls the production dump_db(sc, OHCI_MAX_DMA_CH) with softc tail fields shaped like the post-fwohci_init state (maxrec=10, sid_dma.dma_map=0x10). Two earlier craft iterations mis-identified which tail field lands under phantom->top (STAILQ_HEAD is 16 bytes) - panic.first.txt preserves the first variant.

Verified recommended fix

Change the DUMPDMA bound to *dmach < OHCI_MAX_DMA_CH (see fix.diff); ideally also range-check IT/IR channels against the probed nisodma.

Verdict

DUMPDMA's channel bound check uses <= against OHCI_MAX_DMA_CH (0x44, a count), so ch==0x44 is accepted (fwohci.c:372); dump_db then computes dbch=&sc->ir[0x20] - one past ir[OHCI_DMA_IRCH] (fwohci.c:2261-2263) - which type-confuses the fwohci_softc tail (ndb==maxrec is always non-zero after reset per fwohci.c:521; top==sid_dma.dma_map), and the descriptor walk (fwohci.c:2275-2303) follows those fields as fwohcidb_tr pointers, printing or faulting on kernel heap internals; dump_dma sibling reads MMIO at 0x800 past the 2KB register file (fwohci.c:2203-2206). Reproduced on the guest: production dump_db(sc,0x44) with the aliased tail fields shaped as post-init state faulted at 0x10 in dump_db+0x58 (STAILQ_NEXT). Hardware-gated for the ioctl entry (no FW controller: fwohci_ioctl's devclass lookup fails first), so the walk was driven directly; static chain from the ioctl is exact.