cardbus_detach_card ignores device_detach/device_delete_child failures and pci_freecfg's the child devinfo anyway: surviving child keeps freed ivars (UAF) while its bus resources are ripped out from under the running driver
Summary
Both error returns are dropped yet the devinfo is unconditionally kfree'd - a busy child (NIC up / open tty) survives eject/suspend with freed ivars AND its resources released + BARs zeroed: the next driver kldload walks freed dinfo (UAF read; pci_cfg_restore writes card-controlled config into reclaimed heap) while the old driver keeps using released bus space. Hw-gated (no cbb on guest); memcorrupt class. Propagate-both-errors fix (git-apply clean).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4977 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.6 KB | β raw | |
| VERDICT.md | β | 4.9 KB | β raw | |
| trigger.md | β | 2.2 KB | β raw | |
| env.txt | β | 1.4 KB | view raw | |
| build.sh | β | 106 B | view raw | |
| run.sh | β | 142 B | view raw | |
| fix.diff | β | 1.4 KB | view raw | |
| verdict.json | β | 3.5 KB | view raw |
DF-4977 β cardbus_detach_card ignores device_detach/device_delete_child failures and frees the child devinfo anyway β dangling ivars UAF
Target file: sys/dev/pccard/cardbus/cardbus.c (pass 2, GLM 5.3)
TL;DR
cardbus_detach_card() (sys/dev/pccard/cardbus/cardbus.c:220-251):
236 for (tmp = 0; tmp < numdevs; tmp++) {
237 struct cardbus_devinfo *dinfo = device_get_ivars(devlist[tmp]);
238 int status = device_get_state(devlist[tmp]);
239
240 if (dinfo->pci.cfg.dev != devlist[tmp])
241 device_printf(cbdev, "devinfo dev mismatch\n");
242 if (status == DS_ATTACHED || status == DS_BUSY)
243 device_detach(devlist[tmp]); /* (1) error ignored */
244 cardbus_release_all_resources(cbdev, dinfo); /* (2) rips resources from a possibly-live driver */
245 device_delete_child(cbdev, devlist[tmp]); /* (3) error ignored */
246 pci_freecfg((struct pci_devinfo *)dinfo); /* (4) unconditional kfree(dinfo) */
247 }
If the child driver's device_detach at (1) fails (returns EBUSY β e.g. the device is
open/busy), DragonFly's device_delete_child() returns the error and leaves the child
device in the tree (sys/kern/subr_bus.c:1284-1293: if ((error = device_detach(child)) != 0) return(error); β
the kobj_delete/TAILQ_REMOVE happen only after a successful detach). But the loop then:
- releases every bus resource of the still-live driver and zeroes its BARs
(cardbus.c:300-311,
rle->res = NULLwhile the driver's softc still holds thestruct resource *), and pci_freecfg(dinfo)βkfree(dinfo, M_DEVBUF)(sys/bus/pci/pci.c:2139-2156), whilechild->ivars == dinfostill.
Result: a live device whose every later interaction dereferences freed memory, plus already-released MMIO/IO windows still mapped by the running driver (cross-device bus-space aliasing).
Concrete trigger (requires CardBus hardware)
- Insert a CardBus card whose driver keeps the device busy across detach β classic
candidates compiled into this kernel (see
env.txt):cardbus/fxp,cardbus/if_re,cardbus/if_dc,pccard/sio(serial: an open/dev/ttydXkeeps it busy without root on many setups; an IFF_UP NIC detach returns EBUSY in several DF drivers). - Eject the card (or suspend the machine:
cardbus_suspendβcardbus_detach_card, cardbus.c:121-126; or re-insert another card:cardbus_attach_cardcallscardbus_detach_cardfirst, cardbus.c:168). device_detach(child)returns EBUSY β child survives with freed ivars.- Any later touch of the surviving child is a UAF: e.g.
kldloada new cardbus driver βcardbus_driver_addedβdinfo = device_get_ivars(dev)+pci_print_verbose(&dinfo->pci)(cardbus.c:281-282, 287) reads freed heap; any bus method on the child (read_ivar/probe/allocation) followschild->ivars.
Why "untested" on this run
The audit QEMU guest (DragonFly 6.5-DEVELOPMENT x86_64, see env.txt) has no
CardBus bridge β pciconf -l shows only i440FX/virtio devices, no cbb. The
cardbus driver set is compiled into X86_64_GENERIC (kldstat -v shows cbb/cardbus)
but with zero cbb parent devices there is no cardbus bus instance, so
cardbus_detach_card can never execute. The defect is proven by code reading
(cardbus.c:236-247 + subr_bus.c:1284-1310 + pci.c:2139-2156); dynamic reproduction
needs physical CardBus hardware (card eject / suspend with a busy card).
Fix
See fix.diff β abort the per-child teardown (keep child + dinfo alive, propagate the
error) when either device_detach or device_delete_child fails, and only free
dinfo after the child is actually gone.
VERDICT β DF-4977
Status: untested (restricted enum; the natural-language classification is "not testable on this guest: no CardBus hardware"). Reproduced: 0.
What the defect is (certain, by code reading)
cardbus_detach_card() at sys/dev/pccard/cardbus/cardbus.c:236-247:
device_detach(devlist[tmp])(line 243) β return value ignored.cardbus_release_all_resources(cbdev, dinfo)(line 244) β releases every resource the (possibly still-attached) child driver holds and zeroes its BARs (cardbus.c:300-311;rle->res = NULLat :304,pci_write_config(..., 0, 4)at :309).device_delete_child(cbdev, devlist[tmp])(line 245) β return value ignored. DragonFly semantics:device_delete_childcallsdevice_detach(child)again and returns the error without removing the child when detach fails (sys/kern/subr_bus.c:1284-1293):c 1292 if ((error = device_detach(child)) != 0) 1293 return(error);i.e. on a busy child the device stays incbdev's child list withchild->ivars == dinfointact.pci_freecfg((struct pci_devinfo *)dinfo)(line 246) β unconditionalkfree(dinfo, M_DEVBUF)(sys/bus/pci/pci.c:2139-2156).
Net effect when a child's detach fails (EBUSY from an open device / IFF_UP NIC):
- Use-after-free of
dinfo: the surviving child's ivars point at freed memory. Next dereference:cardbus_driver_addedon any later driver kldload (cardbus.c:281-287:dinfo = device_get_ivars(dev),pci_print_verbose(&dinfo->pci),pci_cfg_restore(dinfo->pci.cfg.dev, &dinfo->pci)), or any bus method (read_ivar/write_ivar/resource ops) on the surviving child. The freedcardbus_devinfo(~0x200+ bytes, M_DEVBUF zone) is attacker-reclaimable via the usual slab grooming if the attacker can drive kernel allocations between the free and the reuse β on a machine with the hardware this is a plausible privilege- escalation primitive (ivars confusion / config-space restore of a forged pcicfgregs), but that chain was not developed here because the primitive could not even be triggered on the audit guest (no hardware). - Resource lifetime violation: the still-running child driver keeps
struct resource *pointers whose bus space was released at step 2 and whose BARs were zeroed β the windows can be re-allocated to another device while the old driver keeps doingbus_space_writeto them β cross-device MMIO aliasing (hardware-level memory corruption, device-dependent).
Trigger requirements (why untested)
- A
cbb(CardBus bridge) controller with a card inserted β the audit QEMU guest has none (env.txt:pciconf -llists only i440FX/virtio;kldstat -vshows the cardbus drivers compiled in but idle). QEMU's default machine cannot surface acbbdevice, and DF has no userland way to synthesize a PCI bridge. - A child driver whose
device_detachreturns non-zero while attached (busy device). Candidates in this kernel:cardbus/if_re,cardbus/if_dc,cardbus/if_fxp(detach commonly EBUSY while interface up / bpf attached),pccard/sio(open tty). - The card event itself: physical eject, re-insert of another card
(
cardbus_attach_cardat :168 callscardbus_detach_cardfirst), or suspend (cardbus_suspendat :121-126). Root can reach the same path viakldunload-style driver detach, but then it is already privileged.
Severity is filed as Low per the audit rubric ("requires specific hardware"), though the defect class is memcorrupt (UAF + resource lifetime) and would rate higher on hardware that exposes it.
Honest classification mapping
- Guest reachable but the target device/feature is absent β per the restricted enum
for this run the status is recorded as
untested(impactnone, no dynamic evidence). Attempts: 0 (nothing executable without the hardware; the "PoC" is the trigger analysis inREADME.md+trigger.md).
Fix validation
Not possible on this guest (same hardware gate); fix.diff is authored against the
read-only sys/ tree from line-accurate reading, never applied there.
fix_status: not_testable.
Key citations
- sys/dev/pccard/cardbus/cardbus.c:236-247 (ignored detach/delete errors + free)
- sys/dev/pccard/cardbus/cardbus.c:300-313 (release_all_resources over live driver)
- sys/dev/pccard/cardbus/cardbus.c:121-126, :168 (suspend / re-insert callers)
- sys/dev/pccard/cardbus/cardbus.c:277-290 (driver_added UAF consumer)
- sys/kern/subr_bus.c:1284-1310 (device_delete_child returns error, keeps child)
- sys/bus/pci/pci.c:2139-2156 (pci_freecfg β kfree(dinfo))
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.
DF-4977 β trigger sequence (hardware-gated; not executable on the audit guest)
No userland program can drive this path on a machine without a CardBus controller:
the entry points are card events (CSC interrupts β card_attach_card/card_detach_card
on the cbb driver) or privileged driver detach. The faithful reproduction is a
physical sequence:
Preconditions
- Machine with a
cbbCardBus bridge (TI/Ricoh/O2Micro PCI-PCI bridge, hdrtype 2), DragonFly kernel withcardbus(built into X86_64_GENERIC β confirmed viakldstat -von the audit guest, see env.txt). - A CardBus card whose driver fails
device_detachwhile busy. Example with the compiled-inif_re/fxp/dcfamilies:
# as root: bring the cardbus NIC up so its detach will refuse ifconfig re0 up # or: keep a bpf reader attached: tcpdump -i re0 >/dev/null & # OR with a serial card (pccard/sio): stty -f /dev/ttyd0.open # keep dial-out open as any user of group dialer
Fire
# physical: eject the card -> cbb CSC interrupt -> CARD_DETACH_CARD(cardbus) # -> cardbus_detach_card() -> device_detach()==EBUSY # -> resources released + dinfo freed, child survives # equivalent software path (root): acpiconf -s 1 (cardbus_suspend -> same function)
Observe (kernel debugger or instrumented build)
kgdb> p *(struct cardbus_devinfo *)<child->ivars> # freed memory read
pciconf -lafter the botched detach still lists the child (device_delete_child failed) β its ivars are freed.kldload <any cardbus driver>βcardbus_driver_added()βpci_print_verbose(&dinfo->pci)walks the freed devinfo (cardbus.c:281-287) β UAF read;pci_cfg_restorewrites restored BAR values intodinfo->pci.cfgβ UAF write of card-controlled config values into reclaimed heap.- The still-live driver continues to touch its released resources β bus-space aliasing.
Why not run
The audit QEMU guest has no cbb device (env.txt pciconf -l), and neither QEMU's
i440FX machine nor DF userland offers a way to synthesize a CardBus bridge, so the
sequence above cannot be executed there. Nothing was built or run; attempts = 0.
Fix verification
not_testableFix validation impossible on the audit guest for the same reason the PoC is untested (no CardBus controller; the path cannot execute). fix.diff was authored against the read-only sys/ tree from line-accurate reading and never applied there.
findings/poc/DF-4977/fix.diff
Confirmed kernel references
- sys/dev/pccard/cardbus/cardbus.c:236
- sys/dev/pccard/cardbus/cardbus.c:243
- sys/dev/pccard/cardbus/cardbus.c:244
- sys/dev/pccard/cardbus/cardbus.c:245
- sys/dev/pccard/cardbus/cardbus.c:246
- sys/dev/pccard/cardbus/cardbus.c:300
- sys/dev/pccard/cardbus/cardbus.c:281
- sys/kern/subr_bus.c:1284
- sys/kern/subr_bus.c:1292
- sys/bus/pci/pci.c:2139
Detail
Evidence (decisive lines)
findings/poc/DF-4977/VERDICT.md (code-path proof), README.md/trigger.md (physical trigger), env.txt (guest pciconf/kldstat proof of no cardbus hardware), fix.diff (authored, not applied to sys/)
PoC changes
No seeded PoC existed; the pack was authored from scratch as a hardware-gated trigger analysis (trigger.md). Nothing was built or run (attempts 0) because the guest lacks any cbb/CardBus controller.
Verified recommended fix
Abort per-child teardown when device_detach or device_delete_child fails: keep the child and its dinfo alive and propagate the error; only pci_freecfg after the child is actually deleted (see fix.diff).
Verdict
cardbus_detach_card (cardbus.c:236-247) ignores the return of device_detach (line 243) and device_delete_child (line 245) yet unconditionally pci_freecfg's the child devinfo (line 246). DF's device_delete_child returns the detach error and leaves the child in the tree (subr_bus.c:1284-1293), so a busy child (detach EBUSY) survives with child->ivars pointing at freed memory, while cardbus_release_all_resources (line 244) has already ripped its bus resources and zeroed its BARs out from under the running driver. Next touch of the surviving child (e.g. cardbus_driver_added at cardbus.c:281-287 after any driver kldload: pci_print_verbose(&dinfo->pci) read, pci_cfg_restore write) is a UAF. Untested because the trigger needs a CardBus bridge and a card event (eject/re-insert/suspend) with a busy cardbus child; the audit QEMU guest has no cbb controller at all (env.txt), so the code path cannot execute there. Defect proven by code reading; exploit chain not developed (hardware gate), hence impact recorded as none-demonstrated with the class being UAF/memcorrupt.
No comments yet.