axge_attach() failure paths call axge_detach() on a never-attached usb_ether -> deterministic kernel panic at evil-USB plug-in (lockmgr through NULL ue_lock; latent ether_ifdetach-on-unattached ifnet corruption on the second branch)
Summary
Third member of the DF-5334/5384 class: both attach failure branches goto the full detach - the usbd_transfer_setup failure (fully device-controlled: evil adapter with an axge VID:PID missing the bulk endpoints) runs uether_ifdetach on an entirely-zeroed usb_ether -> lockmgr(NULL) Fatal trap 12 VA 0; the uether_ifattach-failure branch additionally drains a never-init watchdog, ether_ifdetaches a never-attached ifnet (ifindex2ifnet[0]/ifnet-list corruption) and double-puts ue_unit. KLD-harness-verified on the guest: replay of the exact post-failure state panics at lockmgr_exclusive+0x5d 1/1; detach_usb rerouting + class-wide ue_lock guard fix validated (SURVIVED, clean loads).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5395 Β· 15 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 4.1 KB | β raw | |
| VERDICT.md | β | 5.4 KB | β raw | |
| axge_attach_poc.c | β | 4.9 KB | view raw | |
| Makefile | β | 149 B | β download | |
| build.sh | β | 109 B | view raw | |
| run.sh | β | 126 B | view raw | |
| build.log | β | 911 B | view raw | |
| panic.txt | β | 1.0 KB | view raw | |
| run.log | β | 1.7 KB | view raw | |
| fix_uether_build.log | β | 924 B | view raw | |
| fix_if_axge_build.log | β | 909 B | view raw | |
| env.txt | β | 1.1 KB | view raw | |
| fix.diff | β | 1.9 KB | view raw | |
| manifest.json | β | 1.3 KB | view raw | |
| verdict.json | β | 8.3 KB | view raw |
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-558goto detachβif_axge.c:573-575axge_detach(dev).- At that point
ue->ue_lock,ue_dev,ue_udev,ue_methods,ue_tqare ALL still zero β they are assigned only atif_axge.c:560-564, after the failing call (onlylockinit(&sc->sc_lock)at :550 anddevice_set_usb_desc()at :549 have run). axge_detach()(if_axge.c:587) callsuether_ifdetach(ue)(usb_ethernet.c:267-305):usb_proc_drain()is a no-op on the zeroedue_tq;uether_getifp()returns the embedded arpcom ifnet (never NULL β theif (ifp != NULL)guard is dead code); thenusb_ethernet.c:281UE_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).
# 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 harnessbuild.sh/run.shβ exact build/run commandsbuild.logβ harness build (full, -Werror)fix_uether_build.log/fix_if_axge_build.logβ patched module buildspanic.txtβ baseline serial record (stock modules)run.logβ patched runs (survival proof, 2x)env.txtβ guest uname, kldstat, dmesgfix.diffβ the fix (never applied to the audit tree)VERDICT.md,manifest.json,verdict.json
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 theif (ifp != NULL)guard at :278 is dead code (same as DF-5334/DF-5384).UE_LOCK(ue)(usb_ethernet.c:281, macro at :61) islockmgr(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 (zeroedstruct axge_softc+lockinit(&sc->sc_lock)per if_axge.c:550 β the only lock initialisation before the failing call at :553) and replaysaxge_detach()verbatim: the productionusbd_transfer_unsetup()then the productionuether_ifdetach(). - Baseline (pristine
with-srcguest, stock/boot/kernel/uether.koauto-loaded byMODULE_DEPEND): the three markers print (ue_lock=0 ue_dev=0 ue_udev=0 ue_tq.up_lock=0β the exact branch-1 state), thenFatal 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.diffapplied to guest/usr/srcvia patch(1) Plan A, all hunks;uether.ko+if_axge.korebuilt-Werrorclean; patcheduether.kointo/boot/kernel): the identical harness run printsaxge_poc: SURVIVED uether_ifdetach()(the guard's early return),KLDLOAD_RC=0,dmesg | grep -c 'Fatal trap'= 0,kldunloadclean β run twice, stable β and the patchedif_axge.kodriver 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.
Fix verification
fixedfix.diff applied in-guest to /usr/src (patch(1) Plan A, both files, all hunks); uether.ko and if_axge.ko rebuilt -Werror clean; patched uether.ko installed into /boot/kernel; the IDENTICAL harness re-run printed 'axge_poc: SURVIVED uether_ifdetach()', 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. Baseline panic record in panic.txt, patched survival in run.log. fix.diff verified git-apply-able against the clean read-only sys/ tree.
run.log (SURVIVED marker + KLDLOAD_RC=0 + kldstat + UNLOAD_OK x2 + FATAL_COUNT=0 + DRIVER_LOAD_OK/DRIVER_UNLOAD_OK); fix_uether_build.log / fix_if_axge_build.log (-Werror clean); panic.txt (baseline trap on identical input); fix.diff (git apply --check OK on clean tree)
Confirmed kernel references
- sys/bus/u4b/net/if_axge.c:549-558
- sys/bus/u4b/net/if_axge.c:560-564
- sys/bus/u4b/net/if_axge.c:566-575
- sys/bus/u4b/net/if_axge.c:579-591
- sys/bus/u4b/net/usb_ethernet.c:61
- sys/bus/u4b/net/usb_ethernet.c:133-137
- sys/bus/u4b/net/usb_ethernet.c:262
- sys/bus/u4b/net/usb_ethernet.c:267-305
- sys/bus/u4b/usb_process.c:397-410
Detail
Evidence (decisive lines)
panic.txt (baseline serial: 3 axge_poc markers with ue_lock=0 ue_dev=0 ue_udev=0 ue_tq.up_lock=0 -> Fatal trap 12, fault virtual address = 0x0, current process = kldload, Stopped at lockmgr_exclusive+0x5d: orl (%r14),%esi, db> prompt); run.log (patched run: SURVIVED marker, KLDLOAD_RC=0, kldstat, UNLOAD_OK twice, FATAL_COUNT=0, patched if_axge.ko DRIVER_LOAD_OK/DRIVER_UNLOAD_OK); build.log + fix_uether_build.log + fix_if_axge_build.log (-Werror clean builds); env.txt (guest uname/kldstat/dmesg); fix.diff (git-apply-able against clean sys/, git apply --check OK)
PoC changes
No seeded PoC existed. Wrote axge_attach_poc.c from scratch: KLD harness (MODULE_DEPEND uether) that kmallocs a zeroed struct axge_softc, lockinit()s sc->sc_lock exactly as if_axge.c:550 does (the only softc initialisation before the failing usbd_transfer_setup() at :553), prints the zeroed ue state, then replays axge_detach() verbatim: production usbd_transfer_unsetup(sc->sc_xfer, AXGE_N_TRANSFER) (if_axge.c:586) + production uether_ifdetach(&sc->sc_ue) (if_axge.c:587). Initial builds failed -Werror twice on the verbatim-quoted source block in the header comment (nested '/' from the quoted '/ failure */' token closed the harness comment, UTF-8 apostrophes/em-dashes from the aue template) - fixed by flattening the quoted comment to ASCII without nested comment tokens; final build -Werror clean. Physical AX88179 emulation is infeasible in QEMU; static axge_attach/axge_detach cannot be linked against, so the branch-1 state + exact call sequence replay is the strongest runnable evidence and uses only production code after the goto.
Verified recommended fix
axge_attach(): route both pre-attach failures to a detach_usb label doing only usbd_transfer_unsetup()+lockuninit(); uether_ifdetach(): early-return when ue->ue_lock==NULL (class-wide, same as DF-5334/DF-5384 fix)
Verdict
Deterministic kernel panic at USB device plug-in: axge_attach()'s usbd_transfer_setup() failure branch (if_axge.c:555-558 'allocating USB transfers failed') goto-jumps into the full axge_detach() (if_axge.c:573-575) while the embedded usb_ether is still entirely zeroed (ue_lock/ue_dev/ue_udev/ue_methods/ue_tq 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 run). axge_detach() then calls uether_ifdetach(ue) (if_axge.c:587), which lockmgr()s through the NULL ue_lock (Fatal trap 12, fault VA 0x0, Stopped at lockmgr_exclusive+0x5d: orl (%r14),%esi) - the same DF-5334 (if_cdce) / DF-5384 (if_aue) class: usb_proc_drain() no-ops on the zeroed ue_tq and the ifp!=NULL guard is dead code because uether_getifp() returns the embedded arpcom ifnet. The trigger is fully device-controlled: any evil USB adapter presenting 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 without the expected bulk-in/out endpoints fails the transfer setup. The second failure branch (uether_ifattach error, only on usb_proc_create ENOMEM) additionally runs usb_callout_drain() on a never-initialized watchdog and ether_ifdetach() on a never-if_attach()ed ifnet -> ifindex2ifnet[0] nulling + global ifnet list corruption + double devfs_clone_bitmap_put() of ue_unit. Verified with a KLD harness replaying axge_detach() verbatim (its two production calls in order) on the exact branch-1 state, using production usbd_transfer_unsetup()+uether_ifdetach(): baseline on pristine with-src guest with stock uether.ko -> Fatal trap 12 VA 0x0, guest dead in DDB (panic.txt); with fix.diff ((a) uether_ifdetach early-return on NULL ue_lock + (b) axge_attach detach_usb label doing only unsetup+lockuninit, required for branch 2) both modules rebuilt -Werror clean, patched uether.ko installed, and the identical harness run SURVIVED with KLDLOAD_RC=0, 0 Fatal traps, clean kldunload (twice), patched if_axge.ko loads/unloads clean (run.log). Not memory corruption with attacker-controlled data: fixed-address NULL deref at plug time; impact is panic/DoS from physical/VM USB access, same tier as DF-5334/DF-5384 (Medium).
No comments yet.