1 // Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
2 //
3 // This program is free software; you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License, version 2.0,
5 // as published by the Free Software Foundation.
6 //
7 // This program is also distributed with certain software (including
8 // but not limited to OpenSSL) that is licensed under separate terms,
9 // as designated in a particular file or component or in included license
10 // documentation.  The authors of MySQL hereby grant you an additional
11 // permission to link the program and your derivative works with the
12 // separately licensed software that they have included with MySQL.
13 //
14 // This program is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 // GNU General Public License, version 2.0, for more details.
18 //
19 // You should have received a copy of the GNU General Public License
20 // along with this program; if not, write to the Free Software
21 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
22 
23 /// @file safe_process_win.cc
24 ///
25 /// Utility program that encapsulates process creation, monitoring
26 /// and bulletproof process cleanup.
27 ///
28 /// Usage:
29 ///   safe_process [options to safe_process] -- progname arg1 ... argn
30 ///
31 /// To safeguard mysqld you would invoke safe_process with a few options
32 /// for safe_process itself followed by a double dash to indicate start
33 /// of the command line for the program you really want to start.
34 ///
35 /// $> safe_process --output=output.log -- mysqld --datadir=var/data1 ...
36 ///
37 /// This would redirect output to output.log and then start mysqld,
38 /// once it has done that it will continue to monitor the child as well
39 /// as the parent.
40 ///
41 /// The safe_process then checks the follwing things:
42 /// 1. Child exits, propagate the childs return code to the parent
43 ///    by exiting with the same return code as the child.
44 ///
45 /// 2. Parent dies, immediately kill the child and exit, thus the
46 ///    parent does not need to properly cleanup any child, it is handled
47 ///    automatically.
48 ///
49 /// 3. Signal's recieced by the process will trigger same action as 2)
50 ///
51 /// 4. The named event "safe_process[pid]" can be signaled and will
52 ///    trigger same action as 2)
53 ///
54 /// WARNING
55 ///   Be careful when using ProcessExplorer, since it will open a handle
56 ///   to each process(and maybe also the Job), the process spawned by
57 ///   safe_process will not be closed off when safe_process is killed.
58 
59 #include <signal.h>
60 #include <windows.h>
61 
62 // Needs to be included after <windows.h>.
63 #include <tlhelp32.h>
64 
65 #include <cstdio>
66 #include <cstring>
67 #include <string>
68 
69 enum { PARENT, CHILD, EVENT, NUM_HANDLES };
70 HANDLE shutdown_event;
71 
72 static char safe_process_name[32];
73 static int verbose = 0;
74 
message(const char * fmt,...)75 static void message(const char *fmt, ...) {
76   if (!verbose) return;
77   va_list args;
78   std::fprintf(stderr, "%s: ", safe_process_name);
79   va_start(args, fmt);
80   std::vfprintf(stderr, fmt, args);
81   std::fprintf(stderr, "\n");
82   va_end(args);
83   fflush(stderr);
84 }
85 
die(const char * fmt,...)86 static void die(const char *fmt, ...) {
87   DWORD last_err = GetLastError();
88 
89   va_list args;
90   std::fprintf(stderr, "%s: FATAL ERROR, ", safe_process_name);
91   va_start(args, fmt);
92   std::vfprintf(stderr, fmt, args);
93   std::fprintf(stderr, "\n");
94   va_end(args);
95 
96   if (last_err) {
97     char *message_text;
98     if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
99                           FORMAT_MESSAGE_ALLOCATE_BUFFER |
100                           FORMAT_MESSAGE_IGNORE_INSERTS,
101                       NULL, last_err, 0, (LPSTR)&message_text, 0, NULL)) {
102       std::fprintf(stderr, "error: %d, %s\n", last_err, message_text);
103       LocalFree(message_text);
104     } else {
105       // FormatMessage failed, print error code only
106       std::fprintf(stderr, "error:%d\n", last_err);
107     }
108   }
109 
110   fflush(stderr);
111   std::exit(1);
112 }
113 
get_parent_pid(DWORD pid)114 DWORD get_parent_pid(DWORD pid) {
115   PROCESSENTRY32 pe32;
116   pe32.dwSize = sizeof(PROCESSENTRY32);
117 
118   HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
119   if (snapshot == INVALID_HANDLE_VALUE) die("CreateToolhelp32Snapshot failed");
120 
121   if (!Process32First(snapshot, &pe32)) {
122     CloseHandle(snapshot);
123     die("Process32First failed");
124   }
125 
126   DWORD parent_pid = -1;
127   do {
128     if (pe32.th32ProcessID == pid) parent_pid = pe32.th32ParentProcessID;
129   } while (Process32Next(snapshot, &pe32));
130 
131   CloseHandle(snapshot);
132 
133   if (parent_pid == -1) die("Could not find parent pid");
134 
135   return parent_pid;
136 }
137 
handle_signal(int signal)138 void handle_signal(int signal) {
139   message("Got signal: %d", signal);
140   if (SetEvent(shutdown_event) == 0) {
141     // exit safe_process and (hopefully) kill off the child
142     die("Failed to SetEvent");
143   }
144 }
145 
146 /** Sets the append flag (FILE_APPEND_DATA) so that the handle inherited by the
147  * child process will be in append mode. This is in contrast to the C runtime
148  * flag that is set by f(re)open methods and is not communicated to OS, i.e. is
149  * local to process and is lost in the context of the child process. This method
150  * allows several processes to append a common file concurrently, without the
151  * safe_process child overwriting what others append.
152  * A bug to MSVCRT was submitted:
153  https://developercommunity.visualstudio.com/content/problem/921279/createprocess-does-not-inherit-fappend-flags-set-b.html
154  */
fix_file_append_flag_inheritance(DWORD std_handle)155 void fix_file_append_flag_inheritance(DWORD std_handle) {
156   HANDLE old_handle = GetStdHandle(std_handle);
157   HANDLE new_handle = ReOpenFile(old_handle, FILE_APPEND_DATA,
158                                  FILE_SHARE_WRITE | FILE_SHARE_READ, 0);
159   if (new_handle != INVALID_HANDLE_VALUE) {
160     SetHandleInformation(new_handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
161     SetStdHandle(std_handle, new_handle);
162     CloseHandle(old_handle);
163   }
164 }
165 
main(int argc,const char ** argv)166 int main(int argc, const char **argv) {
167   DWORD pid = GetCurrentProcessId();
168   sprintf(safe_process_name, "safe_process[%d]", pid);
169 
170   // Create an event for the signal handler
171   if ((shutdown_event = CreateEvent(NULL, TRUE, FALSE, safe_process_name)) ==
172       NULL)
173     die("Failed to create shutdown_event");
174 
175   HANDLE wait_handles[NUM_HANDLES] = {0};
176   wait_handles[EVENT] = shutdown_event;
177 
178   signal(SIGINT, handle_signal);
179   signal(SIGBREAK, handle_signal);
180   signal(SIGTERM, handle_signal);
181 
182   message("Started");
183 
184   BOOL nocore = FALSE;
185   DWORD parent_pid = get_parent_pid(pid);
186   char child_args[8192] = {0};
187   std::string exe_name;
188 
189   // Parse arguments
190   for (int i = 1; i < argc; i++) {
191     const char *arg = argv[i];
192     char *to = child_args;
193 
194     if (strcmp(arg, "--") == 0 && strlen(arg) == 2) {
195       // Got the "--" delimiter
196       if (i >= argc) die("No real args -> nothing to do");
197 
198       // Copy the remaining args to child_arg
199       for (int j = i + 1; j < argc; j++) {
200         arg = argv[j];
201         if (strchr(arg, ' ') && arg[0] != '\"' && arg[strlen(arg)] != '\"') {
202           // Quote arg that contains spaces and are not quoted already
203           to += std::snprintf(to, child_args + sizeof(child_args) - to,
204                               "\"%s\" ", arg);
205         } else {
206           to += std::snprintf(to, child_args + sizeof(child_args) - to, "%s ",
207                               arg);
208         }
209       }
210 
211       // Check if executable is mysqltest.exe client
212       if (exe_name.compare("mysqltest") == 0) {
213         char safe_process_pid[32];
214 
215         // Pass safeprocess PID to mysqltest which is used to create an event
216         std::sprintf(safe_process_pid, "--safe-process-pid=%d", pid);
217         to += std::snprintf(to, child_args + sizeof(child_args) - to, "%s ",
218                             safe_process_pid);
219       }
220       break;
221     } else {
222       if (strcmp(arg, "--verbose") == 0)
223         verbose++;
224       else if (strncmp(arg, "--parent-pid", 10) == 0) {
225         // Override parent_pid with a value provided by user
226         const char *start;
227         if ((start = strstr(arg, "=")) == NULL)
228           die("Could not find start of option value in '%s'", arg);
229         // Step past '='
230         start++;
231         if ((parent_pid = atoi(start)) == 0)
232           die("Invalid value '%s' passed to --parent-id", start);
233       } else if (strcmp(arg, "--nocore") == 0) {
234         nocore = TRUE;
235       } else if (strncmp(arg, "--env ", 6) == 0) {
236         putenv(strdup(arg + 6));
237       } else if (std::strncmp(arg, "--safe-process-name", 19) == 0) {
238         exe_name = arg + 20;
239       } else
240         die("Unknown option: %s", arg);
241     }
242   }
243 
244   if (*child_args == '\0') die("nothing to do");
245 
246   // Open a handle to the parent process
247   message("parent_pid: %d", parent_pid);
248   if (parent_pid == pid) die("parent_pid is equal to own pid!");
249 
250   if ((wait_handles[PARENT] = OpenProcess(SYNCHRONIZE, FALSE, parent_pid)) ==
251       NULL)
252     die("Failed to open parent process with pid: %d", parent_pid);
253 
254   // Create the child process in a job
255   JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = {0};
256   STARTUPINFO si = {0};
257   si.cb = sizeof(si);
258 
259   // Create the job object to make it possible to kill the process
260   // and all of it's children in one go.
261   HANDLE job_handle;
262   if ((job_handle = CreateJobObject(NULL, NULL)) == NULL)
263     die("CreateJobObject failed");
264 
265 // Make all processes associated with the job terminate when the
266 // last handle to the job is closed.
267 #ifndef JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
268 #define JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE 0x00002000
269 #endif
270 
271   jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
272   if (SetInformationJobObject(job_handle, JobObjectExtendedLimitInformation,
273                               &jeli, sizeof(jeli)) == 0)
274     message("SetInformationJobObject failed, continue anyway...");
275 
276   // Avoid popup box
277   if (nocore)
278     SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX |
279                  SEM_NOOPENFILEERRORBOX);
280 
281   fix_file_append_flag_inheritance(STD_OUTPUT_HANDLE);
282   fix_file_append_flag_inheritance(STD_ERROR_HANDLE);
283 
284 #if 0
285   // Setup stdin, stdout and stderr redirect
286   si.dwFlags= STARTF_USESTDHANDLES;
287   si.hStdInput= GetStdHandle(STD_INPUT_HANDLE);
288   si.hStdOutput= GetStdHandle(STD_OUTPUT_HANDLE);
289   si.hStdError= GetStdHandle(STD_ERROR_HANDLE);
290 #endif
291 
292   // Create the process suspended to make sure it's assigned to the
293   // Job before it creates any process of it's own.
294   //
295   // Allow the new process to break away from any job that this
296   // process is part of so that it can be assigned to the new JobObject
297   // we just created. This is safe since the new JobObject is created with
298   // the JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE flag, making sure it will be
299   // terminated when the last handle to it is closed(which is owned by
300   // this process).
301   //
302   // If breakaway from job fails on some reason, fallback is to create a
303   // new process group. Process groups also allow to kill process and its
304   // descedants, subject to some restrictions (processes have to run within
305   // the same console,and must not ignore CTRL_BREAK)
306   DWORD create_flags[] = {CREATE_BREAKAWAY_FROM_JOB, CREATE_NEW_PROCESS_GROUP,
307                           0};
308 
309   BOOL jobobject_assigned = FALSE;
310   BOOL process_created = FALSE;
311   PROCESS_INFORMATION process_info = {0};
312 
313   for (int i = 0; i < sizeof(create_flags) / sizeof(create_flags[0]); i++) {
314     process_created = CreateProcess(
315         NULL, (LPSTR)child_args, NULL, NULL, TRUE,  // Inherit handles
316         CREATE_SUSPENDED | create_flags[i], NULL, NULL, &si, &process_info);
317     if (process_created) {
318       jobobject_assigned =
319           AssignProcessToJobObject(job_handle, process_info.hProcess);
320       break;
321     }
322   }
323 
324   if (!process_created) {
325     die("CreateProcess failed");
326   }
327 
328   ResumeThread(process_info.hThread);
329   CloseHandle(process_info.hThread);
330   wait_handles[CHILD] = process_info.hProcess;
331 
332   message("Started child %d", process_info.dwProcessId);
333 
334   // Monitor loop
335   DWORD child_exit_code = 1;
336   DWORD wait_res =
337       WaitForMultipleObjects(NUM_HANDLES, wait_handles, FALSE, INFINITE);
338   switch (wait_res) {
339     case WAIT_OBJECT_0 + PARENT:
340       message("Parent exit");
341       break;
342     case WAIT_OBJECT_0 + CHILD:
343       if (GetExitCodeProcess(wait_handles[CHILD], &child_exit_code) == 0)
344         message("Child exit: could not get exit_code");
345       else
346         message("Child exit: exit_code: %d", child_exit_code);
347       break;
348     case WAIT_OBJECT_0 + EVENT:
349       message("Wake up from shutdown_event");
350       break;
351     default:
352       message("Unexpected result %d from WaitForMultipleObjects", wait_res);
353       break;
354   }
355 
356   message("Exiting, child: %d", process_info.dwProcessId);
357 
358   if (TerminateJobObject(job_handle, 201) == 0)
359     message("TerminateJobObject failed");
360 
361   CloseHandle(job_handle);
362   message("Job terminated and closed");
363 
364   if (!jobobject_assigned) {
365     GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, process_info.dwProcessId);
366     TerminateProcess(process_info.hProcess, 202);
367   }
368 
369   if (wait_res != WAIT_OBJECT_0 + CHILD) {
370     // The child has not yet returned, wait for it
371     message("waiting for child to exit");
372 
373     if ((wait_res = WaitForSingleObject(wait_handles[CHILD], INFINITE)) !=
374         WAIT_OBJECT_0) {
375       message("child wait failed: %d", wait_res);
376     } else {
377       message("child wait succeeded");
378     }
379     // Child's exit code should now be 201, no need to get it
380   }
381 
382   message("Closing handles");
383   for (int i = 0; i < NUM_HANDLES; i++) CloseHandle(wait_handles[i]);
384 
385   message("Exiting, exit_code: %d", child_exit_code);
386   std::exit(child_exit_code);
387 }
388