{
 "finding_id": "DF-3173",
 "status": "reproduced",
 "reproduced": 1,
 "impact": "panic",
 "confidence": "certain",
 "verdict": "REPRODUCED on stock INVARIANTS X86_64_GENERIC (6-cpu guest): deterministic round-1 kernel panic Fatal trap 12, fault virtual address = 0x0, Stopped at taskqueue_cancel_locked+0x45 \u2014 the wrong-per-cpu-taskqueue cancel in autofs_trigger_one() walking a foreign STAILQ to NULL. The adjacent interleaving (task dequeued on home queue) yields a UAF: wrong-queue taskqueue_drain_timeout returns without waiting, objcache_put frees the request while autofs_task still reads (AUTOFS_WARN %s of ar_path from freed memory) and writes (ar_error=ETIMEDOUT/ar_wildcards/ar_done/ar_in_progress) into the freed, reusable autofs_request object \u2014 cross-cpu frees and post-free field changes observed via same-magazine re-get; the write primitive is confined to the dedicated objcache with fixed values/offsets, so the realistic ceiling is local DoS (panic) plus corruption of an unrelated in-flight request, not a general arbitrary-write or unpriv->root chain. Trigger side is the real unprivileged path (stat -> VOP_NRESOLVE -> autofs_trigger with an EINTR early creator exit); the boundary completion is the automountd AUTOFSDONE ioctl racing the timeout frontier. fix.diff (remember arming queue in ar_taskqueue; cancel/drain against it) validated by in-guest kernel rebuild: baseline panics round 1, patched runs 22+ clean rounds with zero traps.",
 "exploit_chain": "",
 "evidence": [
  "run.log \u2014 module narration: R01 ref=2 (dedup by cross-cpu waiter) then ta_pending=1 tq=cpu4 (timeout task queued on HOME queue) immediately before the trap",
  "panic.txt \u2014 Fatal trap 12, fault virtual address 0x0, cpuid=0 (releaser cpu) vs home_tq=cpu4, current process = the releaser stat, Stopped at taskqueue_cancel_locked+0x45 movq (%rcx),%rax",
  "runB_markers.log \u2014 MARKER[n] obj==request SAME-BLOCK (freed request re-gotten from the FREEING cpus magazine) and readback e=60/done=1 write-after-free markers",
  "fix_run_dmesg.txt \u2014 patched kernel: 22+ complete (request freed=yes) rounds, zero Fatal traps, guest up (baseline panics in round 1)",
  "VERDICT.md \u2014 full path:line trace, harness design rationale, fix validation"
 ],
 "kernel_refs": [
  "sys/vfs/autofs/autofs.c:303",
  "sys/vfs/autofs/autofs.c:324",
  "sys/vfs/autofs/autofs.c:338",
  "sys/vfs/autofs/autofs.c:357",
  "sys/vfs/autofs/autofs.c:359",
  "sys/vfs/autofs/autofs.c:366",
  "sys/vfs/autofs/autofs.c:395",
  "sys/vfs/autofs/autofs.c:398",
  "sys/vfs/autofs/autofs.c:399",
  "sys/vfs/autofs/autofs.c:400",
  "sys/vfs/autofs/autofs.c:196",
  "sys/vfs/autofs/autofs.c:202",
  "sys/vfs/autofs/autofs.c:506",
  "sys/kern/subr_taskqueue.c:184",
  "sys/kern/subr_taskqueue.c:424",
  "sys/kern/subr_taskqueue.c:493",
  "sys/kern/subr_taskqueue.c:522",
  "sys/sys/queue.h:319",
  "sys/sys/taskqueue.h:169"
 ],
 "poc_changes": "Complete rewrite vs any seed: kthread-driven waiter abandoned (autofs_set_sigmask derefs td_lwp==NULL for kthreads under vfs.autofs.interruptible=1 \u2014 NULL+offset fault); BOTH waiters must be real user processes. Creator = pending_stat (self-pending blocked SIGTERM via thread+kill(getpid) \u2014 POSIX timers absent from DF libc, ITIMER_REAL delivers SIGALRM which autofs does not unmask) so the EINTR early exit lands at a precise moment. Releaser = long-lived helper processes on other cpus (fork co-location on idle box) + one usermode spinner for scheduler migration pressure. The runner-occupying task must SLEEP not spin: this kernels softclock is a per-cpu thread (kern_timeout.c:423) that a busy taskq daemon starves, preventing the very callout the race needs (empirically: ta_pending never left 0 with a spinner). Module: publishes round/key/stage sysctls, samples request state (ref/done/inprog/ta_pending/ta_queue) under sc_lock, per-cpu getter kthreads identify the freeing cpu via objcache magazine locality.",
 "attempts": 9,
 "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": 35,
 "guest_dirty": 0,
 "build_cmd": "sh /root/poc/df3173/build.sh (make -m /usr/share/mk SYSDIR=/usr/src/sys KMOD=df3173_race; cc -O2 fake_daemon3173.c; cc -O2 -pthread pending_stat.c)",
 "run_cmd": "sh /root/poc/df3173/run.sh",
 "code_hash": "c7f300433606d0fbc8cc7b6d74ac0b8843e7dca1181c77c28c8711efa84f2163",
 "notes": "Panic reproduced 4x (variant-A decisive run + twice spontaneously during variant-B timing + once on the pre-fix-validation misrun). ETIMEDOUT==60 on DragonFly (not 145); the modules (e0 already 145 at get) label is a stale string \u2014 the check is e1==ETIMEDOUT. Mode-B clean marker (same-block e0=0 -> e1=60 plus WARNING printing the planted ZZZZ marker) was out-raced by the panic-prone interleaving in final runs; the drain-side UAF is nevertheless code-certain (subr_taskqueue.c:493) and post-free field changes on freed same-block objects were observed. Guest left reset to the clean with-src snapshot. build.log was produced against the patched /usr/src (module source identical; only the autofs.h field from fix.diff differs).",
 "recommended_fix": "Store the arming per-cpu taskqueue in the request (ar_taskqueue) at creation and use it for taskqueue_cancel_timeout/taskqueue_drain_timeout in the last-releaser free path",
 "fix_status": "fixed",
 "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Mon Sep 7 20:24:03 UTC 2026   root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64",
 "fix_baseline_reproduced": 1,
 "fix_patched_reproduced": 0,
 "fix_verdict": "fix.diff (ar_taskqueue remembered; teardown targets it) applied to in-guest /usr/src, nativekernel+installkernel, reboot into #1: exact baseline PoC ran 22+ rounds with zero traps and normal autofs operation; baseline panicked deterministically in round 1. Cross-cpu frees still occur post-fix (observed via magazine getters) but teardown is now correct and harmless.",
 "fix_evidence": "fix_run_dmesg.txt"
}