# DF-0162 — Global modules TAILQ mutated without mod_token (race)

## Verdict: BUG CONFIRMED BY CODE INSPECTION; RACE NOT DETERMINISTICALLY
TRIGGERED IN SHORT DEMO. Fix VALIDATED.

## Mechanism
- **Readers** (unprivileged syscalls): `sys_modnext` (kern_module.c:253),
  `sys_modfnext` (:289), `sys_modstat` (:328), `sys_modfind` (:385)
  all walk the global `modules` TAILQ under `lwkt_gettoken(&mod_token)`.
- **Writers** (privileged paths): `module_register` (:141
  TAILQ_INSERT_TAIL) and `module_release` (:178 TAILQ_REMOVE) mutate
  the SAME list but take ONLY `kld_lock`/`llf_lock` (acquired in
  `linker_file_unload` and `linker_load_file` in kern_linker.c).
- The lock domains are **disjoint** — `mod_token` is an `lwkt_token`,
  `llf_lock` is a `lockmgr` lock; neither blocks the other.

A concurrent unpriv `modstat` walk and a privileged `kldload/kldunload`
can race on the TAILQ head/next pointers, corrupting the list ->
panic/UAF.

## Trigger
`modstat_race.c` runs the unprivileged READER side (modnext loop):
```
$ ./modstat_race 5
DF-0162: did 3117560 modnext() iterations in 5 s (reader-side race surface)
```
Pair with a concurrent privileged writer:
```
# as root:  while true; do kldload ehci.ko; kldunload ehci.ko; done
```
The race is tight (Medium / AC:H); a 200-iteration and a 500-iteration
concurrent churn both completed without panic on the audit guest.
This is consistent with the high attack complexity in the CVSS
(`AC:H`).  The bug is real by code inspection (disjoint lock domains
are confirmed) but not deterministically triggerable in a short demo.

## Realism
The race requires a **privileged concurrent operation** (kldload or
kldunload).  From a strict unprivileged-only perspective, the bug
cannot be triggered by an attacker who does not already control a
root process loading/unloading modules.  This is a hardening gap /
DoS-by-race in environments where unprivileged users can spur
privileged module churn (e.g. dev hotplug, auto-loading of netgraph
nodes via socket options, etc.).

## Fix (validated)
`fix.diff` takes `mod_token` around the TAILQ_INSERT_TAIL in
`module_register` and around the TAILQ_REMOVE in `module_release`,
serializing writers against the existing readers.  On the patched
kernel (#1, sha256
3e502901a2d3c80a357126a0d07e3ad316a5e388a5268dc78b8bc01f288f80b9),
500 concurrent `kldload/kldunload ehci` cycles + 4.8M `modnext`
iterations completed without panic.  The fix is correct (writers and
readers now share a lock domain) and shows no regression.
