# DF-3238 VERDICT — REPRODUCED (panic), fix validated

## What was run
- PoC: `df3238_mapwalk_null.c` — unprivileged (uid 1001 maxx), single process,
  two pthreads: reader hammers `read()` on `/proc/self/map`; churner
  mmaps/munmaps a 32-entry file-backed block at 0x100000 (lowest mappings,
  vnode objects -> wide per-entry unlock window via vn_fullpath).
- Guest: DragonFly 6.5-DEVELOPMENT #0 X86_64_GENERIC (stock, Jul 2 2026),
  QEMU/KVM, 6 vCPUs.

## Baseline result (stock kernel #0)
Panic. Serial console (dfbsd-qemu/boot.log, copy in panic.txt):

    Fatal user address access from kernel mode from df3238_mapwalk_n at ffffffff809a2540
    Fatal trap 12: page fault while in kernel mode
    cpuid = 5; lapic id = 5
    fault virtual address  = 0x8
    fault code             = supervisor read data, page not present
    instruction pointer    = 0x8:0xffffffff809a2540
    current process        = 995
    CPU5 stopping CPUs: 0x0000001f
    Stopped at  vm_map_rb_tree_RB_NEXT: movq 0x8(%rdi),%rax
    db>

`0x8` = `offsetof(struct vm_map_entry, rb_entry.rbe_right)`, `%rdi` = NULL:
exactly `RB_NEXT(vm_map_rb_tree, root, NULL)` from the RB_FOREACH increment
after `procfs_domap()` stored a failed `vm_map_lookup_entry()` result
(`*entry = NULL`, sys/vm/vm_map.c:1215-1232) into the loop variable at
sys/vfs/procfs/procfs_map.c:236-240. Reproduced on the first run (< ~4 min).

## Root cause (source, line-accurate)
- procfs_map.c:142-143: per-entry `vm_map_unlock(map)` (vn_fullpath +
  sbuf_printf run unlocked).
- procfs_map.c:236-240: on `map->timestamp` change (any munmap bumps it,
  vm_map.h:440-461), `vm_map_lookup_entry(map, e_start, &reentry)`'s return
  value is ignored; when everything at/below `e_start` is gone it sets
  `reentry = NULL`, which is assigned to `entry`.
- tree.h:1037 RB_FOREACH increments `entry = RB_NEXT(name, root, entry)`
  = `vm_map_rb_tree_RB_NEXT(NULL)`; tree.h:817 `RB_RIGHT(elm, field)`
  dereferences elm -> read at 0x8 -> fatal kernel-mode fault.

## Exploitability
Unprivileged local DoS (hard panic to DDB; all CPUs stopped). Not an
escalation primitive: the dereferenced address is fixed NULL+8; page 0 is
never mapped/predictable. Same crash is reachable against ANY dying process
whose map is being read (exit1's stage-1 vmspace teardown empties the whole
tree under the reader, kern_exit.c:433 -> vm_map.c:494-496), so "read
/proc/pid/map of a process that exits" is a realistic accidental trigger,
not just a synthetic churn race.

## Fix validation
- fix.diff: `if (reentry == NULL) break;` in the revalidation block.
- Applied in guest /usr/src, `make nativekernel KERNCONF=X86_64_GENERIC`
  (BUILD_RC=0) + `make installkernel`, rebooted into
  `#1: Wed Sep  9 04:36:32 UTC 2026`.
- Exact same PoC re-run for 240 s: 596,000+ read iterations, reader+churner
  healthy, POC_EXIT=0 (timeout), guest up, dmesg clean. No panic.
  Baseline panicked on the first run. -> fix_status=fixed.

## Artifacts
- panic.txt   — serial console panic block (kernel #0 baseline)
- run.log     — baseline run narrative (ssh session died at panic)
- run.2.log   — patched-kernel run log (no panic)
- fix_validation.log — build/install/reboot/re-run transcript summary
- fix.diff    — git-apply-able unified diff (validated)
- env.txt     — guest uname

## 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.
