DF-2834 / verdict.json
{ "finding_id": "DF-2834", "status": "reproduced", "reproduced": 1, "impact": "dos", "confidence": "certain", "verdict": "Verified on the stock INVARIANTS kernel: with vm.vkernel_enable=1 an unprivileged process (uid 65534) created 120000 live vmspaces in 6.9 seconds at a steady 17350 creates/s with zero failures and no cap of any kind on the path (no rlimit, no priv_check, no sysctl; only the vkernel_enable gate at sys/vm/vm_vmspace.c:155). Each create linearly pins 2.374-2.376 wired pages (9.7KB unswappable: 1 wired PML4 page via pmap_allocpte/vm_page_wire at pmap.c:2757 plus vmspace slab) and 9752B of kernel KVA (8KB kmem_alloc_pageable at pmap.c:2368) until destroy or exit; pmap_pinit2 is a no-op on x86_64 (pmap.c:2509), refining the finding text. Extrapolated honestly per single-tenant guest policy: 992529 wirable pages / 2.374 per create = about 418000 creates = about 24 seconds for one unprivileged process to wire all 4GB; the exhaustion point is a vm_wait loop on unwireable memory (pmap.c:2751-2756) with no clean error return, i.e. a hard system wedge. NOT privesc (no uid=0 route; pure resource exhaustion) and gated on the root tunable that defaults to 0, so Medium severity stands for vkernel-hosting configurations. Fix validated A/B on a patched kernel (nativekernel build in the guest): default cap 64 stops the unpriv loop at create 65 with clean ETOOMANYREFS and flat wire count, destroy frees slots (counter decrement verified), two concurrent processes are capped independently, and with the operator-raised sysctl (vm.max_vkernel_vmspaces=100000) 5000 unpriv creates still succeed, so legitimate usage is unchanged.", "exploit_chain": "1) operator sets vm.vkernel_enable=1 (vkernel host); 2) unprivileged local user runs vmsp_grow looping syscall 486 with distinct ids; 3) kernel pins 2.37 wired pages + 9.75KB KVA per create with no limit (17.3k creates/s measured); 4) about 418000 creates (about 24s measured rate) wire all 1039519 pages of the 4GB guest; 5) vm_page_alloc in pmap_allocpte fails and vm_wait cannot be satisfied because the exhaustion is in wired memory, stalling every M_WAITOK allocator system-wide - hard wedge of the machine (deliberately not executed to the wedge point; rate and totals measured and extrapolated).", "evidence": "run.log (stock: 20k cold +2.376 pages/create, 20k warm reuse proving retention is cache not leak, 120k capped run +2.374 pages/create linear, exit-reclaim samples), run.fixed.log (cap A/B legs A-D), VERDICT.md section 3-6 for the math and fix validation, build.log warning-free, env.txt for guest state", "kernel_refs": [ "sys/vm/vm_vmspace.c:146-201", "sys/vm/vm_vmspace.c:155", "sys/vm/vm_vmspace.c:182-187", "sys/vm/vm_map.c:233", "sys/vm/vm_map.c:309-340", "sys/platform/pc64/x86_64/pmap.c:2347", "sys/platform/pc64/x86_64/pmap.c:2368-2370", "sys/platform/pc64/x86_64/pmap.c:2376-2380", "sys/platform/pc64/x86_64/pmap.c:2509-2512", "sys/platform/pc64/x86_64/pmap.c:2751-2757" ], "poc_changes": "no seed pack existed (findings/poc/DF-2834 was empty); PoC written fresh in verify phase: syscall 486 via libc syscall(), self-drop to uid/gid 65534, per-sample sysctl accounting of vm.stats.vm.v_wire_count / vm.kvm_free / vm.stats.vm.v_free_count; run deliberately capped at 120k creates to keep the single-tenant guest healthy; vmsp_cap.c added for fix-validation cap arithmetic", "attempts": 7, "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": 1500, "guest_dirty": 0, "build_cmd": "cc -O2 -Wall -o /tmp/vmsp_grow /tmp/vmsp_grow.c && cc -O2 -Wall -o /tmp/vmsp_cap /tmp/vmsp_cap.c", "run_cmd": "dfbsd-qemu/vm.sh run_root sysctl vm.vkernel_enable=1 && /tmp/vmsp_grow 120000 10000 -u; sysctl vm.vkernel_enable=0", "code_hash": "6584d16a0630a725d672cc7950ef7b68f0ebb4749532f0b64d4a3627c0448740", "notes": "Team directive privesc question answered: NOT privesc, impact dos; gated on vm.vkernel_enable=1 (root tunable, default 0) so default-config machines return EOPNOTSUPP; on vkernel hosts any local user can wedge the box in seconds. Post-exit retention (objcache magazines keep vmspace+pmap cached, 390MB M_VKERNEL after 120k run) is reused not leaked (warm-run KVA delta ~0). vm.vkernel_enable restored to 0 and guest reset to with-src snapshot after verification. Fix kernel built with make nativekernel (buildkernel needs buildworld tools not present on the image).", "recommended_fix": "per-vkernel_proc nvmspaces counter capped by vm.max_vkernel_vmspaces (default 64), checked and incremented under vkp->token with the RB insert, decremented in vmspace_entry_delete; excess create returns ETOOMANYREFS" } |