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)
PoC verification
Evidence pack
findings/poc/DF-5302 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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_SSTBUFbcopies the user'sfw_isobufreqintod->bufreqverbatim (rx.nchunk,rx.npacket,rx.psizeunchecked).fwdev.c:108-109βfwdev_allocbuf()callsfwdma_malloc_multiseg(..., b->nchunk * b->npacket). Withnpacket == 0(or the product == 0 mod 2^32, e.g. 65536Γ65536) this passesn = 0;fwdma.c:153-161computesnseg = howmany(0, k) = 0, allocates nothing, and returns a validamβ soq->buf != NULLandFWXFERQ_EXTBUFis set (fwdev.c:134): the allocation "succeeds".fwdev.c:495βFW_SRSTREAMthen callsfc->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):
fwohci_db_init(): refusendb == 0(defense in depth; leavesFWOHCI_DBCH_INITclear so both callers return their existingENOMEM).fwohci_irx_enable(): validatendb == 0βkprintf+EINVAL.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
fixedBaseline (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).
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.
No comments yet.