dcons_crom_detach NULLs post_busreset with no synchronization against fw_busreset's unlocked check-then-call dispatch running in the fwohci interrupt handler β a physical bus reset racing kldunload executes dcons_crom_post_busreset on freed softc: interrupt-context UAF write (bzero of sc->unit + crom_add_* stores)
Summary
firewire.c snapshots children and calls post_busreset with no lock from the fwohci ISR on every PHY bus reset; no DS_DETACHING state exists so mid-detach children still pass the guard - the NULL clear races the load+indirect call, and a winning dispatch bzero/crom_add-writes into the freed ~100B slab from interrupt context. Root+FireWire-hw+race gated (no OHCI on guest). Ref-count-and-drain fix (3 files).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5116 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.3 KB | β raw | |
| VERDICT.md | β | 4.1 KB | β raw | |
| trigger.sh | β | 1.2 KB | view raw | |
| env.txt | β | 1.0 KB | view raw | |
| fix.diff | β | 1.7 KB | view raw | |
| verdict.json | β | 3.4 KB | view raw |
DF-5116 evidence pack
Finding: dcons_crom_detach (dcons_crom.c:196) clears post_busreset
with no synchronization against fw_busreset's dispatch loop
(firewire.c:652-658), which runs in the fwohci interrupt handler on every
physical bus reset (fwohci.c:1798). DragonFly has no DS_DETACHING state, so a
mid-detach child still passes the >= DS_ATTACHED guard; a dispatch that has
already loaded the non-NULL pointer proceeds to call
dcons_crom_post_busreset on a softc freed by the completed detach/unload β
interrupt-context UAF write (bzero(&sc->unit) + crom_add_* stores).
Status: untested β needs FireWire hardware and a won race; the audit
guest has no OHCI controller (env.txt).
Contents
VERDICT.mdβ full interleaving trace and family notes (sbp twin)trigger.shβ hardware-gated unload/reload vs bus-reset hammerenv.txtβ live guest proof of FireWire absencefix.diffβ fw-core dispatch reference count + detach drain (firewirereg.h / firewire.c / dcons_crom.c), authored statically
Reproduce (on real hardware only)
sh trigger.sh # + generate bus resets (cable events) during the loop
Success criterion: panic with dcons_crom_post_busreset / crom_add_* in the
backtrace while dcons_crom.ko is unloaded.
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, unsynchronizedsc->fd.post_busreset = NULLat :196)sys/bus/firewire/firewire.c:652-658(fw_busresetdispatch 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 onOHCI_INT_PHY_BUS_Rβ any physical bus reset: cable event, PHY reset, power glitch) andsys/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_busresetwritesbzero(&sc->unit, ...)at :113 andcrom_add_chunk/crom_add_entry/crom_add_simple_textusing&sc->unit/&sc->spec/&sc->verat :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)
# 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.
Fix verification
not_testablefix.diff (firewirereg.h + firewire.c + dcons_crom.c) implements the take-ref-before-check / clear-and-drain pattern; it cannot be build- or race-validated on this guest because no FireWire controller exists to instantiate the dispatch. Authored statically against the read-only sys/ tree; never applied.
fix.diff; VERDICT.md failure-interleaving section
Confirmed kernel references
Detail
Exploit chain
requires FireWire hardware + root kldload/kldunload dcons_crom loop racing physical bus resets -> fw_busreset dispatch vs dcons_crom_detach -> post_busreset called on freed softc -> interrupt-context UAF write (panic; heap-groomed corruption theoretical)
Evidence (decisive lines)
VERDICT.md (interleaving trace with path:line); env.txt (no class 0x0c0010 device, no /dev/fw*, dcons_crom.ko loads but identify never runs); trigger.sh (hardware-gated race hammer)
PoC changes
n/a - no seed could run: guest lacks FireWire controller; trigger.sh encodes the hardware-gated race hammer instead
Verified recommended fix
Reference-count the fw_busreset child dispatch (fd_cb_ref taken before the post_busreset NULL check) and make dcons_crom_detach clear the callback and drain the refcount before returning.
Verdict
Mechanism is certain in code and the window is wide: dcons_crom.c:196 NULLs sc->fd.post_busreset with no lock, while fw_busreset (firewire.c:652-658, invoked from the fwohci interrupt handler at fwohci.c:1798 on every physical bus reset) snapshots children, passes the >= DS_ATTACHED guard for the whole duration of a concurrent detach (no DS_DETACHING state exists in sys/sys/bus.h:76-82), and makes an unlocked check-then-indirect-call on the same pointer. A dispatch that reads non-NULL before the store proceeds to run dcons_crom_post_busreset (dcons_crom.c:102-122) on softc freed right after dcons_crom_detach returns (module unload deletes the unit): bzero(&sc->unit) at :113 and crom_add_* stores via &sc->spec/&sc->ver at :115-121 become interrupt-context UAF writes into a freed ~100-byte slab - realistically a crash, theoretically groomable corruption. Not reproduced: the audit guest has no OHCI FireWire controller (pciconf/env.txt), so dcons_crom never instantiates and neither side of the race can execute. Root (kldunload) + hardware + race cap severity at Low.
No comments yet.