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.
694 lines
19 KiB
694 lines
19 KiB
/*++
|
|
|
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
|
|
|
Module Name:
|
|
|
|
thread.c
|
|
|
|
Abstract:
|
|
|
|
This module implements verification functions for thread interfaces.
|
|
|
|
Author:
|
|
|
|
Silviu Calinoiu (SilviuC) 22-Feb-2001
|
|
|
|
Revision History:
|
|
|
|
Daniel Mihai (DMihai) 25-Apr-2002
|
|
|
|
Hook thread pool and WMI threads.
|
|
--*/
|
|
|
|
#include "pch.h"
|
|
|
|
#include "verifier.h"
|
|
#include "support.h"
|
|
#include "logging.h"
|
|
#include "tracker.h"
|
|
|
|
//
|
|
// Why do we hook Exit/TerminateThread instead of NtTerminateThread?
|
|
//
|
|
// Because kernel32 calls NtTerminateThread in legitimate contexts.
|
|
// After all this is the implementation for Exit/TerminateThread.
|
|
// It would be difficult to discriminate good calls from bad calls.
|
|
// So we prefer to intercept Exit/Thread and returns from thread
|
|
// functions.
|
|
//
|
|
|
|
//
|
|
// Standard function used for hooking a thread function.
|
|
//
|
|
|
|
DWORD
|
|
WINAPI
|
|
AVrfpStandardThreadFunction (
|
|
LPVOID Info
|
|
);
|
|
|
|
//
|
|
// Common point to check for thread termination.
|
|
//
|
|
|
|
VOID
|
|
AVrfpCheckThreadTermination (
|
|
HANDLE Thread
|
|
);
|
|
|
|
VOID
|
|
AVrfpCheckCurrentThreadTermination (
|
|
VOID
|
|
);
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
//WINBASEAPI
|
|
DECLSPEC_NORETURN
|
|
VOID
|
|
WINAPI
|
|
AVrfpExitProcess(
|
|
IN UINT uExitCode
|
|
)
|
|
{
|
|
typedef VOID (WINAPI * FUNCTION_TYPE) (UINT);
|
|
FUNCTION_TYPE Function;
|
|
|
|
Function = AVRFP_GET_ORIGINAL_EXPORT (AVrfpKernel32Thunks,
|
|
AVRF_INDEX_KERNEL32_EXITPROCESS);
|
|
|
|
//
|
|
// Check out if there are other threads running while ExitProcess
|
|
// gets called. This can cause problems because the threads are
|
|
// terminated unconditionally and then ExitProcess() calls
|
|
// LdrShutdownProcess() which will try to notify all DLLs to cleanup.
|
|
// During cleanup any number of operations can happen that will result
|
|
// in deadlocks since all those threads have been terminated
|
|
// unconditionally
|
|
//
|
|
#if 0
|
|
if ((AVrfpProvider.VerifierFlags & RTL_VRF_FLG_DANGEROUS_APIS) != 0) {
|
|
|
|
PCHAR InfoBuffer;
|
|
ULONG RequiredLength = 0;
|
|
ULONG NumberOfThreads;
|
|
ULONG EntryOffset;
|
|
PSYSTEM_PROCESS_INFORMATION ProcessInfo;
|
|
NTSTATUS Status;
|
|
|
|
Status = NtQuerySystemInformation (SystemProcessInformation,
|
|
NULL,
|
|
0,
|
|
&RequiredLength);
|
|
|
|
if (Status == STATUS_INFO_LENGTH_MISMATCH && RequiredLength != 0) {
|
|
|
|
InfoBuffer = AVrfpAllocate (RequiredLength);
|
|
|
|
if (InfoBuffer) {
|
|
|
|
//
|
|
// Note that the RequiredLength is not 100% guaranteed to be good
|
|
// since in between the two query calls several other processes
|
|
// may have been created. If this is the case we bail out and skip
|
|
// the verification.
|
|
//
|
|
|
|
Status = NtQuerySystemInformation (SystemProcessInformation,
|
|
InfoBuffer,
|
|
RequiredLength,
|
|
NULL);
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
|
|
EntryOffset = 0;
|
|
NumberOfThreads = 0;
|
|
|
|
do {
|
|
ProcessInfo = (PSYSTEM_PROCESS_INFORMATION)&InfoBuffer[EntryOffset];
|
|
|
|
if (ProcessInfo->UniqueProcessId == NtCurrentTeb()->ClientId.UniqueProcess) {
|
|
NumberOfThreads = ProcessInfo->NumberOfThreads;
|
|
break;
|
|
}
|
|
|
|
EntryOffset += ProcessInfo->NextEntryOffset;
|
|
|
|
} while(ProcessInfo->NextEntryOffset != 0);
|
|
|
|
ASSERT (NumberOfThreads > 0);
|
|
|
|
if (NumberOfThreads > 1) {
|
|
|
|
VERIFIER_STOP (APPLICATION_VERIFIER_INVALID_EXIT_PROCESS_CALL | APPLICATION_VERIFIER_NO_BREAK,
|
|
"ExitProcess() called while multiple threads are running",
|
|
NumberOfThreads, "Number of threads running",
|
|
0, NULL, 0, NULL, 0, NULL);
|
|
|
|
}
|
|
}
|
|
else {
|
|
|
|
DbgPrint ("AVRF: NtQuerySystemInformation(SystemProcessInformation) "
|
|
"failed with %X \n",
|
|
Status);
|
|
|
|
}
|
|
|
|
//
|
|
// We are done with the buffer. Time to free it.
|
|
//
|
|
|
|
AVrfpFree (InfoBuffer);
|
|
}
|
|
}
|
|
else {
|
|
|
|
DbgPrint ("AVRF: NtQuerySystemInformation(SystemProcessInformation, null) "
|
|
"failed with %X \n",
|
|
Status);
|
|
|
|
}
|
|
}
|
|
#endif // #if 0
|
|
|
|
//
|
|
// Make a note of who called ExitProcess(). This can be helpful for debugging
|
|
// weird process shutdown hangs.
|
|
//
|
|
|
|
AVrfLogInTracker (AVrfThreadTracker,
|
|
TRACK_EXIT_PROCESS,
|
|
(PVOID)(ULONG_PTR)uExitCode,
|
|
NULL, NULL, NULL, _ReturnAddress());
|
|
|
|
//
|
|
// Call the real thing.
|
|
//
|
|
|
|
(* Function)(uExitCode);
|
|
|
|
}
|
|
|
|
//WINBASEAPI
|
|
DECLSPEC_NORETURN
|
|
VOID
|
|
WINAPI
|
|
AVrfpExitThread(
|
|
IN DWORD dwExitCode
|
|
)
|
|
{
|
|
typedef VOID (WINAPI * FUNCTION_TYPE) (DWORD);
|
|
FUNCTION_TYPE Function;
|
|
PAVRF_THREAD_ENTRY Entry;
|
|
|
|
Function = AVRFP_GET_ORIGINAL_EXPORT (AVrfpKernel32Thunks,
|
|
AVRF_INDEX_KERNEL32_EXITTHREAD);
|
|
|
|
//
|
|
// Perform all typical checks for a thread that will exit.
|
|
//
|
|
|
|
AVrfpCheckCurrentThreadTermination ();
|
|
|
|
//
|
|
// Before calling the real ExitThread we need to free the thread
|
|
// entry from the thread table.
|
|
//
|
|
|
|
Entry = AVrfpThreadTableSearchEntry (NtCurrentTeb()->ClientId.UniqueThread);
|
|
|
|
//
|
|
// N.B. It is possible to not find an entry in the thread table if the
|
|
// thread was not created using CreateThread but rather some more
|
|
// basic function from ntdll.dll.
|
|
//
|
|
|
|
if (Entry != NULL) {
|
|
|
|
AVrfpThreadTableRemoveEntry (Entry);
|
|
AVrfpFree (Entry);
|
|
}
|
|
|
|
//
|
|
// Call the real thing.
|
|
//
|
|
|
|
(* Function)(dwExitCode);
|
|
}
|
|
|
|
|
|
//WINBASEAPI
|
|
DECLSPEC_NORETURN
|
|
VOID
|
|
WINAPI
|
|
AVrfpFreeLibraryAndExitThread(
|
|
IN HMODULE hLibModule,
|
|
IN DWORD dwExitCode
|
|
)
|
|
{
|
|
typedef VOID (WINAPI * FUNCTION_TYPE) (HMODULE, DWORD);
|
|
FUNCTION_TYPE Function;
|
|
PAVRF_THREAD_ENTRY Entry;
|
|
|
|
Function = AVRFP_GET_ORIGINAL_EXPORT (AVrfpKernel32Thunks,
|
|
AVRF_INDEX_KERNEL32_FREELIBRARYANDEXITTHREAD);
|
|
|
|
//
|
|
// Perform all typical checks for a thread that will exit.
|
|
//
|
|
|
|
AVrfpCheckCurrentThreadTermination ();
|
|
|
|
//
|
|
// Before calling the real FreeLibraryAndExitThread we need to free the thread
|
|
// entry from the thread table.
|
|
//
|
|
|
|
Entry = AVrfpThreadTableSearchEntry (NtCurrentTeb()->ClientId.UniqueThread);
|
|
|
|
//
|
|
// N.B. It is possible to not find an entry in the thread table if the
|
|
// thread was not created using CreateThread but rather some more
|
|
// basic function from ntdll.dll.
|
|
//
|
|
|
|
if (Entry != NULL) {
|
|
|
|
AVrfpThreadTableRemoveEntry (Entry);
|
|
AVrfpFree (Entry);
|
|
}
|
|
|
|
//
|
|
// Call the real thing.
|
|
//
|
|
|
|
(* Function)(hLibModule, dwExitCode);
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////// Terminate thread / Suspend thread
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
//WINBASEAPI
|
|
BOOL
|
|
WINAPI
|
|
AVrfpTerminateThread(
|
|
IN OUT HANDLE hThread,
|
|
IN DWORD dwExitCode
|
|
)
|
|
{
|
|
typedef BOOL (WINAPI * FUNCTION_TYPE) (HANDLE, DWORD);
|
|
FUNCTION_TYPE Function;
|
|
|
|
Function = AVRFP_GET_ORIGINAL_EXPORT (AVrfpKernel32Thunks,
|
|
AVRF_INDEX_KERNEL32_TERMINATETHREAD);
|
|
|
|
//
|
|
// Keep track of who calls TerminateThread() even if we are going to break
|
|
// for it. This helps investigations of deadlocked processes that have run
|
|
// without the dangerous_apis check enabled.
|
|
//
|
|
|
|
AVrfLogInTracker (AVrfThreadTracker,
|
|
TRACK_TERMINATE_THREAD,
|
|
hThread,
|
|
NULL, NULL, NULL, _ReturnAddress());
|
|
|
|
//
|
|
// Perform all typical checks for a thread that will exit.
|
|
//
|
|
|
|
AVrfpCheckThreadTermination (hThread);
|
|
|
|
//
|
|
// This API should not be called. We need to report this.
|
|
// This is useful if we did not detect any orphans but we still want
|
|
// to complain.
|
|
//
|
|
|
|
if ((AVrfpProvider.VerifierFlags & RTL_VRF_FLG_DANGEROUS_APIS) != 0) {
|
|
|
|
VERIFIER_STOP (APPLICATION_VERIFIER_TERMINATE_THREAD_CALL | APPLICATION_VERIFIER_CONTINUABLE_BREAK,
|
|
"TerminateThread() called. This function should not be used.",
|
|
NtCurrentTeb()->ClientId.UniqueThread, "Caller thread ID",
|
|
0, NULL, 0, NULL, 0, NULL);
|
|
}
|
|
|
|
return (* Function)(hThread, dwExitCode);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
//WINBASEAPI
|
|
DWORD
|
|
WINAPI
|
|
AVrfpSuspendThread(
|
|
IN HANDLE hThread
|
|
)
|
|
{
|
|
typedef DWORD (WINAPI * FUNCTION_TYPE) (HANDLE);
|
|
FUNCTION_TYPE Function;
|
|
|
|
Function = AVRFP_GET_ORIGINAL_EXPORT (AVrfpKernel32Thunks,
|
|
AVRF_INDEX_KERNEL32_SUSPENDTHREAD);
|
|
|
|
//
|
|
// Keep track of who calls SuspendThread() even if we are not going to break
|
|
// for it. This helps investigations of deadlocked processes.
|
|
//
|
|
|
|
AVrfLogInTracker (AVrfThreadTracker,
|
|
TRACK_SUSPEND_THREAD,
|
|
hThread,
|
|
NULL, NULL, NULL, _ReturnAddress());
|
|
|
|
//
|
|
// One might think that we can check for orphan locks at this point
|
|
// by calling RtlCheckForOrphanedCriticalSections(hThread).
|
|
// Unfortunately this cannot be done because garbage collectors
|
|
// for various virtual machines (Java, C#) can do this in valid
|
|
// conditions.
|
|
//
|
|
|
|
return (* Function)(hThread);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
//WINBASEAPI
|
|
HANDLE
|
|
WINAPI
|
|
AVrfpCreateThread(
|
|
IN LPSECURITY_ATTRIBUTES lpThreadAttributes,
|
|
IN SIZE_T dwStackSize,
|
|
IN LPTHREAD_START_ROUTINE lpStartAddress,
|
|
IN LPVOID lpParameter,
|
|
IN DWORD dwCreationFlags,
|
|
OUT LPDWORD lpThreadId
|
|
)
|
|
/*++
|
|
|
|
CreateThread hook
|
|
|
|
--*/
|
|
{
|
|
typedef HANDLE (WINAPI * FUNCTION_TYPE) (LPSECURITY_ATTRIBUTES,
|
|
SIZE_T,
|
|
LPTHREAD_START_ROUTINE,
|
|
LPVOID,
|
|
DWORD,
|
|
LPDWORD);
|
|
FUNCTION_TYPE Function;
|
|
HANDLE Result;
|
|
PAVRF_THREAD_ENTRY Info;
|
|
|
|
Function = AVRFP_GET_ORIGINAL_EXPORT (AVrfpKernel32Thunks,
|
|
AVRF_INDEX_KERNEL32_CREATETHREAD);
|
|
|
|
Info = AVrfpAllocate (sizeof *Info);
|
|
|
|
if (Info == NULL) {
|
|
|
|
NtCurrentTeb()->LastErrorValue = ERROR_NOT_ENOUGH_MEMORY;
|
|
return NULL;
|
|
}
|
|
|
|
Info->Parameter = lpParameter;
|
|
Info->Function = lpStartAddress;
|
|
Info->ParentThreadId = NtCurrentTeb()->ClientId.UniqueThread;
|
|
Info->StackSize = dwStackSize;
|
|
Info->CreationFlags = dwCreationFlags;
|
|
|
|
Result = (* Function) (lpThreadAttributes,
|
|
dwStackSize,
|
|
AVrfpStandardThreadFunction,
|
|
(PVOID)Info,
|
|
dwCreationFlags,
|
|
lpThreadId);
|
|
|
|
if (Result == FALSE) {
|
|
|
|
AVrfpFree (Info);
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
|
|
ULONG
|
|
AVrfpThreadFunctionExceptionFilter (
|
|
ULONG ExceptionCode,
|
|
PVOID ExceptionRecord
|
|
)
|
|
{
|
|
//
|
|
// Skip timeout exceptions because they are dealt with in
|
|
// the default exception handler.
|
|
//
|
|
|
|
if (ExceptionCode == STATUS_POSSIBLE_DEADLOCK) {
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
|
|
//
|
|
// Skip breakpoint exceptions raised within thread functions.
|
|
//
|
|
|
|
if (ExceptionCode == STATUS_BREAKPOINT) {
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
|
|
VERIFIER_STOP (APPLICATION_VERIFIER_UNEXPECTED_EXCEPTION | APPLICATION_VERIFIER_CONTINUABLE_BREAK,
|
|
"unexpected exception raised in thread function",
|
|
ExceptionCode, "Exception code.",
|
|
((PEXCEPTION_POINTERS)ExceptionRecord)->ExceptionRecord, "Exception record. Use .exr to display it.",
|
|
((PEXCEPTION_POINTERS)ExceptionRecord)->ContextRecord, "Context record. Use .cxr to display it.",
|
|
0, "");
|
|
|
|
//
|
|
// After we issued a verifier stop, if we decide to continue then
|
|
// we need to look for the next exception handler.
|
|
//
|
|
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
|
|
|
|
DWORD
|
|
WINAPI
|
|
AVrfpStandardThreadFunction (
|
|
LPVOID Context
|
|
)
|
|
{
|
|
PAVRF_THREAD_ENTRY Info = (PAVRF_THREAD_ENTRY)Context;
|
|
DWORD Result;
|
|
PAVRF_THREAD_ENTRY SearchEntry;
|
|
|
|
//
|
|
// The initialization below matters only in case the thread function raises
|
|
// an access violation. In most cases this will terminate the entire
|
|
// process.
|
|
//
|
|
|
|
Result = 0;
|
|
|
|
try {
|
|
|
|
//
|
|
// Add the thread entry to the thread table.
|
|
//
|
|
|
|
Info->Id = NtCurrentTeb()->ClientId.UniqueThread;
|
|
AVrfpThreadTableAddEntry (Info);
|
|
|
|
//
|
|
// Call the real thing.
|
|
//
|
|
|
|
Result = (Info->Function)(Info->Parameter);
|
|
}
|
|
except (AVrfpThreadFunctionExceptionFilter (_exception_code(), _exception_info())) {
|
|
|
|
//
|
|
// Nothing.
|
|
//
|
|
}
|
|
|
|
//
|
|
// Perform all typical checks for a thread that has just finished.
|
|
//
|
|
|
|
AVrfpCheckCurrentThreadTermination ();
|
|
|
|
//
|
|
// The thread entry should be `Info' but we will search it in the thread
|
|
// table nevertheless because there is a case when they can be different.
|
|
// This happens if fibers are used and a fiber starts in one thread and
|
|
// exits in another one. It is not clear if this is a safe programming
|
|
// practice but it is not rejected by current implementation and
|
|
// documentation.
|
|
//
|
|
|
|
SearchEntry = AVrfpThreadTableSearchEntry (NtCurrentTeb()->ClientId.UniqueThread);
|
|
|
|
if (SearchEntry != NULL) {
|
|
|
|
AVrfpThreadTableRemoveEntry (SearchEntry);
|
|
AVrfpFree (SearchEntry);
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
///////////////////////////////////////////// thread pool thread hook
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
PRTLP_START_THREAD AVrfpBaseCreateThreadPoolThreadOriginal;
|
|
PRTLP_EXIT_THREAD AVrfpBaseExitThreadPoolThreadOriginal;
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
AVrfpBaseCreateThreadPoolThread(
|
|
PUSER_THREAD_START_ROUTINE Function,
|
|
PVOID Parameter,
|
|
HANDLE * ThreadHandleReturn
|
|
)
|
|
{
|
|
PAVRF_THREAD_ENTRY Info;
|
|
PTEB Teb;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
Teb = NtCurrentTeb();
|
|
|
|
Info = AVrfpAllocate (sizeof *Info);
|
|
|
|
if (Info == NULL) {
|
|
|
|
Status = STATUS_NO_MEMORY;
|
|
goto Done;
|
|
}
|
|
|
|
Info->Parameter = Parameter;
|
|
Info->Function = (PTHREAD_START_ROUTINE)Function;
|
|
Info->ParentThreadId = Teb->ClientId.UniqueThread;
|
|
|
|
Status = (*AVrfpBaseCreateThreadPoolThreadOriginal) ((PUSER_THREAD_START_ROUTINE)AVrfpStandardThreadFunction,
|
|
Info,
|
|
ThreadHandleReturn);
|
|
|
|
Done:
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
|
|
if (Info != NULL) {
|
|
|
|
AVrfpFree (Info);
|
|
}
|
|
|
|
Teb->LastStatusValue = Status;
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
AVrfpBaseExitThreadPoolThread(
|
|
NTSTATUS Status
|
|
)
|
|
{
|
|
PAVRF_THREAD_ENTRY Entry;
|
|
|
|
//
|
|
// Perform all typical checks for a thread that will exit.
|
|
//
|
|
|
|
AVrfpCheckCurrentThreadTermination ();
|
|
|
|
//
|
|
// Before calling the real ExitThread we need to free the thread
|
|
// entry from the thread table.
|
|
//
|
|
|
|
Entry = AVrfpThreadTableSearchEntry (NtCurrentTeb()->ClientId.UniqueThread);
|
|
|
|
if (Entry != NULL) {
|
|
|
|
AVrfpThreadTableRemoveEntry (Entry);
|
|
AVrfpFree (Entry);
|
|
}
|
|
|
|
//
|
|
// Call the real thing.
|
|
//
|
|
|
|
return (*AVrfpBaseExitThreadPoolThreadOriginal) (Status);
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
AVrfpRtlSetThreadPoolStartFunc(
|
|
PRTLP_START_THREAD StartFunc,
|
|
PRTLP_EXIT_THREAD ExitFunc
|
|
)
|
|
{
|
|
//
|
|
// Save the original thread pool start and exit functions.
|
|
//
|
|
|
|
AVrfpBaseCreateThreadPoolThreadOriginal = StartFunc;
|
|
AVrfpBaseExitThreadPoolThreadOriginal = ExitFunc;
|
|
|
|
//
|
|
// Hook the thread pool start and exit functions to our private version.
|
|
//
|
|
|
|
return RtlSetThreadPoolStartFunc (AVrfpBaseCreateThreadPoolThread,
|
|
AVrfpBaseExitThreadPoolThread);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
VOID
|
|
AVrfpCheckThreadTermination (
|
|
HANDLE Thread
|
|
)
|
|
{
|
|
//
|
|
// Traverse the list of critical sections and look for any that
|
|
// have issues (double initialized, corrupted, etc.). The function
|
|
// will also break for locks abandoned (owned by the thread just
|
|
// about to terminate).
|
|
//
|
|
|
|
RtlCheckForOrphanedCriticalSections (Thread);
|
|
}
|
|
|
|
VOID
|
|
AVrfpCheckCurrentThreadTermination (
|
|
VOID
|
|
)
|
|
{
|
|
PAVRF_TLS_STRUCT TlsStruct;
|
|
|
|
TlsStruct = AVrfpGetVerifierTlsValue();
|
|
|
|
if (TlsStruct != NULL && TlsStruct->CountOfOwnedCriticalSections > 0) {
|
|
|
|
AVrfpCheckThreadTermination (NtCurrentThread());
|
|
}
|
|
}
|
|
|