Raw
1 #ifndef EXIT_PROCESS_H
2 #define EXIT_PROCESS_H
3
4 /*
5 * This file contains functions to terminate a Win32 process, as gently as
6 * possible.
7 *
8 * At first, we will attempt to inject a thread that calls ExitProcess(). If
9 * that fails, we will fall back to terminating the entire process tree.
10 *
11 * For simplicity, these functions are marked as file-local.
12 */
13
14 #include <tlhelp32.h>
15
16 /*
17 * Terminates the process corresponding to the process ID and all of its
18 * directly and indirectly spawned subprocesses.
19 *
20 * This way of terminating the processes is not gentle: the processes get
21 * no chance of cleaning up after themselves (closing file handles, removing
22 * .lock files, terminating spawned processes (if any), etc).
23 */
24 static int terminate_process_tree(HANDLE main_process, int exit_status)
25 {
26 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
27 PROCESSENTRY32 entry;
28 DWORD pids[16384];
29 int max_len = sizeof(pids) / sizeof(*pids), i, len, ret = 0;
30 pid_t pid = GetProcessId(main_process);
31
32 pids[0] = (DWORD)pid;
33 len = 1;
34
35 /*
36 * Even if Process32First()/Process32Next() seem to traverse the
37 * processes in topological order (i.e. parent processes before
38 * child processes), there is nothing in the Win32 API documentation
39 * suggesting that this is guaranteed.
40 *
41 * Therefore, run through them at least twice and stop when no more
42 * process IDs were added to the list.
43 */
44 for (;;) {
45 int orig_len = len;
46
47 memset(&entry, 0, sizeof(entry));
48 entry.dwSize = sizeof(entry);
49
50 if (!Process32First(snapshot, &entry))
51 break;
52
53 do {
54 for (i = len - 1; i >= 0; i--) {
55 if (pids[i] == entry.th32ProcessID)
56 break;
57 if (pids[i] == entry.th32ParentProcessID)
58 pids[len++] = entry.th32ProcessID;
59 }
60 } while (len < max_len && Process32Next(snapshot, &entry));
61
62 if (orig_len == len || len >= max_len)
63 break;
64 }
65
66 for (i = len - 1; i > 0; i--) {
67 HANDLE process = OpenProcess(PROCESS_TERMINATE, FALSE, pids[i]);
68
69 if (process) {
70 if (!TerminateProcess(process, exit_status))
71 ret = -1;
72 CloseHandle(process);
73 }
74 }
75 if (!TerminateProcess(main_process, exit_status))
76 ret = -1;
77 CloseHandle(main_process);
78
79 return ret;
80 }
81
82 /**
83 * Determine whether a process runs in the same architecture as the current
84 * one. That test is required before we assume that GetProcAddress() returns
85 * a valid address *for the target process*.
86 */
87 static inline int process_architecture_matches_current(HANDLE process)
88 {
89 static BOOL current_is_wow = -1;
90 BOOL is_wow;
91
92 if (current_is_wow == -1 &&
93 !IsWow64Process (GetCurrentProcess(), &current_is_wow))
94 current_is_wow = -2;
95 if (current_is_wow == -2)
96 return 0; /* could not determine current process' WoW-ness */
97 if (!IsWow64Process (process, &is_wow))
98 return 0; /* cannot determine */
99 return is_wow == current_is_wow;
100 }
101
102 /**
103 * Inject a thread into the given process that runs ExitProcess().
104 *
105 * Note: as kernel32.dll is loaded before any process, the other process and
106 * this process will have ExitProcess() at the same address.
107 *
108 * This function expects the process handle to have the access rights for
109 * CreateRemoteThread(): PROCESS_CREATE_THREAD, PROCESS_QUERY_INFORMATION,
110 * PROCESS_VM_OPERATION, PROCESS_VM_WRITE, and PROCESS_VM_READ.
111 *
112 * The idea comes from the Dr Dobb's article "A Safer Alternative to
113 * TerminateProcess()" by Andrew Tucker (July 1, 1999),
114 * http://www.drdobbs.com/a-safer-alternative-to-terminateprocess/184416547
115 *
116 * If this method fails, we fall back to running terminate_process_tree().
117 */
118 static int exit_process(HANDLE process, int exit_code)
119 {
120 DWORD code;
121
122 if (GetExitCodeProcess(process, &code) && code == STILL_ACTIVE) {
123 static int initialized;
124 static LPTHREAD_START_ROUTINE exit_process_address;
125 PVOID arg = (PVOID)(intptr_t)exit_code;
126 DWORD thread_id;
127 HANDLE thread = NULL;
128
129 if (!initialized) {
130 HINSTANCE kernel32 = GetModuleHandleA("kernel32");
131 if (!kernel32)
132 die("BUG: cannot find kernel32");
133 exit_process_address =
134 (LPTHREAD_START_ROUTINE)(void (*)(void))
135 GetProcAddress(kernel32, "ExitProcess");
136 initialized = 1;
137 }
138 if (!exit_process_address ||
139 !process_architecture_matches_current(process))
140 return terminate_process_tree(process, exit_code);
141
142 thread = CreateRemoteThread(process, NULL, 0,
143 exit_process_address,
144 arg, 0, &thread_id);
145 if (thread) {
146 CloseHandle(thread);
147 /*
148 * If the process survives for 10 seconds (a completely
149 * arbitrary value picked from thin air), fall back to
150 * killing the process tree via TerminateProcess().
151 */
152 if (WaitForSingleObject(process, 10000) ==
153 WAIT_OBJECT_0) {
154 CloseHandle(process);
155 return 0;
156 }
157 }
158
159 return terminate_process_tree(process, exit_code);
160 }
161
162 CloseHandle(process);
163 return 0;
164 }
165
166 #endif