mirror of
https://github.com/reactos/reactos.git
synced 2024-08-28 22:28:36 +00:00
1185 lines
32 KiB
C
1185 lines
32 KiB
C
/*
|
|
* PROJECT: ReactOS Kernel
|
|
* LICENSE: GPL - See COPYING in the top level directory
|
|
* FILE: ntoskrnl/ps/thread.c
|
|
* PURPOSE: Process Manager: Thread Management
|
|
* PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
|
|
* Thomas Weidenmueller (w3seek@reactos.org)
|
|
*/
|
|
|
|
/* INCLUDES ****************************************************************/
|
|
|
|
#include <ntoskrnl.h>
|
|
#define NDEBUG
|
|
#include <debug.h>
|
|
|
|
/* GLOBALS ******************************************************************/
|
|
|
|
extern BOOLEAN CcPfEnablePrefetcher;
|
|
extern ULONG MmReadClusterSize;
|
|
POBJECT_TYPE PsThreadType = NULL;
|
|
|
|
/* PRIVATE FUNCTIONS *********************************************************/
|
|
|
|
VOID
|
|
NTAPI
|
|
PspUserThreadStartup(IN PKSTART_ROUTINE StartRoutine,
|
|
IN PVOID StartContext)
|
|
{
|
|
PETHREAD Thread;
|
|
PTEB Teb;
|
|
BOOLEAN DeadThread = FALSE;
|
|
KIRQL OldIrql;
|
|
PAGED_CODE();
|
|
PSTRACE(PS_THREAD_DEBUG,
|
|
"StartRoutine: %p StartContext: %p\n", StartRoutine, StartContext);
|
|
|
|
/* Go to Passive Level */
|
|
KeLowerIrql(PASSIVE_LEVEL);
|
|
Thread = PsGetCurrentThread();
|
|
|
|
/* Check if the thread is dead */
|
|
if (Thread->DeadThread)
|
|
{
|
|
/* Remember that we're dead */
|
|
DeadThread = TRUE;
|
|
}
|
|
else
|
|
{
|
|
/* Get the Locale ID and save Preferred Proc */
|
|
Teb = NtCurrentTeb();
|
|
Teb->CurrentLocale = MmGetSessionLocaleId();
|
|
Teb->IdealProcessor = Thread->Tcb.IdealProcessor;
|
|
}
|
|
|
|
/* Check if this is a dead thread, or if we're hiding */
|
|
if (!(Thread->DeadThread) && !(Thread->HideFromDebugger))
|
|
{
|
|
/* We're not, so notify the debugger */
|
|
DbgkCreateThread(Thread, StartContext);
|
|
}
|
|
|
|
/* Make sure we're not already dead */
|
|
if (!DeadThread)
|
|
{
|
|
/* Check if the Prefetcher is enabled */
|
|
if (CcPfEnablePrefetcher)
|
|
{
|
|
/* FIXME: Prepare to prefetch this process */
|
|
}
|
|
|
|
/* Raise to APC */
|
|
KeRaiseIrql(APC_LEVEL, &OldIrql);
|
|
|
|
/* Queue the User APC */
|
|
KiInitializeUserApc(KeGetExceptionFrame(&Thread->Tcb),
|
|
KeGetTrapFrame(&Thread->Tcb),
|
|
PspSystemDllEntryPoint,
|
|
NULL,
|
|
PspSystemDllBase,
|
|
NULL);
|
|
|
|
/* Lower it back to passive */
|
|
KeLowerIrql(PASSIVE_LEVEL);
|
|
}
|
|
else
|
|
{
|
|
/* We're dead, kill us now */
|
|
PspTerminateThreadByPointer(Thread,
|
|
STATUS_THREAD_IS_TERMINATING,
|
|
TRUE);
|
|
}
|
|
|
|
/* Do we have a cookie set yet? */
|
|
while (!SharedUserData->Cookie)
|
|
{
|
|
LARGE_INTEGER SystemTime;
|
|
ULONG NewCookie;
|
|
PKPRCB Prcb;
|
|
|
|
/* Generate a new cookie */
|
|
KeQuerySystemTime(&SystemTime);
|
|
Prcb = KeGetCurrentPrcb();
|
|
NewCookie = (Prcb->MmPageFaultCount ^ Prcb->InterruptTime ^
|
|
SystemTime.u.LowPart ^ SystemTime.u.HighPart ^
|
|
(ULONG)(ULONG_PTR)&SystemTime);
|
|
|
|
/* Set the new cookie*/
|
|
InterlockedCompareExchange((LONG*)&SharedUserData->Cookie,
|
|
NewCookie,
|
|
0);
|
|
}
|
|
}
|
|
|
|
LONG
|
|
PspUnhandledExceptionInSystemThread(PEXCEPTION_POINTERS ExceptionPointers)
|
|
{
|
|
/* Print debugging information */
|
|
DPRINT1("PS: Unhandled Kernel Mode Exception Pointers = 0x%p\n",
|
|
ExceptionPointers);
|
|
DPRINT1("Code %x Addr %p Info0 %p Info1 %p Info2 %p Info3 %p\n",
|
|
ExceptionPointers->ExceptionRecord->ExceptionCode,
|
|
ExceptionPointers->ExceptionRecord->ExceptionAddress,
|
|
ExceptionPointers->ExceptionRecord->ExceptionInformation[0],
|
|
ExceptionPointers->ExceptionRecord->ExceptionInformation[1],
|
|
ExceptionPointers->ExceptionRecord->ExceptionInformation[2],
|
|
ExceptionPointers->ExceptionRecord->ExceptionInformation[3]);
|
|
|
|
/* Bugcheck the system */
|
|
KeBugCheckEx(SYSTEM_THREAD_EXCEPTION_NOT_HANDLED,
|
|
ExceptionPointers->ExceptionRecord->ExceptionCode,
|
|
(ULONG_PTR)ExceptionPointers->ExceptionRecord->ExceptionAddress,
|
|
(ULONG_PTR)ExceptionPointers->ExceptionRecord,
|
|
(ULONG_PTR)ExceptionPointers->ContextRecord);
|
|
return 0;
|
|
}
|
|
|
|
VOID
|
|
NTAPI
|
|
PspSystemThreadStartup(IN PKSTART_ROUTINE StartRoutine,
|
|
IN PVOID StartContext)
|
|
{
|
|
PETHREAD Thread;
|
|
PSTRACE(PS_THREAD_DEBUG,
|
|
"StartRoutine: %p StartContext: %p\n", StartRoutine, StartContext);
|
|
|
|
/* Unlock the dispatcher Database */
|
|
KeLowerIrql(PASSIVE_LEVEL);
|
|
Thread = PsGetCurrentThread();
|
|
|
|
/* Make sure the thread isn't gone */
|
|
_SEH2_TRY
|
|
{
|
|
if (!(Thread->Terminated) && !(Thread->DeadThread))
|
|
{
|
|
/* Call the Start Routine */
|
|
StartRoutine(StartContext);
|
|
}
|
|
}
|
|
_SEH2_EXCEPT(PspUnhandledExceptionInSystemThread(_SEH2_GetExceptionInformation()))
|
|
{
|
|
/* Bugcheck if we got here */
|
|
KeBugCheck(KMODE_EXCEPTION_NOT_HANDLED);
|
|
}
|
|
_SEH2_END;
|
|
|
|
/* Exit the thread */
|
|
PspTerminateThreadByPointer(Thread, STATUS_SUCCESS, TRUE);
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
PspCreateThread(OUT PHANDLE ThreadHandle,
|
|
IN ACCESS_MASK DesiredAccess,
|
|
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
|
|
IN HANDLE ProcessHandle,
|
|
IN PEPROCESS TargetProcess,
|
|
OUT PCLIENT_ID ClientId,
|
|
IN PCONTEXT ThreadContext,
|
|
IN PINITIAL_TEB InitialTeb,
|
|
IN BOOLEAN CreateSuspended,
|
|
IN PKSTART_ROUTINE StartRoutine OPTIONAL,
|
|
IN PVOID StartContext OPTIONAL)
|
|
{
|
|
HANDLE hThread;
|
|
PEPROCESS Process;
|
|
PETHREAD Thread;
|
|
PTEB TebBase = NULL;
|
|
KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
|
|
NTSTATUS Status, AccessStatus;
|
|
HANDLE_TABLE_ENTRY CidEntry;
|
|
ACCESS_STATE LocalAccessState;
|
|
PACCESS_STATE AccessState = &LocalAccessState;
|
|
AUX_ACCESS_DATA AuxData;
|
|
BOOLEAN Result, SdAllocated;
|
|
PSECURITY_DESCRIPTOR SecurityDescriptor;
|
|
SECURITY_SUBJECT_CONTEXT SubjectContext;
|
|
PAGED_CODE();
|
|
PSTRACE(PS_THREAD_DEBUG,
|
|
"ThreadContext: %p TargetProcess: %p ProcessHandle: %p\n",
|
|
ThreadContext, TargetProcess, ProcessHandle);
|
|
|
|
/* If we were called from PsCreateSystemThread, then we're kernel mode */
|
|
if (StartRoutine) PreviousMode = KernelMode;
|
|
|
|
/* Reference the Process by handle or pointer, depending on what we got */
|
|
if (ProcessHandle)
|
|
{
|
|
/* Normal thread or System Thread */
|
|
Status = ObReferenceObjectByHandle(ProcessHandle,
|
|
PROCESS_CREATE_THREAD,
|
|
PsProcessType,
|
|
PreviousMode,
|
|
(PVOID*)&Process,
|
|
NULL);
|
|
PSREFTRACE(Process);
|
|
}
|
|
else
|
|
{
|
|
/* System thread inside System Process, or Normal Thread with a bug */
|
|
if (StartRoutine)
|
|
{
|
|
/* Reference the Process by Pointer */
|
|
ObReferenceObject(TargetProcess);
|
|
Process = TargetProcess;
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
/* Fake ObReference returning this */
|
|
Status = STATUS_INVALID_HANDLE;
|
|
}
|
|
}
|
|
|
|
/* Check for success */
|
|
if (!NT_SUCCESS(Status)) return Status;
|
|
|
|
/* Also make sure that User-Mode isn't trying to create a system thread */
|
|
if ((PreviousMode != KernelMode) && (Process == PsInitialSystemProcess))
|
|
{
|
|
/* Fail */
|
|
ObDereferenceObject(Process);
|
|
return STATUS_INVALID_HANDLE;
|
|
}
|
|
|
|
/* Create Thread Object */
|
|
Status = ObCreateObject(PreviousMode,
|
|
PsThreadType,
|
|
ObjectAttributes,
|
|
PreviousMode,
|
|
NULL,
|
|
sizeof(ETHREAD),
|
|
0,
|
|
0,
|
|
(PVOID*)&Thread);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
/* We failed; dereference the process and exit */
|
|
ObDereferenceObject(Process);
|
|
return Status;
|
|
}
|
|
|
|
/* Zero the Object entirely */
|
|
RtlZeroMemory(Thread, sizeof(ETHREAD));
|
|
|
|
/* Initialize rundown protection */
|
|
ExInitializeRundownProtection(&Thread->RundownProtect);
|
|
|
|
/* Initialize exit code */
|
|
Thread->ExitStatus = STATUS_PENDING;
|
|
|
|
/* Set the Process CID */
|
|
Thread->ThreadsProcess = Process;
|
|
Thread->Cid.UniqueProcess = Process->UniqueProcessId;
|
|
|
|
/* Create Cid Handle */
|
|
CidEntry.Object = Thread;
|
|
CidEntry.GrantedAccess = 0;
|
|
Thread->Cid.UniqueThread = ExCreateHandle(PspCidTable, &CidEntry);
|
|
if (!Thread->Cid.UniqueThread)
|
|
{
|
|
/* We couldn't create the CID, dereference the thread and fail */
|
|
ObDereferenceObject(Thread);
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
/* Save the read cluster size */
|
|
Thread->ReadClusterSize = MmReadClusterSize;
|
|
|
|
/* Initialize the LPC Reply Semaphore */
|
|
KeInitializeSemaphore(&Thread->LpcReplySemaphore, 0, 1);
|
|
|
|
/* Initialize the list heads and locks */
|
|
InitializeListHead(&Thread->LpcReplyChain);
|
|
InitializeListHead(&Thread->IrpList);
|
|
InitializeListHead(&Thread->PostBlockList);
|
|
InitializeListHead(&Thread->ActiveTimerListHead);
|
|
KeInitializeSpinLock(&Thread->ActiveTimerListLock);
|
|
|
|
/* Acquire rundown protection */
|
|
if (!ExAcquireRundownProtection (&Process->RundownProtect))
|
|
{
|
|
/* Fail */
|
|
ObDereferenceObject(Thread);
|
|
return STATUS_PROCESS_IS_TERMINATING;
|
|
}
|
|
|
|
/* Now let the kernel initialize the context */
|
|
if (ThreadContext)
|
|
{
|
|
/* User-mode Thread, create Teb */
|
|
Status = MmCreateTeb(Process, &Thread->Cid, InitialTeb, &TebBase);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
/* Failed to create the TEB. Release rundown and dereference */
|
|
ExReleaseRundownProtection(&Process->RundownProtect);
|
|
ObDereferenceObject(Thread);
|
|
return Status;
|
|
}
|
|
|
|
/* Set the Start Addresses from the untrusted ThreadContext */
|
|
_SEH2_TRY
|
|
{
|
|
Thread->StartAddress = (PVOID)KeGetContextPc(ThreadContext);
|
|
Thread->Win32StartAddress = (PVOID)KeGetContextReturnRegister(ThreadContext);
|
|
}
|
|
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
Status = _SEH2_GetExceptionCode();
|
|
}
|
|
_SEH2_END;
|
|
|
|
/* Let the kernel intialize the Thread */
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
Status = KeInitThread(&Thread->Tcb,
|
|
NULL,
|
|
PspUserThreadStartup,
|
|
NULL,
|
|
Thread->StartAddress,
|
|
ThreadContext,
|
|
TebBase,
|
|
&Process->Pcb);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* System Thread */
|
|
Thread->StartAddress = StartRoutine;
|
|
PspSetCrossThreadFlag(Thread, CT_SYSTEM_THREAD_BIT);
|
|
|
|
/* Let the kernel intialize the Thread */
|
|
Status = KeInitThread(&Thread->Tcb,
|
|
NULL,
|
|
PspSystemThreadStartup,
|
|
StartRoutine,
|
|
StartContext,
|
|
NULL,
|
|
NULL,
|
|
&Process->Pcb);
|
|
}
|
|
|
|
/* Check if we failed */
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
/* Delete the TEB if we had done */
|
|
if (TebBase) MmDeleteTeb(Process, TebBase);
|
|
|
|
/* Release rundown and dereference */
|
|
ExReleaseRundownProtection(&Process->RundownProtect);
|
|
ObDereferenceObject(Thread);
|
|
return Status;
|
|
}
|
|
|
|
/* Lock the process */
|
|
KeEnterCriticalRegion();
|
|
ExAcquirePushLockExclusive(&Process->ProcessLock);
|
|
|
|
/* Make sure the proces didn't just die on us */
|
|
if (Process->ProcessDelete) goto Quickie;
|
|
|
|
/* Check if the thread was ours, terminated and it was user mode */
|
|
if ((Thread->Terminated) &&
|
|
(ThreadContext) &&
|
|
(Thread->ThreadsProcess == Process))
|
|
{
|
|
/* Cleanup, we don't want to start it up and context switch */
|
|
goto Quickie;
|
|
}
|
|
|
|
/*
|
|
* Insert the Thread into the Process's Thread List
|
|
* Note, this is the ETHREAD Thread List. It is removed in
|
|
* ps/kill.c!PspExitThread.
|
|
*/
|
|
InsertTailList(&Process->ThreadListHead, &Thread->ThreadListEntry);
|
|
Process->ActiveThreads++;
|
|
|
|
/* Start the thread */
|
|
KeStartThread(&Thread->Tcb);
|
|
|
|
/* Release the process lock */
|
|
ExReleasePushLockExclusive(&Process->ProcessLock);
|
|
KeLeaveCriticalRegion();
|
|
|
|
/* Release rundown */
|
|
ExReleaseRundownProtection(&Process->RundownProtect);
|
|
|
|
/* Notify WMI */
|
|
//WmiTraceProcess(Process, TRUE);
|
|
//WmiTraceThread(Thread, InitialTeb, TRUE);
|
|
|
|
/* Notify Thread Creation */
|
|
PspRunCreateThreadNotifyRoutines(Thread, TRUE);
|
|
|
|
/* Reference ourselves as a keep-alive */
|
|
ObReferenceObjectEx(Thread, 2);
|
|
|
|
/* Suspend the Thread if we have to */
|
|
if (CreateSuspended) KeSuspendThread(&Thread->Tcb);
|
|
|
|
/* Check if we were already terminated */
|
|
if (Thread->Terminated) KeForceResumeThread(&Thread->Tcb);
|
|
|
|
/* Create an access state */
|
|
Status = SeCreateAccessStateEx(NULL,
|
|
ThreadContext ?
|
|
PsGetCurrentProcess() : Process,
|
|
&LocalAccessState,
|
|
&AuxData,
|
|
DesiredAccess,
|
|
&PsThreadType->TypeInfo.GenericMapping);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
/* Access state failed, thread is dead */
|
|
PspSetCrossThreadFlag(Thread, CT_DEAD_THREAD_BIT);
|
|
|
|
/* If we were suspended, wake it up */
|
|
if (CreateSuspended) KeResumeThread(&Thread->Tcb);
|
|
|
|
/* Dispatch thread */
|
|
KeReadyThread(&Thread->Tcb);
|
|
|
|
/* Dereference completely to kill it */
|
|
ObDereferenceObjectEx(Thread, 2);
|
|
return Status;
|
|
}
|
|
|
|
/* Insert the Thread into the Object Manager */
|
|
Status = ObInsertObject(Thread,
|
|
AccessState,
|
|
DesiredAccess,
|
|
0,
|
|
NULL,
|
|
&hThread);
|
|
|
|
/* Delete the access state if we had one */
|
|
if (AccessState) SeDeleteAccessState(AccessState);
|
|
|
|
/* Check for success */
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
/* Wrap in SEH to protect against bad user-mode pointers */
|
|
_SEH2_TRY
|
|
{
|
|
/* Return Cid and Handle */
|
|
if (ClientId) *ClientId = Thread->Cid;
|
|
*ThreadHandle = hThread;
|
|
}
|
|
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
/* Thread insertion failed, thread is dead */
|
|
PspSetCrossThreadFlag(Thread, CT_DEAD_THREAD_BIT);
|
|
|
|
/* If we were suspended, wake it up */
|
|
if (CreateSuspended) KeResumeThread(&Thread->Tcb);
|
|
|
|
/* Dispatch thread */
|
|
KeReadyThread(&Thread->Tcb);
|
|
|
|
/* Dereference it, leaving only the keep-alive */
|
|
ObDereferenceObject(Thread);
|
|
|
|
/* Close its handle, killing it */
|
|
ObCloseHandle(hThread, PreviousMode);
|
|
|
|
/* Return the exception code */
|
|
_SEH2_YIELD(return _SEH2_GetExceptionCode());
|
|
}
|
|
_SEH2_END;
|
|
}
|
|
else
|
|
{
|
|
/* Thread insertion failed, thread is dead */
|
|
PspSetCrossThreadFlag(Thread, CT_DEAD_THREAD_BIT);
|
|
|
|
/* If we were suspended, wake it up */
|
|
if (CreateSuspended) KeResumeThread(&Thread->Tcb);
|
|
}
|
|
|
|
/* Get the create time */
|
|
KeQuerySystemTime(&Thread->CreateTime);
|
|
ASSERT(!(Thread->CreateTime.HighPart & 0xF0000000));
|
|
|
|
/* Make sure the thread isn't dead */
|
|
if (!Thread->DeadThread)
|
|
{
|
|
/* Get the thread's SD */
|
|
Status = ObGetObjectSecurity(Thread,
|
|
&SecurityDescriptor,
|
|
&SdAllocated);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
/* Thread insertion failed, thread is dead */
|
|
PspSetCrossThreadFlag(Thread, CT_DEAD_THREAD_BIT);
|
|
|
|
/* If we were suspended, wake it up */
|
|
if (CreateSuspended) KeResumeThread(&Thread->Tcb);
|
|
|
|
/* Dispatch thread */
|
|
KeReadyThread(&Thread->Tcb);
|
|
|
|
/* Dereference it, leaving only the keep-alive */
|
|
ObDereferenceObject(Thread);
|
|
|
|
/* Close its handle, killing it */
|
|
ObCloseHandle(hThread, PreviousMode);
|
|
return Status;
|
|
}
|
|
|
|
/* Create the subject context */
|
|
SubjectContext.ProcessAuditId = Process;
|
|
SubjectContext.PrimaryToken = PsReferencePrimaryToken(Process);
|
|
SubjectContext.ClientToken = NULL;
|
|
|
|
/* Do the access check */
|
|
Result = SeAccessCheck(SecurityDescriptor,
|
|
&SubjectContext,
|
|
FALSE,
|
|
MAXIMUM_ALLOWED,
|
|
0,
|
|
NULL,
|
|
&PsThreadType->TypeInfo.GenericMapping,
|
|
PreviousMode,
|
|
&Thread->GrantedAccess,
|
|
&AccessStatus);
|
|
|
|
/* Dereference the token and let go the SD */
|
|
ObFastDereferenceObject(&Process->Token,
|
|
SubjectContext.PrimaryToken);
|
|
ObReleaseObjectSecurity(SecurityDescriptor, SdAllocated);
|
|
|
|
/* Remove access if it failed */
|
|
if (!Result) Process->GrantedAccess = 0;
|
|
|
|
/* Set least some minimum access */
|
|
Thread->GrantedAccess |= (THREAD_TERMINATE |
|
|
THREAD_SET_INFORMATION |
|
|
THREAD_QUERY_INFORMATION);
|
|
}
|
|
else
|
|
{
|
|
/* Set the thread access mask to maximum */
|
|
Thread->GrantedAccess = THREAD_ALL_ACCESS;
|
|
}
|
|
|
|
/* Dispatch thread */
|
|
KeReadyThread(&Thread->Tcb);
|
|
|
|
/* Dereference it, leaving only the keep-alive */
|
|
ObDereferenceObject(Thread);
|
|
|
|
/* Return */
|
|
return Status;
|
|
|
|
/* Most annoying failure case ever, where we undo almost all manually */
|
|
Quickie:
|
|
/* When we get here, the process is locked, unlock it */
|
|
ExReleasePushLockExclusive(&Process->ProcessLock);
|
|
KeLeaveCriticalRegion();
|
|
|
|
/* Uninitailize it */
|
|
KeUninitThread(&Thread->Tcb);
|
|
|
|
/* If we had a TEB, delete it */
|
|
if (TebBase) MmDeleteTeb(Process, TebBase);
|
|
|
|
/* Release rundown protection, which we also hold */
|
|
ExReleaseRundownProtection(&Process->RundownProtect);
|
|
|
|
/* Dereference the thread and return failure */
|
|
ObDereferenceObject(Thread);
|
|
return STATUS_PROCESS_IS_TERMINATING;
|
|
}
|
|
|
|
/* PUBLIC FUNCTIONS **********************************************************/
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
PsCreateSystemThread(OUT PHANDLE ThreadHandle,
|
|
IN ACCESS_MASK DesiredAccess,
|
|
IN POBJECT_ATTRIBUTES ObjectAttributes,
|
|
IN HANDLE ProcessHandle,
|
|
IN PCLIENT_ID ClientId,
|
|
IN PKSTART_ROUTINE StartRoutine,
|
|
IN PVOID StartContext)
|
|
{
|
|
PEPROCESS TargetProcess = NULL;
|
|
HANDLE Handle = ProcessHandle;
|
|
PAGED_CODE();
|
|
PSTRACE(PS_THREAD_DEBUG,
|
|
"ProcessHandle: %p StartRoutine: %p StartContext: %p\n",
|
|
ProcessHandle, StartRoutine, StartContext);
|
|
|
|
/* Check if we have a handle. If not, use the System Process */
|
|
if (!ProcessHandle)
|
|
{
|
|
Handle = NULL;
|
|
TargetProcess = PsInitialSystemProcess;
|
|
}
|
|
|
|
/* Call the shared function */
|
|
return PspCreateThread(ThreadHandle,
|
|
DesiredAccess,
|
|
ObjectAttributes,
|
|
Handle,
|
|
TargetProcess,
|
|
ClientId,
|
|
NULL,
|
|
NULL,
|
|
FALSE,
|
|
StartRoutine,
|
|
StartContext);
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
PsLookupThreadByThreadId(IN HANDLE ThreadId,
|
|
OUT PETHREAD *Thread)
|
|
{
|
|
PHANDLE_TABLE_ENTRY CidEntry;
|
|
PETHREAD FoundThread;
|
|
NTSTATUS Status = STATUS_INVALID_PARAMETER;
|
|
PAGED_CODE();
|
|
PSTRACE(PS_THREAD_DEBUG, "ThreadId: %p\n", ThreadId);
|
|
KeEnterCriticalRegion();
|
|
|
|
/* Get the CID Handle Entry */
|
|
CidEntry = ExMapHandleToPointer(PspCidTable, ThreadId);
|
|
if (CidEntry)
|
|
{
|
|
/* Get the Thread */
|
|
FoundThread = CidEntry->Object;
|
|
|
|
/* Make sure it's really a thread */
|
|
if (FoundThread->Tcb.Header.Type == ThreadObject)
|
|
{
|
|
/* Safe Reference and return it */
|
|
if (ObReferenceObjectSafe(FoundThread))
|
|
{
|
|
*Thread = FoundThread;
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
/* Unlock the Entry */
|
|
ExUnlockHandleTableEntry(PspCidTable, CidEntry);
|
|
}
|
|
|
|
/* Return to caller */
|
|
KeLeaveCriticalRegion();
|
|
return Status;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
ULONG
|
|
NTAPI
|
|
PsGetThreadFreezeCount(IN PETHREAD Thread)
|
|
{
|
|
return Thread->Tcb.FreezeCount;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
BOOLEAN
|
|
NTAPI
|
|
PsGetThreadHardErrorsAreDisabled(IN PETHREAD Thread)
|
|
{
|
|
return Thread->HardErrorsAreDisabled ? TRUE : FALSE;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
HANDLE
|
|
NTAPI
|
|
PsGetThreadId(IN PETHREAD Thread)
|
|
{
|
|
return Thread->Cid.UniqueThread;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
HANDLE
|
|
NTAPI
|
|
PsGetCurrentThreadId(VOID)
|
|
{
|
|
return PsGetCurrentThread()->Cid.UniqueThread;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
PEPROCESS
|
|
NTAPI
|
|
PsGetThreadProcess(IN PETHREAD Thread)
|
|
{
|
|
return Thread->ThreadsProcess;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
PEPROCESS
|
|
NTAPI
|
|
PsGetCurrentThreadProcess(VOID)
|
|
{
|
|
return PsGetCurrentThread()->ThreadsProcess;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
HANDLE
|
|
NTAPI
|
|
PsGetThreadProcessId(IN PETHREAD Thread)
|
|
{
|
|
return Thread->Cid.UniqueProcess;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
HANDLE
|
|
NTAPI
|
|
PsGetCurrentThreadProcessId(VOID)
|
|
{
|
|
return PsGetCurrentThread()->Cid.UniqueProcess;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
ULONG
|
|
NTAPI
|
|
PsGetThreadSessionId(IN PETHREAD Thread)
|
|
{
|
|
return MmGetSessionId(Thread->ThreadsProcess);
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
PTEB
|
|
NTAPI
|
|
PsGetThreadTeb(IN PETHREAD Thread)
|
|
{
|
|
return Thread->Tcb.Teb;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
PVOID
|
|
NTAPI
|
|
PsGetCurrentThreadTeb(VOID)
|
|
{
|
|
return PsGetCurrentThread()->Tcb.Teb;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
PVOID
|
|
NTAPI
|
|
PsGetThreadWin32Thread(IN PETHREAD Thread)
|
|
{
|
|
return Thread->Tcb.Win32Thread;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
PVOID
|
|
NTAPI
|
|
PsGetCurrentThreadWin32Thread(VOID)
|
|
{
|
|
return PsGetCurrentThread()->Tcb.Win32Thread;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
PVOID
|
|
NTAPI
|
|
PsGetCurrentThreadWin32ThreadAndEnterCriticalRegion(
|
|
_Out_ HANDLE* OutProcessId)
|
|
{
|
|
PETHREAD CurrentThread;
|
|
|
|
/* Get the current thread */
|
|
CurrentThread = PsGetCurrentThread();
|
|
|
|
/* Return the process id */
|
|
*OutProcessId = CurrentThread->Cid.UniqueProcess;
|
|
|
|
/* Enter critical region */
|
|
KeEnterCriticalRegion();
|
|
|
|
/* Return the win32 thread */
|
|
return CurrentThread->Tcb.Win32Thread;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
KPROCESSOR_MODE
|
|
NTAPI
|
|
PsGetCurrentThreadPreviousMode(VOID)
|
|
{
|
|
return (KPROCESSOR_MODE)PsGetCurrentThread()->Tcb.PreviousMode;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
PVOID
|
|
NTAPI
|
|
PsGetCurrentThreadStackBase(VOID)
|
|
{
|
|
return PsGetCurrentThread()->Tcb.StackBase;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
PVOID
|
|
NTAPI
|
|
PsGetCurrentThreadStackLimit(VOID)
|
|
{
|
|
return (PVOID)PsGetCurrentThread()->Tcb.StackLimit;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
BOOLEAN
|
|
NTAPI
|
|
PsIsThreadTerminating(IN PETHREAD Thread)
|
|
{
|
|
return Thread->Terminated ? TRUE : FALSE;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
BOOLEAN
|
|
NTAPI
|
|
PsIsSystemThread(IN PETHREAD Thread)
|
|
{
|
|
return Thread->SystemThread ? TRUE: FALSE;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
BOOLEAN
|
|
NTAPI
|
|
PsIsThreadImpersonating(IN PETHREAD Thread)
|
|
{
|
|
return Thread->ActiveImpersonationInfo ? TRUE : FALSE;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
VOID
|
|
NTAPI
|
|
PsSetThreadHardErrorsAreDisabled(IN PETHREAD Thread,
|
|
IN BOOLEAN HardErrorsAreDisabled)
|
|
{
|
|
Thread->HardErrorsAreDisabled = HardErrorsAreDisabled;
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
PVOID
|
|
NTAPI
|
|
PsSetThreadWin32Thread(
|
|
_Inout_ PETHREAD Thread,
|
|
_In_ PVOID Win32Thread,
|
|
_In_ PVOID OldWin32Thread)
|
|
{
|
|
/* Are we setting the win32 process? */
|
|
if (Win32Thread != NULL)
|
|
{
|
|
/* Just exchange it */
|
|
return InterlockedExchangePointer(&Thread->Tcb.Win32Thread,
|
|
Win32Thread);
|
|
}
|
|
else
|
|
{
|
|
/* We are resetting, only exchange when the old win32 thread matches */
|
|
return InterlockedCompareExchangePointer(&Thread->Tcb.Win32Thread,
|
|
Win32Thread,
|
|
OldWin32Thread);
|
|
}
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
PsWrapApcWow64Thread(IN OUT PVOID *ApcContext,
|
|
IN OUT PVOID *ApcRoutine)
|
|
{
|
|
UNIMPLEMENTED;
|
|
return STATUS_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
NtCreateThread(OUT PHANDLE ThreadHandle,
|
|
IN ACCESS_MASK DesiredAccess,
|
|
IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
|
|
IN HANDLE ProcessHandle,
|
|
OUT PCLIENT_ID ClientId,
|
|
IN PCONTEXT ThreadContext,
|
|
IN PINITIAL_TEB InitialTeb,
|
|
IN BOOLEAN CreateSuspended)
|
|
{
|
|
INITIAL_TEB SafeInitialTeb;
|
|
PAGED_CODE();
|
|
PSTRACE(PS_THREAD_DEBUG,
|
|
"ProcessHandle: %p Context: %p\n", ProcessHandle, ThreadContext);
|
|
|
|
/* Check if this was from user-mode */
|
|
if (KeGetPreviousMode() != KernelMode)
|
|
{
|
|
/* Make sure that we got a context */
|
|
if (!ThreadContext) return STATUS_INVALID_PARAMETER;
|
|
|
|
/* Protect checks */
|
|
_SEH2_TRY
|
|
{
|
|
/* Make sure the handle pointer we got is valid */
|
|
ProbeForWriteHandle(ThreadHandle);
|
|
|
|
/* Check if the caller wants a client id */
|
|
if (ClientId)
|
|
{
|
|
/* Make sure we can write to it */
|
|
ProbeForWrite(ClientId, sizeof(CLIENT_ID), sizeof(ULONG));
|
|
}
|
|
|
|
/* Make sure that the entire context is readable */
|
|
ProbeForRead(ThreadContext, sizeof(CONTEXT), sizeof(ULONG));
|
|
|
|
/* Check the Initial TEB */
|
|
ProbeForRead(InitialTeb, sizeof(INITIAL_TEB), sizeof(ULONG));
|
|
SafeInitialTeb = *InitialTeb;
|
|
}
|
|
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
/* Return the exception code */
|
|
_SEH2_YIELD(return _SEH2_GetExceptionCode());
|
|
}
|
|
_SEH2_END;
|
|
}
|
|
else
|
|
{
|
|
/* Use the Initial TEB as is */
|
|
SafeInitialTeb = *InitialTeb;
|
|
}
|
|
|
|
/* Call the shared function */
|
|
return PspCreateThread(ThreadHandle,
|
|
DesiredAccess,
|
|
ObjectAttributes,
|
|
ProcessHandle,
|
|
NULL,
|
|
ClientId,
|
|
ThreadContext,
|
|
&SafeInitialTeb,
|
|
CreateSuspended,
|
|
NULL,
|
|
NULL);
|
|
}
|
|
|
|
/*
|
|
* @implemented
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
NtOpenThread(OUT PHANDLE ThreadHandle,
|
|
IN ACCESS_MASK DesiredAccess,
|
|
IN POBJECT_ATTRIBUTES ObjectAttributes,
|
|
IN PCLIENT_ID ClientId OPTIONAL)
|
|
{
|
|
KPROCESSOR_MODE PreviousMode = KeGetPreviousMode();
|
|
CLIENT_ID SafeClientId;
|
|
ULONG Attributes = 0;
|
|
HANDLE hThread = NULL;
|
|
NTSTATUS Status;
|
|
PETHREAD Thread;
|
|
BOOLEAN HasObjectName = FALSE;
|
|
ACCESS_STATE AccessState;
|
|
AUX_ACCESS_DATA AuxData;
|
|
PAGED_CODE();
|
|
PSTRACE(PS_THREAD_DEBUG,
|
|
"ClientId: %p ObjectAttributes: %p\n", ClientId, ObjectAttributes);
|
|
|
|
/* Check if we were called from user mode */
|
|
if (PreviousMode != KernelMode)
|
|
{
|
|
/* Enter SEH for probing */
|
|
_SEH2_TRY
|
|
{
|
|
/* Probe the thread handle */
|
|
ProbeForWriteHandle(ThreadHandle);
|
|
|
|
/* Check for a CID structure */
|
|
if (ClientId)
|
|
{
|
|
/* Probe and capture it */
|
|
ProbeForRead(ClientId, sizeof(CLIENT_ID), sizeof(ULONG));
|
|
SafeClientId = *ClientId;
|
|
ClientId = &SafeClientId;
|
|
}
|
|
|
|
/*
|
|
* Just probe the object attributes structure, don't capture it
|
|
* completely. This is done later if necessary
|
|
*/
|
|
ProbeForRead(ObjectAttributes,
|
|
sizeof(OBJECT_ATTRIBUTES),
|
|
sizeof(ULONG));
|
|
HasObjectName = (ObjectAttributes->ObjectName != NULL);
|
|
|
|
/* Validate user attributes */
|
|
Attributes = ObpValidateAttributes(ObjectAttributes->Attributes, PreviousMode);
|
|
}
|
|
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
/* Return the exception code */
|
|
_SEH2_YIELD(return _SEH2_GetExceptionCode());
|
|
}
|
|
_SEH2_END;
|
|
}
|
|
else
|
|
{
|
|
/* Otherwise just get the data directly */
|
|
HasObjectName = (ObjectAttributes->ObjectName != NULL);
|
|
|
|
/* Still have to sanitize attributes */
|
|
Attributes = ObpValidateAttributes(ObjectAttributes->Attributes, PreviousMode);
|
|
}
|
|
|
|
/* Can't pass both, fail */
|
|
if ((HasObjectName) && (ClientId)) return STATUS_INVALID_PARAMETER_MIX;
|
|
|
|
/* Create an access state */
|
|
Status = SeCreateAccessState(&AccessState,
|
|
&AuxData,
|
|
DesiredAccess,
|
|
&PsThreadType->TypeInfo.GenericMapping);
|
|
if (!NT_SUCCESS(Status)) return Status;
|
|
|
|
/* Check if this is a debugger */
|
|
if (SeSinglePrivilegeCheck(SeDebugPrivilege, PreviousMode))
|
|
{
|
|
/* Did he want full access? */
|
|
if (AccessState.RemainingDesiredAccess & MAXIMUM_ALLOWED)
|
|
{
|
|
/* Give it to him */
|
|
AccessState.PreviouslyGrantedAccess |= THREAD_ALL_ACCESS;
|
|
}
|
|
else
|
|
{
|
|
/* Otherwise just give every other access he could want */
|
|
AccessState.PreviouslyGrantedAccess |=
|
|
AccessState.RemainingDesiredAccess;
|
|
}
|
|
|
|
/* The caller desires nothing else now */
|
|
AccessState.RemainingDesiredAccess = 0;
|
|
}
|
|
|
|
/* Open by name if one was given */
|
|
if (HasObjectName)
|
|
{
|
|
/* Open it */
|
|
Status = ObOpenObjectByName(ObjectAttributes,
|
|
PsThreadType,
|
|
PreviousMode,
|
|
&AccessState,
|
|
0,
|
|
NULL,
|
|
&hThread);
|
|
|
|
/* Get rid of the access state */
|
|
SeDeleteAccessState(&AccessState);
|
|
}
|
|
else if (ClientId)
|
|
{
|
|
/* Open by Thread ID */
|
|
if (ClientId->UniqueProcess)
|
|
{
|
|
/* Get the Process */
|
|
Status = PsLookupProcessThreadByCid(ClientId, NULL, &Thread);
|
|
}
|
|
else
|
|
{
|
|
/* Get the Process */
|
|
Status = PsLookupThreadByThreadId(ClientId->UniqueThread, &Thread);
|
|
}
|
|
|
|
/* Check if we didn't find anything */
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
/* Get rid of the access state and return */
|
|
SeDeleteAccessState(&AccessState);
|
|
return Status;
|
|
}
|
|
|
|
/* Open the Thread Object */
|
|
Status = ObOpenObjectByPointer(Thread,
|
|
Attributes,
|
|
&AccessState,
|
|
0,
|
|
PsThreadType,
|
|
PreviousMode,
|
|
&hThread);
|
|
|
|
/* Delete the access state and dereference the thread */
|
|
SeDeleteAccessState(&AccessState);
|
|
ObDereferenceObject(Thread);
|
|
}
|
|
else
|
|
{
|
|
/* Neither an object name nor a client id was passed */
|
|
return STATUS_INVALID_PARAMETER_MIX;
|
|
}
|
|
|
|
/* Check for success */
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
/* Protect against bad user-mode pointers */
|
|
_SEH2_TRY
|
|
{
|
|
/* Write back the handle */
|
|
*ThreadHandle = hThread;
|
|
}
|
|
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
/* Get the exception code */
|
|
Status = _SEH2_GetExceptionCode();
|
|
}
|
|
_SEH2_END;
|
|
}
|
|
|
|
/* Return status */
|
|
return Status;
|
|
}
|
|
|
|
/* EOF */
|