OLD | NEW |
---|---|
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 Loading... | |
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 Loading... | |
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(®ex, "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(®ex, 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(®ex); // 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(®ex, "^[[: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(®ex, 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(®ex); | |
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 Loading... | |
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 |
OLD | NEW |