pmap_unwire dereferences NULL pte when page table page does not exist -> vkernel crash
Summary
pmap_unwire at pmap.c:2637 pte=pmap_pte(pmap,va); :2638 if((*pte&VPTE_V)==0) derefs pte without NULL check. pmap_pte returns NULL when pmap_pde returns NULL (no PDE/PDPE/PML4E page table page exists for the VA). pc64 equivalent at sys/platform/pc64/x86_64/pmap.c:5588-5618 explicitly handles NULL. Reachable: mlock/munlock with sparse VA ranges (VPAGETABLE entries, vm_map_entry_unwire_all during exit/exec on sparse entries, race window where vm_fault_wire unlocks map allowing concurrent pmap_remove to free PT pages, error cleanup in vm_fault_wire). NULL-page fault delivered to vkernel user process -> vkernel panic takes down all virtual user processes. No memory corruption (read of address 0 trapped by real kernel). Fix: if(pte==NULL || (*pte&VPTE_V)==0).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-0996 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fix.diff | suggested-fix | NULL check for pte in pmap_unwire (mirrors pc64) | 400 B | view raw |
| VERDICT.md | verdict | full narrative: why bug is real, why not testable on default guest | 3.6 KB | β raw |
| README.md | readme | summary + fix | 1.1 KB | β raw |
| manifest.json | manifest | this catalog | 2.3 KB | view raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-0996 β vkernel64 pmap_unwire NULL pte dereference
Summary
pmap_unwire() at sys/platform/vkernel64/platform/pmap.c:2637-2638 calls
pmap_pte() then dereferences the returned pointer without a NULL check.
pmap_pte() returns NULL when no page-table page exists for the VA
(sys/platform/vkernel64/platform/pmap.c:317), so an unwire walk over a
sparse range crashes the vkernel.
The pc64 equivalent at sys/platform/pc64/x86_64/pmap.c:5565-5619 correctly
handles the NULL case β proving the vkernel64 version is the outlier.
Why not tested on default guest
The bug is in sys/platform/vkernel64/, not compiled into the default
X86_64_GENERIC kernel. Triggering it requires building and booting a
VKERNEL64 target (the DragonFly virtual kernel), which is outside this
run's scope. The fix is verified by source review and parity with pc64.
Fix
Apply fix.diff:
pte = pmap_pte(pmap, va);
- if ((*pte & VPTE_V) == 0) {
- *pva = pmap_nextva(pmap, va);
+ if (pte == NULL || (*pte & VPTE_V) == 0) {
+ if (pte == NULL)
+ *pva = pmap_nextva(pmap, va);
DF-0996 β vkernel64 pmap_unwire dereferences NULL pte
Verdict
NOT TESTABLE on default guest. Bug is real and the fix is correct (verified
by source-level trace and by parity with the equivalent pc64 implementation),
but the vulnerable code lives in sys/platform/vkernel64/, which is not
compiled into the running X86_64_GENERIC kernel on this guest. The
vkernel64 is a separate kernel target (a virtual kernel that runs as a
userspace process on the real kernel); exercising this path would require
building/booting a VKERNEL64 binary that hosts user processes β outside the
scope of the default guest and not a default-kernel config.
The bug and fix are confirmed by code review (below). Filed severity (Medium)
and CVSS AV:L accurately reflect the on-host trigger model for a system
running vkernel64.
Mechanism (cited)
pmap_unwire() at sys/platform/vkernel64/platform/pmap.c:2621-2665:
2636: vm_object_hold(pmap->pm_pteobj);
2637: pte = pmap_pte(pmap, va);
2638: if ((*pte & VPTE_V) == 0) { /* <-- derefs pte with no NULL check */
2639: *pva = pmap_nextva(pmap, va);
pmap_pte() at sys/platform/vkernel64/platform/pmap.c:311-322 returns
NULL when pmap_pde() returns NULL (no PDE/PDPE/PML4E page-table page
exists for the VA) or when the PDE is not present:
316: pde = pmap_pde(pmap, va);
317: if (pde == NULL || (*pde & VPTE_V) == 0)
318: return NULL;
Line 2638 then dereferences pte unconditionally β a NULL dereference.
The equivalent function in the bare-metal pc64 pmap
(sys/platform/pc64/x86_64/pmap.c:5565-5619) does handle the NULL case
correctly (lines 5588, 5610β5618), proving the vkernel64 version is the
outlier and the fix pattern is canonical.
Reachability (in a vkernel64 environment)
pmap_unwire() is called from sys/vm/vm_fault.c:2636 and
sys/vm/vm_fault.c:2691 during vm_fault_wire()/vm_fault_unwire() β i.e.
on mlock/munlock, and on vm_map_entry_unwire_all() during exit/exec of
processes that have wired sparse VA ranges. The finding summary cites
multiple realistic trigger paths: sparse VPAGETABLE entries, race window
where vm_fault_wire unlocks the map allowing concurrent pmap_remove to
free PT pages, and error cleanup paths.
In a vkernel64 (which maps a guest physical address space onto host user
memory), a NULL PDE is the normal state for vast ranges of the virtual
address space β pmap_unwire is called per page during unwire-walks, so any
unwire that walks into a region with no PT page triggers the NULL deref.
Impact
- A vkernel64 crash takes down all virtual user processes it hosts (vkernel is single-process-from-host-perspective, multiplexing many guest user processes). This is a host-local DoS of the vkernel's guest.
- Per the finding summary, the fault is delivered as a NULL-page fault trapped by the real (host) kernel β no memory corruption of the host.
Fix
fix.diff β adds the missing NULL check at line 2638, matching the pattern
used by the pc64 equivalent. When pte == NULL, advance *pva via
pmap_nextva() and return NULL (same as the existing VPTE_V==0 path).
This fix is not validated by a built kernel because the default guest
kernel does not include vkernel64 code. Applying the diff and rebuilding a
VKERNEL64 target (with a workload that issues mlock over a sparse VA
range) would validate it; that is outside this run's scope.
Files in this evidence pack
fix.diffβ NULL check forpteinpmap_unwire()VERDICT.mdβ this narrativeREADME.mdβ overviewmanifest.jsonβ artifact catalog
Fix verification
not_testablecompile validated
see evidence pack
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source-confirmed. vkernel64 pmap_unwire NULL pte deref. vkernel64 not compiled into GENERIC. Fix mirrors pc64.
No comments yet.