fixed a few race conditions during thread/process termination leading to dead-locks
[reactos.git] / reactos / ntoskrnl / ps / kill.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS kernel
4 * FILE: ntoskrnl/ps/kill.c
5 * PURPOSE: Thread Termination and Reaping
6 *
7 * PROGRAMMERS: Alex Ionescu (alex@relsoft.net)
8 * David Welch (welch@cwcom.net)
9 */
10
11 /* INCLUDES *****************************************************************/
12
13 #include <ntoskrnl.h>
14 #define NDEBUG
15 #include <internal/debug.h>
16
17 /* GLOBALS *******************************************************************/
18
19 #define TAG_TERMINATE_APC TAG('T', 'A', 'P', 'C')
20
21 PETHREAD PspReaperList = NULL;
22 WORK_QUEUE_ITEM PspReaperWorkItem;
23 BOOLEAN PspReaping = FALSE;
24 extern LIST_ENTRY PsActiveProcessHead;
25 extern FAST_MUTEX PspActiveProcessMutex;
26
27 /* FUNCTIONS *****************************************************************/
28
29 STDCALL
30 VOID
31 PspReapRoutine(PVOID Context)
32 {
33 KIRQL OldIrql;
34 PETHREAD Thread, NewThread;
35
36 /* Acquire lock */
37 DPRINT("Evil reaper running!!\n");
38 OldIrql = KeAcquireDispatcherDatabaseLock();
39
40 /* Get the first Thread Entry */
41 Thread = PspReaperList;
42 PspReaperList = NULL;
43 DPRINT("PspReaperList: %x\n", Thread);
44
45 /* Check to see if the list is empty */
46 do {
47
48 /* Unlock the Dispatcher */
49 KeReleaseDispatcherDatabaseLock(OldIrql);
50
51 /* Is there a thread on the list? */
52 while (Thread) {
53
54 /* Get the next Thread */
55 DPRINT("Thread: %x\n", Thread);
56 DPRINT("Thread: %x\n", Thread->ReaperLink);
57 NewThread = Thread->ReaperLink;
58
59 /* Remove reference to current thread */
60 ObDereferenceObject(Thread);
61
62 /* Move to next Thread */
63 Thread = NewThread;
64 }
65
66 /* No more linked threads... Reacquire the Lock */
67 OldIrql = KeAcquireDispatcherDatabaseLock();
68
69 /* Now try to get a new thread from the list */
70 Thread = PspReaperList;
71 PspReaperList = NULL;
72 DPRINT("PspReaperList: %x\n", Thread);
73
74 /* Loop again if there is a new thread */
75 } while (Thread);
76
77 PspReaping = FALSE;
78 DPRINT("Done reaping\n");
79 KeReleaseDispatcherDatabaseLock(OldIrql);
80 }
81
82 VOID
83 STDCALL
84 PspTerminateProcessThreads(PEPROCESS Process,
85 NTSTATUS ExitStatus)
86 {
87 PLIST_ENTRY CurrentEntry;
88 PETHREAD Thread, CurrentThread = PsGetCurrentThread();
89
90 CurrentEntry = Process->ThreadListHead.Flink;
91 while (CurrentEntry != &Process->ThreadListHead) {
92
93 /* Get the Current Thread */
94 Thread = CONTAINING_RECORD(CurrentEntry, ETHREAD, ThreadListEntry);
95
96 /* Move to the Next Thread */
97 CurrentEntry = CurrentEntry->Flink;
98
99 /* Make sure it's not the one we're in */
100 if (Thread != CurrentThread) {
101
102 /* Make sure it didn't already terminate */
103 if (!Thread->HasTerminated) {
104
105 Thread->HasTerminated = TRUE;
106
107 /* Terminate it by APC */
108 PspTerminateThreadByPointer(Thread, ExitStatus);
109 }
110 }
111 }
112 }
113
114 VOID
115 STDCALL
116 PspDeleteProcess(PVOID ObjectBody)
117 {
118 PEPROCESS Process = (PEPROCESS)ObjectBody;
119
120 DPRINT("PiDeleteProcess(ObjectBody %x)\n",Process);
121
122 /* Delete the CID Handle */
123 if(Process->UniqueProcessId != NULL) {
124
125 PsDeleteCidHandle(Process->UniqueProcessId, PsProcessType);
126 }
127
128 /* KDB hook */
129 KDB_DELETEPROCESS_HOOK(Process);
130
131 /* Dereference the Token and release Memory Information */
132 ObDereferenceObject(Process->Token);
133 MmReleaseMmInfo(Process);
134
135 /* Delete the W32PROCESS structure if there's one associated */
136 if(Process->Win32Process != NULL) ExFreePool(Process->Win32Process);
137 }
138
139 VOID
140 STDCALL
141 PspDeleteThread(PVOID ObjectBody)
142 {
143 PETHREAD Thread = (PETHREAD)ObjectBody;
144 PEPROCESS Process = Thread->ThreadsProcess;
145
146 DPRINT("PiDeleteThread(ObjectBody 0x%x, process 0x%x)\n",ObjectBody, Thread->ThreadsProcess);
147
148 /* Deassociate the Process */
149 Thread->ThreadsProcess = NULL;
150
151 /* Delete the CID Handle */
152 if(Thread->Cid.UniqueThread != NULL) {
153
154 PsDeleteCidHandle(Thread->Cid.UniqueThread, PsThreadType);
155 }
156
157 /* Free the W32THREAD structure if present */
158 if(Thread->Tcb.Win32Thread != NULL) ExFreePool (Thread->Tcb.Win32Thread);
159
160 /* Release the Thread */
161 KeReleaseThread(ETHREAD_TO_KTHREAD(Thread));
162
163 /* Dereference the Process */
164 ObDereferenceObject(Process);
165 }
166
167 /*
168 * FUNCTION: Terminates the current thread
169 * See "Windows Internals" - Chapter 13, Page 50-53
170 */
171 VOID
172 STDCALL
173 PspExitThread(NTSTATUS ExitStatus)
174 {
175 PETHREAD CurrentThread;
176 BOOLEAN Last;
177 PEPROCESS CurrentProcess;
178 SIZE_T Length = PAGE_SIZE;
179 PVOID TebBlock;
180 PTERMINATION_PORT TerminationPort;
181
182 DPRINT("PspExitThread(ExitStatus %x), Current: 0x%x\n", ExitStatus, PsGetCurrentThread());
183
184 /* Get the Current Thread and Process */
185 CurrentThread = PsGetCurrentThread();
186 CurrentProcess = CurrentThread->ThreadsProcess;
187
188 /* Set the Exit Status and Exit Time */
189 CurrentThread->ExitStatus = ExitStatus;
190 KeQuerySystemTime(&CurrentThread->ExitTime);
191
192 /* Can't terminate a thread if it attached another process */
193 if (KeIsAttachedProcess()) {
194
195 KEBUGCHECKEX(INVALID_PROCESS_ATTACH_ATTEMPT, (ULONG) CurrentProcess,
196 (ULONG) CurrentThread->Tcb.ApcState.Process,
197 (ULONG) CurrentThread->Tcb.ApcStateIndex,
198 (ULONG) CurrentThread);
199 }
200
201 /* Lower to Passive Level */
202 KeLowerIrql(PASSIVE_LEVEL);
203
204 /* Lock the Process before we modify its thread entries */
205 PsLockProcess(CurrentProcess, FALSE);
206
207 /* wake up the thread so we don't deadlock on PsLockProcess */
208 KeForceResumeThread(&CurrentThread->Tcb);
209
210 /* Run Thread Notify Routines before we desintegrate the thread */
211 PspRunCreateThreadNotifyRoutines(CurrentThread, FALSE);
212
213 /* Remove the thread from the thread list of its process */
214 RemoveEntryList(&CurrentThread->ThreadListEntry);
215 Last = IsListEmpty(&CurrentProcess->ThreadListHead);
216
217 /* Set the last Thread Exit Status */
218 CurrentProcess->LastThreadExitStatus = ExitStatus;
219
220 if (Last) {
221
222 /* Save the Exit Time if not already done by NtTerminateProcess. This
223 happens when the last thread just terminates without explicitly
224 terminating the process. */
225 CurrentProcess->ExitTime = CurrentThread->ExitTime;
226 }
227
228 /* Check if the process has a debug port */
229 if (CurrentProcess->DebugPort) {
230
231 /* Notify the Debug API. TODO */
232 //Last ? DbgkExitProcess(ExitStatus) : DbgkExitThread(ExitStatus);
233 }
234
235 /* Process the Termination Ports */
236 TerminationPort = CurrentThread->TerminationPort;
237 DPRINT("TerminationPort: %p\n", TerminationPort);
238 while (TerminationPort) {
239
240 /* Send the LPC Message */
241 LpcSendTerminationPort(TerminationPort->Port, CurrentThread->CreateTime);
242
243 /* Free the Port */
244 ExFreePool(TerminationPort);
245
246 /* Get the next one */
247 TerminationPort = TerminationPort->Next;
248 DPRINT("TerminationPort: %p\n", TerminationPort);
249 }
250
251 /* Rundown Win32 Structures */
252 PsTerminateWin32Thread(CurrentThread);
253 if (Last) PsTerminateWin32Process(CurrentProcess);
254
255 /* Rundown Registry Notifications. TODO (refers to NtChangeNotify, not Cm callbacks) */
256 //CmNotifyRunDown(CurrentThread);
257
258 /* Free the TEB */
259 if(CurrentThread->Tcb.Teb) {
260
261 DPRINT("Decommit teb at %p\n", CurrentThread->Tcb.Teb);
262 TebBlock = MM_ROUND_DOWN(CurrentThread->Tcb.Teb, MM_VIRTMEM_GRANULARITY);
263
264 ZwFreeVirtualMemory(NtCurrentProcess(),
265 (PVOID *)&CurrentThread->Tcb.Teb,
266 &Length,
267 MEM_DECOMMIT);
268
269 DPRINT("teb %p, TebBlock %p\n", CurrentThread->Tcb.Teb, TebBlock);
270
271 if (TebBlock != CurrentProcess->TebBlock ||
272 CurrentProcess->TebBlock == CurrentProcess->TebLastAllocated) {
273
274 MmLockAddressSpace(&CurrentProcess->AddressSpace);
275 MmReleaseMemoryAreaIfDecommitted(CurrentProcess, &CurrentProcess->AddressSpace, TebBlock);
276 MmUnlockAddressSpace(&CurrentProcess->AddressSpace);
277 }
278
279 CurrentThread->Tcb.Teb = NULL;
280 }
281
282 /* The last Thread shuts down the Process */
283 if (Last) PspExitProcess(CurrentProcess);
284
285 /* Unlock the Process */
286 PsUnlockProcess(CurrentProcess);
287
288 /* Cancel I/O for the thread. */
289 IoCancelThreadIo(CurrentThread);
290
291 /* Rundown Timers */
292 ExTimerRundown();
293 KeCancelTimer(&CurrentThread->Tcb.Timer);
294
295 /* If the Processor Control Block's NpxThread points to the current thread
296 * unset it.
297 */
298 InterlockedCompareExchangePointer(&KeGetCurrentPrcb()->NpxThread,
299 NULL,
300 (PKPROCESS)CurrentThread);
301
302 /* Rundown Mutexes */
303 KeRundownThread();
304
305 /* Terminate the Thread from the Scheduler */
306 KeTerminateThread(0);
307 DPRINT1("Unexpected return, CurrentThread %x PsGetCurrentThread() %x\n", CurrentThread, PsGetCurrentThread());
308 KEBUGCHECK(0);
309 }
310
311 VOID
312 STDCALL
313 PsExitSpecialApc(PKAPC Apc,
314 PKNORMAL_ROUTINE* NormalRoutine,
315 PVOID* NormalContext,
316 PVOID* SystemArgument1,
317 PVOID* SystemArguemnt2)
318 {
319 NTSTATUS ExitStatus = (NTSTATUS)Apc->NormalContext;
320
321 DPRINT("PsExitSpecialApc called: 0x%x (proc: 0x%x)\n", PsGetCurrentThread(), PsGetCurrentProcess());
322
323 /* Free the APC */
324 ExFreePool(Apc);
325
326 /* Terminate the Thread */
327 PspExitThread(ExitStatus);
328
329 /* we should never reach this point! */
330 KEBUGCHECK(0);
331 }
332
333 VOID
334 STDCALL
335 PspExitNormalApc(PVOID NormalContext,
336 PVOID SystemArgument1,
337 PVOID SystemArgument2)
338 {
339 /* Not fully supported yet... must work out some issues that
340 * I don't understand yet -- Alex
341 */
342 DPRINT1("APC2\n");
343 PspExitThread((NTSTATUS)NormalContext);
344
345 /* we should never reach this point! */
346 KEBUGCHECK(0);
347 }
348
349 /*
350 * See "Windows Internals" - Chapter 13, Page 49
351 */
352 VOID
353 STDCALL
354 PspTerminateThreadByPointer(PETHREAD Thread,
355 NTSTATUS ExitStatus)
356 {
357 PKAPC Apc;
358
359 DPRINT("PspTerminatedThreadByPointer(Thread %x, ExitStatus %x)\n",
360 Thread, ExitStatus);
361
362 /* Check if we are already in the right context */
363 if (PsGetCurrentThread() == Thread) {
364
365 /* Directly terminate the thread */
366 PspExitThread(ExitStatus);
367
368 /* we should never reach this point! */
369 KEBUGCHECK(0);
370 }
371
372 /* Allocate the APC */
373 Apc = ExAllocatePoolWithTag(NonPagedPool, sizeof(KAPC), TAG_TERMINATE_APC);
374
375 /* Initialize a Kernel Mode APC to Kill the Thread */
376 KeInitializeApc(Apc,
377 &Thread->Tcb,
378 OriginalApcEnvironment,
379 PsExitSpecialApc,
380 NULL,
381 PspExitNormalApc,
382 KernelMode,
383 (PVOID)ExitStatus);
384
385 /* Insert it into the APC Queue */
386 KeInsertQueueApc(Apc,
387 Apc,
388 NULL,
389 2);
390
391 /* Forcefully resume the thread */
392 KeForceResumeThread(&Thread->Tcb);
393 }
394
395 NTSTATUS
396 STDCALL
397 PspExitProcess(PEPROCESS Process)
398 {
399 DPRINT("PspExitProcess 0x%x\n", Process);
400
401 PspRunCreateProcessNotifyRoutines(Process, FALSE);
402
403 /* Remove it from the Active List */
404 ExAcquireFastMutex(&PspActiveProcessMutex);
405 RemoveEntryList(&Process->ProcessListEntry);
406 ExReleaseFastMutex(&PspActiveProcessMutex);
407
408 /* close all handles associated with our process, this needs to be done
409 when the last thread still runs */
410 ObKillProcess(Process);
411
412 KeSetProcess(&Process->Pcb, IO_NO_INCREMENT);
413
414 return(STATUS_SUCCESS);
415 }
416
417 NTSTATUS
418 STDCALL
419 NtTerminateProcess(IN HANDLE ProcessHandle OPTIONAL,
420 IN NTSTATUS ExitStatus)
421 {
422 NTSTATUS Status;
423 PEPROCESS Process;
424 PETHREAD CurrentThread;
425 BOOLEAN KillByHandle;
426
427 PAGED_CODE();
428
429 DPRINT("NtTerminateProcess(ProcessHandle %x, ExitStatus %x)\n",
430 ProcessHandle, ExitStatus);
431
432 KillByHandle = (ProcessHandle != NULL);
433
434 /* Get the Process Object */
435 Status = ObReferenceObjectByHandle((KillByHandle ? ProcessHandle : NtCurrentProcess()),
436 PROCESS_TERMINATE,
437 PsProcessType,
438 KeGetPreviousMode(),
439 (PVOID*)&Process,
440 NULL);
441 if (!NT_SUCCESS(Status)) {
442
443 DPRINT1("Invalid handle to Process\n");
444 return(Status);
445 }
446
447 CurrentThread = PsGetCurrentThread();
448
449 PsLockProcess(Process, FALSE);
450
451 if(Process->ExitTime.QuadPart != 0)
452 {
453 PsUnlockProcess(Process);
454 return STATUS_PROCESS_IS_TERMINATING;
455 }
456
457 /* Terminate all the Process's Threads */
458 PspTerminateProcessThreads(Process, ExitStatus);
459
460 /* only kill the calling thread if it either passed a process handle or
461 NtCurrentProcess() */
462 if (KillByHandle) {
463
464 /* set the exit time as we're about to release the process lock before
465 we kill ourselves to prevent threads outside of our process trying
466 to kill us */
467 KeQuerySystemTime(&Process->ExitTime);
468
469 /* Only master thread remains... kill it off */
470 if (CurrentThread->ThreadsProcess == Process) {
471
472 /* mark our thread as terminating so attempts to terminate it, when
473 unlocking the process, fail */
474 CurrentThread->HasTerminated = TRUE;
475
476 PsUnlockProcess(Process);
477
478 /* we can safely dereference the process because the current thread
479 holds a reference to it until it gets reaped */
480 ObDereferenceObject(Process);
481
482 /* now the other threads get a chance to terminate, we don't wait but
483 just kill ourselves right now. The process will be run down when the
484 last thread terminates */
485
486 PspExitThread(ExitStatus);
487
488 /* we should never reach this point! */
489 KEBUGCHECK(0);
490 }
491 }
492
493 /* unlock and dereference the process so the threads can kill themselves */
494 PsUnlockProcess(Process);
495 ObDereferenceObject(Process);
496
497 return(STATUS_SUCCESS);
498 }
499
500 NTSTATUS
501 STDCALL
502 NtTerminateThread(IN HANDLE ThreadHandle,
503 IN NTSTATUS ExitStatus)
504 {
505 PETHREAD Thread;
506 NTSTATUS Status;
507
508 PAGED_CODE();
509
510 /* Get the Thread Object */
511 Status = ObReferenceObjectByHandle(ThreadHandle,
512 THREAD_TERMINATE,
513 PsThreadType,
514 KeGetPreviousMode(),
515 (PVOID*)&Thread,
516 NULL);
517 if (!NT_SUCCESS(Status)) {
518
519 DPRINT1("Could not reference thread object\n");
520 return(Status);
521 }
522
523 /* Make sure this is not a system thread */
524 if (PsIsSystemThread(Thread)) {
525
526 DPRINT1("Trying to Terminate a system thread!\n");
527 ObDereferenceObject(Thread);
528 return STATUS_INVALID_PARAMETER;
529 }
530
531 /* Check to see if we're running in the same thread */
532 if (Thread != PsGetCurrentThread()) {
533
534 /* we need to lock the process to make sure it's not already terminating */
535 PsLockProcess(Thread->ThreadsProcess, FALSE);
536
537 /* This isn't our thread, terminate it if not already done */
538 if (!Thread->HasTerminated) {
539
540 Thread->HasTerminated = TRUE;
541
542 /* Terminate it */
543 PspTerminateThreadByPointer(Thread, ExitStatus);
544 }
545
546 PsUnlockProcess(Thread->ThreadsProcess);
547
548 /* Dereference the Thread and return */
549 ObDereferenceObject(Thread);
550
551 } else {
552
553 Thread->HasTerminated = TRUE;
554
555 /* it's safe to dereference thread, there's at least the keep-alive
556 reference which will be removed by the thread reaper causing the
557 thread to be finally destroyed */
558 ObDereferenceObject(Thread);
559
560 /* Terminate him, he's ours */
561 PspExitThread(ExitStatus);
562
563 /* We do never reach this point */
564 KEBUGCHECK(0);
565 }
566
567 return(STATUS_SUCCESS);
568 }
569
570 /*
571 * @implemented
572 */
573 NTSTATUS
574 STDCALL
575 PsTerminateSystemThread(NTSTATUS ExitStatus)
576 {
577 PETHREAD Thread = PsGetCurrentThread();
578
579 /* Make sure this is a system thread */
580 if (!PsIsSystemThread(Thread)) {
581
582 DPRINT1("Trying to Terminate a non-system thread!\n");
583 return STATUS_INVALID_PARAMETER;
584 }
585
586 /* Terminate it for real */
587 PspExitThread(ExitStatus);
588
589 /* we should never reach this point! */
590 KEBUGCHECK(0);
591
592 return(STATUS_SUCCESS);
593 }
594
595 NTSTATUS
596 STDCALL
597 NtRegisterThreadTerminatePort(HANDLE PortHandle)
598 {
599 NTSTATUS Status;
600 PTERMINATION_PORT TerminationPort;
601 PVOID TerminationLpcPort;
602 PETHREAD Thread;
603
604 PAGED_CODE();
605
606 /* Get the Port */
607 Status = ObReferenceObjectByHandle(PortHandle,
608 PORT_ALL_ACCESS,
609 LpcPortObjectType,
610 KeGetPreviousMode(),
611 &TerminationLpcPort,
612 NULL);
613 if (!NT_SUCCESS(Status)) {
614
615 DPRINT1("Failed to reference Port\n");
616 return(Status);
617 }
618
619 /* Allocate the Port and make sure it suceeded */
620 if((TerminationPort = ExAllocatePoolWithTag(NonPagedPool,
621 sizeof(PTERMINATION_PORT),
622 TAG('P', 's', 'T', '=')))) {
623
624 /* Associate the Port */
625 Thread = PsGetCurrentThread();
626 TerminationPort->Port = TerminationLpcPort;
627 DPRINT("TerminationPort: %p\n", TerminationPort);
628 TerminationPort->Next = Thread->TerminationPort;
629 Thread->TerminationPort = TerminationPort;
630 DPRINT("TerminationPort: %p\n", Thread->TerminationPort);
631
632 /* Return success */
633 return(STATUS_SUCCESS);
634
635 } else {
636
637 /* Dereference and Fail */
638 ObDereferenceObject(TerminationPort);
639 return(STATUS_INSUFFICIENT_RESOURCES);
640 }
641 }