β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-2673

Recursive vm_map_lock in vm_map_growstack() MAP_WIREFUTURE path β€” deterministic kernel panic (mlockall(MCL_FUTURE) + stack growth)

Field Value
ID DF-2673
Status new
Severity Low
CVSS 3.1 CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:H
CWE CWE-667 Improper Locking
File sys/vm/vm_map.c
Lines 4274-4278 (sink :2594)
Area vm
Confidence certain
Discovered 2026-08-29
Pass 2 (GLM 5.3 second pass)
Bucket memcorrupt
Reported pending
Known CVE none
CVE match novel

Summary

vm_map_growstack() holds the exclusive map lock (upgraded at :4228) and then calls vm_map_user_wiring() at :4274 when MAP_WIREFUTURE is set; vm_map_user_wiring() immediately re-acquires the exclusive lock (:2594). The map lock is initialized without LK_CANRECURSE (:647), so lockmgr panics "locking against yourself".

Threat model & preconditions

Privileged local user (mlockall requires SYSCAP_RESTRICTEDROOT); the flag survives privilege drop β€” freeze/crash with a single page fault below the stack. Local DoS/panic.

Proof of concept

VERIFIED (findings/poc/DF-2673/grow_wirefuture.c): as root, mlockall(MCL_FUTURE) then *(volatile char*)(sp-8MB)=1 β†’ ssh dies, guest down in DDB with lockmgr_exclusive <- vm_map_user_wiring+0x3a <- vm_map_grow_stack+0x399 <- vm_fault <- trap_pfault. Deterministic, first attempt. Fix (wire after releasing the map lock, mirroring vm_mmap.c:1510-1511) validated.

Capture the range, release the map lock, then wire (same pattern as vm_mmap.c:1510-1511) β€” see findings/poc/DF-2673/fix.diff.

Timeline

  • 2026-08-29 Discovered during pass-2 audit of vm_map.c (GLM 5.3); reproduced + fix validated same run.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2673 Β· 11 files
FileTypeDescriptionSize
README.md β€” 1.4 KB ↓ raw
VERDICT.md β€” 1.8 KB ↓ raw
grow_wirefuture.c β€” 1.0 KB view raw
build.sh β€” 98 B view raw
run.sh β€” 343 B view raw
build.log β€” 175 B view raw
run.log β€” 493 B view raw
panic.txt β€” 606 B view raw
fix_run.log β€” 130 B view raw
fix.diff β€” 1019 B view raw
env.txt β€” 320 B view raw

DF-2673 β€” recursive vm_map_lock panic in vm_map_growstack() under MAP_WIREFUTURE

What

vm_map_growstack() upgrades to the exclusive map lock (sys/vm/vm_map.c:4228-4233), inserts the stack growth, and then β€” if mlockall(MCL_FUTURE) set MAP_WIREFUTURE β€” calls vm_map_user_wiring() at sys/vm/vm_map.c:4274-4278 while still holding that exclusive lock. vm_map_user_wiring() immediately does vm_map_lock(map) (sys/vm/vm_map.c:2594) β†’ lockmgr(LK_EXCLUSIVE) recursion without LK_CANRECURSE β†’ panic("lockmgr: locking against myself") (sys/kern/kern_lock.c:310).

Impact

Privileged (mlockall requires SYSCAP_RESTRICTEDROOT) local kernel panic / denial of service. Every stack growth in an MCL_FUTURE process panics.

Reproduce

./build.sh            # cc -O2 -o /tmp/grow_wirefuture grow_wirefuture.c
                      # RUN AS ROOT
/tmp/grow_wirefuture  # mlockall(MCL_FUTURE); fault 8MB below the stack

Expected (observed): guest dies in DDB with

panic: lockmgr: locking against myself
lockmgr_exclusive() at lockmgr_exclusive+0x3e0
lockmgr_exclusive() at lockmgr_exclusive+0x3e0
vm_map_user_wiring() at vm_map_user_wiring+0x3a
vm_map_grow_stack() at vm_map_growstack+0x399
vm_fault() at vm_fault+0x11f
trap_pfault() at trap_pfault+0x209

Fix

fix.diff β€” save the grown range and perform the wiring after the map lock is released (same pattern vm_mmap.c:1510-1511 already uses).

VERDICT.md
↓ download raw

DF-2673 VERDICT β€” REPRODUCED (kernel panic), fix validated

Question

Does a fault below the stack in a process with mlockall(MCL_FUTURE) panic the kernel?

Root cause (path:line)

  • vm_map_growstack() upgrades to the exclusive map lock at sys/vm/vm_map.c:4228-4233 (vm_map_lock_upgrade / Retry with use_read_lock = 0).
  • After a successful growth insert it calls, at sys/vm/vm_map.c:4274-4278, vm_map_user_wiring(map, ...) β€” which begins with vm_map_lock(map) (sys/vm/vm_map.c:2594) = lockmgr(LK_EXCLUSIVE).
  • The map lock is initialized without LK_CANRECURSE (sys/vm/vm_map.c:647, lockinit(&map->lock, "vm_maplk", ..., 0)), so the recursive acquisition panics: panic("lockmgr: locking against myself") (sys/kern/kern_lock.c:310).

Reproduction (run as root β€” mlockall needs SYSCAP_RESTRICTEDROOT)

Observed serial-console panic (panic.txt):

panic: lockmgr: locking against myself
cpuid = 1
lockmgr_exclusive() at lockmgr_exclusive+0x3e0
lockmgr_exclusive() at lockmgr_exclusive+0x3e0
vm_map_user_wiring() at vm_map_user_wiring+0x3a
vm_map_grow_stack() at vm_map_growstack+0x399
vm_fault() at vm_fault+0x11f
trap_pfault() at trap_pfault+0x209
Debugger("panic")

The backtrace is exactly the statically-traced path (guest down in DDB; vm.sh status β†’ down). Reproduced on the first attempt β€” deterministic, not a race.

Impact: privileged local DoS (panic). Low severity only because mlockall(MCL_FUTURE) requires root; note a root process that sets MCL_FUTURE then drops privileges keeps the panic armed.

Fix validation

fix.diff records the grown range and calls vm_map_user_wiring() after vm_map_unlock() (same pattern as sys/vm/vm_mmap.c:1510-1511). On the patched kernel the PoC completes without panicking and the grown range is wired (see fix run in run.log).

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

Kernel rebuilt with fix.diff (range saved, wiring moved after vm_map_unlock): the identical PoC that panicked the stock kernel completes normally (0.00s, rc=0) and the guest remains up.

fix_run.log
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #2: Sun Aug 30 11:26:23 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

Confirmed kernel references

Detail

Exploit chain

root: mlockall(MCL_FUTURE); touch an address below the current stack mapping -> vm_fault -> vm_map_growstack -> recursive lockmgr -> panic. Privileged-only (mlockall requires SYSCAP_RESTRICTEDROOT); the flag survives privilege drop.

Evidence (decisive lines)

['panic.txt: panic backtrace lockmgr_exclusive <- vm_map_user_wiring+0x3a <- vm_map_grow_stack+0x399 <- vm_fault+0x11f <- trap_pfault+0x209', 'run.log: ssh died mid-PoC, vm.sh status: down', 'fix_run.log: patched kernel #2 - same PoC completes in 0.00s, rc=0, guest up']

PoC changes

none of substance - seed sketch compiled as-is once PROT/flag constants were matched to DragonFly

Verified recommended fix

defer the vm_map_user_wiring() call until after vm_map_unlock() in vm_map_growstack() (fix.diff, validated)

Verdict

vm_map_growstack() calls vm_map_user_wiring() (which takes the exclusive map lock) at sys/vm/vm_map.c:4274 while already holding the exclusive map lock acquired at :4228 - the map lock is initialized without LK_CANRECURSE (sys/vm/vm_map.c:647), so any stack growth in a process with mlockall(MCL_FUTURE) set deterministically panics the kernel: 'panic: lockmgr: locking against myself'. Verified on the guest as root; ssh session died, guest down in DDB with the exact predicted backtrace vm_map_growstack -> vm_map_user_wiring -> lockmgr_exclusive.