# DF-4114 — nge(4) TBI mode: `sc->nge_miibus = dev` self-reference makes nge_detach delete itself — recursion panic / live-device free (UAF)

## What this is

When no MII PHY is found and the TBI (ten-bit/fiber) interface is
enabled, `nge_attach` points the miibus handle at the device itself:

```c
} else if (CSR_READ_4(sc, NGE_CFG) & NGE_CFG_TBI_EN) {   /* if_nge.c:860 */
        sc->nge_tbi = 1;
        sc->nge_miibus = dev;                             /* if_nge.c:864 - SELF */
```

`nge_detach` then hands that handle to newbus as if it were a child:

```c
if (sc->nge_miibus)
        device_delete_child(dev, sc->nge_miibus);          /* if_nge.c:941-942 = (dev, dev) */
```

`device_delete_child(parent, child)` (sys/kern/subr_bus.c:1284-1310)
unconditionally does:

```c
if ((error = device_detach(child)) != 0)          /* subr_bus.c:1292 */
        return(error);
while ((grandchild = TAILQ_FIRST(&child->children)))    /* :1296 */
        device_delete_child(child, grandchild);
if (child->devclass)
        devclass_delete_device(child->devclass, child);  /* :1302-1303 */
TAILQ_REMOVE(&dev->children, child, link);        /* :1304 - child NOT a member of dev->children */
TAILQ_REMOVE(&bus_data_devices, child, devlink);  /* :1305 */
kobj_delete((kobj_t)child, M_BUS);                /* :1306 - frees the device */
```

Two distinct blow-ups:

**A. Normal detach (kldunload if_nge / devctl) — unbounded recursion.**
`nge_detach` is invoked *from* `device_detach` while `dev->state` is
still DS_ATTACHED (state is only set to DS_NOTPRESENT at
subr_bus.c:2149, after DEVICE_DETACH returns).  The nested
`device_delete_child(dev, dev)` → `device_detach(dev)` sees DS_ATTACHED
(subr_bus.c:2136-2137) → calls `DEVICE_DETACH(dev)` → `nge_detach`
**again** → same `device_delete_child(dev, dev)` → …  Each level
re-runs the attached block: second `bus_teardown_intr` on the stale
cookie, second `ether_ifdetach`/`if_detach` on an already-detached
ifnet, double `contigfree` of `nge_ldata` and the 3.3 MB jumbo arena —
list corruption / double-free panics within a few levels, or kernel
stack exhaustion if it somehow gets further.  Panic either way.

**B. Attach-failure path — live device freed (UAF).**  `nge_attach`
calls `nge_detach(dev)` directly on its own `goto fail` (if_nge.c:921)
— e.g. `bus_setup_intr` failure (if_nge.c:913-916) after the TBI branch
was taken.  There `dev->state` is DS_ATTACHING, so the nested
`device_detach(dev)` returns 0 immediately (subr_bus.c:2136-2137 "state
!= DS_ATTACHED → return(0)"), the grandchild loop is empty, and
execution reaches `TAILQ_REMOVE(&dev->children, dev, link)` (subr_bus.c:1304)
— removing the device from a list it is not a member of, which
(tqe links being per-entry) silently unlinks `dev` from its *parent
PCI bus's* children list mid-attach — followed by
`kobj_delete((kobj_t)dev)` (**:1306**) freeing the live `device_t`.
`device_attach` then keeps using the freed device (state store,
sysctl_fini, printf) → use-after-free write/read in newbus on a
root-triggered path.

## Threat model

Requires (a) a TBI-mode DP83820 board (fiber/1000baseSX; no external
PHY) — `NGE_CFG_TBI_EN` — and (b) root (kldunload/devctl/driver
re-probe, or an IRQ-allocation failure at boot for path B).  Privileged
+ specific hardware → Low severity, but a genuine newbus-corrupting
logic bug (and DFly-specific: the TBI branch is a DragonFly addition).

## PoC status — untested (hardware absent)

Guest has no DP83820 of any kind (presence_probe.sh); the TBI branch
cannot be entered.  Source-level certainty: every step above is
unconditional on that path.

## Fix

Never delete ourselves — guard `sc->nge_miibus != dev` (fix.diff).
