# DF-4842 — kthread_stop() frees a struct thread it does not own

`sys/dev/drm/linux_kthread.c:89-101` — the DragonFly linux-kthread shim's
`kthread_stop()` sleeps a **fixed 1 second** (`tsleep(kthread_stop, 0, "kstop", hz)`)
— which proves nothing about the target thread's state — and then calls
`lwkt_free_thread(ts->dfly_td)` unconditionally.

On DragonFly a kthread reclaims **itself**: when the body returns, the
`cpu_set_thread_handler()` wrapper runs `kthread_exit()` → `lwkt_exit()`
(sys/kern/lwkt_thread.c:1684-1744), which parks the exiting `struct thread`
in `gd->gd_freetd` for the *next* thread exiting on that CPU to
`objcache_put()` into `thread_cache`.  `kthread_stop()`'s extra
`lwkt_free_thread()` therefore hits one of two cases:

* **Case B — body still running at +1s** (e.g. `drm_sched_main()` busy inside
  `run_job()`, an ithread draining, any slow kthread):
  `lwkt_free_thread()` on a `TDF_RUNNING`/`TDF_TSLEEPQ` thread →
  `KKASSERT` fatal panic (assertions at lwkt_thread.c:512-514) — or, on a
  kernel built without those assertions catching it, a **freed-but-scheduled
  live td** (UAF of the thread control block).
* **Case A — body exited within 1s** (the common case for an idle drm
  scheduler): the td is already parked in `gd_freetd[cpu]` **and**
  `kthread_stop()` `objcache_put()`s the same td into `thread_cache` — the
  same pointer exists in two freelists.  The next thread exit on that CPU
  consumes `gd_freetd` and puts the td into the cache a **second time**;
  two subsequent thread allocations can be handed the **same `struct
  thread`**, or a live thread's td is freed underneath it.

In-tree callers: `drm_sched_fini()` (sys/dev/drm/scheduler/sched_main.c:652-656)
— every amdgpu ring teardown / driver unload — and
`intel_engine_fini_breadcrumbs()` (sys/dev/drm/i915/intel_breadcrumbs.c:891).
Also note `kthread_stop()`'s helper `kthread_unpark()` does a raw
`lwkt_schedule()` on a thread that may be running or tsleeping (runq/sleepq
invariant breakage), and the `task_struct` itself is leaked (never kfree'd).

## Build (guest, root)

```sh
kldload amdgpu            # pulls in drm.ko, provides drm_sched_* symbols
cd /tmp/df4842 && make    # Makefile next to df4842.c
```

## Run

```sh
kldload /tmp/df4842/df4842.ko
sysctl hw.df4842_test=1   # case B: 'panic: assertion ... lwkt_free_thread' ~1s later, db> wedge
# after vm.sh reset with-src + rebuild:
sysctl hw.df4842_test=2   # case A: staged double-put evidence, guest stays up
```

## Expected output

* test 1 (baseline): see `panic.txt` —
  `panic: assertion "(td->td_flags & (TDF_RUNNING | ...)) == 0" failed in lwkt_free_thread`
  with trace `lwkt_free_thread ← kthread_stop ← drm_sched_fini`.
* test 2 (baseline): see `run.log` — probe shows `gd_freetd[cpu] == tdS`
  after `drm_sched_fini()` returned (td still parked while kthread_stop
  already cache-put it), the sacrificial exit drives the second put, and a
  cache scan observes `thread_cache` handing `tdS` to a fresh allocation.
* patched (`fix.diff`, rebuilt drm.ko + amdgpu.ko): see `run_fixed.log` —
  test 1 returns cleanly in ~3 s (kthread_stop waits for the real body
  exit), no panic, guest up; `run_fixed_slowbody.log` shows the wait
  blocking a stuck body for the full 120 s safety timeout instead of
  freeing the live thread.
