{
  "finding_id": "DF-5548",
  "status": "reproduced",
  "reproduced": true,
  "impact": "unsynchronized si_drv1/refcount lifecycle: live open/close race -> Fatal trap 12 (NULL+offsetof(refcount) deref in production fwmem_close, fwmem.c:292); post-race NULL-si_drv1 state -> supervisor WRITE at 0x0 in fwmem_ioctl (fwmem.c:410); racing ioctl/close -> 8 fully attacker-controlled bytes at offset 0 of a freed 16-byte-zone chunk == DragonFly SLAB freelist next pointer -> kmalloc hands out the attacker-chosen address / silently duplicates a live chunk (on this INVARIANTS kernel: deterministic allocator-assert panics; on production builds: silent double allocation)",
  "confidence": "certain",
  "verdict": "Reproduced three ways on production code: (1) 4 threads racing production fwmem_open/fwmem_close on one shared cdev panicked the guest in fwmem_close+0x13 (movl 0x8(%rbx),%eax, fms==NULL); (2) the post-race fd-with-NULL-si_drv1 state turns production fwmem_ioctl(FW_SDEUI64) into a supervisor write at 0x0; (3) the ioctl-vs-final-close interleaving gives an 8-byte controlled UAF write over the freed chunks slab freelist pointer, followed by kmalloc handing out the poisoned address (already allocated assert for a live-chunk target; zone-membership assert for an arbitrary module-global target). Fix (spinlock around the whole lifecycle + NULL guards + right malloc type) validated: 769,792 racing open/close pairs with zero damage vs baseline panic, and the NULL ioctl face returns ENXIO.",
  "exploit_chain": "operator-group user, FW controller present: race open() vs close() (or ioctl vs final close) on /dev/fwmem0.0 -> (a) NULL+8 write / NULL bcopy -> kernel panic DoS, or (b) FW_SDEUI64s 8 user-controlled bytes land on the freed fwmem_softc chunks slab freelist next pointer -> next-next kmalloc(16) returns the attacker-chosen kernel address or duplicates a live 16-byte-zone object -> overlap/corruption of a live kernel object (arbitrary-address allocation observed; on INVARIANTS kernels the allocator asserts panic first)",
  "evidence": "run.log: Stopped at dfw_fwmem_close+0x13: movl 0x8(%rbx),%eax (live race); fault virtual address = 0x0, supervisor write (test 3); FREED chunk ... now holds ... (== ATTACKER VALUE, slab freelist poisoned) + kmalloc#1 = ... (== freed chunk) + panic: memory chunk ... is already allocated! (test 2) and the chunk_mark_allocated zone assert (test 5); fix_validation.log: race finished, 769792 open+close pairs survived and UNREACHED: ioctl returned (ENXIO) on the fixed build",
  "kernel_refs": [
    "sys/bus/firewire/fwmem.c:266",
    "sys/bus/firewire/fwmem.c:269",
    "sys/bus/firewire/fwmem.c:270",
    "sys/bus/firewire/fwmem.c:291",
    "sys/bus/firewire/fwmem.c:292",
    "sys/bus/firewire/fwmem.c:296",
    "sys/bus/firewire/fwmem.c:297",
    "sys/bus/firewire/fwmem.c:342",
    "sys/bus/firewire/fwmem.c:407",
    "sys/bus/firewire/fwmem.c:410",
    "sys/kern/kern_slaballoc.c:1659"
  ],
  "poc_changes": "no seed PoC; same DF-5413-family harness as DF-5547 (production firewire.c+fwmem.c, -D renames incl. fwmem block-RPC exports and M_FWMEM). Fixes vs drafts: racer threads must sleep 1 tick every 256 pairs (spinning LWKT kernel threads starve userland and wedge the guest without proving anything); the same-slab groom needs a 4096-chunk bank on the HEAP (a 4096-entry stack array blew the kernel stack guard) to carve fresh contiguous slab pages; the groom partner index bug (pairc vs pair+1) corrected after the first successful run",
  "attempts": 7,
  "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": 900,
  "guest_dirty": 0,
  "build_cmd": "cd /root/df5547 && sh build.sh   # same harness as DF-5547 (df5547.ko)",
  "run_cmd": "kldload ./df5547.ko; sysctl kern.df5547_test=4   # live race (panics); =2 replay poisoning (panics); =3 NULL ioctl (panics); =5 arbitrary-address variant (panics)",
  "code_hash": "e26e27bb0a2ca070737e8fccc2e330e0b6d94aa1dfeb0bd2f6a50b79d81e8cbe",
  "notes": "each fatal test followed by vm.sh reset with-src; /usr/src verified pristine afterwards (md5 737a55f06e1365e04c5ac2f4e3b96ec3); distinct from DF-5321 (tlabel/xfer completion race in firewire.c) -- same missing-serialization family, different objects and entry points; includes the DF-5549 M_FW->M_FWMEM one-liner in fwmem_close",
  "recommended_fix": "serialize the si_drv1/refcount lifecycle with a spinlock across fwmem_open/fwmem_close, take the lock (and return ENXIO on NULL) in fwmem_ioctl/fwmem_strategy; free with M_FWMEM (see fix.diff)",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff applied to guest /usr/src and module rebuilt from patched production sources: live race survived 769,792 open+close pairs across 4 threads with zero damage (baseline: Fatal trap 12 in fwmem_close within moments); the NULL-si_drv1 ioctl face returns ENXIO instead of a supervisor write at 0x0; the stepwise replay is intentionally not fix-sensitive (it hand-constructs the mid-race state), so validation used the production-path race and NULL-guard faces",
  "fix_evidence": "fix_validation.log (verbatim baseline/fixed outputs for tests 3 and 4); fix.diff (spinlock + NULL guards + M_FWMEM)"
}