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

Use-after-free and double-free in fwohci_pci_add_child error path

Summary

fwohci_pci_add_child at fwohci_pci.c:446-447 error path calls fwohci_pci_detach(parent) which at :351-354 does device_delete_child(self, sc->fc.bdev) where sc->fc.bdev == child (set at :439). device_delete_child calls kobj_delete -> kfree(child) at subr_bus.c:1306. Then line 447 device_delete_child(parent, child) dereferences freed child (UAF read of child->state and child->link), corrupts children TAILQ, and kfree(child) again (double-free). Triggered when device_probe_and_attach(child) fails during fwohci_pci_add_child invoked from bus_generic_probe during attach. Currently firewire probe/attach always succeed; requires unusual conditions (memory pressure causing newbus allocation failure, malicious FireWire PCI controller hot-plug). Impact ranges from panic (DoS) to potential code execution via corrupted freelist.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1097 Β· 8 files
FileTypeDescriptionSize
df1097.c trigger-source documentation-only source trace stub 1.6 KB view raw
build.sh build-script no compilation needed (latent) 359 B view raw
run.sh run-script no runtime trigger 261 B view raw
fix.diff suggested-fix clear sc->fc.bdev before fwohci_pci_detach to prevent double-free 715 B view raw
VERDICT.md verdict full source-level trace + Phase 6 hard-blocker analysis 4.5 KB ↓ raw
README.md readme claim summary + reproducibility notes 2.8 KB ↓ raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
README.md readme claim summary + reproducibility notes
↓ download raw

DF-1097 β€” UAF/double-free in fwohci_pci_add_child error path

Claim

fwohci_pci_add_child() at sys/bus/firewire/fwohci_pci.c:442-447:

err = device_probe_and_attach(child);
if (err) {
    device_printf(parent, "probe_and_attach failed with err=%d\n", err);
    fwohci_pci_detach(parent);          /* line 446 */
    device_delete_child(parent, child); /* line 447 β€” child already freed */
    return NULL;
}

sc->fc.bdev is set to child at line 439. fwohci_pci_detach(parent) enters its body at fwohci_pci.c:342, and at lines 351-354 calls device_delete_child(self, sc->fc.bdev). device_delete_child at sys/kern/subr_bus.c:1284-1309 ends with kobj_delete((kobj_t)child, M_BUS) at subr_bus.c:1306 β†’ child is kfree()'d. It also TAILQ_REMOVE's child from dev->children (subr_bus.c:1304) and bus_data_devices (subr_bus.c:1305).

Then control returns to fwohci_pci_add_child line 447, which calls device_delete_child(parent, child) on the now-freed pointer: - device_detach(child) reads child->state (UAF read) - TAILQ_FIRST(&child->children) dereferences freed memory (UAF read) - child->devclass (UAF read) - TAILQ_REMOVE(&dev->children, child, link) corrupts the parent's children list (the link was already removed) β€” use-after-free with list corruption. - kobj_delete((kobj_t)child, M_BUS) at subr_bus.c:1306 β€” double-free.

Trigger

Reachable only when device_probe_and_attach(child) fails inside fwohci_pci_add_child. The firewire (fwohci) driver invokes fwohci_pci_add_child as its bus_add_child method during attach (fwohci_pci.c:473). On production hardware the firewire child always probes and attaches successfully, so the error path is never exercised. Realistic triggers: 1. Memory pressure during newbus allocation in the child's attach path. 2. A malicious/buggy FireWire OHCI PCI controller that probes but fails to attach (hot-plug via Thunderbolt/ExpressCard/PCIe).

Reproducibility on this guest

The default audit guest has no FireWire OHCI PCI device (QEMU ships no FW-OHCI device model). pciconf -lv shows no FireWire class (0x0c00) device and dmesg | grep -i fwohci is empty. The driver is compiled into the GENERIC kernel but never attaches, so fwohci_pci_add_child is never even called. The bug is latent β€” confirmed by source trace but not runtime-triggerable on this guest.

A realistic runtime trigger would require either a physical FireWire PCI card or a QEMU patch adding an FW-OHCI device model. Neither is available in the audit environment. See VERDICT.md for the full source-level trace.

Fix

fix.diff: clear sc->fc.bdev before calling fwohci_pci_detach so the detach's device_delete_child(self, sc->fc.bdev) is a no-op, leaving the explicit device_delete_child(parent, child) at line 447 as the sole delete.

VERDICT.md verdict full source-level trace + Phase 6 hard-blocker analysis
↓ download raw

DF-1097 β€” VERDICT

Verdict: NOT REPRODUCED at runtime (latent β€” requires FireWire OHCI hardware not present in the audit guest). Bug confirmed by source trace.

Mechanism (source-confirmed)

fwohci_pci_add_child() at sys/bus/firewire/fwohci_pci.c:442-447:

err = device_probe_and_attach(child);
if (err) {
    device_printf(parent, "probe_and_attach failed with err=%d\n", err);
    fwohci_pci_detach(parent);          /* :446 */
    device_delete_child(parent, child); /* :447 */
    return NULL;
}

sc->fc.bdev is assigned to child at fwohci_pci.c:439 before device_probe_and_attach(child) runs. On failure, line 446 calls fwohci_pci_detach(parent), which at fwohci_pci.c:351-353 executes:

if (sc->fc.bdev) {
    device_delete_child(self, sc->fc.bdev);   /* frees child */
    sc->fc.bdev = NULL;
}

device_delete_child at sys/kern/subr_bus.c:1284-1309 ends at subr_bus.c:1306 with kobj_delete((kobj_t)child, M_BUS); β€” child is kfree()'d. It also TAILQ_REMOVE(&dev->children, child, link) at subr_bus.c:1304 and TAILQ_REMOVE(&bus_data_devices, child, devlink) at subr_bus.c:1305.

Control then returns to fwohci_pci.c:447, which calls device_delete_child(parent, child) on the now-freed child:

  • device_detach(child) (subr_bus.c:1292) reads child->state β€” UAF read.
  • TAILQ_FIRST(&child->children) (subr_bus.c:1296) dereferences freed memory β€” UAF read.
  • child->devclass (subr_bus.c:1302) β€” UAF read.
  • TAILQ_REMOVE(&dev->children, child, link) (subr_bus.c:1304) operates on a child already removed from dev->children β€” corrupts the parent's TAILQ (use-after-free with list corruption).
  • kobj_delete((kobj_t)child, M_BUS) (subr_bus.c:1306) β€” double-free.

Why it can't be triggered on this guest

The default audit QEMU guest is launched with -device virtio-net-pci and no FireWire PCI device. QEMU ships no FW-OHCI device model at all (no -device option for it exists upstream). Verified:

$ dmesg | grep -iE 'fwohci|firewire'
(empty)
$ pciconf -lv | grep -i 'class: 0x0c00'
(empty)

The fwohci driver IS compiled into X86_64_GENERIC (it's not a loadable module), but fwohci_pci_attach is never called without a FireWire PCI device, so fwohci_pci_add_child (its bus_add_child method) is never reached. The error path is therefore unreachable from any userspace action on this guest.

Realistic triggers (all out of scope of an audit guest): 1. A real FireWire OHCI PCI controller on the host (rare on modern HW). 2. A malicious/buggy FW-OHCI PCI device hot-plugged via Thunderbolt / ExpressCard / PCIe that probes as fwohci but fails to attach its firewire child (e.g. due to memory pressure during newbus allocation).

Exploit chain

Primitive class: UAF + double-free in newbus. In principle a UAF on a device_t object could be converted to a controlled write via slab-grooming + re-claiming the freed device_t with a victim object containing a function pointer. BUT:

  • The primitive is reachable only from the kernel FireWire attach path, which requires either a malicious PCI device on the bus or memory pressure during firewire attach (not reproducible on the audit guest).
  • Even with FireWire hardware, the trigger requires device_probe_and_attach(child) to fail in a specific way that the normal firewire stack won't reproduce β€” realistic only for a malicious controller.

This is a valid hard blocker ("vulnerable code path is dead code at runtime on this guest AND no harness can exercise it"): the path is real in source but unreachable without hardware the audit guest lacks. Documented as latent. No uid=0 achievable on this guest.

Fix

fix.diff clears sc->fc.bdev = NULL; before the detach call, so the detach's device_delete_child(self, sc->fc.bdev) is skipped. The explicit device_delete_child(parent, child) at line 447 then becomes the sole delete β€” no UAF, no double-free. Validated as applies + compiles in a clean GENERIC kernel build.

Fix validation

not_testable β€” the path is not runtime-reachable on this guest (no FireWire hardware). Validated fix.diff applies cleanly with patch -p1 --dry-run and compiles with -Werror in a full make nativekernel KERNCONF=X86_64_GENERIC (the four target files fwohci_pci.c, xhci_pci.c, smbacpi.c, acpi_sdt.c were all rebuilt fresh and produced no warnings/errors β€” see ../DF-1096/all_fixes_build.log).

PoC changes

  • Wrote df1097.c (documentation-only stub explaining the source trace).
  • Wrote fix.diff, build.sh, run.sh.

Fix verification

not_testable

compile validated

see evidence pack

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source-confirmed. fwohci_pci_add_child detach->delete_child UAF+double-free. No FireWire HW. Compile validated.