cdce_attach() failure paths call cdce_detach() on a never-attached usb_ether -> kernel panic at device plug-in (NULL lockmgr, then latent ifindex/ifnetlist corruption)
Summary
The FreeBSD attach-failure detach pattern was imported unchanged, but DF's uether_getifp() returns the EMBEDDED ifnet (never NULL) so uether_ifdetach's guards are dead code: device-controlled attach failure (no data interface / no valid alt setting - the STOCK QEMU usb-net gadget's RNDIS config) -> lockmgr(ue->ue_lock==NULL) Fatal trap 12 at VA 0; had it not faulted, if_detach on the zeroed ifnet would NULL ifindex2ifnet[0] (destroying lo0) and corrupt the global ifnet list tail. REPRODUCED 3/3 with stock -device usb-net, no code needed, DDB backtrace pinned; fixed modules boot to ssh with the data interface attaching (ue0 works). ue_lock-NULL guard in uether_ifdetach (class-wide for all uether drivers) + detach_usb label fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5334 Β· 15 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 4.6 KB | β raw | |
| VERDICT.md | β | 4.9 KB | β raw | |
| trigger-attach.sh | β | 1.7 KB | view raw | |
| serial_drive.py | β | 1.4 KB | view raw | |
| build.sh | β | 281 B | view raw | |
| run.sh | β | 258 B | view raw | |
| build.log | β | 1.9 KB | view raw | |
| run.log | β | 12.3 KB | view raw | |
| run.serial.log | β | 12.3 KB | view raw | |
| run.2.log | β | 771 B | view raw | |
| panic.txt | β | 1.4 KB | view raw | |
| env.txt | β | 379 B | view raw | |
| fix.diff | β | 1.7 KB | view raw | |
| manifest.json | β | 1.6 KB | view raw | |
| verdict.json | β | 4.8 KB | view raw |
DF-5334 β cdce_attach failure path calls cdce_detach() on a
never-attached usb_ether -> kernel panic (NULL lockmgr / zeroed ifnet teardown)
What
sys/bus/u4b/net/if_cdce.c (USB CDC-Ethernet / NCM host driver). When a
USB device is claimed by cdce_probe() but cdce_attach() fails early β
e.g. the CDC union descriptor names a slave interface that does not exist
("no data interface found", if_cdce.c:610), or no alternate setting of
the data interface admits a bulk IN/OUT transfer setup ("No valid
alternate setting found", if_cdce.c:660) β the error path does
goto detach; cdce_detach(dev); (if_cdce.c:731). At that point the
usb_ether embedded in the softc was never initialised: ue->ue_lock
is assigned only at if_cdce.c:720 (after all failure gotos), the
ue_tq task queue is created by uether_ifattach() (never called), and
the embedded ue_arpcom.ac_if was never if_attach()ed.
uether_ifdetach() then walks the FreeBSD-era teardown on this dead
object and, on DragonFly, every guard is dead code:
usb_proc_drain()early-returns (zeroedup_lock) β safe;ifp = uether_getifp(ue)returns the address of the embeddedue->ue_arpcom.ac_if(usb_ethernet.c:136) which on DragonFly is never NULL, soif (ifp != NULL)(usb_ethernet.c:278) is always true;UE_LOCK(ue)βlockmgr(ue->ue_lock == NULL)β fault VA 0x0 (first observed fault,uether_ifdetach+0x2f).
Had the NULL ue_lock not faulted first, the teardown would proceed to
ether_ifdetach() β if_detach() on the zeroed embedded ifnet, which
* sets ifindex2ifnet[0] = NULL β destroying the loopback interface's
index slot (sys/net/if.c:966), and
* executes TAILQ_REMOVE(&ifnetlist, ifp, if_link) (sys/net/if.c:982)
on a zeroed if_link (tqe_prev == NULL) β corrupts the global
ifnet list tail and writes through NULL.
Reachability
An attacker supplies a crafted USB device (evil NIC / charging-cable /
dock; "evil USB network adapter" is exactly this driver's threat model).
cdce_probe() matches on interface class/subclass only
(if_cdce.c:538-547), so a device presenting a CDC ECM/NCM/MDLM control
interface whose data-interface alt settings never yield both bulk
endpoints β or whose union descriptor slave number is out of range β
deterministically hits the branch. No exploit payload is needed: the
stock QEMU usb-net gadget (RNDIS+CDC dual config, as used to give VMs
networking) triggers the panic at boot, because its RNDIS control
interface matches cdce and its data interface fails the alternate-setting
loop.
Reproduce (baseline β unpatched guest)
cd dfbsd-qemu
qemu-img snapshot -a with-src disk.qcow2 # clean disk, stock modules
QEMU_EXTRA="-device piix3-usb-uhci,id=uhci0 -netdev user,id=n1 \
-device usb-net,bus=uhci0.0,netdev=n1" ./vm.sh up 240
Expected: vm.sh up times out (ssh never starts). Serial log shows:
cdce0: <QEMU USB Net RNDIS Control Interface> on usbus0 cdce0: No valid alternate setting found Fatal trap 12: page fault while in kernel mode fault virtual address = 0x0 instruction pointer = 0x8:0xffffffff80647bad Stopped at lockmgr_exclusive+0x5d: orl (%r14),%esi db> trace lockmgr_exclusive() at lockmgr_exclusive+0x5d uether_ifdetach() at uether_ifdetach+0x2f cdce_detach() at cdce_detach+0x2a cdce_attach() at cdce_attach+0x56b device_doattach() at device_doattach+0x3af usb_probe_and_attach() at usb_probe_and_attach+0x176 uhub_explore() at uhub_explore+0x231 usb_bus_explore() at usb_bus_explore+0xd0 usb_process() at usb_process+0x76
Full untrimmed logs: run.log (decisive run, incl. DDB trace + dumpsys),
panic.txt (extract). Backtrace capture used serial_drive.py (drives
the DDB prompt over the serial unix socket).
Fix validation (same QEMU config, patched modules)
# in guest, as root, with fix.diff applied to /usr/src: cd /usr/src/sys/bus/u4b/net/uether && make && cp uether.ko /boot/kernel/ cd /usr/src/sys/bus/u4b/net/cdce && make && cp if_cdce.ko /boot/kernel/ # reboot with the same -device usb-net attached:
Expected (observed): boot completes, SSH comes up, dmesg shows
cdce0: <QEMU USB Net RNDIS Control Interface> on usbus0 cdce0: No valid alternate setting found device_probe_and_attach: cdce0 attach returned 6 cdce0: <QEMU USB Net Data Interface> on usbus0 cdce0: faking MAC address ue0: <USB Ethernet> on cdce0
i.e. the same device that panicked the kernel now fails the control-
interface claim cleanly (ENXIO), then successfully attaches to the data
interface and yields a working ue0. dmesg | grep -c "Fatal trap" = 0.
See run.2.log, build.log, fix.diff.
DF-5334 β VERDICT
Status: reproduced (kernel panic, deterministic, 3 independent observations) β fix validated (patched modules boot clean with the same device attached).
Root cause (path:line)
cdce_attach()initialisessc->sc_lockatsys/bus/u4b/net/if_cdce.c:580but assignsue->ue_lock,ue->ue_sc/dev/udev/methodsonly atif_cdce.c:717-721, i.e. after every failuregoto.- The failure branches reachable from device-controlled descriptors /
device behaviour:
* "no data interface found" β
if_cdce.c:603-611: the CDC union descriptor'sbSlaveInterface[0](if_cdce.c:593, only checked forbLength >= sizeof(*ud)at:586) matches no interface number the device actually presented. * "No valid alternate setting found" βif_cdce.c:642-664: for every alt setting 0..31 eitherusbd_set_alt_interface_index()fails (device STALLs SET_INTERFACE) orusbd_transfer_setup()finds no bulk IN+OUT pair. Both dogoto detachβcdce_detach(dev)(if_cdce.c:730-732). cdce_detach()unconditionally callsuether_ifdetach(ue)(if_cdce.c:743).uether_ifdetach()(sys/bus/u4b/net/usb_ethernet.c:267-305): *usb_proc_drain(&ue->ue_tq)early-returns (zeroedup->up_lock) βsys/bus/u4b/usb_process.c. *ifp = uether_getifp(ue)returns&ue->ue_arpcom.ac_ifβ the embedded ifnet, never NULL on DragonFly (usb_ethernet.c:133-137), so the FreeBSD-eraif (ifp != NULL)guard at:278is dead code. *UE_LOCK(ue)at:281βlockmgr(NULL, LK_EXCLUSIVE)β read/write through NULL β Fatal trap 12, fault VA 0x0 (observed:lockmgr_exclusive+0x5d: orl (%r14),%esi, r14=0, backtraceuether_ifdetach+0x2f).- Latent second-stage corruption (proven statically, pre-empted by the
first fault):
ether_ifdetach()βif_detach()on the zeroed ifnet wouldifindex2ifnet[0] = NULL(destroying lo0's index slot,sys/net/if.c:966) andTAILQ_REMOVE(&ifnetlist, ifp, if_link)(sys/net/if.c:982) withtqe_prev == NULLβ setsifnetlist.tqh_last = NULL(global list corruption) and writes through NULL.
Why it is DragonFly-specific
FreeBSD's uether_getifp() returns ue->ue_ifp, a pointer field that is
NULL until if_alloc(). DragonFly embeds struct arpcom in
usb_ether and uether_getifp() returns its address β the NULL guard
can never fire. The failure-path cdce_detach() call pattern was
imported from FreeBSD where it was harmless; on DragonFly it is a
plug-time kernel panic.
Observed evidence
- Baseline (stock
/boot/kernel/if_cdce.ko,uether.ko), stock QEMUusb-net(RNDIS+CDC) on UHCI, boot-time enumeration:run.log/panic.txtβ panic at attach, DDB backtrace captured via serial socket (serial_drive.py), crash dump written bycall dumpsys. Guest never reaches ssh;vm.sh uptimes out. - Reproduced 3x: (a) qemu-xhci hot-plug
device_add usb-net(guest died minutes after attach; log lost to reboot), (b) UHCI boot-time panic (vm.sh serial log), (c) UHCI boot-time panic with DDB trace (run.log). Deterministic β 3/3 boots with the device panicked. - Patched (fix.diff β rebuilt
if_cdce.ko,uether.ko, installed to/boot/kernel), identical QEMU command line: boots to ssh; dmesg:cdce0: No valid alternate setting found,device_probe_and_attach: cdce0 attach returned 6, then clean re-probe βcdce0: <QEMU USB Net Data Interface>,ue0: <USB Ethernet> on cdce0(run.2.log),dmesg | grep -c "Fatal trap"= 0.
Impact
Unauthenticated local DoS (kernel panic) from a crafted USB device at
plug/enumeration time; also any VM configuration using QEMU's stock
usb-net device on a DragonFly master guest panics at boot. No
privilege escalation: the fault is a NULL-page access (write target
fixed at VA 0), not a controllable overwrite. The latent if_detach()
second stage (global ifnet list corruption + lo0 index destruction)
would turn a survivable first fault into persistent networking-state
corruption, but is pre-empted by the immediate NULL lockmgr fault.
Fix (fix.diff, module-only β both .ko rebuilt in-guest)
sys/bus/u4b/net/usb_ethernet.c:uether_ifdetach()bails out early whenue->ue_lock == NULL(never passeduether_ifattach()). Guards the entire uether driver class on DragonFly.sys/bus/u4b/net/if_cdce.c: the two pre-attach failure branches take a newdetach_usblabel that only doesusbd_transfer_unsetup()+lockuninit()instead of the fullcdce_detach().
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; if_cdce.ko and uether.ko rebuilt (-Werror clean) and installed into /boot/kernel; rebooted with the identical QEMU usb-net configuration that panicked 3/3 baseline boots: guest boots to ssh, dmesg shows 'cdce0: No valid alternate setting found' + 'device_probe_and_attach: cdce0 attach returned 6' followed by successful attach of the data interface and a working ue0; dmesg|grep -c 'Fatal trap' = 0. Baseline panic and patched-clean boot both on record in run.log / run.2.log.
run.2.log (patched boot serial: clean ENXUI then ue0 up, 'No core dumps found'); build.log (module rebuild + md5 match into /boot/kernel); fix.diff (unified, git-apply-able against clean sys/ tree)
Confirmed kernel references
- sys/bus/u4b/net/if_cdce.c:580
- sys/bus/u4b/net/if_cdce.c:593
- sys/bus/u4b/net/if_cdce.c:603-613
- sys/bus/u4b/net/if_cdce.c:642-664
- sys/bus/u4b/net/if_cdce.c:717-721
- sys/bus/u4b/net/if_cdce.c:730-732
- sys/bus/u4b/net/if_cdce.c:741-743
- sys/bus/u4b/net/usb_ethernet.c:133-137
- sys/bus/u4b/net/usb_ethernet.c:267-305
- sys/net/if.c:966
- sys/net/if.c:982
Detail
Evidence (decisive lines)
run.log/run.serial.log (full serial: 'cdce0: No valid alternate setting found' -> Fatal trap 12 fault VA 0x0 -> DDB trace lockmgr_exclusive <- uether_ifdetach+0x2f <- cdce_detach+0x2a <- cdce_attach+0x56b <- device_doattach <- usb_probe_and_attach <- uhub_explore, plus dumpsys); panic.txt (extract); run.2.log (patched boot: clean 'attach returned 6' then 'ue0: <USB Ethernet> on cdce0', 'No core dumps found'); build.log (both fixed modules rebuilt -Werror clean, md5-matched into /boot/kernel); fix.diff (git-apply-able, module-only); env.txt (uname, cc 8.3, ue0 state, kldstat)
PoC changes
No compiled PoC was needed: the trigger is the stock QEMU 'usb-net' device (12 Mbps, CDC/RNDIS dual config) attached to a UHCI controller; initial attempts on qemu-xhci also panicked (hot-plug). Added serial_drive.py to drive the DDB prompt over a unix-socket serial to capture the backtrace and call dumpsys.
Verified recommended fix
uether_ifdetach(): return early when ue->ue_lock == NULL; cdce_attach(): route the two pre-attach failure branches to a detach_usb label doing only usbd_transfer_unsetup()+lockuninit() instead of full cdce_detach()
Verdict
Deterministic kernel panic at USB device enumeration: cdce_attach() failure branches (if_cdce.c:610 'no data interface found' / :660 'No valid alternate setting found') jump to cdce_detach() before the embedded usb_ether was initialised (ue_lock assigned only at :720, ue_tq/ifnet never created). uether_ifdetach() then lockmgr()s through the NULL ue_lock (fault VA 0x0) and would proceed to if_detach() on the zeroed embedded ifnet, NULLing ifindex2ifnet[0] (lo0) and corrupting the global ifnetlist via TAILQ_REMOVE - the NULL-guard it relies on is dead code on DragonFly because uether_getifp() returns the embedded arpcom ifnet which is never NULL. Reproduced 3/3 with the STOCK QEMU usb-net (RNDIS+CDC) device at boot and via hot-plug; backtrace captured through DDB over serial. Patched modules (fix.diff: early-out in uether_ifdetach when ue_lock==NULL + detach_usb label in cdce_attach) boot clean with the same device: ENXIO on the failing claim, driver then attaches the data interface and ue0 works; 0 Fatal traps.
No comments yet.