1 /*
2  * Copyright (c) 2012 The Native Client Authors. All rights reserved.
3  * Use of this source code is governed by a BSD-style license that can be
4  * found in the LICENSE file.
5  */
6 
7 #include <string.h>
8 
9 #include "native_client/src/include/build_config.h"
10 #include "native_client/src/include/nacl_macros.h"
11 #include "native_client/src/shared/platform/nacl_check.h"
12 #include "native_client/src/shared/platform/nacl_exit.h"
13 #include "native_client/src/shared/platform/nacl_log.h"
14 #include "native_client/src/trusted/service_runtime/include/bits/nacl_syscalls.h"
15 #include "native_client/src/trusted/service_runtime/load_file.h"
16 #include "native_client/src/trusted/service_runtime/nacl_all_modules.h"
17 #include "native_client/src/trusted/service_runtime/nacl_app.h"
18 #include "native_client/src/trusted/service_runtime/nacl_app_thread.h"
19 #include "native_client/src/trusted/service_runtime/nacl_signal.h"
20 #include "native_client/src/trusted/service_runtime/nacl_syscall_register.h"
21 #include "native_client/src/trusted/service_runtime/sel_ldr.h"
22 #include "native_client/src/trusted/service_runtime/win/debug_exception_handler.h"
23 
24 #if NACL_WINDOWS
25 # include <windows.h>
26 # include <dbghelp.h>
27 #elif NACL_LINUX
28 # include <signal.h>
29 #endif
30 
31 /*
32  * This test case checks that an exception handler registered via
33  * Windows' SetUnhandledExceptionFilter() API gets called if a crash
34  * occurs inside a NaCl syscall handler.  On x86-64, we have to ensure
35  * that the stack is set up in a way that Windows likes, otherwise
36  * this exception handler does not get called.  For background, see
37  * http://code.google.com/p/nativeclient/issues/detail?id=2237.
38  */
39 
40 
41 static const char *g_crash_type;
42 
43 
TestWithUntrustedExceptionHandling(void)44 static int TestWithUntrustedExceptionHandling(void) {
45   return strcmp(g_crash_type, "NACL_TEST_CRASH_JUMP_TO_ZERO") == 0 ||
46          strcmp(g_crash_type, "NACL_TEST_CRASH_JUMP_INTO_SANDBOX") == 0;
47 }
48 
49 
50 #if NACL_WINDOWS
51 
52 #define MAX_SYMBOL_NAME_LENGTH 100
53 
PrintSymbolForAddress(DWORD64 addr)54 static void PrintSymbolForAddress(DWORD64 addr) {
55   /*
56    * Code adapted from Chromium's stack_trace_win.cc, in turn adapted
57    * from an MSDN example:
58    * http://msdn.microsoft.com/en-us/library/ms680578(VS.85).aspx
59    */
60   ULONG64 buffer[(sizeof(SYMBOL_INFO) +
61                   MAX_SYMBOL_NAME_LENGTH * sizeof(wchar_t) +
62                   sizeof(ULONG64) - 1) /
63                  sizeof(ULONG64)];
64   DWORD64 sym_displacement = 0;
65   PSYMBOL_INFO symbol = (PSYMBOL_INFO) buffer;
66   BOOL has_symbol;
67   memset(buffer, 0, sizeof(buffer));
68 
69   SymInitialize(GetCurrentProcess(), NULL, TRUE);
70 
71   symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
72   symbol->MaxNameLen = MAX_SYMBOL_NAME_LENGTH - 1;
73   has_symbol = SymFromAddr(GetCurrentProcess(),
74                            addr, &sym_displacement, symbol);
75   if (has_symbol) {
76     fprintf(stderr, "%s + 0x%x\n", symbol->Name, sym_displacement);
77   } else {
78     fprintf(stderr, "<no symbol>\n");
79   }
80 }
81 
82 /*
83  * On x86-64 Windows, we expect the backtrace to contain the
84  * following, with no gaps:
85  *
86  *   NaClSyscallCSegHook
87  *   NaClSwitchSavingStackPtr
88  *   NaClStartThreadInApp
89  *
90  * We could check for those names, but symbols are not always
91  * available.  Instead we check for bogus stack frames below, which
92  * the stack unwinder lets through.
93  */
Backtrace(CONTEXT * initial_context)94 static void Backtrace(CONTEXT *initial_context) {
95   int machine_type;
96   CONTEXT context_for_frame = *initial_context;
97   STACKFRAME64 frame = { 0 };
98   int frame_number = 0;
99   int failed = 0;
100 
101   fprintf(stderr, "Stack backtrace:\n");
102 #if NACL_ARCH(NACL_BUILD_ARCH) == NACL_x86 && NACL_BUILD_SUBARCH == 64
103   machine_type = IMAGE_FILE_MACHINE_AMD64;
104   frame.AddrPC.Offset = initial_context->Rip;
105   frame.AddrFrame.Offset = initial_context->Rbp;
106   frame.AddrStack.Offset = initial_context->Rsp;
107 #elif NACL_ARCH(NACL_BUILD_ARCH) == NACL_x86 && NACL_BUILD_SUBARCH == 32
108   machine_type = IMAGE_FILE_MACHINE_I386;
109   frame.AddrPC.Offset = initial_context->Eip;
110   frame.AddrFrame.Offset = initial_context->Ebp;
111   frame.AddrStack.Offset = initial_context->Esp;
112 #else
113 # error Unknown architecture
114 #endif
115   frame.AddrPC.Mode = AddrModeFlat;
116   frame.AddrFrame.Mode = AddrModeFlat;
117   frame.AddrStack.Mode = AddrModeFlat;
118   frame.Virtual = 0;
119 
120   while (1) {
121     STACKFRAME64 previous_frame = frame;
122     if (!StackWalk64(machine_type, GetCurrentProcess(), GetCurrentThread(),
123                      &frame, &context_for_frame,
124                      NULL, /* use ReadMemory() */
125                      SymFunctionTableAccess64, SymGetModuleBase64, NULL)) {
126       break;
127     }
128     fprintf(stderr, "#%i: ip=%p stack=%llx frame=%llx ",
129             frame_number,
130             frame.AddrPC.Offset,
131             frame.AddrStack.Offset,
132             frame.AddrFrame.Offset);
133     PrintSymbolForAddress(frame.AddrPC.Offset);
134     /*
135      * Perform some sanity checks.  Windows' x86-64 stack unwinder
136      * applies a fallback rule when it sees return addresses without
137      * unwind info, but the fallback rule is for leaf functions.  This
138      * causes the stack unwinder to report stack frames that cannot
139      * possibly be valid in the Windows x86-64 ABI.  We check for such
140      * frames here.  An error here would suggest that the unwind info
141      * for NaClSwitchSavingStackPtr is wrong.
142      *
143      * The frame for a non-leaf function Foo() looks like this:
144      *     32 bytes   shadow space (scratch space for Foo())
145      *   ---- Foo()'s caller's AddrStack points here
146      *      8 bytes   return address (points into Foo()'s caller)
147      *      8 bytes   scratch space for Foo()
148      *   ---- Foo()'s AddrFrame points here (unless the hardware exception
149      *        occurred inside Foo(), in which case this is less well-defined)
150      *   16*n bytes   scratch space for Foo() (for some n >= 0)
151      *     32 bytes   shadow space (scratch space for Foo()'s callees)
152      *   ---- Foo()'s rsp and AddrStack point here
153      *
154      * The frame for a leaf function Bar() that never adjusts rsp
155      * looks like this:
156      *     32 bytes   shadow space (scratch space for Bar())
157      *      8 bytes   return address (points into Bar()'s caller)
158      *   ---- Bar()'s rsp and AddrStack points here
159      *      8 bytes   not usable by Bar() at all!
160      *   ---- Bar()'s AddrFrame points here
161      */
162     if (NACL_ARCH(NACL_BUILD_ARCH) == NACL_x86 && NACL_BUILD_SUBARCH == 64) {
163       /* frame_size must be signed for the check to be useful. */
164       long long frame_size = frame.AddrFrame.Offset - frame.AddrStack.Offset;
165       if (frame_number > 0 && frame_size < 32) {
166         fprintf(stderr, "Error: frame_size=%i, which is too small\n",
167                 frame_size);
168         failed = 1;
169       }
170       if (frame_number > 1 &&
171           frame.AddrStack.Offset != previous_frame.AddrFrame.Offset + 16) {
172         fprintf(stderr, "Error: stack does not fit with previous frame\n");
173         failed = 1;
174       }
175     }
176     frame_number++;
177   }
178   if (failed) {
179     _exit(1);
180   }
181 }
182 
ExceptionHandler(EXCEPTION_POINTERS * exc_info)183 static LONG WINAPI ExceptionHandler(EXCEPTION_POINTERS *exc_info) {
184   printf("Inside exception handler, as expected\n");
185   Backtrace(exc_info->ContextRecord);
186   printf("Stack backtrace passed sanity checks\n");
187 
188   if (strcmp(g_crash_type, "NACL_TEST_CRASH_MEMORY") == 0 ||
189       strcmp(g_crash_type, "NACL_TEST_CRASH_JUMP_TO_ZERO") == 0 ||
190       strcmp(g_crash_type, "NACL_TEST_CRASH_JUMP_INTO_SANDBOX") == 0) {
191     /*
192      * STATUS_ACCESS_VIOLATION is 0xc0000005 but we deliberately
193      * convert this to a signed number since Python's wrapper for
194      * GetExitCodeProcess() treats the STATUS_* values as negative,
195      * although the unsigned values are used in headers and are more
196      * widely recognised
197      */
198     fprintf(stderr, "** intended_exit_status=%i\n", STATUS_ACCESS_VIOLATION);
199   } else if (strcmp(g_crash_type, "NACL_TEST_CRASH_LOG_FATAL") == 0 ||
200              strcmp(g_crash_type, "NACL_TEST_CRASH_CHECK_FAILURE") == 0) {
201     fprintf(stderr, "** intended_exit_status=trusted_sigabrt\n");
202   } else {
203     NaClLog(LOG_FATAL, "Unknown crash type: \"%s\"\n", g_crash_type);
204   }
205   /*
206    * Continuing is what Breakpad does, but this should cause the
207    * process to exit with an exit status that is appropriate for the
208    * type of exception.  We want to test that ExceptionHandler() does
209    * not get called twice, since that does not work with Chrome's
210    * embedding of Breakpad.
211    */
212   return EXCEPTION_CONTINUE_SEARCH;
213 }
214 
RegisterHandlers(void)215 static void RegisterHandlers(void) {
216   /*
217    * The UnhandledExceptionFilter does not get run if a debugger
218    * process is attached, so use vectored exception handling instead
219    * in that case.
220    */
221   if (TestWithUntrustedExceptionHandling()) {
222     CHECK(AddVectoredExceptionHandler(1, ExceptionHandler) != NULL);
223   } else {
224     SetUnhandledExceptionFilter(ExceptionHandler);
225   }
226 }
227 
228 #else
229 
230 static const char exit_message[] = "** intended_exit_status=0\n";
231 
SignalHandler(int sig)232 static void SignalHandler(int sig) {
233   if (strcmp(g_crash_type, "NACL_TEST_CRASH_MEMORY") == 0 ||
234       strcmp(g_crash_type, "NACL_TEST_CRASH_JUMP_TO_ZERO") == 0 ||
235       strcmp(g_crash_type, "NACL_TEST_CRASH_JUMP_INTO_SANDBOX") == 0) {
236     CHECK(sig == SIGSEGV);
237   } else if (strcmp(g_crash_type, "NACL_TEST_CRASH_LOG_FATAL") == 0 ||
238              strcmp(g_crash_type, "NACL_TEST_CRASH_CHECK_FAILURE") == 0) {
239     CHECK(sig == SIGABRT);
240   } else {
241     NaClLog(LOG_FATAL, "Unknown crash type: \"%s\"\n", g_crash_type);
242   }
243   /* Avoid printf() because it uses a lot of stack space. */
244   if (write(2, exit_message, sizeof(exit_message) - 1) != 0) {
245     /* This conditional suppresses a compiler warning. */
246   }
247   _exit(0);
248 }
249 
RegisterHandlers(void)250 static void RegisterHandlers(void) {
251   int signals[] = { SIGSEGV, SIGABRT };
252   size_t index;
253   for (index = 0; index < NACL_ARRAY_SIZE(signals); index++) {
254     CHECK(signal(signals[index], SignalHandler) == 0);
255   }
256   NaClSignalHandlerInit();
257 }
258 
259 #endif
260 
JumpToZeroCrashSyscall(struct NaClAppThread * natp)261 int32_t JumpToZeroCrashSyscall(struct NaClAppThread *natp) {
262   void (*null_func_ptr)(void) = NULL;
263 
264   UNREFERENCED_PARAMETER(natp);
265 
266   null_func_ptr();
267 
268   NaClLog(LOG_FATAL, "JumpToZeroCrashSyscall: Should not reach here\n");
269   return 1;
270 }
271 
JumpIntoSandboxCrashSyscall(struct NaClAppThread * natp)272 int32_t JumpIntoSandboxCrashSyscall(struct NaClAppThread *natp) {
273   void (*bad_func_ptr)(void) = (void (*)(void)) natp->nap->mem_start;
274   bad_func_ptr();
275 
276   NaClLog(LOG_FATAL, "JumpIntoSandboxCrashSyscall: Should not reach here\n");
277   return 1;
278 }
279 
280 NACL_DEFINE_SYSCALL_0(JumpToZeroCrashSyscall)
NACL_DEFINE_SYSCALL_0(JumpIntoSandboxCrashSyscall)281 NACL_DEFINE_SYSCALL_0(JumpIntoSandboxCrashSyscall)
282 
283 int main(int argc, char **argv) {
284   struct NaClApp app;
285 
286   NaClHandleBootstrapArgs(&argc, &argv);
287   NaClDebugExceptionHandlerStandaloneHandleArgs(argc, argv);
288 
289   /* Turn off buffering to aid debugging. */
290   setvbuf(stdout, NULL, _IONBF, 0);
291   setvbuf(stderr, NULL, _IONBF, 0);
292 
293   NaClAllModulesInit();
294 
295   if (argc != 3) {
296     NaClLog(LOG_FATAL,
297             "Expected 2 arguments: <executable-filename> <crash-type>\n");
298   }
299 
300   g_crash_type = argv[2];
301 
302   CHECK(NaClAppCtor(&app));
303   CHECK(NaClAppLoadFileFromFilename(&app, argv[1]) == LOAD_OK);
304   NaClAppInitialDescriptorHookup(&app);
305 
306   if (TestWithUntrustedExceptionHandling()) {
307     app.enable_exception_handling = 1;
308 #if NACL_WINDOWS
309     app.attach_debug_exception_handler_func =
310         NaClDebugExceptionHandlerStandaloneAttach;
311 #endif
312   }
313 
314   NACL_REGISTER_SYSCALL(&app, JumpToZeroCrashSyscall, NACL_sys_test_syscall_1);
315   NACL_REGISTER_SYSCALL(&app, JumpIntoSandboxCrashSyscall,
316                         NACL_sys_test_syscall_2);
317 
318   RegisterHandlers();
319 
320   CHECK(NaClCreateMainThread(&app, argc - 1, argv + 1, NULL));
321   NaClWaitForMainThreadToExit(&app);
322 
323   NaClLog(LOG_ERROR, "We did not expect the test program to exit cleanly\n");
324   return 1;
325 }
326