β¬’ 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 β€” 2.2 KB ↓ raw
grow_wirefuture.c β€” 1.0 KB view raw
build.sh β€” 103 B view raw
run.sh β€” 158 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 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.

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.