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

Multicast filter buffer allocated before device descriptor is fetched -> write through ZERO_LENGTH_PTR in kue_setmulti

Summary

kue_attach() allocates sc->sc_mcfilters using KUE_MCFILTCNT(sc) which reads sc->sc_desc.kue_mcastfilt. But descriptor only fetched later asynchronously in kue_attach_post() (queued by uether_ifattach()). At allocation time newbus-zero-initialized softc yields KUE_MCFILTCNT(sc)==0 so kmalloc(0) returns ZERO_LENGTH_PTR=((void*)-8). Non-NULL check at :490 does NOT catch. Later kue_setmulti() executes memcpy(KUE_MCFILT(sc i) LLADDR ETHER_ADDR_LEN). With sc->sc_mcfilters==(void*)-8 first write targets 0xFFFFFFFFFFFFFFF8 unmapped top-of-space guard word -> faults. Malicious KL5KUSB101B adapter controlling 16-byte GET_ETHER_DESCRIPTOR reply sets kue_mcastfilt!=0. Impact: deterministic kernel page-fault panic local/physical DoS via untrusted USB adapter. kfree(ZERO_LENGTH_PTR) no-op so detach safe.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2340 Β· 15 files
FileTypeDescriptionSize
kue_setmulti_poc.c β€” 11.0 KB view raw
Makefile β€” 289 B ↓ download
build.sh β€” 184 B view raw
run.sh β€” 215 B view raw
build.log β€” 902 B view raw
run.log β€” 2.4 KB view raw
run.2.log β€” 1.8 KB view raw
panic.txt β€” 1.0 KB view raw
fix.diff β€” 1.4 KB view raw
fix_build.log β€” 1.9 KB view raw
fix_run.log β€” 2.1 KB view raw
env.txt β€” 569 B view raw
VERDICT.md β€” 10.2 KB ↓ raw
README.md β€” 7.8 KB ↓ raw
verdict.json β€” 7.2 KB view raw

DF-2340 β€” kue_setmulti() writes through ZERO_LENGTH_PTR (kmalloc(0) sentinel)

sys/bus/u4b/net/if_kue.c β€” Kawasaki KL5KUSB101B USB ethernet driver Verified: reproduced (deterministic kernel page fault in the production kue_setmulti()), fix validated (allocation moved after the descriptor read; identical input survives).

What

kue_attach() sizes the multicast filter buffer from a descriptor that has not been read yet:

488:    sc->sc_mcfilters = kmalloc(KUE_MCFILTCNT(sc) * ETHER_ADDR_LEN,
489:        M_USBDEV, M_WAITOK);
490:    if (sc->sc_mcfilters == NULL) {        /* DEAD: kmalloc(0) != NULL */

KUE_MCFILTCNT(sc) is UGETW(sc->sc_desc.kue_mcastfilt) & 0x7FFF (if_kuereg.h:69). At if_kue.c:488 sc_desc is still the newbus-zeroed blob: the descriptor is fetched only later, asynchronously, in kue_attach_post() (if_kue.c:439-440), which uether_ifattach() merely queues as a post-attach task (usb_ethernet.c:182 β†’ :192-203 β†’ :202 ue_methods->ue_attach_post). So the allocation is kmalloc(0, M_USBDEV, M_WAITOK):

kern_slaballoc.c:193:  #define ZERO_LENGTH_PTR ((void *)-8)
kern_slaballoc.c:885:  /* Handle the degenerate size == 0 case ... */
kern_slaballoc.c:890:  return(ZERO_LENGTH_PTR);

(void *)-8 is not NULL β†’ the :490 guard never fires; kfree() treats the sentinel as a no-op (kern_slaballoc.c:1253) so detach never complains β€” the landmine stays armed in a successfully attached interface.

When the device's (fully device-controlled, 16-byte) GET_ETHER_DESCRIPTOR reply sets kue_mcastfilt != 0 β€” the driver's own comment says "the exact size of the filter can depend on the firmware" (if_kue.c:50-52); every real kue firmware reports a filter β€” the next multicast-list update

SIOCADDMULTI/SIOCDELMULTI (uether_ifioctl, usb_ethernet.c:497-502)
  β†’ ue_setmulti_task under UE_LOCK (usb_ethernet.c:398-405)
  β†’ kue_setmulti():  TAILQ_FOREACH(...) { if (i == KUE_MCFILTCNT(sc)) break;
                       memcpy(KUE_MCFILT(sc, i), LLADDR(...), ETHER_ADDR_LEN); }
                       (if_kue.c:373-387)

re-evaluates the bound from the now-populated descriptor while the buffer was sized for the zero one. The first membership entry memcpys 6 bytes to (char *)-8 + 0 == 0xfffffffffffffff8 β€” top of the kernel VA space, unmapped β†’ deterministic page fault. On real hardware the trigger is simply plugging an evil kue-VID:PID adapter and bringing ue0 up (IPv6 auto-joins multicast groups at interface-up, which suffices; no ioctl privileges needed for the user side).

Reproduction (KLD rig, production code)

kue_setmulti_poc.c #includes the verbatim driver (/usr/src/sys/bus/u4b/net/if_kue.c, unmodified β€” the same static function that panics is the driver's own) and reconstructs the exact production state:

  1. zeroed softc (= newbus M_ZERO softc), lockinit per if_kue.c:478, ue_sc = sc per :495, ue_tq in the usb_proc_drain() state (up_gone=1, usb_process.c:422) so the trailing control requests in kue_setmulti return USB_ERR_IOERROR harmlessly β€” exactly their production behavior while a device is detaching (nothing else in the rig dereferences ue_udev);
  2. replays if_kue.c:488-489 at its true attach-order position β€” with sc_desc still all-zero β†’ prints sc_mcfilters=0xfffffffffffffff8 (ZERO_LENGTH_PTR) and NULL-guard at :490 DEAD;
  3. replays the device's 16-byte descriptor reply (kue_mcastfilt=0x0002 β†’ KUE_MCFILTCNT=2), the effect of if_kue.c:439-440;
  4. builds the embedded arpcom ifnet (uether_getifp, usb_ethernet.c:134-137) with ONE AF_LINK membership, byte-identical to what if_addmulti_serialized() (sys/net/if.c:2667-2720) leaves after SIOCADDMULTI of 01:00:5e:00:00:01;
  5. takes the ue lock (production: ue_setmulti_task holds UE_LOCK) and calls the verbatim static kue_setmulti(&sc->sc_ue).

Baseline (stock /usr/src) β€” run.log / panic.txt:

kue_poc: if_kue.c:488 with sc_desc all-zero: kmalloc(0*6) -> sc_mcfilters=0xfffffffffffffff8 (ZERO_LENGTH_PTR=0xfffffffffffffff8, NULL-guard at :490 DEAD)
kue_poc: descriptor arrived: KUE_MCFILTCNT(sc)=2 (was 0 at allocation time in the stock driver)
kue_poc: entering VERBATIM kue_setmulti() (if_kue.c:355-398): memcpy(KUE_MCFILT(sc,0)=0xfffffffffffffff8, ..., 6)

Fatal trap 12: page fault while in kernel mode
fault virtual address   = 0xfffffffffffffff8
fault code      = supervisor write data, page not present
Stopped at      kue_setmulti+0x89:      movl    %edi,(%rax)

The faulting instruction is the inlined first 4-byte store of the if_kue.c:383 memcpy, inside the driver's own function. Reproduced twice (run.2.log: second guest incarnation, bit-identical fault VA and kue_setmulti+0x89).

Fix validation

fix.diff (git-apply-able, git apply --check -p0 OK against the clean read-only sys/ tree): delete the premature allocation from kue_attach() and allocate in kue_attach_post() after the GET_ETHER_DESCRIPTOR read, with M_ZERO; a zero reported count keeps sc_mcfilters NULL, in which case kue_setmulti() takes the allmulti path without touching the buffer and kfree(NULL) is a no-op.

Applied in-guest to /usr/src (patch(1) Plan A, both hunks), then on the same guest:

  • production if_kue.ko rebuilt from the patched tree, -Werror clean, loads and unloads clean (KUE_DRIVER_LOAD_OK / KUE_DRIVER_UNLOAD_OK);
  • harness rebuilt WITH_DF2340_FIXED=1 β€” it #includes the patched driver and replays the patched allocation position (descriptor reply first, then kmalloc(2*6)); the kue_setmulti() it calls is the verbatim static function from the patched tree;

re-run on identical device data (fix_run.log, two runs):

kue_poc: PATCHED order: descriptor first (KUE_MCFILTCNT=2), then kmalloc(12) -> sc_mcfilters=0xfffff8008d5e16b0
kue_poc: entering VERBATIM kue_setmulti() (if_kue.c:355-398): memcpy(KUE_MCFILT(sc,0)=0xfffff8008d5e16b0, ..., 6)
kue_poc: SURVIVED kue_setmulti() β€” buffer was properly sized (fix in effect?)
KLDLOAD_RC=0 / UNLOAD_OK

Guest stayed up. Baseline panic vs patched survival with the only delta being the fix's allocation ordering. (Build note: sys/conf/kmod.mk:98 rebuilds CFLAGS from COPTS, so the fixed-mode define must ride COPTS+=, not CFLAGS+= β€” first fixed build silently compiled without it and usefully re-panicked as an extra baseline datapoint; see run.2.log.)

Build / run

# in guest (after scp of the pack files to /root/df2340):
sh /root/df2340/build.sh             # baseline (stock /usr/src)
kldload /root/df2340/kue_poc.ko      # => Fatal trap 12 @ 0xfffffffffffffff8
# fix validation:
cd /usr/src && patch -p0 < /root/df2340/fix.diff
sh /root/df2340/build.sh fixed       # WITH_DF2340_FIXED=1 via COPTS
kldload /root/df2340/kue_poc.ko      # => SURVIVED, UNLOAD_OK

Impact ceiling (honest)

  • The write target is ZERO_LENGTH_PTR + i*6, i <= 0x7FFF (device-capped 15-bit count, further bounded by the number of joined memberships). The first store lands at fixed 0xfffffffffffffff8 β€” unmapped guard territory at the very top of kernel VA β€” so the observed primitive is a deterministic fatal page fault, not controllable corruption. No path to uid=0 from this bug alone.
  • Precondition: a USB device presenting one of the 33 kue VID:PID pairs (evil-adapter / usbredir physical-access class, same tier as DF-5396 / DF-5411 / DF-5453), then any multicast-list change (IPv6 auto-join at interface-up is enough).
  • Not reachable from an unprivileged local user without such a device (the multicast ioctl path needs the interface to exist, which needs the adapter).
  • The same allocation ordering exists in the FreeBSD parent driver (malloc(0) there returns a valid minimal allocation instead of a sentinel), where the manifestation would be a heap overflow instead of a fixed-address fault β€” flagged for upstream, not verified here.
VERDICT.md
↓ download raw

DF-2340 VERDICT β€” reproduced (deterministic panic in the production kue_setmulti), fix validated (fixed)

The claim, verified line by line

DF-2340 (filed 2026-07-28, pass 1) claims: "kue_attach() allocates sc->sc_mcfilters using KUE_MCFILTCNT(sc) which reads sc->sc_desc.kue_mcastfilt. But descriptor only fetched later asynchronously in kue_attach_post() (queued by uether_ifattach()). At allocation time newbus-zero-initialized softc yields KUE_MCFILTCNT(sc)==0 so kmalloc(0) returns ZERO_LENGTH_PTR=((void*)-8). Non-NULL check at :490 does NOT catch. Later kue_setmulti() executes memcpy(KUE_MCFILT(sc,i) LLADDR ETHER_ADDR_LEN) ... first write targets 0xFFFFFFFFFFFFFFF8 unmapped top-of-space guard word -> faults. Malicious KL5KUSB101B adapter controlling 16-byte GET_ETHER_DESCRIPTOR reply sets kue_mcastfilt!=0."

Every element checks out against the source:

# Claim Source proof
1 allocation reads the not-yet-fetched descriptor if_kue.c:488-489 uses KUE_MCFILTCNT(sc); sc_desc is filled only at if_kue.c:439-440 inside kue_attach_post(), which uether_ifattach() (if_kue.c:501) queues as an async task (usb_ethernet.c:182, executed at :192-203 via ue_methods->ue_attach_post at :202). The kmalloc at :488 therefore always runs against a zeroed sc_desc.
2 KUE_MCFILTCNT == 0 there if_kuereg.h:69 UGETW(sc_desc.kue_mcastfilt) & 0x7FFF; newbus softcs are zero-allocated.
3 kmalloc(0) β†’ (void*)-8, guard dead kern_slaballoc.c:193 #define ZERO_LENGTH_PTR ((void *)-8); :885-890 size==0 returns it regardless of M_WAITOK; :490's == NULL can never fire. Observed live: harness printed sc_mcfilters=0xfffffffffffffff8 (ZERO_LENGTH_PTR=0xfffffffffffffff8, NULL-guard at :490 DEAD).
4 descriptor is device-controlled if_kue.c:439-440: kue_ctl(KUE_CTL_READ, KUE_CMD_GET_ETHER_DESCRIPTOR, 0, &sc->sc_desc, sizeof(sc->sc_desc)) β€” 16 bytes from the adapter's firmware; if_kue.c:50-52 acknowledges the filter count is firmware-dependent.
5 kue_setmulti re-bounds with the late value and writes through the sentinel if_kue.c:373-387: loop bound i == KUE_MCFILTCNT(sc) (now the device value), memcpy(KUE_MCFILT(sc, i), LLADDR(...), ETHER_ADDR_LEN) with KUE_MCFILT = &sc->sc_mcfilters[i*6] (if_kuereg.h:70-71). Reachable via SIOCADDMULTI/SIOCDELMULTI β†’ ue_setmulti_task (usb_ethernet.c:497-502, :398-405) β€” and IPv6 auto-joins multicast groups at interface-up, so "evil adapter + ifconfig ue0 up" suffices.
6 0xfffffffffffffff8 unmapped β†’ fault Observed: Fatal trap 12 ... fault virtual address = 0xfffffffffffffff8, fault code = supervisor write data, page not present, Stopped at kue_setmulti+0x89: movl %edi,(%rax). The faulting instruction is the driver's own inlined memcpy store.
7 kfree of the sentinel is a no-op (latent, not caught at detach) kern_slaballoc.c:1253 if (ptr == ZERO_LENGTH_PTR) ... handled specially; if_kue.c:522 detach stays quiet.

One refinement to the original text: the finding says "(kmem_alloc(ksize=0) result)" in its title shorthand β€” the actual allocator is kmalloc(0, M_USBDEV, M_WAITOK); the sentinel constant is ZERO_LENGTH_PTR, value (void*)-8, returned by kmalloc()'s size==0 branch (kern_slaballoc.c:885-890). Substance unchanged.

How it was proven (production-code KLD rig)

No KL5KUSB101B hardware exists on the guest; as with DF-5396/DF-5411/DF-5453, a KLD harness compiles the verbatim production driver into the module (#include <bus/u4b/net/if_kue.c> from /usr/src/sys β€” zero modifications; the kue_setmulti() that faults is the driver's own static function) and reconstructs the exact production state around it:

  • zeroed softc (newbus M_ZERO equivalent) + lockinit(&sc->sc_lock, ..., LK_CANRECURSE) (if_kue.c:478) + ue_sc = sc (:495);
  • ue_tq placed in the usb_proc_drain() state (up_gone=1, the same flag usb_process.c:422 sets when a device detaches) so kue_setmulti()'s trailing kue_ctl/kue_setword control requests return USB_ERR_IOERROR immediately via usbd_do_request_proc()'s usb_proc_is_gone() check (usb_request.c:768-771) β€” the harness never fakes a usb_device and never fakes request success; the requests fail exactly as they do on a detaching real device, and kue_setmulti() ignores them in production style;
  • if_kue.c:488-489 replayed at its true position in the attach sequence (sc_desc still zero) β€” this is the actual allocation the driver performs;
  • the 16-byte device descriptor reply lands in sc_desc (the effect of if_kue.c:439-440) with kue_mcastfilt = 0x0002;
  • the ifnet is the embedded ue_arpcom.ac_if (uether_getifp, usb_ethernet.c:134-137) with ONE AF_LINK membership constructed exactly as if_addmulti_serialized() (sys/net/if.c:2688-2720) leaves it;
  • KUE_LOCK + verbatim kue_setmulti(&sc->sc_ue).

The only non-production code is this attach-frame plumbing (kue_attach / kue_setmulti are static and only reachable through newbus with a physical adapter). Every byte allocated, copied and faulted on follows the production call chain.

Results

Baseline (stock /usr/src) β€” reproduced twice (run.log, run.2.log; second guest incarnation, identical fault):

kue_poc: if_kue.c:488 with sc_desc all-zero: kmalloc(0*6) -> sc_mcfilters=0xfffffffffffffff8 (ZERO_LENGTH_PTR=0xfffffffffffffff8, NULL-guard at :490 DEAD)
kue_poc: descriptor arrived: KUE_MCFILTCNT(sc)=2 (was 0 at allocation time in the stock driver)
kue_poc: entering VERBATIM kue_setmulti() (if_kue.c:355-398): memcpy(KUE_MCFILT(sc,0)=0xfffffffffffffff8, ..., 6)

Fatal trap 12: page fault while in kernel mode
fault virtual address   = 0xfffffffffffffff8
fault code      = supervisor write data, page not present
Stopped at      kue_setmulti+0x89:      movl    %edi,(%rax)

Guest dead in DDB (vm.sh status => down). Panic in the driver's own function, fixed fault VA, deterministic across CPUs/boots.

Patched (/usr/src + fix.diff) β€” survived twice (fix_run.log), clean loads/unloads of both the harness and the rebuilt production if_kue.ko (KUE_DRIVER_LOAD_OK), guest up throughout:

kue_poc: PATCHED order: descriptor first (KUE_MCFILTCNT=2), then kmalloc(12) -> sc_mcfilters=0xfffff8008d5e16b0
kue_poc: entering VERBATIM kue_setmulti() (if_kue.c:355-398): memcpy(KUE_MCFILT(sc,0)=0xfffff8008d5e16b0, ..., 6)
kue_poc: SURVIVED kue_setmulti() β€” buffer was properly sized (fix in effect?)
KLDLOAD_RC=0 / UNLOAD_OK / KUE_DRIVER_LOAD_OK / KUE_DRIVER_UNLOAD_OK

Identical device data on both sides; the only delta is the fix's allocation-ordering change (the harness models the attach-order position of the single allocation; kue_setmulti() itself is the verbatim compiled function in both builds). fix.diff is git apply --check -p0-clean against the pristine read-only sys/ tree and patch(1) Plan A in-guest.

Rig-detour note (kept honest in run.2.log): the first "fixed" build silently omitted -DDF2340_FIXED because sys/conf/kmod.mk:98 rebuilds CFLAGS from COPTS; the resulting binary was a baseline replay and panicked on the patched-source guest β€” recorded as baseline datapoint #2 (it proves the panic tracks the replayed ordering, not the tree state). Moving the define to COPTS+= fixed the build; verified -DDF2340_FIXED present in the cc line.

Impact (primary objective: can an unprivileged user get uid=0?)

No. The corrupted "pointer" is a constant sentinel: every write lands at (char *)-8 + i*6, i.e. the top of the kernel VA space (i is capped by the device's 15-bit count AND by the number of joined multicast memberships), a region with no kernel mapping β€” the primitive is a deterministic fatal page fault, observed as such. There is no attacker-chosen address, no attacker-chosen length beyond 6*i, and the copied bytes are multicast MAC addresses. No escalation chain exists from this bug alone; the deliverable here is the DoS proof, consistent with the finding's own AV:P/A:H rating.

Threat model: physical evil-USB (or VM USB-passthrough/usbredir) β€” one of the 33 kue_devs[] VID:PID pairs (if_kue.c:104-140) whose firmware answers GET_ETHER_DESCRIPTOR with a non-zero filter count (all real kue firmware does; the count is firmware-defined per if_kue.c:50-52), followed by any multicast-list change (IPv6 auto-join at interface-up is enough). This is the same evil-USB-net family as DF-1075 (cue), DF-5396 (urndis), DF-5411 (udav), DF-5453 (kue attach).

Non-exploitability notes: * If the descriptor read fails (zeros), KUE_MCFILTCNT == 0: the i == KUE_MCFILTCNT break fires before any memcpy and the driver falls back to ALLMULTI β€” no write. The bug strictly requires the device to report a non-zero count, which real firmware always does β€” i.e. on real kue adapters this path is expected to be live at first multicast join. * The M_WAITOK NULL check at if_kue.c:490-493 is dead code on DragonFly (M_WAITOK cannot return NULL; kmalloc(0) returns the sentinel) β€” removed by the fix along with the moved allocation. * FreeBSD parent-code note: the same pre-descriptor allocation ordering exists upstream, but FreeBSD malloc(0) returns a valid minimal allocation rather than a sentinel β€” the upstream manifestation would be a heap overflow sized by the device-reported count, worth a separate upstream report; not verified in this DragonFly audit.

Fix

fix.diff (authored after verification, never applied to the read-only sys/ tree in the repo): move the allocation from kue_attach() to kue_attach_post() immediately after the GET_ETHER_DESCRIPTOR read, add M_ZERO, and skip allocation for a zero count (kue_setmulti then takes the allmulti path; kfree(NULL) is a no-op). Validated as above: baseline panic β†’ patched survival on identical input; patched driver builds -Werror clean as a standalone module and loads/unloads clean.

Guest hygiene

Guest was left in a good state: final vm.sh reset with-src after all captures (clean-source snapshot, rebooted, ssh-verified), so guest_dirty = 0.

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 (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_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)
↓ fix.diffper-fix-DF-2340

Confirmed kernel references

Detail

Evidence (decisive lines)

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)

PoC changes

No orchestrator seed existed for this pack β€” rig written from scratch on the DF-5453/5411 pattern, upgraded to compile the VERBATIM driver (#include 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 β€” 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 β€” the first fixed build silently compiled without it and panicked (kept as baseline datapoint run.2.log).

Verified 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).

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 β€” 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) β€” 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)' β€” the faulting instruction is the driver's 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) β€” 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.