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

NULL-pointer write panic in fwohci_db_init() via zero ndb from unvalidated FW_SSTBUF bnpacket*bnchunk (RX and TX enable paths)

Summary

db_init unconditionally closes the ring via STAILQ_LAST of a possibly-empty db_trq = NULL -> 8-byte write to address 0; ndb = bnpacket*bnchunk is user-controlled and unvalidated the whole path (FW_SSTBUF bcopies the spec verbatim; fwdma_malloc_multiseg returns a VALID am for n==0; FW_SRSTREAM -> irx_enable computes ndb=0). Three syscalls (open /dev/fwN.M, FW_SSTBUF{npacket:0}, FW_SRSTREAM) -> deterministic panic; 65536*65536 wraps identically. Guest-verified via KLD harness compiling the UNMODIFIED production fwohci.c: Fatal trap 12 supervisor write va=0x0 at fwohci_db_init.cold.11; fixed module returns EINVAL, guest survives. DoS only (fixed address 0, NULL content). ndb==0 guards + EINVAL fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5302 Β· 14 files
FileTypeDescriptionSize
df5302.c β€” 8.4 KB view raw
Makefile β€” 568 B ↓ download
build.sh β€” 159 B view raw
run.sh β€” 142 B view raw
build.log β€” 1.1 KB view raw
run.log β€” 1.2 KB view raw
panic.txt β€” 1.0 KB view raw
fix.diff β€” 1.3 KB view raw
fix_build.log β€” 402 B view raw
fix_run.log β€” 695 B view raw
env.txt β€” 260 B view raw
VERDICT.md β€” 4.6 KB ↓ raw
verdict.json β€” 3.7 KB view raw
manifest.json β€” 1.2 KB view raw
VERDICT.md
↓ download raw

DF-5302 VERDICT

Reproduced: YES β€” kernel panic (NULL write) via the production code path; fix validated.

Narrative

fwohci_db_init() builds the DMA descriptor ring for an OHCI channel. When dbch->ndb == 0 the per-entry loop (fwohci.c:1229-1252) is skipped, so dbch->db_trq stays empty, and the ring-closure statement

STAILQ_LAST(&dbch->db_trq, fwohcidb_tr,link)->link.stqe_next
        = STAILQ_FIRST(&dbch->db_trq);           /* fwohci.c:1253-1254 */

dereferences STAILQ_LAST() of an empty queue, which DragonFly's sys/sys/queue.h defines as NULL. The store then writes 8 bytes of STAILQ_FIRST(...) (also NULL here, but any value would fault the same way) to address NULL + offsetof(struct fwohcidb_tr, link) = 0x0.

ndb is fully user-controlled with no validation anywhere on the path:

  • fwdev.c:504-506 β€” FW_SSTBUF bcopies the user's fw_isobufreq into d->bufreq verbatim (rx.nchunk, rx.npacket, rx.psize unchecked).
  • fwdev.c:108-109 β€” fwdev_allocbuf() calls fwdma_malloc_multiseg(..., b->nchunk * b->npacket). With npacket == 0 (or the product == 0 mod 2^32, e.g. 65536Γ—65536) this passes n = 0; fwdma.c:153-161 computes nseg = howmany(0, k) = 0, allocates nothing, and returns a valid am β€” so q->buf != NULL and FWXFERQ_EXTBUF is set (fwdev.c:134): the allocation "succeeds".
  • fwdev.c:495 β€” FW_SRSTREAM then calls fc->irx_enable().
  • fwohci.c:1606-1608 β€” dbch->ndb = ir->bnpacket * ir->bnchunk (= 0), dbch->ndesc = 2, fwohci_db_init(sc, dbch) β†’ panic.

The TX entry (FW_STSTREAM + first write() β†’ fwohci_itxbuf_enable, fwohci.c:1485-1487) has the identical defect.

Verification (QEMU guest, DragonFly 6.5-DEVELOPMENT #0, no FireWire hardware)

The file is hardware-gated (no FW controller on the guest), so the evidence class is a KLD harness in the DF-5230 style: the module #includes the unmodified production fwohci.c (globals -D-renamed against the kernel-resident copy) and calls the production fwohci_irx_enable() with exactly the fw_xferq state the ioctl path produces. Only the bus_space window (MEM tag + a plain kernel-memory block β€” on x86_64 that is a direct memory store, bus_dma.h:186-196) and the bus itself are synthetic.

Baseline (run.log, panic.txt):

DF5302: calling production fwohci_irx_enable() with bnpacket*bnchunk = 0*8 = 0 ...
Fatal trap 12: page fault while in kernel mode
fault virtual address  = 0x0
fault code             = supervisor write data, page not present
Stopped at  fwohci_db_init.cold.11: movq    $0,0

supervisor write data at 0x0 inside fwohci_db_init β€” the exact store at fwohci.c:1253. The panic is deterministic; reproduced on a fresh vm.sh reset with-src boot (guest down at db> afterwards).

Exploitability

The primitive is a write of a NULL pointer value to the fixed address 0x0 (not attacker-redirectable, not content-controlled) β†’ local denial of service only. Reachable by any user permitted to open /dev/fwN.M (operator group on classic layouts β€” same preconditions as the already-filed DF-2293/DF-2300 in this driver family) with three syscalls. No escalation path identified (the fault virtual address is fixed at 0, which is not mappable from user space on this platform).

Fix validation

fix.diff (3 hunks):

  1. fwohci_db_init(): refuse ndb == 0 (defense in depth; leaves FWOHCI_DBCH_INIT clear so both callers return their existing ENOMEM).
  2. fwohci_irx_enable(): validate ndb == 0 β†’ kprintf + EINVAL.
  3. fwohci_itxbuf_enable(): same for the TX side.

Applied to the guest's /usr/src, module rebuilt against the patched production source, identical trigger (fix_run.log, fix_build.log):

fwohci: invalid iso rx buffer spec (bnpacket=0 bnchunk=8)
DF5302: SURVIVED fwohci_irx_enable, returned 22 (baseline expects panic; fixed kernel expects EINVAL)

Guest stays up. Baseline panic present, patched panic gone, clean error returned β€” fix_status: fixed.

Bottom line

Real, trivially reachable, deterministic local kernel-panic DoS in production code, reproducible on the audit guest through the production function, with a validated three-hunk fix. Not memory-corruption-exploitable (fixed NULL address, NULL content).

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

fixed
baseline reproduced→ patch + rebuild →patched reproduced

Baseline (unpatched /usr/src): Fatal trap 12 write at 0x0 in fwohci_db_init, guest down. After patch -p1 < fix.diff + module rebuild with the patched production source: 'fwohci: invalid iso rx buffer spec (bnpacket=0 bnchunk=8)' + 'SURVIVED fwohci_irx_enable, returned 22', guest up. Panic gone, clean EINVAL.

fix_run.log, fix_build.log, fix.diff (hunks at fwohci.c:1193/1492/1619 after application).
↓ fix.diffper-fix-DF-5302

Confirmed kernel references

Detail

Exploit chain

operator-group user: open /dev/fw0.0 -> ioctl FW_SSTBUF{rx.npacket=0} -> ioctl FW_SRSTREAM -> fwdev_allocbuf succeeds with 0-element buffer -> fwohci_irx_enable -> fwohci_db_init -> NULL+0 write -> kernel panic (local DoS; no uid0 route: the faulting address is fixed at 0x0).

Evidence (decisive lines)

panic.txt: 'fault virtual address = 0x0 / supervisor write data / Stopped at fwohci_db_init.cold.11: movq $0,0'; run.log (baseline), fix_run.log ('SURVIVED ... returned 22 (EINVAL)', guest up); VERDICT.md for the full static chain fwdev.c:504->fwdma.c:153->fwohci.c:1606->1253.

PoC changes

No seed PoC existed. Harness authored fresh: KLD includes the production fwohci.c (globals -D-renamed), calls production fwohci_irx_enable() with the fwdev_allocbuf state for npacket=0; bus_space faked with a MEM-space tag over plain kernel memory (x86_64 bus_space_write_4 is then a direct store).

Verified recommended fix

Validate ndb!=0 in fwohci_irx_enable/fwohci_itxbuf_enable (EINVAL) and guard fwohci_db_init against ndb==0 (see fix.diff).

Verdict

Deterministic kernel NULL-pointer WRITE panic in the production fwohci_db_init() (fwohci.c:1253) whenever dbch->ndb == 0: STAILQ_LAST() of the still-empty db_trq ring is NULL and the ring-closure store writes to address 0x0. ndb arrives unvalidated from user space (FW_SSTBUF rx.npacket*nchunk == 0 via fwdev.c:504-506 -> fwdma_malloc_multiseg(n=0) returns a valid am at fwdma.c:153-161 -> FW_SRSTREAM -> fwohci_irx_enable fwohci.c:1606-1608; TX twin at fwohci.c:1485). Verified on the QEMU guest with a KLD harness that compiles the unmodified production fwohci.c and calls the production fwohci_irx_enable(): Fatal trap 12, fault VA 0x0, supervisor write data, stopped at fwohci_db_init.cold.11: movq $0,0. Hardware-gated for the ioctl plumbing (no FireWire controller on the guest), so the harness replaces only bus_space and the bus state. Impact ceiling is local DoS (fixed address 0, NULL content; not redirectable). fix.diff (3 hunks) applied inside the guest /usr/src: identical trigger returns EINVAL with 'invalid iso rx buffer spec' and the guest stays up - fix validated.