β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-5384

aue_attach() failure paths call aue_detach() on a never-attached usb_ether -> kernel panic at evil-USB plug-in (lockmgr through NULL ue_lock; latent ether_ifdetach-on-unattached ifnet corruption)

Summary

Same goto-detach-on-unattached pattern as DF-5334 (if_cdce): the usbd_transfer_setup failure branch (fully device-controlled - an evil adapter claiming any of the 68 aue VID:PID pairs with missing bulk/int endpoints) routes to aue_detach -> uether_ifdetach with ue_lock/ue_dev/ue_udev/ue_tq ALL still zero -> lockmgr(NULL) Fatal trap 12 VA 0x0; the uether_ifattach-error branch leaves ue_lock valid but the ifnet never if_attach()ed -> ether_ifdetach corrupts ifindex2ifnet[0] (lo0) and the global ifnet list. The DF-5334 validated uether guard was reverted with the guest snapshot - the tree still carries the unguarded sink, and this driver needs its own detach_usb rerouting too (the guard alone does not fix the second branch). KLD-harness-verified on the guest: replay of the exact post-failure state panics at lockmgr_exclusive+0x5d. detach_usb-label + class-wide ue_lock guard fix validated.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5384 Β· 12 files
FileTypeDescriptionSize
aue_attach_poc.c β€” 4.0 KB view raw
Makefile β€” 147 B ↓ download
build.log β€” 5.8 KB view raw
panic.txt β€” 1.1 KB view raw
run.log β€” 1.6 KB view raw
fix_uether_build.log β€” 7.0 KB view raw
fix_if_aue_build.log β€” 6.9 KB view raw
env.txt β€” 2.2 KB view raw
fix.diff β€” 1.7 KB view raw
VERDICT.md β€” 4.9 KB ↓ raw
README.md β€” 3.7 KB ↓ raw
verdict.json β€” 6.5 KB view raw

DF-5384 β€” if_aue.c attach-failure DF-5334-class panic (goto-detach-on-unattached)

What

aue_attach() (sys/bus/u4b/net/if_aue.c) routes its device-controlled failure branch through aue_detach() on a usb_ether that was never attached β€” identical class to DF-5334 (if_cdce.c, proved on this guest):

  • usbd_transfer_setup() fails (missing bulk/interrupt endpoints on an otherwise matching Pegasus device) β†’ if_aue.c:689-691 goto detach β†’ if_aue.c:707-708 aue_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_aue.c:694-698, after the failing call.
  • aue_detach() (if_aue.c:719) 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_aue.c:700-704) has the same shape and additionally reaches ether_ifdetach() on a never-if_attach()ed ifnet β†’ ifindex2ifnet[0] nulling + global ifnet list corruption (requires usb_proc_create() ENOMEM β€” not attacker-timed).

Threat model: attacker-supplied USB device (evil adapter / evil dock / VM attach) presenting any of the 68 VID:PID pairs in aue_devs[] (e.g. ADMtek Pegasus 0x077b:0x2226) at config 0 / iface 0 without the expected bulk-in/out + interrupt-in endpoints. Not remotely triggerable, not an unprivileged-local program β€” physical/VM USB access.

Reproduce (evidence class: KLD harness driving production code)

QEMU cannot emulate a Pegasus AN986. The harness aue_attach_poc.c replays aue_detach() verbatim (its two production calls, in order) on the exact state aue_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 aue_attach/aue_detach frame itself (they are static in if_aue.c and only reachable via goto detach with a physical device present).

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

# run (auto-loads STOCK /boot/kernel/uether.ko as dependency)
kldload -v ./aue_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); (b) aue_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, both modules rebuilt -Werror clean, patched uether.ko installed: identical harness run prints aue_poc: SURVIVED uether_ifdetach(), KLDLOAD_RC=0, dmesg | grep -c 'Fatal trap' = 0, clean kldunload, patched if_aue.ko driver loads/unloads clean. See run.log vs panic.txt.

Files

  • aue_attach_poc.c + Makefile β€” the harness
  • build.log β€” harness build (full, -Werror)
  • fix_uether_build.log / fix_if_aue_build.log β€” patched module builds
  • panic.txt β€” baseline serial record (stock modules)
  • run.log β€” patched run (survival proof)
  • env.txt β€” guest uname, dmesg tail, kldstat
  • fix.diff β€” the fix (never applied to the audit tree)
  • VERDICT.md, manifest.json, verdict.json
VERDICT.md
↓ download raw

DF-5384 VERDICT β€” REPRODUCED (panic), fixed

Finding. aue_attach() (sys/bus/u4b/net/if_aue.c) has the DF-5334 "goto-detach-on-unattached" pattern: the usbd_transfer_setup() failure branch (if_aue.c:689-691) and the uether_ifattach() failure branch (if_aue.c:700-704) both jump to detach: (if_aue.c:707-708) and call the full aue_detach(), which calls uether_ifdetach(&sc->sc_ue) (if_aue.c:719) 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_aue.c:694-698 β€” after the failing call:

  • 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).
  • 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; uether_ifdetach() proceeds to ether_ifdetach() (usb_ethernet.c:294) β†’ if_detach() on if_index 0 β†’ nulls ifindex2ifnet[0] (lo0) and TAILQ_REMOVEs a never-inserted ifnet from the global ifnet list (sys/net/if.c:966/982) β€” latent list corruption pre-empted by a NULL-write fault. That branch 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 68 aue_devs[] VID:PID pairs (e.g. ADMtek Pegasus 0x077b:0x2226) at config 0 / iface 0 while omitting the bulk-IN/OUT + interrupt-IN endpoints makes usbd_transfer_setup() fail (USB_ERR_NO_ENDPOINT) at plug-in.

Verification (evidence class: KLD harness driving production code β€” QEMU cannot emulate a Pegasus AN986; aue_attach/aue_detach are static and reachable only with hardware):

  • The harness (aue_attach_poc.c) constructs the exact post-failure state (zeroed struct aue_softc + lockinit(&sc->sc_lock) per if_aue.c:683 β€” the only initialisation before the failing call) and replays aue_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, then Fatal trap 12: page fault while in kernel mode, fault virtual address = 0x0, current process = 987 (kldload), Stopped at lockmgr_exclusive+0x5d: orl (%r14),%esi β€” guest dead at DDB. Record: panic.txt. 1/1 run (deterministic NULL deref; DF-5334 proved the same sink 3/3 in-situ).
  • Patched (fix.diff applied to guest /usr/src; uether.ko + if_aue.ko rebuilt -Werror clean; patched uether.ko into /boot/kernel): identical harness run prints aue_poc: SURVIVED uether_ifdetach() (the guard's early return), KLDLOAD_RC=0, dmesg | grep -c 'Fatal trap' = 0, kldunload clean, and the patched if_aue.ko driver module loads/unloads clean. Record: run.log, fix_uether_build.log, fix_if_aue_build.log.

Impact. Plug-time kernel NULL dereference (panic/DoS) from a malicious USB device — same severity tier and threat model as DF-5334 (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-5334 was filed against if_cdce.c; its persisted fix (uether_ifdetach guard) was applied in-guest for validation only and then reverted β€” the tree still carries the unguarded uether_ifdetach, and every uether driver's attach-failure path is an independent instance. This finding covers the if_aue.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 validated fix); (b) aue_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, 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

fixed
baseline reproduced→ patch + rebuild →patched clean

fix.diff applied in-guest to /usr/src (patch(1) Plan A, both hununks); uether.ko and if_aue.ko rebuilt -Werror clean; patched uether.ko installed into /boot/kernel; rebooted-equivalent clean session re-ran the IDENTICAL harness: 'aue_poc: SURVIVED uether_ifdetach()', KLDLOAD_RC=0, dmesg|grep -c 'Fatal trap' = 0, kldunload clean, and the patched if_aue.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 + Fatal-trap count 0); fix_uether_build.log / fix_if_aue_build.log (-Werror clean); panic.txt (baseline trap on identical input); fix.diff (git apply --check OK on clean tree)
↓ fix.diffper-fix-DF-5384

Confirmed kernel references

Detail

Evidence (decisive lines)

panic.txt (baseline serial: 3 aue_poc markers -> Fatal trap 12, fault virtual address = 0x0, current process 987 (kldload), Stopped at lockmgr_exclusive+0x5d: orl (%r14),%esi, db> prompt); run.log (patched run: SURVIVED marker, KLDLOAD_RC=0, kldstat, UNLOAD_OK, dmesg Fatal-trap count 0, patched if_aue.ko loads clean); build.log + fix_uether_build.log + fix_if_aue_build.log (-Werror clean builds); env.txt (guest uname/dmesg/kldstat); fix.diff (git-apply-able against clean sys/, git apply --check OK)

PoC changes

No seeded PoC existed. Wrote aue_attach_poc.c from scratch: KLD harness (MODULE_DEPEND uether) that kmallocs a zeroed struct aue_softc, lockinit()s sc->sc_lock exactly as if_aue.c:683 does (the only softc initialisation before the failing usbd_transfer_setup() at :686), prints the zeroed ue state, then replays aue_detach() verbatim: production usbd_transfer_unsetup(sc->sc_xfer, AUE_N_TRANSFER) (if_aue.c:718) + production uether_ifdetach(&sc->sc_ue) (if_aue.c:719). Initial build needed the full if_aue.c include block (sys/bus.h, sys/condvar.h, net/if_var.h) and generated headers in SRCS (opt_usb.h device_if.h bus_if.h usbdevs.h miibus_if.h opt_inet.h). Physical Pegasus emulation is infeasible in QEMU (no usbredir gadget available to this harness class); static aue_attach/aue_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

aue_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 fix)

Verdict

Deterministic kernel panic at USB device plug-in: aue_attach()'s usbd_transfer_setup() failure branch (if_aue.c:689-691 'allocating USB transfers failed') goto-jumps into the full aue_detach() (if_aue.c:707-708) while the embedded usb_ether is still entirely zeroed (ue_lock/ue_dev/ue_udev/ue_methods/ue_tq are assigned only at if_aue.c:694-698, AFTER the failing call). uether_ifdetach() then lockmgr()s through the NULL ue_lock (Fatal trap 12, fault VA 0x0, Stopped at lockmgr_exclusive+0x5d) - the same DF-5334 (if_cdce) 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 68 aue_devs[] VID:PID pairs (e.g. ADMtek Pegasus 0x077b:0x2226) at config 0/iface 0 without the expected bulk/interrupt endpoints fails the transfer setup. The second failure branch (uether_ifattach error, only on usb_proc_create ENOMEM) additionally runs ether_ifdetach() on a never-if_attach()ed ifnet -> ifindex2ifnet[0] nulling + global ifnet list corruption. Verified with a KLD harness replaying aue_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) aue_attach detach_usb label doing only unsetup+lockuninit, required for branch 2) both modules rebuilt -Werror clean and the identical harness run SURVIVED with KLDLOAD_RC=0, 0 Fatal traps, clean unload (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 (Medium).