DF-5416 / verdict.json
{ "finding_id": "DF-5416", "status": "reproduced", "reproduced": true, "impact": "panic", "confidence": "certain", "verdict": "Reproduced on the QEMU guest entirely through the production fw_ioctl path with two synthetic /dev/fw nodes (fw0.0/fw0.1 with separate fw_drv1, as devfs provides): node A registered a FW_SBINDADDR bind (production fw_bindadd links it into fc->binds and fc->ir[0]->binds), node B then issued FW_CBINDADDR with the same address; fw_bindlookup() is global (firewire.c:804-814) so it returned As bind, fwdev.c:599 removed it from fc->binds, and fwdev.c:600 STAILQ_REMOVE(&ir->binds, fwb) tried to unlink it from Bs EMPTY list - DragonFlys STAILQ_REMOVE (queue.h:319-331) has no absent-element guard, the walk dereferenced NULL and the kernel took Fatal trap 12, fault virtual address 0x28, Stopped at fw_ioctl+0x7f6: movq 0x28(%rdx),%rax. Guest down. Deterministic local DoS for operator-group users with two open nodes; it is also a bind-theft primitive (cross-fd ownership is never verified; absent the walk-off it would leave a freed bind on the owners list for a close-time double free). Fix validated: ownership check returns EINVAL (22), no panic, guest alive.", "exploit_chain": "open /dev/fw0.0 and /dev/fw0.1 (operator) -> A: SSTBUF+SRSTREAM+SBINDADDR(addr) -> B: SSTBUF+SRSTREAM -> B: CBINDADDR(addr) -> production fwdev.c:600 STAILQ_REMOVE of a foreign bind from an empty list -> NULL-walk page fault at fw_ioctl+0x7f6 -> reliable kernel panic; conceptually also cross-fd bind theft/double-free on the owners close path", "evidence": "panic.txt (Fatal trap 12, fault va 0x28, Stopped at fw_ioctl+0x7f6, full pre-panic dmesg), fix_run.log (df5416: ... CBINDADDR returned 22 on patched module, guest alive), fix.diff, build.log, fix_build.log", "kernel_refs": [ "sys/bus/firewire/fwdev.c:592", "sys/bus/firewire/fwdev.c:593", "sys/bus/firewire/fwdev.c:599", "sys/bus/firewire/fwdev.c:600", "sys/bus/firewire/fwdev.c:601", "sys/bus/firewire/firewire.c:804", "sys/bus/firewire/firewire.c:847", "sys/bus/firewire/firewire.c:848", "sys/sys/queue.h:319" ], "poc_changes": "Same shared harness (df5413.c test 4): the fake devclass/device pair makes the production fw_ioctl fetch a valid softc, two fake cdevs model the two /dev/fw nodes devfs gives (separate si_drv1), everything else (SSTBUF/SRSTREAM/SBINDADDR/CBINDADDR, fw_bindadd, fw_bindlookup, the removals, the kfree) is production code.", "attempts": 2, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 1380, "guest_dirty": 0, "build_cmd": "sh build.sh (cd /root/df5413 && make; compiles production firewire.c+fwdma.c+fwdev.c into df5413.ko)", "run_cmd": "sh run.sh (kldload df5413.ko; sysctl kern.df5413_test=4; expect panic, capture serial console)", "code_hash": "528bb42b35a2e37de5d5e534e5a6eebd9ac13c30ec32c5b5f9313ca96624931a", "notes": "Hardware-gated only for the /dev/fw nodes (no OHCI controller in QEMU); the entire ioctl sequence ran as production code. DF-1059/DF-2294 (NULL ir in SBIND/CBIND) are a different, already-known facet; this finding is the cross-node foreign-bind walk-off, new in pass 2.", "recommended_fix": "FW_CBINDADDR: verify the looked-up bind is on this irs binds list (STAILQ_FOREACH) before unlinking, protect the removals with crit_enter/exit, and free fwb->xferlist xfers - as in fix.diff.", "fix_status": "fixed", "fix_baseline_reproduced": true, "fix_patched_reproduced": false, "fix_verdict": "Baseline: Fatal trap 12 at fw_ioctl+0x7f6 (fault va 0x28), guest down. Patched (fix.diff on /usr/src, module rebuilt, same trigger): FW_CBINDADDR returns 22 (EINVAL) for the foreign bind, no panic, guest alive through the full battery.", "fix_evidence": "fix_run.log (df5416: UNREACHED: CBINDADDR returned 22), fix_build.log, fix.diff" } |