Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(198)

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. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
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...) Expand all
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 #ifndef _WIN32
40 #include <regex.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44
45 #include <sstream>
46 #include <iterator>
47 #endif // _WIN32
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
45 namespace { 55 namespace {
46 56
47 // This function in libc is called if the program was compiled with 57 // This function in libc is called if the program was compiled with
48 // -fstack-protector and a function's stack canary changes. 58 // -fstack-protector and a function's stack canary changes.
49 const char kStackCheckFailureFunction[] = "__stack_chk_fail"; 59 const char kStackCheckFailureFunction[] = "__stack_chk_fail";
50 60
51 // 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
52 // -D_FORTIFY_SOURCE=2, a function like strcpy() is called, and the runtime 62 // -D_FORTIFY_SOURCE=2, a function like strcpy() is called, and the runtime
53 // can determine that the call would overflow the target buffer. 63 // can determine that the call would overflow the target buffer.
54 const char kBoundsCheckFailureFunction[] = "__chk_fail"; 64 const char kBoundsCheckFailureFunction[] = "__chk_fail";
55 65
66 #ifndef _WIN32
67 const unsigned int MAX_INSTRUCTION_LEN = 15;
68 const unsigned int MAX_OBJDUMP_BUFFER_LEN = 4096;
69 #endif // _WIN32
70
56 } // namespace 71 } // namespace
57 72
58 namespace google_breakpad { 73 namespace google_breakpad {
59 74
60 ExploitabilityLinux::ExploitabilityLinux(Minidump *dump, 75 ExploitabilityLinux::ExploitabilityLinux(Minidump *dump,
61 ProcessState *process_state) 76 ProcessState *process_state)
62 : Exploitability(dump, process_state) { } 77 : Exploitability(dump, process_state),
78 enable_objdump_(false) { }
79
80 ExploitabilityLinux::ExploitabilityLinux(Minidump *dump,
81 ProcessState *process_state,
82 bool enable_objdump)
83 : Exploitability(dump, process_state),
84 enable_objdump_(enable_objdump) { }
85
63 86
64 ExploitabilityRating ExploitabilityLinux::CheckPlatformExploitability() { 87 ExploitabilityRating ExploitabilityLinux::CheckPlatformExploitability() {
65 // Check the crashing thread for functions suggesting a buffer overflow or 88 // Check the crashing thread for functions suggesting a buffer overflow or
66 // stack smash. 89 // stack smash.
67 if (process_state_->requesting_thread() != -1) { 90 if (process_state_->requesting_thread() != -1) {
68 CallStack* crashing_thread = 91 CallStack* crashing_thread =
69 process_state_->threads()->at(process_state_->requesting_thread()); 92 process_state_->threads()->at(process_state_->requesting_thread());
70 const vector<StackFrame*>& crashing_thread_frames = 93 const vector<StackFrame*>& crashing_thread_frames =
71 *crashing_thread->frames(); 94 *crashing_thread->frames();
72 for (size_t i = 0; i < crashing_thread_frames.size(); ++i) { 95 for (size_t i = 0; i < crashing_thread_frames.size(); ++i) {
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
115 BPLOG(INFO) << "Failed to retrieve instruction pointer."; 138 BPLOG(INFO) << "Failed to retrieve instruction pointer.";
116 return EXPLOITABILITY_ERR_PROCESSING; 139 return EXPLOITABILITY_ERR_PROCESSING;
117 } 140 }
118 141
119 // Getting the stack pointer. 142 // Getting the stack pointer.
120 if (!context->GetStackPointer(&stack_ptr)) { 143 if (!context->GetStackPointer(&stack_ptr)) {
121 BPLOG(INFO) << "Failed to retrieve stack pointer."; 144 BPLOG(INFO) << "Failed to retrieve stack pointer.";
122 return EXPLOITABILITY_ERR_PROCESSING; 145 return EXPLOITABILITY_ERR_PROCESSING;
123 } 146 }
124 147
125 // Checking for the instruction pointer in a valid instruction region. 148 // Checking for the instruction pointer in a valid instruction region,
149 // a misplaced stack pointer, and an executable stack or heap.
126 if (!this->InstructionPointerInCode(instruction_ptr) || 150 if (!this->InstructionPointerInCode(instruction_ptr) ||
127 this->StackPointerOffStack(stack_ptr) || 151 this->StackPointerOffStack(stack_ptr) ||
128 this->ExecutableStackOrHeap()) { 152 this->ExecutableStackOrHeap()) {
129 return EXPLOITABILITY_HIGH; 153 return EXPLOITABILITY_HIGH;
130 } 154 }
131 155
156 // Check for write to read only memory or invalid memory, shelling out
157 // to objdump is enabled.
158 if (enable_objdump_ && this->EndedOnIllegalWrite(instruction_ptr)) {
159 return EXPLOITABILITY_HIGH;
160 }
161
132 // There was no strong evidence suggesting exploitability, but the minidump 162 // There was no strong evidence suggesting exploitability, but the minidump
133 // does not appear totally benign either. 163 // does not appear totally benign either.
134 return EXPLOITABILITY_INTERESTING; 164 return EXPLOITABILITY_INTERESTING;
135 } 165 }
136 166
167 bool ExploitabilityLinux::EndedOnIllegalWrite(uint64_t instruction_ptr) {
168 #ifdef _WIN32
169 BPLOG(INFO) << "MinGW does not support fork and exec. Terminating method.";
170 #else
171 // Get memory region containing instruction pointer.
172 MinidumpMemoryList *memory_list = dump_->GetMemoryList();
173 MinidumpMemoryRegion *memory_region =
174 memory_list ?
175 memory_list->GetMemoryRegionForAddress(instruction_ptr) : NULL;
176 if (!memory_region) {
177 BPLOG(INFO) << "No memory region around instruction pointer.";
178 return false;
179 }
180
181 // Get exception data to find architecture.
182 string architecture = "";
183 MinidumpException *exception = dump_->GetException();
184 // This should never evaluate to true, since this should not be reachable
185 // without checking for exception data earlier.
186 if (!exception) {
187 BPLOG(INFO) << "No exception data.";
188 return false;
189 }
190 const MDRawExceptionStream *raw_exception_stream = exception->exception();
191 const MinidumpContext *context = exception->GetContext();
192 // This should not evaluate to true, for the same reason mentioned above.
193 if (!raw_exception_stream || !context) {
194 BPLOG(INFO) << "No exception or architecture data.";
195 return false;
196 }
197 // Check architecture and set architecture variable to corresponding flag
198 // in objdump.
199 switch (context->GetContextCPU()) {
200 case MD_CONTEXT_X86:
201 architecture = "i386";
202 break;
203 case MD_CONTEXT_AMD64:
204 architecture = "i386:x86-64";
205 break;
206 default:
207 // Unsupported architecture. Note that ARM architectures are not
208 // supported because objdump does not support ARM.
209 return false;
210 break;
211 }
212
213 // Get memory region around instruction pointer and the number of bytes
214 // before and after the instruction pointer in the memory region.
215 const uint8_t *raw_memory = memory_region->GetMemory();
216 const uint64_t base = memory_region->GetBase();
217 if (base > instruction_ptr) {
218 BPLOG(ERROR) << "Memory region base value exceeds instruction pointer.";
219 return false;
220 }
221 const uint64_t offset = instruction_ptr - base;
222 if (memory_region->GetSize() < MAX_INSTRUCTION_LEN + offset) {
223 BPLOG(INFO) << "Not enough bytes left to guarantee complete instruction.";
224 return false;
225 }
226
227 // Convert bytes into objdump output.
228 char objdump_output_buffer[MAX_OBJDUMP_BUFFER_LEN] = {0};
229 DisassembleBytes(architecture,
230 raw_memory + offset,
231 MAX_OBJDUMP_BUFFER_LEN,
232 objdump_output_buffer);
233
234 // Put buffer data into stream to output line-by-line.
235 std::stringstream objdump_stream;
236 objdump_stream.str(string(objdump_output_buffer));
237 string line;
238
239 // Pipe each output line into the string until the string contains
240 // the first instruction from objdump.
241 // Loop until the line matches the regex or there are no lines left.
ivanpe 2015/08/20 21:17:51 Please, update the comment.
liuandrew 2015/08/20 21:59:57 Done.
242 do {
243 if (!getline(objdump_stream, line)) {
244 BPLOG(INFO) << "Objdump instructions not found";
245 return false;
246 }
247 } while (line.find("0:") == string::npos);
248 // This first instruction contains the above substring.
249
250 // Convert objdump instruction line into the operation and operands.
251 string instruction = "";
252 string dest = "";
253 string src = "";
254 TokenizeObjdumpInstruction(line, &instruction, &dest, &src);
255
256 // Check if the operation is a write to memory. First, the instruction
257 // must one that can write to memory. Second, the write destination
258 // must be a spot in memory rather than a register. Since there are no
259 // symbols from objdump, the destination will be enclosed by brackets.
260 if (dest.size() > 2 && dest.at(0) == '[' && dest.at(dest.size() - 1) == ']' &&
261 (!instruction.compare("mov") || !instruction.compare("inc") ||
262 !instruction.compare("dec") || !instruction.compare("and") ||
263 !instruction.compare("or") || !instruction.compare("xor") ||
264 !instruction.compare("not") || !instruction.compare("neg") ||
265 !instruction.compare("add") || !instruction.compare("sub") ||
266 !instruction.compare("shl") || !instruction.compare("shr"))) {
267 // Strip away enclosing brackets from the destination address.
268 dest = dest.substr(1, dest.size() - 2);
269 uint64_t write_address = 0;
270 CalculateAddress(dest, *context, &write_address);
271
272 // If the program crashed as a result of a write, the destination of
273 // the write must have been an address that did not permit writing.
274 // However, if the address is under 4k, due to program protections,
275 // the crash does not suggest exploitability for writes with such a
276 // low target address.
277 return write_address > 4096;
278 }
279 #endif // _WIN32
280 return false;
281 }
282
283 #ifndef _WIN32
284 bool ExploitabilityLinux::CalculateAddress(const string &address_expression,
285 const DumpContext &context,
286 uint64_t *write_address) {
287 // The destination should be the format reg+a or reg-a, where reg
288 // is a register and a is a hexadecimal constant. Although more complex
289 // expressions can make valid instructions, objdump's disassembly outputs
290 // it in this simpler format.
291 // TODO(liuandrew): Handle more complex formats, should they arise.
292
293 if (!write_address) {
294 BPLOG(ERROR) << "Null parameter.";
295 return false;
296 }
297
298 // Clone parameter into a non-const string.
299 string expression = address_expression;
300
301 // Parse out the constant that is added to the address (if it exists).
302 size_t delim = expression.find('+');
303 bool positive_add_constant = true;
304 // Check if constant is subtracted instead of added.
305 if (delim == string::npos) {
306 positive_add_constant = false;
307 delim = expression.find('-');
308 }
309 uint32_t add_constant = 0;
310 // Save constant and remove it from the expression.
311 if (delim != string::npos) {
312 if (!sscanf(expression.substr(delim + 1).c_str(), "%x", &add_constant)) {
313 BPLOG(ERROR) << "Failed to scan constant.";
314 return false;
315 }
316 expression = expression.substr(0, delim);
317 }
318
319 // Set the the write address to the corresponding register.
320 // TODO(liuandrew): Add support for partial registers, such as
321 // the rax/eax/ax/ah/al chain.
322 switch (context.GetContextCPU()) {
323 case MD_CONTEXT_X86:
324 if (!expression.compare("eax")) {
325 *write_address = context.GetContextX86()->eax;
326 } else if (!expression.compare("ebx")) {
327 *write_address = context.GetContextX86()->ebx;
328 } else if (!expression.compare("ecx")) {
329 *write_address = context.GetContextX86()->ecx;
330 } else if (!expression.compare("edx")) {
331 *write_address = context.GetContextX86()->edx;
332 } else if (!expression.compare("edi")) {
333 *write_address = context.GetContextX86()->edi;
334 } else if (!expression.compare("esi")) {
335 *write_address = context.GetContextX86()->esi;
336 } else if (!expression.compare("ebp")) {
337 *write_address = context.GetContextX86()->ebp;
338 } else if (!expression.compare("esp")) {
339 *write_address = context.GetContextX86()->esp;
340 } else if (!expression.compare("eip")) {
341 *write_address = context.GetContextX86()->eip;
342 } else {
343 BPLOG(ERROR) << "Unsupported register";
344 return false;
345 }
346 break;
347 case MD_CONTEXT_AMD64:
348 if (!expression.compare("rax")) {
349 *write_address = context.GetContextAMD64()->rax;
350 } else if (!expression.compare("rbx")) {
351 *write_address = context.GetContextAMD64()->rbx;
352 } else if (!expression.compare("rcx")) {
353 *write_address = context.GetContextAMD64()->rcx;
354 } else if (!expression.compare("rdx")) {
355 *write_address = context.GetContextAMD64()->rdx;
356 } else if (!expression.compare("rdi")) {
357 *write_address = context.GetContextAMD64()->rdi;
358 } else if (!expression.compare("rsi")) {
359 *write_address = context.GetContextAMD64()->rsi;
360 } else if (!expression.compare("rbp")) {
361 *write_address = context.GetContextAMD64()->rbp;
362 } else if (!expression.compare("rsp")) {
363 *write_address = context.GetContextAMD64()->rsp;
364 } else if (!expression.compare("rip")) {
365 *write_address = context.GetContextAMD64()->rip;
366 } else if (!expression.compare("r8")) {
367 *write_address = context.GetContextAMD64()->r8;
368 } else if (!expression.compare("r9")) {
369 *write_address = context.GetContextAMD64()->r9;
370 } else if (!expression.compare("r10")) {
371 *write_address = context.GetContextAMD64()->r10;
372 } else if (!expression.compare("r11")) {
373 *write_address = context.GetContextAMD64()->r11;
374 } else if (!expression.compare("r12")) {
375 *write_address = context.GetContextAMD64()->r12;
376 } else if (!expression.compare("r13")) {
377 *write_address = context.GetContextAMD64()->r13;
378 } else if (!expression.compare("r14")) {
379 *write_address = context.GetContextAMD64()->r14;
380 } else if (!expression.compare("r15")) {
381 *write_address = context.GetContextAMD64()->r15;
382 } else {
383 BPLOG(ERROR) << "Unsupported register";
384 return false;
385 }
386 break;
387 default:
388 // This should not occur since the same switch condition
389 // should have terminated this method.
390 return false;
391 break;
392 }
393
394 // Add or subtract constant from write address (if applicable).
395 *write_address =
396 positive_add_constant ?
397 *write_address + add_constant : *write_address - add_constant;
398
399 return true;
400 }
401
402 bool ExploitabilityLinux::TokenizeObjdumpInstruction(const string &line,
403 string *operation,
404 string *dest,
405 string *src) {
406 if (!operation || !dest || !src) {
407 BPLOG(ERROR) << "Null parameters passed.";
408 return false;
409 }
410
411 // Set all pointer values to empty strings.
412 *operation = "";
413 *dest = "";
414 *src = "";
415
416 // Tokenize the objdump line.
417 vector<string> tokens;
418 std::istringstream line_stream(line);
419 copy(std::istream_iterator<string>(line_stream),
420 std::istream_iterator<string>(),
421 std::back_inserter(tokens));
422
423 // Regex for the data in hex form. Each byte is two hex digits.
424 regex_t regex;
425 regcomp(&regex, "^[[:xdigit:]]{2}$", REG_EXTENDED | REG_NOSUB);
426
427 // Find and set the location of the operator. The operator appears
428 // directly after the chain of bytes that define the instruction. The
429 // operands will be the last token, given that the instruction has operands.
430 // If not, the operator is the last token. The loop skips the first token
431 // because the first token is the instruction number (namely "0:").
432 string operands = "";
433 for (size_t i = 1; i < tokens.size(); i++) {
434 // Check if current token no longer is in byte format.
435 if (regexec(&regex, tokens[i].c_str(), 0, NULL, 0)) {
436 // instruction = tokens[i];
437 *operation = tokens[i];
438 // If the operator is the last token, there are no operands.
439 if (i != tokens.size() - 1) {
440 operands = tokens[tokens.size() - 1];
441 }
442 break;
443 }
444 }
445 regfree(&regex);
446
447 if (operation->empty()) {
448 BPLOG(ERROR) << "Failed to parse out operation from objdump instruction.";
449 return false;
450 }
451
452 // Split operands into source and destination (if applicable).
453 if (!operands.empty()) {
454 size_t delim = operands.find(',');
455 if (delim == string::npos) {
456 *dest = operands;
457 } else {
458 *dest = operands.substr(0, delim);
459 *src = operands.substr(delim + 1);
460 }
461 }
462 return true;
463 }
464
465 bool ExploitabilityLinux::DisassembleBytes(const string &architecture,
466 const uint8_t *raw_bytes,
467 const unsigned int buffer_len,
468 char *objdump_output_buffer) {
469 if (!raw_bytes || !objdump_output_buffer) {
470 BPLOG(ERROR) << "Bad input parameters.";
471 return false;
472 }
473
474 // Write raw bytes around instruction pointer to a temporary file to
475 // pass as an argument to objdump.
476 char raw_bytes_tmpfile[] = "/tmp/breakpad_mem_region-raw_bytes-XXXXXX";
477 int raw_bytes_fd = mkstemp(raw_bytes_tmpfile);
478 if (raw_bytes_fd < 0) {
479 BPLOG(ERROR) << "Failed to create tempfile.";
480 unlink(raw_bytes_tmpfile);
481 return false;
482 }
483 if (write(raw_bytes_fd, raw_bytes, MAX_INSTRUCTION_LEN)
484 != MAX_INSTRUCTION_LEN) {
485 BPLOG(ERROR) << "Writing of raw bytes failed.";
486 unlink(raw_bytes_tmpfile);
487 return false;
488 }
489
490 char cmd[1024] = {0};
491 snprintf(cmd,
492 1024,
493 "objdump -D -b binary -M intel -m %s %s",
494 architecture.c_str(),
495 raw_bytes_tmpfile);
496 FILE *objdump_fp = popen(cmd, "r");
497 if (!objdump_fp) {
498 fclose(objdump_fp);
499 unlink(raw_bytes_tmpfile);
500 BPLOG(ERROR) << "Failed to call objdump.";
501 return false;
502 }
503 if (fread(objdump_output_buffer, 1, buffer_len, objdump_fp) <= 0) {
504 fclose(objdump_fp);
505 unlink(raw_bytes_tmpfile);
506 BPLOG(ERROR) << "Failed to read objdump output.";
507 return false;
508 }
509 fclose(objdump_fp);
510 unlink(raw_bytes_tmpfile);
511 return true;
512 }
513 #endif // _WIN32
514
137 bool ExploitabilityLinux::StackPointerOffStack(uint64_t stack_ptr) { 515 bool ExploitabilityLinux::StackPointerOffStack(uint64_t stack_ptr) {
138 MinidumpLinuxMapsList *linux_maps_list = dump_->GetLinuxMapsList(); 516 MinidumpLinuxMapsList *linux_maps_list = dump_->GetLinuxMapsList();
139 // Inconclusive if there are no mappings available. 517 // Inconclusive if there are no mappings available.
140 if (!linux_maps_list) { 518 if (!linux_maps_list) {
141 return false; 519 return false;
142 } 520 }
143 const MinidumpLinuxMaps *linux_maps = 521 const MinidumpLinuxMaps *linux_maps =
144 linux_maps_list->GetLinuxMapsForAddress(stack_ptr); 522 linux_maps_list->GetLinuxMapsForAddress(stack_ptr);
145 // Checks if the stack pointer maps to a valid mapping and if the mapping 523 // 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 524 // is not the stack. If the mapping has no name, it is inconclusive whether
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after
217 case MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED: 595 case MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED:
218 return true; 596 return true;
219 break; 597 break;
220 default: 598 default:
221 return false; 599 return false;
222 break; 600 break;
223 } 601 }
224 } 602 }
225 603
226 } // namespace google_breakpad 604 } // namespace google_breakpad
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698