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)
PoC verification
Evidence pack
findings/poc/DF-1097 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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.
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) readschild->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 fromdev->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_testablecompile 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.
No comments yet.