reactos/ntoskrnl/ps/kill.c
George Bișoc ee697cfeef
[NTOS:PS] Dereference the quota block during process cleanup
Ensure that when we're cleaning up the EPROCESS object, that we are dereferencing the quota block the process in question was using. The routine will automatically request a quota block cleanup if the process that dereferenced the quota block was the last.
2022-01-11 10:11:10 +01:00

1383 lines
41 KiB
C

/*
* PROJECT: ReactOS Kernel
* LICENSE: GPL - See COPYING in the top level directory
* FILE: ntoskrnl/ps/kill.c
* PURPOSE: Process Manager: Process and Thread Termination
* PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
* Filip Navara (xnavara@reactos.org)
* Thomas Weidenmueller (w3seek@reactos.org
*/
/* INCLUDES *****************************************************************/
#include <ntoskrnl.h>
#define NDEBUG
#include <debug.h>
/* GLOBALS *******************************************************************/
LIST_ENTRY PspReaperListHead = { NULL, NULL };
WORK_QUEUE_ITEM PspReaperWorkItem;
LARGE_INTEGER ShortTime = {{-10 * 100 * 1000, -1}};
/* PRIVATE FUNCTIONS *********************************************************/
VOID
NTAPI
PspCatchCriticalBreak(IN PCHAR Message,
IN PVOID ProcessOrThread,
IN PCHAR ImageName)
{
CHAR Action[2];
BOOLEAN Handled = FALSE;
PAGED_CODE();
/* Check if a debugger is enabled */
if (KdDebuggerEnabled)
{
/* Print out the message */
DbgPrint(Message, ProcessOrThread, ImageName);
do
{
/* If a debugger isn't present, don't prompt */
if (KdDebuggerNotPresent) break;
/* A debuger is active, prompt for action */
DbgPrompt("Break, or Ignore (bi)?", Action, sizeof(Action));
switch (Action[0])
{
/* Break */
case 'B': case 'b':
/* Do a breakpoint */
DbgBreakPoint();
/* Ignore */
case 'I': case 'i':
/* Handle it */
Handled = TRUE;
/* Unrecognized */
default:
break;
}
} while (!Handled);
}
/* Did we ultimately handle this? */
if (!Handled)
{
/* We didn't, bugcheck */
KeBugCheckEx(CRITICAL_OBJECT_TERMINATION,
((PKPROCESS)ProcessOrThread)->Header.Type,
(ULONG_PTR)ProcessOrThread,
(ULONG_PTR)ImageName,
(ULONG_PTR)Message);
}
}
NTSTATUS
NTAPI
PspTerminateProcess(IN PEPROCESS Process,
IN NTSTATUS ExitStatus)
{
PETHREAD Thread;
NTSTATUS Status = STATUS_NOTHING_TO_TERMINATE;
PAGED_CODE();
PSTRACE(PS_KILL_DEBUG,
"Process: %p ExitStatus: %d\n", Process, ExitStatus);
PSREFTRACE(Process);
/* Check if this is a Critical Process */
if (Process->BreakOnTermination)
{
/* Break to debugger */
PspCatchCriticalBreak("Terminating critical process 0x%p (%s)\n",
Process,
Process->ImageFileName);
}
/* Set the delete flag */
InterlockedOr((PLONG)&Process->Flags, PSF_PROCESS_DELETE_BIT);
/* Get the first thread */
Thread = PsGetNextProcessThread(Process, NULL);
while (Thread)
{
/* Kill it */
PspTerminateThreadByPointer(Thread, ExitStatus, FALSE);
Thread = PsGetNextProcessThread(Process, Thread);
/* We had at least one thread, so termination is OK */
Status = STATUS_SUCCESS;
}
/* Check if there was nothing to terminate or if we have a debug port */
if ((Status == STATUS_NOTHING_TO_TERMINATE) || (Process->DebugPort))
{
/* Clear the handle table anyway */
ObClearProcessHandleTable(Process);
}
/* Return status */
return Status;
}
NTSTATUS
NTAPI
PsTerminateProcess(IN PEPROCESS Process,
IN NTSTATUS ExitStatus)
{
/* Call the internal API */
return PspTerminateProcess(Process, ExitStatus);
}
VOID
NTAPI
PspShutdownProcessManager(VOID)
{
PEPROCESS Process = NULL;
/* Loop every process */
Process = PsGetNextProcess(Process);
while (Process)
{
/* Make sure this isn't the idle or initial process */
if ((Process != PsInitialSystemProcess) && (Process != PsIdleProcess))
{
/* Kill it */
PspTerminateProcess(Process, STATUS_SYSTEM_SHUTDOWN);
}
/* Get the next process */
Process = PsGetNextProcess(Process);
}
}
VOID
NTAPI
PspExitApcRundown(IN PKAPC Apc)
{
PAGED_CODE();
/* Free the APC */
ExFreePool(Apc);
}
VOID
NTAPI
PspReapRoutine(IN PVOID Context)
{
PSINGLE_LIST_ENTRY NextEntry;
PETHREAD Thread;
PSTRACE(PS_KILL_DEBUG, "Context: %p\n", Context);
/* Start main loop */
do
{
/* Write magic value and return the next entry to process */
NextEntry = InterlockedExchangePointer((PVOID*)&PspReaperListHead.Flink,
(PVOID)1);
ASSERT((NextEntry != NULL) && (NextEntry != (PVOID)1));
/* Start inner loop */
do
{
/* Get the first Thread Entry */
Thread = CONTAINING_RECORD(NextEntry, ETHREAD, ReaperLink);
/* Delete this entry's kernel stack */
MmDeleteKernelStack((PVOID)Thread->Tcb.StackBase,
Thread->Tcb.LargeStack);
Thread->Tcb.InitialStack = NULL;
/* Move to the next entry */
NextEntry = NextEntry->Next;
/* Dereference this thread */
ObDereferenceObject(Thread);
} while ((NextEntry != NULL) && (NextEntry != (PVOID)1));
/* Remove magic value, keep looping if it got changed */
} while (InterlockedCompareExchangePointer((PVOID*)&PspReaperListHead.Flink,
NULL,
(PVOID)1) != (PVOID)1);
}
#if DBG
VOID
NTAPI
PspCheckProcessList(VOID)
{
PLIST_ENTRY Entry;
KeAcquireGuardedMutex(&PspActiveProcessMutex);
DbgPrint("# checking PsActiveProcessHead @ %p\n", &PsActiveProcessHead);
for (Entry = PsActiveProcessHead.Flink;
Entry != &PsActiveProcessHead;
Entry = Entry->Flink)
{
PEPROCESS Process = CONTAINING_RECORD(Entry, EPROCESS, ActiveProcessLinks);
POBJECT_HEADER Header;
PVOID Info, HeaderLocation;
/* Get the header and assume this is what we'll free */
Header = OBJECT_TO_OBJECT_HEADER(Process);
HeaderLocation = Header;
/* To find the header, walk backwards from how we allocated */
if ((Info = OBJECT_HEADER_TO_CREATOR_INFO(Header)))
{
HeaderLocation = Info;
}
if ((Info = OBJECT_HEADER_TO_NAME_INFO(Header)))
{
HeaderLocation = Info;
}
if ((Info = OBJECT_HEADER_TO_HANDLE_INFO(Header)))
{
HeaderLocation = Info;
}
if ((Info = OBJECT_HEADER_TO_QUOTA_INFO(Header)))
{
HeaderLocation = Info;
}
ExpCheckPoolAllocation(HeaderLocation, NonPagedPool, 'corP');
}
KeReleaseGuardedMutex(&PspActiveProcessMutex);
}
#endif
VOID
NTAPI
PspDeleteProcess(IN PVOID ObjectBody)
{
PEPROCESS Process = (PEPROCESS)ObjectBody;
KAPC_STATE ApcState;
PAGED_CODE();
PSTRACE(PS_KILL_DEBUG, "ObjectBody: %p\n", ObjectBody);
PSREFTRACE(Process);
/* Check if it has an Active Process Link */
if (Process->ActiveProcessLinks.Flink)
{
/* Remove it from the Active List */
KeAcquireGuardedMutex(&PspActiveProcessMutex);
RemoveEntryList(&Process->ActiveProcessLinks);
Process->ActiveProcessLinks.Flink = NULL;
Process->ActiveProcessLinks.Blink = NULL;
KeReleaseGuardedMutex(&PspActiveProcessMutex);
}
/* Check for Auditing information */
if (Process->SeAuditProcessCreationInfo.ImageFileName)
{
/* Free it */
ExFreePoolWithTag(Process->SeAuditProcessCreationInfo.ImageFileName,
TAG_SEPA);
Process->SeAuditProcessCreationInfo.ImageFileName = NULL;
}
/* Check if we have a job */
if (Process->Job)
{
/* Remove the process from the job */
PspRemoveProcessFromJob(Process, Process->Job);
/* Dereference it */
ObDereferenceObject(Process->Job);
Process->Job = NULL;
}
/* Increase the stack count */
Process->Pcb.StackCount++;
/* Check if we have a debug port */
if (Process->DebugPort)
{
/* Deference the Debug Port */
ObDereferenceObject(Process->DebugPort);
Process->DebugPort = NULL;
}
/* Check if we have an exception port */
if (Process->ExceptionPort)
{
/* Deference the Exception Port */
ObDereferenceObject(Process->ExceptionPort);
Process->ExceptionPort = NULL;
}
/* Check if we have a section object */
if (Process->SectionObject)
{
/* Deference the Section Object */
ObDereferenceObject(Process->SectionObject);
Process->SectionObject = NULL;
}
#if defined(_X86_)
/* Clean Ldt and Vdm objects */
PspDeleteLdt(Process);
PspDeleteVdmObjects(Process);
#endif
/* Delete the Object Table */
if (Process->ObjectTable)
{
/* Attach to the process */
KeStackAttachProcess(&Process->Pcb, &ApcState);
/* Kill the Object Info */
ObKillProcess(Process);
/* Detach */
KeUnstackDetachProcess(&ApcState);
}
/* Check if we have an address space, and clean it */
if (Process->HasAddressSpace)
{
/* Attach to the process */
KeStackAttachProcess(&Process->Pcb, &ApcState);
/* Clean the Address Space */
PspExitProcess(FALSE, Process);
/* Detach */
KeUnstackDetachProcess(&ApcState);
/* Completely delete the Address Space */
MmDeleteProcessAddressSpace(Process);
}
/* See if we have a PID */
if (Process->UniqueProcessId)
{
/* Delete the PID */
if (!(ExDestroyHandle(PspCidTable, Process->UniqueProcessId, NULL)))
{
/* Something wrong happened, bugcheck */
KeBugCheck(CID_HANDLE_DELETION);
}
}
/* Cleanup security information */
PspDeleteProcessSecurity(Process);
/* Check if we have kept information on the Working Set */
if (Process->WorkingSetWatch)
{
/* Free it */
ExFreePool(Process->WorkingSetWatch);
/* And return the quota it was taking up */
PsReturnProcessNonPagedPoolQuota(Process, 0x2000);
}
/* Dereference the Device Map */
ObDereferenceDeviceMap(Process);
/*
* Dereference the quota block, the function
* will invoke a quota block cleanup if the
* block itself is no longer used by anybody.
*/
PspDereferenceQuotaBlock(Process, Process->QuotaBlock);
}
VOID
NTAPI
PspDeleteThread(IN PVOID ObjectBody)
{
PETHREAD Thread = (PETHREAD)ObjectBody;
PEPROCESS Process = Thread->ThreadsProcess;
PAGED_CODE();
PSTRACE(PS_KILL_DEBUG, "ObjectBody: %p\n", ObjectBody);
PSREFTRACE(Thread);
ASSERT(Thread->Tcb.Win32Thread == NULL);
/* Check if we have a stack */
if (Thread->Tcb.InitialStack)
{
/* Release it */
MmDeleteKernelStack((PVOID)Thread->Tcb.StackBase,
Thread->Tcb.LargeStack);
}
/* Check if we have a CID Handle */
if (Thread->Cid.UniqueThread)
{
/* Delete the CID Handle */
if (!(ExDestroyHandle(PspCidTable, Thread->Cid.UniqueThread, NULL)))
{
/* Something wrong happened, bugcheck */
KeBugCheck(CID_HANDLE_DELETION);
}
}
/* Cleanup impersionation information */
PspDeleteThreadSecurity(Thread);
/* Make sure the thread was inserted, before continuing */
if (!Process) return;
/* Check if the thread list is valid */
if (Thread->ThreadListEntry.Flink)
{
/* Lock the thread's process */
KeEnterCriticalRegion();
ExAcquirePushLockExclusive(&Process->ProcessLock);
/* Remove us from the list */
RemoveEntryList(&Thread->ThreadListEntry);
/* Release the lock */
ExReleasePushLockExclusive(&Process->ProcessLock);
KeLeaveCriticalRegion();
}
/* Dereference the Process */
ObDereferenceObject(Process);
}
/*
* FUNCTION: Terminates the current thread
* See "Windows Internals" - Chapter 13, Page 50-53
*/
VOID
NTAPI
PspExitThread(IN NTSTATUS ExitStatus)
{
CLIENT_DIED_MSG TerminationMsg;
NTSTATUS Status;
PTEB Teb;
PEPROCESS CurrentProcess;
PETHREAD Thread, OtherThread, PreviousThread = NULL;
PVOID DeallocationStack;
SIZE_T Dummy;
BOOLEAN Last = FALSE;
PTERMINATION_PORT TerminationPort, NextPort;
PLIST_ENTRY FirstEntry, CurrentEntry;
PKAPC Apc;
PTOKEN PrimaryToken;
PAGED_CODE();
PSTRACE(PS_KILL_DEBUG, "ExitStatus: %d\n", ExitStatus);
/* Get the Current Thread and Process */
Thread = PsGetCurrentThread();
CurrentProcess = Thread->ThreadsProcess;
ASSERT((Thread) == PsGetCurrentThread());
/* Can't terminate a thread if it attached another process */
if (KeIsAttachedProcess())
{
/* Bugcheck */
KeBugCheckEx(INVALID_PROCESS_ATTACH_ATTEMPT,
(ULONG_PTR)CurrentProcess,
(ULONG_PTR)Thread->Tcb.ApcState.Process,
(ULONG_PTR)Thread->Tcb.ApcStateIndex,
(ULONG_PTR)Thread);
}
/* Lower to Passive Level */
KeLowerIrql(PASSIVE_LEVEL);
/* Can't be a worker thread */
if (Thread->ActiveExWorker)
{
/* Bugcheck */
KeBugCheckEx(ACTIVE_EX_WORKER_THREAD_TERMINATION,
(ULONG_PTR)Thread,
0,
0,
0);
}
/* Can't have pending APCs */
if (Thread->Tcb.CombinedApcDisable != 0)
{
/* Bugcheck */
KeBugCheckEx(KERNEL_APC_PENDING_DURING_EXIT,
0,
Thread->Tcb.CombinedApcDisable,
0,
1);
}
/* Lock the thread */
ExWaitForRundownProtectionRelease(&Thread->RundownProtect);
/* Cleanup the power state */
PopCleanupPowerState((PPOWER_STATE)&Thread->Tcb.PowerState);
/* Call the WMI Callback for Threads */
//WmiTraceThread(Thread, NULL, FALSE);
/* Run Thread Notify Routines before we desintegrate the thread */
PspRunCreateThreadNotifyRoutines(Thread, FALSE);
/* Lock the Process before we modify its thread entries */
KeEnterCriticalRegion();
ExAcquirePushLockExclusive(&CurrentProcess->ProcessLock);
/* Decrease the active thread count, and check if it's 0 */
if (!(--CurrentProcess->ActiveThreads))
{
/* Set the delete flag */
InterlockedOr((PLONG)&CurrentProcess->Flags, PSF_PROCESS_DELETE_BIT);
/* Remember we are last */
Last = TRUE;
/* Check if this termination is due to the thread dying */
if (ExitStatus == STATUS_THREAD_IS_TERMINATING)
{
/* Check if the last thread was pending */
if (CurrentProcess->ExitStatus == STATUS_PENDING)
{
/* Use the last exit status */
CurrentProcess->ExitStatus = CurrentProcess->
LastThreadExitStatus;
}
}
else
{
/* Just a normal exit, write the code */
CurrentProcess->ExitStatus = ExitStatus;
}
/* Loop all the current threads */
FirstEntry = &CurrentProcess->ThreadListHead;
CurrentEntry = FirstEntry->Flink;
while (FirstEntry != CurrentEntry)
{
/* Get the thread on the list */
OtherThread = CONTAINING_RECORD(CurrentEntry,
ETHREAD,
ThreadListEntry);
/* Check if it's a thread that's still alive */
if ((OtherThread != Thread) &&
!(KeReadStateThread(&OtherThread->Tcb)) &&
(ObReferenceObjectSafe(OtherThread)))
{
/* It's a live thread and we referenced it, unlock process */
ExReleasePushLockExclusive(&CurrentProcess->ProcessLock);
KeLeaveCriticalRegion();
/* Wait on the thread */
KeWaitForSingleObject(OtherThread,
Executive,
KernelMode,
FALSE,
NULL);
/* Check if we had a previous thread to dereference */
if (PreviousThread) ObDereferenceObject(PreviousThread);
/* Remember the thread and re-lock the process */
PreviousThread = OtherThread;
KeEnterCriticalRegion();
ExAcquirePushLockExclusive(&CurrentProcess->ProcessLock);
}
/* Go to the next thread */
CurrentEntry = CurrentEntry->Flink;
}
}
else if (ExitStatus != STATUS_THREAD_IS_TERMINATING)
{
/* Write down the exit status of the last thread to get killed */
CurrentProcess->LastThreadExitStatus = ExitStatus;
}
/* Unlock the Process */
ExReleasePushLockExclusive(&CurrentProcess->ProcessLock);
KeLeaveCriticalRegion();
/* Check if we had a previous thread to dereference */
if (PreviousThread) ObDereferenceObject(PreviousThread);
/* Check if the process has a debug port and if this is a user thread */
if ((CurrentProcess->DebugPort) && !(Thread->SystemThread))
{
/* Notify the Debug API. */
Last ? DbgkExitProcess(CurrentProcess->ExitStatus) :
DbgkExitThread(ExitStatus);
}
/* Check if this is a Critical Thread */
if ((KdDebuggerEnabled) && (Thread->BreakOnTermination))
{
/* Break to debugger */
PspCatchCriticalBreak("Critical thread 0x%p (in %s) exited\n",
Thread,
CurrentProcess->ImageFileName);
}
/* Check if it's the last thread and this is a Critical Process */
if ((Last) && (CurrentProcess->BreakOnTermination))
{
/* Check if a debugger is here to handle this */
if (KdDebuggerEnabled)
{
/* Break to debugger */
PspCatchCriticalBreak("Critical process 0x%p (in %s) exited\n",
CurrentProcess,
CurrentProcess->ImageFileName);
}
else
{
/* Bugcheck, we can't allow this */
KeBugCheckEx(CRITICAL_PROCESS_DIED,
(ULONG_PTR)CurrentProcess,
0,
0,
0);
}
}
/* Sanity check */
ASSERT(Thread->Tcb.CombinedApcDisable == 0);
/* Process the Termination Ports */
TerminationPort = Thread->TerminationPort;
if (TerminationPort)
{
/* Setup the message header */
TerminationMsg.h.u2.ZeroInit = 0;
TerminationMsg.h.u2.s2.Type = LPC_CLIENT_DIED;
TerminationMsg.h.u1.s1.TotalLength = sizeof(TerminationMsg);
TerminationMsg.h.u1.s1.DataLength = sizeof(TerminationMsg) -
sizeof(PORT_MESSAGE);
/* Loop each port */
do
{
/* Save the Create Time */
TerminationMsg.CreateTime = Thread->CreateTime;
/* Loop trying to send message */
while (TRUE)
{
/* Send the LPC Message */
Status = LpcRequestPort(TerminationPort->Port,
&TerminationMsg.h);
if ((Status == STATUS_NO_MEMORY) ||
(Status == STATUS_INSUFFICIENT_RESOURCES))
{
/* Wait a bit and try again */
KeDelayExecutionThread(KernelMode, FALSE, &ShortTime);
continue;
}
break;
}
/* Dereference this LPC Port */
ObDereferenceObject(TerminationPort->Port);
/* Move to the next one */
NextPort = TerminationPort->Next;
/* Free the Termination Port Object */
ExFreePoolWithTag(TerminationPort, '=TsP');
/* Keep looping as long as there is a port */
TerminationPort = NextPort;
} while (TerminationPort);
}
else if (((ExitStatus == STATUS_THREAD_IS_TERMINATING) &&
(Thread->DeadThread)) ||
!(Thread->DeadThread))
{
/*
* This case is special and deserves some extra comments. What
* basically happens here is that this thread doesn't have a termination
* port, which means that it died before being fully created. Since we
* still have to notify an LPC Server, we'll use the exception port,
* which we know exists. However, we need to know how far the thread
* actually got created. We have three possibilities:
*
* - NtCreateThread returned an error really early: DeadThread is set.
* - NtCreateThread managed to create the thread: DeadThread is off.
* - NtCreateThread was creating the thread (with DeadThread set,
* but the thread got killed prematurely: STATUS_THREAD_IS_TERMINATING
* is our exit code.)
*
* For the 2 & 3rd scenarios, the thread has been created far enough to
* warrant notification to the LPC Server.
*/
/* Setup the message header */
TerminationMsg.h.u2.ZeroInit = 0;
TerminationMsg.h.u2.s2.Type = LPC_CLIENT_DIED;
TerminationMsg.h.u1.s1.TotalLength = sizeof(TerminationMsg);
TerminationMsg.h.u1.s1.DataLength = sizeof(TerminationMsg) -
sizeof(PORT_MESSAGE);
/* Make sure the process has an exception port */
if (CurrentProcess->ExceptionPort)
{
/* Save the Create Time */
TerminationMsg.CreateTime = Thread->CreateTime;
/* Loop trying to send message */
while (TRUE)
{
/* Send the LPC Message */
Status = LpcRequestPort(CurrentProcess->ExceptionPort,
&TerminationMsg.h);
if ((Status == STATUS_NO_MEMORY) ||
(Status == STATUS_INSUFFICIENT_RESOURCES))
{
/* Wait a bit and try again */
KeDelayExecutionThread(KernelMode, FALSE, &ShortTime);
continue;
}
break;
}
}
}
/* Rundown Win32 Thread if there is one */
if (Thread->Tcb.Win32Thread) PspW32ThreadCallout(Thread,
PsW32ThreadCalloutExit);
/* If we are the last thread and have a W32 Process */
if ((Last) && (CurrentProcess->Win32Process))
{
/* Run it down too */
PspW32ProcessCallout(CurrentProcess, FALSE);
}
/* Make sure Stack Swap is enabled */
if (!Thread->Tcb.EnableStackSwap)
{
/* Stack swap really shouldn't be disabled during exit! */
KeBugCheckEx(KERNEL_STACK_LOCKED_AT_EXIT, 0, 0, 0, 0);
}
/* Cancel I/O for the thread. */
IoCancelThreadIo(Thread);
/* Rundown Timers */
ExTimerRundown();
/* FIXME: Rundown Registry Notifications (NtChangeNotify)
CmNotifyRunDown(Thread); */
/* Rundown Mutexes */
KeRundownThread();
/* Check if we have a TEB */
Teb = Thread->Tcb.Teb;
if (Teb)
{
/* Check if the thread is still alive */
if (!Thread->DeadThread)
{
/* Check if we need to free its stack */
if (Teb->FreeStackOnTermination)
{
/* Set the TEB's Deallocation Stack as the Base Address */
Dummy = 0;
DeallocationStack = Teb->DeallocationStack;
/* Free the Thread's Stack */
ZwFreeVirtualMemory(NtCurrentProcess(),
&DeallocationStack,
&Dummy,
MEM_RELEASE);
}
/* Free the debug handle */
if (Teb->DbgSsReserved[1]) ObCloseHandle(Teb->DbgSsReserved[1],
UserMode);
}
/* Decommit the TEB */
MmDeleteTeb(CurrentProcess, Teb);
Thread->Tcb.Teb = NULL;
}
/* Free LPC Data */
LpcExitThread(Thread);
/* Save the exit status and exit time */
Thread->ExitStatus = ExitStatus;
KeQuerySystemTime(&Thread->ExitTime);
/* Sanity check */
ASSERT(Thread->Tcb.CombinedApcDisable == 0);
/* Check if this is the final thread or not */
if (Last)
{
/* Set the process exit time */
CurrentProcess->ExitTime = Thread->ExitTime;
/* Exit the process */
PspExitProcess(TRUE, CurrentProcess);
/* Get the process token and check if we need to audit */
PrimaryToken = PsReferencePrimaryToken(CurrentProcess);
if (SeDetailedAuditingWithToken(PrimaryToken))
{
/* Audit the exit */
SeAuditProcessExit(CurrentProcess);
}
/* Dereference the process token */
ObFastDereferenceObject(&CurrentProcess->Token, PrimaryToken);
/* Check if this is a VDM Process and rundown the VDM DPCs if so */
if (CurrentProcess->VdmObjects) { /* VdmRundownDpcs(CurrentProcess); */ }
/* Kill the process in the Object Manager */
ObKillProcess(CurrentProcess);
/* Check if we have a section object */
if (CurrentProcess->SectionObject)
{
/* Dereference and clear the Section Object */
ObDereferenceObject(CurrentProcess->SectionObject);
CurrentProcess->SectionObject = NULL;
}
/* Check if the process is part of a job */
if (CurrentProcess->Job)
{
/* Remove the process from the job */
PspExitProcessFromJob(CurrentProcess->Job, CurrentProcess);
}
}
/* Disable APCs */
KeEnterCriticalRegion();
/* Disable APC queueing, force a resumption */
Thread->Tcb.ApcQueueable = FALSE;
KeForceResumeThread(&Thread->Tcb);
/* Re-enable APCs */
KeLeaveCriticalRegion();
/* Flush the User APCs */
FirstEntry = KeFlushQueueApc(&Thread->Tcb, UserMode);
if (FirstEntry)
{
/* Start with the first entry */
CurrentEntry = FirstEntry;
do
{
/* Get the APC */
Apc = CONTAINING_RECORD(CurrentEntry, KAPC, ApcListEntry);
/* Move to the next one */
CurrentEntry = CurrentEntry->Flink;
/* Rundown the APC or de-allocate it */
if (Apc->RundownRoutine)
{
/* Call its own routine */
Apc->RundownRoutine(Apc);
}
else
{
/* Do it ourselves */
ExFreePool(Apc);
}
}
while (CurrentEntry != FirstEntry);
}
/* Clean address space if this was the last thread */
if (Last) MmCleanProcessAddressSpace(CurrentProcess);
/* Call the Lego routine */
if (Thread->Tcb.LegoData) PspRunLegoRoutine(&Thread->Tcb);
/* Flush the APC queue, which should be empty */
FirstEntry = KeFlushQueueApc(&Thread->Tcb, KernelMode);
if ((FirstEntry) || (Thread->Tcb.CombinedApcDisable != 0))
{
/* Bugcheck time */
KeBugCheckEx(KERNEL_APC_PENDING_DURING_EXIT,
(ULONG_PTR)FirstEntry,
Thread->Tcb.CombinedApcDisable,
KeGetCurrentIrql(),
0);
}
/* Signal the process if this was the last thread */
if (Last) KeSetProcess(&CurrentProcess->Pcb, 0, FALSE);
/* Terminate the Thread from the Scheduler */
KeTerminateThread(0);
}
VOID
NTAPI
PsExitSpecialApc(IN PKAPC Apc,
IN OUT PKNORMAL_ROUTINE* NormalRoutine,
IN OUT PVOID* NormalContext,
IN OUT PVOID* SystemArgument1,
IN OUT PVOID* SystemArgument2)
{
NTSTATUS Status;
PAGED_CODE();
PSTRACE(PS_KILL_DEBUG,
"Apc: %p SystemArgument2: %p\n", Apc, SystemArgument2);
/* Don't do anything unless we are in User-Mode */
if (Apc->SystemArgument2)
{
/* Free the APC */
Status = PtrToUlong(Apc->NormalContext);
PspExitApcRundown(Apc);
/* Terminate the Thread */
PspExitThread(Status);
}
}
VOID
NTAPI
PspExitNormalApc(IN PVOID NormalContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2)
{
PKAPC Apc = (PKAPC)SystemArgument1;
PETHREAD Thread = PsGetCurrentThread();
PAGED_CODE();
PSTRACE(PS_KILL_DEBUG, "SystemArgument2: %p\n", SystemArgument2);
/* This should never happen */
ASSERT(!(((ULONG_PTR)SystemArgument2) & 1));
/* If we're here, this is not a System Thread, so kill it from User-Mode */
KeInitializeApc(Apc,
&Thread->Tcb,
OriginalApcEnvironment,
PsExitSpecialApc,
PspExitApcRundown,
PspExitNormalApc,
UserMode,
NormalContext);
/* Now insert the APC with the User-Mode Flag */
if (!(KeInsertQueueApc(Apc,
Apc,
(PVOID)((ULONG_PTR)SystemArgument2 | 1),
2)))
{
/* Failed to insert, free the APC */
PspExitApcRundown(Apc);
}
/* Set the APC Pending flag */
Thread->Tcb.ApcState.UserApcPending = TRUE;
}
/*
* See "Windows Internals" - Chapter 13, Page 49
*/
NTSTATUS
NTAPI
PspTerminateThreadByPointer(IN PETHREAD Thread,
IN NTSTATUS ExitStatus,
IN BOOLEAN bSelf)
{
PKAPC Apc;
NTSTATUS Status = STATUS_SUCCESS;
ULONG Flags;
PAGED_CODE();
PSTRACE(PS_KILL_DEBUG, "Thread: %p ExitStatus: %d\n", Thread, ExitStatus);
PSREFTRACE(Thread);
/* Check if this is a Critical Thread, and Bugcheck */
if (Thread->BreakOnTermination)
{
/* Break to debugger */
PspCatchCriticalBreak("Terminating critical thread 0x%p (%s)\n",
Thread,
Thread->ThreadsProcess->ImageFileName);
}
/* Check if we are already inside the thread */
if ((bSelf) || (PsGetCurrentThread() == Thread))
{
/* This should only happen at passive */
ASSERT_IRQL_EQUAL(PASSIVE_LEVEL);
/* Mark it as terminated */
PspSetCrossThreadFlag(Thread, CT_TERMINATED_BIT);
/* Directly terminate the thread */
PspExitThread(ExitStatus);
}
/* This shouldn't be a system thread */
if (Thread->SystemThread) return STATUS_ACCESS_DENIED;
/* Allocate the APC */
Apc = ExAllocatePoolWithTag(NonPagedPool, sizeof(KAPC), TAG_TERMINATE_APC);
if (!Apc) return STATUS_INSUFFICIENT_RESOURCES;
/* Set the Terminated Flag */
Flags = Thread->CrossThreadFlags | CT_TERMINATED_BIT;
/* Set it, and check if it was already set while we were running */
if (!(InterlockedExchange((PLONG)&Thread->CrossThreadFlags, Flags) &
CT_TERMINATED_BIT))
{
/* Initialize a Kernel Mode APC to Kill the Thread */
KeInitializeApc(Apc,
&Thread->Tcb,
OriginalApcEnvironment,
PsExitSpecialApc,
PspExitApcRundown,
PspExitNormalApc,
KernelMode,
UlongToPtr(ExitStatus));
/* Insert it into the APC Queue */
if (!KeInsertQueueApc(Apc, Apc, NULL, 2))
{
/* The APC was already in the queue, fail */
Status = STATUS_UNSUCCESSFUL;
}
else
{
/* Forcefully resume the thread and return */
KeForceResumeThread(&Thread->Tcb);
return Status;
}
}
/* We failed, free the APC */
ExFreePoolWithTag(Apc, TAG_TERMINATE_APC);
/* Return Status */
return Status;
}
BOOLEAN
NTAPI
PspIsProcessExiting(IN PEPROCESS Process)
{
return Process->Flags & PSF_PROCESS_EXITING_BIT;
}
VOID
NTAPI
PspExitProcess(IN BOOLEAN LastThread,
IN PEPROCESS Process)
{
ULONG Actual;
PAGED_CODE();
PSTRACE(PS_KILL_DEBUG,
"LastThread: %u Process: %p\n", LastThread, Process);
PSREFTRACE(Process);
/* Set Process Exit flag */
InterlockedOr((PLONG)&Process->Flags, PSF_PROCESS_EXITING_BIT);
/* Check if we are the last thread */
if (LastThread)
{
/* Notify the WMI Process Callback */
//WmiTraceProcess(Process, FALSE);
/* Run the Notification Routines */
PspRunCreateProcessNotifyRoutines(Process, FALSE);
}
/* Cleanup the power state */
PopCleanupPowerState((PPOWER_STATE)&Process->Pcb.PowerState);
/* Clear the security port */
if (!Process->SecurityPort)
{
/* So we don't double-dereference */
Process->SecurityPort = (PVOID)1;
}
else if (Process->SecurityPort != (PVOID)1)
{
/* Dereference it */
ObDereferenceObject(Process->SecurityPort);
Process->SecurityPort = (PVOID)1;
}
/* Check if we are the last thread */
if (LastThread)
{
/* Check if we have to set the Timer Resolution */
if (Process->SetTimerResolution)
{
/* Set it to default */
ZwSetTimerResolution(KeMaximumIncrement, 0, &Actual);
}
/* Check if we are part of a Job that has a completion port */
if ((Process->Job) && (Process->Job->CompletionPort))
{
/* FIXME: Check job status code and do I/O completion if needed */
}
/* FIXME: Notify the Prefetcher */
}
else
{
/* Clear process' address space here */
MmCleanProcessAddressSpace(Process);
}
}
/* PUBLIC FUNCTIONS **********************************************************/
/*
* @implemented
*/
NTSTATUS
NTAPI
PsTerminateSystemThread(IN NTSTATUS ExitStatus)
{
PETHREAD Thread = PsGetCurrentThread();
/* Make sure this is a system thread */
if (!Thread->SystemThread) return STATUS_INVALID_PARAMETER;
/* Terminate it for real */
return PspTerminateThreadByPointer(Thread, ExitStatus, TRUE);
}
/*
* @implemented
*/
NTSTATUS
NTAPI
NtTerminateProcess(IN HANDLE ProcessHandle OPTIONAL,
IN NTSTATUS ExitStatus)
{
NTSTATUS Status;
PEPROCESS Process, CurrentProcess = PsGetCurrentProcess();
PETHREAD Thread, CurrentThread = PsGetCurrentThread();
BOOLEAN KillByHandle;
PAGED_CODE();
PSTRACE(PS_KILL_DEBUG,
"ProcessHandle: %p ExitStatus: %d\n", ProcessHandle, ExitStatus);
/* Were we passed a process handle? */
if (ProcessHandle)
{
/* Yes we were, use it */
KillByHandle = TRUE;
}
else
{
/* We weren't... we assume this is suicide */
KillByHandle = FALSE;
ProcessHandle = NtCurrentProcess();
}
/* Get the Process Object */
Status = ObReferenceObjectByHandle(ProcessHandle,
PROCESS_TERMINATE,
PsProcessType,
KeGetPreviousMode(),
(PVOID*)&Process,
NULL);
if (!NT_SUCCESS(Status)) return(Status);
/* Check if this is a Critical Process, and Bugcheck */
if (Process->BreakOnTermination)
{
/* Break to debugger */
PspCatchCriticalBreak("Terminating critical process 0x%p (%s)\n",
Process,
Process->ImageFileName);
}
/* Lock the Process */
if (!ExAcquireRundownProtection(&Process->RundownProtect))
{
/* Failed to lock, fail */
ObDereferenceObject(Process);
return STATUS_PROCESS_IS_TERMINATING;
}
/* Set the delete flag, unless the process is comitting suicide */
if (KillByHandle) PspSetProcessFlag(Process, PSF_PROCESS_DELETE_BIT);
/* Get the first thread */
Status = STATUS_NOTHING_TO_TERMINATE;
Thread = PsGetNextProcessThread(Process, NULL);
if (Thread)
{
/* We know we have at least a thread */
Status = STATUS_SUCCESS;
/* Loop and kill the others */
do
{
/* Ensure it's not ours*/
if (Thread != CurrentThread)
{
/* Kill it */
PspTerminateThreadByPointer(Thread, ExitStatus, FALSE);
}
/* Move to the next thread */
Thread = PsGetNextProcessThread(Process, Thread);
} while (Thread);
}
/* Unlock the process */
ExReleaseRundownProtection(&Process->RundownProtect);
/* Check if we are killing ourselves */
if (Process == CurrentProcess)
{
/* Also make sure the caller gave us our handle */
if (KillByHandle)
{
/* Dereference the process */
ObDereferenceObject(Process);
/* Terminate ourselves */
PspTerminateThreadByPointer(CurrentThread, ExitStatus, TRUE);
}
}
else if (ExitStatus == DBG_TERMINATE_PROCESS)
{
/* Disable debugging on this process */
DbgkClearProcessDebugObject(Process, NULL);
}
/* Check if there was nothing to terminate, or if we have a Debug Port */
if ((Status == STATUS_NOTHING_TO_TERMINATE) ||
((Process->DebugPort) && (KillByHandle)))
{
/* Clear the handle table */
ObClearProcessHandleTable(Process);
/* Return status now */
Status = STATUS_SUCCESS;
}
/* Decrease the reference count we added */
ObDereferenceObject(Process);
/* Return status */
return Status;
}
NTSTATUS
NTAPI
NtTerminateThread(IN HANDLE ThreadHandle,
IN NTSTATUS ExitStatus)
{
PETHREAD Thread;
PETHREAD CurrentThread = PsGetCurrentThread();
NTSTATUS Status;
PAGED_CODE();
PSTRACE(PS_KILL_DEBUG,
"ThreadHandle: %p ExitStatus: %d\n", ThreadHandle, ExitStatus);
/* Handle the special NULL case */
if (!ThreadHandle)
{
/* Check if we're the only thread left */
if (PsGetCurrentProcess()->ActiveThreads == 1)
{
/* This is invalid */
return STATUS_CANT_TERMINATE_SELF;
}
/* Terminate us directly */
goto TerminateSelf;
}
else if (ThreadHandle == NtCurrentThread())
{
TerminateSelf:
/* Terminate this thread */
return PspTerminateThreadByPointer(CurrentThread,
ExitStatus,
TRUE);
}
/* We are terminating another thread, get the Thread Object */
Status = ObReferenceObjectByHandle(ThreadHandle,
THREAD_TERMINATE,
PsThreadType,
KeGetPreviousMode(),
(PVOID*)&Thread,
NULL);
if (!NT_SUCCESS(Status)) return Status;
/* Check to see if we're running in the same thread */
if (Thread != CurrentThread)
{
/* Terminate it */
Status = PspTerminateThreadByPointer(Thread, ExitStatus, FALSE);
/* Dereference the Thread and return */
ObDereferenceObject(Thread);
}
else
{
/* Dereference the thread and terminate ourselves */
ObDereferenceObject(Thread);
goto TerminateSelf;
}
/* Return status */
return Status;
}
NTSTATUS
NTAPI
NtRegisterThreadTerminatePort(IN HANDLE PortHandle)
{
NTSTATUS Status;
PTERMINATION_PORT TerminationPort;
PVOID TerminationLpcPort;
PETHREAD Thread;
PAGED_CODE();
PSTRACE(PS_KILL_DEBUG, "PortHandle: %p\n", PortHandle);
/* Get the Port */
Status = ObReferenceObjectByHandle(PortHandle,
PORT_ALL_ACCESS,
LpcPortObjectType,
KeGetPreviousMode(),
&TerminationLpcPort,
NULL);
if (!NT_SUCCESS(Status)) return(Status);
/* Allocate the Port and make sure it suceeded */
TerminationPort = ExAllocatePoolWithTag(NonPagedPool,
sizeof(TERMINATION_PORT),
'=TsP');
if(TerminationPort)
{
/* Associate the Port */
Thread = PsGetCurrentThread();
TerminationPort->Port = TerminationLpcPort;
TerminationPort->Next = Thread->TerminationPort;
Thread->TerminationPort = TerminationPort;
/* Return success */
return STATUS_SUCCESS;
}
/* Dereference and Fail */
ObDereferenceObject(TerminationLpcPort);
return STATUS_INSUFFICIENT_RESOURCES;
}