# DF-2145: tasklet_kill() UAF — premature return leaves dangling tasklet_entry

## Verdict: NOT REPRODUCED (HW-gated) — source-confirmed real bug

## Reachability
**NOT reachable on this QEMU guest.** `tasklet_kill()` is in `sys/dev/drm/linux_tasklet.c`,
part of `drm.k`. Tasklets are used by DRM GPU drivers (radeon, amdgpu, i915) for deferred
interrupt-bottom-half processing. Without GPU hardware, no tasklets are scheduled, and
`tasklet_kill()` is never called from userspace-reachable paths.

`drm.ko` loads but the tasklet runner (`tasklet_runner` kthread) has an empty queue —
no tasklet_entry structs exist to exploit.

## Mechanism (source-confirmed)
`tasklet_kill()` at `linux_tasklet.c:169-175`:
1. `set_bit(TASKLET_IS_DYING, &t->state)` — marks dying
2. `wakeup(&tasklet_runner)` — wakes the runner thread
3. `tasklet_unlock_wait(t)` — spins until `TASKLET_STATE_RUN` clears

The flaw: `tasklet_unlock_wait()` (line 190-196) only waits for `TASKLET_STATE_RUN` to clear.
`RUN` is set by `tasklet_trylock()` (line 88) **only after** the runner has dequeued the
entry from the STAILQ and cleared `TASKLET_STATE_SCHED`. If the tasklet_entry is still on
the queue (not yet dequeued), `RUN` is never set, so `tasklet_unlock_wait()` returns
immediately.

After `tasklet_kill()` returns, the caller frees the `tasklet_struct`. The runner later
dequeues the entry, reads `te->ts` (pointing to freed memory), and calls `t->func(t->data)`
→ **use-after-free**.

The runner's `PROCESS_TASKLET_LIST` macro checks `TASKLET_IS_DYING` (line 75) and removes
+ frees the entry, but this happens asynchronously — the race window between
`tasklet_kill()` returning and the runner processing is the vulnerability.

## Primitive
- Class: UAF (function pointer call from freed memory)
- The runner calls `t->func(t->data)` where `t` is freed → attacker controls `func` if
  the freed slab is reclaimed
- On this guest (no SMEP): redirected function pointer → userspace shellcode → `uid=0`

## Fix
`fix.diff`: After `tasklet_unlock_wait()`, acquire `tasklet_lock` and manually scan both
`tlist` and `tlist_hi` for any entry whose `ts == t`, remove and free it. This guarantees
no dangling entry remains when `tasklet_kill()` returns.
