replace the VHD/diskpart disk image pipeline with a directory-based vvfat root, we'll see when it breaks, fix /O2, add an all_tests, generalize the fat32 driver into a fat 16+32 driver, change policy on volume drivers, collapse KCPU's fields into a single atomic KE_CPU_STATE state machine, move scheduler ticking to per-CPU LAPIC timers, gate verbose SMP topology logging behind selftest diagnostics and add SMP pool-checkpoint validation around AP admission, add checked intrusive list helpers behind a new KE_DEBUG_LIST_CHECKS flag, add ObInitializeObjectHeader/ObTryReferenceObject, add waitable display clock objects and a SYS_OPEN_DISPLAY_CLOCK syscall for vsync-style waits, refactor kernel input into a per-queue KE_INPUT_CONTEXT with its own key/mouse ring buffers and DPC-driven key repeat, add KeInputQueuePeekEvent/ReadEvent/WaitKey and a SYS_QUERY_GLOBAL_INPUT_STATE syscall, split the kernel pool allocator out of mm_virt, new video files!
This commit is contained in:
1 parent
485a6bbfe7
commit
8b254f84f4
74 files changed
+4488
-2503
No files matched your search
+5
-757
@@ -1,5 +1,5 @@
|
||||
// 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 virtual-memory manager. This file owns VAD bookkeeping, faults, and user-copy validation.
|
||||
// Pool allocation lives in mm_pool.c and page-table construction lives in mm_pte.c.
|
||||
|
||||
// Virtual address layout:
|
||||
// 0000000000001000-00007FFFFFFFF000 is the lower-half user range.
|
||||
@@ -12,9 +12,6 @@
|
||||
// The current VM model uses one shared PML4, so user pointers are globally
|
||||
// meaningful while protection and lifetime are still tracked per process VAD.
|
||||
|
||||
// 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.
|
||||
@@ -22,6 +19,7 @@
|
||||
#include <string.h>
|
||||
#include <intrin.h>
|
||||
#include <kernel/mm_virt.h>
|
||||
#include <kernel/mm_pool.h>
|
||||
#include <kernel/mm_pte.h>
|
||||
#include <kernel/mm_pfn.h>
|
||||
#include <kernel/mm_swap.h>
|
||||
@@ -86,649 +84,6 @@ __declspec(dllexport) KSTATUS CopyToUser(void* userDst, const void* kernelSrc, s
|
||||
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=0x%016llX hdr=0x%016llX heap=[%016llx,%016llx) magic=%08x state=%u(%s) size=%llu req=%llu tag=%08x flags=%08x seq=%llu alloc=0x%016llX free=0x%016llX\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) : "<null>",
|
||||
(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,
|
||||
@@ -803,22 +158,7 @@ static void KiVadInitialize(void) {
|
||||
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);
|
||||
}
|
||||
MmPoolInitialize(poolSize);
|
||||
|
||||
KiVadInitialize();
|
||||
|
||||
@@ -830,26 +170,6 @@ void MmInitializeVirtual(PHYSICAL_ADDRESS bootPml4Phys, size_t poolSize) {
|
||||
(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;
|
||||
@@ -3122,72 +2442,6 @@ void MmVadDebugDumpProcess(PROCESS* process) {
|
||||
if (!count) DbgPrint(" <none>\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=0x%016llX 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=0x%016llX state=%u size=%llx footer=0x%016llX 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=0x%016llX free=0x%016llX\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=0x%016llX 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;
|
||||
@@ -3200,7 +2454,7 @@ void MmDebugDescribeAddress(uintptr_t addr) {
|
||||
(unsigned long long)flags);
|
||||
|
||||
if (addr >= POOL_BASE && addr < POOL_LIMIT) {
|
||||
KiDebugDescribePoolAddress(addr);
|
||||
MmPoolDebugDescribeAddress(addr);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3334,12 +2588,6 @@ __declspec(dllexport) void MmFreeUserImage(void* ptr, size_t size) {
|
||||
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;
|
||||
|
||||
Reference in new issue
Block a user