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

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: 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
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) {
ahonig 2015/08/10 22:53:11 This is a really long function, please break it in
liuandrew 2015/08/17 21:37:36 Done.
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 false;
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 uint64_t base = memory_region->GetBase();
191 if (base > instruction_ptr) {
192 BPLOG(ERROR) << "Memory region base value exceeds instruction pointer.";
193 return false;
194 }
195 const uint64_t offset = instruction_ptr - base;
196 if (memory_region->GetSize() - offset < MAX_INSTRUCTION_LEN) {
ivanpe 2015/08/10 23:27:58 Can "memory_region->GetSize() - offset" become neg
liuandrew 2015/08/11 22:55:39 Done.
197 BPLOG(ERROR) << "Not enough bytes left to guarantee complete instruction.";
198 return false;
199 }
200
201 // Write raw bytes around instruction pointer to a temporary file to
202 // pass as an argument to objdump.
203 char raw_bytes_tmpfile[] = "/tmp/breakpad_mem_region-raw_bytes-XXXXXX";
204 int raw_bytes_fd = mkstemp(raw_bytes_tmpfile);
205 if (raw_bytes_fd < 0) {
206 BPLOG(ERROR) << "Failed to create tempfile.";
ahonig 2015/08/10 22:53:11 This should return rather than continue running wi
liuandrew 2015/08/11 22:55:38 Done.
207 }
208 if (write(raw_bytes_fd, raw_memory + offset, MAX_INSTRUCTION_LEN)
209 != MAX_INSTRUCTION_LEN) {
ahonig 2015/08/10 22:53:11 Add call to unlink and return.
liuandrew 2015/08/11 22:55:38 Done.
210 BPLOG(ERROR) << "Writing of raw bytes failed.";
211 }
212
213 char objdump_output_buffer[MAX_OBJDUMP_BUFFER_LEN];
214 int pipe_fd[2];
215
216 // Open pipe between STDOUT and the objdump output buffer.
217 if (pipe(pipe_fd) < 0) {
218 BPLOG(ERROR) << "Failed to pipe.";
219 unlink(raw_bytes_tmpfile);
220 return false;
221 }
222
223 // Fork process to call objdump.
224 pid_t child_pid;
225 if ((child_pid = fork()) < 0) {
226 BPLOG(ERROR) << "Forking failed.";
227 unlink(raw_bytes_tmpfile);
228 return false;
229 }
230
231 if (child_pid) { // Parent code.
232 close(pipe_fd[1]);
233 // Read piped output from objdump.
234 ssize_t bytes_read = read(pipe_fd[0],
235 objdump_output_buffer,
236 MAX_OBJDUMP_BUFFER_LEN);
237 int status;
238 wait(&status); // Wait for child process to run objdump.
239 unlink(raw_bytes_tmpfile);
240 if (WEXITSTATUS(status)) {
241 BPLOG(ERROR) << "Child process encountered error.";
242 return false;
243 }
244 if (bytes_read < 0) {
245 BPLOG(ERROR) << "Failed to read objdump output.";
246 return false;
247 }
248 close(pipe_fd[0]);
249 } else { // Child code.
250 close(pipe_fd[0]);
251 dup2(pipe_fd[1], STDOUT_FILENO); // Send objdump output across pipe.
252 // Exec objdump.
253 execlp("objdump", "objdump", "-D", "-b", "binary", "-M", "intel",
254 "-m", architecture.c_str(), raw_bytes_tmpfile, NULL);
255 BPLOG(ERROR) << "Exec failed.";
256 exit(EX_OSERR);
257 }
258
259 // Set up regular expression to catch first instruction from objdump.
260 // The line with the instruction will begin with "0:".
261 regex_t regex;
262 regcomp(&regex, "0:", REG_EXTENDED | REG_NOSUB);
263
264 // Put buffer data into stream to output line-by-line.
265 std::stringstream objdump_stream;
266 objdump_stream.str(string(objdump_output_buffer));
267 string line;
268
269 // Pipe each output line into the string until the string contains
270 // the first instruction from objdump.
271 do {
272 if (!getline(objdump_stream, line)) {
273 BPLOG(ERROR) << "Objdump instructions not found";
274 return false;
275 }
276 } while (regexec(&regex, line.c_str(), 0, NULL, 0));
ahonig 2015/08/10 22:53:11 What happens if this finishes prior to finding a l
liuandrew 2015/08/11 22:55:39 What do you mean? The terminating condition for th
277 regfree(&regex); // Free regex data.
278
279 // Tokenize the objdump line.
280 vector<string> tokens;
281 std::istringstream line_stream(line);
282 copy(std::istream_iterator<string>(line_stream),
283 std::istream_iterator<string>(),
284 std::back_inserter(tokens));
285
286 // Parse out the operator and operands from the instruction.
287 string instruction = "";
288 string operands = "";
289
290 // Regex for the data in hex form. Each byte is two hex digits.
291 regcomp(&regex, "^[[:xdigit:]]{2}$", REG_EXTENDED | REG_NOSUB);
292
293 // Find and set the location of the operator. The operator appears
294 // directly after the chain of bytes that define the instruction. The
295 // operands will be the last token, given that the instruction has operands.
296 // If not, the operator is the last token. The loop skips the first token
297 // because the first token is the instruction number (namely "0:").
298 for (size_t i = 1; i < tokens.size(); i++) {
299 // Check if current token no longer is in byte format.
300 if (regexec(&regex, tokens[i].c_str(), 0, NULL, 0)) {
301 instruction = tokens[i];
302 // If the operator is the last token, there are no operands.
303 if (i != tokens.size() - 1) {
304 operands = tokens[tokens.size() - 1];
305 }
306 break;
307 }
308 }
309 regfree(&regex);
310
311 if (instruction.empty()) {
312 BPLOG(ERROR) << "Failed to parse out operation from objdump instruction.";
313 return false;
314 }
315
316 // Split operands into source and destination (if applicable).
317 string dest = "";
318 string src = "";
319 if (!operands.empty()) {
320 size_t delim = operands.find(',');
321 if (delim == string::npos) {
322 dest = operands;
323 } else {
324 dest = operands.substr(0, delim);
325 src = operands.substr(delim + 1);
326 }
327 }
328
329 // Check if the operation is a write to memory. First, the instruction
330 // must one that can write to memory. Second, the write destination
331 // must be a spot in memory rather than a register. Since there are no
332 // symbols from objdump, the destination will be enclosed by brackets.
333 if (dest.at(0) == '[' && dest.at(dest.size() - 1) == ']' &&
ahonig 2015/08/10 22:53:11 Is this reasonable if dest is empty?
liuandrew 2015/08/11 22:55:38 No this is not reasonable at all. I added a length
334 (!instruction.compare("mov") || !instruction.compare("inc") ||
335 !instruction.compare("dec") || !instruction.compare("and") ||
336 !instruction.compare("or") || !instruction.compare("xor") ||
337 !instruction.compare("not") || !instruction.compare("neg") ||
338 !instruction.compare("add") || !instruction.compare("sub") ||
339 !instruction.compare("shl") || !instruction.compare("shr"))) {
340 // Strip away enclosing brackets from the destination address.
341 dest = dest.substr(1, dest.size() - 2);
ahonig 2015/08/10 22:53:11 What happens if dest is []
liuandrew 2015/08/11 22:55:39 This is addressed in the comment above.
342
343 // The destination should be the format [reg+a] or [reg-a], where reg
344 // is a register and a is a hexadecimal constant. Although more complex
345 // expressions can make valid instructions, objdump's disassembly outputs
346 // it in this simpler format.
347
348 // Parse out the constant that is added to the address (if it exists).
349 size_t delim = dest.find('+');
350 bool positive_add_constant = true;
351 // Check if constant is subtracted instead of added.
352 if (delim == string::npos) {
353 positive_add_constant = false;
354 delim = dest.find('-');
ahonig 2015/08/10 22:53:11 There are definitely more complicated destinations
liuandrew 2015/08/11 22:55:38 Added TODO. I'm not sure if that's the case. I tr
355 }
356 uint32_t add_constant = 0;
357 // Save constant and remove it from the expression.
358 if (delim != string::npos) {
359 sscanf(dest.substr(delim + 1).c_str(), "%x", &add_constant);
360 dest = dest.substr(0, delim);
361 }
362
363 // Calculate and set the address that is the target of the write operation.
364 uint64_t write_address = 0;
365
366 // Set the the write address to the corresponding register.
367 // TODO(liuandrew): Add support for partial registers, such as
368 // the rax/eax/ax/ah/al chain.
369 switch (context->GetContextCPU()) {
370 case MD_CONTEXT_X86:
371 if (!dest.compare("eax")) {
372 write_address = context->GetContextX86()->eax;
373 } else if (!dest.compare("ebx")) {
374 write_address = context->GetContextX86()->ebx;
375 } else if (!dest.compare("ecx")) {
376 write_address = context->GetContextX86()->ecx;
377 } else if (!dest.compare("edx")) {
378 write_address = context->GetContextX86()->edx;
379 } else if (!dest.compare("edi")) {
380 write_address = context->GetContextX86()->edi;
381 } else if (!dest.compare("esi")) {
382 write_address = context->GetContextX86()->esi;
383 } else if (!dest.compare("ebp")) {
384 write_address = context->GetContextX86()->ebp;
385 } else if (!dest.compare("esp")) {
386 write_address = context->GetContextX86()->esp;
387 } else if (!dest.compare("eip")) {
388 write_address = context->GetContextX86()->eip;
389 } else {
390 BPLOG(ERROR) << "Unsupported register";
391 return false;
392 }
393 break;
394 case MD_CONTEXT_AMD64:
395 if (!dest.compare("rax")) {
396 write_address = context->GetContextAMD64()->rax;
397 } else if (!dest.compare("rbx")) {
398 write_address = context->GetContextAMD64()->rax;
399 } else if (!dest.compare("rcx")) {
400 write_address = context->GetContextAMD64()->rax;
401 } else if (!dest.compare("rdx")) {
402 write_address = context->GetContextAMD64()->rax;
403 } else if (!dest.compare("rdi")) {
404 write_address = context->GetContextAMD64()->rax;
405 } else if (!dest.compare("rsi")) {
406 write_address = context->GetContextAMD64()->rax;
407 } else if (!dest.compare("rbp")) {
408 write_address = context->GetContextAMD64()->rax;
409 } else if (!dest.compare("rsp")) {
410 write_address = context->GetContextAMD64()->rax;
411 } else if (!dest.compare("rip")) {
412 write_address = context->GetContextAMD64()->rax;
413 } else if (!dest.compare("r8")) {
414 write_address = context->GetContextAMD64()->rax;
415 } else if (!dest.compare("r9")) {
416 write_address = context->GetContextAMD64()->rax;
417 } else if (!dest.compare("r10")) {
418 write_address = context->GetContextAMD64()->rax;
419 } else if (!dest.compare("r11")) {
420 write_address = context->GetContextAMD64()->rax;
421 } else if (!dest.compare("r12")) {
422 write_address = context->GetContextAMD64()->rax;
423 } else if (!dest.compare("r13")) {
424 write_address = context->GetContextAMD64()->rax;
425 } else if (!dest.compare("r14")) {
426 write_address = context->GetContextAMD64()->rax;
427 } else if (!dest.compare("r15")) {
428 write_address = context->GetContextAMD64()->rax;
429 } else {
430 BPLOG(ERROR) << "Unsupported register";
431 return false;
432 }
433 break;
434 default:
435 // This should not occur since the same switch condition
436 // should have terminated this method.
437 return false;
438 break;
439 }
440
441 // Add or subtract constant from write address (if applicable).
442 write_address =
443 positive_add_constant ?
444 write_address + add_constant : write_address - add_constant;
445
446 // If the program crashed as a result of a write, the destination of
447 // the write must have been an address that did not permit writing.
448 // However, if the address is under 4k, due to program protections,
449 // the crash does not suggest exploitability for writes with such a
450 // low target address.
451 return write_address > 4096;
452 }
453 return false;
454 }
455
128 bool ExploitabilityLinux::InstructionPointerInCode(uint64_t instruction_ptr) { 456 bool ExploitabilityLinux::InstructionPointerInCode(uint64_t instruction_ptr) {
129 // Get Linux memory mapping from /proc/self/maps. Checking whether the 457 // Get Linux memory mapping from /proc/self/maps. Checking whether the
130 // region the instruction pointer is in has executable permission can tell 458 // 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 459 // 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 460 // instruction pointer, it is indicative that the instruction pointer is
133 // not within a module, which implies that it is outside a valid area. 461 // not within a module, which implies that it is outside a valid area.
134 MinidumpLinuxMapsList *linux_maps_list = dump_->GetLinuxMapsList(); 462 MinidumpLinuxMapsList *linux_maps_list = dump_->GetLinuxMapsList();
135 const MinidumpLinuxMaps *linux_maps = 463 const MinidumpLinuxMaps *linux_maps =
136 linux_maps_list ? 464 linux_maps_list ?
137 linux_maps_list->GetLinuxMapsForAddress(instruction_ptr) : NULL; 465 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: 502 case MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED:
175 return true; 503 return true;
176 break; 504 break;
177 default: 505 default:
178 return false; 506 return false;
179 break; 507 break;
180 } 508 }
181 } 509 }
182 510
183 } // namespace google_breakpad 511 } // namespace google_breakpad
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698