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

mos_attach() failure paths call mos_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

5th instance of the DF-5334/5384/5395/5397 uether class: both attach failure branches goto the full detach - the usbd_transfer_setup failure (device-controlled: probe matches only VID:PID/config/iface, an evil adapter with a mos_devs[] id omitting the bulk endpoints fails transfer setup) runs uether_ifdetach on an entirely-zeroed usb_ether -> lockmgr(NULL) Fatal trap 12 VA 0; the uether_ifattach-failure branch ether_ifdetaches a never-attached ifnet (ifindex2ifnet[0]/ifnet-list corruption). KLD-harness-verified: exact post-failure state replay panics at lockmgr_exclusive+0x5d 1/1; detach_usb rerouting + class-wide ue_lock guard fix validated (SURVIVED, clean loads).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5399 Β· 13 files
FileTypeDescriptionSize
README.md β€” 4.7 KB ↓ raw
VERDICT.md β€” 5.6 KB ↓ raw
mos_attach_poc.c β€” 4.2 KB view raw
Makefile β€” 147 B ↓ download
build.log β€” 5.8 KB view raw
panic.txt β€” 1020 B view raw
run.log β€” 2.8 KB view raw
env.txt β€” 370 B view raw
fix.diff β€” 1.5 KB view raw
fix_uether_build.log β€” 7.0 KB view raw
fix_if_mos_build.log β€” 6.9 KB view raw
manifest.json β€” 1.0 KB view raw
verdict.json β€” 8.5 KB view raw

DF-5399 β€” mos_attach() detach-on-unattached panic β€” evidence pack

Finding. mos_attach() (sys/bus/u4b/net/if_mos.c) routes both of its failure branches into the full mos_detach():

  • branch 1 β€” usbd_transfer_setup() failure: if_mos.c:700-707 (goto detach at :706)
  • branch 2 β€” uether_ifattach() failure: if_mos.c:722-726 (goto detach at :725)
  • detach: label: if_mos.c:730-732 β†’ mos_detach(dev)

mos_detach() (if_mos.c:756-767) then runs usbd_transfer_unsetup(sc->sc_xfer, MOS_ENDPT_MAX) (:762) and uether_ifdetach(ue) (:763) on a usb_ether that was never attached: every ue field is still zero because the assignments happen only at if_mos.c:708-712, AFTER the failing usbd_transfer_setup(). Before that call, the only softc state attach established is sc->mos_flags (:694) and lockinit(&sc->sc_lock) (:697).

uether_ifdetach() (sys/bus/u4b/net/usb_ethernet.c:267-305): - usb_proc_drain(&ue->ue_tq) (:273) no-ops on the zeroed taskqueue (up->up_lock == NULL, sys/bus/u4b/usb_process.c:406-410); - ifp = uether_getifp(ue) (:276) returns &ue->ue_arpcom.ac_if β€” the embedded ifnet, NEVER NULL β€” so the if (ifp != NULL) guard (:278) is dead code; - UE_LOCK(ue) (:281, macro at :61) = lockmgr(NULL, LK_EXCLUSIVE) β†’ Fatal trap 12, fault VA 0x0.

Trigger (fully device-controlled). Any evil USB adapter presenting one of the 4 mos_devs[] VID:PID pairs (MosChip 0x9710:0x7730 / 0x9710:0x7830 / 0x9710:0x7832, Sitecom LN-030 0x0df6:0x0021 β€” if_mos.c:150-155) at config index 0 / iface 0 (mos_probe, if_mos.c:664-679, matches on IDs only, never on descriptors) but WITHOUT the bulk-IN and bulk-OUT endpoints makes usbd_transfer_setup() fail (USB_ERR_NO_ENDPOINT) at plug-in β†’ branch 1.

Branch 2 (latent corruption) needs uether_ifattach() to fail, i.e. usb_proc_create() ENOMEM: ue_lock is valid there, so the run proceeds to usb_callout_drain() on a never-initialized watchdog, ether_ifdetach() on a never-if_attach()ed ifnet (ifindex2ifnet[0] nulling + global ifnet list corruption, per the DF-5334/DF-5384/DF-5395 analysis) and a double devfs_clone_bitmap_put() of ue_unit. Not attacker-timed; branch 1 is the deterministic one.

Same class as DF-5334 (if_cdce.c), DF-5384 (if_aue.c), DF-5395 (if_axge.c), DF-5397 (if_urndis.c) β€” this pack covers the if_mos.c instance.

Reproduce

No QEMU MCS7730/7830/7832 model exists and mos_attach/mos_detach are static, so the evidence class is a KLD harness driving production code (aue/axge precedents): the harness constructs the exact post-branch-1 softc state (zeroed struct mos_softc + lockinit(&sc->sc_lock) per if_mos.c:697) and replays mos_detach() verbatim β€” the production usbd_transfer_unsetup() then the production uether_ifdetach() (uether.ko auto-loads via MODULE_DEPEND).

# on host
scp -F dfbsd-qemu/config -r <this dir> dfbsd:/root/df5399
vm.sh run_root 'cd /root/df5399 && make -m /usr/src/share/mk'
vm.sh run_root 'kldload -v /root/df5399/mos_poc.ko; echo KLDLOAD_RC=$?'

Baseline expected: three mos_poc: markers, then Fatal trap 12: page fault while in kernel mode, fault virtual address = 0x0, current process = ... (kldload), Stopped at lockmgr_exclusive+0x5d: orl (%r14),%esi, guest dead at db>. (panic.txt; ssh session dies with the guest β€” read the serial console via vm.sh log.)

Patched expected (fix.diff applied to guest /usr/src, uether.ko rebuilt and installed into /boot/kernel): mos_poc: SURVIVED uether_ifdetach(), KLDLOAD_RC=0, dmesg | grep -c "Fatal trap" = 0, clean kldunload (twice), and the patched if_mos.ko driver module loads/unloads clean. (run.log)

Fix validation

fix.diff = (a) if_mos.c: both failures goto detach_usb: doing only usbd_transfer_unsetup() + lockuninit(); (b) class-wide uether_ifdetach() early-return when ue->ue_lock == NULL (guards every unconverted uether driver, same as the DF-5334/DF-5384/DF-5395 validated fixes). (b) alone fixes branch 1; (a) is required for branch 2. Applied in-guest with patch -p1 (Plan A, all hunks), both modules rebuilt -Werror clean (fix_uether_build.log, fix_if_mos_build.log), identical harness re-run SURVIVED (run.log). git apply --check passes against the clean read-only sys/ tree.

Files

  • mos_attach_poc.c / Makefile β€” the KLD harness
  • build.log β€” harness build (-Werror clean)
  • panic.txt β€” baseline serial-console panic record
  • run.log β€” baseline + patched decisive output
  • fix.diff β€” git-apply-able unified diff (if_mos.c + usb_ethernet.c)
  • fix_uether_build.log / fix_if_mos_build.log β€” patched module builds
  • env.txt β€” guest uname/compiler/kldstat
  • VERDICT.md β€” full narrative
  • manifest.json / verdict.json β€” machine records
VERDICT.md
↓ download raw

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

Finding. mos_attach() (sys/bus/u4b/net/if_mos.c) has the DF-5334 "goto-detach-on-unattached" pattern: the usbd_transfer_setup() failure branch (if_mos.c:704-707) and the uether_ifattach() failure branch (if_mos.c:722-726) both jump to detach: (if_mos.c:730-732) and call the full mos_detach() (if_mos.c:756-767), which calls uether_ifdetach(&sc->sc_ue) (if_mos.c:763) 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_mos.c:708-712 β€” after the failing call; before it only sc->mos_flags (:694), device_set_usb_desc() (:696) and lockinit(&sc->sc_lock) (:697) have run:

  • 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/DF-5395).
  • 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, only when usb_proc_create() hits ENOMEM) leaves ue_lock valid but the ifnet never if_attach()ed; uether_ifdetach() then proceeds to usb_callout_drain() on a never-initialized watchdog and 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 β€” latent list corruption, pre-empted in branch 1 by the NULL-write fault. Branch 1 is the attacker-driven one: mos_probe() (if_mos.c:664-679) matches on VID:PID + config/iface index only, never on descriptors, so any evil USB adapter presenting MosChip 0x9710:0x7730 / 0x7830 / 0x7832 or Sitecom 0x0df6:0x0021 at config 0 / iface 0 while omitting the bulk-IN/OUT endpoints makes usbd_transfer_setup() fail (USB_ERR_NO_ENDPOINT) at plug-in β†’ deterministic panic.

Verification (evidence class: KLD harness driving production code β€” QEMU cannot emulate an MCS7730/7830/7830; mos_attach/mos_detach are static and reachable only with hardware):

  • The harness (mos_attach_poc.c) constructs the exact post-failure state (zeroed struct mos_softc + lockinit(&sc->sc_lock) per if_mos.c:697 β€” the only lock initialisation before the failing call at :700) and replays mos_detach() verbatim: the production usbd_transfer_unsetup() (if_mos.c:762) then the production uether_ifdetach() (if_mos.c:763). uether.ko auto-loads via MODULE_DEPEND.
  • Baseline (pristine with-src guest, stock /boot/kernel/uether.ko): the three markers print (ue_lock=0 ue_dev=0 ue_udev=0 ue_tq.up_lock=0), then Fatal trap 12: page fault while in kernel mode, fault virtual address = 0x0, current process = 1008 (kldload), Stopped at lockmgr_exclusive+0x5d: orl (%r14),%esi β€” guest dead at DDB. Record: panic.txt. 1/1 run (deterministic NULL deref; the same sink was proved 3/3 in-situ by DF-5334 and reproduced for aue/axge).
  • Patched (fix.diff applied to guest /usr/src via patch -p1 Plan A, both hunks in both files; uether.ko + if_mos.ko rebuilt -Werror clean; patched uether.ko installed into /boot/kernel): the identical harness run prints mos_poc: SURVIVED uether_ifdetach() (the guard's early return), KLDLOAD_RC=0, dmesg | grep -c 'Fatal trap' = 0, kldunload clean (twice β€” stable), and the patched if_mos.ko driver module loads/unloads clean. Records: run.log, fix_uether_build.log, fix_if_mos_build.log.
  • After validation the guest was vm.sh reset with-src β€” md5s of both touched sources match the pristine read-only tree, no artifacts left.

Impact. Plug-time kernel NULL dereference (panic/DoS) from a malicious USB device — same severity tier and threat model as DF-5334/DF-5384/ DF-5395/DF-5397 (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/DF-5395/DF-5397. Those were filed against if_cdce.c / if_aue.c / if_axge.c / if_urndis.c; the persisted class-wide fix (uether_ifdetach guard) was applied in-guest for validation only and 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_mos.c instance (root cause identical, trigger device-controlled the same way).

Fix. fix.diff: (a) mos_attach() routes both pre-attach failures to a detach_usb: label doing only usbd_transfer_unsetup() + lockuninit() β€” required for the branch-2 ether_ifdetach-on-unattached corruption; (b) uether_ifdetach() early-returns when ue->ue_lock == NULL (class-wide, matches the DF-5334/DF-5384/DF-5395 validated fix; protects every not-yet-converted uether driver).

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_mos.ko rebuilt -Werror clean; patched uether.ko installed into /boot/kernel; the IDENTICAL harness re-run printed 'mos_poc: SURVIVED uether_ifdetach()', KLDLOAD_RC=0, dmesg|grep -c 'Fatal trap' = 0, kldunload clean (run twice - stable), and the patched if_mos.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 + UNLOAD_OK x2 + FATAL_COUNT=0 + DRIVER_LOAD_OK/DRIVER_UNLOAD_OK); fix_uether_build.log / fix_if_mos_build.log (-Werror clean); panic.txt (baseline trap on identical input); fix.diff (git apply --check OK on clean tree)
↓ fix.diffper-fix-DF-5399

Confirmed kernel references

Detail

Evidence (decisive lines)

panic.txt (baseline serial: 3 mos_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, UNLOAD_OK x2, FATAL_COUNT=0, patched if_mos.ko DRIVER_LOAD_OK/DRIVER_UNLOAD_OK); build.log + fix_uether_build.log + fix_if_mos_build.log (-Werror clean builds); env.txt (guest uname/kldstat); fix.diff (git-apply-able against clean sys/, git apply --check OK)

PoC changes

No seeded PoC existed. Wrote mos_attach_poc.c from scratch (aue DF-5384 harness shape, ASCII-only comments): KLD harness (MODULE_DEPEND uether) that kmallocs a zeroed struct mos_softc, lockinit()s sc->sc_lock exactly as if_mos.c:697 does (the only lock initialisation before the failing usbd_transfer_setup() at :700), prints the zeroed ue state, then replays mos_detach() verbatim: production usbd_transfer_unsetup(sc->sc_xfer, MOS_ENDPT_MAX) (if_mos.c:762) + production uether_ifdetach(&sc->sc_ue) (if_mos.c:763). Built -Werror clean on the first try. Physical MCS7730/7830 emulation is infeasible in QEMU; static mos_attach/mos_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

mos_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/DF-5395 fix)

Verdict

Deterministic kernel panic at USB device plug-in: mos_attach()'s usbd_transfer_setup() failure branch (if_mos.c:704-707 'allocating USB transfers failed') goto-jumps into the full mos_detach() (if_mos.c:730-732) while the embedded usb_ether is still entirely zeroed (ue_lock/ue_dev/ue_udev/ue_methods/ue_tq are assigned only at if_mos.c:708-712, AFTER the failing call; before it only sc->mos_flags at :694, device_set_usb_desc() at :696 and lockinit(&sc->sc_lock) at :697 have run). mos_detach() then calls uether_ifdetach(ue) (if_mos.c:763), 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) / DF-5395 (if_axge) / DF-5397 (if_urndis) 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 mos_devs[] VID:PID pairs (MosChip 0x9710:0x7730 / 0x9710:0x7830 / 0x9710:0x7832, Sitecom LN-030 0x0df6:0x0021) at config 0/iface 0 - mos_probe() (if_mos.c:664-679) matches IDs only, never descriptors - without the bulk-IN/OUT endpoints fails the transfer setup with USB_ERR_NO_ENDPOINT. 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 mos_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) mos_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_mos.ko driver module 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/DF-5395/DF-5397 (Medium).