mirror of
https://github.com/Idov31/NovaHypervisor
synced 2026-06-06 15:54:26 +00:00
427 lines
10 KiB
C++
427 lines
10 KiB
C++
#include "pch.h"
|
|
#include "PoolManager.h"
|
|
|
|
_IRQL_requires_max_(APC_LEVEL)
|
|
PoolManager::PoolManager() {
|
|
OBJECT_ATTRIBUTES objectAttributes{};
|
|
running = false;
|
|
this->allocationThread = NULL;
|
|
this->freeThread = NULL;
|
|
runningLock = Spinlock();
|
|
pagingAllocations.Lock = Spinlock();
|
|
eptHookAllocations.Lock = Spinlock();
|
|
allocationRequests = RequestList<ALLOCATION_TYPE>();
|
|
freeRequests = RequestList<POOL_ALLOCATION>();
|
|
|
|
InitializeObjectAttributes(&objectAttributes, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
|
|
InitializeListHead(&pagingAllocations.Head);
|
|
InitializeListHead(&eptHookAllocations.Head);
|
|
NovaHypervisorLog(TRACE_FLAG_DEBUG, "Initialized allocation lists.");
|
|
|
|
for (USHORT i = 0; i < MAX_INITIAL_ALLOCATIONS; i++) {
|
|
AllocateInternal(SPLIT_2MB_PAGING_TO_4KB_PAGE, true);
|
|
AllocateInternal(EPT_HOOK_PAGE, true);
|
|
}
|
|
NovaHypervisorLog(TRACE_FLAG_DEBUG, "Allocated initial blocks.");
|
|
|
|
NTSTATUS status = PsCreateSystemThread(&this->allocationThread, GENERIC_ALL, &objectAttributes, NULL, NULL,
|
|
(PKSTART_ROUTINE)&ProcessAllocationThread, this);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
ExRaiseStatus(status);
|
|
NovaHypervisorLog(TRACE_FLAG_DEBUG, "Started allocation thread.");
|
|
|
|
running = true;
|
|
status = PsCreateSystemThread(&this->freeThread, GENERIC_ALL, &objectAttributes, NULL, NULL,
|
|
(PKSTART_ROUTINE)&ProcessFreeThread, this);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
ExRaiseStatus(status);
|
|
NovaHypervisorLog(TRACE_FLAG_DEBUG, "Started free thread.");
|
|
}
|
|
|
|
_IRQL_requires_max_(APC_LEVEL)
|
|
PoolManager::~PoolManager() {
|
|
StopThreads();
|
|
|
|
auto FreeAllocations = [](_Inout_ PLIST_ENTRY head, _In_ Spinlock& lock) -> void {
|
|
PPOOL_ALLOCATION allocation = nullptr;
|
|
AutoLock<Spinlock> listLock(lock);
|
|
PLIST_ENTRY entry = head->Flink;
|
|
|
|
while (entry != head) {
|
|
PLIST_ENTRY nextEntry = entry->Flink;
|
|
allocation = CONTAINING_RECORD(entry, POOL_ALLOCATION, Entry);
|
|
RemoveEntryList(entry);
|
|
|
|
if (allocation->Address)
|
|
FreeVirtualMemory(allocation->Address);
|
|
FreeVirtualMemory(allocation);
|
|
entry = nextEntry;
|
|
}
|
|
};
|
|
|
|
FreeAllocations(&pagingAllocations.Head, pagingAllocations.Lock);
|
|
FreeAllocations(&eptHookAllocations.Head, eptHookAllocations.Lock);
|
|
}
|
|
|
|
_IRQL_requires_max_(APC_LEVEL)
|
|
bool PoolManager::AllocateInternal(_In_ ALLOCATION_TYPE type, _In_ bool isInit) {
|
|
SIZE_T allocationSize = 0;
|
|
PPOOL_ALLOCATION allocation = nullptr;
|
|
PLIST_ENTRY head = nullptr;
|
|
|
|
switch (type) {
|
|
case SPLIT_2MB_PAGING_TO_4KB_PAGE:
|
|
head = &pagingAllocations.Head;
|
|
allocationSize = sizeof(VMM_EPT_DYNAMIC_SPLIT);
|
|
break;
|
|
case EPT_HOOK_PAGE:
|
|
head = &eptHookAllocations.Head;
|
|
allocationSize = sizeof(EPT_HOOKED_PAGE_DETAIL);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
Spinlock& lock = (type == SPLIT_2MB_PAGING_TO_4KB_PAGE) ?
|
|
pagingAllocations.Lock : eptHookAllocations.Lock;
|
|
AutoLock<Spinlock> autoLock(lock);
|
|
allocation = AllocateVirtualMemory<PPOOL_ALLOCATION>(sizeof(POOL_ALLOCATION), false);
|
|
|
|
if (!allocation)
|
|
return false;
|
|
|
|
// For some reason, when allocating with ExAllocatePool2 it doesn't work so need to use "legacy" allocation.
|
|
allocation->Address = AllocateVirtualMemory<PVOID>(allocationSize, false, true);
|
|
|
|
if (!allocation->Address) {
|
|
FreeVirtualMemory(allocation);
|
|
return false;
|
|
}
|
|
UNREFERENCED_PARAMETER(isInit);
|
|
allocation->Type = type;
|
|
allocation->IsUsed = false;
|
|
|
|
InitializeListHead(&allocation->Entry);
|
|
InsertTailList(head, &allocation->Entry);
|
|
return true;
|
|
}
|
|
|
|
_IRQL_requires_max_(APC_LEVEL)
|
|
void PoolManager::FreeInternal(_In_ PVOID address, _In_ ALLOCATION_TYPE type) {
|
|
PPOOL_ALLOCATION allocation = nullptr;
|
|
PLIST_ENTRY head = nullptr;
|
|
SIZE_T allocationSize = 0;
|
|
|
|
switch (type) {
|
|
case SPLIT_2MB_PAGING_TO_4KB_PAGE:
|
|
head = &pagingAllocations.Head;
|
|
allocationSize = sizeof(VMM_EPT_DYNAMIC_SPLIT);
|
|
break;
|
|
case EPT_HOOK_PAGE:
|
|
head = &eptHookAllocations.Head;
|
|
allocationSize = sizeof(EPT_HOOKED_PAGE_DETAIL);
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
Spinlock& lock = (type == SPLIT_2MB_PAGING_TO_4KB_PAGE) ?
|
|
pagingAllocations.Lock : eptHookAllocations.Lock;
|
|
AutoLock<Spinlock> autoLock(lock);
|
|
|
|
for (PLIST_ENTRY entry = head->Flink; entry != head; entry = entry->Flink) {
|
|
allocation = CONTAINING_RECORD(entry, POOL_ALLOCATION, Entry);
|
|
|
|
if (allocation->Address == address) {
|
|
RtlSecureZeroMemory(allocation->Address, allocationSize);
|
|
allocation->IsUsed = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* FindFreeSlot is responsible for asking to free memory allocation by pushing it to the free requests queue.
|
|
*
|
|
* Parameters:
|
|
* @address [_In_ PVOID] -- The address to free.
|
|
* @type [_In_ ALLOCATION_TYPE] -- The type of the allocation.
|
|
*
|
|
* Returns:
|
|
* There is no return value.
|
|
*/
|
|
PVOID PoolManager::FindFreeSlot(_In_ ALLOCATION_TYPE type) {
|
|
PPOOL_ALLOCATION allocation = nullptr;
|
|
PLIST_ENTRY head = nullptr;
|
|
Spinlock* lock = nullptr;
|
|
|
|
switch (type) {
|
|
case SPLIT_2MB_PAGING_TO_4KB_PAGE:
|
|
head = &pagingAllocations.Head;
|
|
lock = &pagingAllocations.Lock;
|
|
break;
|
|
case EPT_HOOK_PAGE:
|
|
head = &eptHookAllocations.Head;
|
|
lock = &eptHookAllocations.Lock;
|
|
break;
|
|
default:
|
|
return nullptr;
|
|
}
|
|
AutoLock<Spinlock> autoLock(*lock);
|
|
|
|
for (PLIST_ENTRY entry = head->Flink; entry != head; entry = entry->Flink) {
|
|
allocation = CONTAINING_RECORD(entry, POOL_ALLOCATION, Entry);
|
|
|
|
if (!allocation->IsUsed) {
|
|
allocation->IsUsed = true;
|
|
return allocation->Address;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* Allocate is responsible for asking for an allocation by pushing it to the free requests queue.
|
|
*
|
|
* Parameters:
|
|
* @type [_In_ ALLOCATION_TYPE] -- The type of the allocation.
|
|
*
|
|
* Returns:
|
|
* @allocation [PVOID] -- The allocated memory if allocated else nullptr.
|
|
*/
|
|
PVOID PoolManager::Allocate(_In_ ALLOCATION_TYPE type) {
|
|
bool queued = false;
|
|
UINT64 waitCount = 0;
|
|
UINT32 wait = 1;
|
|
|
|
if (!IsValidAllocationType(type))
|
|
return nullptr;
|
|
PVOID allocation = FindFreeSlot(type);
|
|
|
|
if (allocation)
|
|
return allocation;
|
|
|
|
// Queuing the allocation request.
|
|
queued = allocationRequests.Insert(type);
|
|
|
|
if (!queued) {
|
|
do {
|
|
for (UINT32 i = 0; i < wait; i++)
|
|
_mm_pause();
|
|
queued = allocationRequests.Insert(type);
|
|
|
|
wait = wait * 2 > MAX_WAIT ? MAX_WAIT : wait * 2;
|
|
|
|
if (wait == MAX_WAIT)
|
|
waitCount++;
|
|
} while (!queued && waitCount < MAX_WAIT_ITERATIONS);
|
|
|
|
if (waitCount == MAX_WAIT_ITERATIONS)
|
|
return nullptr;
|
|
waitCount = 0;
|
|
wait = 1;
|
|
}
|
|
|
|
// Waiting it to be processed.
|
|
do {
|
|
for (UINT32 i = 0; i < wait; i++)
|
|
_mm_pause();
|
|
allocation = FindFreeSlot(type);
|
|
|
|
wait = wait * 2 > MAX_WAIT ? MAX_WAIT : wait * 2;
|
|
|
|
if (wait == MAX_WAIT)
|
|
waitCount++;
|
|
} while (!allocation && waitCount < MAX_WAIT_ITERATIONS);
|
|
return allocation;
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* Free is responsible for asking to free memory allocation by pushing it to the free requests queue.
|
|
*
|
|
* Parameters:
|
|
* @address [_In_ PVOID] -- The address to free.
|
|
* @type [_In_ ALLOCATION_TYPE] -- The type of the allocation.
|
|
*
|
|
* Returns:
|
|
* There is no return value.
|
|
*/
|
|
void PoolManager::Free(_In_ PVOID address, _In_ ALLOCATION_TYPE type) {
|
|
bool queued = false;
|
|
UINT64 waitCount = 0;
|
|
UINT32 wait = 1;
|
|
POOL_ALLOCATION allocation = { 0 };
|
|
|
|
if (!address || !IsValidAllocationType(type))
|
|
return;
|
|
|
|
allocation.Address = address;
|
|
allocation.Type = type;
|
|
|
|
queued = freeRequests.Insert(allocation);
|
|
|
|
if (!queued) {
|
|
do {
|
|
for (UINT32 i = 0; i < wait; i++)
|
|
_mm_pause();
|
|
queued = freeRequests.Insert(allocation);
|
|
|
|
wait = wait * 2 > MAX_WAIT ? MAX_WAIT : wait * 2;
|
|
|
|
if (wait == MAX_WAIT)
|
|
waitCount++;
|
|
} while (!queued && waitCount < MAX_WAIT_ITERATIONS);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* ProcessAllocation is responsible for periodically allocate memory.
|
|
*
|
|
* Parameters:
|
|
* There are no parameters.
|
|
*
|
|
* Returns:
|
|
* There is no return value.
|
|
*/
|
|
_IRQL_requires_(PASSIVE_LEVEL)
|
|
void PoolManager::ProcessAllocation() {
|
|
ALLOCATION_TYPE type;
|
|
bool isRunning = true;
|
|
|
|
auto UpdateRunningState = [&]() -> void {
|
|
runningLock.Lock();
|
|
isRunning = running;
|
|
runningLock.Unlock();
|
|
};
|
|
|
|
while (isRunning) {
|
|
if (allocationRequests.GetListSize() == 0) {
|
|
Sleep(DEFAULT_SLEEP_MS);
|
|
UpdateRunningState();
|
|
continue;
|
|
}
|
|
type = allocationRequests.Pop();
|
|
AllocateInternal(type);
|
|
UpdateRunningState();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* ProcessFree is responsible for periodically freeing memory.
|
|
*
|
|
* Parameters:
|
|
* There are no parameters.
|
|
*
|
|
* Returns:
|
|
* There is no return value.
|
|
*/
|
|
_IRQL_requires_(PASSIVE_LEVEL)
|
|
void PoolManager::ProcessFree() {
|
|
bool isRunning = true;
|
|
POOL_ALLOCATION allocation = { 0 };
|
|
|
|
auto UpdateRunningState = [&]() -> void {
|
|
runningLock.Lock();
|
|
isRunning = running;
|
|
runningLock.Unlock();
|
|
};
|
|
|
|
while (isRunning) {
|
|
if (freeRequests.GetListSize() == 0) {
|
|
Sleep(DEFAULT_SLEEP_MS);
|
|
UpdateRunningState();
|
|
continue;
|
|
}
|
|
allocation = freeRequests.Pop();
|
|
|
|
if (allocation.Address)
|
|
FreeInternal(allocation.Address, allocation.Type);
|
|
UpdateRunningState();
|
|
}
|
|
|
|
// Free all the remaining allocations.
|
|
if (freeRequests.GetListSize() > 0) {
|
|
while (freeRequests.GetListSize() > 0) {
|
|
allocation = freeRequests.Pop();
|
|
|
|
if (allocation.Address)
|
|
FreeInternal(allocation.Address, allocation.Type);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* StopThreads is responsible for stopping the allocation and free threads.
|
|
*
|
|
* Parameters:
|
|
* There are no parameters.
|
|
*
|
|
* Returns:
|
|
* There is no return value.
|
|
*/
|
|
void PoolManager::StopThreads() {
|
|
auto StopThread = [](HANDLE thread) -> void {
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
|
|
if (thread) {
|
|
do {
|
|
status = ZwWaitForSingleObject(thread, FALSE, NULL);
|
|
} while (status == STATUS_TIMEOUT);
|
|
ZwClose(thread);
|
|
}
|
|
};
|
|
runningLock.Lock();
|
|
|
|
if (running) {
|
|
running = false;
|
|
|
|
// Need to explicitly unlock here to prevent deadlock.
|
|
runningLock.Unlock();
|
|
StopThread(allocationThread);
|
|
allocationThread = NULL;
|
|
StopThread(freeThread);
|
|
freeThread = NULL;
|
|
}
|
|
else {
|
|
runningLock.Unlock();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* ProcessAllocationThread is responsible for periodically allocate memory.
|
|
*
|
|
* Parameters:
|
|
* @StartContext [_In_ PVOID] -- The context that holds the lock, if suppose to continue to run and interval.
|
|
*
|
|
* Returns:
|
|
* There is no return value.
|
|
*/
|
|
_IRQL_requires_(PASSIVE_LEVEL)
|
|
void ProcessAllocationThread(_In_ PVOID StartContext) {
|
|
PoolManager* poolManager = reinterpret_cast<PoolManager*>(StartContext);
|
|
poolManager->ProcessAllocation();
|
|
}
|
|
|
|
/*
|
|
* Description:
|
|
* ProcessFreeThread is responsible for periodically free memory.
|
|
*
|
|
* Parameters:
|
|
* @StartContext [_In_ PVOID] -- The context that holds the lock, if suppose to continue to run and interval.
|
|
*
|
|
* Returns:
|
|
* There is no return value.
|
|
*/
|
|
_IRQL_requires_(PASSIVE_LEVEL)
|
|
void ProcessFreeThread(_In_ PVOID StartContext) {
|
|
PoolManager* poolManager = reinterpret_cast<PoolManager*>(StartContext);
|
|
poolManager->ProcessFree();
|
|
}
|