Chromium Code Reviews| Index: src/processor/exploitability_linux.cc |
| =================================================================== |
| --- src/processor/exploitability_linux.cc (revision 1491) |
| +++ src/processor/exploitability_linux.cc (working copy) |
| @@ -36,6 +36,16 @@ |
| #include "processor/exploitability_linux.h" |
| +#include <regex.h> |
| +#include <stdio.h> |
| +#include <string.h> |
| +#include <sysexits.h> |
| +#include <sys/types.h> |
| +#include <sys/wait.h> |
| + |
| +#include <sstream> |
| +#include <iterator> |
| + |
| #include "google_breakpad/common/minidump_exception_linux.h" |
| #include "google_breakpad/processor/call_stack.h" |
| #include "google_breakpad/processor/process_state.h" |
| @@ -42,6 +52,10 @@ |
| #include "google_breakpad/processor/stack_frame.h" |
| #include "processor/logging.h" |
| +#define MAX_INSTRUCTION_LEN 15 |
| +// This is the buffer size for objdump's output. |
| +#define MAX_OBJDUMP_BUFFER_LEN 4096 |
| + |
| namespace { |
| // This function in libc is called if the program was compiled with |
| @@ -53,6 +67,17 @@ |
| // can determine that the call would overflow the target buffer. |
| const char kBoundsCheckFailureFunction[] = "__chk_fail"; |
| +#ifndef _WIN32 |
| +// PID of child process that is forked to call objdump. |
| +pid_t child_pid = -1; |
| + |
| +// Function to kill the child process used to call objdump in case the child |
| +// process hangs. |
| +void kill_child(int signal) { |
| + kill(child_pid, SIGKILL); |
| +} |
| +#endif // _WIN32 |
| + |
| } // namespace |
| namespace google_breakpad { |
| @@ -122,10 +147,12 @@ |
| return EXPLOITABILITY_ERR_PROCESSING; |
| } |
| - // Checking for the instruction pointer in a valid instruction region. |
| + // Checking for the instruction pointer in a valid instruction region |
| + // or if the crash resulted during an illegal write. |
| if (!this->InstructionPointerInCode(instruction_ptr) || |
| this->StackPointerOffStack(stack_ptr) || |
| - this->ExecutableStackOrHeap()) { |
| + this->ExecutableStackOrHeap() || |
| + this->EndedOnIllegalWrite(instruction_ptr)) { |
| return EXPLOITABILITY_HIGH; |
| } |
| @@ -134,6 +161,389 @@ |
| return EXPLOITABILITY_INTERESTING; |
| } |
| +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.
|
| +#ifdef _WIN32 |
| + BPLOG(INFO) << "MinGW does not support fork and exec. Terminating method."; |
| +#else // _WIN32 |
| + // Get memory region containing instruction pointer. |
| + MinidumpMemoryList *memory_list = dump_->GetMemoryList(); |
| + MinidumpMemoryRegion *memory_region = |
| + memory_list ? |
| + memory_list->GetMemoryRegionForAddress(instruction_ptr) : NULL; |
| + if (!memory_region) { |
| + 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.
|
| + return false; |
| + } |
| + |
| + // Get exception data to find architecture. |
| + string architecture = ""; |
| + MinidumpException *exception = dump_->GetException(); |
| + // This should never evaluate to true, since this should not be reachable |
| + // without checking for exception data earlier. |
| + if (!exception) { |
| + BPLOG(INFO) << "No exception data."; |
| + return false; |
| + } |
| + const MDRawExceptionStream *raw_exception_stream = exception->exception(); |
| + const MinidumpContext *context = exception->GetContext(); |
| + // This should not evaluate to true, for the same reason mentioned above. |
| + if (!raw_exception_stream || !context) { |
| + BPLOG(INFO) << "No exception or architecture data."; |
| + return false; |
| + } |
| + // Check architecture and set architecture variable to corresponding flag |
| + // in objdump. |
| + switch (context->GetContextCPU()) { |
| + case MD_CONTEXT_X86: |
| + architecture = "i386"; |
| + break; |
| + case MD_CONTEXT_AMD64: |
| + architecture = "i386:x86-64"; |
| + break; |
| + default: |
| + // Unsupported architecture. Note that ARM architectures are not |
| + // supported because objdump does not support ARM. |
| + return false; |
| + break; |
| + } |
| + |
| + // Get memory region around instruction pointer and the number of bytes |
| + // before and after the instruction pointer in the memory region. |
| + const uint8_t *raw_memory = memory_region->GetMemory(); |
| + const uint64_t base = memory_region->GetBase(); |
| + if (base > instruction_ptr) { |
| + BPLOG(ERROR) << "Memory region base value exceeds instruction pointer."; |
| + return false; |
| + } |
| + const uint64_t offset = instruction_ptr - base; |
| + if (memory_region->GetSize() < MAX_INSTRUCTION_LEN + offset) { |
| + 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.
|
| + return false; |
| + } |
| + |
| + // Convert bytes into objdump output. |
| + char objdump_output_buffer[MAX_OBJDUMP_BUFFER_LEN] = {0}; |
| + DisassembleBytes(architecture, |
| + raw_memory + offset, |
| + MAX_OBJDUMP_BUFFER_LEN, |
| + objdump_output_buffer); |
| + |
| + // Set up regular expression to catch first instruction from objdump. |
| + // The line with the instruction will begin with "0:". |
| + regex_t regex; |
| + regcomp(®ex, "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.
|
| + |
| + // Put buffer data into stream to output line-by-line. |
| + std::stringstream objdump_stream; |
| + objdump_stream.str(string(objdump_output_buffer)); |
| + string line; |
| + |
| + // Pipe each output line into the string until the string contains |
| + // the first instruction from objdump. |
| + // Loop until the line matches the regex or there are no lines left. |
| + do { |
| + if (!getline(objdump_stream, line)) { |
| + 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.
|
| + 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.
|
| + } |
| + } while (regexec(®ex, line.c_str(), 0, NULL, 0)); |
| + regfree(®ex); // Free regex data. |
| + |
| + // Convert objdump instruction line into the operation and operands. |
| + string instruction = ""; |
| + string dest = ""; |
| + string src = ""; |
| + TokenizeObjdumpInstruction(line, &instruction, &dest, &src); |
| + |
|
ivanpe
2015/08/20 05:37:44
Please use a single empty line here.
liuandrew
2015/08/20 17:11:51
Done.
|
| + |
| + // Check if the operation is a write to memory. First, the instruction |
| + // must one that can write to memory. Second, the write destination |
| + // must be a spot in memory rather than a register. Since there are no |
| + // symbols from objdump, the destination will be enclosed by brackets. |
| + if (dest.size() > 2 && dest.at(0) == '[' && dest.at(dest.size() - 1) == ']' && |
| + (!instruction.compare("mov") || !instruction.compare("inc") || |
| + !instruction.compare("dec") || !instruction.compare("and") || |
| + !instruction.compare("or") || !instruction.compare("xor") || |
| + !instruction.compare("not") || !instruction.compare("neg") || |
| + !instruction.compare("add") || !instruction.compare("sub") || |
| + !instruction.compare("shl") || !instruction.compare("shr"))) { |
| + // Strip away enclosing brackets from the destination address. |
| + dest = dest.substr(1, dest.size() - 2); |
| + uint64_t write_address = 0; |
| + CalculateAddress(dest, *context, &write_address); |
| + |
| + // If the program crashed as a result of a write, the destination of |
| + // the write must have been an address that did not permit writing. |
| + // However, if the address is under 4k, due to program protections, |
| + // the crash does not suggest exploitability for writes with such a |
| + // low target address. |
| + return write_address > 4096; |
| + } |
| +#endif // _WIN32 |
| + return false; |
| +} |
| + |
| +#ifndef _WIN32 |
| +bool ExploitabilityLinux::CalculateAddress(const string &address_expression, |
| + const DumpContext &context, |
| + uint64_t *write_address) { |
| + // The destination should be the format reg+a or reg-a, where reg |
| + // is a register and a is a hexadecimal constant. Although more complex |
| + // expressions can make valid instructions, objdump's disassembly outputs |
| + // it in this simpler format. |
| + // TODO(liuandrew): Handle more complex formats, should they arise. |
| + |
| + if (!write_address) { |
| + BPLOG(ERROR) << "Null parameter."; |
| + return false; |
| + } |
| + |
| + // Clone parameter into a non-const string. |
| + string expression = address_expression; |
| + |
| + // Parse out the constant that is added to the address (if it exists). |
| + size_t delim = expression.find('+'); |
| + bool positive_add_constant = true; |
| + // Check if constant is subtracted instead of added. |
| + if (delim == string::npos) { |
| + positive_add_constant = false; |
| + delim = expression.find('-'); |
| + } |
| + uint32_t add_constant = 0; |
| + // Save constant and remove it from the expression. |
| + if (delim != string::npos) { |
| + if (!sscanf(expression.substr(delim + 1).c_str(), "%x", &add_constant)) { |
| + BPLOG(ERROR) << "Failed to scan constant."; |
| + return false; |
| + } |
| + expression = expression.substr(0, delim); |
| + } |
| + |
| + // Set the the write address to the corresponding register. |
| + // TODO(liuandrew): Add support for partial registers, such as |
| + // the rax/eax/ax/ah/al chain. |
| + switch (context.GetContextCPU()) { |
| + case MD_CONTEXT_X86: |
| + if (!expression.compare("eax")) { |
| + *write_address = context.GetContextX86()->eax; |
| + } else if (!expression.compare("ebx")) { |
| + *write_address = context.GetContextX86()->ebx; |
| + } else if (!expression.compare("ecx")) { |
| + *write_address = context.GetContextX86()->ecx; |
| + } else if (!expression.compare("edx")) { |
| + *write_address = context.GetContextX86()->edx; |
| + } else if (!expression.compare("edi")) { |
| + *write_address = context.GetContextX86()->edi; |
| + } else if (!expression.compare("esi")) { |
| + *write_address = context.GetContextX86()->esi; |
| + } else if (!expression.compare("ebp")) { |
| + *write_address = context.GetContextX86()->ebp; |
| + } else if (!expression.compare("esp")) { |
| + *write_address = context.GetContextX86()->esp; |
| + } else if (!expression.compare("eip")) { |
| + *write_address = context.GetContextX86()->eip; |
| + } else { |
| + BPLOG(ERROR) << "Unsupported register"; |
| + return false; |
| + } |
| + break; |
| + case MD_CONTEXT_AMD64: |
| + if (!expression.compare("rax")) { |
| + *write_address = context.GetContextAMD64()->rax; |
| + } else if (!expression.compare("rbx")) { |
| + *write_address = context.GetContextAMD64()->rbx; |
| + } else if (!expression.compare("rcx")) { |
| + *write_address = context.GetContextAMD64()->rcx; |
| + } else if (!expression.compare("rdx")) { |
| + *write_address = context.GetContextAMD64()->rdx; |
| + } else if (!expression.compare("rdi")) { |
| + *write_address = context.GetContextAMD64()->rdi; |
| + } else if (!expression.compare("rsi")) { |
| + *write_address = context.GetContextAMD64()->rsi; |
| + } else if (!expression.compare("rbp")) { |
| + *write_address = context.GetContextAMD64()->rbp; |
| + } else if (!expression.compare("rsp")) { |
| + *write_address = context.GetContextAMD64()->rsp; |
| + } else if (!expression.compare("rip")) { |
| + *write_address = context.GetContextAMD64()->rip; |
| + } else if (!expression.compare("r8")) { |
| + *write_address = context.GetContextAMD64()->r8; |
| + } else if (!expression.compare("r9")) { |
| + *write_address = context.GetContextAMD64()->r9; |
| + } else if (!expression.compare("r10")) { |
| + *write_address = context.GetContextAMD64()->r10; |
| + } else if (!expression.compare("r11")) { |
| + *write_address = context.GetContextAMD64()->r11; |
| + } else if (!expression.compare("r12")) { |
| + *write_address = context.GetContextAMD64()->r12; |
| + } else if (!expression.compare("r13")) { |
| + *write_address = context.GetContextAMD64()->r13; |
| + } else if (!expression.compare("r14")) { |
| + *write_address = context.GetContextAMD64()->r14; |
| + } else if (!expression.compare("r15")) { |
| + *write_address = context.GetContextAMD64()->r15; |
| + } else { |
| + BPLOG(ERROR) << "Unsupported register"; |
| + return false; |
| + } |
| + break; |
| + default: |
| + // This should not occur since the same switch condition |
| + // should have terminated this method. |
| + return false; |
| + break; |
| + } |
| + |
| + // Add or subtract constant from write address (if applicable). |
| + *write_address = |
| + positive_add_constant ? |
| + *write_address + add_constant : *write_address - add_constant; |
| + |
| + return true; |
| +} |
| + |
| +bool ExploitabilityLinux::TokenizeObjdumpInstruction(const string &line, |
| + string *operation, |
| + string *dest, |
| + string *src) { |
| + 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
|
| + if (!operation || !dest || !src) { |
| + BPLOG(ERROR) << "Null parameters passed."; |
| + return false; |
| + } |
| + |
| + // Set all pointer values to empty strings. |
| + *operation = ""; |
| + *dest = ""; |
| + *src = ""; |
| + |
| + // Tokenize the objdump line. |
| + vector<string> tokens; |
| + std::istringstream line_stream(line); |
| + copy(std::istream_iterator<string>(line_stream), |
| + std::istream_iterator<string>(), |
| + std::back_inserter(tokens)); |
| + |
| + // Regex for the data in hex form. Each byte is two hex digits. |
| + regex_t regex; |
| + regcomp(®ex, "^[[:xdigit:]]{2}$", REG_EXTENDED | REG_NOSUB); |
| + |
| + // Find and set the location of the operator. The operator appears |
| + // directly after the chain of bytes that define the instruction. The |
| + // operands will be the last token, given that the instruction has operands. |
| + // If not, the operator is the last token. The loop skips the first token |
| + // because the first token is the instruction number (namely "0:"). |
| + string operands = ""; |
| + for (size_t i = 1; i < tokens.size(); i++) { |
| + // Check if current token no longer is in byte format. |
| + if (regexec(®ex, tokens[i].c_str(), 0, NULL, 0)) { |
| + // instruction = tokens[i]; |
| + *operation = tokens[i]; |
| + // If the operator is the last token, there are no operands. |
| + if (i != tokens.size() - 1) { |
| + operands = tokens[tokens.size() - 1]; |
| + } |
| + break; |
| + } |
| + } |
| + regfree(®ex); |
| + |
| + if (operation->empty()) { |
| + BPLOG(ERROR) << "Failed to parse out operation from objdump instruction."; |
| + return false; |
| + } |
| + |
| + // Split operands into source and destination (if applicable). |
| + if (!operands.empty()) { |
| + size_t delim = operands.find(','); |
| + if (delim == string::npos) { |
| + *dest = operands; |
| + } else { |
| + *dest = operands.substr(0, delim); |
| + *src = operands.substr(delim + 1); |
| + } |
| + } |
| + return true; |
| +} |
| + |
| +bool ExploitabilityLinux::DisassembleBytes(const string &architecture, |
| + const uint8_t *raw_bytes, |
| + const unsigned int buffer_len, |
| + char *objdump_output_buffer) { |
| + int pipe_fd[2]; |
| + |
| + if (!raw_bytes || !objdump_output_buffer) { |
| + BPLOG(ERROR) << "Bad input parameters."; |
| + return false; |
| + } |
| + |
| + // Write raw bytes around instruction pointer to a temporary file to |
| + // pass as an argument to objdump. |
| + char raw_bytes_tmpfile[] = "/tmp/breakpad_mem_region-raw_bytes-XXXXXX"; |
| + int raw_bytes_fd = mkstemp(raw_bytes_tmpfile); |
| + if (raw_bytes_fd < 0) { |
| + BPLOG(ERROR) << "Failed to create tempfile."; |
| + unlink(raw_bytes_tmpfile); |
| + return false; |
| + } |
| + if (write(raw_bytes_fd, raw_bytes, MAX_INSTRUCTION_LEN) |
| + != MAX_INSTRUCTION_LEN) { |
| + BPLOG(ERROR) << "Writing of raw bytes failed."; |
| + unlink(raw_bytes_tmpfile); |
| + return false; |
| + } |
| + |
| + // Open pipe between STDOUT and the objdump output buffer. |
| + if (pipe(pipe_fd) < 0) { |
| + BPLOG(ERROR) << "Failed to pipe."; |
| + return false; |
| + } |
| + |
| + // Fork process to call objdump. |
| + if ((child_pid = fork()) < 0) { |
| + BPLOG(ERROR) << "Forking failed."; |
| + return false; |
| + } |
| + |
| + if (child_pid) { // Parent code. |
| + close(pipe_fd[1]); |
| + |
| + // Set alarm to kill child process after one second in case it hangs. |
| + signal(SIGALRM, (void (*)(int))kill_child); |
| + alarm(1); |
| + |
| + // Read piped output from objdump. |
| + ssize_t bytes_read = read(pipe_fd[0], |
| + objdump_output_buffer, |
| + buffer_len); |
| + int status; |
| + wait(&status); // Wait for child process to run objdump. |
| + alarm(0); // Disable alarm. |
| + if (WEXITSTATUS(status)) { |
| + BPLOG(ERROR) << "Child process encountered error."; |
| + unlink(raw_bytes_tmpfile); |
| + return false; |
| + } |
| + if (bytes_read < 0) { |
| + BPLOG(ERROR) << "Failed to read objdump output."; |
| + unlink(raw_bytes_tmpfile); |
| + return false; |
| + } |
| + close(pipe_fd[0]); |
| + } else { // Child code. |
| + close(pipe_fd[0]); |
| + dup2(pipe_fd[1], STDOUT_FILENO); // Send objdump output across pipe. |
| + // Exec objdump. |
| + execlp("objdump", "objdump", "-D", "-b", "binary", "-M", "intel", |
| + "-m", architecture.c_str(), raw_bytes_tmpfile, NULL); |
| + BPLOG(ERROR) << "Exec failed."; |
| + exit(EX_OSERR); |
| + } |
| + unlink(raw_bytes_tmpfile); |
| + return true; |
| +} |
| +#endif // _WIN32 |
| + |
| bool ExploitabilityLinux::StackPointerOffStack(uint64_t stack_ptr) { |
| MinidumpLinuxMapsList *linux_maps_list = dump_->GetLinuxMapsList(); |
| // Inconclusive if there are no mappings available. |