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)
PoC verification
Evidence pack
findings/poc/DF-2340 Β· 15 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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:
- zeroed softc (= newbus M_ZERO softc),
lockinitper if_kue.c:478,ue_sc = scper :495,ue_tqin theusb_proc_drain()state (up_gone=1, usb_process.c:422) so the trailing control requests inkue_setmultireturnUSB_ERR_IOERRORharmlessly β exactly their production behavior while a device is detaching (nothing else in the rig dereferencesue_udev); - replays if_kue.c:488-489 at its true attach-order position β with
sc_descstill all-zero β printssc_mcfilters=0xfffffffffffffff8(ZERO_LENGTH_PTR) andNULL-guard at :490 DEAD; - replays the device's 16-byte descriptor reply (
kue_mcastfilt=0x0002βKUE_MCFILTCNT=2), the effect of if_kue.c:439-440; - builds the embedded arpcom ifnet (
uether_getifp, usb_ethernet.c:134-137) with ONE AF_LINK membership, byte-identical to whatif_addmulti_serialized()(sys/net/if.c:2667-2720) leaves after SIOCADDMULTI of 01:00:5e:00:00:01; - 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.korebuilt from the patched tree,-Werrorclean, 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, thenkmalloc(2*6)); thekue_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 fixed0xfffffffffffffff8β 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.
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_tqplaced in theusb_proc_drain()state (up_gone=1, the same flag usb_process.c:422 sets when a device detaches) sokue_setmulti()'s trailingkue_ctl/kue_setwordcontrol requests returnUSB_ERR_IOERRORimmediately viausbd_do_request_proc()'susb_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, andkue_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) withkue_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 asif_addmulti_serialized()(sys/net/if.c:2688-2720) leaves it; KUE_LOCK+ verbatimkue_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
fixedfix.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)
Confirmed kernel references
- 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
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
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.
No comments yet.