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