# DF-2739 — linker_file_unload() veto-path refcount underflow → premature teardown → UAF

## What

`sys/kern/kern_linker.c:linker_file_unload()`. When a module vetoes the
unload (`module_unload(mod) != 0`), the function releases `llf_lock` and then
unconditionally executes `file->refs--` (kern_linker.c:517-518) — dropping the
caller's reference **even though the unload failed and the file stays live on
`linker_files`**. The reference count is driven to 0 on a live object.

Consequences (all deterministic, syscall-only):

1. `kldstat` shows `refs=0` for a loaded file (observable in the run log).
2. A later `kldload` of a module that `MODULE_DEPEND`s on it bumps `refs`
   0→1 (kern_linker.c:1636) — the dependent becomes the sole holder of a
   reference that was already consumed once.
3. The original user's `kldunload` then takes the full-teardown path
   (`refs == 1`, not the `refs > 1` fast path) and **frees the linker_file
   while the dependent's `deps[]` still points at it**.
4. Unloading the dependent recurses into `linker_file_unload(freed_file)`
   (kern_linker.c:544-545): read of `freed->refs`, `TAILQ_REMOVE` on freed
   memory, indirect call through `freed->ops` — use-after-free
   read/write/indirect-call.

## Reproduce (on the DragonFly guest, as root)

```
cd /tmp/kldtest
csh build.sh      # builds vetoa.ko, depb.ko, ./poc
./poc             # run.sh
```

Expected on a vulnerable kernel (decisive lines from run.log):

```
== step 2: ... kldunload(4) = -1 errno=16 (Device busy)
   after vetoed unload        vetoa.ko   : id=4 refs=0     <-- refs==0, file LIVE
== step 3: ... dep loaded; vetoa refs     vetoa.ko   : id=4 refs=1
== step 4: ... kldunload(4) = 0           <-- premature teardown under live dependent
   after second unload        vetoa.ko   : NOT LOADED
*** BUG REPRODUCED ... ***
== step 5: ... -> panic
panic: Bad link elm 0xfffff8008bb64280 next->prev != elm
linker_file_unload() at linker_file_unload+0x478
linker_file_unload() at linker_file_unload+0x478
linker_file_unload() at linker_file_unload+0x14e
sys_kldunload() at sys_kldunload+0x81
```

The two nested `linker_file_unload` frames are the dependency-unload
recursion (kern_linker.c:545) operating on the freed object.

## Fix

`fix.diff` — one-line removal of the erroneous `file->refs--` on the veto
path (the caller keeps its reference when the unload fails). Validated on a
rebuilt kernel: see VERDICT.md.

## Trigger privilege

kldload(2)/kldunload(2) are gated by `caps_priv_check_self(SYSCAP_NOKLD)`
(kern_linker.c:794, :841) — the deterministic trigger requires root. The bug
is a kernel-robustness / memory-corruption defect on the privileged kld
management path, not an unprivileged escalation.
