/*++ 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; }