# 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):

```c
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 = NULL` while the driver's softc still holds the
  `struct resource *`), and
* `pci_freecfg(dinfo)` → `kfree(dinfo, M_DEVBUF)` (sys/bus/pci/pci.c:2139-2156),
  while `child->ivars == dinfo` still.

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)

1. 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/ttydX` keeps it busy without
   root on many setups; an IFF_UP NIC detach returns EBUSY in several DF drivers).
2. Eject the card (or suspend the machine: `cardbus_suspend` → `cardbus_detach_card`,
   cardbus.c:121-126; or re-insert another card: `cardbus_attach_card` calls
   `cardbus_detach_card` first, cardbus.c:168).
3. `device_detach(child)` returns EBUSY → child survives with freed ivars.
4. Any later touch of the surviving child is a UAF: e.g. `kldload` a 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) follows `child->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.
