# DF-2740 — Inconsistent locking of linker shared state (refs / linker_files / found_modules)

## What

`sys/kern/kern_linker.c` guards its shared state with two locks — `kld_lock`
(serializes the syscalls) and `llf_lock` (guards `linker_files`) — but a
large share of reads and writes of `linker_file.refs`, `userrefs`, the
`linker_files` list, and `found_modules` happen **outside** that protocol:

| site | access | protection | problem |
|------|--------|-----------|---------|
| sys/kldload :814-821 | `lf->userrefs++`, read `lf->id` | **after** `LK_RELEASE` of kld_lock | concurrent kldunload can tear down & `kfree(lf)` in the window → UAF write into freed chunk + stale id returned to userspace |
| linker_load_file :320-325 | `lf->refs++` | none (find_file_by_name released llf_lock) | lost update vs. `refs--` under lock → premature teardown |
| linker_file_unload :517-518 | `file->refs--` | outside llf_lock (veto path; aggravates DF-2739) | lost update race |
| linker_load_dependencies :1636 | `lfdep->refs++` | kld_lock only | racy vs. non-syscall unloaders |
| linker_reference_module :1125-1131 | `(*result)->refs++` | llf_lock **SHARED** | non-atomic RMW under a shared lock — data race between CPUs |
| linker_load_module :1529 | `modlist_lookup2()` walk of `found_modules` | none | traversal concurrent with TAILQ removal under llf_lock (unload) |
| sys_kldnext :909-923 | `TAILQ_FIRST`/`TAILQ_NEXT` over `linker_files` | kld_lock only, no llf_lock | **unprivileged** reader racing mutators that don't take kld_lock |
| linker_file_lookup_symbol :644-658 | global `TAILQ_FOREACH(linker_files)` | none | runtime symbol lookup racing list mutation |

The mutators that run **without** `kld_lock` are the exported KPIs
`linker_reference_module()` / `linker_release_module()` — their kld_lock
acquisition is commented out at kern_linker.c:1134 and :1162 — reached from:

- `sys/kern/subr_firmware.c:274` and `:460` (deferred firmware-load thread),
- `sys/dev/disk/dm/dm_target.c:75` / `:84` (device-mapper target autoload),
- `sys/netgraph/netgraph/ng_base.c:342-348`, `sys/netgraph7/socket/ng_socket.c:293`
  (netgraph type autoload — root-gated at ngc_attach, SYSCAP_RESTRICTEDROOT),
- `sys/kern/vfs_syscalls.c:324` (mount fstype autoload — root-gated).

So e.g. an **unprivileged** `kldnext(2)` loop (no priv check — see DF-0025
context) walks `linker_files` under kld_lock only, while root-triggered
firmware/dm/mount autoload mutates the same list under llf_lock only → torn
traversal / use-after-free read → panic-class DoS.

## Verification status (honest)

Static verification complete: every site above was traced to its callers and
lock context (`path:line` in the table). A guest race was NOT attempted as a
reliable win because:

- pure-syscall vs pure-syscall races (kldnext vs kldload/kldunload) are
  fully serialized by kld_lock — not winnable;
- every mutator that skips kld_lock requires a privileged actor on this
  guest (no firmware images, no dm targets configured, ng7 socket not
  loaded), so the race needs privileged setup and a tight window.

Fix validation was therefore not performed; the advisory `fix.diff` below is
code-reviewed only (fix_status: not_testable).

## Advisory fix.diff (subset — the two highest-value hardenings)

```
sys_kldload: keep userrefs++/id read inside the kld_lock critical section;
linker_load_file: take llf_lock around the already-loaded refs++.
```

See fix.diff in this directory. These two changes close the UAF-write window
(sys_kldload) and the lost-update refcount race (linker_load_file) without
introducing new lock orderings (llf_lock remains a leaf lock taken inside
kld_lock, matching linker_file_unload).

## Recommended complete fix

1. All `refs`/`userrefs` mutations under `llf_lock` (EXCLUSIVE for RMW).
2. `sys_kldnext`/`linker_file_lookup_symbol` list walks under `llf_lock`.
3. Either take `kld_lock` in `linker_reference_module`/
   `linker_release_module` (uncomment, mind recursion via
   linker_load_module→linker_load_file which itself doesn't take kld_lock)
   or make all list/refcount operations safe under llf_lock alone so the
   KPI callers don't need kld_lock.
