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

ipheth_attach() failure paths call ipheth_detach() on a never-attached usb_ether -> deterministic kernel panic at evil-USB plug-in (lockmgr through NULL ue_lock on branches 1-2; ether_ifdetach on never-if_attach()ed embedded ifnet on branches 3-4) β€” 9th and final instance of the DF-5334 uether goto-detach class

Summary

All four error branches route into the full detach while the ue fields are assigned only after the failing calls: branches 1-2 (STALL SET_INTERFACE / missing bulk endpoints) run uether_ifdetach with ue_lock NULL -> lockmgr(NULL) Fatal trap 12; branches 3-4 (GET_MACADDR STALL / uether_ifattach ENOMEM) ether_ifdetach a never-attached ifnet. The probe is PRODUCT-AGNOSTIC (any 0x05ac vendor device with class 0xff/0xfd/1) and the firmware only needs to STALL one request. KLD-harness-verified: exact pre-failure state replay panics at lockmgr_exclusive+0x5d; detach_usb rerouting + core guard fix validated (SURVIVED, clean loads). 9th and final instance; owner DF-5493.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5514 Β· 14 files
FileTypeDescriptionSize
ipheth_poc.c β€” 5.9 KB view raw
Makefile β€” 146 B ↓ download
build.sh β€” 152 B view raw
run.sh β€” 227 B view raw
build.log β€” 6.0 KB view raw
panic.txt β€” 2.2 KB view raw
run.log β€” 606 B view raw
fix_build.log β€” 14.1 KB view raw
fix_smoke.log β€” 83 B view raw
env.txt β€” 991 B view raw
fix.diff β€” 2.0 KB view raw
VERDICT.md β€” 8.7 KB ↓ raw
manifest.json β€” 2.0 KB view raw
verdict.json β€” 8.6 KB view raw
VERDICT.md
↓ download raw

VERDICT β€” DF-5514 (if_ipheth.c goto-detach-on-unattached, 9th DF-5334 instance)

Status: reproduced (panic, deterministic) Β· fix validated (fixed)

What was claimed and what the code says

ipheth_attach() (sys/bus/u4b/net/if_ipheth.c:217-265) has four error branches that all goto detach; (:235, :241, :252, :258), and the detach: label (:262-263) calls the full ipheth_detach(dev) β€” which calls uether_ifdetach(&sc->sc_ue) (:276) β€” even when uether_ifattach(ue) (:255) was never reached or never succeeded.

  • Branches 1-2 (usbd_set_alt_interface_index :231-236 fails; usbd_transfer_setup :237-242 fails): the ue->ue_sc/ue_dev/ue_udev/ue_lock/ue_methods assignments at :243-247 run only after both calls, so the embedded usb_ether is still entirely zeroed (newbus allocates the softc M_ZERO, sys/kern/subr_bus.c:1953).
  • uether_ifdetach() (sys/bus/u4b/net/usb_ethernet.c:267-305) then:
  • usb_proc_drain(&ue->ue_tq) (:273) β€” no-op, up->up_lock == NULL early-returns (sys/bus/u4b/usb_process.c:397-410);
  • ifp = uether_getifp(ue) (:276) β€” returns &ue->ue_arpcom.ac_if (usb_ethernet.c:133-137), never NULL because the arpcom is embedded in the softc (usb_ethernet.h:80-84) β€” the if (ifp != NULL) guard at :278 is dead code;
  • UE_LOCK(ue) (:281) β†’ lockmgr(NULL, LK_EXCLUSIVE) β†’ Fatal trap 12, fault VA 0x0.
  • Branch 3 (ipheth_get_mac_addr :249-253 fails β€” device just STALLs the 0x00 GET_MACADDR vendor request): ue_lock is valid, but uether_ifattach() never ran, so ue_tq is still zeroed (drain no-op) and the code proceeds to ether_ifdetach(ifp) (:294) on the embedded ifnet that was never if_attach()ed β€” the DF-2341/DF-5493 latent corruption/panic path (ifindex2ifnet[0]/ifnet-list damage).
  • Branch 4 (uether_ifattach :255-259 fails): the only failure mode is usb_proc_create() ENOMEM, whose error path self-frees the ue_tq (usb_process.c usb_proc_create β†’ usb_proc_free), so the state is branch-3-like: valid ue_lock, zeroed tq, never-attached ifnet.

The trigger is fully device-controlled: ipheth_devs[] is a product-agnostic match (if_ipheth.c:174-177) β€” any USB device with vendor 0x05ac exposing interface class 0xff / subclass 0xfd / protocol 1. Branch 1 additionally needs only a STALLed SET_INTERFACE(alt=1) β€” trivial for evil-adapter/dock/VM firmware, no iPhone needed.

How it was verified (KLD-harness-with-production-code)

No QEMU iPhone model exists, and ipheth_attach/ipheth_detach are static (unreachable without a physical Apple device), so β€” exactly like the 8 sibling packs β€” the harness (ipheth_poc.c) reconstructs the branch-1 post-failure state (kmalloc(M_ZERO) softc, sc_iface_no = 0 per :225, lockinit(&sc->sc_lock, ...) per :229 β€” the only softc init before the failing call) and replays ipheth_detach() verbatim using only production code after the goto: usbd_transfer_unsetup(sc->sc_xfer, IPHETH_N_TRANSFER) (:274) and uether_ifdetach(&sc->sc_ue) (:276), linked from stock uether.ko.

Baseline (stock uether.ko, fresh guest)

kldload -v /root/df5514/ipheth_poc.ko β†’ guest died in DDB, serial console (panic.txt):

ipheth_poc: state check: ue_lock=0 ue_dev=0 ue_udev=0 ue_tq.up_lock=0 (all must be 0/NULL)
ipheth_poc: entering ipheth_detach() replay: usbd_transfer_unsetup() + uether_ifdetach() (if_ipheth.c:274+276)
Fatal user address access from kernel mode from kldload at ffffffff80647bad
Fatal trap 12: page fault while in kernel mode
fault virtual address        = 0x0
Stopped at      lockmgr_exclusive+0x5d: orl     (%r14),%esi

Identical trap signature to all 8 siblings (lockmgr_exclusive+0x5d, VA 0x0). vm.sh status β†’ down. The fault address is fixed 0 (NULL deref) β€” panic/DoS, not attacker-controlled-data corruption; no privilege-escalation path exists from this primitive (nothing is written before the fault), consistent with the class's Medium rating.

Fix validation (fix.diff)

  1. vm.sh reset with-src (clean source + clean modules), apply fix.diff in-guest to /usr/src (both hunks, Plan A).
  2. Rebuild uether.ko and if_ipheth.ko β€” both -Werror clean (fix_build.log).
  3. Install patched uether.ko into /boot/kernel.
  4. Re-run the byte-identical harness (md5 f27308481209578091ab79c853df1979): ipheth_poc: SURVIVED uether_ifdetach(), KLDLOAD_RC=0, kldunload clean, dmesg | grep -c "Fatal trap" = 0 (run.log).
  5. Patched driver module if_ipheth.ko also loads/unloads clean (fix_smoke.log).

fix.diff = (a) class-wide core guard: uether_ifdetach() early-returns when ue->ue_lock == NULL (fixes branches 1-2 everywhere); (b) driver: all four pre-attach error branches route to a minimal detach_usb label doing only usbd_transfer_unsetup() + lockuninit() (required for branches 3-4, where ue_lock is already valid but the ifnet was never attached). git apply --check passes against the clean read-only sys/ tree.

Pass-2 negative space (everything else on the USB-net checklist, killed with proof)

  • (a) RX callback / m_adj accounting (DF-5396 class) β€” BOUNDED. uether_newbuf() returns an MCLBYTES cluster mbuf and itself applies m_adj(ETHER_ALIGN) (usb_ethernet.c:549-560: m_data=cl+2, m_len=2046); ipheth adds a second m_adj(m, ETHER_ALIGN) at :511 (m_data=cl+4, m_len=2044 β€” matches FreeBSD verbatim, wastes 2 bytes, harmless). usbd_xfer_set_frame_data(xfer, 0, m->m_data, m->m_len) (:518) programs the HC DMA to ≀2044 bytes into [cl+4, cl+2048). At TRANSFERRED, len = usbd_xfer_frame_len() (:487) is the host-controller-clamped actual length ≀2044 β€” NOT a device-parsed header field like urndis's rm_datalen. Gate len < 14+2 frees (:489-493); m_adj(m, 2) (:495) β†’ m_len=2042; uether_rxmbuf(ue, m, len-2) (:498) sets m_len=len-2 ≀ 2042 = remaining cluster capacity exactly β€” no OOB possible. aframes ≀ requested frames = 1 (IPHETH_RX_FRAMES_MAX=1), so no multi-frame accounting exists (KASSERT frindex < max_frame_count, usb_transfer.c:2148-2153). No mos-DF-5400-style actlen-vs-pktlen confusion: per-frame length is used, not whole-transfer actlen.
  • (b) TX path β€” BOUNDED. m->m_pkthdr.len clamped to IPHETH_BUF_SIZE=1516 (:422-423); usbd_m_copy_in(pc, 0, m, 0, ≀1516) (:425) into frame slot x of capacity 1516 at offset x*1516 ≀ 10612 < bufsize 8*1516=12128 (:415-416); usbd_frame_zero(pc, len, 1516-len) stays in-slot (:430-431); x ≀ 8. Error path frees sc_tx_buf before tr_setup re-entry (:452, :457-461); CANCELLED path frees (:452, :539). No double-free, no leak.
  • (c) register/carrier helpers β€” exact fit. ipheth_get_mac_addr: wLength=6 into sc_data[64] + 6-byte memcpy to ue_eaddr[6] (:187-201). ipheth_tick: wLength=64 == sizeof(sc_data) (:320, var.h:53,75). Note: sc_carrier_on (:328-329) is never read anywhere β€” carrier check is dead code, and a short control response would leave stale sc_data[0]; since the value is never consumed, no impact (Info, not filed).
  • (d) attach/detach error paths β€” THE finding (above). Also checked: lockuninit after the panic point only; usb_proc_create failure self-frees (label-safe for branch 4); branch-3/4 ether_ifdetach-on-unattached = DF-2341/DF-5493 mechanism (core-owned).
  • (e) device-supplied data feeding m_adj/m_copyback/if_output β€” none beyond the above. Device-fed inputs are only: the 6-byte MAC (spoofable like any USB NIC β€” functional), the carrier byte (dead), and the HC-bounded RX length. No RNDIS-style parsed headers exist in this driver. VLAN frames >1516 are silently truncated on TX (:422-423) β€” inherited verbatim from Linux/FreeBSD ipheth semantics (device protocol limit 1516), functional only, not filed.

Bottom line

ipheth_attach()'s four goto detach branches run the full ipheth_detach() β†’ uether_ifdetach() on a usb_ether that was never attached: branches 1-2 fault at lockmgr(NULL) (Fatal trap 12, VA 0x0 β€” reproduced deterministically on the guest with a production-code KLD harness), branches 3-4 reach ether_ifdetach() on a never-attached ifnet. Fully triggerable by an evil USB adapter (any Apple VID + class ff/fd/1 interface) at plug-in. Fixed by the class-wide core guard + minimal detach_usb label; identical harness survives on the patched build. DoS/panic tier, matching the 8 siblings (Medium).

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 files, all hunks); uether.ko and if_ipheth.ko rebuilt -Werror clean; patched uether.ko installed into /boot/kernel; the byte-identical harness re-run (md5 f27308481209578091ab79c853df1979): 'ipheth_poc: SURVIVED uether_ifdetach()' in dmesg, KLDLOAD_RC=0, dmesg|grep -c 'Fatal trap' = 0, kldunload clean, and the patched if_ipheth.ko driver module loads/unloads clean. Baseline panic record in panic.txt, patched survival in run.log + fix_smoke.log. fix.diff verified git-apply-able against the clean read-only sys/ tree (git apply --check OK).

run.log (SURVIVED marker + KLDLOAD_RC=0 + kldstat + UNLOAD_OK + Fatal-trap count 0 + identical md5); fix_build.log (both modules -Werror clean); fix_smoke.log (patched if_ipheth.ko load/unload clean); panic.txt (baseline trap on identical input); fix.diff (git apply --check OK on clean tree)
↓ fix.diffper-fix-DF-5514

Confirmed kernel references

Detail

Evidence (decisive lines)

panic.txt (baseline serial: 3 ipheth_poc markers -> 'Fatal user address access from kernel mode from kldload', Fatal trap 12, fault virtual address = 0x0, 'Stopped at lockmgr_exclusive+0x5d: orl (%r14),%esi', db> prompt, vm.sh status=down); run.log (patched run: SURVIVED marker in dmesg, KLDLOAD_RC=0, kldstat, UNLOAD_OK, Fatal-trap count 0, identical .ko md5 f27308481209578091ab79c853df1979); build.log (-Werror clean harness build); fix_build.log (uether.ko + if_ipheth.ko rebuilt -Werror clean after fix.diff); fix_smoke.log (patched if_ipheth.ko load/unload clean); env.txt (guest uname/kldstat/dmesg); fix.diff (git apply --check OK on clean read-only sys/ tree)

PoC changes

No seeded PoC existed. Wrote ipheth_poc.c from scratch on the DF-5486 sibling harness template: KLD module (MODULE_DEPEND uether) that kmallocs a zeroed struct ipheth_softc, replicates the only pre-failure softc init exactly (sc_iface_no=0 per :225, lockinit(&sc->sc_lock,...) per :229), prints the zeroed ue state, then replays ipheth_detach() verbatim: production usbd_transfer_unsetup(sc->sc_xfer, IPHETH_N_TRANSFER) (:274) + production uether_ifdetach(&sc->sc_ue) (:276). Built -Werror clean on the first attempt. Physical Apple-device emulation is infeasible in QEMU (no iPhone USB gadget; ipheth_attach/ipheth_detach are static); the branch-1 state + exact production call-sequence replay is the strongest runnable evidence and uses only production code after the goto (same evidence class as the 8 sibling packs).

Verified recommended fix

ipheth_attach(): route all four pre-uether_ifattach() failures to a detach_usb label doing only usbd_transfer_unsetup()+lockuninit(); uether_ifdetach(): early-return when ue->ue_lock==NULL (class-wide, same guard as DF-5334/5384/5486 fix)

Verdict

Deterministic kernel panic at USB device plug-in: ipheth_attach()'s four error branches (if_ipheth.c:235 'Cannot set alternate setting', :241 'Cannot setup USB transfers', :252 'Cannot get MAC address', :258 'could not attach interface') all goto-jump into the full ipheth_detach() (if_ipheth.c:262-263 -> :267-281) while the embedded usb_ether was never attached via uether_ifattach(). On branches 1-2 the ue_sc/ue_dev/ue_udev/ue_lock/ue_methods assignments at :243-247 have not run yet (softc is newbus M_ZERO, subr_bus.c:1953), so uether_ifdetach() (usb_ethernet.c:267-305) lockmgr()s through the NULL ue_lock β€” Fatal trap 12, fault VA 0x0, Stopped at lockmgr_exclusive+0x5d β€” after usb_proc_drain() no-ops on the zeroed ue_tq and the ifp!=NULL guard proves dead (uether_getifp() returns the embedded arpcom ifnet, usb_ethernet.c:133-137). On branches 3-4 (ue_lock valid, uether_ifattach never ran / self-freed its ue_tq via usb_proc_create's error path) the same call instead reaches ether_ifdetach() on the never-if_attach()ed embedded ifnet β€” the DF-2341/DF-5493 latent corruption path. The trigger is fully device-controlled: ipheth_devs[] is a product-agnostic match (:174-177), so any evil USB adapter with vendor 0x05ac exposing interface class 0xff/subclass 0xfd/protocol 1 that STALLs SET_INTERFACE(alt 1), omits the bulk endpoints, or STALLs the 0x40 GET_MACADDR vendor request panics the host at plug-in β€” 9th and last driver instance of the DF-5334 class (cdce/aue/axge/urndis/mos/udav/kue/cue). Verified with a KLD harness replaying ipheth_detach() verbatim (its two production calls in order) on the exact branch-1 state, using production usbd_transfer_unsetup()+uether_ifdetach(): baseline on the fresh 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) ipheth_attach detach_usb label doing only unsetup+lockuninit, required for branches 3-4) both modules rebuilt -Werror clean and the byte-identical harness (md5 f27308481209578091ab79c853df1979) SURVIVED with KLDLOAD_RC=0, 0 Fatal traps, clean unload; patched if_ipheth.ko loads/unloads clean (run.log, fix_smoke.log). Fault address is fixed 0 (NULL lock pointer): panic/DoS from physical/VM USB access, no attacker-controlled data is written, no privesc path β€” same tier as the 8 siblings (Medium).