# DF-5486 — if_cue.c attach-failure DF-5334-class panic (goto-detach-on-unattached)

## What

`cue_attach()` (`sys/bus/u4b/net/if_cue.c`) routes its device-controlled
failure branch through `cue_detach()` on a `usb_ether` that was never
attached — identical class to DF-5334 (if_cdce.c), DF-5384 (if_aue.c),
DF-5395 (if_axge.c), DF-5397 (if_urndis.c), DF-5399 (if_mos.c),
DF-5411 (if_udav.c), DF-5453 (if_kue.c), all proved on this guest:

- `usbd_transfer_setup()` fails (no bulk-in/bulk-out endpoints on an
  otherwise matching CATC device) → `if_cue.c:407-410` `goto detach` →
  `if_cue.c:425-426` `cue_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_cue.c:412-416`,
  *after* the failing call (`lockinit(&sc->sc_lock,...)` at :402 is the
  only softc initialization before it).
- `cue_detach()` (`if_cue.c:437`) calls `uether_ifdetach(ue)`
  (`usb_ethernet.c:267-305`): `usb_proc_drain()` is a no-op on the zeroed
  `ue_tq` (`usb_process.c:409`); `uether_getifp()` returns the embedded
  arpcom ifnet (never NULL — the `if (ifp != NULL)` guard at :278 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_cue.c:418-422`,
only reachable on `usb_proc_create()` ENOMEM) has the same shape and
additionally reaches `ether_ifdetach()` on a never-`if_attach()`ed ifnet →
`ifindex2ifnet[0]` nulling + global ifnet list corruption.

Threat model: attacker-supplied USB device (evil adapter / evil dock /
VM USB attach) presenting one of the 3 VID:PID pairs in `cue_devs[]`
(CATC NETMATE `0x042e:0x5600`, NETMATE2 `0x042e:0x5601`, SmartBridges
SMARTLINK `0x087d:0x5600`) at config 0 / iface 0 **without** the expected
bulk-in + bulk-out endpoints. Not remotely triggerable, not an
unprivileged-local program — physical/VM USB access. (Softc is zeroed by
newbus — `subr_bus.c:1953` `M_ZERO` — so the fault address is fixed at 0,
not attacker-influenced.)

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

QEMU cannot emulate a CATC USB-EL1210A. The harness `cue_attach_poc.c`
replays `cue_detach()` **verbatim** (its two production calls, in order)
on the exact state `cue_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
`cue_attach`/`cue_detach` frame itself (they are `static` in if_cue.c
and only reachable via `goto detach` with a physical CATC present).

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

# run (auto-loads STOCK /boot/kernel/uether.ko as dependency)
kldload -v ./cue_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/`, `git apply --check` OK):
(a) class-wide guard in `uether_ifdetach()` — early return when
`ue->ue_lock == NULL`; (b) `cue_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 rebuilt `-Werror` clean, patched `uether.ko` installed: the
identical harness run prints `cue_poc: SURVIVED uether_ifdetach()`,
`KLDLOAD_RC=0`, `dmesg | grep -c 'Fatal trap'` = 0, clean `kldunload`,
and the patched `if_cue.ko` driver loads/unloads clean.
See `run.log` vs `panic.txt`.

## Files

- `cue_attach_poc.c` + `Makefile` — the harness
- `build.log` — harness build (full, -Werror)
- `fix_uether_build.log` / `fix_if_cue_build.log` — patched module builds
- `panic.txt` — baseline serial record (stock modules)
- `run.log` — patched run (survival proof) + dmesg markers
- `env.txt` — guest uname, kldstat, dmesg tail, compiler
- `fix.diff` — the fix (never applied to the audit tree)
- `VERDICT.md`, `manifest.json`, `verdict.json`
