# DF-5395 — if_axge.c attach-failure DF-5334/DF-5384-class panic (goto-detach-on-unattached)

## What

`axge_attach()` (`sys/bus/u4b/net/if_axge.c`) routes its device-controlled
failure branch through `axge_detach()` on a `usb_ether` that was never
attached — identical class to DF-5334 (`if_cdce.c`) and DF-5384
(`if_aue.c`):

- `usbd_transfer_setup()` fails (missing bulk-in/out endpoints on an
  otherwise matching ASIX device) → `if_axge.c:555-558` `goto detach` →
  `if_axge.c:573-575` `axge_detach(dev)`.
- At that point `ue->ue_lock`, `ue_dev`, `ue_udev`, `ue_methods`, `ue_tq`
  are ALL still zero — they are assigned only at `if_axge.c:560-564`,
  *after* the failing call (only `lockinit(&sc->sc_lock)` at :550 and
  `device_set_usb_desc()` at :549 have run).
- `axge_detach()` (`if_axge.c:587`) calls `uether_ifdetach(ue)`
  (`usb_ethernet.c:267-305`): `usb_proc_drain()` is a no-op on the zeroed
  `ue_tq`; `uether_getifp()` returns the embedded arpcom ifnet (never
  NULL — the `if (ifp != NULL)` guard is dead code); then
  `usb_ethernet.c:281` `UE_LOCK(ue)` = `lockmgr(NULL, LK_EXCLUSIVE)`
  → **Fatal trap 12, fault VA 0x0**.

The second failure branch (`uether_ifattach()` error, `if_axge.c:566-570`)
has the same shape and additionally reaches `ether_ifdetach()` on a
never-`if_attach()`ed ifnet → `ifindex2ifnet[0]` nulling + global ifnet
list corruption + double `devfs_clone_bitmap_put()` of `ue_unit`
(requires `usb_proc_create()` ENOMEM — not attacker-timed).

Threat model: attacker-supplied USB device (evil adapter / evil dock /
VM USB attach) presenting any of the 4 VID:PID pairs in `axge_devs[]`
(ASIX AX88179 `0b95:1790`, AX88178A `0b95:178a`, D-Link DUB1312
`2001:4a00`, Sitecom LN032 `0df6:062c`) at config index 0 / iface 0
**without** the bulk-in/out endpoints `axge_config[]` requires. Not
remotely triggerable, not an unprivileged-local program — physical/VM
USB access.

## Reproduce (evidence class: KLD harness driving production code)

QEMU cannot emulate an AX88179. The harness `axge_attach_poc.c` replays
`axge_detach()` **verbatim** (its two production calls, in order) on the
exact state `axge_attach()`'s failure branch produces, using the
**production** `usbd_transfer_unsetup()` and `uether_ifdetach()` (exported
by `uether.ko`). The only non-production code is the static
`axge_attach`/`axge_detach` frame itself (they are `static` in
`if_axge.c` and only reachable via `goto detach` with a physical device
present).

```sh
# build (in guest; pack synced to /root/df5395)
cd /root/df5395 && make -m /usr/src/share/mk

# run (auto-loads STOCK /boot/kernel/uether.ko as dependency)
kldload -v ./axge_poc.ko
# expected: 3 marker lines, then
#   Fatal trap 12: page fault while in kernel mode
#   fault virtual address = 0x0
#   Stopped at lockmgr_exclusive+0x5d: orl (%r14),%esi
#   (guest dead in DDB)
```

## Fix validation

`fix.diff` (git-apply-able against clean `sys/`): (a) class-wide guard in
`uether_ifdetach()` — early return when `ue->ue_lock == NULL` (same fix
already validated for DF-5334/DF-5384); (b) `axge_attach()` routes both
pre-attach failure branches to a local `detach_usb:` label doing only
`usbd_transfer_unsetup()` + `lockuninit()` (fixes the branch-2
`ether_ifdetach`-on-unattached corruption too).

Applied to the guest's `/usr/src` (patch(1) Plan A, all hunks), both
modules (`uether.ko`, `if_axge.ko`) rebuilt `-Werror` clean, patched
`uether.ko` installed: the identical harness run prints
`axge_poc: SURVIVED uether_ifdetach()`, `KLDLOAD_RC=0`,
`dmesg | grep -c 'Fatal trap'` = 0, clean `kldunload` (twice — stable),
and the patched `if_axge.ko` driver module loads/unloads clean. See
`run.log` vs `panic.txt`.

## Files

- `axge_attach_poc.c` + `Makefile` — the harness
- `build.sh` / `run.sh` — exact build/run commands
- `build.log` — harness build (full, -Werror)
- `fix_uether_build.log` / `fix_if_axge_build.log` — patched module builds
- `panic.txt` — baseline serial record (stock modules)
- `run.log` — patched runs (survival proof, 2x)
- `env.txt` — guest uname, kldstat, dmesg
- `fix.diff` — the fix (never applied to the audit tree)
- `VERDICT.md`, `manifest.json`, `verdict.json`
