# DF-3198 — use-after-free in DEVFS_RULE_RESET: devfs_gc() frees nodes under the topology walker's feet

## Root cause
`devfs_apply_reset_rules_caller()` (`devfs_core.c:1912`) walks the topology with
`devfs_iterate_topology()`, whose `TAILQ_FOREACH_MUTABLE` saves the next sibling
(`node2`) before descending. The callback `devfs_rule_reset_node()`
(`devfs_rules.c:213-234`) calls `devfs_gc()` on every rule-created link, and
`devfs_gc()` (`devfs_core.c:721`) frees ARBITRARY other nodes:
- `devfs_gc_links_callback` frees every link whose `link_target` is the node,
- `devfs_gc_dirs_callback` frees empty directories.

The walker then advances onto freed memory and runs the callback on it.

## Reproduced (deterministic, INVARIANTS kernel)
Two rules build a link chain `L2 -> L1 -> null` (rule 2 matches the alias
created by rule 1 — `devfs_allocp` applies rules to every new node,
`devfs_core.c:291-292`). `DEVFS_RULE_RESET "*"`:

```
panic: assertion "node->link_target" failed in devfs_rule_reset_node
        at /usr/src/sys/vfs/devfs/devfs_rules.c:223
devfs_rule_reset_node() at devfs_rule_reset_node+0xba
devfs_rule_reset_node() at devfs_rule_reset_node+0xba
devfs_iterate_topology() at devfs_iterate_topology+0x67
devfs_apply_reset_rules_caller() at devfs_apply_reset_rules_caller+0x8e
devfs_msg_exec() at devfs_msg_exec+0x3ac
```

Sequence: reset of `L1` runs `devfs_gc(L1)` → `nlinks>0` (L2 points at L1) →
gc frees `L2`; the outer walk's saved cursor `node2 == L2` (freed) →
`reset_node(L2-freed)` → `KKASSERT(node->link_target)` fails because the first
`devfs_unlinkp` already NULLed `link_target` (`devfs_core.c:613`).

On a production (non-INVARIANTS) kernel the very next statement is
`--node->link_target->nlinks` — a write through the NULL low page — and the
stale flags (`DEVFS_ORPHANED`, list cursors) drive double-unlink/orphan-list
corruption. The corruption primitive (list tail routed through freed memory)
is real, but the trigger is a privileged ioctl (root, or jailed root via
DF-3197), so the practical impact here is a reliable privileged panic;
combined with DF-3197 it is a jailed-root → host-kernel-corruption path.

## Fix
Two-pass reset (in `fix.diff`): the walk only unlinks rule-created links and
collects them; all freeing happens after the walk in `devfs_reset_mount()`,
with a `DEVFS_DESTROYED` guard for entries freed by an earlier links pass.
Validated: fixed kernel resets the same chain without a panic (`run.fixed.log`).

## Files
`uaf_reset.c`, `build.sh`, `run.sh`, `run.log`, `panic.txt`, `fix.diff`,
`run.fixed.log` (post-fix), `verdict.json`, `manifest.json`

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
