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)
PoC verification
Evidence pack
findings/poc/DF-2294 Β· 17 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 fromNULL + 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) touchesNULL + offsetof(struct fw_xferq, binds)= 0x38 β same fault (panic.cbindaddr.txt). FW_CBINDADDR only reaches the deref whenfw_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 whosed->iris 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)
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
fixedConfirmed 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.
No comments yet.