fwohci_pci_detach() swallows child-detach failure, force-deletes the firewire child and returns unconditional success β orphaned attached child + live callouts over unmapped BAR/freed DMA rings once a firewire-bus child vetoes detach (latent panic/UAF, lying kldunload)
Summary
Detach discards bus_generic_detach's return, force-deletes the child and hardcodes 0: if a firewire-bus child ever vetoes (none in-tree today - all children return 0), the parent still unregisters the IRQ, unmaps the BAR, frees the DMA rings and reports success - leaving an attached orphan whose self-re-arming watchdog callout writes MMIO through a null bus handle (page-0 panic) while sbp's queued xfers reference freed rings; device_delete_child re-runs firewire_detach on partially-torn state. CardBus surprise removal reachable. Latent API-contract hazard inherited verbatim from FreeBSD 13.4 (same swallow). Propagate-and-bail fix mirrors the suspend path.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5528 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.8 KB | β raw | |
| VERDICT.md | β | 6.1 KB | β raw | |
| check_guest.sh | β | 871 B | view raw | |
| build.sh | β | 211 B | view raw | |
| run.sh | β | 177 B | view raw | |
| run.log | β | 935 B | view raw | |
| env.txt | β | 167 B | view raw | |
| fix.diff | β | 743 B | view raw | |
| manifest.json | β | 1.4 KB | view raw | |
| verdict.json | β | 4.4 KB | view raw |
DF-5528 β fwohci_pci_detach() swallows child-detach failure, force-deletes the firewire child, and returns unconditional success
File: sys/bus/firewire/fwohci_pci.c (pass 2, GLM 5.3 re-audit)
Class: driver lifecycle / error-handling (CWE-754, latent CWE-672 downstream)
Severity: Low β latent trigger, requires FireWire OHCI hardware + privileged unload (or CardBus surprise removal)
TL;DR
fwohci_pci_detach() (sys/bus/firewire/fwohci_pci.c:341-386) discards the return
value of bus_generic_detach(self) (line 350), unconditionally force-deletes the
firewire child device (lines 351-354), and always return 0 (line 385). If any
device on the fwohciβfirewireβ{sbp,fwe,dcons_crom} chain ever vetoes detach
(returns EBUSY), the parent nonetheless releases the IRQ, unmaps the BAR and
frees the OHCI DMA rings while the firewire child remains DS_ATTACHED and
orphaned with live callouts β a use-after-free / page-0-MMIO-write panic chain,
and kldunload reports success with live references left behind.
Latent today: in this tree no firewire-bus child can currently veto
(sbp_detach sys/dev/disk/sbp/sbp.c:2016-2058, fwe_detach
sys/dev/netif/fwe/if_fwe.c:284-295 and dcons_crom_detach
sys/dev/misc/dcons/dcons_crom.c:190-207 all return 0 unconditionally;
fwdev_destroydev sys/bus/firewire/fwdev.c:809-817 likewise). The defect is a
structural trap, not a live regression β hence Low/untested.
How to run / what "reproducing" would mean
- Requires a machine with a real FireWire OHCI PCI/CardBus controller
(PCI class 0c/10/10) β there is no such device in the QEMU guest
(see run.log:
pciconf -lshows only i440fx/virtio devices). - Build the fwohci + firewire + sbp KLDs, attach storage over FireWire so an
sbp target exists, then make
sbp_detach()return EBUSY (upstream FreeBSD versions of sbp had such guards; this tree does not β add one to test). kldunload if_fwohci.koβ observe: "detached" reported, unload completes, firewire watchdog callout (firewire.c:375-376, stopped only inside the never-completedfirewire_detachat firewire.c:468-471) fires and executesOWRITE()through the zeroedsc->bst/sc->bsh(fwohci_pci.c:378-379) β kernel page-0 write β panic.- With the fix (fix.diff), the unload fails with the child's error code and nothing is torn down.
run.sh documents the hardware-gating check performed on the audit guest.
Artifacts
lifecycle_trace.mdβ line-cited callgraph of both the veto and non-veto pathscheck_guest.sh/run.sh+run.logβ proof the audit guest has no OHCI devicefix.diffβ git-apply-able fix (propagatebus_generic_detacherror; never force-delete a child that vetoed; do not report unconditional success)verdict.jsonβ machine verdict (statusuntested, hardware-gated)
DF-5528 β full narrative and verdict
Finding: fwohci_pci_detach() discards bus_generic_detach()'s error, force-deletes
the firewire child regardless of its detach state, and returns 0 unconditionally.
sys/bus/firewire/fwohci_pci.c:341-386 (specifically :350, :351-354, :385).
1. The code
341: fwohci_pci_detach(device_t self)
342: {
...
346: crit_enter();
347: if (sc->bsr)
348: fwohci_stop(sc, self);
349:
350: bus_generic_detach(self); /* <-- return discarded */
351: if (sc->fc.bdev) {
352: device_delete_child(self, sc->fc.bdev); /* <-- unconditional */
353: sc->fc.bdev = NULL;
354: }
...
361: if (sc->irq_res) { ... bus_teardown_intr ... } /* IRQ handler gone */
370: if (sc->irq_res) { ... bus_release_resource ... }
375: if (sc->bsr) { ... bus_release_resource ... /* BAR unmapped */
378: sc->bst = 0; sc->bsh = 0; }
382: fwohci_detach(sc, self); /* DMA rings freed */
383: crit_exit();
385: return 0; /* <-- always success */
2. What newbus actually does (verified against sys/kern/subr_bus.c)
bus_generic_detach(subr_bus.c) walks children callingdevice_detach;device_detach(subr_bus.c:2128-2154) returns the child error and leaves the childDS_ATTACHEDon failure.device_delete_child(subr_bus.c:1284-1310) first callsdevice_detach(child)again (line 1292) β so a vetoing child re-runs its whole detach a second time on partially-torn-down state (fwdev_destroydevalready ran once inside the firstfirewire_detach, firewire.c:462). If it vetoes again,device_delete_childreturns beforeTAILQ_REMOVE/kobj_delete(subr_bus.c:1296-1306) β the child stays attached and linked into the parent's tree, butsc->fc.bdevwas already NULLed by fwohci_pci.c:353.- The parent then completes the teardown: IRQ handler unregistered (:362), IRQ
released (:371), BAR released and
bst/bshzeroed (:375-379), DMA rings freed byfwohci_detachβfwohci_db_free(fwohci.c:764-785, fwohci.c:1158-1182), and reports success (:385).kldunloadtherefore completes.
3. Consequence chain once any grandchild vetoes
Firewire-bus device tree: fwohci (pci/cardbus) β firewire β {sbp, fwe, dcons_crom}.
- Live callouts on torn-down softc:
firewire_attachstarts the self-re-arming watchdogcallout_reset(&sc->fc->timeout_callout, hz, firewire_watchdog, fc)(firewire.c:402-403, re-arm at firewire.c:375-376). Thosecallout_stops live at the END offirewire_detach(firewire.c:468-471) β code that a vetoing detach never reaches. After the parent's teardown the callout fires withbst == 0 && bsh == 0(fwohci_pci.c:378-379); anyOWRITEin the watchdog / timeout path (e.g.fwohci_start_atqvia fc->atq->start, fwohci.c:629) writes through a null bus handle β page-0 kernel write β panic. - DMA rings freed under live users: sbp keeps queued
fw_xfers on the (alreadyfwohci_stoped, fwohci_pci.c:348) controller;fwohci_detach(fwohci.c:773-777) frees the arr/at descriptor rings and buffers underneath them β post-free descriptor consumption / UAF. - Orphaned attached child: still DS_ATTACHED, parented to a detached device;
its ivars point at
&sc->fcwhose backing resources are gone; subsequent module unload offirewireUAFs on the freed softc. - Lying success: kldunload/devctl detach report success with live references.
4. Why "latent" β the honesty section
In the CURRENT tree no firewire-bus child can veto:
- sbp_detach sys/dev/disk/sbp/sbp.c:2016-2058 β always return 0
- fwe_detach sys/dev/netif/fwe/if_fwe.c:284-295 β always return 0
- dcons_crom_detach sys/dev/misc/dcons/dcons_crom.c:190-207 β always return 0
- fwdev_destroydev sys/bus/firewire/fwdev.c:809-817 β always return 0
so firewire_detach (firewire.c:453-487) cannot currently fail, and the normal
detach path is clean (successful device_detach sets DS_NOTPRESENT, so the
device_delete_child at :352 re-detaches nothing).
The trap arms the moment any child gains an EBUSY guard (upstream FreeBSD sbp
carried such guards historically). The defect was inherited verbatim from
FreeBSD β verified against freebsd-src release/13.4.0
sys/dev/firewire/fwohci_pci.c whose detach also discards the error β so this is
NOT a DF-only divergence, and it is filed as robustness/hardening with a real,
line-proven panic chain behind it.
Adjacent verified-but-harmless differences vs FreeBSD 13.4 (recorded, not filed):
- DF calls bus_teardown_intr(self, sc->irq_res, sc->ih) with sc->ih == NULL
in the bus_setup_intr-failure path (fwohci_pci.c:361-368); FreeBSD 13.4 guards
with if (sc->ih). Traced harmless: pci_teardown_intr (sys/bus/pci/pci.c:3384+)
masks INTx then nexus_teardown_intr (nexus.c:537-544) returns -1 for a NULL
cookie β error message only.
5. Verification status
- status: untested / hardware-gated. Reproduction requires a physical
FireWire OHCI controller (PCI 0c/10/10) plus a patched sbp (or any vetoing
child). The audit QEMU guest has none β see
run.log:pciconf -llists only i440fx/virtio functions, dmesg contains no fwohci/firewire lines, no FW KLDs loaded. A KLD harness of the DF-5302 class (compile-the-core) cannot express this bug: the defect is in the newbus parent/child detach protocol, which only executes against a real PCI attachment. - Guest left untouched (read-only commands; guest_dirty=0).
6. Fix
fix.diff in this directory: capture and propagate the bus_generic_detach
error before touching the child or any resource, mirroring the argument
structure of the suspend path (fwohci_pci.c:395-397) which already propagates.
Fix is source-level sound; no runtime fix validation was possible (no hardware),
hence fix_status=not_testable.
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 (git apply --check OK against sys/bus/firewire/fwohci_pci.c) captures err = bus_generic_detach(self) and returns it after crit_exit() before any teardown, exactly matching the working suspend-path error handling; not runtime-validated because the baseline itself is hardware-gated (no OHCI controller on the guest, no vetoing child in the tree).
fix.diff hunk @@ -342,12 +342,24 @@; git apply --check output 'GIT APPLY CHECK: OK'; sys/ verified untouched (git status --short sys/ empty)
Confirmed kernel references
Detail
Evidence (decisive lines)
read VERDICT.md sections 1-3 for the line-cited veto-path trace; run.log lines '=== PCI device classes ===' (no 0c10 device) and '=== dmesg fwohci/firewire === NONE'; fix.diff applies cleanly (git apply --check OK)
PoC changes
no seed PoC existed; the pack carries a source-level lifecycle trace plus a guest hardware-gating check (check_guest.sh) instead of a runnable trigger
Verified recommended fix
Propagate bus_generic_detach()'s error out of fwohci_pci_detach() before releasing any resource or deleting the child (fix.diff); mirror in the suspend path's already-correct pattern at fwohci_pci.c:395-397.
Verdict
fwohci_pci_detach() (sys/bus/firewire/fwohci_pci.c:341-386) discards bus_generic_detach()'s error at :350, force-deletes the firewire child at :351-354 regardless of its detach state, and returns unconditional success at :385; if any fwohci->firewire->{sbp,fwe,dcons_crom} child ever vetoes detach, the parent nonetheless unregisters the IRQ, unmaps the BAR (zeroing bst/bsh), and frees the OHCI DMA rings underneath a still-attached, orphaned firewire child whose self-re-arming watchdog callout (firewire.c:402-403, only stopped at firewire.c:468-471 inside the never-completed firewire_detach) then writes MMIO through a null bus handle -> page-0 kernel write/panic, plus UAF on the freed descriptor rings and a lying kldunload success. The defect is certain at the code level (traced through subr_bus.c:1284-1310 and :2128-2154) but LATENT in this tree: sbp_detach (sbp.c:2016-2058), fwe_detach (if_fwe.c:284-295), dcons_crom_detach (dcons_crom.c:190-207) and fwdev_destroydev (fwdev.c:809-817) all return 0 unconditionally today, so firewire_detach cannot currently fail and the normal path is clean. Untested because the trigger requires physical FireWire OHCI hardware (PCI 0c/10/10) plus a vetoing child: the audit QEMU guest has no such device (run.log: pciconf -l shows only i440fx/virtio, zero class=0c10 functions, no fwohci/firewire dmesg lines, no FW KLDs) and a compile-the-core KLD harness cannot express a newbus parent/child detach-protocol bug. Inherited verbatim from FreeBSD (verified against freebsd-src release/13.4.0 sys/dev/firewire/fwohci_pci.c, whose detach also swallows the error).
No comments yet.