# 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:

```c
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)`:

```c
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 `memcpy`s 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` `#include`s 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 `#include`s 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.
