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.
Recommended fix
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)
PoC verification
Evidence pack
findings/poc/DF-2673 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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).
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/Retrywithuse_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 withvm_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
fixedKernel 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
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.
No comments yet.