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

FW_SBINDADDR / FW_CBINDADDR NULL-pointer dereference when receive xferq is uninitialized (local DoS)

Summary

fw_ioctl unconditionally dereferences ir (d->ir) inside FW_SBINDADDR and FW_CBINDADDR even though ir is NULL until FW_SRSTREAM has been issued. Opening /dev/fw0.0 and immediately calling FW_SBINDADDR dereferences address ~offsetof(fw_xferq dmach) panicking kernel. FW_CBINDADDR same flaw on &ir->binds. Local operator-group user single-open single-ioctl kernel panic. No FireWire bus activity required. [VERIFIED pass2: reproduced both paths - fault at exactly 0x10 SBINDADDR and 0x38 CBINDADDR two-fd; fix-validated EINVAL with legit bind path intact]

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2294 Β· 17 files
FileTypeDescriptionSize
README.md β€” 1.5 KB ↓ raw
VERDICT.md β€” 2.9 KB ↓ raw
build.log β€” 211 B view raw
build.sh β€” 111 B view raw
build_kernel.sh β€” 396 B view raw
env.txt β€” 466 B view raw
fix.diff β€” 1.6 KB view raw
fwharness.c β€” 18.8 KB view raw
fwharness.h β€” 1.4 KB view raw
fwhctl.c β€” 3.7 KB view raw
panic.cbindaddr.txt β€” 1.3 KB view raw
panic.sbindaddr.txt β€” 1.3 KB view raw
poc_2294.c β€” 3.3 KB view raw
prep_guest.sh β€” 1.2 KB view raw
run.sh β€” 90 B view raw
run_fixed.log β€” 604 B view raw
verdict.json β€” 2.8 KB view raw

DF-2294 β€” FW_SBINDADDR / FW_CBINDADDR NULL d->ir dereference

Verdict (short)

REPRODUCED on the stock fwdev.o binary, both cited paths:

  • FW_SBINDADDR (single open + single ioctl): fwb->sub = ir->dmach; (fwdev.c:617) loads from NULL + offsetof(struct fw_xferq, dmach) = 0x10 β†’ Fatal trap 12: page fault, supervisor read, fw_ioctl ← dev_dioctl (panic.sbindaddr.txt).
  • FW_CBINDADDR (two fds on the same controller): STAILQ_REMOVE( &ir->binds, fwb, ...) (fwdev.c:600) touches NULL + offsetof(struct fw_xferq, binds) = 0x38 β†’ same fault (panic.cbindaddr.txt). FW_CBINDADDR only reaches the deref when fw_bindlookup() first finds an existing bind (fwdev.c:593-598 returns EINVAL otherwise), so the reproducer registers a legitimate bind on fd1 (SSTBUF+SRSTREAM+SBINDADDR β€” which itself requires d->ir != NULL on that fd) and then issues FW_CBINDADDR from fd2 whose d->ir is NULL.

Trigger is an operator-group user. Fix validated: NULL-check ir in both cases β†’ EINVAL, no panic, and the legitimate bind/unbind path on a stream-equipped fd still works (fd1 of the A/B run).

Files

  • poc_2294.c β€” trigger, both modes (sbind, cbind)
  • panic.sbindaddr.txt (fault va 0x10), panic.cbindaddr.txt (0x38)
  • run_fixed.log β€” fixed-kernel A/B
  • fix.diff β€” combined fix (this finding: FW_CBINDADDR/FW_SBINDADDR NULL guards)
  • fwharness.c/fwharness.h/fwhctl.c β€” verification harness (see ../DF-2293/ for the harness README)
VERDICT.md
↓ download raw

DF-2294 VERDICT β€” REPRODUCED (panic, local DoS, operator-group user)

Chain (line-accurate, stock fwdev.o)

fw_ioctl (fwdev.c:410) takes ir = d->ir at fwdev.c:443 β€” NULL until a successful FW_SRSTREAM (fwdev.c:494). Neither bind ioctl checks it.

FW_SBINDADDR (fwdev.c:603-634)

612:    fwb = kmalloc(sizeof (struct fw_bind), M_FW, M_WAITOK);
613:    fwb->start = ((u_int64_t)bindreq->start.hi << 32) | bindreq->start.lo;
615:    fwb->end = fwb->start +  bindreq->len;
617:    fwb->sub = ir->dmach;            <-- ir == NULL: load at 0x10

Observed: Fatal trap 12: page fault, fault virtual address = 0x10, supervisor read data, page not present, fw_ioctl() at fw_ioctl+0x6eb ← dev_dioctl (panic.sbindaddr.txt). 0x10 == offsetof(struct fw_xferq, dmach) on this build β€” exactly the predicted NULL+offsetof dereference. Single open + single ioctl.

FW_CBINDADDR (fwdev.c:592-602)

593:    fwb = fw_bindlookup(sc->fc, bindreq->start.hi, bindreq->start.lo);
595:    if(fwb == NULL){ err = EINVAL; break; }   <-- guards only the miss
599:    STAILQ_REMOVE(&sc->fc->binds, fwb, fw_bind, fclist);
600:    STAILQ_REMOVE(&ir->binds, fwb, fw_bind, chlist);  <-- NULL+0x38

Reaching :600 requires a matching bind to exist, which (via the stock code) can only be registered by an fd whose own d->ir is set. The reproducer therefore uses two fds on the same controller: fd1 does the legit SSTBUF+SRSTREAM+SBINDADDR setup (real fw_bindadd, firewire.c:820), fd2 (fresh open, d->ir == NULL) issues FW_CBINDADDR β†’ Fatal trap 12, fault virtual address = 0x38 (== offsetof(struct fw_xferq, binds)), fw_ioctl+0x429 (panic.cbindaddr.txt).

Both panics: uid=1002 opuser (operator group). No FireWire bus activity required.

Reachability honesty

Same as DF-2293: 0660 root:operator nodes exist only on systems with an OHCI controller (stock GENERIC compiles firewire but not fwohci). Verification used the verbatim-interposition harness; the faulting binary is the stock fwdev.o.

Fix validation (A/B, fix.diff FW_CBINDADDR/FW_SBINDADDR guards)

Patched kernel: * poc_2294 sbind β†’ ioctl rc=-1 errno=22, no panic * poc_2294 cbind β†’ fd1 setup still succeeds (legit bind path intact), fd2 FW_CBINDADDR β†’ ioctl rc=-1 errno=22, no panic * guest alive.

Privesc assessment

None β€” NULL-offset load/store, deterministic fault β†’ panic. Impact: dos. (The faulting write at &ir->binds in FW_CBINDADDR is a store to a fixed low address; on this kernel it simply faults β€” no controlled write primitive.)

Artifacts

panic.sbindaddr.txt, panic.cbindaddr.txt, run_fixed.log, poc_2294.c, fwharness.c (rig), env.txt.

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
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #8: Sun Oct 18 07:38:56 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/FWH x86_64

Confirmed kernel references

Detail

Exploit chain

open /dev/fw0.0 + FW_SBINDADDR -> kernel panic (DoS). CBINDADDR needs a second fd on the same controller after a legit bind exists. No escalation: NULL-offset fault only.

Evidence (decisive lines)

panic.sbindaddr.txt (fault va 0x10, fw_ioctl+0x6eb), panic.cbindaddr.txt (fault va 0x38, fw_ioctl+0x429), run_fixed.log (EINVAL both, legit bind path still works)

PoC changes

Seed concept kept; added the honest two-fd CBINDADDR reproducer (a matching bind must exist for fw_bindlookup to succeed before the NULL deref is reached); fd2 uses /dev/fw0.1 like the stock fwdev_clone per-stream minors.

Verified recommended fix

NULL-check d->ir (ir == NULL -> EINVAL) at the top of FW_SBINDADDR and FW_CBINDADDR.

Verdict

FW_SBINDADDR dereferences ir->dmach (fwdev.c:617) and FW_CBINDADDR dereferences &ir->binds (fwdev.c:600) while d->ir is NULL (no FW_SRSTREAM yet). Both reproduced on the stock fwdev.o binary: Fatal trap 12 page faults at exactly 0x10 (offsetof fw_xferq,dmach) for the single-open single-ioctl SBINDADDR case and 0x38 (offsetof fw_xferq,binds) for the two-fd CBINDADDR case (bind registered on fd1 via real fw_bindadd, CBINDADDR issued from fd2 with NULL d->ir). Operator-group user; no bus activity. Fix validated: NULL guards return EINVAL on both paths with the legitimate bind path intact.