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

Unified 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « src/processor/exploitability_linux.h ('k') | src/processor/exploitability_unittest.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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"
+#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"
@@ -53,6 +63,11 @@
// can determine that the call would overflow the target buffer.
const 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 {
@@ -59,8 +74,16 @@
ExploitabilityLinux::ExploitabilityLinux(Minidump *dump,
ProcessState *process_state)
- : Exploitability(dump, 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.
@@ -122,7 +145,8 @@
return EXPLOITABILITY_ERR_PROCESSING;
}
- // Checking for the instruction pointer in a valid instruction region.
+ // 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()) {
@@ -129,11 +153,365 @@
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);
+
+ // 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 shows the first instruction or there are no lines left.
+ do {
+ if (!getline(objdump_stream, line)) {
+ BPLOG(INFO) << "Objdump instructions not found";
+ return false;
+ }
+ } while (line.find("0:") == string::npos);
+ // This first instruction contains the above substring.
+
+ // 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;
+}
+
+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(&regex, "^[[: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(&regex, 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(&regex);
+
+ 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.
« 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
This is Rietveld 408576698