// Kernel virtual-memory manager. This file owns VAD bookkeeping, faults, and user-copy validation. // Pool allocation lives in mm_pool.c and page-table construction lives in mm_pte.c. // Virtual address layout: // 0000000000001000-00007FFFFFFFF000 is the lower-half user range. // 0000100000000000-00007FFFFFFFF000 is the current VAD allocation arena. // FFFF800000000000-FFFF87FFFFFFFFFF is reserved for kernel images and fixed kernel mappings. // FFFF880000000000-FFFF8FFFFFFFFFFF is the physical direct map. // FFFF900000000000-FFFF97FFFFFFFFFF is the PCI MMIO aperture owned by pci.sys. // FFFF980000000000-FFFF9FFFFFFFFFFF is the generic device-MMIO aperture. // FFFFA00000000000-FFFFAFFFFFFFFFFF is the kernel shared-view arena. // FFFFC00000000000-FFFFDFFFFFFFFFFF is the kernel pool. // // The current VM model uses one shared PML4, so user pointers are globally // meaningful while protection and lifetime are still tracked per process VAD. // User mappings: // VADs separate reservation from commitment. Committed pages become resident // when a user fault or kernel prefault path materializes them. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static KSTATUS KiVadEnsureResidentRange(PROCESS* process, uintptr_t start, size_t size, uint64_t accessFlags); static KSTATUS KiCopyFromUserPaged(void* kernelDst, const void* userSrc, size_t size); static KSTATUS KiCopyToUserPaged(void* userDst, const void* kernelSrc, size_t size); static PHYSICAL_ADDRESS gVadZeroPageFrame; // User/kernel copy boundary helpers. // Implements the user Range Valid operation. static bool KiUserRangeValid(uintptr_t addr, size_t size) { if (size == 0) return true; if (addr < USER_BASE) return false; if (addr >= USER_LIMIT) return false; if (size > (size_t)(USER_LIMIT - addr)) return false; return true; } // Probes user Range and materializes demand-zero pages before the kernel copies through it. static KSTATUS KiProbeUserRange(uintptr_t addr, size_t size, uint64_t requiredFlags) { if (!KiUserRangeValid(addr, size)) return NTLSTATUS_ACCESS_DENIED; if (size == 0) return NTLSTATUS_SUCCESS; PROCESS* process = KeGetCurrentProcess(); if (!process) return NTLSTATUS_ACCESS_DENIED; return KiVadEnsureResidentRange(process, addr, size, requiredFlags); } // Probes user Read. __declspec(dllexport) KSTATUS MmProbeUserRead(const void* ptr, size_t size) { return KiProbeUserRange((uintptr_t)ptr, size, PAGE_PRESENT | PAGE_USER); } // Probes user Write. __declspec(dllexport) KSTATUS MmProbeUserWrite(void* ptr, size_t size) { return KiProbeUserRange((uintptr_t)ptr, size, PAGE_PRESENT | PAGE_USER | PAGE_WRITE); } // Copies from a user buffer through page-sized, pinned segments. __declspec(dllexport) KSTATUS CopyFromUser(void* kernelDst, const void* userSrc, size_t size) { return KiCopyFromUserPaged(kernelDst, userSrc, size); } // Copies to a user buffer through page-sized, pinned segments. __declspec(dllexport) KSTATUS CopyToUser(void* userDst, const void* kernelSrc, size_t size) { return KiCopyToUserPaged(userDst, kernelSrc, size); } // VAD arena. typedef enum MM_VAD_PAGE_STATE { MmVadPageFree = 0, MmVadPageCommitted = 1, } MM_VAD_PAGE_STATE; #define MM_VAD_PAGE_COW 0x00000001u #define MM_VAD_PAGE_ZERO_PAGE 0x00000002u #define MM_VAD_PAGE_SWAPPED 0x00000004u #define MM_VAD_PAGE_SHARED_FRAME 0x00000008u #define MM_VAD_PAGE_PAGING 0x00000010u #define MM_VAD_PAGE_FILE_BACKED 0x00000020u #define MM_VAD_PAGE_IMAGE_BACKED 0x00000040u #define MM_VAD_PAGE_IMAGE_PRIVATE 0x00000080u #define MM_VAD_PAGE_IMAGE_FIXUP 0x00000100u #define MM_VAD_PAGE_FILE_SHARED_WRITE 0x00000200u #define MM_VAD_PAGE_FILE_DIRTY_TRACK 0x00000400u typedef struct MM_VAD_PAGE { uint64_t PageFlags; PHYSICAL_ADDRESS Frame; uint8_t State; uint8_t Resident; uint16_t Reserved; uint32_t Flags; uint32_t SwapSlot; } MM_VAD_PAGE; struct MM_KERNEL_VIEW { LIST_ENTRY Link; uintptr_t Base; size_t PageCount; }; typedef struct MM_VAD { LIST_ENTRY ProcessLink; LIST_ENTRY GlobalLink; PROCESS* Owner; uintptr_t ReservationBase; size_t ReservationSize; uintptr_t Base; size_t Size; size_t PayloadSize; size_t PageCount; size_t CommittedPages; uint64_t PageFlags; MM_VAD_TYPE Type; uint32_t Flags; uint32_t LockCount; MM_VAD_PAGE* Pages; FILE_OBJECT* FileObject; uint64_t FileOffset; size_t FileLength; uint32_t FileMappingFlags; IMAGE_SECTION_OBJECT* ImageSection; } MM_VAD; static LIST_ENTRY gVadList; static SPINLOCK gVadLock = SPINLOCK_INIT; static int gVadListReady = 0; static MM_VAD_STATS gVadStats; static LIST_ENTRY gKernelViewList; static SPINLOCK gKernelViewLock = SPINLOCK_INIT; #define MM_VAD_ARENA_BASE 0x0000100000000000ULL #define MM_KERNEL_VIEW_BASE 0xFFFFA00000000000ULL #define MM_KERNEL_VIEW_LIMIT 0xFFFFB00000000000ULL // Initializes the VAD list after the shared page table and kernel pool are ready. static void KiVadInitialize(void) { LIST_INIT(&gVadList); LIST_INIT(&gKernelViewList); gVadLock = (SPINLOCK)SPINLOCK_INIT; gKernelViewLock = (SPINLOCK)SPINLOCK_INIT; memset(&gVadStats, 0, sizeof(gVadStats)); gVadListReady = 1; } // Initializes the shared page tables, kernel pool, and user VAD arena. void MmInitializeVirtual(PHYSICAL_ADDRESS bootPml4Phys, size_t poolSize) { MmPteInitialize(bootPml4Phys); MmPoolInitialize(poolSize); KiVadInitialize(); gVadZeroPageFrame = MmPfnAllocate(MmPfnUseAnonymous); if (!gVadZeroPageFrame) { KePanic(KE_PANIC_CODE_INIT, "MM: failed to allocate global zero page.\n"); } memset(MmPfnToVirtual(gVadZeroPageFrame), 0, PAGE_SIZE); (void)MmPfnPin(gVadZeroPageFrame); } // Inserts a kernel view before one list node while preserving address order. static void KiKernelViewInsertBefore(LIST_ENTRY* before, MM_KERNEL_VIEW* view) { view->Link.Fwd = before; view->Link.Bwd = before->Bwd; before->Bwd->Fwd = &view->Link; before->Bwd = &view->Link; } // Maps existing frames into the kernel shared-view arena. KSTATUS MmCreateKernelViewFromFrames(const PHYSICAL_ADDRESS* frames, size_t pageCount, MM_KERNEL_VIEW** outView) { if (!outView) return NTLSTATUS_INVALID_PARAMETER; *outView = (MM_KERNEL_VIEW*)0; if (!frames || pageCount == 0 || pageCount > (SIZE_MAX / PAGE_SIZE)) return NTLSTATUS_INVALID_PARAMETER; size_t viewBytes = pageCount * PAGE_SIZE; MM_KERNEL_VIEW* view = (MM_KERNEL_VIEW*)MmAllocatePoolTag(sizeof(MM_KERNEL_VIEW), MM_POOL_TAG('k', 'v', 'i', 'w')); if (!view) return NTLSTATUS_NO_MEMORY; memset(view, 0, sizeof(*view)); LIST_INIT(&view->Link); uintptr_t candidate = MM_KERNEL_VIEW_BASE; LIST_ENTRY* before = &gKernelViewList; uintptr_t lockFlags = KeAcquireSpinlock(&gKernelViewLock); for (LIST_ENTRY* it = gKernelViewList.Fwd; it != &gKernelViewList; it = it->Fwd) { MM_KERNEL_VIEW* existing = CONTAINING_RECORD(it, MM_KERNEL_VIEW, Link); if (candidate <= existing->Base && viewBytes <= (size_t)(existing->Base - candidate)) { before = it; break; } uintptr_t existingEnd = existing->Base + existing->PageCount * PAGE_SIZE; if (existingEnd > candidate) candidate = existingEnd; } if (candidate > MM_KERNEL_VIEW_LIMIT || viewBytes > MM_KERNEL_VIEW_LIMIT - candidate) { KeReleaseSpinlock(&gKernelViewLock, lockFlags); MmFreePool(view); return NTLSTATUS_NO_MEMORY; } view->Base = candidate; view->PageCount = pageCount; KiKernelViewInsertBefore(before, view); KeReleaseSpinlock(&gKernelViewLock, lockFlags); const uint64_t hardwareFlags = PAGE_PRESENT | PAGE_WRITE | PAGE_NX; size_t mappedPages = 0; KSTATUS s = NTLSTATUS_SUCCESS; for (; mappedPages < pageCount; mappedPages++) { if (!frames[mappedPages]) { s = NTLSTATUS_INVALID_PARAMETER; break; } s = MmPteMapPage(candidate + mappedPages * PAGE_SIZE, frames[mappedPages], hardwareFlags, MmPteMapFailIfPresent); if (!KSUCCESS(s)) break; } if (!KSUCCESS(s)) { while (mappedPages != 0) { mappedPages--; (void)MmPteUnmapPage(candidate + mappedPages * PAGE_SIZE); } lockFlags = KeAcquireSpinlock(&gKernelViewLock); ListRemove(&view->Link); KeReleaseSpinlock(&gKernelViewLock, lockFlags); MmFreePool(view); return s; } *outView = view; return NTLSTATUS_SUCCESS; } // Returns the base address of one kernel shared view. void* MmKernelViewBase(const MM_KERNEL_VIEW* view) { return view ? (void*)view->Base : (void*)0; } // Unmaps and releases one kernel shared view without releasing its frames. void MmDestroyKernelView(MM_KERNEL_VIEW* view) { if (!view) return; for (size_t i = 0; i < view->PageCount; i++) (void)MmPteUnmapPage(view->Base + i * PAGE_SIZE); uintptr_t lockFlags = KeAcquireSpinlock(&gKernelViewLock); ListRemove(&view->Link); KeReleaseSpinlock(&gKernelViewLock, lockFlags); MmFreePool(view); } // VAD utility helpers. static inline uintptr_t KiAlignUpPtr(uintptr_t v, size_t align) { if (align < PAGE_SIZE) align = PAGE_SIZE; if (align & (align - 1)) return 0; return (v + (uintptr_t)align - 1) & ~((uintptr_t)align - 1); } // Aligns a size to a power-of-two boundary and rejects invalid alignments. static inline size_t KiAlignUpSize(size_t v, size_t align) { if (align < PAGE_SIZE) align = PAGE_SIZE; if (align & (align - 1)) return 0; return (v + align - 1) & ~(align - 1); } // Returns whether two half-open address ranges intersect. static bool KiRangesOverlap(uintptr_t aBase, size_t aSize, uintptr_t bBase, size_t bSize) { uintptr_t aEnd = aBase + aSize; uintptr_t bEnd = bBase + bSize; return aBase < bEnd && bBase < aEnd; } // Finds a free range in the global user VA arena. static KSTATUS KiFindVadRange(size_t reservationSize, size_t alignment, uintptr_t* baseOut) { if (!baseOut || !reservationSize) return NTLSTATUS_INVALID_PARAMETER; if (!gVadListReady) return NTLSTATUS_FAILURE; uintptr_t candidate = KiAlignUpPtr(MM_VAD_ARENA_BASE, alignment); if (!candidate) return NTLSTATUS_INVALID_PARAMETER; while (true) { if (candidate < MM_VAD_ARENA_BASE || reservationSize > (size_t)(USER_LIMIT - candidate)) return NTLSTATUS_NO_MEMORY; uintptr_t next = candidate; int collided = 0; for (LIST_ENTRY* node = gVadList.Fwd; node != &gVadList; node = node->Fwd) { MM_VAD* vad = CONTAINING_RECORD(node, MM_VAD, GlobalLink); if (KiRangesOverlap(candidate, reservationSize, vad->ReservationBase, vad->ReservationSize)) { uintptr_t end = vad->ReservationBase + vad->ReservationSize; next = KiAlignUpPtr(end, alignment); if (!next || next <= candidate) return NTLSTATUS_NO_MEMORY; collided = 1; break; } } if (!collided) { *baseOut = candidate; return NTLSTATUS_SUCCESS; } candidate = next; } } // Returns the page index for an address inside the VAD payload. static size_t KiVadPageIndex(const MM_VAD* vad, uintptr_t va) { return (size_t)((va - vad->Base) / PAGE_SIZE); } // Returns the page count covered by a page-aligned half-open range. static size_t KiVadRangePageCount(uintptr_t alignedStart, uintptr_t alignedEnd) { return (size_t)((alignedEnd - alignedStart) / PAGE_SIZE); } // Finds a VAD by its public base address while the owning process VAD lock is held. static MM_VAD* KiFindVadByBaseLocked(PROCESS* process, const void* base) { if (!process || !base) return (MM_VAD*)0; uintptr_t b = (uintptr_t)base; for (LIST_ENTRY* node = process->VadList.Fwd; node != &process->VadList; node = node->Fwd) { MM_VAD* vad = CONTAINING_RECORD(node, MM_VAD, ProcessLink); if (vad->Base == b) return vad; } return (MM_VAD*)0; } // Finds a VAD whose reserved payload fully contains the supplied range. static MM_VAD* KiFindReservedVadContainingLocked(PROCESS* process, uintptr_t base, size_t size) { if (!process || !base || !size) return (MM_VAD*)0; if (base > (uintptr_t)-1 - (size - 1)) return (MM_VAD*)0; uintptr_t end = base + size; for (LIST_ENTRY* node = process->VadList.Fwd; node != &process->VadList; node = node->Fwd) { MM_VAD* vad = CONTAINING_RECORD(node, MM_VAD, ProcessLink); uintptr_t vadEnd = vad->Base + vad->PayloadSize; if (base >= vad->Base && end <= vadEnd) return vad; } return (MM_VAD*)0; } // Returns whether every page in the aligned range is committed in the VAD metadata. static bool KiVadRangeCommitted(const MM_VAD* vad, uintptr_t alignedStart, uintptr_t alignedEnd) { if (!vad || alignedStart < vad->Base || alignedEnd > vad->Base + vad->PayloadSize) return false; size_t first = KiVadPageIndex(vad, alignedStart); size_t count = KiVadRangePageCount(alignedStart, alignedEnd); if (first > vad->PageCount || count > vad->PageCount - first) return false; for (size_t i = 0; i < count; ++i) { if (vad->Pages[first + i].State != MmVadPageCommitted) return false; } return true; } static uint64_t KiVadHardwarePageFlags(const MM_VAD_PAGE* page); static KSTATUS KiVadMapPageTracked(uintptr_t va, PHYSICAL_ADDRESS frame, uint64_t flags, MM_PTE_MAP_MODE mode); static KSTATUS KiVadUnmapPageTracked(uintptr_t va, PHYSICAL_ADDRESS frame); static KSTATUS KiVadMarkSharedFilePageDirty(MM_VAD* vad, size_t index, int writeProtect) { if (!vad || index >= vad->PageCount || !vad->FileObject) return NTLSTATUS_INVALID_PARAMETER; MM_VAD_PAGE* page = &vad->Pages[index]; if ((page->Flags & MM_VAD_PAGE_FILE_BACKED) == 0 || (page->Flags & MM_VAD_PAGE_FILE_SHARED_WRITE) == 0) return NTLSTATUS_NOT_FOUND; uint64_t pageDelta = (uint64_t)index * (uint64_t)PAGE_SIZE; uint64_t fileOffset = vad->FileOffset + pageDelta; if (fileOffset < vad->FileOffset) return NTLSTATUS_INVALID_PARAMETER; uint32_t validBytes = PAGE_SIZE; if (pageDelta >= (uint64_t)vad->FileLength) { validBytes = PAGE_SIZE; } else if ((uint64_t)vad->FileLength - pageDelta < PAGE_SIZE) { validBytes = (uint32_t)((uint64_t)vad->FileLength - pageDelta); if (validBytes == 0) validBytes = PAGE_SIZE; } MmFileCacheMarkDirty(vad->FileObject, fileOffset, validBytes); uint64_t end = fileOffset + validBytes; uintptr_t flags = KeAcquireSpinlock(&vad->FileObject->Lock); if (end > vad->FileObject->Size) vad->FileObject->Size = end; KeReleaseSpinlock(&vad->FileObject->Lock, flags); if (writeProtect && page->Resident && page->Frame && page->PageFlags) { uintptr_t va = vad->Base + index * PAGE_SIZE; page->Flags |= MM_VAD_PAGE_FILE_DIRTY_TRACK; (void)MmPteProtectRange(va, PAGE_SIZE, KiVadHardwarePageFlags(page)); } gVadStats.FileBackedDirtySyncs++; return NTLSTATUS_SUCCESS; } static void KiVadSyncSharedFilePageBeforeRelease(MM_VAD* vad, size_t index) { if (!vad || index >= vad->PageCount) return; MM_VAD_PAGE* page = &vad->Pages[index]; if ((page->Flags & MM_VAD_PAGE_FILE_SHARED_WRITE) == 0) return; uintptr_t va = vad->Base + index * PAGE_SIZE; if (!page->Resident || !page->Frame) return; int wasDirty = MmPteTestAndClearDirty(va); bool wasWriteEnabled = (page->Flags & MM_VAD_PAGE_FILE_DIRTY_TRACK) == 0; if (wasDirty || wasWriteEnabled) (void)KiVadMarkSharedFilePageDirty(vad, index, 0); } // Marks a page uncommitted and releases any physical frame it still owns. static void KiVadReleasePage(MM_VAD* vad, size_t index) { if (!vad || index >= vad->PageCount) return; MM_VAD_PAGE* page = &vad->Pages[index]; uintptr_t va = vad->Base + index * PAGE_SIZE; KiVadSyncSharedFilePageBeforeRelease(vad, index); if (page->Resident && page->PageFlags) { (void)KiVadUnmapPageTracked(va, page->Frame); } if (page->Resident && page->Frame && !(page->Flags & MM_VAD_PAGE_ZERO_PAGE)) { (void)MmPfnRelease(page->Frame); } if ((page->Flags & MM_VAD_PAGE_SWAPPED) && page->SwapSlot != 0xFFFFFFFFu) { MmSwapFreeSlot(page->SwapSlot); } if (page->State == MmVadPageCommitted && vad->CommittedPages) vad->CommittedPages--; page->PageFlags = 0; page->Frame = 0; page->State = MmVadPageFree; page->Resident = 0; page->Flags = 0; page->SwapSlot = 0xFFFFFFFFu; } // Unmaps all resident pages in a VAD and releases their PFNs. static void KiUnmapVadPayload(MM_VAD* vad) { if (!vad || !vad->Pages) return; for (size_t i = 0; i < vad->PageCount; ++i) { if (vad->Pages[i].State == MmVadPageCommitted) KiVadReleasePage(vad, i); } } // Returns the hardware PTE flags used for a resident VAD page. static uint64_t KiVadHardwarePageFlags(const MM_VAD_PAGE* page) { if (!page || !page->PageFlags) return 0; uint64_t flags = page->PageFlags; if (page->Flags & MM_VAD_PAGE_COW) flags &= ~PAGE_WRITE; if (page->Flags & MM_VAD_PAGE_FILE_DIRTY_TRACK) flags &= ~PAGE_WRITE; return flags; } // Maps a user VAD PTE and records the mapping in the PFN database. static KSTATUS KiVadMapPageTracked(uintptr_t va, PHYSICAL_ADDRESS frame, uint64_t flags, MM_PTE_MAP_MODE mode) { KSTATUS s = MmPteMapPage(va, frame, flags, mode); if (!KSUCCESS(s)) return s; s = MmPfnAddMapping(frame); if (!KSUCCESS(s)) { (void)MmPteUnmapPage(va); return s; } return NTLSTATUS_SUCCESS; } // Unmaps a user VAD PTE and removes the matching PFN mapping reference. static KSTATUS KiVadUnmapPageTracked(uintptr_t va, PHYSICAL_ADDRESS frame) { KSTATUS s = MmPteUnmapPage(va); if (KSUCCESS(s)) (void)MmPfnRemoveMapping(frame); return s; } // Returns whether the committed page allows the requested fault or probe access. static KSTATUS KiVadCheckPageAccess(const MM_VAD_PAGE* page, int wantWrite, int wantExecute) { if (!page || page->State != MmVadPageCommitted) return NTLSTATUS_NOT_FOUND; if (!page->PageFlags) return NTLSTATUS_ACCESS_DENIED; if ((page->PageFlags & PAGE_USER) == 0) return NTLSTATUS_ACCESS_DENIED; if (wantWrite && (page->PageFlags & PAGE_WRITE) == 0) return NTLSTATUS_ACCESS_DENIED; if (wantExecute && (page->PageFlags & PAGE_NX)) return NTLSTATUS_ACCESS_DENIED; return NTLSTATUS_SUCCESS; } // Waits for another CPU/thread to finish a page-in/page-out transition on a VAD page. // The pager performs filesystem I/O outside the VAD lock; this transient bit keeps // concurrent faults from treating half-transitioned metadata as either resident or // fully swapped. static void KiVadWaitForPagingComplete(MM_VAD_PAGE* page) { while (page && (NtlAtomicLoadU32(&page->Flags) & MM_VAD_PAGE_PAGING)) KeYield(); } // Atomically claims the transient paging bit. This is deliberately not a spinlock: // a page-in/page-out may allocate frames or do filesystem I/O, so waiters must yield // instead of holding IRQL-raised locks. Without this CAS, two CPUs faulting the same // swapped/demand-zero page can both allocate/read/map it; one map fails and rolls // metadata back to SWAPPED while the other has already published a valid PTE. That // leaves stale or freed swap slots behind and corrupts user data under pressure. static bool KiVadTryBeginPaging(MM_VAD_PAGE* page) { if (!page) return false; while (true) { uint32_t oldFlags = (uint32_t)NtlAtomicLoadU32(&page->Flags); if (oldFlags & MM_VAD_PAGE_PAGING) return false; uint32_t newFlags = oldFlags | MM_VAD_PAGE_PAGING; if ((uint32_t)NtlAtomicCompareExchangeU32(&page->Flags, newFlags, oldFlags) == oldFlags) return true; _mm_pause(); } } static void KiVadEndPaging(MM_VAD_PAGE* page) { if (!page) return; while (true) { uint32_t oldFlags = (uint32_t)NtlAtomicLoadU32(&page->Flags); uint32_t newFlags = oldFlags & ~MM_VAD_PAGE_PAGING; if ((uint32_t)NtlAtomicCompareExchangeU32(&page->Flags, newFlags, oldFlags) == oldFlags) return; _mm_pause(); } } // Reads one page of a file-backed VAD into a freshly allocated user frame. // Bytes beyond EOF or beyond the requested mapping length are zero-filled, // matching the useful mmap/CreateFileMapping contract without materializing the // whole file up front. static KSTATUS KiVadFillFileBackedPage(MM_VAD* vad, size_t pageIndex, PHYSICAL_ADDRESS frame) { if (!vad || !vad->FileObject || !frame || pageIndex >= vad->PageCount) return NTLSTATUS_INVALID_PARAMETER; void* pageVa = MmPfnToVirtual(frame); if (!pageVa) return NTLSTATUS_FAILURE; memset(pageVa, 0, PAGE_SIZE); uint64_t pageDelta = (uint64_t)pageIndex * (uint64_t)PAGE_SIZE; if (pageDelta >= (uint64_t)vad->FileLength) return NTLSTATUS_SUCCESS; size_t remaining = vad->FileLength - (size_t)pageDelta; size_t toRead = remaining < PAGE_SIZE ? remaining : PAGE_SIZE; uint64_t fileOffset = vad->FileOffset + pageDelta; if (fileOffset < vad->FileOffset) return NTLSTATUS_INVALID_PARAMETER; size_t got = 0; KSTATUS s = IoReadFileAt(vad->FileObject, fileOffset, pageVa, toRead, &got); if (!KSUCCESS(s)) { gVadStats.FileBackedReadFailures++; return s; } gVadStats.FileBackedReads++; if (got < toRead) memset((uint8_t*)pageVa + got, 0, toRead - got); return NTLSTATUS_SUCCESS; } // Materializes one image-backed prototype page or zero-fill page for an image VAD. static KSTATUS KiVadFillImageBackedPage(MM_VAD* vad, size_t pageIndex, int wantWrite) { if (!vad || !vad->ImageSection || pageIndex >= vad->PageCount) return NTLSTATUS_INVALID_PARAMETER; MM_VAD_PAGE* page = &vad->Pages[pageIndex]; uintptr_t va = vad->Base + pageIndex * PAGE_SIZE; PHYSICAL_ADDRESS frame = 0; KSTATUS s = LdrRetainImageSectionPage(vad->ImageSection, pageIndex, &frame); if (!KSUCCESS(s)) return s; if (frame) { page->Frame = frame; page->Resident = 1; page->Flags &= ~(MM_VAD_PAGE_ZERO_PAGE | MM_VAD_PAGE_SWAPPED); page->Flags |= MM_VAD_PAGE_IMAGE_BACKED | MM_VAD_PAGE_SHARED_FRAME | MM_VAD_PAGE_COW; page->SwapSlot = 0xFFFFFFFFu; s = KiVadMapPageTracked(va, frame, KiVadHardwarePageFlags(page), MmPteMapFailIfPresent); if (!KSUCCESS(s)) { page->Frame = 0; page->Resident = 0; page->Flags &= ~(MM_VAD_PAGE_SHARED_FRAME | MM_VAD_PAGE_COW); (void)MmPfnRelease(frame); return s; } gVadStats.ImageBackedFaults++; return NTLSTATUS_SUCCESS; } if (!wantWrite && gVadZeroPageFrame) { page->Frame = gVadZeroPageFrame; page->Resident = 1; page->Flags |= MM_VAD_PAGE_IMAGE_BACKED | MM_VAD_PAGE_ZERO_PAGE; if (page->PageFlags & PAGE_WRITE) page->Flags |= MM_VAD_PAGE_COW; page->SwapSlot = 0xFFFFFFFFu; s = KiVadMapPageTracked(va, page->Frame, KiVadHardwarePageFlags(page), MmPteMapFailIfPresent); if (!KSUCCESS(s)) { page->Frame = 0; page->Resident = 0; page->Flags &= ~(MM_VAD_PAGE_ZERO_PAGE | MM_VAD_PAGE_COW); return s; } gVadStats.ImageBackedZeroFaults++; return NTLSTATUS_SUCCESS; } frame = MmPfnAllocate(MmPfnUseImage); if (!frame) return NTLSTATUS_NO_MEMORY; void* pageVa = MmPfnToVirtual(frame); if (!pageVa) { MmPfnFree(frame); return NTLSTATUS_FAILURE; } memset(pageVa, 0, PAGE_SIZE); page->Frame = frame; page->Resident = 1; page->Flags &= ~(MM_VAD_PAGE_COW | MM_VAD_PAGE_ZERO_PAGE | MM_VAD_PAGE_SHARED_FRAME | MM_VAD_PAGE_SWAPPED); page->Flags |= MM_VAD_PAGE_IMAGE_BACKED; page->SwapSlot = 0xFFFFFFFFu; s = KiVadMapPageTracked(va, frame, KiVadHardwarePageFlags(page), MmPteMapFailIfPresent); if (!KSUCCESS(s)) { page->Frame = 0; page->Resident = 0; MmPfnFree(frame); return s; } gVadStats.ImageBackedPrivateFaults++; return NTLSTATUS_SUCCESS; } // Materializes one committed VAD page as either the shared zero page or a private frame. static KSTATUS KiVadEnsurePageResident(MM_VAD* vad, size_t pageIndex, int wantWrite, int wantExecute) { if (!vad || pageIndex >= vad->PageCount) return NTLSTATUS_NOT_FOUND; MM_VAD_PAGE* page = &vad->Pages[pageIndex]; retry_after_paging: KiVadWaitForPagingComplete(page); KSTATUS s = KiVadCheckPageAccess(page, wantWrite, wantExecute); if (!KSUCCESS(s)) return s; uintptr_t va = vad->Base + pageIndex * PAGE_SIZE; if (page->Resident) { gVadStats.AlreadyResidentPages++; return NTLSTATUS_SUCCESS; } if (!KiVadTryBeginPaging(page)) goto retry_after_paging; // Re-check after claiming the transition bit. Another CPU may have resolved // the page between our initial wait/access check and the CAS above. s = KiVadCheckPageAccess(page, wantWrite, wantExecute); if (!KSUCCESS(s)) { KiVadEndPaging(page); return s; } if (page->Resident) { KiVadEndPaging(page); gVadStats.AlreadyResidentPages++; return NTLSTATUS_SUCCESS; } if ((page->Flags & MM_VAD_PAGE_SWAPPED) && page->SwapSlot != 0xFFFFFFFFu) { uint32_t oldSlot = page->SwapSlot; PHYSICAL_ADDRESS frame = MmPfnAllocate(MmPfnUseAnonymous); if (!frame) { KiVadEndPaging(page); return NTLSTATUS_NO_MEMORY; } KSTATUS swapStatus = MmSwapPageIn(oldSlot, frame); if (!KSUCCESS(swapStatus)) { MmPfnFree(frame); KiVadEndPaging(page); return swapStatus; } page->Frame = frame; page->Resident = 1; page->Flags &= ~MM_VAD_PAGE_SWAPPED; page->SwapSlot = 0xFFFFFFFFu; KSTATUS mapStatus = KiVadMapPageTracked(va, frame, KiVadHardwarePageFlags(page), MmPteMapFailIfPresent); if (!KSUCCESS(mapStatus)) { page->Frame = 0; page->Resident = 0; page->Flags |= MM_VAD_PAGE_SWAPPED; page->SwapSlot = oldSlot; KiVadEndPaging(page); MmPfnFree(frame); return mapStatus; } KiVadEndPaging(page); MmSwapFreeSlot(oldSlot); gVadStats.SwapPageIns++; return NTLSTATUS_SUCCESS; } if (page->Flags & MM_VAD_PAGE_IMAGE_BACKED) { s = KiVadFillImageBackedPage(vad, pageIndex, wantWrite); KiVadEndPaging(page); return s; } if (page->Flags & MM_VAD_PAGE_FILE_BACKED) { PHYSICAL_ADDRESS frame = 0; MM_FILE_CACHE_PAGE cachePage; memset(&cachePage, 0, sizeof(cachePage)); uint64_t fileOffset = vad->FileOffset + (uint64_t)pageIndex * (uint64_t)PAGE_SIZE; if (fileOffset < vad->FileOffset) { KiVadEndPaging(page); return NTLSTATUS_INVALID_PARAMETER; } if (page->Flags & MM_VAD_PAGE_FILE_SHARED_WRITE) { uintptr_t f = KeAcquireSpinlock(&vad->FileObject->Lock); uint64_t oldSize = vad->FileObject->Size; KeReleaseSpinlock(&vad->FileObject->Lock, f); int needExisting = fileOffset < oldSize; s = MmFileCacheGetWritablePage(vad->FileObject, fileOffset, oldSize, needExisting, &cachePage); if (!KSUCCESS(s)) { gVadStats.FileBackedReadFailures++; KiVadEndPaging(page); return s; } frame = cachePage.Frame; page->Flags |= MM_VAD_PAGE_FILE_DIRTY_TRACK; gVadStats.FileBackedSharedWritableFaults++; } else if ((page->PageFlags & PAGE_WRITE) == 0) { s = MmFileCacheGetPage(vad->FileObject, fileOffset, &cachePage); if (!KSUCCESS(s)) { gVadStats.FileBackedReadFailures++; KiVadEndPaging(page); return s; } frame = cachePage.Frame; } else { frame = MmPfnAllocate(MmPfnUseMappedFile); if (!frame) { KiVadEndPaging(page); return NTLSTATUS_NO_MEMORY; } s = KiVadFillFileBackedPage(vad, pageIndex, frame); if (!KSUCCESS(s)) { KiVadEndPaging(page); MmPfnFree(frame); return s; } } page->Frame = frame; page->Resident = 1; page->Flags &= ~(MM_VAD_PAGE_ZERO_PAGE | MM_VAD_PAGE_SHARED_FRAME | MM_VAD_PAGE_SWAPPED); page->SwapSlot = 0xFFFFFFFFu; s = KiVadMapPageTracked(va, frame, KiVadHardwarePageFlags(page), MmPteMapFailIfPresent); if (!KSUCCESS(s)) { page->Frame = 0; page->Resident = 0; KiVadEndPaging(page); if (cachePage.Frame) MmFileCacheReleasePage(&cachePage); else MmPfnFree(frame); return s; } KiVadEndPaging(page); gVadStats.FileBackedFaults++; return NTLSTATUS_SUCCESS; } if (!wantWrite && gVadZeroPageFrame) { uint32_t savedFlags = page->Flags; page->Frame = gVadZeroPageFrame; page->Resident = 1; page->Flags |= MM_VAD_PAGE_ZERO_PAGE; if (page->PageFlags & PAGE_WRITE) page->Flags |= MM_VAD_PAGE_COW; s = KiVadMapPageTracked(va, page->Frame, KiVadHardwarePageFlags(page), MmPteMapFailIfPresent); if (!KSUCCESS(s)) { page->Frame = 0; page->Resident = 0; page->Flags = savedFlags; KiVadEndPaging(page); return s; } KiVadEndPaging(page); gVadStats.ZeroPageMaps++; return NTLSTATUS_SUCCESS; } PHYSICAL_ADDRESS frame = MmPfnAllocate(MmPfnUseAnonymous); if (!frame) { KiVadEndPaging(page); return NTLSTATUS_NO_MEMORY; } void* pageVa = MmPfnToVirtual(frame); if (!pageVa) { KiVadEndPaging(page); MmPfnFree(frame); return NTLSTATUS_FAILURE; } memset(pageVa, 0, PAGE_SIZE); page->Frame = frame; page->Resident = 1; page->Flags &= ~(MM_VAD_PAGE_COW | MM_VAD_PAGE_ZERO_PAGE | MM_VAD_PAGE_SHARED_FRAME | MM_VAD_PAGE_SWAPPED); page->SwapSlot = 0xFFFFFFFFu; s = KiVadMapPageTracked(va, frame, KiVadHardwarePageFlags(page), MmPteMapFailIfPresent); if (!KSUCCESS(s)) { page->Frame = 0; page->Resident = 0; KiVadEndPaging(page); MmPfnFree(frame); return s; } KiVadEndPaging(page); gVadStats.DemandZeroPages++; return NTLSTATUS_SUCCESS; } // Splits a copy-on-write page so this VAD receives a private writable frame. static KSTATUS KiVadBreakCopyOnWrite(MM_VAD* vad, size_t pageIndex) { if (!vad || pageIndex >= vad->PageCount) return NTLSTATUS_NOT_FOUND; MM_VAD_PAGE* page = &vad->Pages[pageIndex]; if (page->State != MmVadPageCommitted || !(page->Flags & MM_VAD_PAGE_COW)) return NTLSTATUS_ACCESS_DENIED; if (!page->Resident || !page->Frame) return NTLSTATUS_NOT_FOUND; if ((page->PageFlags & PAGE_WRITE) == 0) return NTLSTATUS_ACCESS_DENIED; PHYSICAL_ADDRESS newFrame = MmPfnAllocate(MmPfnUseCow); if (!newFrame) return NTLSTATUS_NO_MEMORY; void* newPage = MmPfnToVirtual(newFrame); if (!newPage) { MmPfnFree(newFrame); return NTLSTATUS_FAILURE; } if (page->Flags & MM_VAD_PAGE_ZERO_PAGE) { memset(newPage, 0, PAGE_SIZE); gVadStats.ZeroPageCowSplits++; } else { void* oldPage = MmPfnToVirtual(page->Frame); if (!oldPage) { MmPfnFree(newFrame); return NTLSTATUS_FAILURE; } memcpy(newPage, oldPage, PAGE_SIZE); } uintptr_t va = vad->Base + pageIndex * PAGE_SIZE; KSTATUS s = KiVadUnmapPageTracked(va, page->Frame); if (!KSUCCESS(s)) { MmPfnFree(newFrame); return s; } s = KiVadMapPageTracked(va, newFrame, page->PageFlags, MmPteMapFailIfPresent); if (!KSUCCESS(s)) { (void)KiVadMapPageTracked(va, page->Frame, KiVadHardwarePageFlags(page), MmPteMapFailIfPresent); MmPfnFree(newFrame); return s; } if (!(page->Flags & MM_VAD_PAGE_ZERO_PAGE)) (void)MmPfnRelease(page->Frame); page->Frame = newFrame; page->Flags &= ~(MM_VAD_PAGE_COW | MM_VAD_PAGE_ZERO_PAGE | MM_VAD_PAGE_SHARED_FRAME | MM_VAD_PAGE_IMAGE_BACKED); page->Resident = 1; gVadStats.CowPagesCopied++; return NTLSTATUS_SUCCESS; } // Ensures that every committed page in a range is resident and accessible. static KSTATUS KiVadEnsureResidentRange(PROCESS* process, uintptr_t start, size_t size, uint64_t accessFlags) { if (!process || !size) return NTLSTATUS_INVALID_PARAMETER; if (start > (uintptr_t)-1 - (size - 1)) return NTLSTATUS_NO_MEMORY; uintptr_t alignedStart = start & PAGE_MASK; uintptr_t alignedEnd = (start + size + PAGE_SIZE - 1) & PAGE_MASK; if (alignedEnd <= alignedStart) return NTLSTATUS_NO_MEMORY; uintptr_t procFlags = KeAcquireSpinlock(&process->VadLock); MM_VAD* vad = KiFindReservedVadContainingLocked(process, alignedStart, alignedEnd - alignedStart); KeReleaseSpinlock(&process->VadLock, procFlags); if (!vad) return NTLSTATUS_NOT_FOUND; int wantWrite = (accessFlags & PAGE_WRITE) ? 1 : 0; int wantExecute = (accessFlags & PAGE_NX) ? 1 : 0; size_t first = KiVadPageIndex(vad, alignedStart); size_t count = KiVadRangePageCount(alignedStart, alignedEnd); if (first > vad->PageCount || count > vad->PageCount - first) return NTLSTATUS_ACCESS_DENIED; gVadStats.EnsureResidentCalls++; for (size_t i = 0; i < count; ++i) { KSTATUS s = KiVadEnsurePageResident(vad, first + i, wantWrite, wantExecute); if (!KSUCCESS(s)) return s; if (wantWrite && (vad->Pages[first + i].Flags & MM_VAD_PAGE_COW)) { s = KiVadBreakCopyOnWrite(vad, first + i); if (!KSUCCESS(s)) return s; } gVadStats.EnsureResidentPages++; } return NTLSTATUS_SUCCESS; } // Makes an image VAD range private before the loader patches relocations or imports. KSTATUS MmVadMakeImageRangePrivate(PROCESS* process, void* base, size_t size) { KeAssertIrql(PASSIVE_LEVEL); if (!process || !base || !size) return NTLSTATUS_INVALID_PARAMETER; uintptr_t start = (uintptr_t)base; if (start > (uintptr_t)-1 - (size - 1)) return NTLSTATUS_NO_MEMORY; uintptr_t alignedStart = start & PAGE_MASK; uintptr_t alignedEnd = (start + size + PAGE_SIZE - 1) & PAGE_MASK; if (alignedEnd <= alignedStart) return NTLSTATUS_NO_MEMORY; uintptr_t procFlags = KeAcquireSpinlock(&process->VadLock); MM_VAD* vad = KiFindReservedVadContainingLocked(process, alignedStart, alignedEnd - alignedStart); KeReleaseSpinlock(&process->VadLock, procFlags); if (!vad) return NTLSTATUS_NOT_FOUND; if (vad->Type != MmVadImage) return NTLSTATUS_INVALID_PARAMETER; size_t first = KiVadPageIndex(vad, alignedStart); size_t count = KiVadRangePageCount(alignedStart, alignedEnd); if (first > vad->PageCount || count > vad->PageCount - first) return NTLSTATUS_ACCESS_DENIED; gVadStats.ImageFixupPrivateCalls++; for (size_t i = 0; i < count; ++i) { MM_VAD_PAGE* page = &vad->Pages[first + i]; KSTATUS s = KiVadEnsurePageResident(vad, first + i, 1, 0); if (!KSUCCESS(s)) return s; if (page->Flags & MM_VAD_PAGE_COW) { s = KiVadBreakCopyOnWrite(vad, first + i); if (!KSUCCESS(s)) return s; } if ((page->Flags & MM_VAD_PAGE_IMAGE_PRIVATE) == 0) { page->Flags |= MM_VAD_PAGE_IMAGE_PRIVATE | MM_VAD_PAGE_IMAGE_FIXUP; page->Flags &= ~(MM_VAD_PAGE_IMAGE_BACKED | MM_VAD_PAGE_SHARED_FRAME | MM_VAD_PAGE_ZERO_PAGE); gVadStats.ImageFixupPrivatePages++; } else { page->Flags |= MM_VAD_PAGE_IMAGE_FIXUP; } } return NTLSTATUS_SUCCESS; } // Pins the user page that contains the supplied address after materializing it. // The returned KernelAddress is a direct-map alias for the physical frame, so // callers do not continue touching the original user VA after validation. This // closes the unmap-after-pin race and is the primitive future zero-copy-ish I/O // paths should build on. static KSTATUS KiVadPinUserPage(PROCESS* process, uintptr_t address, uint64_t accessFlags, MM_PINNED_USER_PAGE* out) { if (!process || !out) return NTLSTATUS_INVALID_PARAMETER; memset(out, 0, sizeof(*out)); if (!KiUserRangeValid(address, 1)) return NTLSTATUS_ACCESS_DENIED; uintptr_t pageBase = address & PAGE_MASK; KSTATUS s = KiVadEnsureResidentRange(process, pageBase, PAGE_SIZE, accessFlags | PAGE_USER); if (!KSUCCESS(s)) return s; uintptr_t procFlags = KeAcquireSpinlock(&process->VadLock); MM_VAD* vad = KiFindReservedVadContainingLocked(process, pageBase, PAGE_SIZE); PHYSICAL_ADDRESS frame = 0; uint64_t pageFlags = 0; if (vad) { size_t index = KiVadPageIndex(vad, pageBase); if (index < vad->PageCount) { MM_VAD_PAGE* page = &vad->Pages[index]; if (page->State == MmVadPageCommitted && page->Resident && page->Frame && page->PageFlags) { frame = page->Frame; pageFlags = page->PageFlags; } } } KeReleaseSpinlock(&process->VadLock, procFlags); if (!frame) return NTLSTATUS_NOT_FOUND; s = MmPfnPin(frame); if (!KSUCCESS(s)) return s; procFlags = KeAcquireSpinlock(&process->VadLock); vad = KiFindReservedVadContainingLocked(process, pageBase, PAGE_SIZE); int stillValid = 0; if (vad) { size_t index = KiVadPageIndex(vad, pageBase); if (index < vad->PageCount) { MM_VAD_PAGE* page = &vad->Pages[index]; stillValid = page->State == MmVadPageCommitted && page->Resident && page->Frame == frame && page->PageFlags != 0 && ((accessFlags & PAGE_WRITE) == 0 || (page->PageFlags & PAGE_WRITE)); if (stillValid) pageFlags = page->PageFlags; } } KeReleaseSpinlock(&process->VadLock, procFlags); if (!stillValid) { (void)MmPfnUnpin(frame); return NTLSTATUS_NOT_FOUND; } out->UserPage = pageBase; out->PageOffset = (size_t)(address & (PAGE_SIZE - 1)); out->Frame = frame; out->KernelAddress = MmPfnToVirtual(frame); out->PageFlags = pageFlags; if (!out->KernelAddress) { MmUnpinUserPage(out); return NTLSTATUS_FAILURE; } return NTLSTATUS_SUCCESS; } __declspec(dllexport) KSTATUS MmPinUserPageForRead(PROCESS* process, const void* userAddress, MM_PINNED_USER_PAGE* out) { return KiVadPinUserPage(process, (uintptr_t)userAddress, PAGE_PRESENT | PAGE_USER, out); } __declspec(dllexport) KSTATUS MmPinUserPageForWrite(PROCESS* process, void* userAddress, MM_PINNED_USER_PAGE* out) { return KiVadPinUserPage(process, (uintptr_t)userAddress, PAGE_PRESENT | PAGE_USER | PAGE_WRITE, out); } __declspec(dllexport) void MmUnpinUserPage(MM_PINNED_USER_PAGE* page) { if (!page || !page->Frame) return; (void)MmPfnUnpin(page->Frame); memset(page, 0, sizeof(*page)); } typedef struct MM_LOCKED_USER_BUFFER { PROCESS* Process; void* UserAddress; size_t Size; size_t PageCount; int WriteAccess; MM_PINNED_USER_PAGE Pages[1]; } MM_LOCKED_USER_BUFFER; // Returns the per-page VAD lock array that follows the pinned-page array. static MM_VAD** KiLockedUserBufferVads(MM_LOCKED_USER_BUFFER* buffer) { return buffer ? (MM_VAD**)(buffer->Pages + buffer->PageCount) : (MM_VAD**)0; } // Adds one outstanding user-buffer lock to the VAD that owns a page. static KSTATUS KiVadAddUserBufferPageLock(PROCESS* process, uintptr_t pageBase, MM_VAD** vadOut) { if (vadOut) *vadOut = (MM_VAD*)0; if (!process || !vadOut) return NTLSTATUS_INVALID_PARAMETER; uintptr_t procFlags = KeAcquireSpinlock(&process->VadLock); MM_VAD* vad = KiFindReservedVadContainingLocked(process, pageBase, PAGE_SIZE); if (!vad) { KeReleaseSpinlock(&process->VadLock, procFlags); return NTLSTATUS_NOT_FOUND; } if (vad->LockCount == UINT32_MAX) { KeReleaseSpinlock(&process->VadLock, procFlags); return NTLSTATUS_FAILURE; } vad->LockCount++; *vadOut = vad; KeReleaseSpinlock(&process->VadLock, procFlags); return NTLSTATUS_SUCCESS; } // Removes one outstanding user-buffer lock from a VAD. static void KiVadRemoveUserBufferPageLock(PROCESS* process, MM_VAD* vad) { if (!process || !vad) return; uintptr_t procFlags = KeAcquireSpinlock(&process->VadLock); if (vad->LockCount) vad->LockCount--; KeReleaseSpinlock(&process->VadLock, procFlags); } // Locks a user buffer for asynchronous kernel access. __declspec(dllexport) KSTATUS MmLockUserBuffer(PROCESS* process, void* userAddress, size_t size, bool writeAccess, MM_LOCKED_USER_BUFFER** out) { if (!out) return NTLSTATUS_INVALID_PARAMETER; *out = (MM_LOCKED_USER_BUFFER*)0; if (size == 0) return NTLSTATUS_SUCCESS; if (!process || !userAddress) return NTLSTATUS_INVALID_PARAMETER; uintptr_t start = (uintptr_t)userAddress; if (!KiUserRangeValid(start, size)) return NTLSTATUS_ACCESS_DENIED; uintptr_t alignedStart = start & PAGE_MASK; uintptr_t alignedEnd = (start + size + PAGE_SIZE - 1) & PAGE_MASK; if (alignedEnd <= alignedStart) return NTLSTATUS_NO_MEMORY; size_t pageCount = KiVadRangePageCount(alignedStart, alignedEnd); if (!pageCount) return NTLSTATUS_INVALID_PARAMETER; if (pageCount > (((size_t)-1) - sizeof(MM_LOCKED_USER_BUFFER)) / (sizeof(MM_PINNED_USER_PAGE) + sizeof(MM_VAD*))) return NTLSTATUS_NO_MEMORY; size_t allocSize = sizeof(MM_LOCKED_USER_BUFFER) + (pageCount - 1) * sizeof(MM_PINNED_USER_PAGE) + pageCount * sizeof(MM_VAD*); MM_LOCKED_USER_BUFFER* buffer = (MM_LOCKED_USER_BUFFER*)MmAllocatePoolTag(allocSize, MM_POOL_TAG('u', 'l', 'o', 'k')); if (!buffer) return NTLSTATUS_NO_MEMORY; memset(buffer, 0, allocSize); buffer->Process = process; buffer->UserAddress = userAddress; buffer->Size = size; buffer->PageCount = pageCount; buffer->WriteAccess = writeAccess ? 1 : 0; ObReferenceObject(&process->Header); MM_VAD** vads = KiLockedUserBufferVads(buffer); KSTATUS s = NTLSTATUS_SUCCESS; size_t lockedPages = 0; for (size_t i = 0; i < pageCount; ++i) { uintptr_t pageBase = alignedStart + i * PAGE_SIZE; s = KiVadAddUserBufferPageLock(process, pageBase, &vads[i]); if (!KSUCCESS(s)) break; s = writeAccess ? MmPinUserPageForWrite(process, (void*)pageBase, &buffer->Pages[i]) : MmPinUserPageForRead(process, (const void*)pageBase, &buffer->Pages[i]); if (!KSUCCESS(s)) { KiVadRemoveUserBufferPageLock(process, vads[i]); vads[i] = (MM_VAD*)0; break; } lockedPages++; } if (!KSUCCESS(s)) { for (size_t i = 0; i < lockedPages; ++i) MmUnpinUserPage(&buffer->Pages[i]); for (size_t i = 0; i < pageCount; ++i) KiVadRemoveUserBufferPageLock(process, vads[i]); ObDereferenceObject(&process->Header); MmFreePool(buffer); return s; } *out = buffer; return NTLSTATUS_SUCCESS; } // Returns the page count held by a locked user buffer. size_t MmLockedUserBufferPageCount(const MM_LOCKED_USER_BUFFER* buffer) { return buffer ? buffer->PageCount : 0; } // Returns one pinned frame held by a locked user buffer. PHYSICAL_ADDRESS MmLockedUserBufferPageFrame(const MM_LOCKED_USER_BUFFER* buffer, size_t index) { if (!buffer || index >= buffer->PageCount) return (PHYSICAL_ADDRESS)0; return buffer->Pages[index].Frame; } // Unlocks a user buffer locked for asynchronous kernel access. __declspec(dllexport) void MmUnlockUserBuffer(MM_LOCKED_USER_BUFFER* buffer) { if (!buffer) return; MM_VAD** vads = KiLockedUserBufferVads(buffer); for (size_t i = 0; i < buffer->PageCount; ++i) MmUnpinUserPage(&buffer->Pages[i]); for (size_t i = 0; i < buffer->PageCount; ++i) KiVadRemoveUserBufferPageLock(buffer->Process, vads[i]); if (buffer->Process) { ObDereferenceObject(&buffer->Process->Header); buffer->Process = (PROCESS*)0; } MmFreePool(buffer); } // Copies user memory one page at a time while the current page is pinned. // The pin is short-lived and exists only to keep pressure reclaim from removing // the PTE while the kernel is executing the memcpy. static KSTATUS KiCopyUserPaged(void* dst, const void* src, size_t size, int toUser) { if (size == 0) return NTLSTATUS_SUCCESS; if (!dst || !src) return NTLSTATUS_INVALID_PARAMETER; uintptr_t user = toUser ? (uintptr_t)dst : (uintptr_t)src; if (!KiUserRangeValid(user, size)) return NTLSTATUS_ACCESS_DENIED; PROCESS* process = KeGetCurrentProcess(); if (!process) return NTLSTATUS_ACCESS_DENIED; uint8_t* d = (uint8_t*)dst; const uint8_t* s = (const uint8_t*)src; size_t done = 0; uint64_t access = PAGE_PRESENT | PAGE_USER | (toUser ? PAGE_WRITE : 0); while (done < size) { uintptr_t userAddress = user + done; size_t pageOffset = (size_t)(userAddress & (PAGE_SIZE - 1)); size_t chunk = PAGE_SIZE - pageOffset; if (chunk > size - done) chunk = size - done; MM_PINNED_USER_PAGE pinned; KSTATUS st = toUser ? MmPinUserPageForWrite(process, (void*)userAddress, &pinned) : MmPinUserPageForRead(process, (const void*)userAddress, &pinned); if (!KSUCCESS(st)) return st; uint8_t* kernelPage = (uint8_t*)pinned.KernelAddress; if (toUser) memcpy(kernelPage + pinned.PageOffset, s + done, chunk); else memcpy(d + done, kernelPage + pinned.PageOffset, chunk); MmUnpinUserPage(&pinned); done += chunk; } return NTLSTATUS_SUCCESS; } // Copies from user memory after pinning each source page for the duration of the copy. static KSTATUS KiCopyFromUserPaged(void* kernelDst, const void* userSrc, size_t size) { return KiCopyUserPaged(kernelDst, userSrc, size, 0); } // Copies to user memory after pinning each destination page for the duration of the copy. static KSTATUS KiCopyToUserPaged(void* userDst, const void* kernelSrc, size_t size) { return KiCopyUserPaged(userDst, kernelSrc, size, 1); } // Copies between process-owned user ranges without bouncing through a kernel payload buffer. __declspec(dllexport) KSTATUS MmCopyUserToUser(PROCESS* sourceProcess, const void* source, PROCESS* targetProcess, void* target, size_t size) { if (size == 0) return NTLSTATUS_SUCCESS; if (!sourceProcess || !targetProcess || !source || !target) return NTLSTATUS_INVALID_PARAMETER; uintptr_t sourceAddress = (uintptr_t)source; uintptr_t targetAddress = (uintptr_t)target; if (!KiUserRangeValid(sourceAddress, size) || !KiUserRangeValid(targetAddress, size)) return NTLSTATUS_ACCESS_DENIED; size_t done = 0; while (done < size) { MM_PINNED_USER_PAGE sourcePage; MM_PINNED_USER_PAGE targetPage; memset(&sourcePage, 0, sizeof(sourcePage)); memset(&targetPage, 0, sizeof(targetPage)); KSTATUS status = MmPinUserPageForRead(sourceProcess, (const void*)(sourceAddress + done), &sourcePage); if (!KSUCCESS(status)) return status; status = MmPinUserPageForWrite(targetProcess, (void*)(targetAddress + done), &targetPage); if (!KSUCCESS(status)) { MmUnpinUserPage(&sourcePage); return status; } size_t sourceRemaining = PAGE_SIZE - sourcePage.PageOffset; size_t targetRemaining = PAGE_SIZE - targetPage.PageOffset; size_t chunk = size - done; if (chunk > sourceRemaining) chunk = sourceRemaining; if (chunk > targetRemaining) chunk = targetRemaining; memcpy((uint8_t*)targetPage.KernelAddress + targetPage.PageOffset, (const uint8_t*)sourcePage.KernelAddress + sourcePage.PageOffset, chunk); MmUnpinUserPage(&targetPage); MmUnpinUserPage(&sourcePage); done += chunk; } return NTLSTATUS_SUCCESS; } // Reserves a VAD in the global user VA arena without committing physical pages. KSTATUS MmVadReserve(PROCESS* process, size_t size, size_t alignment, uint64_t pageFlags, uint32_t vadFlags, MM_VAD_TYPE type, void** outBase) { KeAssertIrql(PASSIVE_LEVEL); if (!process || !outBase || !size) return NTLSTATUS_INVALID_PARAMETER; *outBase = (void*)0; size_t payloadSize = (size + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); if (payloadSize < size) return NTLSTATUS_NO_MEMORY; size_t guardLow = (vadFlags & MM_VAD_GUARD_LOW) ? PAGE_SIZE : 0; size_t guardHigh = (vadFlags & MM_VAD_GUARD_HIGH) ? PAGE_SIZE : 0; size_t reservationSize = guardLow + payloadSize + guardHigh; if (reservationSize < payloadSize) return NTLSTATUS_NO_MEMORY; if (alignment < PAGE_SIZE) alignment = PAGE_SIZE; if (alignment & (alignment - 1)) return NTLSTATUS_INVALID_PARAMETER; size_t pageCount = payloadSize / PAGE_SIZE; if (!pageCount) return NTLSTATUS_INVALID_PARAMETER; if (pageFlags && !(pageFlags & PAGE_USER)) pageFlags |= PAGE_USER; MM_VAD* vad = (MM_VAD*)MmAllocatePoolTag(sizeof(MM_VAD), MM_POOL_TAG('v', 'a', 'd', ' ')); if (!vad) return NTLSTATUS_NO_MEMORY; memset(vad, 0, sizeof(*vad)); MM_VAD_PAGE* pages = (MM_VAD_PAGE*)MmAllocatePoolTag(pageCount * sizeof(MM_VAD_PAGE), MM_POOL_TAG('v', 'p', 'g', 's')); if (!pages) { MmFreePool(vad); return NTLSTATUS_NO_MEMORY; } memset(pages, 0, pageCount * sizeof(MM_VAD_PAGE)); LIST_INIT(&vad->ProcessLink); LIST_INIT(&vad->GlobalLink); uintptr_t reservationBase = 0; uintptr_t globalFlags = KeAcquireSpinlock(&gVadLock); KSTATUS s = KiFindVadRange(reservationSize, alignment, &reservationBase); if (KSUCCESS(s)) { vad->Owner = process; vad->ReservationBase = reservationBase; vad->ReservationSize = reservationSize; vad->Base = reservationBase + guardLow; vad->Size = size; vad->PayloadSize = payloadSize; vad->PageCount = pageCount; vad->CommittedPages = 0; vad->PageFlags = pageFlags; vad->Type = type; vad->Flags = vadFlags; vad->Pages = pages; ListInsertTail(&gVadList, &vad->GlobalLink); } KeReleaseSpinlock(&gVadLock, globalFlags); if (!KSUCCESS(s)) { MmFreePool(pages); MmFreePool(vad); return s; } uintptr_t procFlags = KeAcquireSpinlock(&process->VadLock); ListInsertTail(&process->VadList, &vad->ProcessLink); KeReleaseSpinlock(&process->VadLock, procFlags); *outBase = (void*)vad->Base; return NTLSTATUS_SUCCESS; } // Commits pages in an existing VAD without allocating PFNs until access. KSTATUS MmVadCommit(PROCESS* process, void* base, size_t size, uint64_t pageFlags, uint32_t commitFlags) { KeAssertIrql(PASSIVE_LEVEL); if (!process || !base || !size) return NTLSTATUS_INVALID_PARAMETER; uintptr_t start = (uintptr_t)base; if (start > (uintptr_t)-1 - (size - 1)) return NTLSTATUS_NO_MEMORY; uintptr_t alignedStart = start & PAGE_MASK; uintptr_t alignedEnd = (start + size + PAGE_SIZE - 1) & PAGE_MASK; if (alignedEnd <= alignedStart) return NTLSTATUS_NO_MEMORY; uintptr_t procFlags = KeAcquireSpinlock(&process->VadLock); MM_VAD* vad = KiFindReservedVadContainingLocked(process, alignedStart, alignedEnd - alignedStart); KeReleaseSpinlock(&process->VadLock, procFlags); if (!vad) { DbgPrint("MM: commit failed: pid=%u range=%016llx-%016llx not inside reserved VAD\n", process->ProcessId, (unsigned long long)alignedStart, (unsigned long long)alignedEnd); return NTLSTATUS_NOT_FOUND; } uint64_t flags = pageFlags ? pageFlags : vad->PageFlags; if (flags) flags |= PAGE_PRESENT | PAGE_USER; size_t first = KiVadPageIndex(vad, alignedStart); size_t count = KiVadRangePageCount(alignedStart, alignedEnd); if (first > vad->PageCount || count > vad->PageCount - first) return NTLSTATUS_ACCESS_DENIED; uint8_t* newlyCommitted = (uint8_t*)MmAllocatePool(count ? count : 1); if (!newlyCommitted) return NTLSTATUS_NO_MEMORY; memset(newlyCommitted, 0, count ? count : 1); KSTATUS s = NTLSTATUS_SUCCESS; for (size_t i = 0; i < count; ++i) { size_t pageIndex = first + i; MM_VAD_PAGE* page = &vad->Pages[pageIndex]; if (page->State == MmVadPageCommitted) { continue; } page->State = MmVadPageCommitted; page->PageFlags = flags; page->Frame = 0; page->Resident = 0; page->Flags = 0; page->SwapSlot = 0xFFFFFFFFu; vad->CommittedPages++; newlyCommitted[i] = 1; } if ((commitFlags & MM_VAD_PREFETCH) && flags) { s = KiVadEnsureResidentRange(process, alignedStart, alignedEnd - alignedStart, flags); } if (!KSUCCESS(s)) { for (size_t i = 0; i < count; ++i) { if (newlyCommitted[i]) KiVadReleasePage(vad, first + i); } } MmFreePool(newlyCommitted); return s; } // Reserves and commits a VAD in one step. KSTATUS MmVadCreateCommitted(PROCESS* process, size_t size, size_t alignment, uint64_t pageFlags, uint32_t vadFlags, MM_VAD_TYPE type, void** outBase) { if (!outBase) return NTLSTATUS_INVALID_PARAMETER; *outBase = (void*)0; void* base = (void*)0; KSTATUS s = MmVadReserve(process, size, alignment, pageFlags, vadFlags, type, &base); if (!KSUCCESS(s)) return s; s = MmVadCommit(process, base, size, pageFlags, vadFlags); if (!KSUCCESS(s)) { MmVadDestroy(process, base); return s; } *outBase = base; return NTLSTATUS_SUCCESS; } // Creates a demand-paged file-backed user mapping. The VAD owns a reference to // the FILE_OBJECT so the caller may close the original file handle immediately // after a view is established. Pages are read from the file on first fault and // then behave like ordinary private user pages: if memory pressure trims them // after they are resident, the swap path preserves later private modifications. KSTATUS MmVadCreateFileMapping(PROCESS* process, FILE_OBJECT* file, uint64_t fileOffset, size_t size, uint64_t pageFlags, uint32_t vadFlags, void** outBase) { KeAssertIrql(PASSIVE_LEVEL); if (!process || !file || !size || !outBase) return NTLSTATUS_INVALID_PARAMETER; *outBase = (void*)0; if (fileOffset & (PAGE_SIZE - 1)) return NTLSTATUS_INVALID_PARAMETER; size_t alignedSize = (size + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); if (!alignedSize || alignedSize < size) return NTLSTATUS_NO_MEMORY; if (pageFlags && !(pageFlags & PAGE_USER)) pageFlags |= PAGE_USER; pageFlags |= PAGE_PRESENT | PAGE_USER; void* base = (void*)0; KSTATUS s = MmVadReserve(process, alignedSize, PAGE_SIZE, pageFlags, vadFlags, MmVadMappedFile, &base); if (!KSUCCESS(s)) return s; uintptr_t procFlags = KeAcquireSpinlock(&process->VadLock); MM_VAD* vad = KiFindVadByBaseLocked(process, base); KeReleaseSpinlock(&process->VadLock, procFlags); if (!vad) { (void)MmVadDestroy(process, base); return NTLSTATUS_NOT_FOUND; } ObReferenceObject(&file->Header); vad->FileObject = file; vad->FileOffset = fileOffset; vad->FileLength = size; vad->FileMappingFlags = vadFlags; for (size_t i = 0; i < vad->PageCount; ++i) { MM_VAD_PAGE* page = &vad->Pages[i]; page->State = MmVadPageCommitted; page->PageFlags = pageFlags; page->Frame = 0; page->Resident = 0; page->Flags = MM_VAD_PAGE_FILE_BACKED; if (vadFlags & MM_VAD_SHARED_WRITE) page->Flags |= MM_VAD_PAGE_FILE_SHARED_WRITE | MM_VAD_PAGE_FILE_DIRTY_TRACK; page->SwapSlot = 0xFFFFFFFFu; vad->CommittedPages++; } *outBase = base; return NTLSTATUS_SUCCESS; } // Creates an image-backed VAD whose pages fault from a shared image section object. KSTATUS MmVadCreateImageSectionMapping(PROCESS* process, IMAGE_SECTION_OBJECT* section, uint64_t pageFlags, uint32_t vadFlags, void** outBase) { KeAssertIrql(PASSIVE_LEVEL); if (!process || !section || !outBase) return NTLSTATUS_INVALID_PARAMETER; *outBase = (void*)0; const IMAGE_SECTION_METADATA* metadata = LdrImageSectionMetadata(section); if (!metadata || !metadata->ImageSize || !metadata->PageCount) return NTLSTATUS_INVALID_PARAMETER; pageFlags |= PAGE_PRESENT | PAGE_USER; void* base = (void*)0; KSTATUS s = MmVadReserve(process, metadata->ImageSize, PAGE_SIZE, pageFlags, vadFlags, MmVadImage, &base); if (!KSUCCESS(s)) return s; uintptr_t procFlags = KeAcquireSpinlock(&process->VadLock); MM_VAD* vad = KiFindVadByBaseLocked(process, base); KeReleaseSpinlock(&process->VadLock, procFlags); if (!vad) { (void)MmVadDestroy(process, base); return NTLSTATUS_NOT_FOUND; } LdrReferenceImageSection(section); vad->ImageSection = section; for (size_t i = 0; i < vad->PageCount; ++i) { MM_VAD_PAGE* page = &vad->Pages[i]; page->State = MmVadPageCommitted; page->PageFlags = pageFlags; page->Frame = 0; page->Resident = 0; page->Flags = MM_VAD_PAGE_IMAGE_BACKED; page->SwapSlot = 0xFFFFFFFFu; vad->CommittedPages++; } *outBase = base; return NTLSTATUS_SUCCESS; } // Materializes committed VAD pages before the kernel or a recovered user fault touches them. KSTATUS MmVadEnsureResident(PROCESS* process, void* base, size_t size, uint64_t accessFlags) { if (!process || !base || !size) return NTLSTATUS_INVALID_PARAMETER; return KiVadEnsureResidentRange(process, (uintptr_t)base, size, accessFlags | PAGE_USER); } // Creates a private copy-on-write view of a resident committed VAD range. KSTATUS MmVadCreateCowView(PROCESS* sourceProcess, void* sourceBase, size_t size, PROCESS* targetProcess, void** outBase) { KeAssertIrql(PASSIVE_LEVEL); if (!sourceProcess || !sourceBase || !targetProcess || !outBase || !size) return NTLSTATUS_INVALID_PARAMETER; *outBase = (void*)0; uintptr_t start = (uintptr_t)sourceBase; if (start & (PAGE_SIZE - 1)) return NTLSTATUS_INVALID_PARAMETER; size_t alignedSize = (size + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); if (!alignedSize || alignedSize < size) return NTLSTATUS_NO_MEMORY; KSTATUS s = KiVadEnsureResidentRange(sourceProcess, start, alignedSize, PAGE_USER); if (!KSUCCESS(s)) return s; uintptr_t srcFlags = KeAcquireSpinlock(&sourceProcess->VadLock); MM_VAD* sourceVad = KiFindReservedVadContainingLocked(sourceProcess, start, alignedSize); KeReleaseSpinlock(&sourceProcess->VadLock, srcFlags); if (!sourceVad || !KiVadRangeCommitted(sourceVad, start, start + alignedSize)) return NTLSTATUS_NOT_FOUND; void* targetBase = (void*)0; s = MmVadReserve(targetProcess, alignedSize, PAGE_SIZE, sourceVad->PageFlags, sourceVad->Flags, sourceVad->Type, &targetBase); if (!KSUCCESS(s)) return s; uintptr_t dstFlags = KeAcquireSpinlock(&targetProcess->VadLock); MM_VAD* targetVad = KiFindVadByBaseLocked(targetProcess, targetBase); KeReleaseSpinlock(&targetProcess->VadLock, dstFlags); if (!targetVad) { (void)MmVadDestroy(targetProcess, targetBase); return NTLSTATUS_NOT_FOUND; } uintptr_t dst = (uintptr_t)targetBase; size_t first = KiVadPageIndex(sourceVad, start); size_t count = alignedSize / PAGE_SIZE; for (size_t i = 0; i < count; ++i) { MM_VAD_PAGE* srcPage = &sourceVad->Pages[first + i]; if (srcPage->State != MmVadPageCommitted || !srcPage->Resident || !srcPage->Frame) { s = NTLSTATUS_NOT_FOUND; break; } s = MmPfnRetain(srcPage->Frame); if (!KSUCCESS(s)) break; MM_VAD_PAGE* dstPage = &targetVad->Pages[i]; dstPage->State = MmVadPageCommitted; dstPage->PageFlags = srcPage->PageFlags; dstPage->Frame = srcPage->Frame; dstPage->Resident = 1; dstPage->Flags = MM_VAD_PAGE_SHARED_FRAME | ((srcPage->PageFlags & PAGE_WRITE) ? MM_VAD_PAGE_COW : 0); dstPage->SwapSlot = 0xFFFFFFFFu; targetVad->CommittedPages++; if (dstPage->Flags) { srcPage->Flags |= MM_VAD_PAGE_COW | MM_VAD_PAGE_SHARED_FRAME; if (srcPage->Resident && srcPage->PageFlags) (void)MmPteProtectRange(sourceVad->Base + (first + i) * PAGE_SIZE, PAGE_SIZE, KiVadHardwarePageFlags(srcPage)); } s = KiVadMapPageTracked(dst + i * PAGE_SIZE, dstPage->Frame, KiVadHardwarePageFlags(dstPage), MmPteMapFailIfPresent); if (!KSUCCESS(s)) break; gVadStats.CowPagesShared++; } if (!KSUCCESS(s)) { (void)MmVadDestroy(targetProcess, targetBase); return s; } gVadStats.CowViewsCreated++; *outBase = targetBase; return NTLSTATUS_SUCCESS; } // Creates a COW view from loader-owned template frames. KSTATUS MmVadCreateCowViewFromFrames(PROCESS* targetProcess, const PHYSICAL_ADDRESS* frames, size_t pageCount, size_t payloadSize, uint64_t pageFlags, uint32_t vadFlags, MM_VAD_TYPE type, void** outBase) { KeAssertIrql(PASSIVE_LEVEL); if (!targetProcess || !frames || !pageCount || !payloadSize || !outBase) return NTLSTATUS_INVALID_PARAMETER; *outBase = (void*)0; size_t alignedSize = (payloadSize + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); if (!alignedSize || alignedSize / PAGE_SIZE > pageCount) return NTLSTATUS_INVALID_PARAMETER; void* targetBase = (void*)0; KSTATUS s = MmVadReserve(targetProcess, alignedSize, PAGE_SIZE, pageFlags, vadFlags, type, &targetBase); if (!KSUCCESS(s)) return s; uintptr_t dstFlags = KeAcquireSpinlock(&targetProcess->VadLock); MM_VAD* targetVad = KiFindVadByBaseLocked(targetProcess, targetBase); KeReleaseSpinlock(&targetProcess->VadLock, dstFlags); if (!targetVad) { (void)MmVadDestroy(targetProcess, targetBase); return NTLSTATUS_NOT_FOUND; } uintptr_t dst = (uintptr_t)targetBase; size_t count = alignedSize / PAGE_SIZE; for (size_t i = 0; i < count; ++i) { MM_VAD_PAGE* page = &targetVad->Pages[i]; page->State = MmVadPageCommitted; page->PageFlags = pageFlags | PAGE_PRESENT | PAGE_USER; page->Frame = frames[i]; page->Resident = frames[i] ? 1 : 0; page->Flags = (frames[i] ? MM_VAD_PAGE_SHARED_FRAME : 0) | ((page->PageFlags & PAGE_WRITE) ? MM_VAD_PAGE_COW : 0); page->SwapSlot = 0xFFFFFFFFu; targetVad->CommittedPages++; if (!frames[i]) continue; s = MmPfnRetain(frames[i]); if (!KSUCCESS(s)) break; s = KiVadMapPageTracked(dst + i * PAGE_SIZE, page->Frame, KiVadHardwarePageFlags(page), MmPteMapFailIfPresent); if (!KSUCCESS(s)) { (void)MmPfnRelease(frames[i]); break; } gVadStats.CowPagesShared++; } if (!KSUCCESS(s)) { (void)MmVadDestroy(targetProcess, targetBase); return s; } gVadStats.CowViewsCreated++; *outBase = targetBase; return NTLSTATUS_SUCCESS; } KSTATUS MmVadCreateSharedViewFromFrames(PROCESS* targetProcess, const PHYSICAL_ADDRESS* frames, size_t pageCount, size_t payloadSize, uint64_t pageFlags, uint32_t vadFlags, MM_VAD_TYPE type, void** outBase) { KeAssertIrql(PASSIVE_LEVEL); if (!targetProcess || !frames || !pageCount || !payloadSize || !outBase) return NTLSTATUS_INVALID_PARAMETER; *outBase = (void*)0; size_t alignedSize = (payloadSize + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); if (!alignedSize || alignedSize / PAGE_SIZE > pageCount) return NTLSTATUS_INVALID_PARAMETER; pageFlags |= PAGE_PRESENT | PAGE_USER; void* targetBase = (void*)0; KSTATUS s = MmVadReserve(targetProcess, alignedSize, PAGE_SIZE, pageFlags, vadFlags, type, &targetBase); if (!KSUCCESS(s)) return s; uintptr_t dstFlags = KeAcquireSpinlock(&targetProcess->VadLock); MM_VAD* targetVad = KiFindVadByBaseLocked(targetProcess, targetBase); KeReleaseSpinlock(&targetProcess->VadLock, dstFlags); if (!targetVad) { (void)MmVadDestroy(targetProcess, targetBase); return NTLSTATUS_NOT_FOUND; } uintptr_t dst = (uintptr_t)targetBase; size_t count = alignedSize / PAGE_SIZE; for (size_t i = 0; i < count; ++i) { if (!frames[i]) { s = NTLSTATUS_INVALID_PARAMETER; break; } s = MmPfnRetain(frames[i]); if (!KSUCCESS(s)) break; MM_VAD_PAGE* page = &targetVad->Pages[i]; page->State = MmVadPageCommitted; page->PageFlags = pageFlags; page->Frame = frames[i]; page->Resident = 1; page->Flags = MM_VAD_PAGE_SHARED_FRAME; page->SwapSlot = 0xFFFFFFFFu; targetVad->CommittedPages++; s = KiVadMapPageTracked(dst + i * PAGE_SIZE, page->Frame, KiVadHardwarePageFlags(page), MmPteMapFailIfPresent); if (!KSUCCESS(s)) { page->PageFlags = 0; page->Frame = 0; page->Resident = 0; page->Flags = 0; page->State = MmVadPageFree; page->SwapSlot = 0xFFFFFFFFu; if (targetVad->CommittedPages) targetVad->CommittedPages--; (void)MmPfnRelease(frames[i]); break; } gVadStats.CowPagesShared++; } if (!KSUCCESS(s)) { (void)MmVadDestroy(targetProcess, targetBase); return s; } gVadStats.CowViewsCreated++; *outBase = targetBase; return NTLSTATUS_SUCCESS; } // Returns whether a resident file-backed page can be discarded instead of // swapped. This is safe only while the page is still clean by construction: // read-only mappings cannot have been modified, and the backing file can // repopulate the page on the next fault. Writable/private file mappings still // fall back to the swap path because this VMM does not yet track dirty bits. static bool KiVadPageCanDiscardCleanFile(const MM_VAD* vad, const MM_VAD_PAGE* page) { if (!vad || !page || vad->Type != MmVadMappedFile) return false; if (!vad->FileObject || page->State != MmVadPageCommitted) return false; if (!page->Resident || !page->Frame || !page->PageFlags) return false; if ((page->Flags & MM_VAD_PAGE_FILE_BACKED) == 0) return false; if (page->PageFlags & PAGE_WRITE) return false; if (page->Flags & (MM_VAD_PAGE_COW | MM_VAD_PAGE_ZERO_PAGE | MM_VAD_PAGE_SHARED_FRAME | MM_VAD_PAGE_SWAPPED | MM_VAD_PAGE_PAGING)) return false; return true; } static bool KiVadPageCanDiscardCleanFileAfterPagingClaim(const MM_VAD* vad, const MM_VAD_PAGE* page) { if (!vad || !page || vad->Type != MmVadMappedFile) return false; if (!vad->FileObject || page->State != MmVadPageCommitted) return false; if (!page->Resident || !page->Frame || !page->PageFlags) return false; if ((page->Flags & MM_VAD_PAGE_FILE_BACKED) == 0) return false; if (page->PageFlags & PAGE_WRITE) return false; if (page->Flags & (MM_VAD_PAGE_COW | MM_VAD_PAGE_ZERO_PAGE | MM_VAD_PAGE_SHARED_FRAME | MM_VAD_PAGE_SWAPPED)) return false; return (page->Flags & MM_VAD_PAGE_PAGING) != 0; } // Returns whether a resident VAD page is safe to move to swap. Clean read-only // file-backed pages are intentionally excluded; the pressure trimmer discards // them and reloads them from the file instead of wasting swap slots. static bool KiVadPageCanSwap(const MM_VAD_PAGE* page) { if (!page || page->State != MmVadPageCommitted) return false; if (!page->Resident || !page->Frame || !page->PageFlags) return false; if ((page->Flags & MM_VAD_PAGE_FILE_BACKED) && ((page->PageFlags & PAGE_WRITE) == 0)) return false; if (page->Flags & MM_VAD_PAGE_FILE_SHARED_WRITE) return false; if (page->Flags & (MM_VAD_PAGE_COW | MM_VAD_PAGE_ZERO_PAGE | MM_VAD_PAGE_SHARED_FRAME | MM_VAD_PAGE_SWAPPED | MM_VAD_PAGE_PAGING)) return false; return true; } static bool KiVadPageCanSwapAfterPagingClaim(const MM_VAD_PAGE* page) { if (!page || page->State != MmVadPageCommitted) return false; if (!page->Resident || !page->Frame || !page->PageFlags) return false; if ((page->Flags & MM_VAD_PAGE_FILE_BACKED) && ((page->PageFlags & PAGE_WRITE) == 0)) return false; if (page->Flags & MM_VAD_PAGE_FILE_SHARED_WRITE) return false; if (page->Flags & (MM_VAD_PAGE_COW | MM_VAD_PAGE_ZERO_PAGE | MM_VAD_PAGE_SHARED_FRAME | MM_VAD_PAGE_SWAPPED)) return false; if ((page->Flags & MM_VAD_PAGE_PAGING) == 0) return false; return true; } // Discards one resident clean file-backed page. No swap slot is allocated; the // VAD keeps its FILE_BACKED state and refaults the page from the file later. static KSTATUS KiVadDiscardCleanFilePage(MM_VAD* vad, size_t pageIndex, void** baseOut) { if (baseOut) *baseOut = (void*)0; if (!vad || pageIndex >= vad->PageCount) return NTLSTATUS_INVALID_PARAMETER; MM_VAD_PAGE* page = &vad->Pages[pageIndex]; if (!KiVadPageCanDiscardCleanFile(vad, page)) return NTLSTATUS_NOT_FOUND; PHYSICAL_ADDRESS frame = page->Frame; MM_PFN* pfn = MmPfnGet(frame); if (!pfn || pfn->PinCount) return NTLSTATUS_NOT_FOUND; uintptr_t va = vad->Base + pageIndex * PAGE_SIZE; // Second chance for hot mapped-file pages. Dropping the active video/WAD // working set is much worse than swapping cold anonymous pages: it causes // repeated file reads, page-table churn, and global TLB shootdowns. Clear // the hardware accessed bit and skip the page once; if it stays cold until // a later pressure scan, it may be discarded then. if (MmPteTestAndClearAccessed(va)) return NTLSTATUS_NOT_FOUND; if (!KiVadTryBeginPaging(page)) return NTLSTATUS_NOT_FOUND; if (!KiVadPageCanDiscardCleanFileAfterPagingClaim(vad, page) || page->Frame != frame) { KiVadEndPaging(page); return NTLSTATUS_NOT_FOUND; } KSTATUS s = MmPfnPin(frame); if (!KSUCCESS(s)) { KiVadEndPaging(page); return s; } s = KiVadUnmapPageTracked(va, frame); if (!KSUCCESS(s)) { KiVadEndPaging(page); (void)MmPfnUnpin(frame); return s; } page->Frame = 0; page->Resident = 0; page->Flags &= ~(MM_VAD_PAGE_SWAPPED | MM_VAD_PAGE_ZERO_PAGE | MM_VAD_PAGE_SHARED_FRAME | MM_VAD_PAGE_COW); page->Flags |= MM_VAD_PAGE_FILE_BACKED; page->SwapSlot = 0xFFFFFFFFu; KiVadEndPaging(page); (void)MmPfnUnpin(frame); (void)MmPfnRelease(frame); if (vad->FileObject) { uint64_t fileOffset = vad->FileOffset + (uint64_t)pageIndex * (uint64_t)PAGE_SIZE; MmFileCacheInvalidate(vad->FileObject, fileOffset, PAGE_SIZE); } gVadStats.FileBackedDiscards++; if (baseOut) *baseOut = (void*)va; return NTLSTATUS_SUCCESS; } // Moves one private resident VAD page to the swap backend. static KSTATUS KiVadTrimPageToSwap(MM_VAD* vad, size_t pageIndex, void** baseOut) { if (baseOut) *baseOut = (void*)0; if (!vad || pageIndex >= vad->PageCount) return NTLSTATUS_INVALID_PARAMETER; MM_VAD_PAGE* page = &vad->Pages[pageIndex]; if (!KiVadPageCanSwap(page)) return NTLSTATUS_NOT_FOUND; PHYSICAL_ADDRESS frame = page->Frame; MM_PFN* pfn = MmPfnGet(frame); if (!pfn || pfn->RefCount != 1 || pfn->PinCount) return NTLSTATUS_NOT_FOUND; // Claim the transient paging bit atomically before pinning/removing the // user PTE. This serializes page-out against another page-out and against // a concurrent fault on the same VAD page without holding a spinlock across // pagefile I/O. if (!KiVadTryBeginPaging(page)) return NTLSTATUS_NOT_FOUND; if (!KiVadPageCanSwapAfterPagingClaim(page) || page->Frame != frame) { KiVadEndPaging(page); return NTLSTATUS_NOT_FOUND; } KSTATUS s = MmPfnPin(frame); if (!KSUCCESS(s)) { KiVadEndPaging(page); return s; } uintptr_t va = vad->Base + pageIndex * PAGE_SIZE; s = KiVadUnmapPageTracked(va, frame); if (!KSUCCESS(s)) { KiVadEndPaging(page); (void)MmPfnUnpin(frame); return s; } page->Resident = 0; uint32_t slot = 0; s = MmSwapPageOut(frame, &slot); if (!KSUCCESS(s)) { page->Resident = 1; KSTATUS mapStatus = KiVadMapPageTracked(va, frame, KiVadHardwarePageFlags(page), MmPteMapFailIfPresent); KiVadEndPaging(page); (void)MmPfnUnpin(frame); return KSUCCESS(mapStatus) ? s : mapStatus; } page->Frame = 0; page->Flags |= MM_VAD_PAGE_SWAPPED; page->SwapSlot = slot; KiVadEndPaging(page); (void)MmPfnUnpin(frame); (void)MmPfnRelease(frame); gVadStats.SwapPageOuts++; if (baseOut) *baseOut = (void*)va; return NTLSTATUS_SUCCESS; } // Finds one private resident page in a process and swaps it out. static KSTATUS KiVadTrimOnePageInProcess(PROCESS* process, void** baseOut) { if (baseOut) *baseOut = (void*)0; if (!process) return NTLSTATUS_INVALID_PARAMETER; for (LIST_ENTRY* node = process->VadList.Fwd; node != &process->VadList; node = node->Fwd) { MM_VAD* vad = CONTAINING_RECORD(node, MM_VAD, ProcessLink); for (size_t i = 0; i < vad->PageCount; ++i) { KSTATUS s = KiVadTrimPageToSwap(vad, i, baseOut); if (KSUCCESS(s)) return NTLSTATUS_SUCCESS; } } return NTLSTATUS_NOT_FOUND; } // Moves one private resident user page to the swap backend for self-tests. KSTATUS MmVadTrimOnePageForTest(PROCESS* process, void** baseOut) { KeAssertIrql(PASSIVE_LEVEL); if (!MmSwapIsEnabled()) return NTLSTATUS_NOT_SUPPORTED; return KiVadTrimOnePageInProcess(process, baseOut); } // Reclaims one resident user page from any process during allocation pressure. // Prefer clean file-backed pages because they can be dropped and re-read without // consuming swap. Fall back to anonymous/private swap only when no such page is // available. KSTATUS MmVadTrimOnePageForPressure(void) { if (!gVadListReady) return NTLSTATUS_NOT_SUPPORTED; KSTATUS cacheDrop = MmFileCacheDropOnePageForPressure(); if (KSUCCESS(cacheDrop)) return NTLSTATUS_SUCCESS; for (LIST_ENTRY* node = gVadList.Fwd; node != &gVadList; node = node->Fwd) { MM_VAD* vad = CONTAINING_RECORD(node, MM_VAD, GlobalLink); for (size_t i = 0; i < vad->PageCount; ++i) { KSTATUS s = KiVadDiscardCleanFilePage(vad, i, (void**)0); if (KSUCCESS(s)) return NTLSTATUS_SUCCESS; } } if (!MmSwapIsEnabled()) return NTLSTATUS_NOT_SUPPORTED; for (LIST_ENTRY* node = gVadList.Fwd; node != &gVadList; node = node->Fwd) { MM_VAD* vad = CONTAINING_RECORD(node, MM_VAD, GlobalLink); for (size_t i = 0; i < vad->PageCount; ++i) { KSTATUS s = KiVadTrimPageToSwap(vad, i, (void**)0); if (KSUCCESS(s)) return NTLSTATUS_SUCCESS; } } return NTLSTATUS_NOT_FOUND; } KSTATUS MmVadSyncFileMappings(FILE_OBJECT* file) { if (!file) return NTLSTATUS_INVALID_PARAMETER; uintptr_t flags = KeAcquireSpinlock(&gVadLock); for (LIST_ENTRY* node = gVadList.Fwd; node != &gVadList; node = node->Fwd) { MM_VAD* vad = CONTAINING_RECORD(node, MM_VAD, GlobalLink); if (vad->Type != MmVadMappedFile || vad->FileObject != file || !vad->Pages) continue; for (size_t i = 0; i < vad->PageCount; ++i) { MM_VAD_PAGE* page = &vad->Pages[i]; if ((page->Flags & MM_VAD_PAGE_FILE_SHARED_WRITE) == 0) continue; if (!page->Resident || !page->Frame || !page->PageFlags) continue; uintptr_t va = vad->Base + i * PAGE_SIZE; int wasDirty = MmPteTestAndClearDirty(va); bool wasWriteEnabled = (page->Flags & MM_VAD_PAGE_FILE_DIRTY_TRACK) == 0; if (wasDirty || wasWriteEnabled) (void)KiVadMarkSharedFilePageDirty(vad, i, 0); if (wasWriteEnabled) { page->Flags |= MM_VAD_PAGE_FILE_DIRTY_TRACK; (void)MmPteProtectRange(va, PAGE_SIZE, KiVadHardwarePageFlags(page)); } } } KeReleaseSpinlock(&gVadLock, flags); return NTLSTATUS_SUCCESS; } // Resolves a user-mode page fault against committed VAD metadata. // Resolves a user-mode page fault against committed VAD metadata. KSTATUS MmVadResolveUserPageFault(PROCESS* process, uintptr_t faultAddress, uint64_t errorCode) { gVadStats.ResolveFaultCalls++; if (!process) return NTLSTATUS_ACCESS_DENIED; if (!KiUserRangeValid(faultAddress, 1)) { gVadStats.NoVadFaults++; gVadStats.FailedFaults++; return NTLSTATUS_ACCESS_DENIED; } uintptr_t va = faultAddress & PAGE_MASK; uintptr_t procFlags = KeAcquireSpinlock(&process->VadLock); MM_VAD* vad = KiFindReservedVadContainingLocked(process, va, PAGE_SIZE); KeReleaseSpinlock(&process->VadLock, procFlags); if (!vad) { gVadStats.NoVadFaults++; gVadStats.FailedFaults++; return NTLSTATUS_NOT_FOUND; } size_t index = KiVadPageIndex(vad, va); if (index >= vad->PageCount || vad->Pages[index].State != MmVadPageCommitted) { gVadStats.UncommittedFaults++; gVadStats.FailedFaults++; return NTLSTATUS_NOT_FOUND; } if (!vad->Pages[index].PageFlags) { gVadStats.NoAccessFaults++; gVadStats.FailedFaults++; return NTLSTATUS_ACCESS_DENIED; } int presentFault = (errorCode & 0x1) ? 1 : 0; int wantWrite = (errorCode & 0x2) ? 1 : 0; int wantExecute = (errorCode & 0x10) ? 1 : 0; if (presentFault) { if (wantWrite && (vad->Pages[index].Flags & MM_VAD_PAGE_FILE_SHARED_WRITE) && (vad->Pages[index].Flags & MM_VAD_PAGE_FILE_DIRTY_TRACK)) { KSTATUS dirty = KiVadMarkSharedFilePageDirty(vad, index, 0); if (KSUCCESS(dirty)) { vad->Pages[index].Flags &= ~MM_VAD_PAGE_FILE_DIRTY_TRACK; KSTATUS ps = MmPteProtectRange(va, PAGE_SIZE, KiVadHardwarePageFlags(&vad->Pages[index])); if (KSUCCESS(ps)) { gVadStats.FileBackedDirtyFaults++; gVadStats.ResolvedFaults++; return NTLSTATUS_SUCCESS; } dirty = ps; } gVadStats.ProtectionFaults++; gVadStats.FailedFaults++; return dirty; } if (wantWrite && (vad->Pages[index].Flags & MM_VAD_PAGE_COW)) { KSTATUS cow = KiVadBreakCopyOnWrite(vad, index); if (KSUCCESS(cow)) { gVadStats.CowFaults++; gVadStats.ResolvedFaults++; return NTLSTATUS_SUCCESS; } } gVadStats.ProtectionFaults++; gVadStats.FailedFaults++; return NTLSTATUS_ACCESS_DENIED; } KSTATUS s = KiVadEnsurePageResident(vad, index, wantWrite, wantExecute); if (!KSUCCESS(s)) { gVadStats.FailedFaults++; return s; } gVadStats.ResolvedFaults++; return NTLSTATUS_SUCCESS; } // Resolves a page fault by looking up the owning VAD in the global user arena. // The single address space intentionally allows the kernel and other processes // to touch any lower-half mapping, so fault ownership is derived from the VAD // that owns the virtual address rather than from the currently running process. KSTATUS MmVadResolvePageFaultByAddress(uintptr_t faultAddress, uint64_t errorCode) { if (!KiUserRangeValid(faultAddress, 1)) return NTLSTATUS_ACCESS_DENIED; if (!gVadListReady) return NTLSTATUS_NOT_SUPPORTED; uintptr_t va = faultAddress & PAGE_MASK; PROCESS* owner = (PROCESS*)0; uintptr_t flags = KeAcquireSpinlock(&gVadLock); for (LIST_ENTRY* node = gVadList.Fwd; node != &gVadList; node = node->Fwd) { MM_VAD* vad = CONTAINING_RECORD(node, MM_VAD, GlobalLink); uintptr_t end = vad->Base + vad->PayloadSize; if (va >= vad->Base && va < end) { owner = vad->Owner; break; } } KeReleaseSpinlock(&gVadLock, flags); if (!owner) return NTLSTATUS_NOT_FOUND; return MmVadResolveUserPageFault(owner, faultAddress, errorCode); } // Changes page protections on a committed VAD range. KSTATUS MmVadProtect(PROCESS* process, void* base, size_t size, uint64_t pageFlags) { KeAssertIrql(PASSIVE_LEVEL); if (!process || !base || !size) return NTLSTATUS_INVALID_PARAMETER; uintptr_t start = (uintptr_t)base; if (start > (uintptr_t)-1 - (size - 1)) return NTLSTATUS_NO_MEMORY; uintptr_t alignedStart = start & PAGE_MASK; uintptr_t alignedEnd = (start + size + PAGE_SIZE - 1) & PAGE_MASK; if (alignedEnd <= alignedStart) return NTLSTATUS_NO_MEMORY; uintptr_t procFlags = KeAcquireSpinlock(&process->VadLock); MM_VAD* vad = KiFindReservedVadContainingLocked(process, alignedStart, alignedEnd - alignedStart); KeReleaseSpinlock(&process->VadLock, procFlags); if (!vad || !KiVadRangeCommitted(vad, alignedStart, alignedEnd)) { DbgPrint("MM: protect VAD failed: pid=%u range=%016llx-%016llx not committed\n", process->ProcessId, (unsigned long long)alignedStart, (unsigned long long)alignedEnd); return NTLSTATUS_NOT_FOUND; } if (vad->LockCount) return NTLSTATUS_ACCESS_DENIED; uint64_t finalFlags = pageFlags ? (pageFlags | PAGE_PRESENT | PAGE_USER) : 0; size_t first = KiVadPageIndex(vad, alignedStart); size_t count = KiVadRangePageCount(alignedStart, alignedEnd); for (size_t i = 0; i < count; ++i) { size_t pageIndex = first + i; MM_VAD_PAGE* page = &vad->Pages[pageIndex]; uintptr_t va = vad->Base + pageIndex * PAGE_SIZE; if (!finalFlags) { if (page->Resident && page->PageFlags) (void)KiVadUnmapPageTracked(va, page->Frame); page->PageFlags = 0; continue; } uint64_t oldFlags = page->PageFlags; page->PageFlags = finalFlags; if ((finalFlags & PAGE_WRITE) == 0 && (page->Flags & (MM_VAD_PAGE_SHARED_FRAME | MM_VAD_PAGE_ZERO_PAGE | MM_VAD_PAGE_IMAGE_BACKED)) == 0) { page->Flags &= ~MM_VAD_PAGE_COW; } if (page->Resident) { uint64_t hardwareFlags = KiVadHardwarePageFlags(page); KSTATUS s = oldFlags ? MmPteProtectRange(va, PAGE_SIZE, hardwareFlags) : KiVadMapPageTracked(va, page->Frame, hardwareFlags, MmPteMapFailIfPresent); if (!KSUCCESS(s)) return s; } } return NTLSTATUS_SUCCESS; } // Destroys a VAD and releases all resident payload pages. KSTATUS MmVadDestroy(PROCESS* process, void* base) { KeAssertIrql(PASSIVE_LEVEL); if (!process || !base) return NTLSTATUS_INVALID_PARAMETER; uintptr_t procFlags = KeAcquireSpinlock(&process->VadLock); MM_VAD* vad = KiFindVadByBaseLocked(process, base); if (!vad) { KeReleaseSpinlock(&process->VadLock, procFlags); return NTLSTATUS_NOT_FOUND; } if (vad->LockCount) { KeReleaseSpinlock(&process->VadLock, procFlags); return NTLSTATUS_ACCESS_DENIED; } ListRemove(&vad->ProcessLink); KeReleaseSpinlock(&process->VadLock, procFlags); uintptr_t globalFlags = KeAcquireSpinlock(&gVadLock); ListRemove(&vad->GlobalLink); KeReleaseSpinlock(&gVadLock, globalFlags); KiUnmapVadPayload(vad); if (vad->FileObject) { ObDereferenceObject(&vad->FileObject->Header); vad->FileObject = (FILE_OBJECT*)0; } if (vad->ImageSection) { LdrDereferenceImageSection(vad->ImageSection); vad->ImageSection = (IMAGE_SECTION_OBJECT*)0; } MmFreePool(vad->Pages); MmFreePool(vad); return NTLSTATUS_SUCCESS; } // Destroys every VAD owned by a process during teardown. void MmVadDestroyAllForProcess(PROCESS* process) { KeAssertIrql(PASSIVE_LEVEL); if (!process) return; while (true) { uintptr_t procFlags = KeAcquireSpinlock(&process->VadLock); LIST_ENTRY* node = (LIST_ENTRY*)0; int sawLocked = 0; for (LIST_ENTRY* it = process->VadList.Fwd; it != &process->VadList; it = it->Fwd) { MM_VAD* candidate = CONTAINING_RECORD(it, MM_VAD, ProcessLink); if (candidate->LockCount) { sawLocked = 1; continue; } node = it; ListRemove(node); break; } KeReleaseSpinlock(&process->VadLock, procFlags); if (!node) { if (sawLocked) { KeYield(); continue; } break; } MM_VAD* vad = CONTAINING_RECORD(node, MM_VAD, ProcessLink); uintptr_t globalFlags = KeAcquireSpinlock(&gVadLock); ListRemove(&vad->GlobalLink); KeReleaseSpinlock(&gVadLock, globalFlags); KiUnmapVadPayload(vad); if (vad->FileObject) { ObDereferenceObject(&vad->FileObject->Header); vad->FileObject = (FILE_OBJECT*)0; } if (vad->ImageSection) { LdrDereferenceImageSection(vad->ImageSection); vad->ImageSection = (IMAGE_SECTION_OBJECT*)0; } MmFreePool(vad->Pages); MmFreePool(vad); } } // Queries the committed VAD range containing an address. KSTATUS MmVadQuery(PROCESS* process, const void* address, void** baseOut, size_t* sizeOut, uint64_t* pageFlagsOut, MM_VAD_TYPE* typeOut, uint32_t* vadFlagsOut) { if (!process || !address) return NTLSTATUS_INVALID_PARAMETER; uintptr_t addr = (uintptr_t)address; uintptr_t procFlags = KeAcquireSpinlock(&process->VadLock); for (LIST_ENTRY* node = process->VadList.Fwd; node != &process->VadList; node = node->Fwd) { MM_VAD* vad = CONTAINING_RECORD(node, MM_VAD, ProcessLink); if (addr < vad->Base || addr >= vad->Base + vad->PayloadSize) continue; size_t index = KiVadPageIndex(vad, addr & PAGE_MASK); if (index >= vad->PageCount || vad->Pages[index].State != MmVadPageCommitted) { KeReleaseSpinlock(&process->VadLock, procFlags); return NTLSTATUS_NOT_FOUND; } uint64_t flags = vad->Pages[index].PageFlags; size_t runStart = index; while (runStart > 0 && vad->Pages[runStart - 1].State == MmVadPageCommitted && vad->Pages[runStart - 1].PageFlags == flags) { runStart--; } size_t runEnd = index + 1; while (runEnd < vad->PageCount && vad->Pages[runEnd].State == MmVadPageCommitted && vad->Pages[runEnd].PageFlags == flags) { runEnd++; } if (baseOut) *baseOut = (void*)(vad->Base + runStart * PAGE_SIZE); if (sizeOut) *sizeOut = (runEnd - runStart) * PAGE_SIZE; if (pageFlagsOut) *pageFlagsOut = flags; if (typeOut) *typeOut = vad->Type; if (vadFlagsOut) *vadFlagsOut = vad->Flags; KeReleaseSpinlock(&process->VadLock, procFlags); return NTLSTATUS_SUCCESS; } KeReleaseSpinlock(&process->VadLock, procFlags); return NTLSTATUS_NOT_FOUND; } // Returns per-process VAD accounting for ps and memory telemetry. void MmVadQueryProcessMemory(PROCESS* process, size_t* vadCountOut, size_t* reservedBytesOut, size_t* committedBytesOut) { size_t vads = 0; size_t reserved = 0; size_t committed = 0; if (process) { uintptr_t flags = KeAcquireSpinlock(&process->VadLock); for (LIST_ENTRY* node = process->VadList.Fwd; node != &process->VadList; node = node->Fwd) { MM_VAD* vad = CONTAINING_RECORD(node, MM_VAD, ProcessLink); vads++; reserved += vad->ReservationSize; committed += vad->CommittedPages * PAGE_SIZE; } KeReleaseSpinlock(&process->VadLock, flags); } if (vadCountOut) *vadCountOut = vads; if (reservedBytesOut) *reservedBytesOut = reserved; if (committedBytesOut) *committedBytesOut = committed; } // Returns VAD and demand-fault telemetry. void MmVadQueryStats(MM_VAD_STATS* out) { if (!out) return; *out = gVadStats; } // Returns the requested payload size for a VAD base. size_t MmVadGetSize(PROCESS* process, const void* base) { if (!process || !base) return 0; uintptr_t procFlags = KeAcquireSpinlock(&process->VadLock); MM_VAD* vad = KiFindVadByBaseLocked(process, base); size_t size = vad ? vad->Size : 0; KeReleaseSpinlock(&process->VadLock, procFlags); return size; } // Returns the requested payload size for a VAD base without knowing the owner. size_t MmVadGetSizeByBase(const void* base) { if (!base) return 0; uintptr_t b = (uintptr_t)base; uintptr_t flags = KeAcquireSpinlock(&gVadLock); for (LIST_ENTRY* node = gVadList.Fwd; node != &gVadList; node = node->Fwd) { MM_VAD* vad = CONTAINING_RECORD(node, MM_VAD, GlobalLink); if (vad->Base == b) { size_t size = vad->Size; KeReleaseSpinlock(&gVadLock, flags); return size; } } KeReleaseSpinlock(&gVadLock, flags); return 0; } // Implements the VAD Type Name operation. static const char* KiVadTypeName(MM_VAD_TYPE type) { switch (type) { case MmVadGeneric: return "generic"; case MmVadHeap: return "heap"; case MmVadImage: return "image"; case MmVadStack: return "stack"; case MmVadTeb: return "teb"; case MmVadPeb: return "peb"; case MmVadEnvironment: return "env"; case MmVadMappedFile: return "mapped"; case MmVadShared: return "shared"; default: return "?"; } } // Implements the debug Dump VADs operation. void MmVadDebugDumpProcess(PROCESS* process) { if (!process) { DbgPrint("MM: VADs: \n"); return; } DbgPrint("MM: VADs for pid=%u name=%s:\n", process->ProcessId, process->Name); uintptr_t flags = KeAcquireSpinlock(&process->VadLock); uint32_t count = 0; for (LIST_ENTRY* node = process->VadList.Fwd; node != &process->VadList; node = node->Fwd) { MM_VAD* r = CONTAINING_RECORD(node, MM_VAD, ProcessLink); DbgPrint(" #%u %-7s res=%016llx-%016llx base=%016llx size=%llx committed=%llx/%llx flags=%x pte=%llx\n", count, KiVadTypeName(r->Type), (unsigned long long)r->ReservationBase, (unsigned long long)(r->ReservationBase + r->ReservationSize), (unsigned long long)r->Base, (unsigned long long)r->Size, (unsigned long long)(r->CommittedPages * PAGE_SIZE), (unsigned long long)r->PayloadSize, r->Flags, (unsigned long long)r->PageFlags); count++; } KeReleaseSpinlock(&process->VadLock, flags); if (!count) DbgPrint(" \n"); } // Implements the debug Describe Address operation. void MmDebugDescribeAddress(uintptr_t addr) { PHYSICAL_ADDRESS phys = 0; uint64_t flags = 0; KSTATUS qs = MmPteQueryPageFor(MmPteQueryDebug, addr, &phys, &flags); DbgPrint("MM: describe va=%016llx query=%d phys=%016llx flags=%016llx\n", (unsigned long long)addr, qs, (unsigned long long)phys, (unsigned long long)flags); if (addr >= POOL_BASE && addr < POOL_LIMIT) { MmPoolDebugDescribeAddress(addr); return; } if (addr >= MM_VAD_ARENA_BASE && addr < USER_LIMIT) { uintptr_t gl = KeAcquireSpinlock(&gVadLock); for (LIST_ENTRY* node = gVadList.Fwd; node != &gVadList; node = node->Fwd) { MM_VAD* r = CONTAINING_RECORD(node, MM_VAD, GlobalLink); if (addr >= r->ReservationBase && addr < r->ReservationBase + r->ReservationSize) { const char* pageState = "guard"; if (addr >= r->Base && addr < r->Base + r->PayloadSize) { size_t pageIndex = KiVadPageIndex(r, addr & PAGE_MASK); if (pageIndex < r->PageCount) { MM_VAD_PAGE* page = &r->Pages[pageIndex]; if (page->State == MmVadPageCommitted) pageState = page->PageFlags ? "mapped" : "noaccess"; else pageState = "reserved"; } } DbgPrint("MM: address belongs to pid=%u name=%s vad=%s page=%s res=%016llx-%016llx base=%016llx size=%llx flags=%x\n", r->Owner ? r->Owner->ProcessId : 0, r->Owner ? r->Owner->Name : "?", KiVadTypeName(r->Type), pageState, (unsigned long long)r->ReservationBase, (unsigned long long)(r->ReservationBase + r->ReservationSize), (unsigned long long)r->Base, (unsigned long long)r->Size, r->Flags); KeReleaseSpinlock(&gVadLock, gl); return; } } KeReleaseSpinlock(&gVadLock, gl); DbgPrint("MM: address is in user arena but not reserved by any MM_VAD\n"); } } // Creates user Memory For Process. __declspec(dllexport) void* MmAllocateUserMemoryForProcess(PROCESS* process, size_t size) { void* base = (void*)0; if (!process || !size) return (void*)0; KSTATUS s = MmVadCreateCommitted(process, size, PAGE_SIZE, PAGE_PRESENT | PAGE_WRITE | PAGE_USER | PAGE_NX, MM_VAD_ZERO, MmVadHeap, &base); if (!KSUCCESS(s)) return (void*)0; s = MmVadEnsureResident(process, base, size, PAGE_WRITE | PAGE_USER); if (!KSUCCESS(s)) { (void)MmVadDestroy(process, base); return (void*)0; } return base; } // Releases user Memory For Process. __declspec(dllexport) void MmFreeUserMemoryForProcess(PROCESS* process, void* ptr) { if (!process || !ptr) return; (void)MmVadDestroy(process, ptr); } // Implements the reallocate User Memory For Process operation. __declspec(dllexport) void* MmReallocateUserMemoryForProcess(PROCESS* process, void* ptr, size_t newSize) { if (!process) return (void*)0; if (!ptr) return MmAllocateUserMemoryForProcess(process, newSize); if (!newSize) { MmFreeUserMemoryForProcess(process, ptr); return (void*)0; } size_t oldSize = MmVadGetSize(process, ptr); if (!oldSize) return (void*)0; if (newSize <= oldSize) return ptr; void* newPtr = MmAllocateUserMemoryForProcess(process, newSize); if (!newPtr) return (void*)0; memcpy(newPtr, ptr, oldSize); MmFreeUserMemoryForProcess(process, ptr); return newPtr; } // Creates user Memory. __declspec(dllexport) void* MmAllocateUserMemory(size_t size) { PROCESS* process = KeGetCurrentProcess(); return MmAllocateUserMemoryForProcess(process, size); } // Releases user Memory. __declspec(dllexport) void MmFreeUserMemory(void* ptr) { PROCESS* process = KeGetCurrentProcess(); MmFreeUserMemoryForProcess(process, ptr); } // Implements the reallocate User Memory operation. __declspec(dllexport) void* MmReallocateUserMemory(void* ptr, size_t newSize) { PROCESS* process = KeGetCurrentProcess(); return MmReallocateUserMemoryForProcess(process, ptr, newSize); } // Creates user Image For Process. __declspec(dllexport) void* MmAllocateUserImageForProcess(PROCESS* process, size_t size) { void* base = (void*)0; if (!process || !size) return (void*)0; KSTATUS s = MmVadCreateCommitted(process, size, PAGE_SIZE, PAGE_PRESENT | PAGE_WRITE | PAGE_USER | PAGE_NX, MM_VAD_ZERO | MM_VAD_GUARD_LOW | MM_VAD_GUARD_HIGH, MmVadImage, &base); if (!KSUCCESS(s)) return (void*)0; return base; } // Releases user Image For Process. __declspec(dllexport) void MmFreeUserImageForProcess(PROCESS* process, void* ptr, size_t size) { (void)size; if (!process || !ptr) return; (void)MmVadDestroy(process, ptr); } // Creates user Image. __declspec(dllexport) void* MmAllocateUserImage(size_t size) { PROCESS* process = KeGetCurrentProcess(); return MmAllocateUserImageForProcess(process, size); } // Releases user Image. __declspec(dllexport) void MmFreeUserImage(void* ptr, size_t size) { PROCESS* process = KeGetCurrentProcess(); MmFreeUserImageForProcess(process, ptr, size); } // Returns user Heap Usage. size_t MmGetUserHeapUsage(void) { size_t total = 0; if (!gVadListReady) return 0; uintptr_t flags = KeAcquireSpinlock(&gVadLock); for (LIST_ENTRY* node = gVadList.Fwd; node != &gVadList; node = node->Fwd) { MM_VAD* r = CONTAINING_RECORD(node, MM_VAD, GlobalLink); total += r->CommittedPages * PAGE_SIZE; } KeReleaseSpinlock(&gVadLock, flags); return total; }