1// Copyright (c) 2013 Google Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8// * Redistributions of source code must retain the above copyright
9// notice, this list of conditions and the following disclaimer.
10// * Redistributions in binary form must reproduce the above
11// copyright notice, this list of conditions and the following disclaimer
12// in the documentation and/or other materials provided with the
13// distribution.
14// * Neither the name of Google Inc. nor the names of its
15// contributors may be used to endorse or promote products derived from
16// this software without specific prior written permission.
17//
18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30// exploitability_linux.cc: Linux specific exploitability engine.
31//
32// Provides a guess at the exploitability of the crash for the Linux
33// platform given a minidump and process_state.
34//
35// Author: Matthew Riley
36
37#include "processor/exploitability_linux.h"
38
39#ifndef _WIN32
40#include <regex.h>
41#include <stdio.h>
42#include <stdlib.h>
43
44#include <sstream>
45#include <iterator>
46#endif // _WIN32
47
48#include <string.h>
49
50#include "google_breakpad/common/minidump_exception_linux.h"
51#include "google_breakpad/processor/call_stack.h"
52#include "google_breakpad/processor/process_state.h"
53#include "google_breakpad/processor/stack_frame.h"
54#include "processor/logging.h"
55
56namespace {
57
58// Prefixes for memory mapping names.
59constexpr char kHeapPrefix[] = "[heap";
60constexpr char kStackPrefix[] = "[stack";
61
62// This function in libc is called if the program was compiled with
63// -fstack-protector and a function's stack canary changes.
64constexpr char kStackCheckFailureFunction[] = "__stack_chk_fail";
65
66// This function in libc is called if the program was compiled with
67// -D_FORTIFY_SOURCE=2, a function like strcpy() is called, and the runtime
68// can determine that the call would overflow the target buffer.
69constexpr char kBoundsCheckFailureFunction[] = "__chk_fail";
70
71#ifndef _WIN32
72const unsigned int MAX_INSTRUCTION_LEN = 15;
73const unsigned int MAX_OBJDUMP_BUFFER_LEN = 4096;
74#endif // _WIN32
75
76} // namespace
77
78namespace google_breakpad {
79
80ExploitabilityLinux::ExploitabilityLinux(Minidump* dump,
81 ProcessState* process_state)
82 : Exploitability(dump, process_state),
83 enable_objdump_(false) { }
84
85ExploitabilityLinux::ExploitabilityLinux(Minidump* dump,
86 ProcessState* process_state,
87 bool enable_objdump)
88 : Exploitability(dump, process_state),
89 enable_objdump_(enable_objdump) { }
90
91
92ExploitabilityRating ExploitabilityLinux::CheckPlatformExploitability() {
93 // Check the crashing thread for functions suggesting a buffer overflow or
94 // stack smash.
95 if (process_state_->requesting_thread() != -1) {
96 CallStack* crashing_thread =
97 process_state_->threads()->at(process_state_->requesting_thread());
98 const vector<StackFrame*>& crashing_thread_frames =
99 *crashing_thread->frames();
100 for (size_t i = 0; i < crashing_thread_frames.size(); ++i) {
101 if (crashing_thread_frames[i]->function_name ==
102 kStackCheckFailureFunction) {
103 return EXPLOITABILITY_HIGH;
104 }
105
106 if (crashing_thread_frames[i]->function_name ==
107 kBoundsCheckFailureFunction) {
108 return EXPLOITABILITY_HIGH;
109 }
110 }
111 }
112
113 // Getting exception data. (It should exist for all minidumps.)
114 MinidumpException* exception = dump_->GetException();
115 if (exception == NULL) {
116 BPLOG(INFO) << "No exception record.";
117 return EXPLOITABILITY_ERR_PROCESSING;
118 }
119 const MDRawExceptionStream* raw_exception_stream = exception->exception();
120 if (raw_exception_stream == NULL) {
121 BPLOG(INFO) << "No raw exception stream.";
122 return EXPLOITABILITY_ERR_PROCESSING;
123 }
124
125 // Checking for benign exceptions that caused the crash.
126 if (this->BenignCrashTrigger(raw_exception_stream)) {
127 return EXPLOITABILITY_NONE;
128 }
129
130 // Check if the instruction pointer is in a valid instruction region
131 // by finding if it maps to an executable part of memory.
132 uint64_t instruction_ptr = 0;
133 uint64_t stack_ptr = 0;
134
135 const MinidumpContext* context = exception->GetContext();
136 if (context == NULL) {
137 BPLOG(INFO) << "No exception context.";
138 return EXPLOITABILITY_ERR_PROCESSING;
139 }
140
141 // Getting the instruction pointer.
142 if (!context->GetInstructionPointer(&instruction_ptr)) {
143 BPLOG(INFO) << "Failed to retrieve instruction pointer.";
144 return EXPLOITABILITY_ERR_PROCESSING;
145 }
146
147 // Getting the stack pointer.
148 if (!context->GetStackPointer(&stack_ptr)) {
149 BPLOG(INFO) << "Failed to retrieve stack pointer.";
150 return EXPLOITABILITY_ERR_PROCESSING;
151 }
152
153 // Checking for the instruction pointer in a valid instruction region,
154 // a misplaced stack pointer, and an executable stack or heap.
155 if (!this->InstructionPointerInCode(instruction_ptr) ||
156 this->StackPointerOffStack(stack_ptr) ||
157 this->ExecutableStackOrHeap()) {
158 return EXPLOITABILITY_HIGH;
159 }
160
161 // Check for write to read only memory or invalid memory, shelling out
162 // to objdump is enabled.
163 if (enable_objdump_ && this->EndedOnIllegalWrite(instruction_ptr)) {
164 return EXPLOITABILITY_HIGH;
165 }
166
167 // There was no strong evidence suggesting exploitability, but the minidump
168 // does not appear totally benign either.
169 return EXPLOITABILITY_INTERESTING;
170}
171
172bool ExploitabilityLinux::EndedOnIllegalWrite(uint64_t instruction_ptr) {
173#ifdef _WIN32
174 BPLOG(INFO) << "MinGW does not support fork and exec. Terminating method.";
175#else
176 // Get memory region containing instruction pointer.
177 MinidumpMemoryList* memory_list = dump_->GetMemoryList();
178 MinidumpMemoryRegion* memory_region =
179 memory_list ?
180 memory_list->GetMemoryRegionForAddress(instruction_ptr) : NULL;
181 if (!memory_region) {
182 BPLOG(INFO) << "No memory region around instruction pointer.";
183 return false;
184 }
185
186 // Get exception data to find architecture.
187 string architecture = "";
188 MinidumpException* exception = dump_->GetException();
189 // This should never evaluate to true, since this should not be reachable
190 // without checking for exception data earlier.
191 if (!exception) {
192 BPLOG(INFO) << "No exception data.";
193 return false;
194 }
195 const MDRawExceptionStream* raw_exception_stream = exception->exception();
196 const MinidumpContext* context = exception->GetContext();
197 // This should not evaluate to true, for the same reason mentioned above.
198 if (!raw_exception_stream || !context) {
199 BPLOG(INFO) << "No exception or architecture data.";
200 return false;
201 }
202 // Check architecture and set architecture variable to corresponding flag
203 // in objdump.
204 switch (context->GetContextCPU()) {
205 case MD_CONTEXT_X86:
206 architecture = "i386";
207 break;
208 case MD_CONTEXT_AMD64:
209 architecture = "i386:x86-64";
210 break;
211 default:
212 // Unsupported architecture. Note that ARM architectures are not
213 // supported because objdump does not support ARM.
214 return false;
215 }
216
217 // Get memory region around instruction pointer and the number of bytes
218 // before and after the instruction pointer in the memory region.
219 const uint8_t* raw_memory = memory_region->GetMemory();
220 const uint64_t base = memory_region->GetBase();
221 if (base > instruction_ptr) {
222 BPLOG(ERROR) << "Memory region base value exceeds instruction pointer.";
223 return false;
224 }
225 const uint64_t offset = instruction_ptr - base;
226 if (memory_region->GetSize() < MAX_INSTRUCTION_LEN + offset) {
227 BPLOG(INFO) << "Not enough bytes left to guarantee complete instruction.";
228 return false;
229 }
230
231 // Convert bytes into objdump output.
232 char objdump_output_buffer[MAX_OBJDUMP_BUFFER_LEN] = {0};
233 DisassembleBytes(architecture,
234 raw_memory + offset,
235 MAX_OBJDUMP_BUFFER_LEN,
236 objdump_output_buffer);
237
238 string line;
239 if (!GetObjdumpInstructionLine(objdump_output_buffer, &line)) {
240 return false;
241 }
242
243 // Convert objdump instruction line into the operation and operands.
244 string instruction = "";
245 string dest = "";
246 string src = "";
247 TokenizeObjdumpInstruction(line, &instruction, &dest, &src);
248
249 // Check if the operation is a write to memory. First, the instruction
250 // must one that can write to memory. Second, the write destination
251 // must be a spot in memory rather than a register. Since there are no
252 // symbols from objdump, the destination will be enclosed by brackets.
253 if (dest.size() > 2 && dest.at(0) == '[' && dest.at(dest.size() - 1) == ']' &&
254 (!instruction.compare("mov") || !instruction.compare("inc") ||
255 !instruction.compare("dec") || !instruction.compare("and") ||
256 !instruction.compare("or") || !instruction.compare("xor") ||
257 !instruction.compare("not") || !instruction.compare("neg") ||
258 !instruction.compare("add") || !instruction.compare("sub") ||
259 !instruction.compare("shl") || !instruction.compare("shr"))) {
260 // Strip away enclosing brackets from the destination address.
261 dest = dest.substr(1, dest.size() - 2);
262 uint64_t write_address = 0;
263 CalculateAddress(dest, *context, &write_address);
264
265 // If the program crashed as a result of a write, the destination of
266 // the write must have been an address that did not permit writing.
267 // However, if the address is under 4k, due to program protections,
268 // the crash does not suggest exploitability for writes with such a
269 // low target address.
270 return write_address > 4096;
271 }
272#endif // _WIN32
273 return false;
274}
275
276#ifndef _WIN32
277bool ExploitabilityLinux::CalculateAddress(const string& address_expression,
278 const DumpContext& context,
279 uint64_t* write_address) {
280 // The destination should be the format reg+a or reg-a, where reg
281 // is a register and a is a hexadecimal constant. Although more complex
282 // expressions can make valid instructions, objdump's disassembly outputs
283 // it in this simpler format.
284 // TODO(liuandrew): Handle more complex formats, should they arise.
285
286 if (!write_address) {
287 BPLOG(ERROR) << "Null parameter.";
288 return false;
289 }
290
291 // Clone parameter into a non-const string.
292 string expression = address_expression;
293
294 // Parse out the constant that is added to the address (if it exists).
295 size_t delim = expression.find('+');
296 bool positive_add_constant = true;
297 // Check if constant is subtracted instead of added.
298 if (delim == string::npos) {
299 positive_add_constant = false;
300 delim = expression.find('-');
301 }
302 uint32_t add_constant = 0;
303 // Save constant and remove it from the expression.
304 if (delim != string::npos) {
305 if (!sscanf(expression.substr(delim + 1).c_str(), "%x", &add_constant)) {
306 BPLOG(ERROR) << "Failed to scan constant.";
307 return false;
308 }
309 expression = expression.substr(0, delim);
310 }
311
312 // Set the the write address to the corresponding register.
313 // TODO(liuandrew): Add support for partial registers, such as
314 // the rax/eax/ax/ah/al chain.
315 switch (context.GetContextCPU()) {
316 case MD_CONTEXT_X86:
317 if (!expression.compare("eax")) {
318 *write_address = context.GetContextX86()->eax;
319 } else if (!expression.compare("ebx")) {
320 *write_address = context.GetContextX86()->ebx;
321 } else if (!expression.compare("ecx")) {
322 *write_address = context.GetContextX86()->ecx;
323 } else if (!expression.compare("edx")) {
324 *write_address = context.GetContextX86()->edx;
325 } else if (!expression.compare("edi")) {
326 *write_address = context.GetContextX86()->edi;
327 } else if (!expression.compare("esi")) {
328 *write_address = context.GetContextX86()->esi;
329 } else if (!expression.compare("ebp")) {
330 *write_address = context.GetContextX86()->ebp;
331 } else if (!expression.compare("esp")) {
332 *write_address = context.GetContextX86()->esp;
333 } else if (!expression.compare("eip")) {
334 *write_address = context.GetContextX86()->eip;
335 } else {
336 BPLOG(ERROR) << "Unsupported register";
337 return false;
338 }
339 break;
340 case MD_CONTEXT_AMD64:
341 if (!expression.compare("rax")) {
342 *write_address = context.GetContextAMD64()->rax;
343 } else if (!expression.compare("rbx")) {
344 *write_address = context.GetContextAMD64()->rbx;
345 } else if (!expression.compare("rcx")) {
346 *write_address = context.GetContextAMD64()->rcx;
347 } else if (!expression.compare("rdx")) {
348 *write_address = context.GetContextAMD64()->rdx;
349 } else if (!expression.compare("rdi")) {
350 *write_address = context.GetContextAMD64()->rdi;
351 } else if (!expression.compare("rsi")) {
352 *write_address = context.GetContextAMD64()->rsi;
353 } else if (!expression.compare("rbp")) {
354 *write_address = context.GetContextAMD64()->rbp;
355 } else if (!expression.compare("rsp")) {
356 *write_address = context.GetContextAMD64()->rsp;
357 } else if (!expression.compare("rip")) {
358 *write_address = context.GetContextAMD64()->rip;
359 } else if (!expression.compare("r8")) {
360 *write_address = context.GetContextAMD64()->r8;
361 } else if (!expression.compare("r9")) {
362 *write_address = context.GetContextAMD64()->r9;
363 } else if (!expression.compare("r10")) {
364 *write_address = context.GetContextAMD64()->r10;
365 } else if (!expression.compare("r11")) {
366 *write_address = context.GetContextAMD64()->r11;
367 } else if (!expression.compare("r12")) {
368 *write_address = context.GetContextAMD64()->r12;
369 } else if (!expression.compare("r13")) {
370 *write_address = context.GetContextAMD64()->r13;
371 } else if (!expression.compare("r14")) {
372 *write_address = context.GetContextAMD64()->r14;
373 } else if (!expression.compare("r15")) {
374 *write_address = context.GetContextAMD64()->r15;
375 } else {
376 BPLOG(ERROR) << "Unsupported register";
377 return false;
378 }
379 break;
380 default:
381 // This should not occur since the same switch condition
382 // should have terminated this method.
383 return false;
384 }
385
386 // Add or subtract constant from write address (if applicable).
387 *write_address =
388 positive_add_constant ?
389 *write_address + add_constant : *write_address - add_constant;
390
391 return true;
392}
393
394// static
395bool ExploitabilityLinux::GetObjdumpInstructionLine(
396 const char* objdump_output_buffer,
397 string* instruction_line) {
398 // Put buffer data into stream to output line-by-line.
399 std::stringstream objdump_stream;
400 objdump_stream.str(string(objdump_output_buffer));
401
402 // Pipe each output line into the string until the string contains the first
403 // instruction from objdump. All lines before the "<.data>:" section are
404 // skipped. Loop until the line shows the first instruction or there are no
405 // lines left.
406 bool data_section_seen = false;
407 do {
408 if (!getline(objdump_stream, *instruction_line)) {
409 BPLOG(INFO) << "Objdump instructions not found";
410 return false;
411 }
412 if (instruction_line->find("<.data>:") != string::npos) {
413 data_section_seen = true;
414 }
415 } while (!data_section_seen || instruction_line->find("0:") == string::npos);
416 // This first instruction contains the above substring.
417
418 return true;
419}
420
421bool ExploitabilityLinux::TokenizeObjdumpInstruction(const string& line,
422 string* operation,
423 string* dest,
424 string* src) {
425 if (!operation || !dest || !src) {
426 BPLOG(ERROR) << "Null parameters passed.";
427 return false;
428 }
429
430 // Set all pointer values to empty strings.
431 *operation = "";
432 *dest = "";
433 *src = "";
434
435 // Tokenize the objdump line.
436 vector<string> tokens;
437 std::istringstream line_stream(line);
438 copy(std::istream_iterator<string>(line_stream),
439 std::istream_iterator<string>(),
440 std::back_inserter(tokens));
441
442 // Regex for the data in hex form. Each byte is two hex digits.
443 regex_t regex;
444 regcomp(&regex, "^[[:xdigit:]]{2}$", REG_EXTENDED | REG_NOSUB);
445
446 // Find and set the location of the operator. The operator appears
447 // directly after the chain of bytes that define the instruction. The
448 // operands will be the last token, given that the instruction has operands.
449 // If not, the operator is the last token. The loop skips the first token
450 // because the first token is the instruction number (namely "0:").
451 string operands = "";
452 for (size_t i = 1; i < tokens.size(); i++) {
453 // Check if current token no longer is in byte format.
454 if (regexec(&regex, tokens[i].c_str(), 0, NULL, 0)) {
455 // instruction = tokens[i];
456 *operation = tokens[i];
457 // If the operator is the last token, there are no operands.
458 if (i != tokens.size() - 1) {
459 operands = tokens[tokens.size() - 1];
460 }
461 break;
462 }
463 }
464 regfree(&regex);
465
466 if (operation->empty()) {
467 BPLOG(ERROR) << "Failed to parse out operation from objdump instruction.";
468 return false;
469 }
470
471 // Split operands into source and destination (if applicable).
472 if (!operands.empty()) {
473 size_t delim = operands.find(',');
474 if (delim == string::npos) {
475 *dest = operands;
476 } else {
477 *dest = operands.substr(0, delim);
478 *src = operands.substr(delim + 1);
479 }
480 }
481 return true;
482}
483
484bool ExploitabilityLinux::DisassembleBytes(const string& architecture,
485 const uint8_t* raw_bytes,
486 const unsigned int buffer_len,
487 char* objdump_output_buffer) {
488 if (!raw_bytes || !objdump_output_buffer) {
489 BPLOG(ERROR) << "Bad input parameters.";
490 return false;
491 }
492
493 // Write raw bytes around instruction pointer to a temporary file to
494 // pass as an argument to objdump.
495 char raw_bytes_tmpfile[] = "/tmp/breakpad_mem_region-raw_bytes-XXXXXX";
496 int raw_bytes_fd = mkstemp(raw_bytes_tmpfile);
497 if (raw_bytes_fd < 0) {
498 BPLOG(ERROR) << "Failed to create tempfile.";
499 unlink(raw_bytes_tmpfile);
500 return false;
501 }
502 if (write(raw_bytes_fd, raw_bytes, MAX_INSTRUCTION_LEN)
503 != MAX_INSTRUCTION_LEN) {
504 BPLOG(ERROR) << "Writing of raw bytes failed.";
505 unlink(raw_bytes_tmpfile);
506 return false;
507 }
508
509 char cmd[1024] = {0};
510 snprintf(cmd,
511 1024,
512 "objdump -D -b binary -M intel -m %s %s",
513 architecture.c_str(),
514 raw_bytes_tmpfile);
515 FILE* objdump_fp = popen(cmd, "r");
516 if (!objdump_fp) {
517 fclose(objdump_fp);
518 unlink(raw_bytes_tmpfile);
519 BPLOG(ERROR) << "Failed to call objdump.";
520 return false;
521 }
522 if (fread(objdump_output_buffer, 1, buffer_len, objdump_fp) <= 0) {
523 fclose(objdump_fp);
524 unlink(raw_bytes_tmpfile);
525 BPLOG(ERROR) << "Failed to read objdump output.";
526 return false;
527 }
528 fclose(objdump_fp);
529 unlink(raw_bytes_tmpfile);
530 return true;
531}
532#endif // _WIN32
533
534bool ExploitabilityLinux::StackPointerOffStack(uint64_t stack_ptr) {
535 MinidumpLinuxMapsList* linux_maps_list = dump_->GetLinuxMapsList();
536 // Inconclusive if there are no mappings available.
537 if (!linux_maps_list) {
538 return false;
539 }
540 const MinidumpLinuxMaps* linux_maps =
541 linux_maps_list->GetLinuxMapsForAddress(stack_ptr);
542 // Checks if the stack pointer maps to a valid mapping and if the mapping
543 // is not the stack. If the mapping has no name, it is inconclusive whether
544 // it is off the stack.
545 return !linux_maps || (linux_maps->GetPathname().compare("") &&
546 linux_maps->GetPathname().compare(
547 0, strlen(kStackPrefix), kStackPrefix));
548}
549
550bool ExploitabilityLinux::ExecutableStackOrHeap() {
551 MinidumpLinuxMapsList* linux_maps_list = dump_->GetLinuxMapsList();
552 if (linux_maps_list) {
553 for (size_t i = 0; i < linux_maps_list->get_maps_count(); i++) {
554 const MinidumpLinuxMaps* linux_maps =
555 linux_maps_list->GetLinuxMapsAtIndex(i);
556 // Check for executable stack or heap for each mapping.
557 if (linux_maps && (!linux_maps->GetPathname().compare(
558 0, strlen(kStackPrefix), kStackPrefix) ||
559 !linux_maps->GetPathname().compare(
560 0, strlen(kHeapPrefix), kHeapPrefix)) &&
561 linux_maps->IsExecutable()) {
562 return true;
563 }
564 }
565 }
566 return false;
567}
568
569bool ExploitabilityLinux::InstructionPointerInCode(uint64_t instruction_ptr) {
570 // Get Linux memory mapping from /proc/self/maps. Checking whether the
571 // region the instruction pointer is in has executable permission can tell
572 // whether it is in a valid code region. If there is no mapping for the
573 // instruction pointer, it is indicative that the instruction pointer is
574 // not within a module, which implies that it is outside a valid area.
575 MinidumpLinuxMapsList* linux_maps_list = dump_->GetLinuxMapsList();
576 const MinidumpLinuxMaps* linux_maps =
577 linux_maps_list ?
578 linux_maps_list->GetLinuxMapsForAddress(instruction_ptr) : NULL;
579 return linux_maps ? linux_maps->IsExecutable() : false;
580}
581
582bool ExploitabilityLinux::BenignCrashTrigger(
583 const MDRawExceptionStream* raw_exception_stream) {
584 // Check the cause of crash.
585 // If the exception of the crash is a benign exception,
586 // it is probably not exploitable.
587 switch (raw_exception_stream->exception_record.exception_code) {
588 case MD_EXCEPTION_CODE_LIN_SIGHUP:
589 case MD_EXCEPTION_CODE_LIN_SIGINT:
590 case MD_EXCEPTION_CODE_LIN_SIGQUIT:
591 case MD_EXCEPTION_CODE_LIN_SIGTRAP:
592 case MD_EXCEPTION_CODE_LIN_SIGABRT:
593 case MD_EXCEPTION_CODE_LIN_SIGFPE:
594 case MD_EXCEPTION_CODE_LIN_SIGKILL:
595 case MD_EXCEPTION_CODE_LIN_SIGUSR1:
596 case MD_EXCEPTION_CODE_LIN_SIGUSR2:
597 case MD_EXCEPTION_CODE_LIN_SIGPIPE:
598 case MD_EXCEPTION_CODE_LIN_SIGALRM:
599 case MD_EXCEPTION_CODE_LIN_SIGTERM:
600 case MD_EXCEPTION_CODE_LIN_SIGCHLD:
601 case MD_EXCEPTION_CODE_LIN_SIGCONT:
602 case MD_EXCEPTION_CODE_LIN_SIGSTOP:
603 case MD_EXCEPTION_CODE_LIN_SIGTSTP:
604 case MD_EXCEPTION_CODE_LIN_SIGTTIN:
605 case MD_EXCEPTION_CODE_LIN_SIGTTOU:
606 case MD_EXCEPTION_CODE_LIN_SIGURG:
607 case MD_EXCEPTION_CODE_LIN_SIGXCPU:
608 case MD_EXCEPTION_CODE_LIN_SIGXFSZ:
609 case MD_EXCEPTION_CODE_LIN_SIGVTALRM:
610 case MD_EXCEPTION_CODE_LIN_SIGPROF:
611 case MD_EXCEPTION_CODE_LIN_SIGWINCH:
612 case MD_EXCEPTION_CODE_LIN_SIGIO:
613 case MD_EXCEPTION_CODE_LIN_SIGPWR:
614 case MD_EXCEPTION_CODE_LIN_SIGSYS:
615 case MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED:
616 return true;
617 default:
618 return false;
619 }
620}
621
622} // namespace google_breakpad
623