diff options
Diffstat (limited to 'toolkit/crashreporter/google-breakpad/src/processor/exploitability_linux.cc')
-rw-r--r-- | toolkit/crashreporter/google-breakpad/src/processor/exploitability_linux.cc | 625 |
1 files changed, 625 insertions, 0 deletions
diff --git a/toolkit/crashreporter/google-breakpad/src/processor/exploitability_linux.cc b/toolkit/crashreporter/google-breakpad/src/processor/exploitability_linux.cc new file mode 100644 index 000000000..63056c438 --- /dev/null +++ b/toolkit/crashreporter/google-breakpad/src/processor/exploitability_linux.cc @@ -0,0 +1,625 @@ +// Copyright (c) 2013 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// exploitability_linux.cc: Linux specific exploitability engine. +// +// Provides a guess at the exploitability of the crash for the Linux +// platform given a minidump and process_state. +// +// Author: Matthew Riley + +#include "processor/exploitability_linux.h" + +#ifndef _WIN32 +#include <regex.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <sstream> +#include <iterator> +#endif // _WIN32 + +#include "google_breakpad/common/minidump_exception_linux.h" +#include "google_breakpad/processor/call_stack.h" +#include "google_breakpad/processor/process_state.h" +#include "google_breakpad/processor/stack_frame.h" +#include "processor/logging.h" + +namespace { + +// Prefixes for memory mapping names. +constexpr char kHeapPrefix[] = "[heap"; +constexpr char kStackPrefix[] = "[stack"; + +// This function in libc is called if the program was compiled with +// -fstack-protector and a function's stack canary changes. +constexpr char kStackCheckFailureFunction[] = "__stack_chk_fail"; + +// This function in libc is called if the program was compiled with +// -D_FORTIFY_SOURCE=2, a function like strcpy() is called, and the runtime +// can determine that the call would overflow the target buffer. +constexpr char kBoundsCheckFailureFunction[] = "__chk_fail"; + +#ifndef _WIN32 +const unsigned int MAX_INSTRUCTION_LEN = 15; +const unsigned int MAX_OBJDUMP_BUFFER_LEN = 4096; +#endif // _WIN32 + +} // namespace + +namespace google_breakpad { + +ExploitabilityLinux::ExploitabilityLinux(Minidump *dump, + ProcessState *process_state) + : Exploitability(dump, process_state), + enable_objdump_(false) { } + +ExploitabilityLinux::ExploitabilityLinux(Minidump *dump, + ProcessState *process_state, + bool enable_objdump) + : Exploitability(dump, process_state), + enable_objdump_(enable_objdump) { } + + +ExploitabilityRating ExploitabilityLinux::CheckPlatformExploitability() { + // Check the crashing thread for functions suggesting a buffer overflow or + // stack smash. + if (process_state_->requesting_thread() != -1) { + CallStack* crashing_thread = + process_state_->threads()->at(process_state_->requesting_thread()); + const vector<StackFrame*>& crashing_thread_frames = + *crashing_thread->frames(); + for (size_t i = 0; i < crashing_thread_frames.size(); ++i) { + if (crashing_thread_frames[i]->function_name == + kStackCheckFailureFunction) { + return EXPLOITABILITY_HIGH; + } + + if (crashing_thread_frames[i]->function_name == + kBoundsCheckFailureFunction) { + return EXPLOITABILITY_HIGH; + } + } + } + + // Getting exception data. (It should exist for all minidumps.) + MinidumpException *exception = dump_->GetException(); + if (exception == NULL) { + BPLOG(INFO) << "No exception record."; + return EXPLOITABILITY_ERR_PROCESSING; + } + const MDRawExceptionStream *raw_exception_stream = exception->exception(); + if (raw_exception_stream == NULL) { + BPLOG(INFO) << "No raw exception stream."; + return EXPLOITABILITY_ERR_PROCESSING; + } + + // Checking for benign exceptions that caused the crash. + if (this->BenignCrashTrigger(raw_exception_stream)) { + return EXPLOITABILITY_NONE; + } + + // Check if the instruction pointer is in a valid instruction region + // by finding if it maps to an executable part of memory. + uint64_t instruction_ptr = 0; + uint64_t stack_ptr = 0; + + const MinidumpContext *context = exception->GetContext(); + if (context == NULL) { + BPLOG(INFO) << "No exception context."; + return EXPLOITABILITY_ERR_PROCESSING; + } + + // Getting the instruction pointer. + if (!context->GetInstructionPointer(&instruction_ptr)) { + BPLOG(INFO) << "Failed to retrieve instruction pointer."; + return EXPLOITABILITY_ERR_PROCESSING; + } + + // Getting the stack pointer. + if (!context->GetStackPointer(&stack_ptr)) { + BPLOG(INFO) << "Failed to retrieve stack pointer."; + return EXPLOITABILITY_ERR_PROCESSING; + } + + // Checking for the instruction pointer in a valid instruction region, + // a misplaced stack pointer, and an executable stack or heap. + if (!this->InstructionPointerInCode(instruction_ptr) || + this->StackPointerOffStack(stack_ptr) || + this->ExecutableStackOrHeap()) { + return EXPLOITABILITY_HIGH; + } + + // Check for write to read only memory or invalid memory, shelling out + // to objdump is enabled. + if (enable_objdump_ && this->EndedOnIllegalWrite(instruction_ptr)) { + return EXPLOITABILITY_HIGH; + } + + // There was no strong evidence suggesting exploitability, but the minidump + // does not appear totally benign either. + return EXPLOITABILITY_INTERESTING; +} + +bool ExploitabilityLinux::EndedOnIllegalWrite(uint64_t instruction_ptr) { +#ifdef _WIN32 + BPLOG(INFO) << "MinGW does not support fork and exec. Terminating method."; +#else + // 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(INFO) << "No memory region around instruction pointer."; + 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(INFO) << "Not enough bytes left to guarantee complete instruction."; + 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); + + string line; + if (!GetObjdumpInstructionLine(objdump_output_buffer, &line)) { + return false; + } + + // Convert objdump instruction line into the operation and operands. + string instruction = ""; + string dest = ""; + string src = ""; + TokenizeObjdumpInstruction(line, &instruction, &dest, &src); + + // 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; +} + +// static +bool ExploitabilityLinux::GetObjdumpInstructionLine( + const char *objdump_output_buffer, + string *instruction_line) { + // Put buffer data into stream to output line-by-line. + std::stringstream objdump_stream; + objdump_stream.str(string(objdump_output_buffer)); + + // Pipe each output line into the string until the string contains the first + // instruction from objdump. All lines before the "<.data>:" section are + // skipped. Loop until the line shows the first instruction or there are no + // lines left. + bool data_section_seen = false; + do { + if (!getline(objdump_stream, *instruction_line)) { + BPLOG(INFO) << "Objdump instructions not found"; + return false; + } + if (instruction_line->find("<.data>:") != string::npos) { + data_section_seen = true; + } + } while (!data_section_seen || instruction_line->find("0:") == string::npos); + // This first instruction contains the above substring. + + return true; +} + +bool ExploitabilityLinux::TokenizeObjdumpInstruction(const string &line, + string *operation, + string *dest, + string *src) { + 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) { + 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; + } + + char cmd[1024] = {0}; + snprintf(cmd, + 1024, + "objdump -D -b binary -M intel -m %s %s", + architecture.c_str(), + raw_bytes_tmpfile); + FILE *objdump_fp = popen(cmd, "r"); + if (!objdump_fp) { + fclose(objdump_fp); + unlink(raw_bytes_tmpfile); + BPLOG(ERROR) << "Failed to call objdump."; + return false; + } + if (fread(objdump_output_buffer, 1, buffer_len, objdump_fp) <= 0) { + fclose(objdump_fp); + unlink(raw_bytes_tmpfile); + BPLOG(ERROR) << "Failed to read objdump output."; + return false; + } + fclose(objdump_fp); + 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. + if (!linux_maps_list) { + return false; + } + const MinidumpLinuxMaps *linux_maps = + linux_maps_list->GetLinuxMapsForAddress(stack_ptr); + // Checks if the stack pointer maps to a valid mapping and if the mapping + // is not the stack. If the mapping has no name, it is inconclusive whether + // it is off the stack. + return !linux_maps || (linux_maps->GetPathname().compare("") && + linux_maps->GetPathname().compare( + 0, strlen(kStackPrefix), kStackPrefix)); +} + +bool ExploitabilityLinux::ExecutableStackOrHeap() { + MinidumpLinuxMapsList *linux_maps_list = dump_->GetLinuxMapsList(); + if (linux_maps_list) { + for (size_t i = 0; i < linux_maps_list->get_maps_count(); i++) { + const MinidumpLinuxMaps *linux_maps = + linux_maps_list->GetLinuxMapsAtIndex(i); + // Check for executable stack or heap for each mapping. + if (linux_maps && (!linux_maps->GetPathname().compare( + 0, strlen(kStackPrefix), kStackPrefix) || + !linux_maps->GetPathname().compare( + 0, strlen(kHeapPrefix), kHeapPrefix)) && + linux_maps->IsExecutable()) { + return true; + } + } + } + return false; +} + +bool ExploitabilityLinux::InstructionPointerInCode(uint64_t instruction_ptr) { + // Get Linux memory mapping from /proc/self/maps. Checking whether the + // region the instruction pointer is in has executable permission can tell + // whether it is in a valid code region. If there is no mapping for the + // instruction pointer, it is indicative that the instruction pointer is + // not within a module, which implies that it is outside a valid area. + MinidumpLinuxMapsList *linux_maps_list = dump_->GetLinuxMapsList(); + const MinidumpLinuxMaps *linux_maps = + linux_maps_list ? + linux_maps_list->GetLinuxMapsForAddress(instruction_ptr) : NULL; + return linux_maps ? linux_maps->IsExecutable() : false; +} + +bool ExploitabilityLinux::BenignCrashTrigger(const MDRawExceptionStream + *raw_exception_stream) { + // Check the cause of crash. + // If the exception of the crash is a benign exception, + // it is probably not exploitable. + switch (raw_exception_stream->exception_record.exception_code) { + case MD_EXCEPTION_CODE_LIN_SIGHUP: + case MD_EXCEPTION_CODE_LIN_SIGINT: + case MD_EXCEPTION_CODE_LIN_SIGQUIT: + case MD_EXCEPTION_CODE_LIN_SIGTRAP: + case MD_EXCEPTION_CODE_LIN_SIGABRT: + case MD_EXCEPTION_CODE_LIN_SIGFPE: + case MD_EXCEPTION_CODE_LIN_SIGKILL: + case MD_EXCEPTION_CODE_LIN_SIGUSR1: + case MD_EXCEPTION_CODE_LIN_SIGUSR2: + case MD_EXCEPTION_CODE_LIN_SIGPIPE: + case MD_EXCEPTION_CODE_LIN_SIGALRM: + case MD_EXCEPTION_CODE_LIN_SIGTERM: + case MD_EXCEPTION_CODE_LIN_SIGCHLD: + case MD_EXCEPTION_CODE_LIN_SIGCONT: + case MD_EXCEPTION_CODE_LIN_SIGSTOP: + case MD_EXCEPTION_CODE_LIN_SIGTSTP: + case MD_EXCEPTION_CODE_LIN_SIGTTIN: + case MD_EXCEPTION_CODE_LIN_SIGTTOU: + case MD_EXCEPTION_CODE_LIN_SIGURG: + case MD_EXCEPTION_CODE_LIN_SIGXCPU: + case MD_EXCEPTION_CODE_LIN_SIGXFSZ: + case MD_EXCEPTION_CODE_LIN_SIGVTALRM: + case MD_EXCEPTION_CODE_LIN_SIGPROF: + case MD_EXCEPTION_CODE_LIN_SIGWINCH: + case MD_EXCEPTION_CODE_LIN_SIGIO: + case MD_EXCEPTION_CODE_LIN_SIGPWR: + case MD_EXCEPTION_CODE_LIN_SIGSYS: + case MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED: + return true; + break; + default: + return false; + break; + } +} + +} // namespace google_breakpad |