/* * Process Hacker - * file-based allocator * * Copyright (C) 2011 wj32 * * This file is part of Process Hacker. * * Process Hacker is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Process Hacker is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Process Hacker. If not, see . */ /* * File pool allows blocks of storage to be allocated from a file. Each file looks like this: * * Segment 0 __________________________________________________________ * | | | | | * | Block Header | File Header | Block Header | Segment Header | * |______________|________________|______________|________________| * | | | | | * | Block Header | User Data | Block Header | User Data | * |______________|________________|______________|________________| * | | | | | * | ... | ... | ... | ... | * |______________|________________|______________|________________| * Segment 1 __________________________________________________________ * | | | | | * | Block Header | Segment Header | Block Header | User Data | * |______________|________________|______________|________________| * | | | | | * | ... | ... | ... | ... | * |______________|________________|______________|________________| * Segment 2 __________________________________________________________ * | | | | | * | ... | ... | ... | ... | * |______________|________________|______________|________________| * */ /* * A file consists of a variable number of segments, with the segment size specified as a power of * two. Each segment contains a fixed number of blocks, leading to a variable block size. Every * allocation made by the user is an allocation of a certain number of blocks, with enough space for * the block header. This is placed at the beginning of each allocation and contains the number of * blocks in the allocation (a better name for it would be the allocation header). * * Block management in each segment is handled by a bitmap which is stored in the segment header at * the beginning of each segment. The first segment (segment 0) is special with the file header * being placed immediately after an initial block header. This is because the segment size is * stored in the file header, and without it we cannot calculate the block size, which is used to * locate everything else in the file. * * To speed up allocations a number of free lists are maintained which categorize each segment based * on how many free blocks they have. This means we can avoid trying to allocate from every existing * segment before finding out we have to allocate a new segment, or trying to allocate from segments * without the required number of free blocks. The downside of this technique is that it doesn't * account for fragmentation within the allocation bitmap. * * Each segment is mapped in separately, and each view is cached. Even after a view becomes inactive * (has a reference count of 0) it remains mapped in until the maximum number of inactive views is * reached. */ #include #include #include /** * Creates or opens a file pool. * * \param Pool A variable which receives the file pool instance. * \param FileHandle A handle to the file. * \param ReadOnly TRUE to disallow writes to the file. * \param Parameters Parameters for on-disk and runtime structures. */ NTSTATUS PhCreateFilePool( _Out_ PPH_FILE_POOL *Pool, _In_ HANDLE FileHandle, _In_ BOOLEAN ReadOnly, _In_opt_ PPH_FILE_POOL_PARAMETERS Parameters ) { NTSTATUS status; PPH_FILE_POOL pool; LARGE_INTEGER fileSize; PH_FILE_POOL_PARAMETERS localParameters; BOOLEAN creating; HANDLE sectionHandle; PPH_FP_BLOCK_HEADER initialBlock; PPH_FP_FILE_HEADER header; ULONG i; if (Parameters) { PhpValidateFilePoolParameters(Parameters); } else { PhpSetDefaultFilePoolParameters(&localParameters); Parameters = &localParameters; } pool = PhAllocate(sizeof(PH_FILE_POOL)); memset(pool, 0, sizeof(PH_FILE_POOL)); pool->FileHandle = FileHandle; pool->ReadOnly = ReadOnly; if (!NT_SUCCESS(status = PhGetFileSize(FileHandle, &fileSize))) goto CleanupExit; creating = FALSE; // If the file is smaller than the page size, assume we're creating a new file. if (fileSize.QuadPart < PAGE_SIZE) { if (ReadOnly) { status = STATUS_BAD_FILE_TYPE; goto CleanupExit; } fileSize.QuadPart = PAGE_SIZE; if (!NT_SUCCESS(status = PhSetFileSize(FileHandle, &fileSize))) goto CleanupExit; creating = TRUE; } // Create a section. status = NtCreateSection( §ionHandle, SECTION_ALL_ACCESS, NULL, &fileSize, !ReadOnly ? PAGE_READWRITE : PAGE_READONLY, SEC_COMMIT, FileHandle ); if (!NT_SUCCESS(status)) goto CleanupExit; pool->SectionHandle = sectionHandle; // Map in the first segment, set up initial parameters, then remap the first segment. if (!NT_SUCCESS(status = PhFppMapRange(pool, 0, PAGE_SIZE, &initialBlock))) goto CleanupExit; header = (PPH_FP_FILE_HEADER)&initialBlock->Body; if (creating) { header->Magic = PH_FP_MAGIC; header->SegmentShift = Parameters->SegmentShift; header->SegmentCount = 1; for (i = 0; i < PH_FP_FREE_LIST_COUNT; i++) header->FreeLists[i] = -1; } else { if (header->Magic != PH_FP_MAGIC) { PhFppUnmapRange(pool, initialBlock); status = STATUS_BAD_FILE_TYPE; goto CleanupExit; } } pool->SegmentShift = header->SegmentShift; pool->SegmentSize = 1 << pool->SegmentShift; pool->BlockShift = pool->SegmentShift - PH_FP_BLOCK_COUNT_SHIFT; pool->BlockSize = 1 << pool->BlockShift; pool->FileHeaderBlockSpan = (sizeof(PH_FP_FILE_HEADER) + pool->BlockSize - 1) >> pool->BlockShift; pool->SegmentHeaderBlockSpan = (sizeof(PH_FP_SEGMENT_HEADER) + pool->BlockSize - 1) >> pool->BlockShift; // Unmap the first segment and remap with the new segment size. PhFppUnmapRange(pool, initialBlock); if (creating) { // Extend the section so it fits the entire first segment. if (!NT_SUCCESS(status = PhFppExtendRange(pool, pool->SegmentSize))) goto CleanupExit; } // Runtime structure initialization PhInitializeFreeList(&pool->ViewFreeList, sizeof(PH_FILE_POOL_VIEW), 32); pool->ByIndexSize = 32; pool->ByIndexBuckets = PhAllocate(sizeof(PPH_FILE_POOL_VIEW) * pool->ByIndexSize); memset(pool->ByIndexBuckets, 0, sizeof(PPH_FILE_POOL_VIEW) * pool->ByIndexSize); PhInitializeAvlTree(&pool->ByBaseSet, PhpFilePoolViewByBaseCompareFunction); pool->MaximumInactiveViews = Parameters->MaximumInactiveViews; InitializeListHead(&pool->InactiveViewsListHead); // File structure initialization pool->FirstBlockOfFirstSegment = PhFppReferenceSegment(pool, 0); pool->Header = (PPH_FP_FILE_HEADER)&pool->FirstBlockOfFirstSegment->Body; if (creating) { PPH_FP_BLOCK_HEADER segmentHeaderBlock; // Set up the first segment properly. pool->FirstBlockOfFirstSegment->Span = pool->FileHeaderBlockSpan; segmentHeaderBlock = (PPH_FP_BLOCK_HEADER)((PCHAR)pool->FirstBlockOfFirstSegment + (pool->FileHeaderBlockSpan << pool->BlockShift)); PhFppInitializeSegment(pool, segmentHeaderBlock, pool->FileHeaderBlockSpan); pool->Header->FreeLists[1] = 0; } CleanupExit: if (NT_SUCCESS(status)) { *Pool = pool; } else { // Don't close the file handle the user passed in. pool->FileHandle = NULL; PhDestroyFilePool(pool); } return status; } /** * Creates or opens a file pool. * * \param Pool A variable which receives the file pool instance. * \param FileName The file name of the file pool. * \param ReadOnly TRUE to disallow writes to the file. * \param ShareAccess The file access granted to other threads. * \param CreateDisposition The action to perform if the file does or does not exist. See * PhCreateFileWin32() for more information. * \param Parameters Parameters for on-disk and runtime structures. */ NTSTATUS PhCreateFilePool2( _Out_ PPH_FILE_POOL *Pool, _In_ PWSTR FileName, _In_ BOOLEAN ReadOnly, _In_ ULONG ShareAccess, _In_ ULONG CreateDisposition, _In_opt_ PPH_FILE_POOL_PARAMETERS Parameters ) { NTSTATUS status; PPH_FILE_POOL pool; HANDLE fileHandle; ULONG createStatus; if (!NT_SUCCESS(status = PhCreateFileWin32Ex( &fileHandle, FileName, !ReadOnly ? (FILE_GENERIC_READ | FILE_GENERIC_WRITE | DELETE) : FILE_GENERIC_READ, FILE_ATTRIBUTE_NORMAL, ShareAccess, CreateDisposition, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, &createStatus ))) { return status; } status = PhCreateFilePool(&pool, fileHandle, ReadOnly, Parameters); if (NT_SUCCESS(status)) { *Pool = pool; } else { if (!ReadOnly && createStatus == FILE_CREATED) { FILE_DISPOSITION_INFORMATION dispositionInfo; IO_STATUS_BLOCK isb; dispositionInfo.DeleteFile = TRUE; NtSetInformationFile(fileHandle, &isb, &dispositionInfo, sizeof(FILE_DISPOSITION_INFORMATION), FileDispositionInformation); } NtClose(fileHandle); } return status; } /** * Frees resources used by a file pool instance. * * \param Pool The file pool. */ VOID PhDestroyFilePool( _In_ _Post_invalid_ PPH_FILE_POOL Pool ) { ULONG i; PLIST_ENTRY head; PLIST_ENTRY entry; PPH_FILE_POOL_VIEW view; // Unmap all views. for (i = 0; i < Pool->ByIndexSize; i++) { if (head = Pool->ByIndexBuckets[i]) { entry = head; do { view = CONTAINING_RECORD(entry, PH_FILE_POOL_VIEW, ByIndexListEntry); entry = entry->Flink; PhFppUnmapRange(Pool, view->Base); PhFreeToFreeList(&Pool->ViewFreeList, view); } while (entry != head); } } if (Pool->ByIndexBuckets) PhFree(Pool->ByIndexBuckets); PhDeleteFreeList(&Pool->ViewFreeList); if (Pool->SectionHandle) NtClose(Pool->SectionHandle); if (Pool->FileHandle) NtClose(Pool->FileHandle); PhFree(Pool); } /** * Validates and corrects file pool parameters. * * \param Parameters The parameters structure which is validated and modified if necessary. */ NTSTATUS PhpValidateFilePoolParameters( _Inout_ PPH_FILE_POOL_PARAMETERS Parameters ) { NTSTATUS status = STATUS_SUCCESS; // 16 <= SegmentShift <= 28 if (Parameters->SegmentShift < 16) { Parameters->SegmentShift = 16; status = STATUS_SOME_NOT_MAPPED; } if (Parameters->SegmentShift > 28) { Parameters->SegmentShift = 28; status = STATUS_SOME_NOT_MAPPED; } return status; } /** * Creates default file pool parameters. * * \param Parameters The parameters structure which receives the default parameter values. */ VOID PhpSetDefaultFilePoolParameters( _Out_ PPH_FILE_POOL_PARAMETERS Parameters ) { Parameters->SegmentShift = 18; // 256kB Parameters->MaximumInactiveViews = 128; } /** * Allocates a block from a file pool. * * \param Pool The file pool. * \param Size The number of bytes to allocate. * \param Rva A variable which receives the relative virtual address of the allocated block. * * \return A pointer to the allocated block. You must call PhDereferenceFilePool() or * PhDereferenceFilePoolByRva() when you no longer need a reference to the block. * * \remarks The returned pointer is not valid beyond the lifetime of the file pool instance. Use the * relative virtual address if you need a permanent reference to the allocated block. */ PVOID PhAllocateFilePool( _Inout_ PPH_FILE_POOL Pool, _In_ ULONG Size, _Out_opt_ PULONG Rva ) { PPH_FP_BLOCK_HEADER blockHeader; ULONG numberOfBlocks; PPH_FP_BLOCK_HEADER firstBlock; PPH_FP_SEGMENT_HEADER segmentHeader; ULONG freeListIndex; ULONG freeListLimit; ULONG segmentIndex; ULONG nextSegmentIndex; ULONG newFreeListIndex; // Calculate the number of blocks needed for the allocation. numberOfBlocks = (FIELD_OFFSET(PH_FP_BLOCK_HEADER, Body) + Size + Pool->BlockSize - 1) >> Pool->BlockShift; if (numberOfBlocks > PH_FP_BLOCK_COUNT - Pool->SegmentHeaderBlockSpan) { // TODO: Perform a large allocation. return NULL; } // Scan each applicable free list and try to allocate from those segments. freeListLimit = PhFppComputeFreeListIndex(Pool, numberOfBlocks); for (freeListIndex = 0; freeListIndex <= freeListLimit; freeListIndex++) { segmentIndex = Pool->Header->FreeLists[freeListIndex]; while (segmentIndex != -1) { firstBlock = PhFppReferenceSegment(Pool, segmentIndex); if (!firstBlock) return NULL; segmentHeader = PhFppGetHeaderSegment(Pool, firstBlock); nextSegmentIndex = segmentHeader->FreeFlink; blockHeader = PhFppAllocateBlocks(Pool, firstBlock, segmentHeader, numberOfBlocks); if (blockHeader) goto BlockAllocated; PhFppDereferenceSegment(Pool, segmentIndex); segmentIndex = nextSegmentIndex; } } // No segments have the required number of contiguous free blocks. Allocate a new one. firstBlock = PhFppAllocateSegment(Pool, &segmentIndex); if (!firstBlock) return NULL; freeListIndex = 0; segmentHeader = PhFppGetHeaderSegment(Pool, firstBlock); blockHeader = PhFppAllocateBlocks(Pool, firstBlock, segmentHeader, numberOfBlocks); if (!blockHeader) { PhFppDereferenceSegment(Pool, segmentIndex); return NULL; } BlockAllocated: // Compute the new free list index of the segment and move it to another free list if necessary. newFreeListIndex = PhFppComputeFreeListIndex(Pool, segmentHeader->FreeBlocks); if (newFreeListIndex != freeListIndex) { PhFppRemoveFreeList(Pool, freeListIndex, segmentIndex, segmentHeader); PhFppInsertFreeList(Pool, newFreeListIndex, segmentIndex, segmentHeader); } if (Rva) { *Rva = PhFppEncodeRva(Pool, segmentIndex, firstBlock, &blockHeader->Body); } return &blockHeader->Body; } /** * Frees a block. * * \param Pool The file pool. * \param SegmentIndex The index of the segment containing the block. * \param FirstBlock The first block of the segment containing the block. * \param Block A pointer to the block. */ VOID PhpFreeFilePool( _Inout_ PPH_FILE_POOL Pool, _In_ ULONG SegmentIndex, _In_ PPH_FP_BLOCK_HEADER FirstBlock, _In_ PVOID Block ) { PPH_FP_SEGMENT_HEADER segmentHeader; ULONG oldFreeListIndex; ULONG newFreeListIndex; segmentHeader = PhFppGetHeaderSegment(Pool, FirstBlock); oldFreeListIndex = PhFppComputeFreeListIndex(Pool, segmentHeader->FreeBlocks); PhFppFreeBlocks(Pool, FirstBlock, segmentHeader, PhFppGetHeaderBlock(Pool, Block)); newFreeListIndex = PhFppComputeFreeListIndex(Pool, segmentHeader->FreeBlocks); // Move the segment into another free list if needed. if (newFreeListIndex != oldFreeListIndex) { PhFppRemoveFreeList(Pool, oldFreeListIndex, SegmentIndex, segmentHeader); PhFppInsertFreeList(Pool, newFreeListIndex, SegmentIndex, segmentHeader); } } /** * Frees a block allocated by PhAllocateFilePool(). * * \param Pool The file pool. * \param Block A pointer to the block. The pointer is no longer valid after you call this function. * Do not use PhDereferenceFilePool() or PhDereferenceFilePoolByRva(). */ VOID PhFreeFilePool( _Inout_ PPH_FILE_POOL Pool, _In_ PVOID Block ) { PPH_FILE_POOL_VIEW view; PPH_FP_BLOCK_HEADER firstBlock; view = PhFppFindViewByBase(Pool, Block); if (!view) PhRaiseStatus(STATUS_INVALID_PARAMETER_2); firstBlock = view->Base; PhpFreeFilePool(Pool, view->SegmentIndex, firstBlock, Block); PhFppDereferenceView(Pool, view); } /** * Frees a block allocated by PhAllocateFilePool(). * * \param Pool The file pool. * \param Rva The relative virtual address of the block. */ BOOLEAN PhFreeFilePoolByRva( _Inout_ PPH_FILE_POOL Pool, _In_ ULONG Rva ) { ULONG segmentIndex; ULONG offset; PPH_FP_BLOCK_HEADER firstBlock; offset = PhFppDecodeRva(Pool, Rva, &segmentIndex); if (offset == -1) return FALSE; firstBlock = PhFppReferenceSegment(Pool, segmentIndex); if (!firstBlock) return FALSE; PhpFreeFilePool(Pool, segmentIndex, firstBlock, (PCHAR)firstBlock + offset); PhFppDereferenceSegment(Pool, segmentIndex); return TRUE; } /** * Increments the reference count for the specified address. * * \param Pool The file pool. * \param Address An address. */ VOID PhReferenceFilePool( _Inout_ PPH_FILE_POOL Pool, _In_ PVOID Address ) { PhFppReferenceSegmentByBase(Pool, Address); } /** * Decrements the reference count for the specified address. * * \param Pool The file pool. * \param Address An address. */ VOID PhDereferenceFilePool( _Inout_ PPH_FILE_POOL Pool, _In_ PVOID Address ) { PhFppDereferenceSegmentByBase(Pool, Address); } /** * Obtains a pointer for a relative virtual address, incrementing the reference count of the * address. * * \param Pool The file pool. * \param Rva A relative virtual address. */ PVOID PhReferenceFilePoolByRva( _Inout_ PPH_FILE_POOL Pool, _In_ ULONG Rva ) { ULONG segmentIndex; ULONG offset; PPH_FP_BLOCK_HEADER firstBlock; if (Rva == 0) return NULL; offset = PhFppDecodeRva(Pool, Rva, &segmentIndex); if (offset == -1) return NULL; firstBlock = PhFppReferenceSegment(Pool, segmentIndex); if (!firstBlock) return NULL; return (PCHAR)firstBlock + offset; } /** * Decrements the reference count for the specified relative virtual address. * * \param Pool The file pool. * \param Rva A relative virtual address. */ BOOLEAN PhDereferenceFilePoolByRva( _Inout_ PPH_FILE_POOL Pool, _In_ ULONG Rva ) { ULONG segmentIndex; ULONG offset; offset = PhFppDecodeRva(Pool, Rva, &segmentIndex); if (offset == -1) return FALSE; PhFppDereferenceSegment(Pool, segmentIndex); return TRUE; } /** * Obtains a relative virtual address for a pointer. * * \param Pool The file pool. * \param Address A pointer. * * \return The relative virtual address. * * \remarks No reference counts are changed. */ ULONG PhEncodeRvaFilePool( _In_ PPH_FILE_POOL Pool, _In_ PVOID Address ) { PPH_FILE_POOL_VIEW view; if (!Address) return 0; view = PhFppFindViewByBase(Pool, Address); if (!view) PhRaiseStatus(STATUS_INVALID_PARAMETER_2); return PhFppEncodeRva(Pool, view->SegmentIndex, view->Base, Address); } /** * Retrieves user data. * * \param Pool The file pool. * \param Context A variable which receives the user data. */ VOID PhGetUserContextFilePool( _In_ PPH_FILE_POOL Pool, _Out_ PULONGLONG Context ) { *Context = Pool->Header->UserContext; } /** * Stores user data. * * \param Pool The file pool. * \param Context A variable which contains the user data. */ VOID PhSetUserContextFilePool( _Inout_ PPH_FILE_POOL Pool, _In_ PULONGLONG Context ) { Pool->Header->UserContext = *Context; } /** * Extends a file pool. * * \param Pool The file pool. * \param NewSize The new size of the file, in bytes. */ NTSTATUS PhFppExtendRange( _Inout_ PPH_FILE_POOL Pool, _In_ ULONG NewSize ) { LARGE_INTEGER newSectionSize; newSectionSize.QuadPart = NewSize; return NtExtendSection(Pool->SectionHandle, &newSectionSize); } /** * Maps in a view of a file pool. * * \param Pool The file pool. * \param Offset The offset of the view, in bytes. * \param Size The size of the view, in bytes. * \param Base A variable which receives the base address of the view. */ NTSTATUS PhFppMapRange( _Inout_ PPH_FILE_POOL Pool, _In_ ULONG Offset, _In_ ULONG Size, _Out_ PVOID *Base ) { NTSTATUS status; PVOID baseAddress; LARGE_INTEGER sectionOffset; SIZE_T viewSize; baseAddress = NULL; sectionOffset.QuadPart = Offset; viewSize = Size; status = NtMapViewOfSection( Pool->SectionHandle, NtCurrentProcess(), &baseAddress, 0, viewSize, §ionOffset, &viewSize, ViewShare, 0, !Pool->ReadOnly ? PAGE_READWRITE : PAGE_READONLY ); if (NT_SUCCESS(status)) *Base = baseAddress; return status; } /** * Unmaps a view of a file pool. * * \param Pool The file pool. * \param Base The base address of the view. */ NTSTATUS PhFppUnmapRange( _Inout_ PPH_FILE_POOL Pool, _In_ PVOID Base ) { return NtUnmapViewOfSection(NtCurrentProcess(), Base); } /** * Initializes a segment. * * \param Pool The file pool. * \param BlockOfSegmentHeader The block header of the span containing the segment header. * \param AdditionalBlocksUsed The number of blocks already allocated from the segment, excluding * the blocks comprising the segment header. */ VOID PhFppInitializeSegment( _Inout_ PPH_FILE_POOL Pool, _Out_ PPH_FP_BLOCK_HEADER BlockOfSegmentHeader, _In_ ULONG AdditionalBlocksUsed ) { PPH_FP_SEGMENT_HEADER segmentHeader; RTL_BITMAP bitmap; BlockOfSegmentHeader->Span = Pool->SegmentHeaderBlockSpan; segmentHeader = (PPH_FP_SEGMENT_HEADER)&BlockOfSegmentHeader->Body; RtlInitializeBitMap(&bitmap, segmentHeader->Bitmap, PH_FP_BLOCK_COUNT); RtlSetBits(&bitmap, 0, Pool->SegmentHeaderBlockSpan + AdditionalBlocksUsed); segmentHeader->FreeBlocks = PH_FP_BLOCK_COUNT - (Pool->SegmentHeaderBlockSpan + AdditionalBlocksUsed); segmentHeader->FreeFlink = -1; segmentHeader->FreeBlink = -1; } /** * Allocates a segment. * * \param Pool The file pool. * \param NewSegmentIndex A variable which receives the index of the new segment. * * \return A pointer to the first block of the segment. */ PPH_FP_BLOCK_HEADER PhFppAllocateSegment( _Inout_ PPH_FILE_POOL Pool, _Out_ PULONG NewSegmentIndex ) { ULONG newSize; ULONG segmentIndex; PPH_FP_BLOCK_HEADER firstBlock; PPH_FP_SEGMENT_HEADER segmentHeader; newSize = (Pool->Header->SegmentCount + 1) << Pool->SegmentShift; if (!NT_SUCCESS(PhFppExtendRange(Pool, newSize))) return NULL; segmentIndex = Pool->Header->SegmentCount++; firstBlock = PhFppReferenceSegment(Pool, segmentIndex); PhFppInitializeSegment(Pool, firstBlock, 0); segmentHeader = (PPH_FP_SEGMENT_HEADER)&firstBlock->Body; PhFppInsertFreeList(Pool, 0, segmentIndex, segmentHeader); *NewSegmentIndex = segmentIndex; return firstBlock; } /** * Retrieves the header of a segment. * * \param Pool The file pool. * \param FirstBlock The first block of the segment. */ PPH_FP_SEGMENT_HEADER PhFppGetHeaderSegment( _Inout_ PPH_FILE_POOL Pool, _In_ PPH_FP_BLOCK_HEADER FirstBlock ) { if (FirstBlock != Pool->FirstBlockOfFirstSegment) { return (PPH_FP_SEGMENT_HEADER)&FirstBlock->Body; } else { // In the first segment, the segment header is after the file header. return (PPH_FP_SEGMENT_HEADER)&((PPH_FP_BLOCK_HEADER)((PCHAR)FirstBlock + (Pool->FileHeaderBlockSpan << Pool->BlockShift)))->Body; } } VOID PhFppAddViewByIndex( _Inout_ PPH_FILE_POOL Pool, _Inout_ PPH_FILE_POOL_VIEW View ) { ULONG index; PLIST_ENTRY head; index = View->SegmentIndex & (Pool->ByIndexSize - 1); head = Pool->ByIndexBuckets[index]; if (head) { InsertHeadList(head, &View->ByIndexListEntry); } else { InitializeListHead(&View->ByIndexListEntry); Pool->ByIndexBuckets[index] = &View->ByIndexListEntry; } } VOID PhFppRemoveViewByIndex( _Inout_ PPH_FILE_POOL Pool, _Inout_ PPH_FILE_POOL_VIEW View ) { ULONG index; PLIST_ENTRY head; index = View->SegmentIndex & (Pool->ByIndexSize - 1); head = Pool->ByIndexBuckets[index]; assert(head); // Unlink the entry from the chain. RemoveEntryList(&View->ByIndexListEntry); if (&View->ByIndexListEntry == head) { // This entry is currently the chain head. // If this was the last entry in the chain, then indicate that the chain is empty. // Otherwise, choose a new head. if (IsListEmpty(head)) Pool->ByIndexBuckets[index] = NULL; else Pool->ByIndexBuckets[index] = head->Flink; } } /** * Finds a view for the specified segment. * * \param Pool The file pool. * \param SegmentIndex The index of the segment. * * \return The view for the segment, or NULL if no view is present for the segment. */ PPH_FILE_POOL_VIEW PhFppFindViewByIndex( _Inout_ PPH_FILE_POOL Pool, _In_ ULONG SegmentIndex ) { ULONG index; PLIST_ENTRY head; PLIST_ENTRY entry; PPH_FILE_POOL_VIEW view; index = SegmentIndex & (Pool->ByIndexSize - 1); head = Pool->ByIndexBuckets[index]; if (!head) return NULL; entry = head; do { view = CONTAINING_RECORD(entry, PH_FILE_POOL_VIEW, ByIndexListEntry); if (view->SegmentIndex == SegmentIndex) return view; entry = entry->Flink; } while (entry != head); return NULL; } LONG NTAPI PhpFilePoolViewByBaseCompareFunction( _In_ PPH_AVL_LINKS Links1, _In_ PPH_AVL_LINKS Links2 ) { PPH_FILE_POOL_VIEW view1 = CONTAINING_RECORD(Links1, PH_FILE_POOL_VIEW, ByBaseLinks); PPH_FILE_POOL_VIEW view2 = CONTAINING_RECORD(Links2, PH_FILE_POOL_VIEW, ByBaseLinks); return uintptrcmp((ULONG_PTR)view1->Base, (ULONG_PTR)view2->Base); } VOID PhFppAddViewByBase( _Inout_ PPH_FILE_POOL Pool, _Inout_ PPH_FILE_POOL_VIEW View ) { PhAddElementAvlTree(&Pool->ByBaseSet, &View->ByBaseLinks); } VOID PhFppRemoveViewByBase( _Inout_ PPH_FILE_POOL Pool, _Inout_ PPH_FILE_POOL_VIEW View ) { PhRemoveElementAvlTree(&Pool->ByBaseSet, &View->ByBaseLinks); } /** * Finds a view containing the specified address. * * \param Pool The file pool. * \param Base The address. * * \return The view containing the address, or NULL if no view is present for the address. */ PPH_FILE_POOL_VIEW PhFppFindViewByBase( _Inout_ PPH_FILE_POOL Pool, _In_ PVOID Base ) { PPH_FILE_POOL_VIEW view; PPH_AVL_LINKS links; PH_FILE_POOL_VIEW lookupView; // This is an approximate search to find the target view in which the specified address lies. lookupView.Base = Base; links = PhUpperDualBoundElementAvlTree(&Pool->ByBaseSet, &lookupView.ByBaseLinks); if (!links) return NULL; view = CONTAINING_RECORD(links, PH_FILE_POOL_VIEW, ByBaseLinks); if ((ULONG_PTR)Base < (ULONG_PTR)view->Base + Pool->SegmentSize) return view; return NULL; } PPH_FILE_POOL_VIEW PhFppCreateView( _Inout_ PPH_FILE_POOL Pool, _In_ ULONG SegmentIndex ) { PPH_FILE_POOL_VIEW view; PVOID base; // Map in the segment. if (!NT_SUCCESS(PhFppMapRange(Pool, SegmentIndex << Pool->SegmentShift, Pool->SegmentSize, &base))) return NULL; // Create and add the view entry. view = PhAllocateFromFreeList(&Pool->ViewFreeList); memset(view, 0, sizeof(PH_FILE_POOL_VIEW)); view->RefCount = 1; view->SegmentIndex = SegmentIndex; view->Base = base; PhFppAddViewByIndex(Pool, view); PhFppAddViewByBase(Pool, view); return view; } VOID PhFppDestroyView( _Inout_ PPH_FILE_POOL Pool, _Inout_ PPH_FILE_POOL_VIEW View ) { PhFppUnmapRange(Pool, View->Base); PhFppRemoveViewByIndex(Pool, View); PhFppRemoveViewByBase(Pool, View); PhFreeToFreeList(&Pool->ViewFreeList, View); } VOID PhFppActivateView( _Inout_ PPH_FILE_POOL Pool, _Inout_ PPH_FILE_POOL_VIEW View ) { RemoveEntryList(&View->InactiveViewsListEntry); Pool->NumberOfInactiveViews--; } VOID PhFppDeactivateView( _Inout_ PPH_FILE_POOL Pool, _Inout_ PPH_FILE_POOL_VIEW View ) { InsertHeadList(&Pool->InactiveViewsListHead, &View->InactiveViewsListEntry); Pool->NumberOfInactiveViews++; // If we have too many inactive views, destroy the least recent ones. while (Pool->NumberOfInactiveViews > Pool->MaximumInactiveViews) { PLIST_ENTRY lruEntry; PPH_FILE_POOL_VIEW lruView; lruEntry = RemoveTailList(&Pool->InactiveViewsListHead); Pool->NumberOfInactiveViews--; assert(lruEntry != &Pool->InactiveViewsListHead); lruView = CONTAINING_RECORD(lruEntry, PH_FILE_POOL_VIEW, InactiveViewsListEntry); PhFppDestroyView(Pool, lruView); } } VOID PhFppReferenceView( _Inout_ PPH_FILE_POOL Pool, _Inout_ PPH_FILE_POOL_VIEW View ) { if (View->RefCount == 0) { // The view is inactive, so make it active. PhFppActivateView(Pool, View); } View->RefCount++; } VOID PhFppDereferenceView( _Inout_ PPH_FILE_POOL Pool, _Inout_ PPH_FILE_POOL_VIEW View ) { if (--View->RefCount == 0) { if (View->SegmentIndex == 0) PhRaiseStatus(STATUS_INTERNAL_ERROR); PhFppDeactivateView(Pool, View); } } PPH_FP_BLOCK_HEADER PhFppReferenceSegment( _Inout_ PPH_FILE_POOL Pool, _In_ ULONG SegmentIndex ) { PPH_FILE_POOL_VIEW view; // Validate parameters. if (SegmentIndex != 0 && SegmentIndex >= Pool->Header->SegmentCount) return NULL; // Try to get a cached view. view = PhFppFindViewByIndex(Pool, SegmentIndex); if (view) { PhFppReferenceView(Pool, view); return (PPH_FP_BLOCK_HEADER)view->Base; } // No cached view, so create one. view = PhFppCreateView(Pool, SegmentIndex); if (!view) return NULL; return (PPH_FP_BLOCK_HEADER)view->Base; } VOID PhFppDereferenceSegment( _Inout_ PPH_FILE_POOL Pool, _In_ ULONG SegmentIndex ) { PPH_FILE_POOL_VIEW view; view = PhFppFindViewByIndex(Pool, SegmentIndex); if (!view) PhRaiseStatus(STATUS_INTERNAL_ERROR); PhFppDereferenceView(Pool, view); } VOID PhFppReferenceSegmentByBase( _Inout_ PPH_FILE_POOL Pool, _In_ PVOID Base ) { PPH_FILE_POOL_VIEW view; view = PhFppFindViewByBase(Pool, Base); if (!view) PhRaiseStatus(STATUS_INTERNAL_ERROR); PhFppReferenceView(Pool, view); } VOID PhFppDereferenceSegmentByBase( _Inout_ PPH_FILE_POOL Pool, _In_ PVOID Base ) { PPH_FILE_POOL_VIEW view; view = PhFppFindViewByBase(Pool, Base); if (!view) PhRaiseStatus(STATUS_INTERNAL_ERROR); PhFppDereferenceView(Pool, view); } /** * Allocates blocks from a segment. * * \param Pool The file pool. * \param FirstBlock The first block of the segment. * \param SegmentHeader The header of the segment. * \param NumberOfBlocks The number of blocks to allocate. * * \return The header of the allocated span, or NULL if there is an insufficient number of * contiguous free blocks for the allocation. */ PPH_FP_BLOCK_HEADER PhFppAllocateBlocks( _Inout_ PPH_FILE_POOL Pool, _In_ PPH_FP_BLOCK_HEADER FirstBlock, _Inout_ PPH_FP_SEGMENT_HEADER SegmentHeader, _In_ ULONG NumberOfBlocks ) { RTL_BITMAP bitmap; ULONG hintIndex; ULONG foundIndex; PPH_FP_BLOCK_HEADER blockHeader; if (FirstBlock != Pool->FirstBlockOfFirstSegment) hintIndex = Pool->SegmentHeaderBlockSpan; else hintIndex = Pool->SegmentHeaderBlockSpan + Pool->FileHeaderBlockSpan; RtlInitializeBitMap(&bitmap, SegmentHeader->Bitmap, PH_FP_BLOCK_COUNT); // Find a range of free blocks and mark them as in-use. foundIndex = RtlFindClearBitsAndSet(&bitmap, NumberOfBlocks, hintIndex); if (foundIndex == -1) { // No more space. return NULL; } SegmentHeader->FreeBlocks -= NumberOfBlocks; blockHeader = (PPH_FP_BLOCK_HEADER)((PCHAR)FirstBlock + (foundIndex << Pool->BlockShift)); blockHeader->Flags = 0; blockHeader->Span = NumberOfBlocks; return blockHeader; } /** * Frees blocks in a segment. * * \param Pool The file pool. * \param FirstBlock The first block of the segment. * \param SegmentHeader The header of the segment. * \param BlockHeader The header of the allocated span. */ VOID PhFppFreeBlocks( _Inout_ PPH_FILE_POOL Pool, _In_ PPH_FP_BLOCK_HEADER FirstBlock, _Inout_ PPH_FP_SEGMENT_HEADER SegmentHeader, _In_ PPH_FP_BLOCK_HEADER BlockHeader ) { RTL_BITMAP bitmap; ULONG startIndex; ULONG blockSpan; RtlInitializeBitMap(&bitmap, SegmentHeader->Bitmap, PH_FP_BLOCK_COUNT); // Mark the blocks as free. startIndex = (ULONG)((PCHAR)BlockHeader - (PCHAR)FirstBlock) >> Pool->BlockShift; blockSpan = BlockHeader->Span; RtlClearBits(&bitmap, startIndex, blockSpan); SegmentHeader->FreeBlocks += blockSpan; } /** * Computes the free list index (category) for a specified number of blocks. * * \param Pool The file pool. * \param NumberOfBlocks The number of free or required blocks. */ ULONG PhFppComputeFreeListIndex( _In_ PPH_FILE_POOL Pool, _In_ ULONG NumberOfBlocks ) { // Use a binary tree to speed up comparison. if (NumberOfBlocks >= PH_FP_BLOCK_COUNT / 64) { if (NumberOfBlocks >= PH_FP_BLOCK_COUNT / 2) { if (NumberOfBlocks >= PH_FP_BLOCK_COUNT - Pool->SegmentHeaderBlockSpan) return 0; else return 1; } else { if (NumberOfBlocks >= PH_FP_BLOCK_COUNT / 16) return 2; else return 3; } } else { if (NumberOfBlocks >= 4) { if (NumberOfBlocks >= PH_FP_BLOCK_COUNT / 256) return 4; else return 5; } else { if (NumberOfBlocks >= 1) return 6; else return 7; } } } /** * Inserts a segment into a free list. * * \param Pool The file pool. * \param FreeListIndex The index of a free list. * \param SegmentIndex The index of the segment. * \param SegmentHeader The header of the segment. */ BOOLEAN PhFppInsertFreeList( _Inout_ PPH_FILE_POOL Pool, _In_ ULONG FreeListIndex, _In_ ULONG SegmentIndex, _In_ PPH_FP_SEGMENT_HEADER SegmentHeader ) { ULONG oldSegmentIndex; PPH_FP_BLOCK_HEADER oldSegmentFirstBlock; PPH_FP_SEGMENT_HEADER oldSegmentHeader; oldSegmentIndex = Pool->Header->FreeLists[FreeListIndex]; // Try to reference the segment before we commit any changes. if (oldSegmentIndex != -1) { oldSegmentFirstBlock = PhFppReferenceSegment(Pool, oldSegmentIndex); if (!oldSegmentFirstBlock) return FALSE; } // Insert the segment into the list. SegmentHeader->FreeBlink = -1; SegmentHeader->FreeFlink = oldSegmentIndex; Pool->Header->FreeLists[FreeListIndex] = SegmentIndex; if (oldSegmentIndex != -1) { oldSegmentHeader = PhFppGetHeaderSegment(Pool, oldSegmentFirstBlock); oldSegmentHeader->FreeBlink = SegmentIndex; PhFppDereferenceSegment(Pool, oldSegmentIndex); } return TRUE; } /** * Removes a segment from a free list. * * \param Pool The file pool. * \param FreeListIndex The index of a free list. * \param SegmentIndex The index of the segment. * \param SegmentHeader The header of the segment. */ BOOLEAN PhFppRemoveFreeList( _Inout_ PPH_FILE_POOL Pool, _In_ ULONG FreeListIndex, _In_ ULONG SegmentIndex, _In_ PPH_FP_SEGMENT_HEADER SegmentHeader ) { ULONG flinkSegmentIndex; PPH_FP_BLOCK_HEADER flinkSegmentFirstBlock; PPH_FP_SEGMENT_HEADER flinkSegmentHeader; ULONG blinkSegmentIndex; PPH_FP_BLOCK_HEADER blinkSegmentFirstBlock; PPH_FP_SEGMENT_HEADER blinkSegmentHeader; flinkSegmentIndex = SegmentHeader->FreeFlink; blinkSegmentIndex = SegmentHeader->FreeBlink; // Try to reference the segments before we commit any changes. if (flinkSegmentIndex != -1) { flinkSegmentFirstBlock = PhFppReferenceSegment(Pool, flinkSegmentIndex); if (!flinkSegmentFirstBlock) return FALSE; } if (blinkSegmentIndex != -1) { blinkSegmentFirstBlock = PhFppReferenceSegment(Pool, blinkSegmentIndex); if (!blinkSegmentFirstBlock) { if (flinkSegmentIndex != -1) PhFppDereferenceSegment(Pool, flinkSegmentIndex); return FALSE; } } // Unlink the segment from the list. if (flinkSegmentIndex != -1) { flinkSegmentHeader = PhFppGetHeaderSegment(Pool, flinkSegmentFirstBlock); flinkSegmentHeader->FreeBlink = blinkSegmentIndex; PhFppDereferenceSegment(Pool, flinkSegmentIndex); } if (blinkSegmentIndex != -1) { blinkSegmentHeader = PhFppGetHeaderSegment(Pool, blinkSegmentFirstBlock); blinkSegmentHeader->FreeFlink = flinkSegmentIndex; PhFppDereferenceSegment(Pool, blinkSegmentIndex); } else { // The segment was the list head; select a new one. Pool->Header->FreeLists[FreeListIndex] = flinkSegmentIndex; } return TRUE; } /** * Retrieves the header of a block. * * \param Pool The file pool. * \param Block A pointer to the body of the block. */ PPH_FP_BLOCK_HEADER PhFppGetHeaderBlock( _In_ PPH_FILE_POOL Pool, _In_ PVOID Block ) { return CONTAINING_RECORD(Block, PH_FP_BLOCK_HEADER, Body); } /** * Creates a relative virtual address. * * \param Pool The file pool. * \param SegmentIndex The index of the segment containing \a Address. * \param FirstBlock The first block of the segment containing \a Address. * \param Address An address. */ ULONG PhFppEncodeRva( _In_ PPH_FILE_POOL Pool, _In_ ULONG SegmentIndex, _In_ PPH_FP_BLOCK_HEADER FirstBlock, _In_ PVOID Address ) { return (SegmentIndex << Pool->SegmentShift) + (ULONG)((PCHAR)Address - (PCHAR)FirstBlock); } /** * Decodes a relative virtual address. * * \param Pool The file pool. * \param Rva The relative virtual address. * \param SegmentIndex A variable which receives the segment index. * * \return An offset into the segment specified by \a SegmentIndex, or -1 if \a Rva is invalid. */ ULONG PhFppDecodeRva( _In_ PPH_FILE_POOL Pool, _In_ ULONG Rva, _Out_ PULONG SegmentIndex ) { ULONG segmentIndex; segmentIndex = Rva >> Pool->SegmentShift; if (segmentIndex >= Pool->Header->SegmentCount) return -1; *SegmentIndex = segmentIndex; return Rva & (Pool->SegmentSize - 1); }