# DF-2816 — boot()/shutdown_cleanup_proc() tears down the calling process while its sibling threads still run

## What

`sys_reboot()` → `boot()` (sys/kern/kern_shutdown.c:250) calls
`shutdown_cleanup_proc(curproc)` at kern_shutdown.c:291 **while the calling
process's other threads keep running on the other CPUs** — nothing stops
them (only `initproc` gets a `SIGSTOP` + wait at :293-297; other CPUs are
never stopped in the `sys_reboot` path).

`shutdown_cleanup_proc()` (kern_shutdown.c:566-610) then, on the live
process:

- `kern_closefrom(0)` closes every fd (:576),
- `cache_drop(&fdp->fd_ncdir)` — which **NULLs `fd_ncdir.ncp` and
  `.mount`** (sys/kern/vfs_cache.c:1090-1096) — and likewise fd_nrdir /
  fd_njdir (:577-591),
- `pmap_remove_pages()` + `vm_map_remove(VM_MIN_USER_ADDRESS,
  VM_MAX_USER_ADDRESS)` — destroys the whole user address space (:601-609).

A sibling thread that subsequently enters the kernel on any other CPU:

- doing a **path lookup**: `nlookup_init()` copies `fd_ncdir`/`fd_nrdir`
  into `nd->nl_nch` **with no NULL-ncp guard**
  (sys/kern/vfs_nlookup.c:149-161; cf. the guarded `fd_njdir` at :153/163),
  and `nlookup()`'s permission check `naccess()` dereferences
  `nch->ncp` at +0x58 (vfs_nlookup.c:655-659) → **NULL-deref page fault →
  kernel panic**;
- doing **any syscall with copyin/copyout** of its now-unmapped user
  memory → fatal "user address access from kernel mode" → panic.

## Reproduced

Two independent triggers on DragonFly 6.5-DEVELOPMENT x86_64
(X86_64_GENERIC, INVARIANTS), 2/2 + 1/1 deterministic:

1. `race2` (4 sibling threads spamming `ioctl(DIOCGKERNELDUMP)` + main
   thread calling `reboot(RB_DUMP)`) — panic in `naccess+0x33 ←
   nlookup+0x15e`, `fault virtual address = 0x58`, process `race2`,
   faulting CPU = a churn CPU (serial.naccess-panic.log / panic trace in
   VERDICT).  Reproduced twice.
2. `sibs` (4 sibling threads doing only `open("etc/passwd")` in a loop +
   main thread calling `reboot(RB_DUMP|RB_NOSYNC)`) — identical
   `naccess+0x33` NULL-deref panic, then an IPI-wedged machine
   (`send_ipiq N->M tgt not draining`) — a hard hang, not even a clean
   panic reboot.

Decisive serial output (from race2):

```
Fatal user address access from kernel mode from race2 at ffffffff806ee803
Fatal trap 12: page fault while in kernel mode
fault virtual address    = 0x58
instruction pointer      = 0x8:0xffffffff806ee803
current process          = 879 (race2)
panic: page fault
naccess() at naccess+0x33
nlookup() at nlookup+0x15e
```

with `naccess+0x33` disassembled (stock kernel) as:

```
ffffffff806ee7ee: 4c 8b 3e        mov    (%rsi),%r15      ; r15 = nch->ncp
ffffffff806ee803: 41 0f b7 57 58  movzwl 0x58(%r15),%edx  ; NULL+0x58 -> trap
```

## Impact

Kernel NULL-deref panic / hard wedge during `reboot(2)`. Requires the
reboot privilege (`SYSCAP_NOREBOOT`, kern_shutdown.c:186) in the calling
process — i.e. an already-privileged user converts a clean reboot into a
kernel panic with a wedge (and a corrupted/absent crash dump). Not an
escalation; robustness/reliability defect with a trivially reliable
trigger. Defense-in-depth note: any *future* caller of `boot()` outside
`sys_reboot` inherits the same hazard.

## Fix

fix.diff (verified in-guest): only run `shutdown_cleanup_proc(curproc)`
when the process is single-threaded (`curproc->p_nthreads < 2`); multi-
threaded callers keep their state through the shutdown (harmless — forced
unmount already copes with lingering refs). Recommended hardening on top:
NULL-guard the `cache_copy(&fdp->fd_ncdir/fd_nrdir, ...)` anchors in
`nlookup_init()` (vfs_nlookup.c:149-161), mirroring the existing
`fd_njdir.ncp` checks.
