// Kernel virtual-memory manager. This file owns kernel pool allocation, vad bookkeeping, and user-copy validation. // Page-table construction lives in mm_pte.c so the higher VMM can grow toward VADs, faults, COW, and swap without mixing those policies with raw PTE edits. // Kernel pool: // A boundary-tag allocator starts at POOL_BASE and commits pages on demand. // 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 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 int KiUserRangeValid(uintptr_t addr, size_t size) { if (size == 0) return 1; if (addr < USER_BASE) return 0; if (addr >= USER_LIMIT) return 0; if (size > (size_t)(USER_LIMIT - addr)) return 0; return 1; } // 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 STATUS_ACCESS_DENIED; if (size == 0) return STATUS_SUCCESS; PROCESS* process = KeGetCurrentProcess(); if (!process) return STATUS_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); } // Boundary-tag allocator (used for both pool and user heap) // Each allocation has a BLOCK_HDR immediately before the user data, and a // BLOCK_FTR immediately after. Both carry the block size and a 'free' flag. // This allows O(1) coalescing of adjacent free blocks. #define BLOCK_MAGIC 0xB10CB10CUL #define BLOCK_STATE_USED 0u #define BLOCK_STATE_FREE 1u #define BLOCK_STATE_QUARANTINE 2u #define BLOCK_FLAG_CANARY 0x00000001u #define BLOCK_FLAG_POISONED 0x00000002u #define POOL_TAIL_CANARY_BASE 0xC0FFEE1234ABCDEFULL #define POOL_POISON_FREE 0xDD #define POOL_QUARANTINE_SLOTS 256u #define POOL_TAG_DEFAULT 0x6C6F6F70u /* 'pool' */ typedef struct BLOCK_HDR { uint32_t Magic; uint32_t Free; // BLOCK_STATE_* size_t Size; // usable allocator bytes (excludes header + footer) struct BLOCK_HDR* PrevFree; struct BLOCK_HDR* NextFree; // Debug metadata. Present in all builds so boot.ini bits can enable // diagnostics without changing allocator layout or ABI. size_t RequestedSize; uint32_t Tag; uint32_t Flags; uint64_t Sequence; uintptr_t AllocCaller; uintptr_t FreeCaller; } BLOCK_HDR; typedef struct BLOCK_FTR { size_t Size; uint32_t Free; uint32_t Magic; } BLOCK_FTR; #define HDR_SIZE (sizeof(BLOCK_HDR)) #define FTR_SIZE (sizeof(BLOCK_FTR)) #define OVERHEAD (HDR_SIZE + FTR_SIZE) #define MIN_BLOCK (OVERHEAD + sizeof(void*)) // Implements the hdr To Ftr operation. static inline BLOCK_FTR* KiHdrToFtr(BLOCK_HDR* h) { return (BLOCK_FTR *)((char *)h + HDR_SIZE + h->Size); } // Implements the ftr To Hdr operation. static inline BLOCK_HDR* KiFtrToHdr(BLOCK_FTR* f) { return (BLOCK_HDR *)((char *)f - HDR_SIZE - f->Size); } // Implements the next Block operation. static inline BLOCK_HDR* KiNextBlock(BLOCK_HDR* h) { return (BLOCK_HDR *)((char *)h + HDR_SIZE + h->Size + FTR_SIZE); } // Implements the prev Ftr operation. static inline BLOCK_FTR* KiPrevFtr(BLOCK_HDR* h) { return (BLOCK_FTR *)((char *)h - FTR_SIZE); } typedef struct HEAP HEAP; struct HEAP { BLOCK_HDR* FreeList; // doubly-linked list of free blocks uintptr_t Start; // virtual base of the arena uintptr_t Brk; // current committed end (page-aligned) uintptr_t Limit; // hard upper bound uint64_t MapFlags; // PAGE_* flags for new committed pages SPINLOCK Lock; int DecommitTailOnFree; uint64_t NextSequence; BLOCK_HDR* Quarantine[POOL_QUARANTINE_SLOTS]; size_t QuarantineCursor; size_t QuarantineCount; }; static inline uint32_t KiPoolDebugFlags(void) { return KeGetDebugFlags() & (KE_DEBUG_POOL_CANARY | KE_DEBUG_POOL_POISON | KE_DEBUG_POOL_QUARANTINE | KE_DEBUG_POOL_CALLERS); } static inline uint64_t KiPoolTailCanaryValue(const BLOCK_HDR* blk) { return POOL_TAIL_CANARY_BASE ^ ((uint64_t)(uintptr_t)blk << 13) ^ ((uint64_t)blk->RequestedSize << 1) ^ blk->Sequence; } static inline uint8_t* KiBlockUserBase(BLOCK_HDR* blk) { return (uint8_t*)blk + HDR_SIZE; } static inline uint64_t* KiBlockTailCanary(BLOCK_HDR* blk) { return (uint64_t*)(void*)(KiBlockUserBase(blk) + blk->RequestedSize); } static const char* KiPoolStateName(uint32_t state) { switch (state) { case BLOCK_STATE_USED: return "used"; case BLOCK_STATE_FREE: return "free"; case BLOCK_STATE_QUARANTINE: return "quarantine"; default: return "bad-state"; } } static _Noreturn void KiPoolPanic(const char* reason, HEAP* h, BLOCK_HDR* blk, const void* ptr) { KePanic(KE_PANIC_CODE_GENERIC, "MM: pool corruption: %s ptr=%p hdr=%p heap=[%016llx,%016llx) magic=%08x state=%u(%s) size=%llu req=%llu tag=%08x flags=%08x seq=%llu alloc=%p free=%p\n", reason ? reason : "unknown", ptr, blk, (unsigned long long)(h ? h->Start : 0), (unsigned long long)(h ? h->Brk : 0), blk ? blk->Magic : 0, blk ? blk->Free : 0, blk ? KiPoolStateName(blk->Free) : "", (unsigned long long)(blk ? blk->Size : 0), (unsigned long long)(blk ? blk->RequestedSize : 0), blk ? blk->Tag : 0, blk ? blk->Flags : 0, (unsigned long long)(blk ? blk->Sequence : 0), blk ? (void*)blk->AllocCaller : (void*)0, blk ? (void*)blk->FreeCaller : (void*)0); } static void KiPoolValidateFooterLocked(HEAP* h, BLOCK_HDR* blk, const char* where) { if (!h || !blk) KiPoolPanic(where, h, blk, blk ? KiBlockUserBase(blk) : (void*)0); if (blk->Magic != BLOCK_MAGIC) KiPoolPanic(where, h, blk, KiBlockUserBase(blk)); if ((uintptr_t)blk < h->Start || (uintptr_t)blk >= h->Brk) KiPoolPanic("block outside heap", h, blk, KiBlockUserBase(blk)); if (blk->Size > (size_t)(h->Brk - (uintptr_t)blk - OVERHEAD)) KiPoolPanic("block size outside heap", h, blk, KiBlockUserBase(blk)); BLOCK_FTR* ftr = KiHdrToFtr(blk); if ((uintptr_t)ftr + FTR_SIZE > h->Brk) KiPoolPanic("footer outside heap", h, blk, KiBlockUserBase(blk)); if (ftr->Magic != BLOCK_MAGIC || ftr->Size != blk->Size || ftr->Free != blk->Free) KiPoolPanic(where, h, blk, KiBlockUserBase(blk)); } static void KiPoolWriteTailCanary(BLOCK_HDR* blk) { if (!blk || !(blk->Flags & BLOCK_FLAG_CANARY)) return; *KiBlockTailCanary(blk) = KiPoolTailCanaryValue(blk); } static void KiPoolValidateTailCanaryLocked(HEAP* h, BLOCK_HDR* blk, const char* where) { if (!blk || !(blk->Flags & BLOCK_FLAG_CANARY)) return; if (blk->RequestedSize + sizeof(uint64_t) > blk->Size) KiPoolPanic("invalid canary layout", h, blk, KiBlockUserBase(blk)); uint64_t actual = *KiBlockTailCanary(blk); uint64_t expect = KiPoolTailCanaryValue(blk); if (actual != expect) KiPoolPanic(where, h, blk, KiBlockUserBase(blk)); } static void KiPoolPoisonFreedPayload(BLOCK_HDR* blk) { if (!blk) return; size_t bytes = blk->RequestedSize; if (bytes > blk->Size) bytes = blk->Size; memset(KiBlockUserBase(blk), POOL_POISON_FREE, bytes); blk->Flags |= BLOCK_FLAG_POISONED; KiPoolWriteTailCanary(blk); } static void KiPoolValidateFreedPoisonLocked(HEAP* h, BLOCK_HDR* blk, const char* where) { if (!blk || !(blk->Flags & BLOCK_FLAG_POISONED)) return; size_t bytes = blk->RequestedSize; if (bytes > blk->Size) bytes = blk->Size; const uint8_t* p = KiBlockUserBase(blk); for (size_t i = 0; i < bytes; i++) { if (p[i] != POOL_POISON_FREE) KiPoolPanic(where, h, blk, KiBlockUserBase(blk)); } } // Rolls back a failed heap grow attempt. KiHeapGrow maps pages before the // new free block is linked into the allocator; if a later frame allocation or // page-table allocation fails, those earlier mappings must be returned or the // pool silently leaks physical frames and leaves stale mappings below Brk. static void KiHeapRollbackGrow(uintptr_t start, uintptr_t end) { for (uintptr_t va = start; va < end; va += PAGE_SIZE) { PHYSICAL_ADDRESS frame = MmPteTranslate(va); if (!frame) continue; if (KSUCCESS(MmPteUnmapPage(va))) (void)MmPfnFree(frame); } } // Extend heap by at least 'need' bytes (rounded up to page boundary). static int KiHeapGrow(HEAP* h, size_t need) { size_t grow = (need + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); if (h->Brk + grow > h->Limit) return 0; uintptr_t start = h->Brk; uintptr_t mappedEnd = start; for (uintptr_t va = start; va < start + grow; va += PAGE_SIZE) { PHYSICAL_ADDRESS frame = MmPfnAllocate(MmPfnUseKernel); if (!frame) { KiHeapRollbackGrow(start, mappedEnd); return 0; } if (!KSUCCESS(MmPteMapPage(va, frame, h->MapFlags, MmPteMapFailIfPresent))) { MmPfnFree(frame); KiHeapRollbackGrow(start, mappedEnd); return 0; } mappedEnd = va + PAGE_SIZE; memset((void *)va, 0, PAGE_SIZE); } // The grow path owns the only transition from reserved heap VA to mapped heap // pages, so a successful loop above is the heap's committed-frontier proof. // Re-querying every heap page on later allocations only burns page-table locks. // Missing pool pages are corruption now, not something the allocator repairs. uintptr_t oldBrk = h->Brk; uintptr_t newBrk = oldBrk + grow; // If the current tail block is already free, extend it in-place instead of // leaving adjacent free blocks. This matters once tail decommit is enabled: // a later grow should rebuild the same tail block, not fragment the arena // into many page-sized free runs. if (oldBrk > h->Start) { BLOCK_FTR* prevFtr = (BLOCK_FTR*)(oldBrk - FTR_SIZE); if (prevFtr->Magic == BLOCK_MAGIC && prevFtr->Free == BLOCK_STATE_FREE) { BLOCK_HDR* prev = KiFtrToHdr(prevFtr); if ((uintptr_t)prev >= h->Start && prev->Magic == BLOCK_MAGIC && prev->Free == BLOCK_STATE_FREE) { prev->Size += grow; h->Brk = newBrk; KiHdrToFtr(prev)->Magic = BLOCK_MAGIC; KiHdrToFtr(prev)->Free = BLOCK_STATE_FREE; KiHdrToFtr(prev)->Size = prev->Size; return 1; } } } // Make the new pages a single free block. BLOCK_HDR* blk = (BLOCK_HDR *)oldBrk; blk->Magic = BLOCK_MAGIC; blk->Free = BLOCK_STATE_FREE; blk->Size = grow - OVERHEAD; blk->PrevFree = (BLOCK_HDR *)0; blk->NextFree = h->FreeList; blk->RequestedSize = 0; blk->Tag = 0; blk->Flags = 0; blk->Sequence = 0; blk->AllocCaller = 0; blk->FreeCaller = 0; if (h->FreeList) h->FreeList->PrevFree = blk; h->FreeList = blk; h->Brk = newBrk; KiHdrToFtr(blk)->Magic = BLOCK_MAGIC; KiHdrToFtr(blk)->Free = BLOCK_STATE_FREE; KiHdrToFtr(blk)->Size = blk->Size; return 1; } // Unlink a free block from the free list. static void KiFreeListRemove(HEAP* h, BLOCK_HDR* blk) { if (blk->PrevFree) blk->PrevFree->NextFree = blk->NextFree; else h->FreeList = blk->NextFree; if (blk->NextFree) blk->NextFree->PrevFree = blk->PrevFree; blk->PrevFree = blk->NextFree = (BLOCK_HDR *)0; } // Implements the heap Try Decommit Tail operation. static void KiHeapTryDecommitTail(HEAP* h, BLOCK_HDR* blk) { if (!h || !blk || blk->Free == BLOCK_STATE_USED) return; // Do not eagerly decommit the kernel pool on every free. Large transient // syscall bounce buffers, especially SysBlitScreen's frame-sized buffer, // are often allocated/freed once per frame. If the tail block is unmapped // immediately, the next frame has to map the same pages again, producing // massive page-table churn and global TLB shootdowns. Keep the committed // pool high-water resident during normal operation; add an explicit idle or // pressure pool trim later if needed. if (!h->DecommitTailOnFree) { KiHdrToFtr(blk)->Size = blk->Size; KiHdrToFtr(blk)->Magic = BLOCK_MAGIC; KiHdrToFtr(blk)->Free = BLOCK_STATE_FREE; return; } uintptr_t blockBase = (uintptr_t)blk; uintptr_t oldBrk = h->Brk; // Only the last free block can return committed heap pages. Interior free // blocks must remain mapped because the allocator's boundary tags and free // list links are stored inside those blocks. if ((uintptr_t)KiNextBlock(blk) != oldBrk) { KiHdrToFtr(blk)->Size = blk->Size; KiHdrToFtr(blk)->Magic = BLOCK_MAGIC; KiHdrToFtr(blk)->Free = BLOCK_STATE_FREE; return; } // Keep enough mapped space for the tail block's header, footer, and a small // usable payload, then decommit whole pages above that. The footer moves to // the new Brk so future coalescing can still walk the boundary tag chain. uintptr_t minEnd = blockBase + MIN_BLOCK; uintptr_t newBrk = (minEnd + PAGE_SIZE - 1) & ~(uintptr_t)(PAGE_SIZE - 1); if (newBrk >= oldBrk) { KiHdrToFtr(blk)->Size = blk->Size; KiHdrToFtr(blk)->Magic = BLOCK_MAGIC; KiHdrToFtr(blk)->Free = BLOCK_STATE_FREE; return; } for (uintptr_t va = newBrk; va < oldBrk; va += PAGE_SIZE) { PHYSICAL_ADDRESS frame = MmPteTranslate(va); if (!frame) continue; if (KSUCCESS(MmPteUnmapPage(va))) (void)MmPfnFree(frame); } h->Brk = newBrk; blk->Size = newBrk - blockBase - OVERHEAD; KiHdrToFtr(blk)->Size = blk->Size; KiHdrToFtr(blk)->Magic = BLOCK_MAGIC; KiHdrToFtr(blk)->Free = BLOCK_STATE_FREE; } // Releases a previously quarantined block into the real free list. Caller holds h->Lock. static void KiHeapReleaseFreeBlockLocked(HEAP* h, BLOCK_HDR* blk) { if (!h || !blk) return; KiPoolValidateFooterLocked(h, blk, "release free footer corrupt"); KiPoolValidateTailCanaryLocked(h, blk, "release free tail canary corrupt"); KiPoolValidateFreedPoisonLocked(h, blk, "use-after-free payload write detected"); blk->Free = BLOCK_STATE_FREE; blk->RequestedSize = 0; blk->Tag = 0; blk->Flags = 0; blk->AllocCaller = 0; // Keep FreeCaller/Sequence for diagnostics until the block is reused. KiHdrToFtr(blk)->Free = BLOCK_STATE_FREE; KiHdrToFtr(blk)->Magic = BLOCK_MAGIC; KiHdrToFtr(blk)->Size = blk->Size; // Coalesce with next block if it is really free. Quarantined neighbors are // intentionally not coalesced; their payload must remain intact for UAF checks. BLOCK_HDR* next = KiNextBlock(blk); if ((uintptr_t)next < h->Brk) { KiPoolValidateFooterLocked(h, next, "next footer corrupt while freeing"); if (next->Free == BLOCK_STATE_FREE) { KiFreeListRemove(h, next); blk->Size += OVERHEAD + next->Size; KiHdrToFtr(blk)->Size = blk->Size; KiHdrToFtr(blk)->Magic = BLOCK_MAGIC; KiHdrToFtr(blk)->Free = BLOCK_STATE_FREE; } } // Coalesce with previous block if it is really free. if ((uintptr_t)blk > h->Start) { BLOCK_FTR* prevFtr = KiPrevFtr(blk); if (prevFtr->Magic == BLOCK_MAGIC) { BLOCK_HDR* prev = KiFtrToHdr(prevFtr); KiPoolValidateFooterLocked(h, prev, "previous footer corrupt while freeing"); if (prev->Free == BLOCK_STATE_FREE) { KiFreeListRemove(h, prev); prev->Size += OVERHEAD + blk->Size; KiHdrToFtr(prev)->Size = prev->Size; KiHdrToFtr(prev)->Magic = BLOCK_MAGIC; KiHdrToFtr(prev)->Free = BLOCK_STATE_FREE; blk = prev; } } } // Insert at head of free list. blk->PrevFree = (BLOCK_HDR *)0; blk->NextFree = h->FreeList; if (h->FreeList) h->FreeList->PrevFree = blk; h->FreeList = blk; KiHdrToFtr(blk)->Free = BLOCK_STATE_FREE; KiHdrToFtr(blk)->Magic = BLOCK_MAGIC; KiHdrToFtr(blk)->Size = blk->Size; KiHeapTryDecommitTail(h, blk); } static void KiHeapReleaseQuarantineSlotLocked(HEAP* h, size_t index) { if (!h || index >= POOL_QUARANTINE_SLOTS) return; BLOCK_HDR* old = h->Quarantine[index]; if (!old) return; h->Quarantine[index] = (BLOCK_HDR*)0; if (h->QuarantineCount) h->QuarantineCount--; if (old->Free != BLOCK_STATE_QUARANTINE) KiPoolPanic("quarantine slot state corrupt", h, old, KiBlockUserBase(old)); KiHeapReleaseFreeBlockLocked(h, old); } static void KiHeapDrainQuarantineLocked(HEAP* h) { if (!h) return; for (size_t i = 0; i < POOL_QUARANTINE_SLOTS; i++) KiHeapReleaseQuarantineSlotLocked(h, i); h->QuarantineCursor = 0; } static void KiHeapQuarantineBlockLocked(HEAP* h, BLOCK_HDR* blk) { if (!h || !blk) return; size_t idx = h->QuarantineCursor % POOL_QUARANTINE_SLOTS; KiHeapReleaseQuarantineSlotLocked(h, idx); blk->Free = BLOCK_STATE_QUARANTINE; KiHdrToFtr(blk)->Free = BLOCK_STATE_QUARANTINE; KiHdrToFtr(blk)->Magic = BLOCK_MAGIC; KiHdrToFtr(blk)->Size = blk->Size; h->Quarantine[idx] = blk; h->QuarantineCursor = (idx + 1u) % POOL_QUARANTINE_SLOTS; h->QuarantineCount++; } // Implements the heap Alloc operation. static void* KiHeapAllocEx(HEAP* h, size_t requestedSize, uint32_t tag, uintptr_t caller) { if (!requestedSize) return (void *)0; uint32_t dbg = KiPoolDebugFlags(); size_t size = requestedSize; if (dbg & KE_DEBUG_POOL_CANARY) { if (size > (size_t)-1 - sizeof(uint64_t)) return (void*)0; size += sizeof(uint64_t); } size = (size + 7) & ~7ULL; // 8-byte align uintptr_t flags = KeAcquireSpinlock(&h->Lock); // Best-fit search. BLOCK_HDR* best = (BLOCK_HDR *)0; for (BLOCK_HDR* cur = h->FreeList; cur; cur = cur->NextFree) { KiPoolValidateFooterLocked(h, cur, "free list block corrupt"); if (cur->Free != BLOCK_STATE_FREE) KiPoolPanic("free list contains non-free block", h, cur, KiBlockUserBase(cur)); if (cur->Size >= size) { if (!best || cur->Size < best->Size) best = cur; if (best->Size == size) break; } } if (!best && h->QuarantineCount) { KiHeapDrainQuarantineLocked(h); for (BLOCK_HDR* cur = h->FreeList; cur; cur = cur->NextFree) { KiPoolValidateFooterLocked(h, cur, "free list block corrupt after quarantine drain"); if (cur->Size >= size) { if (!best || cur->Size < best->Size) best = cur; if (best->Size == size) break; } } } if (!best) { if (!KiHeapGrow(h, size + OVERHEAD)) { KeReleaseSpinlock(&h->Lock, flags); return (void *)0; } // Re-search; KiHeapGrow inserted at the front. best = h->FreeList; while (best && best->Size < size) best = best->NextFree; if (!best) { KeReleaseSpinlock(&h->Lock, flags); return (void *)0; } KiPoolValidateFooterLocked(h, best, "grown free block corrupt"); } KiFreeListRemove(h, best); // Split the block if the remainder is big enough. if (best->Size >= size + OVERHEAD + sizeof(void*)) { BLOCK_HDR* rem = (BLOCK_HDR *)((char *)best + HDR_SIZE + size + FTR_SIZE); rem->Magic = BLOCK_MAGIC; rem->Free = BLOCK_STATE_FREE; rem->Size = best->Size - size - OVERHEAD; rem->PrevFree = (BLOCK_HDR *)0; rem->NextFree = h->FreeList; rem->RequestedSize = 0; rem->Tag = 0; rem->Flags = 0; rem->Sequence = 0; rem->AllocCaller = 0; rem->FreeCaller = 0; if (h->FreeList) h->FreeList->PrevFree = rem; h->FreeList = rem; KiHdrToFtr(rem)->Magic = BLOCK_MAGIC; KiHdrToFtr(rem)->Free = BLOCK_STATE_FREE; KiHdrToFtr(rem)->Size = rem->Size; best->Size = size; } best->Free = BLOCK_STATE_USED; best->RequestedSize = requestedSize; best->Tag = tag ? tag : POOL_TAG_DEFAULT; best->Flags = 0; best->Sequence = ++h->NextSequence; best->AllocCaller = (dbg & KE_DEBUG_POOL_CALLERS) ? caller : 0; best->FreeCaller = 0; if (dbg & KE_DEBUG_POOL_CANARY) best->Flags |= BLOCK_FLAG_CANARY; KiHdrToFtr(best)->Free = BLOCK_STATE_USED; KiHdrToFtr(best)->Magic = BLOCK_MAGIC; KiHdrToFtr(best)->Size = best->Size; memset(KiBlockUserBase(best), 0, requestedSize); if (best->Size > requestedSize) memset(KiBlockUserBase(best) + requestedSize, 0, best->Size - requestedSize); KiPoolWriteTailCanary(best); KeReleaseSpinlock(&h->Lock, flags); return KiBlockUserBase(best); } static void* KiHeapAlloc(HEAP* h, size_t size) { return KiHeapAllocEx(h, size, POOL_TAG_DEFAULT, (uintptr_t)_ReturnAddress()); } // Implements the heap Free operation. static void KiHeapFreeEx(HEAP* h, void* ptr, uintptr_t caller) { if (!ptr) return; uintptr_t u = (uintptr_t)ptr; if (!h || u < h->Start + HDR_SIZE || u >= h->Brk) KiPoolPanic("invalid free pointer outside heap", h, (BLOCK_HDR*)0, ptr); BLOCK_HDR* blk = (BLOCK_HDR *)(u - HDR_SIZE); uintptr_t flags = KeAcquireSpinlock(&h->Lock); if (blk->Magic != BLOCK_MAGIC) KiPoolPanic("invalid free pointer/bad header magic", h, blk, ptr); KiPoolValidateFooterLocked(h, blk, "allocated footer corrupt on free"); if (blk->Free == BLOCK_STATE_FREE) KiPoolPanic("double free", h, blk, ptr); if (blk->Free == BLOCK_STATE_QUARANTINE) KiPoolPanic("double free/quarantined UAF", h, blk, ptr); if (blk->Free != BLOCK_STATE_USED) KiPoolPanic("invalid block state on free", h, blk, ptr); KiPoolValidateTailCanaryLocked(h, blk, "tail canary corrupt on free"); uint32_t dbg = KiPoolDebugFlags(); blk->FreeCaller = (dbg & KE_DEBUG_POOL_CALLERS) ? caller : 0; if (dbg & KE_DEBUG_POOL_POISON) KiPoolPoisonFreedPayload(blk); if (dbg & KE_DEBUG_POOL_QUARANTINE) { KiHeapQuarantineBlockLocked(h, blk); } else { KiHeapReleaseFreeBlockLocked(h, blk); } KeReleaseSpinlock(&h->Lock, flags); } static void KiHeapFree(HEAP* h, void* ptr) { KiHeapFreeEx(h, ptr, (uintptr_t)_ReturnAddress()); } // Implements the heap Realloc operation. static void* KiHeapReallocEx(HEAP* h, void* ptr, size_t newSize, uintptr_t caller) { // If ptr is NULL, realloc acts like malloc. if (!ptr) { return KiHeapAllocEx(h, newSize, POOL_TAG_DEFAULT, caller); } // If size is 0, realloc acts like free. if (newSize == 0) { KiHeapFreeEx(h, ptr, caller); return (void*)0; } uintptr_t u = (uintptr_t)ptr; if (!h || u < h->Start + HDR_SIZE || u >= h->Brk) KiPoolPanic("invalid realloc pointer outside heap", h, (BLOCK_HDR*)0, ptr); BLOCK_HDR* blk = (BLOCK_HDR *)(u - HDR_SIZE); uintptr_t flags = KeAcquireSpinlock(&h->Lock); if (blk->Magic != BLOCK_MAGIC) KiPoolPanic("invalid realloc pointer/bad header magic", h, blk, ptr); KiPoolValidateFooterLocked(h, blk, "allocated footer corrupt on realloc"); if (blk->Free != BLOCK_STATE_USED) KiPoolPanic("realloc of non-allocated block", h, blk, ptr); KiPoolValidateTailCanaryLocked(h, blk, "tail canary corrupt on realloc"); size_t oldRequested = blk->RequestedSize; size_t oldCopy = oldRequested < newSize ? oldRequested : newSize; if (newSize <= oldRequested) { blk->RequestedSize = newSize; if (blk->Flags & BLOCK_FLAG_CANARY) KiPoolWriteTailCanary(blk); KeReleaseSpinlock(&h->Lock, flags); return ptr; } KeReleaseSpinlock(&h->Lock, flags); void* newPtr = KiHeapAllocEx(h, newSize, POOL_TAG_DEFAULT, caller); if (!newPtr) return (void*)0; memcpy(newPtr, ptr, oldCopy); KiHeapFreeEx(h, ptr, caller); return newPtr; } static void* KiHeapRealloc(HEAP* h, void* ptr, size_t newSize) { return KiHeapReallocEx(h, ptr, newSize, (uintptr_t)_ReturnAddress()); } // Kernel pool. static HEAP gPoolHeap; // 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; 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; #define MM_VAD_ARENA_BASE 0x0000100000000000ULL // Initializes the VAD list after the shared page table and kernel pool are ready. static void KiVadInitialize(void) { LIST_INIT(&gVadList); gVadLock = (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); gPoolHeap.FreeList = (BLOCK_HDR *)0; gPoolHeap.Start = POOL_BASE; gPoolHeap.Brk = POOL_BASE; gPoolHeap.Limit = POOL_LIMIT; gPoolHeap.MapFlags = PAGE_PRESENT | PAGE_WRITE | PAGE_NX; gPoolHeap.Lock = (SPINLOCK)SPINLOCK_INIT; gPoolHeap.DecommitTailOnFree = 0; gPoolHeap.NextSequence = 0; gPoolHeap.QuarantineCursor = 0; gPoolHeap.QuarantineCount = 0; memset(gPoolHeap.Quarantine, 0, sizeof(gPoolHeap.Quarantine)); if (poolSize) { poolSize = (poolSize + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); KiHeapGrow(&gPoolHeap, 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); } // Allocates zeroed kernel-pool memory from the non-user heap. __declspec(dllexport) void* MmAllocatePool(size_t size) { return KiHeapAllocEx(&gPoolHeap, size, POOL_TAG_DEFAULT, (uintptr_t)_ReturnAddress()); } // Allocates zeroed kernel-pool memory with a diagnostic tag. __declspec(dllexport) void* MmAllocatePoolTag(size_t size, uint32_t tag) { return KiHeapAllocEx(&gPoolHeap, size, tag, (uintptr_t)_ReturnAddress()); } // Releases kernel-pool memory back to the non-user heap. __declspec(dllexport) void MmFreePool(void* ptr) { KiHeapFreeEx(&gPoolHeap, ptr, (uintptr_t)_ReturnAddress()); } // Reallocates kernel-pool memory while preserving existing contents. __declspec(dllexport) void* MmReallocatePool(void* ptr, size_t newSize) { return KiHeapReallocEx(&gPoolHeap, ptr, newSize, (uintptr_t)_ReturnAddress()); } // 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 int 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 STATUS_INVALID_PARAMETER; if (!gVadListReady) return STATUS_FAILURE; uintptr_t candidate = KiAlignUpPtr(MM_VAD_ARENA_BASE, alignment); if (!candidate) return STATUS_INVALID_PARAMETER; for (;;) { if (candidate < MM_VAD_ARENA_BASE || reservationSize > (size_t)(USER_LIMIT - candidate)) return STATUS_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 STATUS_NO_MEMORY; collided = 1; break; } } if (!collided) { *baseOut = candidate; return STATUS_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 int KiVadRangeCommitted(const MM_VAD* vad, uintptr_t alignedStart, uintptr_t alignedEnd) { if (!vad || alignedStart < vad->Base || alignedEnd > vad->Base + vad->PayloadSize) return 0; size_t first = KiVadPageIndex(vad, alignedStart); size_t count = KiVadRangePageCount(alignedStart, alignedEnd); if (first > vad->PageCount || count > vad->PageCount - first) return 0; for (size_t i = 0; i < count; ++i) { if (vad->Pages[first + i].State != MmVadPageCommitted) return 0; } return 1; } 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 STATUS_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 STATUS_NOT_FOUND; uint64_t pageDelta = (uint64_t)index * (uint64_t)PAGE_SIZE; uint64_t fileOffset = vad->FileOffset + pageDelta; if (fileOffset < vad->FileOffset) return STATUS_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 STATUS_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 && MmPteTestAndClearDirty(va)) (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 STATUS_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 STATUS_NOT_FOUND; if (!page->PageFlags) return STATUS_ACCESS_DENIED; if ((page->PageFlags & PAGE_USER) == 0) return STATUS_ACCESS_DENIED; if (wantWrite && (page->PageFlags & PAGE_WRITE) == 0) return STATUS_ACCESS_DENIED; if (wantExecute && (page->PageFlags & PAGE_NX)) return STATUS_ACCESS_DENIED; return STATUS_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 && (_InterlockedCompareExchange((volatile long*)&page->Flags, 0, 0) & 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 int KiVadTryBeginPaging(MM_VAD_PAGE* page) { if (!page) return 0; for (;;) { uint32_t oldFlags = (uint32_t)_InterlockedCompareExchange((volatile long*)&page->Flags, 0, 0); if (oldFlags & MM_VAD_PAGE_PAGING) return 0; uint32_t newFlags = oldFlags | MM_VAD_PAGE_PAGING; if ((uint32_t)_InterlockedCompareExchange((volatile long*)&page->Flags, (long)newFlags, (long)oldFlags) == oldFlags) return 1; _mm_pause(); } } static void KiVadEndPaging(MM_VAD_PAGE* page) { if (!page) return; for (;;) { uint32_t oldFlags = (uint32_t)_InterlockedCompareExchange((volatile long*)&page->Flags, 0, 0); uint32_t newFlags = oldFlags & ~MM_VAD_PAGE_PAGING; if ((uint32_t)_InterlockedCompareExchange((volatile long*)&page->Flags, (long)newFlags, (long)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 STATUS_INVALID_PARAMETER; void* pageVa = MmPfnToVirtual(frame); if (!pageVa) return STATUS_FAILURE; memset(pageVa, 0, PAGE_SIZE); uint64_t pageDelta = (uint64_t)pageIndex * (uint64_t)PAGE_SIZE; if (pageDelta >= (uint64_t)vad->FileLength) return STATUS_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 STATUS_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 STATUS_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 STATUS_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 STATUS_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 STATUS_SUCCESS; } frame = MmPfnAllocate(MmPfnUseImage); if (!frame) return STATUS_NO_MEMORY; void* pageVa = MmPfnToVirtual(frame); if (!pageVa) { MmPfnFree(frame); return STATUS_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 STATUS_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 STATUS_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 STATUS_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 STATUS_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 STATUS_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 STATUS_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 STATUS_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 STATUS_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 STATUS_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 STATUS_SUCCESS; } PHYSICAL_ADDRESS frame = MmPfnAllocate(MmPfnUseAnonymous); if (!frame) { KiVadEndPaging(page); return STATUS_NO_MEMORY; } void* pageVa = MmPfnToVirtual(frame); if (!pageVa) { KiVadEndPaging(page); MmPfnFree(frame); return STATUS_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 STATUS_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 STATUS_NOT_FOUND; MM_VAD_PAGE* page = &vad->Pages[pageIndex]; if (page->State != MmVadPageCommitted || !(page->Flags & MM_VAD_PAGE_COW)) return STATUS_ACCESS_DENIED; if (!page->Resident || !page->Frame) return STATUS_NOT_FOUND; if ((page->PageFlags & PAGE_WRITE) == 0) return STATUS_ACCESS_DENIED; PHYSICAL_ADDRESS newFrame = MmPfnAllocate(MmPfnUseCow); if (!newFrame) return STATUS_NO_MEMORY; void* newPage = MmPfnToVirtual(newFrame); if (!newPage) { MmPfnFree(newFrame); return STATUS_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 STATUS_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 STATUS_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 STATUS_INVALID_PARAMETER; if (start > (uintptr_t)-1 - (size - 1)) return STATUS_NO_MEMORY; uintptr_t alignedStart = start & PAGE_MASK; uintptr_t alignedEnd = (start + size + PAGE_SIZE - 1) & PAGE_MASK; if (alignedEnd <= alignedStart) return STATUS_NO_MEMORY; uintptr_t procFlags = KeAcquireSpinlock(&process->VadLock); MM_VAD* vad = KiFindReservedVadContainingLocked(process, alignedStart, alignedEnd - alignedStart); KeReleaseSpinlock(&process->VadLock, procFlags); if (!vad) return STATUS_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 STATUS_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 STATUS_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 STATUS_INVALID_PARAMETER; uintptr_t start = (uintptr_t)base; if (start > (uintptr_t)-1 - (size - 1)) return STATUS_NO_MEMORY; uintptr_t alignedStart = start & PAGE_MASK; uintptr_t alignedEnd = (start + size + PAGE_SIZE - 1) & PAGE_MASK; if (alignedEnd <= alignedStart) return STATUS_NO_MEMORY; uintptr_t procFlags = KeAcquireSpinlock(&process->VadLock); MM_VAD* vad = KiFindReservedVadContainingLocked(process, alignedStart, alignedEnd - alignedStart); KeReleaseSpinlock(&process->VadLock, procFlags); if (!vad) return STATUS_NOT_FOUND; if (vad->Type != MmVadImage) return STATUS_INVALID_PARAMETER; size_t first = KiVadPageIndex(vad, alignedStart); size_t count = KiVadRangePageCount(alignedStart, alignedEnd); if (first > vad->PageCount || count > vad->PageCount - first) return STATUS_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 STATUS_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 STATUS_INVALID_PARAMETER; memset(out, 0, sizeof(*out)); if (!KiUserRangeValid(address, 1)) return STATUS_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 STATUS_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 STATUS_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 STATUS_FAILURE; } return STATUS_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 STATUS_INVALID_PARAMETER; uintptr_t procFlags = KeAcquireSpinlock(&process->VadLock); MM_VAD* vad = KiFindReservedVadContainingLocked(process, pageBase, PAGE_SIZE); if (!vad) { KeReleaseSpinlock(&process->VadLock, procFlags); return STATUS_NOT_FOUND; } if (vad->LockCount == UINT32_MAX) { KeReleaseSpinlock(&process->VadLock, procFlags); return STATUS_FAILURE; } vad->LockCount++; *vadOut = vad; KeReleaseSpinlock(&process->VadLock, procFlags); return STATUS_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, int writeAccess, MM_LOCKED_USER_BUFFER** out) { if (!out) return STATUS_INVALID_PARAMETER; *out = (MM_LOCKED_USER_BUFFER*)0; if (size == 0) return STATUS_SUCCESS; if (!process || !userAddress) return STATUS_INVALID_PARAMETER; uintptr_t start = (uintptr_t)userAddress; if (!KiUserRangeValid(start, size)) return STATUS_ACCESS_DENIED; uintptr_t alignedStart = start & PAGE_MASK; uintptr_t alignedEnd = (start + size + PAGE_SIZE - 1) & PAGE_MASK; if (alignedEnd <= alignedStart) return STATUS_NO_MEMORY; size_t pageCount = KiVadRangePageCount(alignedStart, alignedEnd); if (!pageCount) return STATUS_INVALID_PARAMETER; if (pageCount > (((size_t)-1) - sizeof(MM_LOCKED_USER_BUFFER)) / (sizeof(MM_PINNED_USER_PAGE) + sizeof(MM_VAD*))) return STATUS_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 STATUS_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 = STATUS_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 STATUS_SUCCESS; } // 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 STATUS_SUCCESS; if (!dst || !src) return STATUS_INVALID_PARAMETER; uintptr_t user = toUser ? (uintptr_t)dst : (uintptr_t)src; if (!KiUserRangeValid(user, size)) return STATUS_ACCESS_DENIED; PROCESS* process = KeGetCurrentProcess(); if (!process) return STATUS_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 STATUS_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); } // 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 STATUS_INVALID_PARAMETER; *outBase = (void*)0; size_t payloadSize = (size + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); if (payloadSize < size) return STATUS_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 STATUS_NO_MEMORY; if (alignment < PAGE_SIZE) alignment = PAGE_SIZE; if (alignment & (alignment - 1)) return STATUS_INVALID_PARAMETER; size_t pageCount = payloadSize / PAGE_SIZE; if (!pageCount) return STATUS_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 STATUS_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 STATUS_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 STATUS_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 STATUS_INVALID_PARAMETER; uintptr_t start = (uintptr_t)base; if (start > (uintptr_t)-1 - (size - 1)) return STATUS_NO_MEMORY; uintptr_t alignedStart = start & PAGE_MASK; uintptr_t alignedEnd = (start + size + PAGE_SIZE - 1) & PAGE_MASK; if (alignedEnd <= alignedStart) return STATUS_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 STATUS_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 STATUS_ACCESS_DENIED; uint8_t* newlyCommitted = (uint8_t*)MmAllocatePool(count ? count : 1); if (!newlyCommitted) return STATUS_NO_MEMORY; memset(newlyCommitted, 0, count ? count : 1); KSTATUS s = STATUS_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 STATUS_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 STATUS_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 STATUS_INVALID_PARAMETER; *outBase = (void*)0; if (fileOffset & (PAGE_SIZE - 1)) return STATUS_INVALID_PARAMETER; size_t alignedSize = (size + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); if (!alignedSize || alignedSize < size) return STATUS_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 STATUS_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 STATUS_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 STATUS_INVALID_PARAMETER; *outBase = (void*)0; const IMAGE_SECTION_METADATA* metadata = LdrImageSectionMetadata(section); if (!metadata || !metadata->ImageSize || !metadata->PageCount) return STATUS_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 STATUS_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 STATUS_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 STATUS_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 STATUS_INVALID_PARAMETER; *outBase = (void*)0; uintptr_t start = (uintptr_t)sourceBase; if (start & (PAGE_SIZE - 1)) return STATUS_INVALID_PARAMETER; size_t alignedSize = (size + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); if (!alignedSize || alignedSize < size) return STATUS_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 STATUS_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 STATUS_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 = STATUS_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 STATUS_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 STATUS_INVALID_PARAMETER; *outBase = (void*)0; size_t alignedSize = (payloadSize + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); if (!alignedSize || alignedSize / PAGE_SIZE > pageCount) return STATUS_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 STATUS_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 STATUS_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 STATUS_INVALID_PARAMETER; *outBase = (void*)0; size_t alignedSize = (payloadSize + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1); if (!alignedSize || alignedSize / PAGE_SIZE > pageCount) return STATUS_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 STATUS_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 = STATUS_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 STATUS_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 int KiVadPageCanDiscardCleanFile(const MM_VAD* vad, const MM_VAD_PAGE* page) { if (!vad || !page || vad->Type != MmVadMappedFile) return 0; if (!vad->FileObject || page->State != MmVadPageCommitted) return 0; if (!page->Resident || !page->Frame || !page->PageFlags) return 0; if ((page->Flags & MM_VAD_PAGE_FILE_BACKED) == 0) return 0; if (page->PageFlags & PAGE_WRITE) return 0; 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 0; return 1; } static int KiVadPageCanDiscardCleanFileAfterPagingClaim(const MM_VAD* vad, const MM_VAD_PAGE* page) { if (!vad || !page || vad->Type != MmVadMappedFile) return 0; if (!vad->FileObject || page->State != MmVadPageCommitted) return 0; if (!page->Resident || !page->Frame || !page->PageFlags) return 0; if ((page->Flags & MM_VAD_PAGE_FILE_BACKED) == 0) return 0; if (page->PageFlags & PAGE_WRITE) return 0; if (page->Flags & (MM_VAD_PAGE_COW | MM_VAD_PAGE_ZERO_PAGE | MM_VAD_PAGE_SHARED_FRAME | MM_VAD_PAGE_SWAPPED)) return 0; 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 int KiVadPageCanSwap(const MM_VAD_PAGE* page) { if (!page || page->State != MmVadPageCommitted) return 0; if (!page->Resident || !page->Frame || !page->PageFlags) return 0; if ((page->Flags & MM_VAD_PAGE_FILE_BACKED) && ((page->PageFlags & PAGE_WRITE) == 0)) return 0; if (page->Flags & MM_VAD_PAGE_FILE_SHARED_WRITE) return 0; 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 0; return 1; } static int KiVadPageCanSwapAfterPagingClaim(const MM_VAD_PAGE* page) { if (!page || page->State != MmVadPageCommitted) return 0; if (!page->Resident || !page->Frame || !page->PageFlags) return 0; if ((page->Flags & MM_VAD_PAGE_FILE_BACKED) && ((page->PageFlags & PAGE_WRITE) == 0)) return 0; if (page->Flags & MM_VAD_PAGE_FILE_SHARED_WRITE) return 0; if (page->Flags & (MM_VAD_PAGE_COW | MM_VAD_PAGE_ZERO_PAGE | MM_VAD_PAGE_SHARED_FRAME | MM_VAD_PAGE_SWAPPED)) return 0; if ((page->Flags & MM_VAD_PAGE_PAGING) == 0) return 0; return 1; } // 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 STATUS_INVALID_PARAMETER; MM_VAD_PAGE* page = &vad->Pages[pageIndex]; if (!KiVadPageCanDiscardCleanFile(vad, page)) return STATUS_NOT_FOUND; PHYSICAL_ADDRESS frame = page->Frame; MM_PFN* pfn = MmPfnGet(frame); if (!pfn || pfn->PinCount) return STATUS_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 STATUS_NOT_FOUND; if (!KiVadTryBeginPaging(page)) return STATUS_NOT_FOUND; if (!KiVadPageCanDiscardCleanFileAfterPagingClaim(vad, page) || page->Frame != frame) { KiVadEndPaging(page); return STATUS_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 STATUS_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 STATUS_INVALID_PARAMETER; MM_VAD_PAGE* page = &vad->Pages[pageIndex]; if (!KiVadPageCanSwap(page)) return STATUS_NOT_FOUND; PHYSICAL_ADDRESS frame = page->Frame; MM_PFN* pfn = MmPfnGet(frame); if (!pfn || pfn->RefCount != 1 || pfn->PinCount) return STATUS_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 STATUS_NOT_FOUND; if (!KiVadPageCanSwapAfterPagingClaim(page) || page->Frame != frame) { KiVadEndPaging(page); return STATUS_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 STATUS_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 STATUS_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 STATUS_SUCCESS; } } return STATUS_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 STATUS_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 STATUS_NOT_SUPPORTED; KSTATUS cacheDrop = MmFileCacheDropOnePageForPressure(); if (KSUCCESS(cacheDrop)) return STATUS_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 STATUS_SUCCESS; } } if (!MmSwapIsEnabled()) return STATUS_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 STATUS_SUCCESS; } } return STATUS_NOT_FOUND; } KSTATUS MmVadSyncFileMappings(FILE_OBJECT* file) { if (!file) return STATUS_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); if (wasDirty) (void)KiVadMarkSharedFilePageDirty(vad, i, 0); if ((page->Flags & MM_VAD_PAGE_FILE_DIRTY_TRACK) == 0) { page->Flags |= MM_VAD_PAGE_FILE_DIRTY_TRACK; (void)MmPteProtectRange(va, PAGE_SIZE, KiVadHardwarePageFlags(page)); } } } KeReleaseSpinlock(&gVadLock, flags); return STATUS_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 STATUS_ACCESS_DENIED; if (!KiUserRangeValid(faultAddress, 1)) { gVadStats.NoVadFaults++; gVadStats.FailedFaults++; return STATUS_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 STATUS_NOT_FOUND; } size_t index = KiVadPageIndex(vad, va); if (index >= vad->PageCount || vad->Pages[index].State != MmVadPageCommitted) { gVadStats.UncommittedFaults++; gVadStats.FailedFaults++; return STATUS_NOT_FOUND; } if (!vad->Pages[index].PageFlags) { gVadStats.NoAccessFaults++; gVadStats.FailedFaults++; return STATUS_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 STATUS_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 STATUS_SUCCESS; } } gVadStats.ProtectionFaults++; gVadStats.FailedFaults++; return STATUS_ACCESS_DENIED; } KSTATUS s = KiVadEnsurePageResident(vad, index, wantWrite, wantExecute); if (!KSUCCESS(s)) { gVadStats.FailedFaults++; return s; } gVadStats.ResolvedFaults++; return STATUS_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 STATUS_ACCESS_DENIED; if (!gVadListReady) return STATUS_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 STATUS_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 STATUS_INVALID_PARAMETER; uintptr_t start = (uintptr_t)base; if (start > (uintptr_t)-1 - (size - 1)) return STATUS_NO_MEMORY; uintptr_t alignedStart = start & PAGE_MASK; uintptr_t alignedEnd = (start + size + PAGE_SIZE - 1) & PAGE_MASK; if (alignedEnd <= alignedStart) return STATUS_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 STATUS_NOT_FOUND; } if (vad->LockCount) return STATUS_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 STATUS_SUCCESS; } // Destroys a VAD and releases all resident payload pages. KSTATUS MmVadDestroy(PROCESS* process, void* base) { KeAssertIrql(PASSIVE_LEVEL); if (!process || !base) return STATUS_INVALID_PARAMETER; uintptr_t procFlags = KeAcquireSpinlock(&process->VadLock); MM_VAD* vad = KiFindVadByBaseLocked(process, base); if (!vad) { KeReleaseSpinlock(&process->VadLock, procFlags); return STATUS_NOT_FOUND; } if (vad->LockCount) { KeReleaseSpinlock(&process->VadLock, procFlags); return STATUS_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 STATUS_SUCCESS; } // Destroys every VAD owned by a process during teardown. void MmVadDestroyAllForProcess(PROCESS* process) { KeAssertIrql(PASSIVE_LEVEL); if (!process) return; for (;;) { 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 STATUS_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 STATUS_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 STATUS_SUCCESS; } KeReleaseSpinlock(&process->VadLock, procFlags); return STATUS_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 Pool Address operation. static void KiDebugDescribePoolAddress(uintptr_t addr) { DbgPrint("MM: kernel pool start=%016llx brk=%016llx limit=%016llx usage=%llu KiB\n", (unsigned long long)gPoolHeap.Start, (unsigned long long)gPoolHeap.Brk, (unsigned long long)gPoolHeap.Limit, (unsigned long long)((gPoolHeap.Brk - gPoolHeap.Start) / 1024)); if (addr < gPoolHeap.Start || addr >= gPoolHeap.Limit) return; if (addr >= gPoolHeap.Brk) { DbgPrint("MM: address %016llx is inside pool VA range but above committed brk by +0x%llx\n", (unsigned long long)addr, (unsigned long long)(addr - gPoolHeap.Brk)); return; } uintptr_t flags = KeAcquireSpinlock(&gPoolHeap.Lock); for (BLOCK_HDR* cur = (BLOCK_HDR*)gPoolHeap.Start; (uintptr_t)cur < gPoolHeap.Brk; cur = KiNextBlock(cur)) { if (cur->Magic != BLOCK_MAGIC) { DbgPrint("MM: pool walk corrupt at hdr=%p magic=%08x\n", cur, cur->Magic); break; } uintptr_t hdr = (uintptr_t)cur; uintptr_t data = hdr + HDR_SIZE; uintptr_t end = data + cur->Size; uintptr_t ftr = end; BLOCK_FTR* footer = KiHdrToFtr(cur); if ((uintptr_t)footer + FTR_SIZE > gPoolHeap.Brk || footer->Magic != BLOCK_MAGIC || footer->Size != cur->Size || footer->Free != cur->Free) { DbgPrint("MM: pool walk corrupt footer hdr=%p state=%u size=%llx footer=%p fmagic=%08x fstate=%u fsize=%llx\n", cur, cur->Free, (unsigned long long)cur->Size, footer, ((uintptr_t)footer + FTR_SIZE <= gPoolHeap.Brk) ? footer->Magic : 0, ((uintptr_t)footer + FTR_SIZE <= gPoolHeap.Brk) ? footer->Free : 0, ((uintptr_t)footer + FTR_SIZE <= gPoolHeap.Brk) ? (unsigned long long)footer->Size : 0ULL); break; } if (addr >= hdr && addr < ftr + FTR_SIZE) { DbgPrint("MM: address %016llx in pool block hdr=%016llx data=%016llx-%016llx size=%llx req=%llx state=%u(%s) tag=%08x flags=%08x seq=%llu alloc=%p free=%p\n", (unsigned long long)addr, (unsigned long long)hdr, (unsigned long long)data, (unsigned long long)end, (unsigned long long)cur->Size, (unsigned long long)cur->RequestedSize, cur->Free, KiPoolStateName(cur->Free), cur->Tag, cur->Flags, (unsigned long long)cur->Sequence, (void*)cur->AllocCaller, (void*)cur->FreeCaller); break; } if (KiNextBlock(cur) <= cur) { DbgPrint("MM: pool walk stopped: non-advancing block hdr=%p size=%llx\n", cur, (unsigned long long)cur->Size); break; } } KeReleaseSpinlock(&gPoolHeap.Lock, flags); } // 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) { KiDebugDescribePoolAddress(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 kernel Pool Usage. size_t MmGetKernelPoolUsage(void) { // Returns bytes committed to the kernel pool. return (size_t)(gPoolHeap.Brk - gPoolHeap.Start); } // 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; }