DF-5547 / verdict.json
{ "finding_id": "DF-5547", "status": "reproduced", "reproduced": true, "impact": "kernel out-of-bounds WRITE of 1-3 fully responder-controlled bytes past the RPC destination buffer (proven cross-chunk into the adjacent 32-byte heap chunk via production fwmem_strategy->fwmem_read_block->fw_rcv/fw_rcv_copy); on current DFly physio the plain user read(2) path absorbs it into bounce-buffer slack, kernel-originated (UIO_SYSSPACE) reads / direct bio issuers get the full heap overrun", "confidence": "certain", "verdict": "Reproduced on the production code path: fwmem_read_block (fwmem.c:197) advertises recv.pay_len=roundup2(len,4) for a len-byte destination buffer and fw_rcv_copy (firewire.c:1707-1722) honors pay_len, not the buffer size, so a quadlet-padded RRESB writes up to 3 attacker/responder-chosen bytes past bp->b_data+b_bcount. Verified twice on independent boots with a length matrix (all odd lengths 1..2047 overrun; multiples of 4 do not) and a groomed adjacent-chunk layout showing 0x41 0x42 0x43 landing in the neighbour. Fix (advertise len, not the roundup) validated: identical trigger produces zero overruns and the RPC still completes.", "exploit_chain": "odd-length read on /dev/fwmem* (operator-group user, FW bus present) -> fwmem_strategy -> fwmem_read_block(len=N%4!=0) -> recv.pay_len=roundup2(N,4) -> padded/malicious RRESB via fw_rcv -> fw_rcv_copy copies pay_len bytes into the N-byte buffer -> 1-3 controlled bytes past the end; kernel bio issuers / UIO_SYSSPACE get the corruption in kernel heap (demonstrated cross-chunk in the KLD harness); user-path physio bounce absorbs it today (honest ceiling: contract violation + latent corruption, deterministic for kernel-side callers)", "evidence": "run.log: the decisive lines bcount=29 pay_len=32 ... bytes past buffer: 41 42 43 aa -> OOB WRITE and N[0..3] now: 41 42 43 aa; fix_validation.log: all lengths no change after the one-line fix; df5547.c test 1 = production fwmem_open/fwmem_strategy/fw_asyreq/fw_rcv driving the real RPC state machine", "kernel_refs": [ "sys/bus/firewire/fwmem.c:197", "sys/bus/firewire/fwmem.c:209", "sys/bus/firewire/fwmem.c:357", "sys/bus/firewire/fwmem.c:365", "sys/bus/firewire/firewire.c:1707", "sys/bus/firewire/firewire.c:1710", "sys/bus/firewire/firewire.c:1716", "sys/kern/kern_physio.c:90" ], "poc_changes": "seed had no runnable PoC; built the DF-5413-family KLD harness: unmodified production firewire.c+fwmem.c #included with -D-renamed globals, synthetic fc/fw_device/devclass state (fwohci attach equivalent), a TX-start stand-in replicating fwohci_start_atq/fwohci_txd dequeue+FWXF_SENT, and RRESB injection through production fw_rcv (fwohci_arcv equivalent). Fixed vs the first draft: fwdev.fc must be set (fwmem_xfer_req copies it), _hw_firewire_fwmem and fwmem_debug sysctl renames, rresb has no dest_hi/lo members, kthread_create signature.", "attempts": 3, "guest_uname": "DragonFly dfbsd 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": 120, "guest_dirty": 0, "build_cmd": "cd /root/df5547 && sh build.sh # compiles production firewire.c + fwmem.c into df5547.ko", "run_cmd": "kldload /root/df5547/df5547.ko && sysctl kern.df5547_test=1 && sleep 1 && dmesg | grep -E bytes past buffer|N\\[0..3\\]", "code_hash": "e26e27bb0a2ca070737e8fccc2e330e0b6d94aa1dfeb0bd2f6a50b79d81e8cbe", "notes": "guest reset (with-src) after the run; /usr/src verified pristine (md5 737a55f06e1365e04c5ac2f4e3b96ec3)", "recommended_fix": "fwmem.c:197: pass len (not roundup2(len,4)) as the receive length: fwmem_xfer_req(fwdev, sc, spd, 0, len, hand)", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "with fix.diff applied to the guest /usr/src and the module rebuilt from the patched production file, the identical trigger shows zero writes past any buffer (all lengths no change) while the RPC still completes (biodone=1, b_error=0, resid=0)", "fix_evidence": "fix_validation.log (verbatim before/after matrix); fix.diff (single hunk)" } |