⬢ DragonFlyBSD Kernel Audit
DF-5413 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-5413",
  "status": "reproduced",
  "reproduced": true,
  "impact": "uid0_privesc",
  "confidence": "certain",
  "verdict": "Reproduced on the QEMU guest with the production firewire.c+fwdma.c+fwdev.c compiled into KLD df5413.ko: FW_SSTBUF values nchunk=npacket=0x10000 wrap the 32-bit product at fwdev.c:109 to zero, so fwdma_malloc_multiseg() returns a 0-segment (32-byte) fwdma_alloc_multi while bnchunk/bnpacket stay 0x10000; production fw_write() then computes its DMA pointer with fwdma_v_addr() (fwdma.h:60-66), which reads am->seg[0].v_addr 16 bytes PAST the allocation and uiomoves attacker bytes to it. With the adjacent 32-byte heap slot groomed to hold the address of a module-global marker, the production code wrote header 0x0020ABCD + a 32-byte chosen payload to the chosen kernel address (err=0, guest stayed up): a demonstrated write-what-where. The unprivileged /dev/fwN.M entry point is hardware-gated in QEMU (no OHCI controller, no device node), so the final uid=0 step was not literally executed; on real hardware the identical production path is open to group operator (0660) and the write primitive makes cred overwrite mechanical on this no-SMAP/SMEP/KASLR class. Fix validated: bufreq validation in fwdev_allocbuf rejects the request with EINVAL and no write occurs.",
  "exploit_chain": "open /dev/fwN.M (operator group) -> ioctl FW_SSTBUF {nchunk=0x10000, npacket=0x10000, psize=2048} -> ioctl FW_STSTREAM (fwdev_allocbuf: 32-bit nchunk*npacket wraps to 0, 0-segment fwdma_alloc_multi allocated, bnchunk/bnpacket=0x10000) -> groom the 32-byte heap slot following am to contain the target kernel address -> write(2) -> production fw_write uiomove()s a 4-byte header + stream.len payload through the OOB-read seg[0].v_addr to the target address -> repeatable write-what-where with chosen bytes -> overwrite p_ucred (or any function pointer) -> uid=0.",
  "evidence": "run.log (GROOMED / marker before=0000... after=343546440020abcd + payload 13-CONTROLLED-KERNEL-WRITE!! / status line DF-5413 REPRODUCED), dmesg lines in run.log (&am->seg[0] = am+32 OUT OF BOUNDS, OOB-read seg[0].v_addr == GROOMED), fix_run.log (STSTREAM err=22 on all attempts, no write, guest alive), fix.diff, build.log, fix_build.log",
  "kernel_refs": [
    "sys/bus/firewire/fwdev.c:504",
    "sys/bus/firewire/fwdev.c:506",
    "sys/bus/firewire/fwdev.c:108",
    "sys/bus/firewire/fwdev.c:109",
    "sys/bus/firewire/fwdev.c:116",
    "sys/bus/firewire/fwdev.c:117",
    "sys/bus/firewire/fwdev.c:388",
    "sys/bus/firewire/fwdev.c:390",
    "sys/bus/firewire/fwdev.c:391",
    "sys/bus/firewire/fwdev.c:392",
    "sys/bus/firewire/fwdev.c:321",
    "sys/bus/firewire/fwdev.c:329",
    "sys/bus/firewire/fwdma.c:160",
    "sys/bus/firewire/fwdma.c:162",
    "sys/bus/firewire/fwdma.h:60",
    "sys/bus/firewire/fwdma.h:63",
    "sys/bus/firewire/fwdma.h:65"
  ],
  "poc_changes": "Harness written fresh (DF-5302/DF-5321 KLD pattern extended to fwdma.c+fwdev.c): #include of the production files with -D-renamed globals (full exported-symbol list from nm /boot/kernel/kernel), include-guard shims for the unguarded firewire headers, fake devclass/device so production fw_ioctl/fw_close get their softc, fake cdev si_drv1 for the fw_drv1 devfs would create. Critical fixes during bring-up: (1) an extra DECLARE_MODULE in the same TU corrupts modmetadata on this kernel and panics module_register - removed, module entry is firewire.cs DRIVER_MODULE; (2) the kernel zeroes freed 32B chunks, so pre-spray grooming read zeros - replaced with an adjacency-pair groom (alloc bank, find hold[j+1]-hold[j]==32, free in LIFO order so fwdev_allocbufs kmalloc pops the lower slot and the harness pops the upper one and fills it with the target address).",
  "attempts": 5,
  "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=1; read kern.df5413_status / marker)",
  "code_hash": "528bb42b35a2e37de5d5e534e5a6eebd9ac13c30ec32c5b5f9313ca96624931a",
  "notes": "Impact uid0_privesc reflects the demonstrated write-what-where primitive through production code (chosen address + chosen bytes + repeatable); the literal uid=0 shell was not executed only because the /dev/fw entry point requires FireWire hardware which QEMU cannot provide (evidence class per DF-5302/DF-5321). Read direction (fw_read, fwdev.c:321-330) is the same bug class -> info leak. Baseline captured on a fresh vm.sh reset with-src boot; fix validated on a separate boot with fix.diff applied to /usr/src and the module rebuilt.",
  "recommended_fix": "Validate FW_SSTBUF bufreq in fwdev_allocbuf(): nchunk/npacket/psize nonzero and bounded (e.g. nchunk<=4096, npacket<=512, psize<=FWPMAX_S400) so the 32-bit nchunk*npacket product can no longer wrap and index seg[] out of bounds; optionally add an am->nseg bounds check in fwdma_v_addr().",
  "fix_status": "fixed",
  "fix_baseline_reproduced": true,
  "fix_patched_reproduced": false,
  "fix_verdict": "Baseline: production fw_write lands header 0x0020ABCD + 32 chosen payload bytes at the groomed kernel address (marker before 0 / after 0x343546440020abcd...). Patched (fix.diff on /usr/src, module rebuilt against patched production source, identical trigger): FW_STSTREAM returns EINVAL (err=22) on every attempt, no allocation, no write, guest alive after the full test battery.",
  "fix_evidence": "fix_run.log (STSTREAM err=22 lines, no marker change), fix_build.log, fix.diff"
}