Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 Loading... | |
| 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"; |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 108 BPLOG(INFO) << "No exception context."; | 122 BPLOG(INFO) << "No exception context."; |
| 109 return EXPLOITABILITY_ERR_PROCESSING; | 123 return EXPLOITABILITY_ERR_PROCESSING; |
| 110 } | 124 } |
| 111 | 125 |
| 112 // Getting the instruction pointer. | 126 // Getting the instruction pointer. |
| 113 if (!context->GetInstructionPointer(&instruction_ptr)) { | 127 if (!context->GetInstructionPointer(&instruction_ptr)) { |
| 114 BPLOG(INFO) << "Failed to retrieve instruction pointer."; | 128 BPLOG(INFO) << "Failed to retrieve instruction pointer."; |
| 115 return EXPLOITABILITY_ERR_PROCESSING; | 129 return EXPLOITABILITY_ERR_PROCESSING; |
| 116 } | 130 } |
| 117 | 131 |
| 118 // Checking for the instruction pointer in a valid instruction region. | 132 // Checking for the instruction pointer in a valid instruction region |
| 119 if (!this->InstructionPointerInCode(instruction_ptr)) { | 133 // or if the crash resulted during an illegal write. |
| 134 if (!this->InstructionPointerInCode(instruction_ptr) || | |
| 135 this->EndedOnIllegalWrite(instruction_ptr)) { | |
| 120 return EXPLOITABILITY_HIGH; | 136 return EXPLOITABILITY_HIGH; |
| 121 } | 137 } |
| 122 | 138 |
| 123 // There was no strong evidence suggesting exploitability, but the minidump | 139 // There was no strong evidence suggesting exploitability, but the minidump |
| 124 // does not appear totally benign either. | 140 // does not appear totally benign either. |
| 125 return EXPLOITABILITY_INTERESTING; | 141 return EXPLOITABILITY_INTERESTING; |
| 126 } | 142 } |
| 127 | 143 |
| 144 bool ExploitabilityLinux::EndedOnIllegalWrite(uint64_t instruction_ptr) { | |
| 145 // Get memory region containing instruction pointer. | |
| 146 MinidumpMemoryList *memory_list = dump_->GetMemoryList(); | |
| 147 MinidumpMemoryRegion *memory_region = | |
| 148 memory_list ? | |
| 149 memory_list->GetMemoryRegionForAddress(instruction_ptr) : NULL; | |
| 150 if (memory_region == NULL) { | |
| 151 BPLOG(ERROR) << "No memory region around instruction pointer."; | |
| 152 return false; | |
| 153 } | |
| 154 | |
| 155 // Get exception data to find architecture. | |
| 156 string architecture = ""; | |
| 157 MinidumpException *exception = dump_->GetException(); | |
| 158 // This should never evaluate to true, since this should not be reachable | |
| 159 // without checking for exception data earlier. | |
| 160 if (exception == NULL) { | |
| 161 BPLOG(INFO) << "No exception data."; | |
| 162 return 0; | |
|
ivanpe
2015/08/10 19:58:04
The return value type is bool
liuandrew
2015/08/10 22:00:40
Done.
| |
| 163 } | |
| 164 const MDRawExceptionStream *raw_exception_stream = exception->exception(); | |
| 165 const MinidumpContext *context = exception->GetContext(); | |
| 166 // This should not evaluate to true, for the same reason mentioned above. | |
| 167 if (raw_exception_stream == NULL || context == NULL) { | |
| 168 BPLOG(INFO) << "No exception or architecture data."; | |
| 169 return false; | |
| 170 } | |
| 171 // Check architecture and set architecture variable to corresponding flag | |
| 172 // in objdump. | |
| 173 switch (context->GetContextCPU()) { | |
| 174 case MD_CONTEXT_X86: | |
| 175 architecture = "i386"; | |
| 176 break; | |
| 177 case MD_CONTEXT_AMD64: | |
| 178 architecture = "i386:x86-64"; | |
| 179 break; | |
| 180 default: | |
| 181 // Unsupported architecture. Note that ARM architectures are not | |
| 182 // supported because objdump does not support ARM. | |
| 183 return false; | |
| 184 break; | |
| 185 } | |
| 186 | |
| 187 // Get memory region around instruction pointer and the number of bytes | |
| 188 // before and after the instruction pointer in the memory region. | |
| 189 const uint8_t *raw_memory = memory_region->GetMemory(); | |
| 190 const uint32_t offset = instruction_ptr - memory_region->GetBase(); | |
|
ivanpe
2015/08/10 19:58:04
Both instruction_ptr and memory_region->GetBase()
liuandrew
2015/08/10 22:00:40
Done.
| |
| 191 if (memory_region->GetSize() - offset < MAX_INSTRUCTION_LEN) { | |
| 192 BPLOG(ERROR) << "Not enough bytes left to guarantee complete instruction"; | |
| 193 return false; | |
| 194 } | |
| 195 | |
| 196 // Write raw bytes around instruction pointer to a temporary file to | |
| 197 // pass as an argument to objdump. | |
| 198 char raw_bytes_tmpfile[] = "/tmp/breakpad_mem_region-raw_bytes-XXXXXX"; | |
|
ivanpe
2015/08/10 19:58:04
const char?
liuandrew
2015/08/10 22:00:40
No, it should not be const. Calling mkstemp modifi
| |
| 199 int raw_bytes_fd = mkstemp(raw_bytes_tmpfile); | |
|
ivanpe
2015/08/10 19:58:04
Please, check return value
liuandrew
2015/08/10 22:00:40
Done.
| |
| 200 if (write(raw_bytes_fd, raw_memory + offset, MAX_INSTRUCTION_LEN) | |
| 201 != MAX_INSTRUCTION_LEN) { | |
| 202 BPLOG(ERROR) << "Writing of raw bytes failed."; | |
| 203 } | |
| 204 | |
| 205 char objdump_output_buffer[MAX_OBJDUMP_BUFFER_LEN]; | |
| 206 int pipe_fd[2]; | |
| 207 | |
| 208 // Open pipe between STDOUT and the objdump output buffer. | |
| 209 if (pipe(pipe_fd) < 0) { | |
| 210 BPLOG(ERROR) << "Failed to pipe."; | |
| 211 unlink(raw_bytes_tmpfile); | |
| 212 return false; | |
| 213 } | |
| 214 | |
| 215 // Fork process to call objdump. | |
| 216 pid_t child_pid; | |
| 217 if ((child_pid = fork()) < 0) { | |
| 218 BPLOG(ERROR) << "Forking failed."; | |
| 219 unlink(raw_bytes_tmpfile); | |
| 220 return false; | |
| 221 } | |
| 222 | |
| 223 if (child_pid) { // Parent code. | |
| 224 close(pipe_fd[1]); | |
| 225 // Read piped output from objdump. | |
| 226 ssize_t bytes_read = read(pipe_fd[0], | |
| 227 objdump_output_buffer, | |
| 228 MAX_OBJDUMP_BUFFER_LEN); | |
| 229 wait(NULL); // Wait for child process to run objdump. | |
| 230 unlink(raw_bytes_tmpfile); | |
| 231 if (bytes_read < 0) { | |
| 232 BPLOG(ERROR) << "Failed to read objdump output."; | |
| 233 return false; | |
| 234 } | |
| 235 close(pipe_fd[0]); | |
| 236 } else { // Child code. | |
| 237 close(pipe_fd[0]); | |
| 238 dup2(pipe_fd[1], STDOUT_FILENO); // Send objdump output across pipe. | |
| 239 // Exec objdump. | |
| 240 execlp("objdump", "objdump", "-D", "-b", "binary", "-M", "intel", | |
|
ivanpe
2015/08/10 19:58:04
This function seems to have lots of portability is
liuandrew
2015/08/10 22:00:40
There looked to be no good disassembler libraries
ivanpe
2015/08/10 23:27:58
Fork and pipe will have portability issues if brea
liuandrew
2015/08/11 22:55:38
I tested objdump, and it works in borg.
If I set
ivanpe
2015/08/11 23:29:43
I guess that if someone wants to compile this on a
liuandrew
2015/08/17 21:37:36
I set an alarm to kill the child process after one
| |
| 241 "-m", architecture.c_str(), raw_bytes_tmpfile, NULL); | |
| 242 BPLOG(ERROR) << "Exec failed."; | |
| 243 exit(EX_OSERR); | |
| 244 } | |
| 245 | |
| 246 // Set up regular expression to catch first instruction from objdump. | |
| 247 // The line with the instruction will begin with "0:". | |
| 248 regex_t regex; | |
| 249 regcomp(®ex, "0:", REG_EXTENDED | REG_NOSUB); | |
| 250 | |
| 251 // Put buffer data into stream to output line-by-line. | |
| 252 std::stringstream objdump_stream; | |
| 253 objdump_stream.str(string(objdump_output_buffer)); | |
| 254 string line; | |
| 255 | |
| 256 // Pipe each output line into the string until the string contains | |
| 257 // the first instruction from objdump. | |
| 258 do { | |
| 259 if (!getline(objdump_stream, line)) { | |
| 260 BPLOG(ERROR) << "Objdump instructions not found"; | |
| 261 return false; | |
| 262 } | |
| 263 } while (regexec(®ex, line.c_str(), 0, NULL, 0)); | |
| 264 regfree(®ex); // Free regex data. | |
| 265 | |
| 266 // Tokenize the objdump line. | |
| 267 vector<string> tokens; | |
| 268 std::istringstream line_stream(line); | |
| 269 copy(std::istream_iterator<string>(line_stream), | |
| 270 std::istream_iterator<string>(), | |
| 271 std::back_inserter(tokens)); | |
| 272 | |
| 273 // Parse out the operator and operands from the instruction. | |
| 274 string instruction = ""; | |
| 275 string operands = ""; | |
| 276 | |
| 277 // Regex for the data in hex form. Each byte is two hex digits. | |
| 278 regcomp(®ex, "^[[:xdigit:]]{2}$", REG_EXTENDED | REG_NOSUB); | |
| 279 | |
| 280 // Find and set the location of the operator. The operator appears | |
| 281 // directly after the chain of bytes that define the instruction. The | |
| 282 // operands will be the last token, given that the instruction has operands. | |
| 283 // If not, the operator is the last token. The loop skips the first token | |
| 284 // because the first token is the instruction number (namely "0:"). | |
| 285 for (size_t i = 1; i < tokens.size(); i++) { | |
| 286 // Check if current token no longer is in byte format. | |
| 287 if (regexec(®ex, tokens[i].c_str(), 0, NULL, 0)) { | |
| 288 instruction = tokens[i]; | |
| 289 // If the operator is the last token, there are no operands. | |
| 290 if (i != tokens.size() - 1) { | |
| 291 operands = tokens[tokens.size() - 1]; | |
| 292 } | |
| 293 break; | |
| 294 } | |
| 295 } | |
| 296 regfree(®ex); | |
| 297 | |
| 298 if (instruction.empty()) { | |
| 299 BPLOG(ERROR) << "Failed to parse out operation from objdump instruction."; | |
| 300 return false; | |
| 301 } | |
| 302 | |
| 303 // Split operands into source and destination (if applicable). | |
| 304 string dest = ""; | |
| 305 string src = ""; | |
| 306 if (!operands.empty()) { | |
| 307 size_t delim = operands.find(','); | |
| 308 if (delim == string::npos) { | |
| 309 dest = operands; | |
| 310 } else { | |
| 311 dest = operands.substr(0, delim); | |
| 312 src = operands.substr(delim + 1); | |
| 313 } | |
| 314 } | |
| 315 | |
| 316 // Check if the operation is a write to memory. First, the instruction | |
| 317 // must one that can write to memory. Second, the write destination | |
| 318 // must be a spot in memory rather than a register. Since there are no | |
| 319 // symbols from objdump, the destination will be enclosed by brackets. | |
| 320 if (dest.at(0) == '[' && dest.at(dest.size() - 1) == ']' && | |
| 321 (!instruction.compare("mov") || !instruction.compare("inc") || | |
| 322 !instruction.compare("dec") || !instruction.compare("and") || | |
| 323 !instruction.compare("or") || !instruction.compare("xor") || | |
| 324 !instruction.compare("not") || !instruction.compare("neg") || | |
| 325 !instruction.compare("add") || !instruction.compare("sub") || | |
| 326 !instruction.compare("shl") || !instruction.compare("shr"))) { | |
| 327 // Strip away enclosing brackets from the destination address. | |
| 328 dest = dest.substr(1, dest.size() - 2); | |
| 329 | |
| 330 // The destination should be the format [reg+a] or [reg-a], where reg | |
| 331 // is a register and a is a hexadecimal constant. Although more complex | |
| 332 // expressions can make valid instructions, objdump's disassembly outputs | |
| 333 // it in this simpler format. | |
| 334 | |
| 335 // Parse out the constant that is added to the address (if it exists). | |
| 336 size_t delim = dest.find('+'); | |
| 337 bool positive_add_constant = true; | |
| 338 // Check if constant is subtracted instead of added. | |
| 339 if (delim == string::npos) { | |
| 340 positive_add_constant = false; | |
| 341 delim = dest.find('-'); | |
| 342 } | |
| 343 uint32_t add_constant = 0; | |
| 344 // Save constant and remove it from the expression. | |
| 345 if (delim != string::npos) { | |
| 346 sscanf(dest.substr(delim + 1).c_str(), "%x", &add_constant); | |
| 347 dest = dest.substr(0, delim); | |
| 348 } | |
| 349 | |
| 350 // Calculate and set the address that is the target of the write operation. | |
| 351 uint64_t write_address = 0; | |
| 352 | |
| 353 // Set the the write address to the corresponding register. | |
| 354 // TODO(liuandrew): Add support for partial registers, such as | |
| 355 // the rax/eax/ax/ah/al chain. | |
| 356 switch (context->GetContextCPU()) { | |
| 357 case MD_CONTEXT_X86: | |
| 358 if (!dest.compare("eax")) { | |
| 359 write_address = context->GetContextX86()->eax; | |
| 360 } else if (!dest.compare("ebx")) { | |
| 361 write_address = context->GetContextX86()->ebx; | |
| 362 } else if (!dest.compare("ecx")) { | |
| 363 write_address = context->GetContextX86()->ecx; | |
| 364 } else if (!dest.compare("edx")) { | |
| 365 write_address = context->GetContextX86()->edx; | |
| 366 } else if (!dest.compare("edi")) { | |
| 367 write_address = context->GetContextX86()->edi; | |
| 368 } else if (!dest.compare("esi")) { | |
| 369 write_address = context->GetContextX86()->esi; | |
| 370 } else if (!dest.compare("ebp")) { | |
| 371 write_address = context->GetContextX86()->ebp; | |
| 372 } else if (!dest.compare("esp")) { | |
| 373 write_address = context->GetContextX86()->esp; | |
| 374 } else if (!dest.compare("eip")) { | |
| 375 write_address = context->GetContextX86()->eip; | |
| 376 } else { | |
| 377 BPLOG(ERROR) << "Unsupported register"; | |
| 378 return false; | |
| 379 } | |
| 380 break; | |
| 381 case MD_CONTEXT_AMD64: | |
| 382 if (!dest.compare("rax")) { | |
| 383 write_address = context->GetContextAMD64()->rax; | |
| 384 } else if (!dest.compare("rbx")) { | |
| 385 write_address = context->GetContextAMD64()->rax; | |
| 386 } else if (!dest.compare("rcx")) { | |
| 387 write_address = context->GetContextAMD64()->rax; | |
| 388 } else if (!dest.compare("rdx")) { | |
| 389 write_address = context->GetContextAMD64()->rax; | |
| 390 } else if (!dest.compare("rdi")) { | |
| 391 write_address = context->GetContextAMD64()->rax; | |
| 392 } else if (!dest.compare("rsi")) { | |
| 393 write_address = context->GetContextAMD64()->rax; | |
| 394 } else if (!dest.compare("rbp")) { | |
| 395 write_address = context->GetContextAMD64()->rax; | |
| 396 } else if (!dest.compare("rsp")) { | |
| 397 write_address = context->GetContextAMD64()->rax; | |
| 398 } else if (!dest.compare("rip")) { | |
| 399 write_address = context->GetContextAMD64()->rax; | |
| 400 } else if (!dest.compare("r8")) { | |
| 401 write_address = context->GetContextAMD64()->rax; | |
| 402 } else if (!dest.compare("r9")) { | |
| 403 write_address = context->GetContextAMD64()->rax; | |
| 404 } else if (!dest.compare("r10")) { | |
| 405 write_address = context->GetContextAMD64()->rax; | |
| 406 } else if (!dest.compare("r11")) { | |
| 407 write_address = context->GetContextAMD64()->rax; | |
| 408 } else if (!dest.compare("r12")) { | |
| 409 write_address = context->GetContextAMD64()->rax; | |
| 410 } else if (!dest.compare("r13")) { | |
| 411 write_address = context->GetContextAMD64()->rax; | |
| 412 } else if (!dest.compare("r14")) { | |
| 413 write_address = context->GetContextAMD64()->rax; | |
| 414 } else if (!dest.compare("r15")) { | |
| 415 write_address = context->GetContextAMD64()->rax; | |
| 416 } else { | |
| 417 BPLOG(ERROR) << "Unsupported register"; | |
| 418 return false; | |
| 419 } | |
| 420 break; | |
| 421 default: | |
| 422 // This should not occur since the same switch condition | |
| 423 // should have terminated this method. | |
| 424 return false; | |
| 425 break; | |
| 426 } | |
| 427 | |
| 428 // Add or subtract constant from write address (if applicable). | |
| 429 write_address = | |
| 430 positive_add_constant ? | |
| 431 write_address + add_constant : write_address - add_constant; | |
| 432 | |
| 433 // If the program crashed as a result of a write, the destination of | |
| 434 // the write must have been an address that did not permit writing. | |
| 435 // However, if the address is under 4k, due to program protections, | |
| 436 // the crash does not suggest exploitability for writes with such a | |
| 437 // low target address. | |
| 438 return write_address > 4096; | |
| 439 } | |
| 440 return false; | |
| 441 } | |
| 442 | |
| 128 bool ExploitabilityLinux::InstructionPointerInCode(uint64_t instruction_ptr) { | 443 bool ExploitabilityLinux::InstructionPointerInCode(uint64_t instruction_ptr) { |
| 129 // Get Linux memory mapping from /proc/self/maps. Checking whether the | 444 // Get Linux memory mapping from /proc/self/maps. Checking whether the |
| 130 // region the instruction pointer is in has executable permission can tell | 445 // region the instruction pointer is in has executable permission can tell |
| 131 // whether it is in a valid code region. If there is no mapping for the | 446 // whether it is in a valid code region. If there is no mapping for the |
| 132 // instruction pointer, it is indicative that the instruction pointer is | 447 // instruction pointer, it is indicative that the instruction pointer is |
| 133 // not within a module, which implies that it is outside a valid area. | 448 // not within a module, which implies that it is outside a valid area. |
| 134 MinidumpLinuxMapsList *linux_maps_list = dump_->GetLinuxMapsList(); | 449 MinidumpLinuxMapsList *linux_maps_list = dump_->GetLinuxMapsList(); |
| 135 const MinidumpLinuxMaps *linux_maps = | 450 const MinidumpLinuxMaps *linux_maps = |
| 136 linux_maps_list ? | 451 linux_maps_list ? |
| 137 linux_maps_list->GetLinuxMapsForAddress(instruction_ptr) : NULL; | 452 linux_maps_list->GetLinuxMapsForAddress(instruction_ptr) : NULL; |
| (...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 174 case MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED: | 489 case MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED: |
| 175 return true; | 490 return true; |
| 176 break; | 491 break; |
| 177 default: | 492 default: |
| 178 return false; | 493 return false; |
| 179 break; | 494 break; |
| 180 } | 495 } |
| 181 } | 496 } |
| 182 | 497 |
| 183 } // namespace google_breakpad | 498 } // namespace google_breakpad |
| OLD | NEW |