Chromium Code Reviews

Side by Side Diff: src/processor/exploitability_linux.cc

Issue 1273823004: Add check for Linux minidump ending on bad write for exploitability rating. (Closed) Base URL: http://google-breakpad.googlecode.com/svn/trunk/
Patch Set: Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | | Annotate | Revision Log
« no previous file with comments | « src/processor/exploitability_linux.h ('k') | src/processor/exploitability_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2013 Google Inc. 1 // Copyright (c) 2013 Google Inc.
2 // All rights reserved. 2 // All rights reserved.
3 // 3 //
4 // Redistribution and use in source and binary forms, with or without 4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are 5 // modification, are permitted provided that the following conditions are
6 // met: 6 // met:
7 // 7 //
8 // * Redistributions of source code must retain the above copyright 8 // * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer. 9 // notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above 10 // * Redistributions in binary form must reproduce the above
(...skipping 18 matching lines...)
29 29
30 // exploitability_linux.cc: Linux specific exploitability engine. 30 // exploitability_linux.cc: Linux specific exploitability engine.
31 // 31 //
32 // Provides a guess at the exploitability of the crash for the Linux 32 // Provides a guess at the exploitability of the crash for the Linux
33 // platform given a minidump and process_state. 33 // platform given a minidump and process_state.
34 // 34 //
35 // Author: Matthew Riley 35 // Author: Matthew Riley
36 36
37 #include "processor/exploitability_linux.h" 37 #include "processor/exploitability_linux.h"
38 38
39 #include <regex.h>
40 #include <stdio.h>
41 #include <string.h>
42 #include <sysexits.h>
43 #include <sys/types.h>
44 #include <sys/wait.h>
45
46 #include <sstream>
47 #include <iterator>
48
39 #include "google_breakpad/common/minidump_exception_linux.h" 49 #include "google_breakpad/common/minidump_exception_linux.h"
40 #include "google_breakpad/processor/call_stack.h" 50 #include "google_breakpad/processor/call_stack.h"
41 #include "google_breakpad/processor/process_state.h" 51 #include "google_breakpad/processor/process_state.h"
42 #include "google_breakpad/processor/stack_frame.h" 52 #include "google_breakpad/processor/stack_frame.h"
43 #include "processor/logging.h" 53 #include "processor/logging.h"
44 54
55 #define MAX_INSTRUCTION_LEN 15
56 // This is the buffer size for objdump's output.
57 #define MAX_OBJDUMP_BUFFER_LEN 4096
58
45 namespace { 59 namespace {
46 60
47 // This function in libc is called if the program was compiled with 61 // This function in libc is called if the program was compiled with
48 // -fstack-protector and a function's stack canary changes. 62 // -fstack-protector and a function's stack canary changes.
49 const char kStackCheckFailureFunction[] = "__stack_chk_fail"; 63 const char kStackCheckFailureFunction[] = "__stack_chk_fail";
50 64
51 // This function in libc is called if the program was compiled with 65 // This function in libc is called if the program was compiled with
52 // -D_FORTIFY_SOURCE=2, a function like strcpy() is called, and the runtime 66 // -D_FORTIFY_SOURCE=2, a function like strcpy() is called, and the runtime
53 // can determine that the call would overflow the target buffer. 67 // can determine that the call would overflow the target buffer.
54 const char kBoundsCheckFailureFunction[] = "__chk_fail"; 68 const char kBoundsCheckFailureFunction[] = "__chk_fail";
55 69
70 #ifndef _WIN32
71 // PID of child process that is forked to call objdump.
72 pid_t child_pid = -1;
73
74 // Function to kill the child process used to call objdump in case the child
75 // process hangs.
76 void kill_child(int signal) {
77 kill(child_pid, SIGKILL);
78 }
79 #endif // _WIN32
80
56 } // namespace 81 } // namespace
57 82
58 namespace google_breakpad { 83 namespace google_breakpad {
59 84
60 ExploitabilityLinux::ExploitabilityLinux(Minidump *dump, 85 ExploitabilityLinux::ExploitabilityLinux(Minidump *dump,
61 ProcessState *process_state) 86 ProcessState *process_state)
62 : Exploitability(dump, process_state) { } 87 : Exploitability(dump, process_state) { }
63 88
64 ExploitabilityRating ExploitabilityLinux::CheckPlatformExploitability() { 89 ExploitabilityRating ExploitabilityLinux::CheckPlatformExploitability() {
65 // Check the crashing thread for functions suggesting a buffer overflow or 90 // Check the crashing thread for functions suggesting a buffer overflow or
(...skipping 49 matching lines...)
115 BPLOG(INFO) << "Failed to retrieve instruction pointer."; 140 BPLOG(INFO) << "Failed to retrieve instruction pointer.";
116 return EXPLOITABILITY_ERR_PROCESSING; 141 return EXPLOITABILITY_ERR_PROCESSING;
117 } 142 }
118 143
119 // Getting the stack pointer. 144 // Getting the stack pointer.
120 if (!context->GetStackPointer(&stack_ptr)) { 145 if (!context->GetStackPointer(&stack_ptr)) {
121 BPLOG(INFO) << "Failed to retrieve stack pointer."; 146 BPLOG(INFO) << "Failed to retrieve stack pointer.";
122 return EXPLOITABILITY_ERR_PROCESSING; 147 return EXPLOITABILITY_ERR_PROCESSING;
123 } 148 }
124 149
125 // Checking for the instruction pointer in a valid instruction region. 150 // Checking for the instruction pointer in a valid instruction region
151 // or if the crash resulted during an illegal write.
126 if (!this->InstructionPointerInCode(instruction_ptr) || 152 if (!this->InstructionPointerInCode(instruction_ptr) ||
127 this->StackPointerOffStack(stack_ptr) || 153 this->StackPointerOffStack(stack_ptr) ||
128 this->ExecutableStackOrHeap()) { 154 this->ExecutableStackOrHeap() ||
155 this->EndedOnIllegalWrite(instruction_ptr)) {
129 return EXPLOITABILITY_HIGH; 156 return EXPLOITABILITY_HIGH;
130 } 157 }
131 158
132 // There was no strong evidence suggesting exploitability, but the minidump 159 // There was no strong evidence suggesting exploitability, but the minidump
133 // does not appear totally benign either. 160 // does not appear totally benign either.
134 return EXPLOITABILITY_INTERESTING; 161 return EXPLOITABILITY_INTERESTING;
135 } 162 }
136 163
164 bool ExploitabilityLinux::EndedOnIllegalWrite(uint64_t instruction_ptr) {
ivanpe 2015/08/20 05:37:44 I think that this functionality should be disabled
liuandrew 2015/08/20 17:11:51 Done.
165 #ifdef _WIN32
166 BPLOG(INFO) << "MinGW does not support fork and exec. Terminating method.";
167 #else // _WIN32
168 // Get memory region containing instruction pointer.
169 MinidumpMemoryList *memory_list = dump_->GetMemoryList();
170 MinidumpMemoryRegion *memory_region =
171 memory_list ?
172 memory_list->GetMemoryRegionForAddress(instruction_ptr) : NULL;
173 if (!memory_region) {
174 BPLOG(ERROR) << "No memory region around instruction pointer.";
ivanpe 2015/08/20 05:37:44 Please, change this to INFO. In general we want t
liuandrew 2015/08/20 17:11:51 Done.
175 return false;
176 }
177
178 // Get exception data to find architecture.
179 string architecture = "";
180 MinidumpException *exception = dump_->GetException();
181 // This should never evaluate to true, since this should not be reachable
182 // without checking for exception data earlier.
183 if (!exception) {
184 BPLOG(INFO) << "No exception data.";
185 return false;
186 }
187 const MDRawExceptionStream *raw_exception_stream = exception->exception();
188 const MinidumpContext *context = exception->GetContext();
189 // This should not evaluate to true, for the same reason mentioned above.
190 if (!raw_exception_stream || !context) {
191 BPLOG(INFO) << "No exception or architecture data.";
192 return false;
193 }
194 // Check architecture and set architecture variable to corresponding flag
195 // in objdump.
196 switch (context->GetContextCPU()) {
197 case MD_CONTEXT_X86:
198 architecture = "i386";
199 break;
200 case MD_CONTEXT_AMD64:
201 architecture = "i386:x86-64";
202 break;
203 default:
204 // Unsupported architecture. Note that ARM architectures are not
205 // supported because objdump does not support ARM.
206 return false;
207 break;
208 }
209
210 // Get memory region around instruction pointer and the number of bytes
211 // before and after the instruction pointer in the memory region.
212 const uint8_t *raw_memory = memory_region->GetMemory();
213 const uint64_t base = memory_region->GetBase();
214 if (base > instruction_ptr) {
215 BPLOG(ERROR) << "Memory region base value exceeds instruction pointer.";
216 return false;
217 }
218 const uint64_t offset = instruction_ptr - base;
219 if (memory_region->GetSize() < MAX_INSTRUCTION_LEN + offset) {
220 BPLOG(ERROR) << "Not enough bytes left to guarantee complete instruction.";
ivanpe 2015/08/20 05:37:43 Please, replace with INFO
liuandrew 2015/08/20 17:11:51 Done.
221 return false;
222 }
223
224 // Convert bytes into objdump output.
225 char objdump_output_buffer[MAX_OBJDUMP_BUFFER_LEN] = {0};
226 DisassembleBytes(architecture,
227 raw_memory + offset,
228 MAX_OBJDUMP_BUFFER_LEN,
229 objdump_output_buffer);
230
231 // Set up regular expression to catch first instruction from objdump.
232 // The line with the instruction will begin with "0:".
233 regex_t regex;
234 regcomp(&regex, "0:", REG_EXTENDED | REG_NOSUB);
ivanpe 2015/08/20 05:37:44 1. You are not checking the return value. 2. Isn'
liuandrew 2015/08/20 17:11:51 Done.
235
236 // Put buffer data into stream to output line-by-line.
237 std::stringstream objdump_stream;
238 objdump_stream.str(string(objdump_output_buffer));
239 string line;
240
241 // Pipe each output line into the string until the string contains
242 // the first instruction from objdump.
243 // Loop until the line matches the regex or there are no lines left.
244 do {
245 if (!getline(objdump_stream, line)) {
246 BPLOG(ERROR) << "Objdump instructions not found";
ivanpe 2015/08/20 05:37:44 please change this to LOG
liuandrew 2015/08/20 17:11:51 Done.
247 return false;
ivanpe 2015/08/20 05:37:44 Don't you need to free regex?
liuandrew 2015/08/20 17:11:51 Removed since I'm now using string::find.
248 }
249 } while (regexec(&regex, line.c_str(), 0, NULL, 0));
250 regfree(&regex); // Free regex data.
251
252 // Convert objdump instruction line into the operation and operands.
253 string instruction = "";
254 string dest = "";
255 string src = "";
256 TokenizeObjdumpInstruction(line, &instruction, &dest, &src);
257
ivanpe 2015/08/20 05:37:44 Please use a single empty line here.
liuandrew 2015/08/20 17:11:51 Done.
258
259 // Check if the operation is a write to memory. First, the instruction
260 // must one that can write to memory. Second, the write destination
261 // must be a spot in memory rather than a register. Since there are no
262 // symbols from objdump, the destination will be enclosed by brackets.
263 if (dest.size() > 2 && dest.at(0) == '[' && dest.at(dest.size() - 1) == ']' &&
264 (!instruction.compare("mov") || !instruction.compare("inc") ||
265 !instruction.compare("dec") || !instruction.compare("and") ||
266 !instruction.compare("or") || !instruction.compare("xor") ||
267 !instruction.compare("not") || !instruction.compare("neg") ||
268 !instruction.compare("add") || !instruction.compare("sub") ||
269 !instruction.compare("shl") || !instruction.compare("shr"))) {
270 // Strip away enclosing brackets from the destination address.
271 dest = dest.substr(1, dest.size() - 2);
272 uint64_t write_address = 0;
273 CalculateAddress(dest, *context, &write_address);
274
275 // If the program crashed as a result of a write, the destination of
276 // the write must have been an address that did not permit writing.
277 // However, if the address is under 4k, due to program protections,
278 // the crash does not suggest exploitability for writes with such a
279 // low target address.
280 return write_address > 4096;
281 }
282 #endif // _WIN32
283 return false;
284 }
285
286 #ifndef _WIN32
287 bool ExploitabilityLinux::CalculateAddress(const string &address_expression,
288 const DumpContext &context,
289 uint64_t *write_address) {
290 // The destination should be the format reg+a or reg-a, where reg
291 // is a register and a is a hexadecimal constant. Although more complex
292 // expressions can make valid instructions, objdump's disassembly outputs
293 // it in this simpler format.
294 // TODO(liuandrew): Handle more complex formats, should they arise.
295
296 if (!write_address) {
297 BPLOG(ERROR) << "Null parameter.";
298 return false;
299 }
300
301 // Clone parameter into a non-const string.
302 string expression = address_expression;
303
304 // Parse out the constant that is added to the address (if it exists).
305 size_t delim = expression.find('+');
306 bool positive_add_constant = true;
307 // Check if constant is subtracted instead of added.
308 if (delim == string::npos) {
309 positive_add_constant = false;
310 delim = expression.find('-');
311 }
312 uint32_t add_constant = 0;
313 // Save constant and remove it from the expression.
314 if (delim != string::npos) {
315 if (!sscanf(expression.substr(delim + 1).c_str(), "%x", &add_constant)) {
316 BPLOG(ERROR) << "Failed to scan constant.";
317 return false;
318 }
319 expression = expression.substr(0, delim);
320 }
321
322 // Set the the write address to the corresponding register.
323 // TODO(liuandrew): Add support for partial registers, such as
324 // the rax/eax/ax/ah/al chain.
325 switch (context.GetContextCPU()) {
326 case MD_CONTEXT_X86:
327 if (!expression.compare("eax")) {
328 *write_address = context.GetContextX86()->eax;
329 } else if (!expression.compare("ebx")) {
330 *write_address = context.GetContextX86()->ebx;
331 } else if (!expression.compare("ecx")) {
332 *write_address = context.GetContextX86()->ecx;
333 } else if (!expression.compare("edx")) {
334 *write_address = context.GetContextX86()->edx;
335 } else if (!expression.compare("edi")) {
336 *write_address = context.GetContextX86()->edi;
337 } else if (!expression.compare("esi")) {
338 *write_address = context.GetContextX86()->esi;
339 } else if (!expression.compare("ebp")) {
340 *write_address = context.GetContextX86()->ebp;
341 } else if (!expression.compare("esp")) {
342 *write_address = context.GetContextX86()->esp;
343 } else if (!expression.compare("eip")) {
344 *write_address = context.GetContextX86()->eip;
345 } else {
346 BPLOG(ERROR) << "Unsupported register";
347 return false;
348 }
349 break;
350 case MD_CONTEXT_AMD64:
351 if (!expression.compare("rax")) {
352 *write_address = context.GetContextAMD64()->rax;
353 } else if (!expression.compare("rbx")) {
354 *write_address = context.GetContextAMD64()->rbx;
355 } else if (!expression.compare("rcx")) {
356 *write_address = context.GetContextAMD64()->rcx;
357 } else if (!expression.compare("rdx")) {
358 *write_address = context.GetContextAMD64()->rdx;
359 } else if (!expression.compare("rdi")) {
360 *write_address = context.GetContextAMD64()->rdi;
361 } else if (!expression.compare("rsi")) {
362 *write_address = context.GetContextAMD64()->rsi;
363 } else if (!expression.compare("rbp")) {
364 *write_address = context.GetContextAMD64()->rbp;
365 } else if (!expression.compare("rsp")) {
366 *write_address = context.GetContextAMD64()->rsp;
367 } else if (!expression.compare("rip")) {
368 *write_address = context.GetContextAMD64()->rip;
369 } else if (!expression.compare("r8")) {
370 *write_address = context.GetContextAMD64()->r8;
371 } else if (!expression.compare("r9")) {
372 *write_address = context.GetContextAMD64()->r9;
373 } else if (!expression.compare("r10")) {
374 *write_address = context.GetContextAMD64()->r10;
375 } else if (!expression.compare("r11")) {
376 *write_address = context.GetContextAMD64()->r11;
377 } else if (!expression.compare("r12")) {
378 *write_address = context.GetContextAMD64()->r12;
379 } else if (!expression.compare("r13")) {
380 *write_address = context.GetContextAMD64()->r13;
381 } else if (!expression.compare("r14")) {
382 *write_address = context.GetContextAMD64()->r14;
383 } else if (!expression.compare("r15")) {
384 *write_address = context.GetContextAMD64()->r15;
385 } else {
386 BPLOG(ERROR) << "Unsupported register";
387 return false;
388 }
389 break;
390 default:
391 // This should not occur since the same switch condition
392 // should have terminated this method.
393 return false;
394 break;
395 }
396
397 // Add or subtract constant from write address (if applicable).
398 *write_address =
399 positive_add_constant ?
400 *write_address + add_constant : *write_address - add_constant;
401
402 return true;
403 }
404
405 bool ExploitabilityLinux::TokenizeObjdumpInstruction(const string &line,
406 string *operation,
407 string *dest,
408 string *src) {
409 std::cout << "LINE: " << line << std::endl;
ivanpe 2015/08/20 05:37:44 Please, do not output to console.
liuandrew 2015/08/20 17:11:51 Done. My bad, I accidentally left it here while d
410 if (!operation || !dest || !src) {
411 BPLOG(ERROR) << "Null parameters passed.";
412 return false;
413 }
414
415 // Set all pointer values to empty strings.
416 *operation = "";
417 *dest = "";
418 *src = "";
419
420 // Tokenize the objdump line.
421 vector<string> tokens;
422 std::istringstream line_stream(line);
423 copy(std::istream_iterator<string>(line_stream),
424 std::istream_iterator<string>(),
425 std::back_inserter(tokens));
426
427 // Regex for the data in hex form. Each byte is two hex digits.
428 regex_t regex;
429 regcomp(&regex, "^[[:xdigit:]]{2}$", REG_EXTENDED | REG_NOSUB);
430
431 // Find and set the location of the operator. The operator appears
432 // directly after the chain of bytes that define the instruction. The
433 // operands will be the last token, given that the instruction has operands.
434 // If not, the operator is the last token. The loop skips the first token
435 // because the first token is the instruction number (namely "0:").
436 string operands = "";
437 for (size_t i = 1; i < tokens.size(); i++) {
438 // Check if current token no longer is in byte format.
439 if (regexec(&regex, tokens[i].c_str(), 0, NULL, 0)) {
440 // instruction = tokens[i];
441 *operation = tokens[i];
442 // If the operator is the last token, there are no operands.
443 if (i != tokens.size() - 1) {
444 operands = tokens[tokens.size() - 1];
445 }
446 break;
447 }
448 }
449 regfree(&regex);
450
451 if (operation->empty()) {
452 BPLOG(ERROR) << "Failed to parse out operation from objdump instruction.";
453 return false;
454 }
455
456 // Split operands into source and destination (if applicable).
457 if (!operands.empty()) {
458 size_t delim = operands.find(',');
459 if (delim == string::npos) {
460 *dest = operands;
461 } else {
462 *dest = operands.substr(0, delim);
463 *src = operands.substr(delim + 1);
464 }
465 }
466 return true;
467 }
468
469 bool ExploitabilityLinux::DisassembleBytes(const string &architecture,
470 const uint8_t *raw_bytes,
471 const unsigned int buffer_len,
472 char *objdump_output_buffer) {
473 int pipe_fd[2];
474
475 if (!raw_bytes || !objdump_output_buffer) {
476 BPLOG(ERROR) << "Bad input parameters.";
477 return false;
478 }
479
480 // Write raw bytes around instruction pointer to a temporary file to
481 // pass as an argument to objdump.
482 char raw_bytes_tmpfile[] = "/tmp/breakpad_mem_region-raw_bytes-XXXXXX";
483 int raw_bytes_fd = mkstemp(raw_bytes_tmpfile);
484 if (raw_bytes_fd < 0) {
485 BPLOG(ERROR) << "Failed to create tempfile.";
486 unlink(raw_bytes_tmpfile);
487 return false;
488 }
489 if (write(raw_bytes_fd, raw_bytes, MAX_INSTRUCTION_LEN)
490 != MAX_INSTRUCTION_LEN) {
491 BPLOG(ERROR) << "Writing of raw bytes failed.";
492 unlink(raw_bytes_tmpfile);
493 return false;
494 }
495
496 // Open pipe between STDOUT and the objdump output buffer.
497 if (pipe(pipe_fd) < 0) {
498 BPLOG(ERROR) << "Failed to pipe.";
499 return false;
500 }
501
502 // Fork process to call objdump.
503 if ((child_pid = fork()) < 0) {
504 BPLOG(ERROR) << "Forking failed.";
505 return false;
506 }
507
508 if (child_pid) { // Parent code.
509 close(pipe_fd[1]);
510
511 // Set alarm to kill child process after one second in case it hangs.
512 signal(SIGALRM, (void (*)(int))kill_child);
513 alarm(1);
514
515 // Read piped output from objdump.
516 ssize_t bytes_read = read(pipe_fd[0],
517 objdump_output_buffer,
518 buffer_len);
519 int status;
520 wait(&status); // Wait for child process to run objdump.
521 alarm(0); // Disable alarm.
522 if (WEXITSTATUS(status)) {
523 BPLOG(ERROR) << "Child process encountered error.";
524 unlink(raw_bytes_tmpfile);
525 return false;
526 }
527 if (bytes_read < 0) {
528 BPLOG(ERROR) << "Failed to read objdump output.";
529 unlink(raw_bytes_tmpfile);
530 return false;
531 }
532 close(pipe_fd[0]);
533 } else { // Child code.
534 close(pipe_fd[0]);
535 dup2(pipe_fd[1], STDOUT_FILENO); // Send objdump output across pipe.
536 // Exec objdump.
537 execlp("objdump", "objdump", "-D", "-b", "binary", "-M", "intel",
538 "-m", architecture.c_str(), raw_bytes_tmpfile, NULL);
539 BPLOG(ERROR) << "Exec failed.";
540 exit(EX_OSERR);
541 }
542 unlink(raw_bytes_tmpfile);
543 return true;
544 }
545 #endif // _WIN32
546
137 bool ExploitabilityLinux::StackPointerOffStack(uint64_t stack_ptr) { 547 bool ExploitabilityLinux::StackPointerOffStack(uint64_t stack_ptr) {
138 MinidumpLinuxMapsList *linux_maps_list = dump_->GetLinuxMapsList(); 548 MinidumpLinuxMapsList *linux_maps_list = dump_->GetLinuxMapsList();
139 // Inconclusive if there are no mappings available. 549 // Inconclusive if there are no mappings available.
140 if (!linux_maps_list) { 550 if (!linux_maps_list) {
141 return false; 551 return false;
142 } 552 }
143 const MinidumpLinuxMaps *linux_maps = 553 const MinidumpLinuxMaps *linux_maps =
144 linux_maps_list->GetLinuxMapsForAddress(stack_ptr); 554 linux_maps_list->GetLinuxMapsForAddress(stack_ptr);
145 // Checks if the stack pointer maps to a valid mapping and if the mapping 555 // Checks if the stack pointer maps to a valid mapping and if the mapping
146 // is not the stack. If the mapping has no name, it is inconclusive whether 556 // is not the stack. If the mapping has no name, it is inconclusive whether
(...skipping 70 matching lines...)
217 case MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED: 627 case MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED:
218 return true; 628 return true;
219 break; 629 break;
220 default: 630 default:
221 return false; 631 return false;
222 break; 632 break;
223 } 633 }
224 } 634 }
225 635
226 } // namespace google_breakpad 636 } // namespace google_breakpad
OLDNEW
« no previous file with comments | « src/processor/exploitability_linux.h ('k') | src/processor/exploitability_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine