{
  "finding_id": "DF-2739",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "REPRODUCED end-to-end on the stock INVARIANTS guest with pure syscalls (kldload(2)/kldunload(2)/kldstat(2)) and two purpose-built KLDs. linker_file_unload()'s module-veto path (sys/kern/kern_linker.c:517-518) drops the caller's reference even though the unload failed, leaving a live linker_file with refs==0 (directly observable via kldstat: 'vetoa.ko refs=0' while loaded). A subsequent MODULE_DEPEND load bumps refs 0->1 (kern_linker.c:1636); the original user's kldunload then takes the full-teardown path and frees the file while the dependent's deps[] still references it. Unloading the dependent recurses into linker_file_unload(freed) (kern_linker.c:544-545) -> use-after-free: read of freed->refs, TAILQ_REMOVE on freed memory, indirect call via freed->ops. Guest panicked with 'Bad link elm ... next->prev != elm' and a backtrace showing the nested linker_file_unload frames. Primitive: deterministic UAF read+write+indirect-call on an M_LINKER chunk (~200B, groomable via the linker's own allocations). uid=0 escalation is NOT reachable: both kldload(2) and kldunload(2) are gated by caps_priv_check_self(SYSCAP_NOKLD) (kern_linker.c:794,:841) — the hard blocker is privilege, verified by tracing every linker_file_unload caller. Fix validated on a rebuilt kernel (make nativekernel): with the erroneous refs-- removed, refs never underflows (1->2->1), vetoa survives the dependent, dependent unload is clean, no panic, guest stays up.",
  "exploit_chain": "root: kldload vetoa.ko (refs=1,userrefs=1) -> kern.vetoa_veto=1 -> kldunload (module vetoes; kernel refs-- -> refs=0, file live) -> kldload depb.ko (MODULE_DEPEND vetoa; refs 0->1) -> veto off -> kldunload vetoa (refs==1 -> FULL TEARDOWN under live dependent; depb->deps[0] dangles) -> kldunload depb -> linker_file_unload(freed vetoa) -> TAILQ_REMOVE on freed chunk -> panic (INVARIANTS) / silent heap corruption + controllable ops->unload indirect call on production builds. Privilege gate: SYSCAP_NOKLD (root-only trigger).",
  "evidence": [
    "run.log — baseline: refs=0 after vetoed unload, premature teardown at step 4, hang/panic at step 5 (ssh timed out as guest died)",
    "panic.txt — 'panic: Bad link elm 0xfffff8008bb64280 next->prev != elm' + nested linker_file_unload frames from sys_kldunload",
    "run.fixed.log — patched kernel: refs stays 1 after veto, refs=2 after dep load, vetoa survives step-4 unload (refs=1), clean depb unload, guest up",
    "build.log / build2 kernel install in VERDICT.md",
    "fix.diff — one-line removal of the veto-path refs--"
  ],
  "kernel_refs": [
    "sys/kern/kern_linker.c:514",
    "sys/kern/kern_linker.c:517",
    "sys/kern/kern_linker.c:518",
    "sys/kern/kern_linker.c:494",
    "sys/kern/kern_linker.c:540",
    "sys/kern/kern_linker.c:544",
    "sys/kern/kern_linker.c:556",
    "sys/kern/kern_linker.c:1636",
    "sys/kern/kern_linker.c:841",
    "sys/kern/kern_linker.c:853"
  ],
  "poc_changes": "Seed had no runnable PoC. Authored: vetoa.ko (module whose MOD_UNLOAD returns EBUSY while sysctl kern.vetoa_veto=1), depb.ko (MODULE_DEPEND on vetoa), poc.c (syscall orchestration + kldstat refs observability). Fixed the bug-marker heuristic after fix validation (key on kldfind ENOENT, not on kldunload success).",
  "attempts": 2,
  "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": 130,
  "guest_dirty": 0,
  "build_cmd": "cd /tmp/kldtest && csh build.sh   (vetoa_mod+depb_mod via bsd.kmod.mk, cc -O -o poc poc.c)",
  "run_cmd": "cd /tmp/kldtest && ./poc",
  "code_hash": "sha256(poc.c)=e537385cc78bcf71646172bbf8196f0b2de7af861f90afba04218cfd5450692b vetoa.c=4d0ceecb162513a85c995db160833f733e740dcc6c05678a68df756535905f45 depb.c=d7302dd02d3a362a3e118d009d7032e86180afacec03a8d7d4883a907160d55 fix.diff=9b02abc76b0b7208534072a9a01f0b433a95b04a5047f538b4b813bd59d07054",
  "notes": "Root-only trigger (SYSCAP_NOKLD) — kernel-robustness bug, not an unprivileged escalation. On non-INVARIANTS builds the UAF is silent corruption; the freed chunk is M_LINKER (same zone as link_elf elf_file/symtab allocations) so grooming is trivial for the privileged attacker. runtime_sec=130 is the wall time of the decisive baseline run (ssh killed at 120s while the guest panicked in step 5); the panic itself occurred ~25s into the PoC.",
  "recommended_fix": "Remove the file->refs-- on the module-veto path of linker_file_unload(): the caller's reference must survive a failed unload (fix.diff, validated).",
  "fix_status": "fixed",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Mon Aug 31 12:46:55 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 applied to guest /usr/src, kernel rebuilt (make -j6 nativekernel KERNCONF=X86_64_GENERIC) and installed, guest rebooted into #1. Exact same PoC re-run: step-2 EBUSY leaves refs=1 (no underflow), step-3 refs=2, step-4 kldunload takes the refs>1 fast path and vetoa STAYS loaded (kldstat refs=1), step-5 dependent unload is clean. No panic, no corruption, guest remains up. Bad behavior eliminated.",
  "fix_evidence": [
    "run.fixed.log (vetoa.ko refs sequence 1 -> 1 -> 2 -> 1, 'poc exit: 0', kldstat tail shows both modules loaded and unloadable)",
    "VERDICT.md 'Fix validation' section"
  ]
}
