{
  "finding_id": "DF-5176",
  "status": "reproduced",
  "reproduced": true,
  "impact": "kernel panic (local DoS) on INVARIANTS kernels via KKASSERT in taskqueue_enqueue_locked; unlocked cross-CPU task-state race on non-INVARIANTS kernels; triggered from drm hrtimer usage (i915_pmu/intel_uncore/i915_perf/amdgpu cross-CPU restart of a timer whose previous expiry task is still pending)",
  "confidence": "certain",
  "verdict": "The LinuxKPI hrtimer shim enqueues the single shared timer->task on the FIRING cpus taskqueue_thread[mycpuid] (linux_hrtimer.c:37) while (re-)arming the underlying systimer on the CALLING cpu (linux_hrtimer.c:115 / kern_systimer.c:368), so restarting one hrtimer from two different cpus migrates the same struct task between per-cpu taskqueues \u2014 explicitly forbidden by subr_taskqueue.c:185-188 because tasks are not locked. Deterministically reproduced on the guest with a KLD driving the exact sequence (block q0, start from cpu0, fire, start from cpu1, fire): panic assertion \"queue == task->ta_queue\" failed in taskqueue_enqueue_locked at /usr/src/sys/kern/subr_taskqueue.c:206 with __hrtimer_function in the backtrace, on the first attempt. Distinct from known DF-2174 (epilogue active-flag clobber) and DF-2175 (re-init of an ARMED systimer): here the previous systimer already fired, the bzero+add is queue-safe, and the defect is the cross-queue task migration. fix.diff (pin timer->tq at hrtimer_init; enqueue on the pinned queue) rebuilt as drm.ko inside the guest eliminates the panic (5 attempts, FAILED to trigger - no panic, guest stays up).",
  "exploit_chain": "unpriv user on a GPU machine (drm.ko + i915/amdgpu loaded): cause the same hrtimer to be restarted from two different cpus while the first expirys task is still pending on the first cpus taskqueue_thread (e.g. i915 pmu sampling timer started from gt-unpark/engine-enable paths on cpu A and again on cpu B under load, intel_uncore.c:70-78 forcewake re-arms, or i915_perf poll timer) -> fire on cpu B -> taskqueue_enqueue_locked KKASSERT(queue == task->ta_queue) -> kernel panic (DoS). On non-INVARIANTS kernels the same path does an unlocked ta_pending++ against a task linked into another cpus queue, a state-corruption window rather than a clean panic. Root escalation not developed (panic-class primitive).",
  "evidence": "panic.txt + run.log.full: [df5176] task ta_pending=1 ta_queue == q0 then after re-arm from cpu1 panic: assertion \"queue == task->ta_queue\" failed ... taskqueue_enqueue_locked+0x159 <- taskqueue_enqueue+0x45 <- __hrtimer_function+0x21 <- systimer_intr+0xd6 <- pcpu_timer_process_oncpu+0x82; run.fix.log: post-fix task ta_pending=0 ta_queue != q0 ... FAILED to trigger - no panic ... GUEST-STILL-UP; env.txt: INVARIANTS=1, X86_64_GENERIC, 6 cpus, drm.ko loaded",
  "kernel_refs": [
    "sys/dev/drm/linux_hrtimer.c:33-40",
    "sys/dev/drm/linux_hrtimer.c:87-119",
    "sys/kern/subr_taskqueue.c:185-188",
    "sys/kern/subr_taskqueue.c:205-209",
    "sys/kern/kern_systimer.c:359-371",
    "sys/dev/drm/i915/i915_pmu.c:130-135",
    "sys/dev/drm/i915/intel_uncore.c:70-78",
    "sys/dev/drm/i915/intel_uncore.c:393",
    "sys/dev/drm/i915/i915_perf.c:1914",
    "sys/dev/drm/amd/amdgpu/dce_virtual.c:709"
  ],
  "poc_changes": "PoC written from scratch (no seed): KLD with MODULE_DEPEND(drm); v1 migrated the kldload user thread via lwkt_setcpu_self which wedged the guest scheduler, v2 uses dedicated per-cpu kernel threads created with lwkt_create(..., TDF_NOSTART, cpuid) + lwkt_schedule; DFs DECLARE_MODULE requires a moduledata_t VARIABLE (passing the handler directly makes the kernel strcmp() machine-code bytes as the module name \u2014 fixed by following the selffree.c pattern); taskqueue_block(taskqueue_thread[0]) keeps the fired task deterministically pending so no race is needed",
  "attempts": 3,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul  2 06:02:54 UTC 2026     root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC  x86_64",
  "runtime_sec": 1500,
  "guest_dirty": 1,
  "build_cmd": "scp df5176.c Makefile dfbsd:/root/df5176/ ; ssh dfbsd cd /root/df5176 && make SYSDIR=/usr/src/sys   ; fix phase: cd /usr/src && patch -p1 < /root/fix.diff && cd sys/dev/drm/drm && make SYSDIR=/usr/src/sys && cp drm.ko /boot/kernel && sync",
  "run_cmd": "ssh dfbsd kldload drm; kldload /root/df5176/df5176.ko   (expect immediate panic; see panic.txt)",
  "code_hash": "d5daa5d749cf6b23449902a719698f4cc1d157b2c2fcdf4f9cbf74f995532c21",
  "notes": "Known pair DF-2174/DF-2175 NOT re-reported; this is the taskqueue-side contract violation (task shared across per-cpu queues), which fires even when the systimer-side re-init is queue-safe (previous timer already fired). Two harness-only crashes during development (DECLARE_MODULE metadata misuse -> strcmp GPF in module_lookupbyname; user-thread lwkt_setcpu_self migration -> guest wedge) were diagnosed and eliminated \u2014 they are not kernel findings. Guest reset (with-src) performed after validation.",
  "recommended_fix": "Pin the tasks home queue for the timers lifetime: add struct taskqueue *tq to struct hrtimer, set timer->tq = taskqueue_thread[mycpuid] in hrtimer_init, and make __hrtimer_function enqueue on timer->tq instead of the firing cpus queue (see fix.diff)",
  "fix_status": "fixed",
  "fix_baseline_reproduced": true,
  "fix_patched_reproduced": false,
  "fix_verdict": "fix.diff applied inside guest /usr/src (patch -p1 clean), drm.ko rebuilt from patched source and installed to /boot/kernel, guest rebooted into it, PoC rebuilt against the patched header: task stayed on its pinned home queue (ta_queue != q0, ta_pending=0 coalescing), 5 attempts, NO panic, guest up \u2014 the baseline panic is gone",
  "fix_evidence": "run.fix.log ([df5176] task ta_pending=0 ta_queue != q0 ... FAILED to trigger - no panic ... GUEST-STILL-UP) vs panic.txt/run.log.full (baseline KKASSERT panic through __hrtimer_function)"
}