# DF-5493 — uether_ifdetach() missing never-attached guard (class-wide owner)

**status: reproduced / impact: panic / confidence: certain / fix validated**

## What

`uether_ifdetach()` (`sys/bus/u4b/net/usb_ethernet.c:267-305`) validates
nothing about the `struct usb_ether` it is handed:

- `usb_proc_drain()` (:273) silently no-ops on an all-zero `ue_tq`
  (`sys/bus/u4b/usb_process.c:409`),
- `uether_getifp()` (:276, :133-137) returns the **embedded** arpcom ifnet,
  so the `if (ifp != NULL)` guard (:278) is dead code — always true,
- it then lockmgr()s through `ue_lock` (:281) and runs
  `ether_ifdetach(ifp)` (:294) unconditionally.

Eight drivers this pass (DF-5334/5384/5395/5397/5399/5411/5453/5486) call
their detach — and thus `uether_ifdetach()` — on attach-failure paths where
the ue was never attached. This file owns the class-wide defect and the
central guard; the per-driver findings own each driver's `goto detach`.

## Two failure arms verified on the guest

- **Branch A** (ue fields zero — all 8 sibling drivers' pre-`uether_ifattach`
  failures, e.g. `if_cue.c:405-410`): `UE_LOCK(ue)` → `lockmgr(NULL)` →
  `Fatal trap 12, fault VA 0x0, Stopped at lockmgr_exclusive+0x5d`
  (`panic_a_serial.txt`).
- **Branch B** (`uether_ifattach()` itself failed at `usb_proc_create()`
  ENOMEM, `usb_ethernet.c:173-178`; ue_lock valid, ue_tq unwound to zero by
  `usb_process.c:217-219,244`; every driver's
  `if (uether_ifattach(ue)) goto detach;` branch):
  `ether_ifdetach()` → `if_down()` on the never-`if_attach()`ed ifnet →
  `Fatal trap 12, fault VA 0x0, Stopped at if_down_dispatch+0x66`
  (`panic_b_serial.txt`). Behind this crash also lurk
  `sysctl_ctx_free()` on a never-init'd ctx (:297) and
  `devfs_clone_bitmap_put(..., ue_unit=0)` of a never-allocated unit
  (:300) which would hand unit 0 to the next adapter while a live ue0
  exists.

## Rig (KLD harness + production code)

`ue_guard_a.c` / `ue_guard_b.c`: each kmallocs a zeroed `struct usb_ether`,
constructs the exact post-failure state, and calls the **production**
`uether_ifdetach()` from the stock auto-loaded `uether.ko`
(`MODULE_DEPEND(uether)`). The failing call sites are static driver-attach
paths needing a physical evil-USB device (or a taskqueue-creation ENOMEM),
so state + call-sequence replay is the strongest runnable evidence — same
evidence class as the 8 sibling rigs (see `findings/poc/DF-5486/`).

## Build / run (in guest)

```
cd /root/df5493 && sh ./build.sh        # builds ue_guard_a.ko + ue_guard_b.ko, -Werror
sh ./run.sh a                           # baseline: Fatal trap 12 VA 0x0 (lockmgr NULL)
# (reset with-src, rebuild)
sh ./run.sh b                           # baseline: Fatal trap 12 VA 0x0 (if_down_dispatch)
```

With `fix.diff` applied to `/usr/src`, `uether.ko` rebuilt and installed,
and the rigs rebuilt against the patched header, both branches print
`SURVIVED uether_ifdetach()`, `KLDLOAD_RC=0`, `traps: 0`, clean unload
(`run_patched.log`).

## Fix

`fix.diff`: adds `ue_attaching` / `ue_attached` state flags to
`struct usb_ether`; `uether_ifattach()` sets `ue_attaching` only after
`usb_proc_create()` succeeds; `ue_attach_post_task()` sets `ue_attached`
after publishing the ifnet; `uether_ifdetach()` early-returns on
`ue_lock == NULL || !ue->ue_attaching` and gates the teardown block on
`ue->ue_attached` (the failed-attach task already returned the unit).
Covers branch A, branch B, and DF-2341's branch C (async PHY-probe attach
failure) in one central place. `git apply --check` clean on the read-only
tree.
