# DF-5395 VERDICT — REPRODUCED (panic), fixed

**Finding.** `axge_attach()` (sys/bus/u4b/net/if_axge.c) has the
DF-5334/DF-5384 "goto-detach-on-unattached" pattern: the
`usbd_transfer_setup()` failure branch (if_axge.c:555-558) and the
`uether_ifattach()` failure branch (if_axge.c:566-570) both jump to
`detach:` (if_axge.c:573-575) and call the full `axge_detach(dev)`,
which calls `uether_ifdetach(&sc->sc_ue)` (if_axge.c:587) on a
`usb_ether` that was never attached.

**Why it faults.** On the transfer-setup failure path, every `ue` field
that `uether_ifdetach()` needs is still zero because they are assigned
only at if_axge.c:560-564 — *after* the failing call (before it, only
`device_set_usb_desc()` at :549 and `lockinit(&sc->sc_lock)` at :550
have touched the softc):

- `usb_proc_drain(&ue->ue_tq)` (usb_ethernet.c:273) returns immediately
  (`up->up_lock == NULL`, usb_process.c:406-410) — no fault, no
  initialization.
- `ifp = uether_getifp(ue)` (usb_ethernet.c:276) returns
  `&ue->ue_arpcom.ac_if` — never NULL, so the `if (ifp != NULL)` guard
  at :278 is dead code (same as DF-5334/DF-5384).
- `UE_LOCK(ue)` (usb_ethernet.c:281, macro at :61) is
  `lockmgr(ue->ue_lock /* NULL */, LK_EXCLUSIVE)` → read at VA 0x0 →
  Fatal trap 12.

The second branch (uether_ifattach error) leaves `ue_lock` valid but the
ifnet never `if_attach()`ed and `ue_watchdog` never
`usb_callout_init()`ed; `uether_ifdetach()` proceeds to
`ether_ifdetach()` (usb_ethernet.c:294) → `if_detach()` on a
never-attached ifnet → `ifindex2ifnet[0]` nulling + `TAILQ_REMOVE` of a
never-inserted ifnet from the global ifnet list + double
`devfs_clone_bitmap_put()` of `ue_unit` (first put already happened in
`ue_attach_post_task` fail path, usb_ethernet.c:262) — latent list/unit
corruption pre-empted on branch 1 by the NULL-write fault. Branch 2
needs `usb_proc_create()` to fail (ENOMEM), so it is not attacker-timed;
the transfer-setup branch is fully device-controlled: any USB device
exposing one of the 4 `axge_devs[]` VID:PID pairs (ASIX AX88179
`0b95:1790`, AX88178A `0b95:178a`, D-Link DUB1312 `2001:4a00`, Sitecom
LN032 `0df6:062c`) at config 0 / iface 0 while omitting the bulk-IN/OUT
endpoints makes `usbd_transfer_setup()` fail (USB_ERR_NO_ENDPOINT /
USB_ERR_NO_PIPE) at plug-in.

**Verification** (evidence class: KLD harness driving production code —
QEMU cannot emulate an AX88179; `axge_attach`/`axge_detach` are static
and reachable only with hardware):

- The harness (`axge_attach_poc.c`) constructs the exact post-failure
  state (zeroed `struct axge_softc` + `lockinit(&sc->sc_lock)` per
  if_axge.c:550 — the only lock initialisation before the failing call
  at :553) and replays `axge_detach()` verbatim: the production
  `usbd_transfer_unsetup()` then the production `uether_ifdetach()`.
- **Baseline** (pristine `with-src` guest, stock
  `/boot/kernel/uether.ko` auto-loaded by `MODULE_DEPEND`): the three
  markers print (`ue_lock=0 ue_dev=0 ue_udev=0 ue_tq.up_lock=0` — the
  exact branch-1 state), then `Fatal trap 12: page fault while in kernel
  mode, fault virtual address = 0x0`, `Stopped at
  lockmgr_exclusive+0x5d: orl (%r14),%esi` — guest dead at DDB
  (`panic.txt`). 1/1 run (deterministic NULL deref; the same sink was
  already proved 3/3 in-situ for the class in DF-5334/5384).
- **Patched** (`fix.diff` applied to guest `/usr/src` via patch(1) Plan
  A, all hunks; `uether.ko` + `if_axge.ko` rebuilt `-Werror` clean;
  patched `uether.ko` into `/boot/kernel`): the identical harness run
  prints `axge_poc: SURVIVED uether_ifdetach()` (the guard's early
  return), `KLDLOAD_RC=0`, `dmesg | grep -c 'Fatal trap'` = 0,
  `kldunload` clean — run twice, stable — and the patched `if_axge.ko`
  driver module loads/unloads clean (`run.log`,
  `fix_uether_build.log`, `fix_if_axge_build.log`).

**Impact.** Plug-time kernel NULL dereference (panic/DoS) from a
malicious USB device — same severity tier and threat model as
DF-5334/DF-5384 (Medium, physical/VM USB access). NOT memory corruption
with attacker-controlled data: the fault address is fixed VA 0, the only
"dereference" is the lockmgr read of the lock word at NULL. No
user→root route. The latent branch-2 second stage (ifindex/ifnetlist
corruption) is likewise plug-time corruption of fixed addresses — DoS.

**Why this is a separate finding from DF-5334/DF-5384.** Those were
filed against if_cdce.c and if_aue.c; the tree still carries the
unguarded `uether_ifdetach` (their in-guest fixes were reverted after
validation), and every uether driver's attach-failure path is an
independent instance. This finding covers the if_axge.c instance (root
cause identical, trigger device-controlled the same way).

**Fix.** `fix.diff`: (a) `uether_ifdetach()` early-returns when
`ue->ue_lock == NULL` (class-wide, matches the DF-5334/DF-5384 validated
fix); (b) `axge_attach()` routes both pre-attach failures to
`detach_usb:` doing only `usbd_transfer_unsetup()` + `lockuninit()` —
(b) is required for the branch-2 `ether_ifdetach`-on-unattached
corruption, which (a) alone does not stop. Validated in-guest (baseline
panic gone, both modules -Werror clean, driver loads).

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
