vm_fault_unwire() UKSMAP fictitious asymmetry: munlock/exit-unwire of mlock()ed /dev/upmap or /dev/mem UKSMAP ranges runs vm_page_unwire() on never-wired pages and an unchecked PHYS_TO_VM_PAGE() array index
Summary
vm_fault_wire classifies UKSMAP entries as fictitious (the fault path pmap_enters a fictitious stack page, never vm_page_wires) but vm_fault_unwire computes fictitious ONLY from object type β UKSMAP entries have ba.object==NULL β so unwiring such a range runs pmap_unwire+vm_page_busy_wait+vm_page_unwire on a never-wired page, and pmap_unwire returns PHYS_TO_VM_PAGE(pa) unchecked. Guest-reproduced as root: mmap /dev/mem at pa 0xFF000000 (above Maxmem, ~4000 entries past vm_page_array end), mlock, munlock -> stock panic 'vm_page_unwire: invalid wire count: 0', guest down; /dev/upmap (0666) variant silently decrements the live kernel upmap page's wiring (wire_count 1->0 -> vm_page_activate of a kernel heap page) β delayed corruption. mlock is SYSCAP_RESTRICTEDROOT-gated on x86-64 so no unprivileged route (Low). Maptype-switch mirror fix validated by nativekernel rebuild: both variants survive, rc=0.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5802 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| uksmap_unwire.c | β | 3.6 KB | view raw | |
| build.sh | β | 134 B | view raw | |
| run.sh | β | 169 B | view raw | |
| build.log | β | 28 B | view raw | |
| run.log | β | 567 B | view raw | |
| run.patched.log | β | 732 B | view raw | |
| fix_run.log | β | 732 B | view raw | |
| fix_build.log | β | 1.3 KB | view raw | |
| panic.txt | β | 2.1 KB | view raw | |
| env.txt | β | 376 B | view raw | |
| fix.diff | β | 1.1 KB | view raw | |
| VERDICT.md | β | 4.9 KB | β raw | |
| verdict.json | β | 3.8 KB | view raw |
DF-5802 VERDICT β REPRODUCED (panic, privileged trigger)
What the bug is
vm_fault_wire() and vm_fault_unwire() classify "fictitious" entries with
two DIFFERENT rules:
sys/vm/vm_fault.c:2607-2620(wire): aswitch (entry->maptype)βVM_MAPTYPE_UKSMAPβfictitious = TRUE(case at :2614-2616). Correct: the UKSMAP fault path (vm_fault():562-580)pmap_enter()s a stackfakempage and never callsvm_page_wire()on anything.sys/vm/vm_fault.c:2679-2681(unwire):fictitiouscomputed ONLY fromentry->ba.object->type(OBJT_DEVICE/OBJT_MGTDEVICE). UKSMAP entries haveba.object == NULL(set byvm_map_lookup, sys/vm/vm_map.c:4552-4555) βfictitious = FALSE.
Consequently, after mlock() of a UKSMAP range (which the wire path handles
correctly), munlock() β or process-exit unwiring β executes
vm_fault_unwire() :2691-2695:
m = pmap_unwire(pmap, &va); /* pmap.c:5633: m = PHYS_TO_VM_PAGE(pa); */
vm_page_busy_wait(m, FALSE, "vmwrpg");
vm_page_unwire(m, 1);
on a page the wire path never wired:
-
RAM-backed UKSMAP mappings (
/dev/upmap, minor 5,sys/kern/kern_memio.c:726+βpmap_kextractof the live per-processp_upmapkernel page):PHYS_TO_VM_PAGE()yields the real vm_page of a kernel heap page whosewire_countbelongs to kmem, not to this mapping.vm_page_unwire()either panics (vm_page.c:3302-3303,wire_count == 0) or silently steals one wiring from the kernel page (wire_count1β0 βvm_page_activate()of a live kernel data page β delayed-corruption face). On the stock guest this variant survived visibly β i.e. it took the silent-corruption path. -
/dev/memat pa above Maxmem (minor 0,fake->phys_addr = ptoa(pindex),sys/kern/kern_memio.c:448-452β the mmap offset IS the physical address, user-chosen):PHYS_TO_VM_PAGE(pa)is an uncheckedvm_page_arrayindex (sys/vm/vm_page.h:454-455&vm_page_array[atop(pa) - first_page]). For pa = 0xFF000000 on the reference guest (v_page_count 998273, Maxmem β 0xFDBC0000) the index runs ~4,000 entries past the end of the array;vm_page_busy_wait()/vm_page_unwire()then read/write kernel memory beyondvm_page_array. Observed:panic: vm_page_unwire: invalid wire count: 0from garbagewire_count, depending on adjacent memory the outcome is a panic, a DDB wedge, or an unmonitored atomic write past the array.
Reproduction (stock kernel, guest 6.5-DEVELOPMENT #0 X86_64_GENERIC)
- Built in-guest with base cc 8.3 (
build.log). run.log: variantupmapβ survived munlock (silent wiring-accounting corruption face); variantmemβ output truncated atmunlock(), guest DOWN immediately after.panic.txt(serial console): exact causal chainsys_munlock β vm_map_user_wiring β vm_fault_unwire β vm_page_unwire β panic "vm_page_unwire: invalid wire count: 0".
Fix validation
fix.diffmirrorsvm_fault_wire()'s maptype switch invm_fault_unwire()(VM_MAPTYPE_UKSMAP β fictitious = TRUE).- Applied to the guest's
/usr/src(Hunk #1 succeeded at 2676),make nativekernel KERNCONF=X86_64_GENERIC(buildkernel refused: no buildworld tools),make installkernel, reboot into#1kernel (fix_build.log). - Re-ran the identical
run.sh: both variants printSURVIVED munlock, rc=0, guest stays up (run.patched.log,fix_run.log). The panic is gone; the upmap variant's silent unwire is gone with it. - Guest reset to the clean
with-srcsnapshot afterwards.
Impact ceiling β honest
Privileged trigger: mlock(2) is SYSCAP_RESTRICTEDROOT-gated on x86-64
(pc64 does not define pmap_wired_count, so sys_mlock compiles the
privilege branch, sys/vm/vm_mmap.c:1028-1034), and the decisive mem
variant additionally needs /dev/mem (root:kmem 0640). /dev/upmap is
0666, but the mlock gate still binds for it. So: root user β kernel panic /
kernel-memory write past vm_page_array / corruption of kmem wiring β
robustness defect, not a privilege boundary crossing. No userβroot route
(class doesn't permit one from an unprivileged seat). Severity Low,
confidence certain.
Why this is not DF-0944/2685/2686
Those are: MGTDEVICE pager-FAIL NULL page (readrest path), mlock wiring
zero-filling swapped pages (TRYPAGER wire mask), and the pager-error PIP
wakeup. This finding is the unwire classification asymmetry β different
function (vm_fault_unwire), different mechanism (fictitious
misclassification + unchecked PHYS_TO_VM_PAGE), different fix locus.
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
fixedfix.diff applied to guest /usr/src (hunk at 2676), nativekernel rebuild + installkernel, reboot into 'DragonFly 6.5-DEVELOPMENT #1 Mon Oct 12 23:04:55 UTC 2026'; identical run.sh then printed SURVIVED munlock for both variants with rc=0 and the guest stayed up - the stock panic and the silent upmap unwire are both gone.
fix_build.log (patch + kernel build/install), fix_run.log == run.patched.log (both variants survive), VERDICT.md fix-validation section
Confirmed kernel references
Detail
Evidence (decisive lines)
panic.txt (full serial-console backtrace), run.log (stock decisive run: upmap survived, mem killed guest at munlock), run.patched.log/fix_run.log (both variants SURVIVED on fix kernel), fix_build.log (Hunk #1 succeeded at 2676; kernel #1 Mon Oct 12 23:04:55 UTC 2026)
PoC changes
no seed existed; trigger written fresh for this pass. Two variants: 'upmap' (RAM-backed kernel page, silent wiring corruption) and 'mem' (pa above Maxmem, OOB vm_page_array index -> panic). fix.diff first drafted by hand was malformed; regenerated mechanically with diff -u against the tree.
Verified recommended fix
In vm_fault_unwire(), mirror vm_fault_wire()'s maptype switch: treat VM_MAPTYPE_UKSMAP entries as fictitious so pmap_unwire()/vm_page_unwire() never run on their never-wired device ptes.
Verdict
vm_fault_unwire() (sys/vm/vm_fault.c:2679-2681) derives 'fictitious' only from entry->ba.object->type, while vm_fault_wire() (:2614-2616) additionally treats VM_MAPTYPE_UKSMAP as fictitious; UKSMAP entries have ba.object==NULL, so munlock()/exit-unwire of a mlock()ed /dev/upmap or /dev/mem UKSMAP range runs pmap_unwire()->PHYS_TO_VM_PAGE(pa) (unchecked vm_page_array index, vm_page.h:454) and vm_page_unwire() on a page the wire path never wired. Reproduced on the stock guest: panic 'vm_page_unwire: invalid wire count: 0' via sys_munlock->vm_map_user_wiring->vm_fault_unwire->vm_page_unwire for /dev/mem pa 0xFF000000 (~4000 entries past vm_page_array, v_page_count 998273); the RAM-backed /dev/upmap variant silently steals a wiring from the live kernel upmap page. Single-fix nativekernel rebuild with fix.diff (mirror vm_fault_wire()'s maptype switch): both variants survive, rc=0, guest healthy. Trigger is privileged (mlock is SYSCAP_RESTRICTEDROOT-gated on pc64, sys/vm/vm_mmap.c:1030; /dev/mem is 0640) -> Low severity, no unprivileged user->root route.
No comments yet.