# DF-2338 — NULL-deref in wsp_intr_callback when ntouch==0 (sys/bus/u4b/input/wsp.c)

## Verdict: NOT REPRODUCED (HW-gated); REAL BUG IN SOURCE (defense-in-depth fix warranted)

## Hardware gate (why the PoC cannot run on this guest)

`wsp` is the Apple Wireless Trackpad / Magic Trackpad USB HID driver. It attaches
only to specific Apple USB trackpad products (VID 0x05ac PIDs 0x0221/0x0230/
0x0236/0x0307 etc. — see `wsp_devs[]`). The audit QEMU/KVM guest has **no USB
device of any kind attached**, let alone an Apple trackpad:

```
$ usbconfig list               # No device match or lack of permissions.
$ ls /dev/ugen* /dev/wsp*      # No such file or directory
$ ls /dev/usb*                 # only /dev/usbctl (operator group; maxx not in operator)
$ id maxx                      # uid=1001(maxx) gid=1001(maxx) groups=1001(maxx)
```

With no matching USB device the `wsp` driver never probes/attaches, so its USB
interrupt callback `wsp_intr_callback` never executes. The unprivileged `maxx`
user cannot plug a USB trackpad into the QEMU guest nor inject a HID report.

## Source trace — the bug is REAL (sys/bus/u4b/input/wsp.c)

Softc field (`wsp.c:550`): `struct tp_finger *index[MAX_FINGERS];` — an array of
pointers, zero-initialised (softc allocated with M_ZERO).

The interrupt callback (`wsp.c:882-966`) computes `ntouch`:
```c
h = (struct tp_header *)(sc->tp_data);
if (params->tp_type >= TYPE2) {                  /* wsp.c:884 */
    ibt    = sc->tp_data[params->tp_button];
    ntouch = sc->tp_data[params->tp_button - 1]; /* only read for TYPE2+ */
}
if (ntouch < 0)              ntouch = 0;         /* wsp.c:889 */
else if (ntouch > MAX_FINGERS) ntouch = MAX_FINGERS;

for (i = 0; i != ntouch; i++) {                  /* wsp.c:894 */
    f = (struct tp_finger *)(sc->tp_data + params->tp_offset + ...);
    ...
    sc->index[i] = f;                            /* wsp.c:920 — ONLY assignment */
}
```

For TYPE1 devices the `ntouch` byte is **never read** (the read is gated on
`params->tp_type >= TYPE2` at line 884), so `ntouch` stays 0, the loop body never
runs, and `sc->index[0..]` remain NULL. Then at line 966 (and 942/947/952/1027/
1030/1040) the code unconditionally dereferences `sc->index[0]->touch_major`:
```c
if (sc->index[0]->touch_major < tun.pressure_untouch_threshold &&  /* wsp.c:966 */
    sc->sc_status.button == 0) {
```
→ NULL dereference: reads address `offsetof(struct tp_finger, touch_major)` ≈ 16,
an unmapped low address → kernel panic in the USB interrupt thread context
holding `sc->sc_lock`. For TYPE2+ devices a malicious unit sending a
valid-length report with the `ntouch` byte cleared panics on the first report.

Trigger: a USB device enumerating as an Apple trackpad (automatic attach via
`wsp_devs[]` PIDs which are in the GENERIC kernel). No privilege, no user
interaction beyond plug-in. Reliable local kernel panic / DoS.

## Exploit chain status

Not pursuable — primitive (NULL-deref panic) behind absent USB trackpad hardware
(valid Phase-6 hard blocker: dead path at runtime on this guest). The primitive
is a pure DoS panic, not a write primitive; no escalation chain.

## PoC changes

None. No USB trackpad on guest; verified by source trace only.

## Recommended fix

Guard the `sc->index[0]` dereferences against `ntouch == 0` (skip the tap/untouch
logic when no fingers are present). See `fix.diff` (matches finding proposal: add
an `ntouch == 0` early-skip / NULL guard before the index dereferences).
