Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1718 lines
45 KiB

/*++
Copyright (c) Microsoft Corporation. All rights reserved.
Module Name:
support.c
Abstract:
This module implements internal support routines
for the verification code.
Author:
Silviu Calinoiu (SilviuC) 1-Mar-2001
Revision History:
--*/
#include "pch.h"
#include "verifier.h"
#include "support.h"
#include "critsect.h"
#include "vspace.h"
#include "logging.h"
#include "tracker.h"
//
// Global data.
//
SYSTEM_BASIC_INFORMATION AVrfpSysBasicInfo;
//
// Global counters (for statistics).
//
ULONG AVrfpCounter[CNT_MAXIMUM_INDEX];
//
// Break triggers.
//
ULONG AVrfpBreak [BRK_MAXIMUM_INDEX];
/////////////////////////////////////////////////////////////////////
////////////////////////////////// Private ntdll entrypoints pointers
/////////////////////////////////////////////////////////////////////
PFN_RTLP_DEBUG_PAGE_HEAP_CREATE AVrfpRtlpDebugPageHeapCreate;
PFN_RTLP_DEBUG_PAGE_HEAP_DESTROY AVrfpRtlpDebugPageHeapDestroy;
PFN_RTLP_GET_STACK_TRACE_ADDRESS AVrfpGetStackTraceAddress;
//
// Exception logging support.
//
PAVRF_EXCEPTION_LOG_ENTRY AVrfpExceptionLog = NULL;
const ULONG AVrfpExceptionLogEntriesNo = 128;
LONG AVrfpExceptionLogCurrentIndex = 0;
PVOID AVrfpVectoredExceptionPointer;
//
// Internal functions declarations
//
LONG
NTAPI
AVrfpVectoredExceptionHandler (
struct _EXCEPTION_POINTERS * ExceptionPointers
);
VOID
AVrfpCheckFirstChanceException (
struct _EXCEPTION_POINTERS * ExceptionPointers
);
VOID
AVrfpInitializeExceptionChecking (
VOID
)
{
PVOID Handler;
//
// Establish a first chance exception handler.
//
Handler = RtlAddVectoredExceptionHandler (1, AVrfpVectoredExceptionHandler);
AVrfpVectoredExceptionPointer = Handler;
//
// Allocate memory for our exception logging database.
// If the allocation fails we will simply continue execution
// with this feature disabled.
//
ASSERT (AVrfpExceptionLog == NULL);
AVrfpExceptionLog = (PAVRF_EXCEPTION_LOG_ENTRY)
AVrfpAllocate (AVrfpExceptionLogEntriesNo * sizeof (AVRF_EXCEPTION_LOG_ENTRY));
}
VOID
AVrfpCleanupExceptionChecking (
VOID
)
{
//
// Establish a first chance exception handler.
//
if (AVrfpVectoredExceptionPointer) {
RtlRemoveVectoredExceptionHandler (AVrfpVectoredExceptionPointer);
}
//
// Free exception log database.
//
if (AVrfpExceptionLog) {
AVrfpFree (AVrfpExceptionLog);
AVrfpExceptionLog = NULL;
}
}
VOID
AVrfpLogException (
struct _EXCEPTION_POINTERS * ExceptionPointers
)
{
ULONG NewIndex;
if (AVrfpExceptionLog != NULL) {
NewIndex = (ULONG)InterlockedIncrement (&AVrfpExceptionLogCurrentIndex) % AVrfpExceptionLogEntriesNo;
AVrfpExceptionLog[NewIndex].ThreadId = NtCurrentTeb()->ClientId.UniqueThread;
AVrfpExceptionLog[NewIndex].ExceptionCode = ExceptionPointers->ExceptionRecord->ExceptionCode;
AVrfpExceptionLog[NewIndex].ExceptionAddress = ExceptionPointers->ExceptionRecord->ExceptionAddress;
AVrfpExceptionLog[NewIndex].ExceptionRecord = ExceptionPointers->ExceptionRecord;
AVrfpExceptionLog[NewIndex].ContextRecord = ExceptionPointers->ContextRecord;
}
}
LONG
NTAPI
AVrfpVectoredExceptionHandler (
struct _EXCEPTION_POINTERS * ExceptionPointers
)
{
DWORD ExceptionCode;
//
// We are holding RtlpCalloutEntryLock at this point
// so we are trying to protect ourselves with this other
// try...except against possible other exceptions
// (e.g. an inpage error) that could leave the lock orphaned.
//
try {
AVrfpLogException (ExceptionPointers);
AVrfpCheckFirstChanceException (ExceptionPointers);
ExceptionCode = ExceptionPointers->ExceptionRecord->ExceptionCode;
if ((AVrfpProvider.VerifierDebug & VRFP_DEBUG_EXCEPTIONS) != 0) {
DbgPrint ("AVRF: Exception %x from address %p\n",
ExceptionCode,
ExceptionPointers->ExceptionRecord->ExceptionAddress);
}
if (ExceptionCode == STATUS_INVALID_HANDLE) {
//
// RPC is using STATUS_INVALID_HANDLE exceptions with EXCEPTION_NONCONTINUABLE
// for a private notification mechanism. The exceptions we are looking for
// are coming from the kernel code and they don't have the EXCEPTION_NONCONTINUABLE
// flag set.
//
if ((ExceptionPointers->ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE) == 0) {
//
// Note. When run under debugger this message will not kick in when an
// exception gets raised because the debugger will break on first chance
// exception. Only if the debugger is launched with `-xd ch' (ignore
// first chance invalid handle exception) the message will be seen first.
// Otherwise you see a plain exception and only after you hit go in
// the debugger console you get the message.
//
VERIFIER_STOP (APPLICATION_VERIFIER_INVALID_HANDLE | APPLICATION_VERIFIER_CONTINUABLE_BREAK,
"invalid handle exception for current stack trace",
ExceptionCode, "Exception code.",
ExceptionPointers->ExceptionRecord, "Exception record. Use .exr to display it.",
ExceptionPointers->ContextRecord, "Context record. Use .cxr to display it.",
0, "");
//
// We are hiding this exception after the verifier stop so the callers
// of APIs like SetEvent with an invalid handle will not see the exception.
//
return EXCEPTION_CONTINUE_EXECUTION;
}
}
}
except (EXCEPTION_EXECUTE_HANDLER) {
// NOTHING;
}
return EXCEPTION_CONTINUE_SEARCH;
}
VOID
AVrfpDirtyThreadStack (
VOID
)
{
PTEB Teb = NtCurrentTeb();
ULONG_PTR StackStart;
ULONG_PTR StackEnd;
try {
StackStart = (ULONG_PTR)(Teb->NtTib.StackLimit);
//
// We dirty stacks only on x86 architectures.
//
#if defined(_X86_)
_asm mov StackEnd, ESP;
#else
StackEnd = StackStart;
#endif
//
// Limit stack dirtying to only 8K.
//
if (StackStart < StackEnd - 0x2000) {
StackStart = StackEnd - 0x2000;
}
if ((AVrfpProvider.VerifierDebug & VRFP_DEBUG_DIRTY_STACKS) != 0) {
DbgPrint ("Dirtying stack range %p - %p for thread %p \n",
StackStart, StackEnd, Teb->ClientId.UniqueThread);
}
while (StackStart < StackEnd) {
*((PULONG)StackStart) = 0xBAD1BAD1;
StackStart += sizeof(ULONG);
}
}
except (EXCEPTION_EXECUTE_HANDLER) {
// nothing
}
}
/////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////// Per thread table
/////////////////////////////////////////////////////////////////////
#define THREAD_TABLE_SIZE 61
LIST_ENTRY AVrfpThreadTable [THREAD_TABLE_SIZE];
RTL_CRITICAL_SECTION AVrfpThreadTableLock;
//
// Keep this constant so the debugger can read it.
//
const ULONG AVrfpThreadTableEntriesNo = THREAD_TABLE_SIZE;
//
// Keep this for debugging purposes.
//
AVRF_THREAD_ENTRY AVrfpMostRecentRemovedThreadEntry;
NTSTATUS
AVrfpThreadTableInitialize (
VOID
)
{
PAVRF_THREAD_ENTRY Entry;
ULONG I;
NTSTATUS Status;
Status = RtlInitializeCriticalSection (&AVrfpThreadTableLock);
if (!NT_SUCCESS(Status)) {
return Status;
}
for (I = 0; I < THREAD_TABLE_SIZE; I += 1) {
InitializeListHead (&(AVrfpThreadTable[I]));
}
//
// Create an entry for the current thread (main thread). The function
// is called during verifier!DllMain when there is a single thread
// running in the process.
//
Entry = AVrfpAllocate (sizeof *Entry);
if (Entry == NULL) {
return STATUS_NO_MEMORY;
}
Entry->Id = NtCurrentTeb()->ClientId.UniqueThread;
AVrfpThreadTableAddEntry (Entry);
return STATUS_SUCCESS;
}
VOID
AVrfpThreadTableAddEntry (
PAVRF_THREAD_ENTRY Entry
)
{
ULONG ChainIndex;
ASSERT (Entry != NULL);
ASSERT (Entry->Id != NULL);
ChainIndex = (HandleToUlong(Entry->Id) >> 2) % THREAD_TABLE_SIZE;
RtlEnterCriticalSection (&AVrfpThreadTableLock);
//
// It is important to add the new entry at the head of the list
// (not tail) because the list can contain zombies left after someone
// called TerminateThread and the thread handle value got reused.
//
InsertHeadList (&(AVrfpThreadTable[ChainIndex]),
&(Entry->HashChain));
RtlLeaveCriticalSection (&AVrfpThreadTableLock);
}
VOID
AVrfpThreadTableRemoveEntry (
PAVRF_THREAD_ENTRY Entry
)
{
RtlEnterCriticalSection (&AVrfpThreadTableLock);
RtlCopyMemory (&AVrfpMostRecentRemovedThreadEntry,
Entry,
sizeof (AVrfpMostRecentRemovedThreadEntry));
RemoveEntryList (&(Entry->HashChain));
RtlLeaveCriticalSection (&AVrfpThreadTableLock);
}
PAVRF_THREAD_ENTRY
AVrfpThreadTableSearchEntry (
HANDLE Id
)
{
ULONG ChainIndex;
PLIST_ENTRY Current;
PAVRF_THREAD_ENTRY Entry;
PAVRF_THREAD_ENTRY Result;
ChainIndex = (HandleToUlong(Id) >> 2) % THREAD_TABLE_SIZE;
Result = NULL;
RtlEnterCriticalSection (&AVrfpThreadTableLock);
Current = AVrfpThreadTable[ChainIndex].Flink;
while (Current != &(AVrfpThreadTable[ChainIndex])) {
Entry = CONTAINING_RECORD (Current,
AVRF_THREAD_ENTRY,
HashChain);
if (Entry->Id == Id) {
Result = Entry;
goto Exit;
}
Current = Current->Flink;
}
Exit:
RtlLeaveCriticalSection (&AVrfpThreadTableLock);
return Result;
}
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////// Verifier TLS slot
/////////////////////////////////////////////////////////////////////
#define INVALID_TLS_INDEX 0xFFFFFFFF
ULONG AVrfpTlsIndex = INVALID_TLS_INDEX;
AVRF_TLS_STRUCT AVrfpFirstThreadTlsStruct = { 0 };
LIST_ENTRY AVrfpTlsListHead;
NTSTATUS
AVrfpAllocateVerifierTlsSlot (
VOID
)
{
PPEB Peb;
PTEB Teb;
DWORD Index;
NTSTATUS Status;
InitializeListHead (&AVrfpTlsListHead);
Peb = NtCurrentPeb();
Teb = NtCurrentTeb();
Status = STATUS_SUCCESS;
RtlAcquirePebLock();
//
// This function is called very early during process startup therefore
// we expect to find a TLS index in the first slots (most typically
// it is zero although we do not do anything specific to enforce that).
//
Index = RtlFindClearBitsAndSet((PRTL_BITMAP)Peb->TlsBitmap,1,0);
if (Index == INVALID_TLS_INDEX) {
DbgPrint ("AVRF: failed to allocated a verifier TLS slot.\n");
Status = STATUS_UNSUCCESSFUL;
goto Exit;
}
AVrfpTlsIndex = Index;
AVrfpFirstThreadTlsStruct.Teb = Teb;
AVrfpFirstThreadTlsStruct.ThreadId = Teb->ClientId.UniqueThread;
InsertHeadList (&AVrfpTlsListHead,
&AVrfpFirstThreadTlsStruct.ListEntry);
Teb->TlsSlots[Index] = &AVrfpFirstThreadTlsStruct;
Exit:
RtlReleasePebLock();
return Status;
}
PAVRF_TLS_STRUCT
AVrfpGetVerifierTlsValue(
VOID
)
{
PTEB Teb;
PVOID *Slot;
if (AVrfpTlsIndex == INVALID_TLS_INDEX) {
return NULL;
}
Teb = NtCurrentTeb();
Slot = &Teb->TlsSlots[AVrfpTlsIndex];
return (PAVRF_TLS_STRUCT)*Slot;
}
VOID
AVrfpSetVerifierTlsValue(
PAVRF_TLS_STRUCT Value
)
{
PTEB Teb;
if (AVrfpTlsIndex == INVALID_TLS_INDEX) {
return;
}
Teb = NtCurrentTeb();
Teb->TlsSlots[AVrfpTlsIndex] = Value;
}
VOID
AvrfpThreadAttach (
VOID
)
{
PAVRF_TLS_STRUCT TlsStruct;
PTEB Teb;
ASSERT (AVrfpGetVerifierTlsValue () == NULL);
TlsStruct = (PAVRF_TLS_STRUCT) AVrfpAllocate (sizeof *TlsStruct);
if (TlsStruct != NULL) {
Teb = NtCurrentTeb();
TlsStruct->ThreadId = Teb->ClientId.UniqueThread;
TlsStruct->Teb = Teb;
//
// We are protected by the loader lock so we shouldn't
// need any additional synchronization here.
//
InsertHeadList (&AVrfpTlsListHead,
&TlsStruct->ListEntry);
AVrfpSetVerifierTlsValue (TlsStruct);
}
}
VOID
AvrfpThreadDetach (
VOID
)
{
volatile PAVRF_TLS_STRUCT TlsStruct;
PTEB Teb;
TlsStruct = AVrfpGetVerifierTlsValue();
if (TlsStruct != NULL && TlsStruct != &AVrfpFirstThreadTlsStruct) {
//
// We are protected by the loader lock so we shouldn't
// need any additional synchronization here.
//
Teb = NtCurrentTeb();
if (TlsStruct->Teb != Teb || TlsStruct->ThreadId != Teb->ClientId.UniqueThread) {
VERIFIER_STOP (APPLICATION_VERIFIER_INTERNAL_ERROR,
"Corrupted TLS structure",
TlsStruct->Teb, "TEB address",
Teb, "Expected TEB address",
TlsStruct->ThreadId, "Thread ID",
Teb->ClientId.UniqueThread, "Expected Thread ID");
}
RemoveEntryList (&TlsStruct->ListEntry);
AVrfpFree (TlsStruct);
AVrfpSetVerifierTlsValue (NULL);
}
//
// Delete the virtual space region containing the thread's stack from
// the tracked. Since the stack is freed from kernel mode we will miss
// the operation otherwise.
//
if ((AVrfpProvider.VerifierFlags & RTL_VRF_FLG_VIRTUAL_SPACE_TRACKING) != 0) {
AVrfpVsTrackDeleteRegionContainingAddress (&TlsStruct);
}
}
/////////////////////////////////////////////////////////////////////
///////////////////////////////////////////// Dll entry point hooking
/////////////////////////////////////////////////////////////////////
typedef struct _DLL_ENTRY_POINT_INFO {
LIST_ENTRY List;
PVOID DllBase;
PDLL_INIT_ROUTINE EntryPoint;
PLDR_DATA_TABLE_ENTRY Ldr;
} DLL_ENTRY_POINT_INFO, * PDLL_ENTRY_POINT_INFO;
LIST_ENTRY DllLoadListHead;
RTL_CRITICAL_SECTION DllLoadListLock;
PDLL_ENTRY_POINT_INFO
AVrfpFindDllEntryPoint (
PVOID DllBase
);
BOOLEAN
AVrfpStandardDllEntryPointRoutine (
IN PVOID DllHandle,
IN ULONG Reason,
IN PCONTEXT Context OPTIONAL
);
ULONG
AVrfpDllEntryPointExceptionFilter (
ULONG ExceptionCode,
PVOID ExceptionRecord,
PDLL_ENTRY_POINT_INFO DllInfo
);
NTSTATUS
AVrfpDllInitialize (
VOID
)
/*++
Routine description:
This routine initializes dll entry point hooking structures.
It is called during the PROCESS_VERIFIER for verifier.dll.
It cannot be called during PROCESS_ATTACH because it is too late and
by that time we already need the structures initialized.
Parameters:
None.
Return value:
None.
--*/
{
NTSTATUS Status;
InitializeListHead (&DllLoadListHead);
Status = RtlInitializeCriticalSection (&DllLoadListLock);
return Status;
}
VOID
AVrfpDllLoadCallback (
PWSTR DllName,
PVOID DllBase,
SIZE_T DllSize,
PVOID Reserved
)
/*++
Routine description:
This routine is a dll load callback called by the verifier engine
(from ntdll.dll) whenever a dll gets loaded.
Parameters:
DllName - name of the dll
DllBase - base load address
DllSize - size of the dll
Reserved - pointer to the LDR_DATA_TABLE_ENTRY structure maintained by the
loader for this dll.
Return value:
None.
--*/
{
PLDR_DATA_TABLE_ENTRY Ldr;
PDLL_ENTRY_POINT_INFO Info;
UNREFERENCED_PARAMETER (DllBase);
UNREFERENCED_PARAMETER (DllSize);
Ldr = (PLDR_DATA_TABLE_ENTRY)Reserved;
ASSERT (Ldr != NULL);
//
// Make sure we do not have a null entry point. We will ignore
// these ones. No harm done.
//
if (Ldr->EntryPoint == NULL) {
if ((AVrfpProvider.VerifierDebug & VRFP_DEBUG_DLLMAIN_HOOKING) != 0) {
DbgPrint ("AVRF: %ws: null entry point.\n", DllName);
}
return;
}
else {
if ((AVrfpProvider.VerifierDebug & VRFP_DEBUG_DLLMAIN_HOOKING) != 0) {
DbgPrint ("AVRF: %ws @ %p: entry point @ %p .\n",
DllName, Ldr->DllBase, Ldr->EntryPoint);
}
}
//
// We will change the dll entry point.
//
Info = AVrfpAllocate (sizeof *Info);
if (Info == NULL) {
//
// If we cannot allocate the dll info we will let everything
// continue. We will just not verify this dll entry.
//
if ((AVrfpProvider.VerifierDebug & VRFP_DEBUG_DLLMAIN_HOOKING) != 0) {
DbgPrint ("AVRF: low memory: will not verify entry point for %ws .\n", DllName);
}
return;
}
Info->EntryPoint = (PDLL_INIT_ROUTINE)(Ldr->EntryPoint);
Info->DllBase = Ldr->DllBase;
Info->Ldr = Ldr;
RtlEnterCriticalSection (&DllLoadListLock);
try {
Ldr->EntryPoint = AVrfpStandardDllEntryPointRoutine;
InsertTailList (&DllLoadListHead, &(Info->List));
}
finally {
RtlLeaveCriticalSection (&DllLoadListLock);
}
if ((AVrfpProvider.VerifierDebug & VRFP_DEBUG_DLLMAIN_HOOKING) != 0) {
DbgPrint ("AVRF: hooked dll entry point for dll %ws \n", DllName);
}
}
VOID
AVrfpDllUnloadCallback (
PWSTR DllName,
PVOID DllBase,
SIZE_T DllSize,
PVOID Reserved
)
/*++
Routine description:
This routine is a dll unload callback called by the verifier engine
(from ntdll.dll) whenever a dll gets unloaded.
Parameters:
DllName - name of the dll
DllBase - base load address
DllSize - size of the dll
Reserved - pointer to the LDR_DATA_TABLE_ENTRY structure maintained by the
loader for this dll.
Return value:
None.
--*/
{
PDLL_ENTRY_POINT_INFO Info;
BOOLEAN FoundEntry;
UNREFERENCED_PARAMETER (Reserved);
FoundEntry = FALSE;
Info = NULL;
ASSERT (DllBase != NULL);
//
// Notify anybody interested in checking the fact that DLL's virtual
// region will be discarded.
//
AVrfpFreeMemNotify (VerifierFreeMemTypeUnloadDll,
DllBase,
DllSize,
DllName);
//
// We need to find the dll in our own dll list, remove it from
// the list and free entry point information. There are a few cases
// where there might be no entry there so we have to protect against
// that (null entry point in the first place or low memory).
//
RtlEnterCriticalSection (&DllLoadListLock);
try {
Info = AVrfpFindDllEntryPoint (DllBase);
if (Info) {
RemoveEntryList (&(Info->List));
}
}
finally {
RtlLeaveCriticalSection (&DllLoadListLock);
}
if (Info) {
AVrfpFree (Info);
}
}
PDLL_ENTRY_POINT_INFO
AVrfpFindDllEntryPoint (
PVOID DllBase
)
/*++
Routine description:
This routine searches for a dll entry point descriptor in the list of
descriptors kept by verifier for one that matches the dll base address
passed as a parameter.
Before calling this function the DllLoadListLock must be acquired.
Parameters:
DllBase - dll base load address for the dll to be found.
Return value:
A pointer to a dll descriptor if an entry was found and null otherwise.
--*/
{
PDLL_ENTRY_POINT_INFO Info;
BOOLEAN FoundEntry;
PLIST_ENTRY Current;
FoundEntry = FALSE;
Info = NULL;
ASSERT (DllBase != NULL);
//
// Search for the dll in our own dll list.
//
Current = DllLoadListHead.Flink;
while (Current != &DllLoadListHead) {
Info = CONTAINING_RECORD (Current,
DLL_ENTRY_POINT_INFO,
List);
Current = Current->Flink;
if (Info->DllBase == DllBase) {
FoundEntry = TRUE;
break;
}
}
if (FoundEntry == FALSE) {
return NULL;
}
else {
return Info;
}
}
BOOLEAN
AVrfpStandardDllEntryPointRoutine (
IN PVOID DllHandle,
IN ULONG Reason,
IN PCONTEXT Context OPTIONAL
)
/*++
Routine description:
This routine is the standard DllMain routine that replaces all the entry points
hooked. It will call in turn the original entry point.
Parameters:
Same as the original dll entry point.
Return value:
Same as the original dll entry point.
--*/
{
PDLL_ENTRY_POINT_INFO DllInfo;
BOOLEAN Result;
PAVRF_TLS_STRUCT TlsStruct;
Result = FALSE;
DllInfo = NULL;
//
// Search a dll entry point descriptor for this dll address.
//
RtlEnterCriticalSection (&DllLoadListLock);
try {
DllInfo = AVrfpFindDllEntryPoint (DllHandle);
//
// If we did not manage to find a dll descriptor for this one it is
// weird. For out of memory conditions we do not change the original
// entry point therefore we should never get into this function w/o
// a descriptor in the dll list.
//
if (DllInfo == NULL) {
DbgPrint ("AVRF: warning: no descriptor for DLL loaded @ %p .\n", DllHandle);
ASSERT (DllInfo != NULL);
//
// Simulate a successful return;
//
RtlLeaveCriticalSection (&DllLoadListLock);
return TRUE;
}
else {
//
// If we found a dll entry but the entry point is null we just
// simulate a successful return from DllMain.
//
if (DllInfo->EntryPoint == NULL) {
DbgPrint ("AVRF: warning: null entry point for DLL descriptor @ %p .\n", DllInfo);
ASSERT (DllInfo->EntryPoint != NULL);
RtlLeaveCriticalSection (&DllLoadListLock);
return TRUE;
}
}
}
except (EXCEPTION_EXECUTE_HANDLER) {
}
RtlLeaveCriticalSection (&DllLoadListLock);
//
// Mark this thread as loader lock owner.
// If the real DllMain later on calls WaitForSingleObject
// on another thread handle we will use this flag to detect the issue
// and break into debugger because that other thread will need the
// loader lock when it will call ExitThread.
//
TlsStruct = AVrfpGetVerifierTlsValue();
if (TlsStruct != NULL) {
TlsStruct->Flags |= VRFP_THREAD_FLAGS_LOADER_LOCK_OWNER;
}
//
// Call the real entry point wrapped in try/except.
//
try {
try {
if ((AVrfpProvider.VerifierDebug & VRFP_DEBUG_DLLMAIN_CALL) != 0) {
DbgPrint ("AVRF: dll entry @ %p (%ws, %x) \n",
DllInfo->EntryPoint,
DllInfo->Ldr->FullDllName.Buffer,
Reason);
}
Result = (DllInfo->EntryPoint) (DllHandle, Reason, Context);
}
except (AVrfpDllEntryPointExceptionFilter (_exception_code(), _exception_info(), DllInfo)) {
NOTHING;
}
}
finally {
if (TlsStruct != NULL) {
TlsStruct->Flags &= ~VRFP_THREAD_FLAGS_LOADER_LOCK_OWNER;
}
}
return Result;
}
ULONG
AVrfpDllEntryPointExceptionFilter (
ULONG ExceptionCode,
PVOID ExceptionRecord,
PDLL_ENTRY_POINT_INFO DllInfo
)
/*++
Routine description:
This routine is the exception filter used to cath exceptions raised
from a dll initialization function.
Parameters:
ExceptionCode - exception code.
ExceptionRecord - exception pointers.
Return value:
Returns EXCEPTION_CONTINUE_SEARCH.
--*/
{
PEXCEPTION_POINTERS Exception;
//
// Skip timeout and breakpoint exceptions.
//
if (ExceptionCode != STATUS_POSSIBLE_DEADLOCK &&
ExceptionCode != STATUS_BREAKPOINT) {
Exception = (PEXCEPTION_POINTERS)ExceptionRecord;
VERIFIER_STOP (APPLICATION_VERIFIER_UNEXPECTED_EXCEPTION | APPLICATION_VERIFIER_CONTINUABLE_BREAK,
"unexpected exception raised in DLL entry point routine",
DllInfo->Ldr->BaseDllName.Buffer, "DLL name (use du to dump it)",
Exception->ExceptionRecord, "Exception record (.exr THIS-ADDRESS)",
Exception->ContextRecord, "Context record (.cxr THIS-ADDRESS)",
DllInfo, "Verifier dll descriptor");
}
return EXCEPTION_CONTINUE_SEARCH;
}
VOID
AVrfpVerifyLegalWait (
CONST HANDLE *Handles,
DWORD Count,
BOOL WaitAll
)
{
DWORD Index;
PAVRF_TLS_STRUCT TlsStruct;
NTSTATUS Status;
THREAD_BASIC_INFORMATION ThreadInfo;
BYTE QueryBuffer[200];
POBJECT_TYPE_INFORMATION TypeInfo = (POBJECT_TYPE_INFORMATION)QueryBuffer;
if ((AVrfpProvider.VerifierFlags & RTL_VRF_FLG_HANDLE_CHECKS) == 0) {
goto Done;
}
if (Handles == NULL || Count == 0) {
VERIFIER_STOP (APPLICATION_VERIFIER_INCORRECT_WAIT_CALL | APPLICATION_VERIFIER_CONTINUABLE_BREAK,
"incorrect Wait call",
Handles, "Address of object handle(s)",
Count, "Number of handles",
NULL, "",
NULL, "");
}
else {
//
// Check if the current thread owns the loader lock.
//
TlsStruct = AVrfpGetVerifierTlsValue();
for (Index = 0; Index < Count; Index += 1) {
//
// Verify that the handle is not NULL.
//
if (Handles[Index] == NULL) {
VERIFIER_STOP (APPLICATION_VERIFIER_NULL_HANDLE | APPLICATION_VERIFIER_CONTINUABLE_BREAK,
"using NULL handle",
NULL, "",
NULL, "",
NULL, "",
NULL, "");
continue;
}
if ((TlsStruct == NULL) ||
((TlsStruct->Flags & VRFP_THREAD_FLAGS_LOADER_LOCK_OWNER) == 0) ||
(RtlDllShutdownInProgress() != FALSE) ||
(WaitAll == FALSE)) {
continue;
}
//
// The current thread is the loader lock owner.
// Check if any of the objects we are about to wait on is
// a thread in the current process. This would be illegal because
// that thread will need the loader lock when calling ExitThread
// so we will most likely deadlock.
//
Status = NtQueryObject (Handles[Index],
ObjectTypeInformation,
QueryBuffer,
sizeof (QueryBuffer),
NULL);
if (NT_SUCCESS(Status) &&
RtlEqualUnicodeString (&AVrfpThreadObjectName,
&(((POBJECT_TYPE_INFORMATION)TypeInfo)->TypeName),
FALSE)) {
//
// We are trying to wait on this thread handle.
// Check if this thread is in the current process.
//
Status = NtQueryInformationThread (Handles[Index],
ThreadBasicInformation,
&ThreadInfo,
sizeof (ThreadInfo),
NULL);
if (NT_SUCCESS(Status) &&
ThreadInfo.ClientId.UniqueProcess == NtCurrentTeb()->ClientId.UniqueProcess) {
VERIFIER_STOP (APPLICATION_VERIFIER_WAIT_IN_DLLMAIN | APPLICATION_VERIFIER_CONTINUABLE_BREAK,
"waiting on a thread handle in DllMain",
Handles[Index], "Thread handle",
NULL, "",
NULL, "",
NULL, "");
}
}
}
}
Done:
NOTHING;
}
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////// Race verifier
/////////////////////////////////////////////////////////////////////
//
// Race verifier
//
// Race verifier introduces short random delays immediately after
// a thread acquires a resource (successful wait or enter/tryenter
// critical section). The idea behind it is that this will create
// a significant amount of timing randomization in the process.
//
ULONG AVrfpRaceDelayInitialSeed;
ULONG AVrfpRaceDelaySeed;
ULONG AVrfpRaceProbability = 5; // 5%
VOID
AVrfpCreateRandomDelay (
VOID
)
{
LARGE_INTEGER PerformanceCounter;
LARGE_INTEGER TimeOut;
ULONG Random;
if (AVrfpRaceDelayInitialSeed == 0) {
NtQueryPerformanceCounter (&PerformanceCounter, NULL);
AVrfpRaceDelayInitialSeed = PerformanceCounter.LowPart;
AVrfpRaceDelaySeed = AVrfpRaceDelayInitialSeed;
}
Random = RtlRandom (&AVrfpRaceDelaySeed) % 100;
if (Random <= AVrfpRaceProbability) {
//
// A null timeout means the thread will just release
// the rest of the time slice it has on this processor.
//
TimeOut.QuadPart = (LONGLONG)0;
NtDelayExecution (FALSE, &TimeOut);
BUMP_COUNTER (CNT_RACE_DELAYS_INJECTED);
}
else {
BUMP_COUNTER (CNT_RACE_DELAYS_SKIPPED);
}
}
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////// First chance AV logic
/////////////////////////////////////////////////////////////////////
VOID
AVrfpCheckFirstChanceException (
struct _EXCEPTION_POINTERS * ExceptionPointers
)
{
DWORD ExceptionCode;
if ((AVrfpProvider.VerifierFlags & RTL_VRF_FLG_FIRST_CHANCE_EXCEPTION_CHECKS) == 0) {
return;
}
ExceptionCode = ExceptionPointers->ExceptionRecord->ExceptionCode;
if (ExceptionCode == STATUS_ACCESS_VIOLATION) {
if (NtCurrentPeb()->BeingDebugged) {
if (ExceptionPointers->ExceptionRecord->NumberParameters > 1) {
if (ExceptionPointers->ExceptionRecord->ExceptionInformation[1] > 0x10000) {
VERIFIER_STOP (APPLICATION_VERIFIER_ACCESS_VIOLATION | APPLICATION_VERIFIER_CONTINUABLE_BREAK,
"first chance access violation for current stack trace",
ExceptionPointers->ExceptionRecord->ExceptionInformation[1],
"Invalid address being accessed",
ExceptionPointers->ExceptionRecord->ExceptionAddress,
"Code performing invalid access",
ExceptionPointers->ExceptionRecord,
"Exception record. Use .exr to display it.",
ExceptionPointers->ContextRecord,
"Context record. Use .cxr to display it.");
}
}
}
}
}
/////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////// Free memory checks
/////////////////////////////////////////////////////////////////////
#define AVRF_FREE_MEMORY_CALLBACKS 16
#define FREE_CALLBACK_OK_TO_CALL 0
#define FREE_CALLBACK_ACTIVE 1
#define FREE_CALLBACK_DELETING 2
LONG AVrfpFreeCallbackState;
LONG AVrfpFreeCallbackCallers;
PVOID AVrfpFreeMemoryCallbacks[AVRF_FREE_MEMORY_CALLBACKS];
NTSTATUS
AVrfpAddFreeMemoryCallback (
VERIFIER_FREE_MEMORY_CALLBACK Callback
)
{
ULONG Index;
PVOID Value;
for (Index = 0; Index < AVRF_FREE_MEMORY_CALLBACKS; Index += 1) {
Value = InterlockedCompareExchangePointer (&(AVrfpFreeMemoryCallbacks[Index]),
Callback,
NULL);
if (Value == NULL) {
return STATUS_SUCCESS;
}
}
DbgPrint ("AVRF: failed to add free memory callback @ %p \n", Callback);
DbgBreakPoint ();
return STATUS_NO_MEMORY;
}
NTSTATUS
AVrfpDeleteFreeMemoryCallback (
VERIFIER_FREE_MEMORY_CALLBACK Callback
)
{
ULONG Index;
PVOID Value;
LONG State;
//
// Spin until we can delete a callback. If some region got freed and some
// callbacks are running we will wait until they finish.
//
do {
State = InterlockedCompareExchange (&AVrfpFreeCallbackState,
FREE_CALLBACK_DELETING,
FREE_CALLBACK_OK_TO_CALL);
} while (State != FREE_CALLBACK_OK_TO_CALL);
for (Index = 0; Index < AVRF_FREE_MEMORY_CALLBACKS; Index += 1) {
Value = InterlockedCompareExchangePointer (&(AVrfpFreeMemoryCallbacks[Index]),
NULL,
Callback);
if (Value == Callback) {
InterlockedExchange (&AVrfpFreeCallbackState,
FREE_CALLBACK_OK_TO_CALL);
return STATUS_SUCCESS;
}
}
DbgPrint ("AVRF: attempt to delete invalid free memory callback @ %p \n", Callback);
DbgBreakPoint ();
InterlockedExchange (&AVrfpFreeCallbackState,
FREE_CALLBACK_OK_TO_CALL);
return STATUS_UNSUCCESSFUL;
}
VOID
AVrfpCallFreeMemoryCallbacks (
PVOID StartAddress,
SIZE_T RegionSize,
PWSTR UnloadedDllName
)
{
ULONG Index;
PVOID Value;
LONG State;
LONG Callers;
//
// If some thread is deleting a callback then we will not call any
// callback. Since this is a rare event (callbacks do not get
// deleted often) we will not lose bugs (maybe a few weird ones).
//
// If zero or more threads execute callbacks it is ok to call them also
// from this thread.
//
//
// Callers++ will prevent the State to go from Active to OkToCall.
// Therefore we block any deletes.
//
InterlockedIncrement (&AVrfpFreeCallbackCallers);
State = InterlockedCompareExchange (&AVrfpFreeCallbackState,
FREE_CALLBACK_ACTIVE,
FREE_CALLBACK_OK_TO_CALL);
if (State != FREE_CALLBACK_DELETING) {
for (Index = 0; Index < AVRF_FREE_MEMORY_CALLBACKS; Index += 1) {
Value = InterlockedCompareExchangePointer (&(AVrfpFreeMemoryCallbacks[Index]),
NULL,
NULL);
if (Value != NULL) {
((VERIFIER_FREE_MEMORY_CALLBACK)Value) (StartAddress,
RegionSize,
UnloadedDllName);
}
}
//
// Exit protocol. If callers == 1 then this thread needs to change from Active
// to OkToCall. This way we give green light for possible deletes.
//
Callers = InterlockedCompareExchange (&AVrfpFreeCallbackCallers,
0,
1);
if (Callers == 1) {
InterlockedExchange (&AVrfpFreeCallbackState,
FREE_CALLBACK_OK_TO_CALL);
}
else {
InterlockedDecrement (&AVrfpFreeCallbackCallers);
}
}
else {
//
// Some other thread deletes callbacks.
// We will skip them this time.
//
InterlockedDecrement (&AVrfpFreeCallbackCallers);
}
}
BOOL
AVrfpFreeMemSanityChecks (
VERIFIER_DLL_FREEMEM_TYPE FreeMemType,
PVOID StartAddress,
SIZE_T RegionSize,
PWSTR UnloadedDllName
)
{
BOOL Success = TRUE;
if ((AVrfpProvider.VerifierFlags & RTL_VRF_FLG_VIRTUAL_MEM_CHECKS) == 0) {
goto Done;
}
//
// Break for invalid StartAddress/RegionSize combinations.
//
if ((AVrfpSysBasicInfo.MaximumUserModeAddress <= (ULONG_PTR)StartAddress) ||
((AVrfpSysBasicInfo.MaximumUserModeAddress - (ULONG_PTR)StartAddress) < RegionSize)) {
Success = FALSE;
switch (FreeMemType) {
case VerifierFreeMemTypeFreeHeap:
//
// Nothing. Let page heap handle the bogus block.
//
break;
case VerifierFreeMemTypeVirtualFree:
case VerifierFreeMemTypeUnmap:
//
// Our caller is AVrfpFreeVirtualMemNotify and that should have
// signaled this error already.
//
break;
case VerifierFreeMemTypeUnloadDll:
ASSERT (UnloadedDllName != NULL);
VERIFIER_STOP (APPLICATION_VERIFIER_INVALID_FREEMEM | APPLICATION_VERIFIER_CONTINUABLE_BREAK,
"Unloading DLL with invalid size or start address",
StartAddress, "Allocation base address",
RegionSize, "Memory region size",
UnloadedDllName, "DLL name address. Use du to dump it.",
NULL, "" );
break;
default:
ASSERT (FALSE );
break;
}
}
else {
//
// Verify that we are not trying to free a portion of the current thread's stack (!)
//
if (((StartAddress >= NtCurrentTeb()->DeallocationStack) && (StartAddress < NtCurrentTeb()->NtTib.StackBase)) ||
((StartAddress < NtCurrentTeb()->DeallocationStack) && ((PCHAR)StartAddress + RegionSize > (PCHAR)NtCurrentTeb()->DeallocationStack)))
{
Success = FALSE;
switch (FreeMemType) {
case VerifierFreeMemTypeFreeHeap:
VERIFIER_STOP (APPLICATION_VERIFIER_INVALID_FREEMEM | APPLICATION_VERIFIER_CONTINUABLE_BREAK,
"Freeing heap memory block inside current thread's stack address range",
StartAddress, "Allocation base address",
RegionSize, "Memory region size",
NtCurrentTeb()->DeallocationStack, "Stack low limit address",
NtCurrentTeb()->NtTib.StackBase, "Stack high limit address" );
break;
case VerifierFreeMemTypeVirtualFree:
VERIFIER_STOP (APPLICATION_VERIFIER_INVALID_FREEMEM | APPLICATION_VERIFIER_CONTINUABLE_BREAK,
"Freeing memory block inside current thread's stack address range",
StartAddress, "Allocation base address",
RegionSize, "Memory region size",
NtCurrentTeb()->DeallocationStack, "Stack low limit address",
NtCurrentTeb()->NtTib.StackBase, "Stack high limit address" );
break;
case VerifierFreeMemTypeUnloadDll:
ASSERT (UnloadedDllName != NULL);
VERIFIER_STOP (APPLICATION_VERIFIER_INVALID_FREEMEM | APPLICATION_VERIFIER_CONTINUABLE_BREAK,
"Unloading DLL inside current thread's stack address range",
StartAddress, "Allocation base address",
RegionSize, "Memory region size",
UnloadedDllName, "DLL name address. Use du to dump it.",
NtCurrentTeb()->DeallocationStack, "Stack low limit address");
break;
case VerifierFreeMemTypeUnmap:
VERIFIER_STOP (APPLICATION_VERIFIER_INVALID_FREEMEM | APPLICATION_VERIFIER_CONTINUABLE_BREAK,
"Unmapping memory region inside current thread's stack address range",
StartAddress, "Allocation base address",
RegionSize, "Memory region size",
NtCurrentTeb()->DeallocationStack, "Stack low limit address",
NtCurrentTeb()->NtTib.StackBase, "Stack high limit address" );
break;
default:
ASSERT (FALSE );
break;
}
}
}
Done:
return Success;
}
VOID
AVrfpFreeMemNotify (
VERIFIER_DLL_FREEMEM_TYPE FreeMemType,
PVOID StartAddress,
SIZE_T RegionSize,
PWSTR UnloadedDllName
)
{
BOOL Success;
//
// Simple checks for allocation start address and size.
//
Success = AVrfpFreeMemSanityChecks (FreeMemType,
StartAddress,
RegionSize,
UnloadedDllName);
if (Success != FALSE) {
//
// Verify if there are any active critical section
// in the memory we are freeing.
//
AVrfpFreeMemLockChecks (FreeMemType,
StartAddress,
RegionSize,
UnloadedDllName);
}
//
// Call free memory callbacks.
//
AVrfpCallFreeMemoryCallbacks (StartAddress,
RegionSize,
UnloadedDllName);
}
/////////////////////////////////////////////////////////////////////
////////////////////////////////////////// Verifier private heap APIs
/////////////////////////////////////////////////////////////////////
PVOID AVrfpHeap;
PVOID
AVrfpAllocate (
SIZE_T Size
)
{
ASSERT (AVrfpHeap != NULL);
ASSERT (Size > 0);
return RtlAllocateHeap (AVrfpHeap,
HEAP_ZERO_MEMORY,
Size);
}
VOID
AVrfpFree (
PVOID Address
)
{
ASSERT (AVrfpHeap != NULL);
ASSERT (Address != NULL);
RtlFreeHeap (AVrfpHeap,
0,
Address);
}
/////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////// Call trackers
/////////////////////////////////////////////////////////////////////
PAVRF_TRACKER AVrfThreadTracker;
PAVRF_TRACKER AVrfHeapTracker;
PAVRF_TRACKER AVrfVspaceTracker;
NTSTATUS
AVrfCreateTrackers (
VOID
)
{
if ((AVrfThreadTracker = AVrfCreateTracker (16)) == NULL) {
goto CLEANUP_AND_FAIL;
}
if ((AVrfHeapTracker = AVrfCreateTracker (8192)) == NULL) {
goto CLEANUP_AND_FAIL;
}
if ((AVrfVspaceTracker = AVrfCreateTracker (8192)) == NULL) {
goto CLEANUP_AND_FAIL;
}
return STATUS_SUCCESS;
CLEANUP_AND_FAIL:
AVrfDestroyTracker (AVrfThreadTracker);
AVrfDestroyTracker (AVrfHeapTracker);
AVrfDestroyTracker (AVrfVspaceTracker);
return STATUS_NO_MEMORY;
}