β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-5116

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5116 Β· 6 files
FileTypeDescriptionSize
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 hammer
  • env.txt β€” live guest proof of FireWire absence
  • fix.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.

VERDICT.md
↓ download raw

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

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_testable
baseline no→ patch + rebuild →patched clean

fix.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
↓ fix.diffper-fix-DF-5116

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.