uether_ifdetach() performs no never-attached state validation β class-wide owner of the 8-instance DF-5334 goto-detach panic family (lockmgr through NULL ue_lock; ether_ifdetach() on a never-if_attach()ed ifnet)
Summary
The core tears down whatever ue it is handed with zero state checks: the drain no-ops on an all-zero ue_tq, the embedded-ifp getter defeats the NULL guard, lockmgr goes through NULL (branch A) or ether_ifdetach tears down a never-published ifnet (branch B - usb_proc_create ENOMEM, ue_tq unwound to zero, attach task never queued, plus sysctl_ctx_free on a never-init ctx and devfs_clone_bitmap_put of a never-allocated unit). Owner of the 8 driver instances proven this pass (DF-5334/5384/5395/5397/5399/5411/5453/5486). Both branches reproduced against the PRODUCTION uether_ifdetach (Fatal trap 12 at lockmgr_exclusive+0x5d and if_down_dispatch+0x66 respectively); two-flag central-guard fix validated (both SURVIVED, clean unloads).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5493 Β· 15 files| File | Type | Description | Size | |
|---|---|---|---|---|
| ue_guard_a.c | β | 2.9 KB | view raw | |
| ue_guard_b.c | β | 3.8 KB | view raw | |
| Makefile.a | β | 163 B | β download | |
| Makefile.b | β | 163 B | β download | |
| build.sh | β | 165 B | view raw | |
| run.sh | β | 448 B | view raw | |
| build.log | β | 6.2 KB | view raw | |
| build_b.log | β | 6.2 KB | view raw | |
| panic_a_serial.txt | β | 2.2 KB | view raw | |
| panic_b_serial.txt | β | 2.2 KB | view raw | |
| run_patched.log | β | 2.6 KB | view raw | |
| env.txt | β | 362 B | view raw | |
| fix.diff | β | 2.6 KB | view raw | |
| VERDICT.md | β | 7.9 KB | β raw | |
| README.md | β | 3.5 KB | β raw |
DF-5493 β uether_ifdetach() missing never-attached guard (class-wide owner)
status: reproduced / impact: panic / confidence: certain / fix validated
What
uether_ifdetach() (sys/bus/u4b/net/usb_ethernet.c:267-305) validates
nothing about the struct usb_ether it is handed:
usb_proc_drain()(:273) silently no-ops on an all-zeroue_tq(sys/bus/u4b/usb_process.c:409),uether_getifp()(:276, :133-137) returns the embedded arpcom ifnet, so theif (ifp != NULL)guard (:278) is dead code β always true,- it then lockmgr()s through
ue_lock(:281) and runsether_ifdetach(ifp)(:294) unconditionally.
Eight drivers this pass (DF-5334/5384/5395/5397/5399/5411/5453/5486) call
their detach β and thus uether_ifdetach() β on attach-failure paths where
the ue was never attached. This file owns the class-wide defect and the
central guard; the per-driver findings own each driver's goto detach.
Two failure arms verified on the guest
- Branch A (ue fields zero β all 8 sibling drivers' pre-
uether_ifattachfailures, e.g.if_cue.c:405-410):UE_LOCK(ue)βlockmgr(NULL)βFatal trap 12, fault VA 0x0, Stopped at lockmgr_exclusive+0x5d(panic_a_serial.txt). - Branch B (
uether_ifattach()itself failed atusb_proc_create()ENOMEM,usb_ethernet.c:173-178; ue_lock valid, ue_tq unwound to zero byusb_process.c:217-219,244; every driver'sif (uether_ifattach(ue)) goto detach;branch):ether_ifdetach()βif_down()on the never-if_attach()ed ifnet βFatal trap 12, fault VA 0x0, Stopped at if_down_dispatch+0x66(panic_b_serial.txt). Behind this crash also lurksysctl_ctx_free()on a never-init'd ctx (:297) anddevfs_clone_bitmap_put(..., ue_unit=0)of a never-allocated unit (:300) which would hand unit 0 to the next adapter while a live ue0 exists.
Rig (KLD harness + production code)
ue_guard_a.c / ue_guard_b.c: each kmallocs a zeroed struct usb_ether,
constructs the exact post-failure state, and calls the production
uether_ifdetach() from the stock auto-loaded uether.ko
(MODULE_DEPEND(uether)). The failing call sites are static driver-attach
paths needing a physical evil-USB device (or a taskqueue-creation ENOMEM),
so state + call-sequence replay is the strongest runnable evidence β same
evidence class as the 8 sibling rigs (see findings/poc/DF-5486/).
Build / run (in guest)
cd /root/df5493 && sh ./build.sh # builds ue_guard_a.ko + ue_guard_b.ko, -Werror sh ./run.sh a # baseline: Fatal trap 12 VA 0x0 (lockmgr NULL) # (reset with-src, rebuild) sh ./run.sh b # baseline: Fatal trap 12 VA 0x0 (if_down_dispatch)
With fix.diff applied to /usr/src, uether.ko rebuilt and installed,
and the rigs rebuilt against the patched header, both branches print
SURVIVED uether_ifdetach(), KLDLOAD_RC=0, traps: 0, clean unload
(run_patched.log).
Fix
fix.diff: adds ue_attaching / ue_attached state flags to
struct usb_ether; uether_ifattach() sets ue_attaching only after
usb_proc_create() succeeds; ue_attach_post_task() sets ue_attached
after publishing the ifnet; uether_ifdetach() early-returns on
ue_lock == NULL || !ue->ue_attaching and gates the teardown block on
ue->ue_attached (the failed-attach task already returned the unit).
Covers branch A, branch B, and DF-2341's branch C (async PHY-probe attach
failure) in one central place. git apply --check clean on the read-only
tree.
VERDICT β DF-5493 (uether_ifdetach() missing class-wide never-attached guard)
status: reproduced / impact: panic / confidence: certain / fix: fixed
Root cause (line-accurate)
sys/bus/u4b/net/usb_ethernet.c:
- :267-305
uether_ifdetach()β no validation of theusb_etherstate: - :273
usb_proc_drain(&ue->ue_tq)no-ops on an all-zeroue_tq(sys/bus/u4b/usb_process.c:406-410returns whenup->up_lock == NULL). - :276
ifp = uether_getifp(ue)returns&ue->ue_arpcom.ac_if(:133-137) β an embedded member, never NULL. - :278
if (ifp != NULL)β dead guard, always true. - :281
UE_LOCK(ue)=lockmgr(ue->ue_lock, LK_EXCLUSIVE)(:61) β branch A:ue->ue_lock == NULLβFatal trap 12, fault VA 0x0. - :294
ether_ifdetach(ifp)β branch B: ifnet neverif_attach()ed βif_down()/if_detach()on a zeroed ifnet βFatal trap 12, VA 0x0 (if_down_dispatch+0x66; deeper in,sys/net/if.c:975ifindex2ifnet[0] = NULLand :982TAILQ_REMOVE(&ifnetlist, ...)through a NULLtqe_prev). -
:297
sysctl_ctx_free()on a never-sysctl_ctx_init()ed ctx, :300devfs_clone_bitmap_put(..., ue_unit=0)of a never-allocated unit (would double-issue "ue0") β latent behind the :294 crash. -
:161-189
uether_ifattach()returns error leavingue_lockvalid butue_tqfully unwound to zero (usb_process.c:208-219error path callsusb_proc_free()which resetsup->up_lock = NULLat :244) andue_attach_post_tasknever queued (queued only at :181-185 after success) β this is branch B's exact state.
Reachability (attacker model)
- Branch A: evil-USB / evil-dock / VM-attach β the 8 sibling findings
(DF-5334 cdce, DF-5384 aue, DF-5395 axge, DF-5397 urndis, DF-5399 mos,
DF-5411 udav, DF-5453 kue, DF-5486 cue) each proved a deterministic
device-controlled attach failure reaching
goto detachβuether_ifdetach()with a zeroed ue. All those driver modules are in the default module set and auto-load on device match. - Branch B: any of the same 8 drivers when
usb_proc_create()fails (kernel-thread/resource exhaustion β ENOMEM) β every driver has theerror = uether_ifattach(ue); if (error) goto detach;branch. - Fixed-address NULL derefs both arms (softc zeroed by newbus,
sys/kern/subr_bus.c:1953M_ZERO); no attacker-controlled data reaches the fault β local DoS from physical/VM USB access, no uid0 path. Same tier as the 8 siblings: Medium.
Evidence
panic_a_serial.txt(baseline, stock uether.ko): 3ue_guard_a:markers,ue_lock=0,Fatal trap 12,fault virtual address = 0x0,Stopped at lockmgr_exclusive+0x5d: orl (%r14),%esi,db>β guest dead.panic_b_serial.txt(baseline, stock uether.ko, fresh reset):ue_guard_b:markers withue_lock=0xffffffff826001e0 ue_tq.up_lock=0,Fatal trap 12, VA 0x0,current process = Idle,Stopped at if_down_dispatch+0x66: movq (%rax),%r13β the crash is insideether_ifdetach()βif_down()on the never-attached ifnet, proving the call sequence survives UE_LOCK (:281) and usb_callout_drain (:286) and dies exactly at :294's teardown.run_patched.log(fix validation): afterfix.diffapplied in-guest,uether.korebuilt-Werrorclean and installed, rigs rebuilt against the patched header: both branches printSURVIVED uether_ifdetach(),KLDLOAD_RC=0,traps: 0,UNLOAD_OK.build.log/build_b.log:-Werrorclean builds of both rigs.fix.diff:git apply --checkclean against the read-onlysys/tree; applied in-guest withpatch -p1(all 4 hunks, Plan A).
Rig honesty
The failing call sites (driver attach() statics; usb_proc_create
ENOMEM) cannot be linked from a KLD β replaying the exact post-failure
state + the single production uether_ifdetach() call (auto-loaded stock
uether.ko) is the strongest runnable evidence, the same evidence class
as the 8 sibling rigs this pass. One rig defect was caught and fixed
during verification: CFLAGS+= -DBRANCH_A in a kmod Makefile is dropped
by bsd.kmod.mk, so the first "branch A" binary actually contained
branch B code (accidentally producing the first branch-B reproduction);
rebuilt as two self-contained sources (ue_guard_a.c / ue_guard_b.c)
with distinct module names and a cmp anti-identity check in build.sh.
Relationship to known findings
- Not DF-1082 (miibus device_t UAF via SIOCGIFMEDIA) β different code.
- Not DF-2341 (its branch C:
ue_attach_post_taskfails atmii_phy_probe/attach_post_subβ laterether_ifdetachcrash) β same symptom sink, different trigger state; the central guard here additionally covers DF-2341's arm, and DF-2341's own per-arm handling is untouched. The 8 per-driver DF-5334-family findings own each driver'sgoto detach; this finding owns the missing central guard that would have neutralized the entire class in one place.
Pass-2 negative results (file checklist, all killed)
- (a)
uether_ifattachpartial-failure unwind: only failure isusb_proc_create(branch B); the taskqueue-drain-then-read-ifp ordering (:273β:276) is correct for the success path (drain waits for the async attach task). - (b)
uether_ifdetachordering under concurrency: flag-clear under ue_lock (:281-283) precedesusb_callout_drain(:286); a watchdog racing in the window queues nothing (usb_proc_msignaldrops onup_gone, usb_process.c:268-269) and its re-arm (:453) is stopped by the drain;ue_tick_taskearly-returns on the cleared IFF_RUNNING (:464). Method-table audit: all 10 drivers set every pointer the core derefs unconditionally (ue_attach_post:202,ue_setpromisc:394,ue_setmulti:404,ue_init:341,ue_stop:361,ue_start:383); mii-less tables (cdce/cue/ipheth/kue/urndis/udav_nophy) make the core skipmii_phy_probe(:230-231) soue_ifmedia_task's unconditionalue_mii_upd(:437) is unreachable today (API hazard only, no finding). - (c)
uether_rxbuf(:589) clamps the destination (len < ETHER_HDR_LEN || len > MCLBYTES - ETHER_ALIGN) into a fresh MCLBYTES cluster β bounded. Source sideusbd_copy_out(:598) is unbounded vs the page cache (usb_busdma.c:108setsres.length = (usb_size_t)-1for single-segment) but every live caller passes a constant offset (0 or 2) withlen β€ actlen β€ bufsize.uether_rxmbuf(:563-578) has no clamp β filed as DF-5495 (Info); live instance is DF-5396 (urndis), other callers (cdce :1001/:1550, ipheth :498) are clamped. - (d) Watchdog/queue machinery:
ue_watchdogre-arm loop is bounded by the drain ordering above;ue_queue_command's post-msignal field writes are safe under the held ue_lock; start/stop tasks are synchronous viausb_proc_mwait(:129-130).uether_ifdetachnever drainsue->ue_rxqβ filed as DF-5494 (Low; cdcetr_stallat if_cdce.c:1014 skips the :1026 flush, mbufs leak with the softc). - (e) Device-supplied sizes reaching the core: only via the rx entry points above β no new live unbounded path beyond DF-5396.
Fix validated
fix.diff (central): ue_attaching set in uether_ifattach() only after
usb_proc_create() succeeds; ue_attached set in ue_attach_post_task()
after the ifnet is published; uether_ifdetach() early-returns on
ue_lock == NULL || !ue_attaching and gates the whole teardown block on
ue_attached (the failed-attach task already returned its unit at :262).
Applied in-guest, uether.ko rebuilt -Werror clean, installed, both
baseline-panicking rigs re-run β SURVIVED, RC=0, 0 traps, clean unloads.
Guest reset with-src after validation.
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 (ue_attaching set in uether_ifattach() after usb_proc_create() success; ue_attached set in ue_attach_post_task() after the ifnet is published; uether_ifdetach() early-returns on ue_lock==NULL || !ue_attaching and gates the teardown block on ue_attached) applied in-guest via patch -p1 (4 hunks Plan A), uether.ko rebuilt -Werror clean, installed to /boot/kernel, both rigs rebuilt against the patched header (struct layout grew) and re-run on the IDENTICAL inputs: branch A and branch B both print SURVIVED uether_ifdetach(), KLDLOAD_RC=0, dmesg Fatal-trap count 0, clean kldunload of both modules. Baseline panics for the same inputs are in panic_a_serial.txt / panic_b_serial.txt. The guard also covers DF-2341's branch C and would have neutralized the entire 8-driver DF-5334 class centrally. git apply --check passes on the clean read-only sys/ tree.
run_patched.log (both SURVIVED markers + KLDLOAD_RC=0 + traps:0 + UNLOAD_OK x2); fix_uether build inside build.sh logs; fix.diff (git apply --check OK); panic_a_serial.txt + panic_b_serial.txt (baseline traps on identical inputs)
Confirmed kernel references
- sys/bus/u4b/net/usb_ethernet.c:61
- sys/bus/u4b/net/usb_ethernet.c:133-137
- sys/bus/u4b/net/usb_ethernet.c:161-189
- sys/bus/u4b/net/usb_ethernet.c:173-178
- sys/bus/u4b/net/usb_ethernet.c:267-305
- sys/bus/u4b/net/usb_ethernet.c:278
- sys/bus/u4b/net/usb_ethernet.c:281
- sys/bus/u4b/net/usb_ethernet.c:294
- sys/bus/u4b/usb_process.c:206-219
- sys/bus/u4b/usb_process.c:406-410
- sys/net/if.c:975
- sys/net/if.c:982
Detail
Evidence (decisive lines)
panic_a_serial.txt (baseline branch A: 3 ue_guard_a markers, ue_lock=0, Fatal trap 12 fault VA 0x0, Stopped at lockmgr_exclusive+0x5d, db> prompt); panic_b_serial.txt (baseline branch B on fresh with-src reset: ue_lock=0xffffffff826001e0 ue_tq.up_lock=0 markers, Fatal trap 12 VA 0x0, current process=Idle, Stopped at if_down_dispatch+0x66); run_patched.log (fix validation: BOTH branches SURVIVED + KLDLOAD_RC=0 + traps:0 + UNLOAD_OK after patched uether.ko installed); build.log/build_b.log (-Werror clean); fix.diff (git apply --check OK on clean read-only sys/ tree; patch -p1 all hunks Plan A in guest); VERDICT.md (full narrative incl. rig-honesty note: first rig build lost -DBRANCH_A to bsd.kmod.mk CFLAGS handling -- accidentally produced the first branch-B reproduction; rebuilt as two self-contained sources with cmp anti-identity check)
PoC changes
No seeded PoC existed. Wrote the rig from scratch: ue_guard_a.c/ue_guard_b.c KLD harnesses (MODULE_DEPEND uether) that kmalloc a zeroed struct usb_ether, construct each exact post-failure state (A: ue_lock NULL; B: lockinit'd ue_lock + everything else zero, matching usb_proc_create's fully-unwound error state), print the state, and call the production uether_ifdetach() from the stock uether.ko. Build issues fixed: two missing kernel includes (sys/sysctl.h, sys/bus.h + sys/condvar.h) flagged by -Werror; then the decisive rig defect: CFLAGS+= -DBRANCH_A in a kmod Makefile is silently dropped by bsd.kmod.mk, so the 'branch A' binary contained branch B code (caught via identical .ko sizes; that accidental run was the first branch-B reproduction) -- rebuilt as two self-contained .c files with distinct module names and added a cmp anti-identity check to build.sh.
Verified recommended fix
Central guard in uether_ifdetach(): add ue_attaching/ue_attached state flags to struct usb_ether; early-return when ue_lock==NULL or !ue_attaching; gate the ifnet teardown block on ue_attached (see fix.diff)
Verdict
uether_ifdetach() (sys/bus/u4b/net/usb_ethernet.c:267-305) performs zero state validation on the usb_ether handed to it: usb_proc_drain() (:273) no-ops on an all-zero ue_tq (usb_process.c:409), uether_getifp() (:276/:133-137) returns the EMBEDDED arpcom ifnet so the ifp!=NULL guard (:278) is dead code, and it then lockmgr()s through ue_lock (:281) and runs ether_ifdetach() (:294) unconditionally. This file owns the class-wide defect instantiated 8 times this pass (DF-5334 cdce, DF-5384 aue, DF-5395 axge, DF-5397 urndis, DF-5399 mos, DF-5411 udav, DF-5453 kue, DF-5486 cue): each driver's attach-failure goto-detach reaches uether_ifdetach() on a never-attached ue. Both arms verified on the guest with KLD rigs calling the PRODUCTION uether_ifdetach() from the stock auto-loaded uether.ko: branch A (zeroed ue, ue_lock NULL -- the 8-sibling state) -> Fatal trap 12, fault VA 0x0, Stopped at lockmgr_exclusive+0x5d (panic_a_serial.txt); branch B (ue_lock valid, ue_tq unwound to zero -- uether_ifattach()'s own usb_proc_create ENOMEM state, reached by every driver's if(uether_ifattach(ue)) goto detach branch) -> survives UE_LOCK and usb_callout_drain and dies at ether_ifdetach()/if_down() on the never-if_attach()ed ifnet: Fatal trap 12, VA 0x0, Stopped at if_down_dispatch+0x66 (panic_b_serial.txt); latent behind that crash are sysctl_ctx_free on a never-init'd ctx (:297) and devfs_clone_bitmap_put of unit 0 never allocated (:300). Fixed-address NULL derefs (softc zeroed by newbus M_ZERO), no attacker-controlled data at the fault -> panic/DoS from evil-USB plug-in, same Medium tier as the 8 siblings; no uid0 path. Central fix (fix.diff: ue_attaching/ue_attached state flags; early-return on ue_lock==NULL || !ue_attaching; teardown gated on ue_attached) applied in-guest, uether.ko rebuilt -Werror clean and installed, identical rigs re-run -> both branches SURVIVED, KLDLOAD_RC=0, 0 Fatal traps, clean unloads. The guard also neutralizes DF-2341's branch (async PHY-probe attach failure).
No comments yet.