# DF-2719 — coredump `each_segment` iterates the vm_map RB-tree with no lock while a "stopped" sibling LWP can still be mutating it

- **File**: sys/kern/imgact_elf.c
- **Severity**: Medium (race/UAF; kernel panic at minimum when it manifests)
- **Confidence**: likely — code-certain race; crash not reproduced in ~13,400 attempts
- **Class**: memcorrupt (use-after-free read) / CWE-362 + CWE-416

## Root cause

`generic_elf_coredump` (sys/kern/imgact_elf.c:988) walks the victim's map
four times (count :1004, dry fp-count via `elf_puttextvp` :1656/1660,
`cb_put_phdr` :1393, `cb_put_fp` :1656) through:

- sys/kern/imgact_elf.c:1199 `RB_FOREACH(entry, vm_map_rb_tree, &map->rb_root)` — **no vm_map lock held**.

`sigexit` (sys/kern/kern_sig.c:2394-2398) stops LWPs via `proc_stop(SCORE)` +
`proc_stopwait`, but `proc_stop` **pre-counts a sleeping LWP as stopped**
(kern_sig.c:1598-1612: "We're sleeping, but we will stop before returning to
userspace, so count us as stopped") and only *notifies* running LWPs
(kern_sig.c:1614-1620) — they stop at the *next userret*. Therefore an LWP
that is inside a map-mutating syscall (mmap/munmap/mprotect) when the fatal
signal hits **completes that syscall — mutating the RB tree — while the
coredump's unlocked RB_FOREACH is traversing it**. Entries are unlinked and
freed (`vm_map_entry_unlink`/`vm_map_entry_free`) under the reader.

The p_token held by sigexit does not help: mmap/munmap run under the vm_map
lock, not p_token (which only guards the LWP list).

## Reproduce (stress)

```
cc -O2 -D__BSD_VISIBLE=1 -include sys/resource.h -include sys/time.h \
   -o racedump racedump.c -lpthread
./racedump 2500 40000 500 10000        # parent map of N mappings; child:
                                       #  worker: ONE munmap() of all N entries
                                       #  main: usleep(delay) then *(int*)0=0
```

Calibration: `munmap(40000 entries)` ≈ 11.1 ms in-guest — the delay sweep
aims the fatal signal into the middle of that syscall so the dump's
traversals run concurrently with the unlinks.

Observed (evidence the race window is entered): core files written with
mid-teardown maps — e.g. racedump.core of 1019904 bytes ≈ 8.5k vn_hdrs when
the process had 20,000 file mappings: ~11.5k entries were unlinked *while
the dump was running* (pass-2/pass-4 traverse MAP_NOCORE entries since
`elf_puttextvp` uses writable=0).

Result: no panic in ~13,400 attempts (3 configs: 20k maps serialized, 40k
maps ×3 parallel racers with per-pid core files, 100k maps ×2 racers) on the
INVARIANTS guest — freed-but-not-yet-reused vm_map_entry contents remain
readable and RB traversal over stale fragments stays self-consistent.

## Impact if it manifests

Reader dereferences freed/reused `vm_map_entry` (entry->ba.object,
rb-node links mid-rotation) → wild pointer or torn object pointer → kernel
panic; theoretically foreign-map entries reused into this traversal can
write *other processes'* file handles into the core file (fhandle leak).

## Fix

Take the vm_map lock around the whole each_segment pass set (see fix.diff —
vm_map_lock/unlock around the RB_FOREACH; callbacks do no sleeping VFS ops
beyond VFS_VPTOFH, which is safe under vm_map_lock's lockmgr semantics) —
or re-verify the hardening: snapshot entry list under lock first.
