DF-2340 / verdict.json
{ "finding_id": "DF-2340", "status": "reproduced", "reproduced": true, "impact": "panic", "confidence": "certain", "verdict": "CONFIRMED as claimed. kue_attach() (if_kue.c:488-489) sizes sc->sc_mcfilters with KUE_MCFILTCNT(sc)=UGETW(sc_desc.kue_mcastfilt)&0x7FFF (if_kuereg.h:69) while sc_desc is still the zeroed softc blob \u2014 the descriptor is only fetched asynchronously later in kue_attach_post() (if_kue.c:439-440, queued by uether_ifattach at usb_ethernet.c:182/202) \u2014 so the allocation is kmalloc(0,M_USBDEV,M_WAITOK), which DragonFly returns ZERO_LENGTH_PTR ((void*)-8, kern_slaballoc.c:193/885-890) for; the NULL guard at if_kue.c:490 is dead and kfree() no-ops on the sentinel (kern_slaballoc.c:1253) so the landmine survives detach. Once the device-controlled 16-byte GET_ETHER_DESCRIPTOR reply sets kue_mcastfilt!=0, any multicast-list change (SIOCADDMULTI -> ue_setmulti_task, usb_ethernet.c:497-502/398-405; IPv6 auto-join at interface-up suffices) makes the verbatim kue_setmulti() memcpy 6 bytes through &sc_mcfilters[0]==0xfffffffffffffff8. Proven on the QEMU guest with a production-code KLD that #includes the VERBATIM driver from /usr/src and replays the exact attach-order state: Fatal trap 12, fault VA 0xfffffffffffffff8, supervisor write data, page not present, Stopped at kue_setmulti+0x89: movl %edi,(%rax) \u2014 the faulting instruction is the drivers own inlined memcpy store (if_kue.c:383). Reproduced twice across two guest incarnations (identical fault VA and function+offset); deterministic. Impact ceiling honestly assessed: the write target is a constant sentinel in unmapped top-of-kernel-VA (i advances only to min(device 15-bit count, joined memberships)), so the primitive is a deterministic fatal page fault (evil-USB physical DoS, same tier as DF-5396/5411/5453) \u2014 NO uid=0 chain exists from this bug alone; the copied bytes are multicast MACs, no attacker-chosen address. fix.diff (allocate after the descriptor read in kue_attach_post, M_ZERO, zero count -> keep NULL/allmulti) validated in-guest: patched if_kue.ko builds -Werror clean and loads/unloads; identical device input on the patched tree SURVIVED with clean unload, twice, guest up.", "exploit_chain": "", "evidence": "panic.txt + run.log (baseline: sc_mcfilters=0xfffffffffffffff8 (ZERO_LENGTH_PTR...) NULL-guard at :490 DEAD then Fatal trap 12 ... fault virtual address = 0xfffffffffffffff8 ... Stopped at kue_setmulti+0x89: movl %edi,(%rax)); run.2.log (bit-identical second baseline panic, different boot/CPU); fix_run.log (PATCHED order ... kmalloc(12) -> 0xfffff8008d5e16b0 + SURVIVED kue_setmulti() + KLDLOAD_RC=0/UNLOAD_OK/KUE_DRIVER_LOAD_OK, guest stayed up); fix_build.log (-DDF2340_FIXED build + patched if_kue.ko -Werror clean); build.log (baseline rig build); env.txt (uname/cc/kldstat/src state); kue_setmulti_poc.c (rig: verbatim driver include + state reconstruction, every replayed line mapped to if_kue.c)", "kernel_refs": [ "sys/bus/u4b/net/if_kue.c:488", "sys/bus/u4b/net/if_kue.c:490", "sys/bus/u4b/net/if_kue.c:439", "sys/bus/u4b/net/if_kue.c:383", "sys/bus/u4b/net/if_kue.c:373", "sys/bus/u4b/net/if_kue.c:522", "sys/bus/u4b/net/if_kuereg.h:69", "sys/bus/u4b/net/if_kuereg.h:70", "sys/kern/kern_slaballoc.c:193", "sys/kern/kern_slaballoc.c:885", "sys/kern/kern_slaballoc.c:1253", "sys/bus/u4b/net/usb_ethernet.c:182", "sys/bus/u4b/net/usb_ethernet.c:202", "sys/bus/u4b/net/usb_ethernet.c:398", "sys/bus/u4b/net/usb_ethernet.c:497", "sys/bus/u4b/usb_process.c:422", "sys/bus/u4b/usb_request.c:768", "sys/net/if.c:2667" ], "poc_changes": "No orchestrator seed existed for this pack \u2014 rig written from scratch on the DF-5453/5411 pattern, upgraded to compile the VERBATIM driver (#include <bus/u4b/net/if_kue.c> from /usr/src) so the faulting kue_setmulti() is the production function itself. Fixes during bring-up: (1) if_kuereg.h has no include guards \u2014 dropped duplicate harness-side include; (2) the control-request tail of kue_setmulti is neutralized the PRODUCTION way (ue_tq.up_gone=1 == usb_proc_drain state, usb_process.c:422 -> usbd_do_request_proc returns USB_ERR_IOERROR at usb_request.c:768-771, no fake udev needed); (3) fixed-mode define must ride COPTS+= because sys/conf/kmod.mk:98 rebuilds CFLAGS \u2014 the first fixed build silently compiled without it and panicked (kept as baseline datapoint run.2.log).", "attempts": 4, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 1500, "guest_dirty": 0, "build_cmd": "sh /root/df2340/build.sh # baseline; fixed: sh /root/df2340/build.sh fixed (after: cd /usr/src && patch -p0 < /root/df2340/fix.diff)", "run_cmd": "kldload /root/df2340/kue_poc.ko # baseline: panic; fixed: SURVIVED then kldunload kue_poc", "code_hash": "d2237d28a96c736badec248c04efc1b26d0227b9971a6b8d2da9bccf7509c4fe", "notes": "Physical evil-USB DoS (33 kue_devs[] VID:PID pairs, if_kue.c:104-140); trigger on real hardware = plug adapter + bring ue0 up (IPv6 auto-join). Zero-count descriptor is safe (loop breaks before memcpy -> allmulti). Dead code removed by fix: if_kue.c:490-493 M_WAITOK NULL check. Pass-2 audit of if_kue.c (2026-10-05) explicitly knew DF-2340 and did not re-file it. Upstream note: FreeBSD parent driver has the same ordering, but FreeBSD malloc(0) yields a valid minimal allocation \u2014 different (heap-overflow) manifestation there, flagged for upstream, not verified here.", "recommended_fix": "Allocate sc->sc_mcfilters in kue_attach_post() AFTER the GET_ETHER_DESCRIPTOR read (M_ZERO; skip allocation when the count is 0 so kue_setmulti takes the allmulti path), instead of pre-descriptor in kue_attach() where it degenerates to kmalloc(0) -> ZERO_LENGTH_PTR; validated in-guest (panic -> survive on identical input).", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff (allocation moved from kue_attach to post-descriptor-read in kue_attach_post, M_ZERO, zero-count keeps NULL -> allmulti path, dead NULL-check removed) applied in-guest to /usr/src via patch(1) Plan A (both hunks); patched production if_kue.ko rebuilt -Werror clean from /usr/src/sys/bus/u4b/net/kue and loads/unloads clean (KUE_DRIVER_LOAD_OK/KUE_DRIVER_UNLOAD_OK); harness recompiled WITH_DF2340_FIXED=1 (#includes the PATCHED driver, replays the patched alloc position) and re-run twice on IDENTICAL device data (same 16-byte descriptor reply kue_mcastfilt=0x0002, same 01:00:5e:00:00:01 membership): PATCHED order ... kmalloc(12) -> real heap address, SURVIVED kue_setmulti(), KLDLOAD_RC=0, clean kldunload, guest stayed up. Baseline (stock tree, identical input): Fatal trap 12 @ 0xfffffffffffffff8 in kue_setmulti+0x89. fix.diff is git apply --check -p0 clean against the pristine read-only sys/ tree.", "fix_evidence": "fix_run.log (SURVIVED + KLDLOAD_RC=0 + UNLOAD_OK + KUE_DRIVER_LOAD_OK, two runs); fix_build.log (-DDF2340_FIXED harness build + patched if_kue.ko -Werror clean); panic.txt + run.log/run.2.log (baseline trap on identical input); fix.diff (git apply --check -p0 OK); env.txt (patched-src state md5 e43c81cb5c0ca04a441be5b63e4515ba)" } |