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

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: Add check for Linux minidump ending on bad write for exploitability rating. 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
« no previous file with comments | « src/processor/exploitability_linux.h ('k') | src/processor/exploitability_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 #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
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(&regex, "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(&regex, line.c_str(), 0, NULL, 0));
264 regfree(&regex); // 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(&regex, "^[[: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(&regex, 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(&regex);
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
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
OLDNEW
« 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