1 /* win32-utils.c
2  * Win32 utility routines
3  *
4  * Wireshark - Network traffic analyzer
5  * By Gerald Combs <gerald@wireshark.org>
6  * Copyright 2006 Gerald Combs
7  *
8  * SPDX-License-Identifier: GPL-2.0-or-later
9  */
10 
11 #include <config.h>
12 
13 #include "win32-utils.h"
14 
15 #include <tchar.h>
16 #include <VersionHelpers.h>
17 
18 #include <wsutil/wslog.h>
19 
20 /* Quote the argument element if necessary, so that it will get
21  * reconstructed correctly in the C runtime startup code.  Note that
22  * the unquoting algorithm in the C runtime is really weird, and
23  * rather different than what Unix shells do. See stdargv.c in the C
24  * runtime sources (in the Platform SDK, in src/crt).
25  *
26  * Stolen from GLib's protect_argv(), an internal routine that quotes
27  * string in an argument list so that they arguments will be handled
28  * correctly in the command-line string passed to CreateProcess()
29  * if that string is constructed by gluing those strings together.
30  */
31 gchar *
protect_arg(const gchar * argv)32 protect_arg (const gchar *argv)
33 {
34     gchar *new_arg;
35     const gchar *p = argv;
36     gchar *q;
37     gint len = 0;
38     gboolean need_dblquotes = FALSE;
39 
40     while (*p) {
41         if (*p == ' ' || *p == '\t')
42             need_dblquotes = TRUE;
43         else if (*p == '"')
44             len++;
45         else if (*p == '\\') {
46             const gchar *pp = p;
47 
48             while (*pp && *pp == '\\')
49                 pp++;
50             if (*pp == '"')
51                 len++;
52         }
53         len++;
54         p++;
55     }
56 
57     q = new_arg = g_malloc (len + need_dblquotes*2 + 1);
58     p = argv;
59 
60     if (need_dblquotes)
61         *q++ = '"';
62 
63     while (*p) {
64         if (*p == '"')
65             *q++ = '\\';
66         else if (*p == '\\') {
67             const gchar *pp = p;
68 
69             while (*pp && *pp == '\\')
70                 pp++;
71             if (*pp == '"')
72                 *q++ = '\\';
73         }
74         *q++ = *p;
75         p++;
76     }
77 
78     if (need_dblquotes)
79         *q++ = '"';
80     *q++ = '\0';
81 
82     return new_arg;
83 }
84 
85 /*
86  * Generate a UTF-8 string for a Windows error.
87  */
88 
89 /*
90  * We make the buffer at least this big, under the assumption that doing
91  * so will reduce the number of reallocations to do.  (Otherwise, why
92  * did Microsoft bother supporting a minimum buffer size?)
93  */
94 #define ERRBUF_SIZE    128
95 const char *
win32strerror(DWORD error)96 win32strerror(DWORD error)
97 {
98     DWORD retval;
99     WCHAR *utf16_message;
100     char *utf8_message;
101     char *tempmsg;
102     const char *msg;
103 
104     /*
105      * XXX - what language ID to use?
106      *
107      * For UN*Xes, g_strerror() may or may not return localized strings.
108      *
109      * We currently don't have localized strings, except for GUI items,
110      * but we might want to do so.  On the other hand, if most of these
111      * messages are going to be read by Wireshark developers, English
112      * might be a better choice, so the developer doesn't have to get
113      * the message translated if it's in a language they don't happen
114      * to understand.  Then again, we're including the error number,
115      * so the developer can just look that up.
116      */
117     retval = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_MAX_WIDTH_MASK,
118                             NULL, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
119                             (LPTSTR)&utf16_message, ERRBUF_SIZE, NULL);
120     if (retval == 0) {
121         /* Failed. */
122         tempmsg = g_strdup_printf("Couldn't get error message for error (%lu) (because %lu)",
123                                   error, GetLastError());
124         msg = g_intern_string(tempmsg);
125         g_free(tempmsg);
126         return msg;
127     }
128 
129     utf8_message = g_utf16_to_utf8(utf16_message, -1, NULL, NULL, NULL);
130     LocalFree(utf16_message);
131     if (utf8_message == NULL) {
132         /* Conversion failed. */
133         tempmsg = g_strdup_printf("Couldn't convert error message for error to UTF-8 (%lu) (because %lu)",
134                                   error, GetLastError());
135         msg = g_intern_string(tempmsg);
136         g_free(tempmsg);
137         return msg;
138     }
139     tempmsg = g_strdup_printf("%s (%lu)", utf8_message, error);
140     g_free(utf8_message);
141     msg = g_intern_string(tempmsg);
142     g_free(tempmsg);
143     return msg;
144 }
145 
146 /*
147  * Generate a string for a Win32 exception code.
148  */
149 const char *
win32strexception(DWORD exception)150 win32strexception(DWORD exception)
151 {
152     static char errbuf[ERRBUF_SIZE+1];
153     static const struct exception_msg {
154         int code;
155         char *msg;
156     } exceptions[] = {
157         { EXCEPTION_ACCESS_VIOLATION, "Access violation" },
158         { EXCEPTION_ARRAY_BOUNDS_EXCEEDED, "Array bounds exceeded" },
159         { EXCEPTION_BREAKPOINT, "Breakpoint" },
160         { EXCEPTION_DATATYPE_MISALIGNMENT, "Data type misalignment" },
161         { EXCEPTION_FLT_DENORMAL_OPERAND, "Denormal floating-point operand" },
162         { EXCEPTION_FLT_DIVIDE_BY_ZERO, "Floating-point divide by zero" },
163         { EXCEPTION_FLT_INEXACT_RESULT, "Floating-point inexact result" },
164         { EXCEPTION_FLT_INVALID_OPERATION, "Invalid floating-point operation" },
165         { EXCEPTION_FLT_OVERFLOW, "Floating-point overflow" },
166         { EXCEPTION_FLT_STACK_CHECK, "Floating-point stack check" },
167         { EXCEPTION_FLT_UNDERFLOW, "Floating-point underflow" },
168         { EXCEPTION_GUARD_PAGE, "Guard page violation" },
169         { EXCEPTION_ILLEGAL_INSTRUCTION, "Illegal instruction" },
170         { EXCEPTION_IN_PAGE_ERROR, "Page-in error" },
171         { EXCEPTION_INT_DIVIDE_BY_ZERO, "Integer divide by zero" },
172         { EXCEPTION_INT_OVERFLOW, "Integer overflow" },
173         { EXCEPTION_INVALID_DISPOSITION, "Invalid disposition" },
174         { EXCEPTION_INVALID_HANDLE, "Invalid handle" },
175         { EXCEPTION_NONCONTINUABLE_EXCEPTION, "Non-continuable exception" },
176         { EXCEPTION_PRIV_INSTRUCTION, "Privileged instruction" },
177         { EXCEPTION_SINGLE_STEP, "Single-step complete" },
178         { EXCEPTION_STACK_OVERFLOW, "Stack overflow" },
179         { 0, NULL }
180     };
181 #define N_EXCEPTIONS    (sizeof exceptions / sizeof exceptions[0])
182     int i;
183 
184     for (i = 0; i < N_EXCEPTIONS; i++) {
185         if (exceptions[i].code == exception)
186             return exceptions[i].msg;
187     }
188     g_snprintf(errbuf, (gulong)sizeof errbuf, "Exception 0x%08x", exception);
189     return errbuf;
190 }
191 
192 // This appears to be the closest equivalent to SIGPIPE on Windows.
193 // https://devblogs.microsoft.com/oldnewthing/?p=2433
194 // https://stackoverflow.com/a/53214/82195
195 
win32_kill_child_on_exit(HANDLE child_handle)196 static void win32_kill_child_on_exit(HANDLE child_handle) {
197     static HANDLE cjo_handle = NULL;
198     if (!cjo_handle) {
199         cjo_handle = CreateJobObject(NULL, NULL);
200 
201         if (!cjo_handle) {
202             ws_log(LOG_DOMAIN_CAPTURE, LOG_LEVEL_DEBUG, "Could not create child cleanup job object: %s",
203                 win32strerror(GetLastError()));
204             return;
205         }
206 
207         JOBOBJECT_EXTENDED_LIMIT_INFORMATION cjo_jel_info = { 0 };
208         cjo_jel_info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
209         BOOL sijo_ret = SetInformationJobObject(cjo_handle, JobObjectExtendedLimitInformation,
210             &cjo_jel_info, sizeof(cjo_jel_info));
211         if (!sijo_ret) {
212             ws_log(LOG_DOMAIN_CAPTURE, LOG_LEVEL_DEBUG, "Could not set child cleanup limits: %s",
213                 win32strerror(GetLastError()));
214         }
215     }
216 
217     BOOL aptjo_ret = AssignProcessToJobObject(cjo_handle, child_handle);
218     if (!aptjo_ret) {
219         ws_log(LOG_DOMAIN_CAPTURE, LOG_LEVEL_DEBUG, "Could not assign child cleanup process: %s",
220             win32strerror(GetLastError()));
221     }
222 }
223 
win32_create_process(const char * application_name,const char * command_line,LPSECURITY_ATTRIBUTES process_attributes,LPSECURITY_ATTRIBUTES thread_attributes,BOOL inherit_handles,DWORD creation_flags,LPVOID environment,const char * current_directory,LPSTARTUPINFO startup_info,LPPROCESS_INFORMATION process_information)224 BOOL win32_create_process(const char *application_name, const char *command_line, LPSECURITY_ATTRIBUTES process_attributes, LPSECURITY_ATTRIBUTES thread_attributes, BOOL inherit_handles, DWORD creation_flags, LPVOID environment, const char *current_directory, LPSTARTUPINFO startup_info, LPPROCESS_INFORMATION process_information)
225 {
226     gunichar2 *wappname = NULL, *wcurrentdirectory = NULL;
227     gunichar2 *wcommandline = g_utf8_to_utf16(command_line, -1, NULL, NULL, NULL);
228     // CREATE_SUSPENDED: Suspend the child so that we can cleanly call
229     //     AssignProcessToJobObject.
230     DWORD wcreationflags = creation_flags|CREATE_SUSPENDED;
231     // CREATE_BREAKAWAY_FROM_JOB: The main application might be associated with a job,
232     //     e.g. if we're running under "Run As", ConEmu, or Visual Studio. On Windows
233     //     <= 7 our child process needs to break away from it so that we can cleanly
234     //     call AssignProcessToJobObject on *our* job.
235     //     Windows >= 8 supports nested jobs so this isn't necessary there.
236     //     https://blogs.msdn.microsoft.com/winsdk/2014/09/22/job-object-insanity/
237     //
238     if (! IsWindowsVersionOrGreater(6, 2, 0)) { // Windows 8
239         wcreationflags |= CREATE_BREAKAWAY_FROM_JOB;
240     }
241 
242     if (application_name) {
243         wappname = g_utf8_to_utf16(application_name, -1, NULL, NULL, NULL);
244     }
245     if (current_directory) {
246         wcurrentdirectory = g_utf8_to_utf16(current_directory, -1, NULL, NULL, NULL);
247     }
248     BOOL cp_res = CreateProcess(wappname, wcommandline, process_attributes, thread_attributes,
249         inherit_handles, wcreationflags, environment, wcurrentdirectory, startup_info,
250         process_information);
251     if (cp_res) {
252         win32_kill_child_on_exit(process_information->hProcess);
253         ResumeThread(process_information->hThread);
254     }
255     // XXX Else try again if CREATE_BREAKAWAY_FROM_JOB and GetLastError() == ERROR_ACCESS_DENIED?
256 
257     g_free(wappname);
258     g_free(wcommandline);
259     g_free(wcurrentdirectory);
260     return cp_res;
261 }
262 
263 /*
264  * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
265  *
266  * Local Variables:
267  * c-basic-offset: 4
268  * tab-width: 8
269  * indent-tabs-mode: nil
270  * End:
271  *
272  * ex: set shiftwidth=4 tabstop=8 expandtab:
273  * :indentSize=4:tabSize=8:noTabs=true:
274  */
275