# DF-5176 VERDICT — REPRODUCED (deterministic panic)

## 1. The bug

`sys/dev/drm/linux_hrtimer.c`:

- `__hrtimer_function` (linux_hrtimer.c:33-40) is the DF systimer callback.
  It runs in hard-interrupt context on **the CPU that owns the systimer**
  (`st.gd`) and does:
  ```c
  struct taskqueue *tq = taskqueue_thread[mycpuid];   /* :37 — firing CPU */
  BUG_ON(taskqueue_enqueue(tq, &timer->task) != 0);   /* :39 */
  ```
- `hrtimer_start_range_ns` (linux_hrtimer.c:87-119) arms that systimer on
  **the calling CPU**: `systimer_init_oneshot(&timer->st, ..., timer->gd = mycpu`
  via `systimer_init_oneshot` → `systimer_add`, kern_systimer.c:359-371,368).

So the single shared `timer->task` is enqueued on the per-CPU thread
taskqueue of whichever CPU last started the timer. A timer that is started
from CPU A and then re-started from CPU B (while A's expiry task has not run
yet) migrates the same `struct task` between per-CPU taskqueues — exactly
what `sys/kern/subr_taskqueue.c:185-188` forbids ("TASKS ARE NOT LOCKED ...
use one task per CPU!").

`taskqueue_enqueue_locked` (subr_taskqueue.c:205-209):

```c
if (task->ta_pending) {
        KKASSERT(queue == task->ta_queue);   /* :206 */
        task->ta_pending++;
        return 0;
}
```

With INVARIANTS (stock `X86_64_GENERIC`, sys/config/X86_64_GENERIC:56 —
confirmed `#define INVARIANTS 1` in the guest's
/usr/obj/usr/src/sys/X86_64_GENERIC/opt_global.h) this is a guaranteed
panic; without it, `ta_pending++` executes on B's queue lock while the task
is linked into A's queue — an unlocked cross-queue race on task state.

This is NOT the known DF-2175 (re-init of an *armed* systimer corrupting the
systimer queue): in this scenario the previous systimer has **already fired**
(its task is pending), so `systimer_init_oneshot`'s bzero+add is queue-safe;
the defect is purely on the taskqueue side.

## 2. Reachability (in-tree callers)

Cross-CPU start of the same hrtimer is legal and used by the Linux drivers
the shim serves:

- `i915_pmu`: `__i915_pmu_maybe_start_timer()` (i915_pmu.c:130-135) calls
  `hrtimer_start_range_ns` from `i915_pmu_gt_unparked`/engine-pmu paths
  under `pmu.lock` on **any CPU**, while `i915_pmu_unregister` teardown
  (i915_pmu.c:1089) cancels; PMU events schedule on arbitrary CPUs.
- `intel_uncore`: `fw_domain_arm_timer` (intel_uncore.c:70-78) re-arms from
  forcewake-get paths on any CPU; `intel_uncore_forcewake_reset`
  (intel_uncore.c:393) cancels **outside** `uncore.lock`.
- `i915_perf`: poll timer started at i915_perf.c:1914 / cancelled at
  i915_perf.c:1952.
- `amdgpu dce_virtual`: dce_virtual.c:709/732/736.

Any of these started from two different CPUs in quick succession (task still
pending on the first CPU's queue — easy under load, since the per-CPU
taskqueue thread may be busy) hits the assert.

## 3. PoC and result (deterministic — no race required)

`df5176.c` (KLD, `MODULE_DEPEND(drm)`):

1. `taskqueue_block(taskqueue_thread[0])` — keeps the fired task pending
   (enqueue-while-blocked does not wake the queue thread,
   subr_taskqueue.c:232-237).
2. Kernel thread pinned to cpu0 (`lwkt_create(..., cpu=0)`) calls
   `hrtimer_start(1ms)` → fire on cpu0 → task pending on q0
   (`ta_pending=1`, verified by the PoC printout).
3. Kernel thread pinned to cpu1 calls `hrtimer_start(2ms)` — legal per the
   Linux API, and *not* the DF-2175 shape because the old systimer already
   fired.
4. Fire on cpu1 → `__hrtimer_function` → `taskqueue_enqueue(q1, task)` with
   `ta_queue == q0` → **KKASSERT panic**.

Baseline run (run.log / run.log.full / panic.txt):

```
[df5176] attempt 1: arming on cpu0 (1ms)
[df5176] task ta_pending=1 ta_queue == q0
[df5176] re-arming from cpu1 (2ms)
panic with 1 spinlocks held
panic: assertion "queue == task->ta_queue" failed in taskqueue_enqueue_locked at /usr/src/sys/kern/subr_taskqueue.c:206
cpuid = 1
taskqueue_enqueue_locked() at taskqueue_enqueue_locked+0x159
taskqueue_enqueue() at taskqueue_enqueue+0x45
__hrtimer_function() at __hrtimer_function+0x21      <-- linux_hrtimer.c:39
systimer_intr() at systimer_intr+0xd6
pcpu_timer_process_oncpu() at pcpu_timer_process_oncpu+0x82
```

The `__hrtimer_function` frame directly implicates the shim.

## 4. Fix validation

`fix.diff` (shared with DF-5177): pin the task's home taskqueue at
`hrtimer_init` (`timer->tq = taskqueue_thread[mycpuid]`) and enqueue on
`timer->tq` in `__hrtimer_function`; cancel semantics fixed separately.

Applied inside the guest `/usr/src` (`patch -p1`), `drm.ko` rebuilt and
installed, guest rebooted, PoC rebuilt against the patched header and
re-run (run.fix.log):

```
[df5176] task ta_pending=0 ta_queue != q0     (task stayed on its pinned home queue)
[df5176] FAILED to trigger - no panic         (5 attempts)
GUEST-STILL-UP
```

Cross-queue enqueue can no longer occur (coalescing on the pinned queue
instead); the panic is gone.

## 5. Impact

- INVARIANTS kernels (stock GENERIC): deterministic local DoS (panic) from
  any drm hrtimer restarted cross-CPU while a previous expiry's task is
  pending — unprivileged-reachable on GPU machines via drm ioctls/perf.
- non-INVARIANTS kernels: unlocked `ta_pending`/`ta_queue` mutation racing
  the owning CPU's taskqueue thread (state corruption window rather than a
  clean panic).

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