# DF-5116 — dcons_crom detach races unlocked post_busreset dispatch: callback on freed softc (interrupt-context UAF write)

## Verdict: UNTESTED — trigger requires FireWire hardware (spontaneous bus reset racing a root kldunload); the audit guest has no OHCI controller (proved below). Mechanism is certain from the source; practical exploitation needs the race won.

## Where

- `sys/dev/misc/dcons/dcons_crom.c:190-207` (`dcons_crom_detach`, unsynchronized `sc->fd.post_busreset = NULL` at :196)
- `sys/bus/firewire/firewire.c:652-658` (`fw_busreset` dispatch loop: snapshot children → `if (fdc->post_busreset != NULL) fdc->post_busreset(fdc)` — **no lock**)
- Dispatch contexts: `sys/bus/firewire/fwohci.c:1798` (fwohci interrupt handler on `OHCI_INT_PHY_BUS_R` — any *physical* bus reset: cable event, PHY reset, power glitch) and `sys/bus/firewire/firewire.c:412` (fw_attach initial reset).
- Consumer side of the race: `sys/dev/misc/dcons/dcons_crom.c:102-122` (`dcons_crom_post_busreset` writes `bzero(&sc->unit, ...)` at :113 and `crom_add_chunk/crom_add_entry/crom_add_simple_text` using `&sc->unit/&sc->spec/&sc->ver` at :115-121).

## Why the window is wide

DragonFly's device state enum has **no DS_DETACHING** (`sys/sys/bus.h:76-82`:
DS_NOTPRESENT, DS_ALIVE, DS_INPROGRESS, DS_ATTACHED, DS_BUSY) — a device
remains `>= DS_ATTACHED` for the entire duration of its detach. So
`fw_busreset`'s per-child guard (firewire.c:654) passes for a child that is
mid-detach; the only protection is the NULL check of `post_busreset`, and
that store (dcons_crom.c:196) has no common lock with the load+call
(firewire.c:656-657). Nothing takes a reference on the device or softc.

## Failure sequence

```
fwproc/ithread (fw_busreset)              kldunload thread
---------------------------------------   ------------------------------------
device_get_children(fc->bdev) snapshot
for i: state(ipsdev/fwdev) >= DS_ATTACHED
  fdc = device_get_softc(devlistp[i])
                                          dcons_crom_detach(dev)
                                            sc->fd.post_busreset = NULL
                                            ... detach completes ...
                                          (module unload, devclass unit delete:
                                           softc freed)
  fdc->post_busreset != NULL  (stale read)
  fdc->post_busreset(fdc)  -------------------> dcons_crom_post_busreset on
                                               FREED softc: bzero(&sc->unit)
                                               + crom_add_* writes  ==> UAF WRITE
```

The victim object is a ~100-byte device softc; the write pattern is a
bzero followed by chunk/key quadlet stores — freed-slab corruption from
interrupt context (crash is the realistic outcome; controlled corruption
theoretical).

## Trigger (needs hardware)

```sh
# loop unload/reload while the peer resets the bus (cable pull, or a second
# node sending PHY reset)
while true; do kldload dcons_crom; kldunload dcons_crom; done &
# repeatedly unplug/replug the FireWire cable
```

## Why untested on this guest

No OHCI controller (no class 0x0c0010 device in pciconf, no /dev/fw*), so no
firewire bus device exists and `dcons_crom` never instantiates
(`bus_generic_identify_sameunit` only runs under an attached firewire
device). dcons_crom.ko + dcons.ko load cleanly and idle — see env.txt.

## Family note

`sbp.c:1924` assigns `sbp->fd.post_busreset = sbp_post_busreset` and its
detach never clears it at all — same dispatch-race class, worse; that is a
separate file and out of this finding's scope.

## Fix

Reference-count the dispatch (`fd_cb_ref` on `struct firewire_dev_comm`),
take the reference before the NULL check in `fw_busreset`, and have
`dcons_crom_detach` clear the callback and drain the refcount before
returning — see fix.diff (authored statically; not buildable here).

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
