{
  "finding_id": "DF-2839",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "dos",
  "confidence": "certain",
  "verdict": "Unprivileged /dev/lpmap page fault on a mapping created by a concurrently-exiting thread lands in the wide drain window of lwp_userunmap() (which sleeps in vm_map_remove with LWP_MP_WEXIT not yet set) and installs a fresh lpmap into the exiting LWP via lwp_usermap(); nothing ever frees it (lwp_userunmap is the only free site and already ran) - a permanent 4KB M_UPMAP kernel heap leak per hit. Demonstrated at ~150 pages/s (~28MB per 45s) from 4 unprivileged processes with constant live-process count, monotonic across runs; the 390M M_UPMAP cap is exhaustible in minutes (local kernel-memory-exhaustion DoS). Narrow same-window edges (pmap_enter racing the drain's pmap_remove; pmap_kextract of the page freed at kern_proc.c:1381) could expose a freed kernel heap page RW to userspace - source-proven only, not demonstrated, documented as speculative. The initially-suspected AB-BA deadlock was disproved: LWKT tokens are re-acquired by the scheduler across tsleep (lwkt_getalltokens), so the exiter does not hold lwp_token while sleeping; two 90s probe runs showed zero stalls.",
  "exploit_chain": "unpriv user opens /dev/lpmap (0666); thread V mmaps lpmap regions (+faults one for a real lpmap); V pthread_exit -> lwp_exit -> lwp_userunmap parks in vm_map_remove drain; sibling thread T faults a not-yet-removed V-created page -> vm_fault holds map read-lock -> user_kernel_mapping sees lwp_lpmap==NULL -> lwp_usermap passes the LWP_MP_WEXIT guard (not set yet) and allocates+installs a new lpmap; V finishes teardown having already saved/freed the old one; LWP reaped with the new allocation installed -> 4KB M_UPMAP leaked. Repeat at kHz rates -> kmem exhaustion DoS.",
  "evidence": [
    "run.3.log/leak_sample.txt: upmap 15.4K/61.4M -> 22.4K/89.5M -> 29.2K/117M across two 45s unpriv runs, ps count constant 196",
    "fix.run.log: patched kernel (uname #1 Sep 2 05:47:01) stays at 37/148K across two identical runs (96.6K requests, zero growth)",
    "VERDICT.md: full interleaving trace with path:line, deadlock negative results (run.log, run.2.log)"
  ],
  "kernel_refs": [
    "sys/kern/kern_memio.c:801-824",
    "sys/kern/kern_memio.c:813",
    "sys/kern/kern_proc.c:1318-1345",
    "sys/kern/kern_proc.c:1356-1384",
    "sys/kern/kern_exit.c:688",
    "sys/kern/kern_exit.c:695",
    "sys/vm/vm_fault.c:562-580",
    "sys/vm/vm_map.c:1660-1665"
  ],
  "poc_changes": "v1 (deadlock probe) and v2 (storm) never wedged because LWKT tokens do not persist across tsleep; v3 retargeted the same interleaving at the leak: victims mmap 6 regions each + fault one page (real lpmap), 6 storm threads churn the map to stretch the drain window, 3 touchers sweep victims' regions shuffled with time jitter so faults land inside the drain; children setuid(1001) themselves; detection via vmstat -m upmap live-count deltas instead of unkillable-child detection.",
  "attempts": 3,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64",
  "runtime_sec": 3300,
  "guest_dirty": 0,
  "build_cmd": "cc -O -o /tmp/lpdeadlock3 /tmp/lpdeadlock3.c -lpthread",
  "run_cmd": "/tmp/lpdeadlock3 4 45 (x2, vmstat -m | grep -w upmap between runs)",
  "code_hash": "68d75cf58b353cc507c8ff63517b5e1f636d8d44de08974cc100f69470b16f3f",
  "notes": "Fresh-boot baseline on the patched kernel is 37 upmap allocations (148K) - the 15.4K residual seen mid-session on the stock kernel was 100% leak from earlier probe runs. Full guest build log was in /tmp and lost at reboot (see fix.build.log substitute evidence: uname #1 timestamp + /boot/kernel/kernel mtime + hunk-1-succeeded + behavioral zero-growth). Guest reset with-src performed after validation. Suggested bucket: base:kern (dos); speculative memcorrupt edges documented in VERDICT.md for triage.",
  "recommended_fix": "In lwp_exit() set LWP_MP_WEXIT before lwp_userunmap() so lwp_usermap()'s token-serialized post-allocation guard refuses to install an lpmap into an LWP whose teardown already ran (sys/kern/kern_exit.c, one hunk - see fix.diff).",
  "fix_status": "fixed",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Wed Sep  2 05:47:01 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Applied fix.diff (move LWP_MP_WEXIT set before lwp_userunmap in lwp_exit) to the guest /usr/src, make -j6 nativekernel + installkernel + reboot. Stock kernel: +~7,000 upmap allocations (~28MB) per 45s PoC run. Patched kernel: 37/148K flat across two identical runs (96.6K allocation requests served, wave rates unchanged ~750/child/45s, live-proc count constant). Leak eliminated; guard analysis shows every token interleaving now either frees the late allocation in the drain or refuses it (also closes the stray pmap_enter edge since in-window faults now fail EINVAL).",
  "fix_evidence": [
    "findings/poc/DF-2839/fix.diff",
    "findings/poc/DF-2839/fix.run.log (patched: baseline 37/148K -> 37/148K -> 37/148K)",
    "findings/poc/DF-2839/fix.build.log (uname #1 + /boot/kernel/kernel mtime Sep 2 05:50)",
    "findings/poc/DF-2839/run.3.log (baseline: +7.0K/+6.8K allocations per run)"
  ]
}
