Usage: Run abrt-action-analyze-vulnerability in a directory which contains ./coredump file. If crash looks exploitable, the tool creates ./exploitable file. Example of such a file:
""" Likely crash reason: Write to an invalid address Exploitable rating (0-9 scale): 6 """
This patch adds abrt-action-analyze-vulnerability invocation to "EVENT=post-create analyzer=CCpp".
TODO: * instruction analyzer is x86 specific now, make it per-arch
Signed-off-by: Denys Vlasenko dvlasenk@redhat.com --- src/plugins/Makefile.am | 10 +- src/plugins/abrt-action-analyze-vulnerability | 41 ++ src/plugins/abrt-gdb-exploitable | 519 +++++++++++++++++++++ src/plugins/ccpp_event.conf | 5 +- tests/abrt-exploitable/.gitignore | 10 + tests/abrt-exploitable/Makefile | 67 +++ tests/abrt-exploitable/testDivideByZero.c | 10 + tests/abrt-exploitable/testExecuteInvalid.c | 17 + .../abrt-exploitable/testFloatingPointException.c | 10 + tests/abrt-exploitable/testReadNull.c | 12 + tests/abrt-exploitable/testReadRandom.c | 14 + tests/abrt-exploitable/testSignalAbort.c | 10 + tests/abrt-exploitable/testSignalIll.c | 10 + tests/abrt-exploitable/testStackBufferOverflow.c | 23 + tests/abrt-exploitable/testStackRecursion.c | 15 + tests/abrt-exploitable/testWriteRandom.c | 15 + 16 files changed, 785 insertions(+), 3 deletions(-) create mode 100755 src/plugins/abrt-action-analyze-vulnerability create mode 100755 src/plugins/abrt-gdb-exploitable create mode 100644 tests/abrt-exploitable/.gitignore create mode 100644 tests/abrt-exploitable/Makefile create mode 100644 tests/abrt-exploitable/testDivideByZero.c create mode 100644 tests/abrt-exploitable/testExecuteInvalid.c create mode 100644 tests/abrt-exploitable/testFloatingPointException.c create mode 100644 tests/abrt-exploitable/testReadNull.c create mode 100644 tests/abrt-exploitable/testReadRandom.c create mode 100644 tests/abrt-exploitable/testSignalAbort.c create mode 100644 tests/abrt-exploitable/testSignalIll.c create mode 100644 tests/abrt-exploitable/testStackBufferOverflow.c create mode 100644 tests/abrt-exploitable/testStackRecursion.c create mode 100644 tests/abrt-exploitable/testWriteRandom.c
diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am index 767c045..20e0297 100644 --- a/src/plugins/Makefile.am +++ b/src/plugins/Makefile.am @@ -3,6 +3,7 @@ bin_SCRIPTS = \ abrt-action-install-debuginfo \ abrt-action-analyze-core \ + abrt-action-analyze-vulnerability \ abrt-action-analyze-vmcore \ abrt-action-list-dsos \ abrt-action-perform-ccpp-analysis \ @@ -29,9 +30,12 @@ bin_PROGRAMS += \ abrt-bodhi endif
-libexec_PROGRAMS = abrt-action-install-debuginfo-to-abrt-cache +libexec_PROGRAMS = \ + abrt-action-install-debuginfo-to-abrt-cache
-libexec_SCRIPTS = abrt-action-ureport +libexec_SCRIPTS = \ + abrt-action-ureport \ + abrt-gdb-exploitable
#dist_pluginsconf_DATA = Python.conf
@@ -68,6 +72,7 @@ PYTHON_FILES = \ abrt-action-install-debuginfo.in \ abrt-action-list-dsos \ abrt-action-analyze-core \ + abrt-action-analyze-vulnerability \ abrt-action-analyze-vmcore.in \ abrt-action-perform-ccpp-analysis.in
@@ -84,6 +89,7 @@ EXTRA_DIST = \ abrt-action-analyze-vmcore \ abrt-action-save-kernel-data \ abrt-action-ureport \ + abrt-gdb-exploitable \ https-utils.h \ post_report.xml.in \ abrt-action-analyze-ccpp-local diff --git a/src/plugins/abrt-action-analyze-vulnerability b/src/plugins/abrt-action-analyze-vulnerability new file mode 100755 index 0000000..aa63ff0 --- /dev/null +++ b/src/plugins/abrt-action-analyze-vulnerability @@ -0,0 +1,41 @@ +#!/bin/sh + +# Do we have the tools we need? +# If no, exit silently. +type gdb >/dev/null 2>&1 || exit 0 +type eu-readelf >/dev/null 2>&1 || exit 0 + +# Do we have coredump? +test -r coredump || { + echo 'No file "coredump" in current directory' >&2 + exit 1 +} + +# Find "cursig: N" and extract N. +# This gets used by abrt-exploitable as a fallback +# if gdb and/or kernel is uncooperative. +# "grep -m1": take the first match (on Linux, every thread has its own +# prstatus struct in the coredump, but the signal number which killed us +# must be the same in all these structs). +SIGNO_OF_THE_COREDUMP=$(eu-readelf -n coredump | grep -m1 -o 'cursig: *[0-9]*' | sed 's/[^0-9]//g') +export SIGNO_OF_THE_COREDUMP + +# Run gdb, hiding its messages. Example: +# Missing separate debuginfo for the main executable file +# Core was generated by... +# Program terminated with signal 11, Segmentation fault. +# #0 0x09fa5348 in ?? () +# We don't want to see all this. +# abrt-exploitable plugin is instructed to create ./exploitable file +# with explanation if severity is >= 4 +GDBOUT=$( +gdb --batch \ + -ex 'python execfile("/usr/libexec/abrt-gdb-exploitable")' \ + -ex 'core-file ./coredump' \ + -ex 'abrt-exploitable 4 ./exploitable' \ + 2>&1 \ +) && exit 0 + +# There was an error. Show the messages. +printf "Error while running gdb:\n%s\n" "$GDBOUT" +exit 1 diff --git a/src/plugins/abrt-gdb-exploitable b/src/plugins/abrt-gdb-exploitable new file mode 100755 index 0000000..4ad95e5 --- /dev/null +++ b/src/plugins/abrt-gdb-exploitable @@ -0,0 +1,519 @@ +#!/usr/bin/python +# This is a GDB plugin. +# Usage: +# gdb --batch -ex 'python execfile("THIS_FILE")' -ex run -ex abrt-exploitable PROG +# or +# gdb --batch -ex 'python execfile("THIS_FILE")' -ex 'core COREDUMP' -ex abrt-exploitable + +import sys +import os +import signal +import gdb + +_WRITES_ALWAYS = -1 +_WRITES_IF_MEMREF = -2 + +_writing_instr = { + # insn:N, where N: + # -1: this insn always writes to memory + # -2: writes to memory if any operand is a memory operand + # 2: writes to memory if 2nd (or later) operand is a memory operand + # + # Two-operand insns + "add":2, + "adc":2, + "sub":2, + "sbb":2, + "and":2, + "xor":2, + "or":2, + "xadd":2, + "cmpxchg":2, + # One-operand insns. Can use 1 or _WRITES_IF_MEMREF + "inc":_WRITES_IF_MEMREF, + "dec":_WRITES_IF_MEMREF, + "neg":_WRITES_IF_MEMREF, + "not":_WRITES_IF_MEMREF, + "pop":_WRITES_IF_MEMREF, + # "Set byte on condition". One-operand insns. + "seta":_WRITES_IF_MEMREF, + "setae":_WRITES_IF_MEMREF, + "setb":_WRITES_IF_MEMREF, + "setbe":_WRITES_IF_MEMREF, + "setc":_WRITES_IF_MEMREF, + "sete":_WRITES_IF_MEMREF, + "setg":_WRITES_IF_MEMREF, + "setge":_WRITES_IF_MEMREF, + "setl":_WRITES_IF_MEMREF, + "setle":_WRITES_IF_MEMREF, + "setna":_WRITES_IF_MEMREF, + "setnae":_WRITES_IF_MEMREF, + "setnb":_WRITES_IF_MEMREF, + "setnbe":_WRITES_IF_MEMREF, + "setnc":_WRITES_IF_MEMREF, + "setne":_WRITES_IF_MEMREF, + "setng":_WRITES_IF_MEMREF, + "setnge":_WRITES_IF_MEMREF, + "setnl":_WRITES_IF_MEMREF, + "setnle":_WRITES_IF_MEMREF, + "setno":_WRITES_IF_MEMREF, + "setnp":_WRITES_IF_MEMREF, + "setns":_WRITES_IF_MEMREF, + "setnz":_WRITES_IF_MEMREF, + "seto":_WRITES_IF_MEMREF, + "setp":_WRITES_IF_MEMREF, + "setpe":_WRITES_IF_MEMREF, + "setpo":_WRITES_IF_MEMREF, + "sets":_WRITES_IF_MEMREF, + "setz":_WRITES_IF_MEMREF, + # Shifts. + # sarl $2,(%rcx) + # sarl (%rax) - *implicit* operand (shift count) 1. + # shld 11,%ecx,(%rdi) - *third* operand is r/m. + # Luckily, any memory operand is a destination, can use _WRITES_IF_MEMREF. + "shl":_WRITES_IF_MEMREF, + "shr":_WRITES_IF_MEMREF, + "sal":_WRITES_IF_MEMREF, + "sar":_WRITES_IF_MEMREF, + "rol":_WRITES_IF_MEMREF, + "ror":_WRITES_IF_MEMREF, + "rcl":_WRITES_IF_MEMREF, + "rcr":_WRITES_IF_MEMREF, + "shld":_WRITES_IF_MEMREF, + "shrd":_WRITES_IF_MEMREF, + # Bit tests. Any memory operand is a destination, can use _WRITES_IF_MEMREF. + "bts":_WRITES_IF_MEMREF, + "btr":_WRITES_IF_MEMREF, + "btc":_WRITES_IF_MEMREF, + # One-operand (register pair is another, implicit operand). + "cmpxchg8b":_WRITES_IF_MEMREF, + "cmpxchg16b":_WRITES_IF_MEMREF, + + # Either mem operand indicates write to mem. + "xchg":_WRITES_IF_MEMREF, + + # String store insns. + # Look similar to widening signed move "movs[bwl][wlq]", + # but aliasing doesn't happen since widening move has two siffixes + "movs":_WRITES_ALWAYS, + "stos":_WRITES_ALWAYS, + # Widening moves never store to mem. + # May look like we need to list them because otherwise they get caught + # by "movXXX", but thankfully their 2nd operand is never a memory reference, + # which "movXXX" wildcard checks. + #"mov[sz][bwl][wlq]":0, + + # One-operand insn. + # These are system insns, but they do NOT cause exception in userspace. + "smsw":_WRITES_IF_MEMREF, + "sgdt":_WRITES_IF_MEMREF, + "sidt":_WRITES_IF_MEMREF, + "sldt":_WRITES_IF_MEMREF, + "str":_WRITES_IF_MEMREF, + + # FPU/SIMD madness follows. + + # FPU store insns. One-operand. + "fsts":_WRITES_IF_MEMREF, + "fstl":_WRITES_IF_MEMREF, + #"fstt" doesn't exist + "fstps":_WRITES_IF_MEMREF, + "fstpl":_WRITES_IF_MEMREF, + "fstpt":_WRITES_IF_MEMREF, + # Saving state. One-operand insns. + "fstcw":_WRITES_IF_MEMREF, + "fnstcw":_WRITES_IF_MEMREF, + "fstsw":_WRITES_IF_MEMREF, + "fnstsw":_WRITES_IF_MEMREF, + "fstenv":_WRITES_IF_MEMREF, + "fnstenv":_WRITES_IF_MEMREF, + "fsave":_WRITES_IF_MEMREF, + "fnsave":_WRITES_IF_MEMREF, + "fxsave":_WRITES_IF_MEMREF, + "xsave":_WRITES_IF_MEMREF, + "xsaveopt":_WRITES_IF_MEMREF, + "fsave64":_WRITES_IF_MEMREF, + "fnsave64":_WRITES_IF_MEMREF, + "fxsave64":_WRITES_IF_MEMREF, + "xsave64":_WRITES_IF_MEMREF, + "xsaveopt64":_WRITES_IF_MEMREF, + "stmxcsr":_WRITES_IF_MEMREF, + "vstmxcsr":_WRITES_IF_MEMREF, + # SIMD store insns. + # Three-operand insns. Any memory operand is a destination. + "vcvtps2ph":_WRITES_IF_MEMREF, + "extractps":_WRITES_IF_MEMREF, + "vextractps":_WRITES_IF_MEMREF, + #[v]extractpd does not exist + "vextractf128":_WRITES_IF_MEMREF, + "vextracti128":_WRITES_IF_MEMREF, + "pextr":_WRITES_IF_MEMREF, # covers pextr[bwq] + "pextrd":_WRITES_IF_MEMREF, + "vpextr":_WRITES_IF_MEMREF, + "vpextrd":_WRITES_IF_MEMREF, + "vmaskmovpd":_WRITES_IF_MEMREF, + "vmaskmovps":_WRITES_IF_MEMREF, + "vpmaskmovd":_WRITES_IF_MEMREF, + "vpmaskmovq":_WRITES_IF_MEMREF, + # These insns have implicit (%edi) dest operand: + "maskmovq":_WRITES_ALWAYS, # mmx version + "maskmovdqu":_WRITES_ALWAYS, + "vmaskmovdqu":_WRITES_ALWAYS, + + # check binutils/gas/testsuite/gas/i386/* for more weird insns + # Instruction Set Reference, A-M and N-Z: + # http://download.intel.com/products/processor/manual/253666.pdf + # http://download.intel.com/products/processor/manual/253667.pdf + # SSE4: + # http://software.intel.com/sites/default/files/m/0/3/c/d/4/18187-d9156103.pdf + # Instruction Set Extensions: + # http://download-software.intel.com/sites/default/files/319433-014.pdf + # Xeon Phi: + # http://download-software.intel.com/sites/default/files/forum/278102/32736400... + + #"[v]movXXX" - special-cased in the code + "mov":2 + + # Note: stack-writing instructions are omitted +} + +_pushing_instr = ( + "push", + "pusha", + "pushf", + "enter", + "call", + "lcall" +) + +_intdiv_instr = ("div", "idiv") + +_jumping_instr = ( + "jmp", # indirect jumps/calls with garbage data + "call", # call: also possible that stack is exhausted (infinite recursion) + "ljmp", + "lcall", + # Yes, lret/iret isn't used in normal userspace code, + # but it does work (compile with "gcc -nostartfiles -nostdlib -m32"): + # + #_start: .globl _start + # pushf + # push %cs + # push $next + # iret # lret or ret would work too + #next: + # movl $42, %ebx + # movl $1, %eax + # int $0x80 # exit(42) + # + "iret", + "lret", + "ret" +) + +# stack was smashed if we crash on one of these +_return_instr = ("iret", "lret", "ret") + +def _fetch_insn_from_table(ins, table): + if not ins: + return None + if ins in table: + if type(table) == dict: + return table[ins] + return ins + # Drop common byte/word/long/quad suffix and try again + if ins[-1] in ("b", "w", "l", "q"): + ins = ins[:-1] + if ins in table: + if type(table) == dict: + return table[ins] + return ins + return None + +class Signal_and_insn: + def get_signal(self): + self.signo = None + try: + # Requires new kernels which record complete siginfo + # in coredumps (Linux 3.9 still don't have it), + # and new gdb: + sig = gdb.parse_and_eval("$_siginfo.si_signo") + # Requires patched gdb: + #sig = gdb.parse_and_eval("$_signo") + # + # type(sig) = <type 'gdb.Value'>, convert to plain int: + self.signo = int(sig) + except gdb.error: + # "Python Exception <class 'gdb.error'> + # Attempt to extract a component of a value that is not a structure" + # Possible reasons why $_siginfo doesn't exist: + # program is still running, program exited normally, + # we work with a coredump from an old kernel. + # + # Lets see whether we are running from the abrt and it + # provided us with signal number. Horrible hack :( + # + try: + self.signo = int(os.environ["SIGNO_OF_THE_COREDUMP"]) + except KeyError: + return False + return True + + def get_instruction(self): + self.current_instruction = None + self.mnemonic = None + self.operands = "" + try: + # just "disassemble $pc" won't work if $pc doesn't point + # inside a known function + instructions = gdb.execute("disassemble $pc,$pc+32", to_string=True) + except gdb.error: + # For example, if tracee already exited normally. + # Another observed case is if $pc points to unmapped area. + # We get "Python Exception <class 'gdb.error'> No registers" + return + + raw_instructions = instructions + instructions = [] + current = None + for line in raw_instructions.split("\n"): + # line can be: + # "Dump of assembler code from 0xAAAA to 0xBBBB:" + # "[=>] 0x00000000004004dc[ <+0>]: push %rbp" + # (" <+0>" part is present when we run on a live process, + # on coredump it is absent) + # "End of assembler dump." + # "" (empty line) + if line.startswith("=>"): + line = line[2:] + current = len(instructions) + line = line.split(":", 1) + if len(line) < 2: # no ":"? + continue + line = line[1] # drop "foo:" + line = line.strip() # drop leading/trailing whitespace + if line: + instructions.append(line) + if current == None: + # not False! we determined that $pc points to a bad address, + # which is an interesting fact. + return + + # There can be a disasm comment: "insn op,op,op # comment"; + # strip it, and whitespace on both ends: + t = instructions[current].split("#", 1)[0].strip() + self.current_instruction = t + # Strip prefixes: + while True: + t = t.split(None, 1) + self.mnemonic = t[0] + if len(t) < 2: + break + if self.mnemonic.startswith("rex."): + t = t[1] + continue + if self.mnemonic in ( + "data32", "data16", "addr32", "addr16", "rex", + "cs", "ds", "es", "ss", "fs", "gs", + "lock", "rep", "repz", "repnz", "xacquire", "xrelease" + ): + t = t[1] + continue + # First word isn't a prefix -> we found the insn word + self.operands = t[1] + break + + def instruction_is_writing(self): + operand = _fetch_insn_from_table(self.mnemonic, _writing_instr) + if not operand: + if not self.mnemonic: + return False + # There are far too many SSE store instructions, + # don't want to pollute the table with them. + # Special-case the check for MOVxxx + # and its SIMD cousins VMOVxxx: + if self.mnemonic[:3] != "mov" and self.mnemonic[:4] != "vmov": + return False + operand = 2 + + if operand == _WRITES_ALWAYS: # no need to check operands, it's a write + return True + + # Memory operands look like this: [%seg:][[-]0xHEXNUM][(%reg[,...])] + # Careful with immediate operands which are $0xHEXNUM + # and FPU register references which are st(N). + if self.operands.startswith("0x") or self.operands.startswith("-0x") or self.operands.startswith("("): + mem_op_pos = 0 + else: + mem_op_pos = self.operands.find(",0x") + if mem_op_pos < 0: + mem_op_pos = self.operands.find(",-0x") + if mem_op_pos < 0: + mem_op_pos = self.operands.find(":0x") + if mem_op_pos < 0: + mem_op_pos = self.operands.find(":-0x") + if mem_op_pos < 0: + mem_op_pos = self.operands.find(",(") + if mem_op_pos < 0: + mem_op_pos = self.operands.find(":(") + if mem_op_pos < 0: + return False # no memory operands + mem_op_pos += 1 + + if operand == _WRITES_IF_MEMREF: # any mem operand indicates write + return True + + comma = self.operands.find(",") + if mem_op_pos < comma: + # "%cs:0x0(%rax,%rax,1),foo" - 1st operand is memory + # "%cs:0x0(%rax),foo" - 1st operand is memory + memory_operand = 1 + elif comma < 0: + # "%cs:0x0(%rax)" - 1st operand is memory + memory_operand = 1 + else: + # mem_op_pos is after comma + # "foo,%cs:0x0(%rax,%rax,1)" - 2nd operand is memory + # (It also can be a third, fourth etc operand) + memory_operand = 2 + + if operand == memory_operand: + return True + return False + + def instruction_is_pushing(self): + if _fetch_insn_from_table(self.mnemonic, _pushing_instr): + return True + return False + + def instruction_is_division(self): + if _fetch_insn_from_table(self.mnemonic, _intdiv_instr): + return True + return False + + def instruction_is_jumping(self): + if _fetch_insn_from_table(self.mnemonic, _jumping_instr): + return True + return False + + def instruction_is_return(self): + if _fetch_insn_from_table(self.mnemonic, _return_instr): + return True + return False + + #Our initial set of testing will use the list Apple included in their + #CrashWrangler announcement: + # + #Exploitable if: + # Crash on write instruction + # Crash executing invalid address + # Crash calling an invalid address + # Crash accessing an uninitialized or freed pointer as indicated by + # using the MallocScribble environment variable + # Illegal instruction exception + # Abort due to -fstack-protector, _FORTIFY_SOURCE, heap corruption + # detected + # Stack trace of crashing thread contains certain functions such as + # malloc, free, szone_error, objc_MsgSend, etc. + def is_exploitable(self): + self.exploitable_rating = 3 + self.exploitable_desc = "" + + if 0: + pass + # SIGABRT Abort signal from abort(3) + # SIGQUIT Quit from keyboard + # SIGXCPU CPU time limit exceeded + # SIGXFSZ File size limit exceeded + # SIGTRAP Trace/breakpoint trap + # SIGSYS Bad argument to routine (SVr4) + # SIGFPE Floating point exception + # SIGILL Illegal Instruction + # SIGSEGV Invalid memory reference + # SIGBUS Bus error (bad memory access) + elif self.signo == signal.SIGABRT: + self.exploitable_rating = 0 + self.exploitable_desc = "ABRT signal (abort() was called?)" + elif self.signo == signal.SIGQUIT: + self.exploitable_rating = 0 + self.exploitable_desc = "QUIT signal (Ctrl-\ pressed?)" + elif self.signo == signal.SIGXCPU: + self.exploitable_rating = 0 + self.exploitable_desc = "XCPU signal (over CPU time limit)" + elif self.signo == signal.SIGXFSZ: + self.exploitable_rating = 0 + self.exploitable_desc = "XFSZ signal (over file size limit)" + elif self.signo == signal.SIGTRAP: + self.exploitable_rating = 0 + self.exploitable_desc = "TRAP signal (can be a bug in a debugger/tracer)" + elif self.signo == signal.SIGSYS: + self.exploitable_rating = 1 + self.exploitable_desc = "SYS signal (unknown/masked syscall was called?)" + + elif self.signo == signal.SIGFPE: + self.exploitable_rating = 1 + self.exploitable_desc = "Arithmetic exception" + if self.instruction_is_division(): + self.exploitable_rating = 0 + self.exploitable_desc = "Division by zero" + elif self.signo == signal.SIGILL: + self.exploitable_rating = 5 + self.exploitable_desc = "Illegal instruction (jump to a random address?)" + + # TODO: check that sig is SIGSEGV/SIGBUS? + + elif self.instruction_is_pushing(): + self.exploitable_rating = 4 + self.exploitable_desc = "Stack overflow" + elif self.instruction_is_writing(): + self.exploitable_rating = 6 + self.exploitable_desc = "Write to an invalid address" + elif self.instruction_is_return(): + self.exploitable_rating = 7 + self.exploitable_desc = "Subroutine return to an invalid address (corrupted stack?)" + # Note: we check "ret" first, _then_ jumps. + # Corrupted stack is different from corrupted data. + elif self.instruction_is_jumping(): + self.exploitable_rating = 6 + self.exploitable_desc = "Jump to an invalid address" + elif not self.current_instruction: + self.exploitable_rating = 6 + self.exploitable_desc = "Jump to an invalid address" + elif self.signo == signal.SIGBUS: + self.exploitable_rating = 5 + self.exploitable_desc = "Access past the end of mapped file, invalid address, unaligned access, etc" + #elif self.signo = signal.SIGfoo: + +class AbrtExploitable(gdb.Command): + "Analyze a crash to determine exploitability" + def __init__(self): + super(AbrtExploitable, self).__init__( + "abrt-exploitable", + gdb.COMMAND_SUPPORT, # command class + gdb.COMPLETE_NONE, # completion method + False # => it's not a prefix command + ) + + # Called when the command is invoked from GDB + def invoke(self, args, from_tty): + si = Signal_and_insn() + if not si.get_signal(): + sys.stderr.write("Can't get signal no and do exploitability analysis\n") + return + si.get_instruction() + min_rating = 0 + if args: + args = args.split(None, 1) + min_rating = int(args[0]) + si.is_exploitable() + if si.exploitable_desc: + if si.exploitable_rating >= min_rating: + f = sys.stdout + if args and len(args) > 1: + f = open(args[1], 'w') + f.write("Likely crash reason: " + si.exploitable_desc + "\n") + f.write("Exploitable rating (0-9 scale): " + str(si.exploitable_rating) + "\n") + else: + sys.stderr.write("Exploitability analysis came up empty\n") + +AbrtExploitable() diff --git a/src/plugins/ccpp_event.conf b/src/plugins/ccpp_event.conf index dfc4908..aa8cdb3 100644 --- a/src/plugins/ccpp_event.conf +++ b/src/plugins/ccpp_event.conf @@ -15,10 +15,13 @@ EVENT=post-create analyzer=CCpp exit 1 fi # Try generating backtrace, if it fails we can still use - # the UUID generated by abrt-action-analyze-c + # the hash generated by abrt-action-analyze-c ##satyr migration: #satyr abrt-create-core-stacktrace "$DUMP_DIR" abrt-action-generate-core-backtrace + # Run GDB plugin to see if crash looks exploitable + abrt-action-analyze-vulnerability + # Generate hash abrt-action-analyze-c && abrt-action-list-dsos -m maps -o dso_list && ( diff --git a/tests/abrt-exploitable/.gitignore b/tests/abrt-exploitable/.gitignore new file mode 100644 index 0000000..b0c3b29 --- /dev/null +++ b/tests/abrt-exploitable/.gitignore @@ -0,0 +1,10 @@ +testDivideByZero +testExecuteInvalid +testFloatingPointException +testReadNull +testReadRandom +testSignalAbort +testSignalIll +testStackBufferOverflow +testStackRecursion +testWriteRandom diff --git a/tests/abrt-exploitable/Makefile b/tests/abrt-exploitable/Makefile new file mode 100644 index 0000000..6672fe3 --- /dev/null +++ b/tests/abrt-exploitable/Makefile @@ -0,0 +1,67 @@ +# Copyright (C) 2010, 2011 Red Hat, Inc. +# +# This file is part of ABRT. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. + +CFLAGS=-g + +TESTS=\ + testDivideByZero\ + testReadNull\ + testStackRecursion\ + testReadRandom\ + testSignalAbort\ + testSignalIll\ + testWriteRandom\ + testExecuteInvalid\ + testStackBufferOverflow\ + testFloatingPointException\ + +all: $(TESTS) + +clean: + rm -f $(TESTS) + +testlive: + for t in $(TESTS); do \ + echo "====="; \ + echo "Test: $$t"; \ + gdb --batch \ + -ex 'python execfile("../../src/plugins/abrt-gdb-exploitable")' \ + -ex 'run' \ + -ex 'disas $$pc-16,$$pc+16' \ + -ex 'abrt-exploitable' \ + -ex 'cont' \ + -ex 'quit' \ + ./$$t; \ + done 2>&1 | tee testlive.log + +testcore: + rm ./core* 2>/dev/null; \ + ulimit -c unlimited; \ + for t in $(TESTS); do \ + echo "====="; \ + echo "Test: $$t"; \ + ./$$t && { echo "No crash???"; continue; }; \ + mv core* core || { echo "No corefile???"; continue; }; \ + gdb --batch \ + -ex 'python execfile("../../src/plugins/abrt-gdb-exploitable")' \ + -ex 'core ./core' \ + -ex 'disas $$pc-16,$$pc+16' \ + -ex 'abrt-exploitable' \ + -ex 'quit' \ + ; \ + rm core; \ + done 2>&1 | tee testcore.log diff --git a/tests/abrt-exploitable/testDivideByZero.c b/tests/abrt-exploitable/testDivideByZero.c new file mode 100644 index 0000000..06d1551 --- /dev/null +++ b/tests/abrt-exploitable/testDivideByZero.c @@ -0,0 +1,10 @@ +/* + * Test a divide by zero error + * This error is not exploitable + */ + +#include <stdio.h> + +int main(int argc, char *argv[]) { + printf("%d\n", 7/0); +} diff --git a/tests/abrt-exploitable/testExecuteInvalid.c b/tests/abrt-exploitable/testExecuteInvalid.c new file mode 100644 index 0000000..86ce515 --- /dev/null +++ b/tests/abrt-exploitable/testExecuteInvalid.c @@ -0,0 +1,17 @@ +/* + * Test a crash attempting to execute an invalid address + * This error is exploitable + */ + +#include <stdlib.h> +#include <string.h> + +int (*function_pointer)(); + +int main(int argc, char *argv[]) { + char *a; + a = malloc(1024); + a = (size_t)a * 1024; // This should put us well outside the valid memory range + function_pointer = a; + function_pointer(); +} diff --git a/tests/abrt-exploitable/testFloatingPointException.c b/tests/abrt-exploitable/testFloatingPointException.c new file mode 100644 index 0000000..7931914 --- /dev/null +++ b/tests/abrt-exploitable/testFloatingPointException.c @@ -0,0 +1,10 @@ +/* + * Test the floating point exception signal + * This error is exploitable + */ + +#include <signal.h> + +int main(int argc, char *argv[]) { + raise(SIGFPE); +} diff --git a/tests/abrt-exploitable/testReadNull.c b/tests/abrt-exploitable/testReadNull.c new file mode 100644 index 0000000..548cf73 --- /dev/null +++ b/tests/abrt-exploitable/testReadNull.c @@ -0,0 +1,12 @@ +/* + * Test a NULL read + * This error is not exploitable + */ + +#include <stdio.h> + +int main(int argc, char *argv[]) { + char *a; + a = 0x0; + puts(a); +} diff --git a/tests/abrt-exploitable/testReadRandom.c b/tests/abrt-exploitable/testReadRandom.c new file mode 100644 index 0000000..8493078 --- /dev/null +++ b/tests/abrt-exploitable/testReadRandom.c @@ -0,0 +1,14 @@ +/* + * Test a crash attempting to read invalid memory + * This error is not exploitable + */ + +#include <stdio.h> +#include <stdlib.h> + +int main(int argc, char *argv[]) { + char *a; + a = malloc(1024); + a = (size_t)a * 1024; // This should put us well outside the valid memory range + printf("%s\n", a); +} diff --git a/tests/abrt-exploitable/testSignalAbort.c b/tests/abrt-exploitable/testSignalAbort.c new file mode 100644 index 0000000..fc41227 --- /dev/null +++ b/tests/abrt-exploitable/testSignalAbort.c @@ -0,0 +1,10 @@ +/* + * Test the abort signal + * This error is not exploitable + */ + +#include <signal.h> + +int main(int argc, char *argv[]) { + raise(SIGABRT); +} diff --git a/tests/abrt-exploitable/testSignalIll.c b/tests/abrt-exploitable/testSignalIll.c new file mode 100644 index 0000000..7783263 --- /dev/null +++ b/tests/abrt-exploitable/testSignalIll.c @@ -0,0 +1,10 @@ +/* + * Test the illegal instruction signal + * This error is exploitable + */ + +#include <signal.h> + +int main(int argc, char *argv[]) { + raise(SIGILL); +} diff --git a/tests/abrt-exploitable/testStackBufferOverflow.c b/tests/abrt-exploitable/testStackBufferOverflow.c new file mode 100644 index 0000000..fcbd95f --- /dev/null +++ b/tests/abrt-exploitable/testStackBufferOverflow.c @@ -0,0 +1,23 @@ +/* + * Test a stack buffer overflow + * This test could be exploitable (it needs further analysis) + */ + +#include <stdio.h> + +int i; + +int my_function() { + char a[2]; + + for (i = 0; i < 1024; i++) { + a[i] = 'A'; + } + printf("%s\n", a); +} + +int main(int argc, char *argv[]) { + my_function(); + my_function(); + return 0; +} diff --git a/tests/abrt-exploitable/testStackRecursion.c b/tests/abrt-exploitable/testStackRecursion.c new file mode 100644 index 0000000..dddf583 --- /dev/null +++ b/tests/abrt-exploitable/testStackRecursion.c @@ -0,0 +1,15 @@ +/* + * Test a crash from stack recursion + * This error is not exploitable + */ + +#include <stdio.h> + +void my_function() { + char a[1024]; + my_function(); +} + +int main(int argc, char *argv[]) { + my_function(); +} diff --git a/tests/abrt-exploitable/testWriteRandom.c b/tests/abrt-exploitable/testWriteRandom.c new file mode 100644 index 0000000..812f94c --- /dev/null +++ b/tests/abrt-exploitable/testWriteRandom.c @@ -0,0 +1,15 @@ +/* + * Test a crash attempting to write invalid memory + * This error is exploitable + */ + +#include <stdlib.h> +#include <string.h> + +int main(int argc, char *argv[]) { + char *a; + char b[] = "pwnt"; + a = malloc(1024); + a = (size_t)a * 1024; // This should put us well outside the valid memory range + strcpy(a, b); +}
Signed-off-by: Denys Vlasenko dvlasenk@redhat.com --- abrt.spec.in | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/abrt.spec.in b/abrt.spec.in index fc11fad..e85a487 100644 --- a/abrt.spec.in +++ b/abrt.spec.in @@ -672,6 +672,7 @@ gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || : %{_initrddir}/abrt-ccpp %endif %{_libexecdir}/abrt-hook-ccpp +%{_libexecdir}/abrt-gdb-exploitable
# attr(6755) ~= SETUID|SETGID %attr(6755, abrt, abrt) %{_libexecdir}/abrt-action-install-debuginfo-to-abrt-cache @@ -679,6 +680,7 @@ gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || : %{_bindir}/abrt-action-analyze-c %{_bindir}/abrt-action-trim-files %{_bindir}/abrt-action-analyze-core +%{_bindir}/abrt-action-analyze-vulnerability %{_bindir}/abrt-action-install-debuginfo %{_bindir}/abrt-action-generate-backtrace %{_bindir}/abrt-action-generate-core-backtrace
NACK. This patch still doesn't address the problems I pointed out in the previous version:
- the "exploitable_desc" strings should be translatable - use regexp instead of the long if - else construction, as it was requested by the majority of team members
Thank you, Jirka
On 06/21/2013 01:51 PM, Denys Vlasenko wrote:
Usage: Run abrt-action-analyze-vulnerability in a directory which contains ./coredump file. If crash looks exploitable, the tool creates ./exploitable file. Example of such a file:
""" Likely crash reason: Write to an invalid address Exploitable rating (0-9 scale): 6 """
This patch adds abrt-action-analyze-vulnerability invocation to "EVENT=post-create analyzer=CCpp".
TODO:
- instruction analyzer is x86 specific now, make it per-arch
Signed-off-by: Denys Vlasenko dvlasenk@redhat.com
src/plugins/Makefile.am | 10 +- src/plugins/abrt-action-analyze-vulnerability | 41 ++ src/plugins/abrt-gdb-exploitable | 519 +++++++++++++++++++++ src/plugins/ccpp_event.conf | 5 +- tests/abrt-exploitable/.gitignore | 10 + tests/abrt-exploitable/Makefile | 67 +++ tests/abrt-exploitable/testDivideByZero.c | 10 + tests/abrt-exploitable/testExecuteInvalid.c | 17 + .../abrt-exploitable/testFloatingPointException.c | 10 + tests/abrt-exploitable/testReadNull.c | 12 + tests/abrt-exploitable/testReadRandom.c | 14 + tests/abrt-exploitable/testSignalAbort.c | 10 + tests/abrt-exploitable/testSignalIll.c | 10 + tests/abrt-exploitable/testStackBufferOverflow.c | 23 + tests/abrt-exploitable/testStackRecursion.c | 15 + tests/abrt-exploitable/testWriteRandom.c | 15 + 16 files changed, 785 insertions(+), 3 deletions(-) create mode 100755 src/plugins/abrt-action-analyze-vulnerability create mode 100755 src/plugins/abrt-gdb-exploitable create mode 100644 tests/abrt-exploitable/.gitignore create mode 100644 tests/abrt-exploitable/Makefile create mode 100644 tests/abrt-exploitable/testDivideByZero.c create mode 100644 tests/abrt-exploitable/testExecuteInvalid.c create mode 100644 tests/abrt-exploitable/testFloatingPointException.c create mode 100644 tests/abrt-exploitable/testReadNull.c create mode 100644 tests/abrt-exploitable/testReadRandom.c create mode 100644 tests/abrt-exploitable/testSignalAbort.c create mode 100644 tests/abrt-exploitable/testSignalIll.c create mode 100644 tests/abrt-exploitable/testStackBufferOverflow.c create mode 100644 tests/abrt-exploitable/testStackRecursion.c create mode 100644 tests/abrt-exploitable/testWriteRandom.c
diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am index 767c045..20e0297 100644 --- a/src/plugins/Makefile.am +++ b/src/plugins/Makefile.am @@ -3,6 +3,7 @@ bin_SCRIPTS = \ abrt-action-install-debuginfo \ abrt-action-analyze-core \
- abrt-action-analyze-vulnerability \ abrt-action-analyze-vmcore \ abrt-action-list-dsos \ abrt-action-perform-ccpp-analysis \
@@ -29,9 +30,12 @@ bin_PROGRAMS += \ abrt-bodhi endif
-libexec_PROGRAMS = abrt-action-install-debuginfo-to-abrt-cache +libexec_PROGRAMS = \
- abrt-action-install-debuginfo-to-abrt-cache
-libexec_SCRIPTS = abrt-action-ureport +libexec_SCRIPTS = \
abrt-action-ureport \
abrt-gdb-exploitable
#dist_pluginsconf_DATA = Python.conf
@@ -68,6 +72,7 @@ PYTHON_FILES = \ abrt-action-install-debuginfo.in \ abrt-action-list-dsos \ abrt-action-analyze-core \
- abrt-action-analyze-vulnerability \ abrt-action-analyze-vmcore.in \ abrt-action-perform-ccpp-analysis.in
@@ -84,6 +89,7 @@ EXTRA_DIST = \ abrt-action-analyze-vmcore \ abrt-action-save-kernel-data \ abrt-action-ureport \
- abrt-gdb-exploitable \ https-utils.h \ post_report.xml.in \ abrt-action-analyze-ccpp-local
diff --git a/src/plugins/abrt-action-analyze-vulnerability b/src/plugins/abrt-action-analyze-vulnerability new file mode 100755 index 0000000..aa63ff0 --- /dev/null +++ b/src/plugins/abrt-action-analyze-vulnerability @@ -0,0 +1,41 @@ +#!/bin/sh
+# Do we have the tools we need? +# If no, exit silently. +type gdb >/dev/null 2>&1 || exit 0 +type eu-readelf >/dev/null 2>&1 || exit 0
+# Do we have coredump? +test -r coredump || {
- echo 'No file "coredump" in current directory' >&2
- exit 1
+}
+# Find "cursig: N" and extract N. +# This gets used by abrt-exploitable as a fallback +# if gdb and/or kernel is uncooperative. +# "grep -m1": take the first match (on Linux, every thread has its own +# prstatus struct in the coredump, but the signal number which killed us +# must be the same in all these structs). +SIGNO_OF_THE_COREDUMP=$(eu-readelf -n coredump | grep -m1 -o 'cursig: *[0-9]*' | sed 's/[^0-9]//g') +export SIGNO_OF_THE_COREDUMP
+# Run gdb, hiding its messages. Example: +# Missing separate debuginfo for the main executable file +# Core was generated by... +# Program terminated with signal 11, Segmentation fault. +# #0 0x09fa5348 in ?? () +# We don't want to see all this. +# abrt-exploitable plugin is instructed to create ./exploitable file +# with explanation if severity is >= 4 +GDBOUT=$( +gdb --batch \
- -ex 'python execfile("/usr/libexec/abrt-gdb-exploitable")' \
- -ex 'core-file ./coredump' \
- -ex 'abrt-exploitable 4 ./exploitable' \
- 2>&1 \
+) && exit 0
+# There was an error. Show the messages. +printf "Error while running gdb:\n%s\n" "$GDBOUT" +exit 1 diff --git a/src/plugins/abrt-gdb-exploitable b/src/plugins/abrt-gdb-exploitable new file mode 100755 index 0000000..4ad95e5 --- /dev/null +++ b/src/plugins/abrt-gdb-exploitable @@ -0,0 +1,519 @@ +#!/usr/bin/python +# This is a GDB plugin. +# Usage: +# gdb --batch -ex 'python execfile("THIS_FILE")' -ex run -ex abrt-exploitable PROG +# or +# gdb --batch -ex 'python execfile("THIS_FILE")' -ex 'core COREDUMP' -ex abrt-exploitable
+import sys +import os +import signal +import gdb
+_WRITES_ALWAYS = -1 +_WRITES_IF_MEMREF = -2
+_writing_instr = {
- # insn:N, where N:
- # -1: this insn always writes to memory
- # -2: writes to memory if any operand is a memory operand
- # 2: writes to memory if 2nd (or later) operand is a memory operand
- #
- # Two-operand insns
- "add":2,
- "adc":2,
- "sub":2,
- "sbb":2,
- "and":2,
- "xor":2,
- "or":2,
- "xadd":2,
- "cmpxchg":2,
- # One-operand insns. Can use 1 or _WRITES_IF_MEMREF
- "inc":_WRITES_IF_MEMREF,
- "dec":_WRITES_IF_MEMREF,
- "neg":_WRITES_IF_MEMREF,
- "not":_WRITES_IF_MEMREF,
- "pop":_WRITES_IF_MEMREF,
- # "Set byte on condition". One-operand insns.
- "seta":_WRITES_IF_MEMREF,
- "setae":_WRITES_IF_MEMREF,
- "setb":_WRITES_IF_MEMREF,
- "setbe":_WRITES_IF_MEMREF,
- "setc":_WRITES_IF_MEMREF,
- "sete":_WRITES_IF_MEMREF,
- "setg":_WRITES_IF_MEMREF,
- "setge":_WRITES_IF_MEMREF,
- "setl":_WRITES_IF_MEMREF,
- "setle":_WRITES_IF_MEMREF,
- "setna":_WRITES_IF_MEMREF,
- "setnae":_WRITES_IF_MEMREF,
- "setnb":_WRITES_IF_MEMREF,
- "setnbe":_WRITES_IF_MEMREF,
- "setnc":_WRITES_IF_MEMREF,
- "setne":_WRITES_IF_MEMREF,
- "setng":_WRITES_IF_MEMREF,
- "setnge":_WRITES_IF_MEMREF,
- "setnl":_WRITES_IF_MEMREF,
- "setnle":_WRITES_IF_MEMREF,
- "setno":_WRITES_IF_MEMREF,
- "setnp":_WRITES_IF_MEMREF,
- "setns":_WRITES_IF_MEMREF,
- "setnz":_WRITES_IF_MEMREF,
- "seto":_WRITES_IF_MEMREF,
- "setp":_WRITES_IF_MEMREF,
- "setpe":_WRITES_IF_MEMREF,
- "setpo":_WRITES_IF_MEMREF,
- "sets":_WRITES_IF_MEMREF,
- "setz":_WRITES_IF_MEMREF,
- # Shifts.
- # sarl $2,(%rcx)
- # sarl (%rax) - *implicit* operand (shift count) 1.
- # shld 11,%ecx,(%rdi) - *third* operand is r/m.
- # Luckily, any memory operand is a destination, can use _WRITES_IF_MEMREF.
- "shl":_WRITES_IF_MEMREF,
- "shr":_WRITES_IF_MEMREF,
- "sal":_WRITES_IF_MEMREF,
- "sar":_WRITES_IF_MEMREF,
- "rol":_WRITES_IF_MEMREF,
- "ror":_WRITES_IF_MEMREF,
- "rcl":_WRITES_IF_MEMREF,
- "rcr":_WRITES_IF_MEMREF,
- "shld":_WRITES_IF_MEMREF,
- "shrd":_WRITES_IF_MEMREF,
- # Bit tests. Any memory operand is a destination, can use _WRITES_IF_MEMREF.
- "bts":_WRITES_IF_MEMREF,
- "btr":_WRITES_IF_MEMREF,
- "btc":_WRITES_IF_MEMREF,
- # One-operand (register pair is another, implicit operand).
- "cmpxchg8b":_WRITES_IF_MEMREF,
- "cmpxchg16b":_WRITES_IF_MEMREF,
- # Either mem operand indicates write to mem.
- "xchg":_WRITES_IF_MEMREF,
- # String store insns.
- # Look similar to widening signed move "movs[bwl][wlq]",
- # but aliasing doesn't happen since widening move has two siffixes
- "movs":_WRITES_ALWAYS,
- "stos":_WRITES_ALWAYS,
- # Widening moves never store to mem.
- # May look like we need to list them because otherwise they get caught
- # by "movXXX", but thankfully their 2nd operand is never a memory reference,
- # which "movXXX" wildcard checks.
- #"mov[sz][bwl][wlq]":0,
- # One-operand insn.
- # These are system insns, but they do NOT cause exception in userspace.
- "smsw":_WRITES_IF_MEMREF,
- "sgdt":_WRITES_IF_MEMREF,
- "sidt":_WRITES_IF_MEMREF,
- "sldt":_WRITES_IF_MEMREF,
- "str":_WRITES_IF_MEMREF,
- # FPU/SIMD madness follows.
- # FPU store insns. One-operand.
- "fsts":_WRITES_IF_MEMREF,
- "fstl":_WRITES_IF_MEMREF,
- #"fstt" doesn't exist
- "fstps":_WRITES_IF_MEMREF,
- "fstpl":_WRITES_IF_MEMREF,
- "fstpt":_WRITES_IF_MEMREF,
- # Saving state. One-operand insns.
- "fstcw":_WRITES_IF_MEMREF,
- "fnstcw":_WRITES_IF_MEMREF,
- "fstsw":_WRITES_IF_MEMREF,
- "fnstsw":_WRITES_IF_MEMREF,
- "fstenv":_WRITES_IF_MEMREF,
- "fnstenv":_WRITES_IF_MEMREF,
- "fsave":_WRITES_IF_MEMREF,
- "fnsave":_WRITES_IF_MEMREF,
- "fxsave":_WRITES_IF_MEMREF,
- "xsave":_WRITES_IF_MEMREF,
- "xsaveopt":_WRITES_IF_MEMREF,
- "fsave64":_WRITES_IF_MEMREF,
- "fnsave64":_WRITES_IF_MEMREF,
- "fxsave64":_WRITES_IF_MEMREF,
- "xsave64":_WRITES_IF_MEMREF,
- "xsaveopt64":_WRITES_IF_MEMREF,
- "stmxcsr":_WRITES_IF_MEMREF,
- "vstmxcsr":_WRITES_IF_MEMREF,
- # SIMD store insns.
- # Three-operand insns. Any memory operand is a destination.
- "vcvtps2ph":_WRITES_IF_MEMREF,
- "extractps":_WRITES_IF_MEMREF,
- "vextractps":_WRITES_IF_MEMREF,
- #[v]extractpd does not exist
- "vextractf128":_WRITES_IF_MEMREF,
- "vextracti128":_WRITES_IF_MEMREF,
- "pextr":_WRITES_IF_MEMREF, # covers pextr[bwq]
- "pextrd":_WRITES_IF_MEMREF,
- "vpextr":_WRITES_IF_MEMREF,
- "vpextrd":_WRITES_IF_MEMREF,
- "vmaskmovpd":_WRITES_IF_MEMREF,
- "vmaskmovps":_WRITES_IF_MEMREF,
- "vpmaskmovd":_WRITES_IF_MEMREF,
- "vpmaskmovq":_WRITES_IF_MEMREF,
- # These insns have implicit (%edi) dest operand:
- "maskmovq":_WRITES_ALWAYS, # mmx version
- "maskmovdqu":_WRITES_ALWAYS,
- "vmaskmovdqu":_WRITES_ALWAYS,
- # check binutils/gas/testsuite/gas/i386/* for more weird insns
- # Instruction Set Reference, A-M and N-Z:
- # http://download.intel.com/products/processor/manual/253666.pdf
- # http://download.intel.com/products/processor/manual/253667.pdf
- # SSE4:
- # http://software.intel.com/sites/default/files/m/0/3/c/d/4/18187-d9156103.pdf
- # Instruction Set Extensions:
- # http://download-software.intel.com/sites/default/files/319433-014.pdf
- # Xeon Phi:
- # http://download-software.intel.com/sites/default/files/forum/278102/32736400...
- #"[v]movXXX" - special-cased in the code
- "mov":2
- # Note: stack-writing instructions are omitted
+}
+_pushing_instr = (
- "push",
- "pusha",
- "pushf",
- "enter",
- "call",
- "lcall"
+)
+_intdiv_instr = ("div", "idiv")
+_jumping_instr = (
- "jmp", # indirect jumps/calls with garbage data
- "call", # call: also possible that stack is exhausted (infinite recursion)
- "ljmp",
- "lcall",
- # Yes, lret/iret isn't used in normal userspace code,
- # but it does work (compile with "gcc -nostartfiles -nostdlib -m32"):
- #
- #_start: .globl _start
- # pushf
- # push %cs
- # push $next
- # iret # lret or ret would work too
- #next:
- # movl $42, %ebx
- # movl $1, %eax
- # int $0x80 # exit(42)
- #
- "iret",
- "lret",
- "ret"
+)
+# stack was smashed if we crash on one of these +_return_instr = ("iret", "lret", "ret")
+def _fetch_insn_from_table(ins, table):
- if not ins:
return None
- if ins in table:
if type(table) == dict:
return table[ins]
return ins
- # Drop common byte/word/long/quad suffix and try again
- if ins[-1] in ("b", "w", "l", "q"):
ins = ins[:-1]
if ins in table:
if type(table) == dict:
return table[ins]
return ins
- return None
+class Signal_and_insn:
- def get_signal(self):
self.signo = None
try:
# Requires new kernels which record complete siginfo
# in coredumps (Linux 3.9 still don't have it),
# and new gdb:
sig = gdb.parse_and_eval("$_siginfo.si_signo")
# Requires patched gdb:
#sig = gdb.parse_and_eval("$_signo")
#
# type(sig) = <type 'gdb.Value'>, convert to plain int:
self.signo = int(sig)
except gdb.error:
# "Python Exception <class 'gdb.error'>
# Attempt to extract a component of a value that is not a structure"
# Possible reasons why $_siginfo doesn't exist:
# program is still running, program exited normally,
# we work with a coredump from an old kernel.
#
# Lets see whether we are running from the abrt and it
# provided us with signal number. Horrible hack :(
#
try:
self.signo = int(os.environ["SIGNO_OF_THE_COREDUMP"])
except KeyError:
return False
return True
- def get_instruction(self):
self.current_instruction = None
self.mnemonic = None
self.operands = ""
try:
# just "disassemble $pc" won't work if $pc doesn't point
# inside a known function
instructions = gdb.execute("disassemble $pc,$pc+32", to_string=True)
except gdb.error:
# For example, if tracee already exited normally.
# Another observed case is if $pc points to unmapped area.
# We get "Python Exception <class 'gdb.error'> No registers"
return
raw_instructions = instructions
instructions = []
current = None
for line in raw_instructions.split("\n"):
# line can be:
# "Dump of assembler code from 0xAAAA to 0xBBBB:"
# "[=>] 0x00000000004004dc[ <+0>]: push %rbp"
# (" <+0>" part is present when we run on a live process,
# on coredump it is absent)
# "End of assembler dump."
# "" (empty line)
if line.startswith("=>"):
line = line[2:]
current = len(instructions)
line = line.split(":", 1)
if len(line) < 2: # no ":"?
continue
line = line[1] # drop "foo:"
line = line.strip() # drop leading/trailing whitespace
if line:
instructions.append(line)
if current == None:
# not False! we determined that $pc points to a bad address,
# which is an interesting fact.
return
# There can be a disasm comment: "insn op,op,op # comment";
# strip it, and whitespace on both ends:
t = instructions[current].split("#", 1)[0].strip()
self.current_instruction = t
# Strip prefixes:
while True:
t = t.split(None, 1)
self.mnemonic = t[0]
if len(t) < 2:
break
if self.mnemonic.startswith("rex."):
t = t[1]
continue
if self.mnemonic in (
"data32", "data16", "addr32", "addr16", "rex",
"cs", "ds", "es", "ss", "fs", "gs",
"lock", "rep", "repz", "repnz", "xacquire", "xrelease"
):
t = t[1]
continue
# First word isn't a prefix -> we found the insn word
self.operands = t[1]
break
- def instruction_is_writing(self):
operand = _fetch_insn_from_table(self.mnemonic, _writing_instr)
if not operand:
if not self.mnemonic:
return False
# There are far too many SSE store instructions,
# don't want to pollute the table with them.
# Special-case the check for MOVxxx
# and its SIMD cousins VMOVxxx:
if self.mnemonic[:3] != "mov" and self.mnemonic[:4] != "vmov":
return False
operand = 2
if operand == _WRITES_ALWAYS: # no need to check operands, it's a write
return True
# Memory operands look like this: [%seg:][[-]0xHEXNUM][(%reg[,...])]
# Careful with immediate operands which are $0xHEXNUM
# and FPU register references which are st(N).
if self.operands.startswith("0x") or self.operands.startswith("-0x") or self.operands.startswith("("):
mem_op_pos = 0
else:
mem_op_pos = self.operands.find(",0x")
if mem_op_pos < 0:
mem_op_pos = self.operands.find(",-0x")
if mem_op_pos < 0:
mem_op_pos = self.operands.find(":0x")
if mem_op_pos < 0:
mem_op_pos = self.operands.find(":-0x")
if mem_op_pos < 0:
mem_op_pos = self.operands.find(",(")
if mem_op_pos < 0:
mem_op_pos = self.operands.find(":(")
if mem_op_pos < 0:
return False # no memory operands
mem_op_pos += 1
if operand == _WRITES_IF_MEMREF: # any mem operand indicates write
return True
comma = self.operands.find(",")
if mem_op_pos < comma:
# "%cs:0x0(%rax,%rax,1),foo" - 1st operand is memory
# "%cs:0x0(%rax),foo" - 1st operand is memory
memory_operand = 1
elif comma < 0:
# "%cs:0x0(%rax)" - 1st operand is memory
memory_operand = 1
else:
# mem_op_pos is after comma
# "foo,%cs:0x0(%rax,%rax,1)" - 2nd operand is memory
# (It also can be a third, fourth etc operand)
memory_operand = 2
if operand == memory_operand:
return True
return False
- def instruction_is_pushing(self):
if _fetch_insn_from_table(self.mnemonic, _pushing_instr):
return True
return False
- def instruction_is_division(self):
if _fetch_insn_from_table(self.mnemonic, _intdiv_instr):
return True
return False
- def instruction_is_jumping(self):
if _fetch_insn_from_table(self.mnemonic, _jumping_instr):
return True
return False
- def instruction_is_return(self):
if _fetch_insn_from_table(self.mnemonic, _return_instr):
return True
return False
- #Our initial set of testing will use the list Apple included in their
- #CrashWrangler announcement:
- #
- #Exploitable if:
- # Crash on write instruction
- # Crash executing invalid address
- # Crash calling an invalid address
- # Crash accessing an uninitialized or freed pointer as indicated by
- # using the MallocScribble environment variable
- # Illegal instruction exception
- # Abort due to -fstack-protector, _FORTIFY_SOURCE, heap corruption
- # detected
- # Stack trace of crashing thread contains certain functions such as
- # malloc, free, szone_error, objc_MsgSend, etc.
- def is_exploitable(self):
self.exploitable_rating = 3
self.exploitable_desc = ""
if 0:
pass
# SIGABRT Abort signal from abort(3)
# SIGQUIT Quit from keyboard
# SIGXCPU CPU time limit exceeded
# SIGXFSZ File size limit exceeded
# SIGTRAP Trace/breakpoint trap
# SIGSYS Bad argument to routine (SVr4)
# SIGFPE Floating point exception
# SIGILL Illegal Instruction
# SIGSEGV Invalid memory reference
# SIGBUS Bus error (bad memory access)
elif self.signo == signal.SIGABRT:
self.exploitable_rating = 0
self.exploitable_desc = "ABRT signal (abort() was called?)"
elif self.signo == signal.SIGQUIT:
self.exploitable_rating = 0
self.exploitable_desc = "QUIT signal (Ctrl-\\ pressed?)"
elif self.signo == signal.SIGXCPU:
self.exploitable_rating = 0
self.exploitable_desc = "XCPU signal (over CPU time limit)"
elif self.signo == signal.SIGXFSZ:
self.exploitable_rating = 0
self.exploitable_desc = "XFSZ signal (over file size limit)"
elif self.signo == signal.SIGTRAP:
self.exploitable_rating = 0
self.exploitable_desc = "TRAP signal (can be a bug in a debugger/tracer)"
elif self.signo == signal.SIGSYS:
self.exploitable_rating = 1
self.exploitable_desc = "SYS signal (unknown/masked syscall was called?)"
elif self.signo == signal.SIGFPE:
self.exploitable_rating = 1
self.exploitable_desc = "Arithmetic exception"
if self.instruction_is_division():
self.exploitable_rating = 0
self.exploitable_desc = "Division by zero"
elif self.signo == signal.SIGILL:
self.exploitable_rating = 5
self.exploitable_desc = "Illegal instruction (jump to a random address?)"
# TODO: check that sig is SIGSEGV/SIGBUS?
elif self.instruction_is_pushing():
self.exploitable_rating = 4
self.exploitable_desc = "Stack overflow"
elif self.instruction_is_writing():
self.exploitable_rating = 6
self.exploitable_desc = "Write to an invalid address"
elif self.instruction_is_return():
self.exploitable_rating = 7
self.exploitable_desc = "Subroutine return to an invalid address (corrupted stack?)"
# Note: we check "ret" first, _then_ jumps.
# Corrupted stack is different from corrupted data.
elif self.instruction_is_jumping():
self.exploitable_rating = 6
self.exploitable_desc = "Jump to an invalid address"
elif not self.current_instruction:
self.exploitable_rating = 6
self.exploitable_desc = "Jump to an invalid address"
elif self.signo == signal.SIGBUS:
self.exploitable_rating = 5
self.exploitable_desc = "Access past the end of mapped file, invalid address, unaligned access, etc"
#elif self.signo = signal.SIGfoo:
+class AbrtExploitable(gdb.Command):
- "Analyze a crash to determine exploitability"
- def __init__(self):
super(AbrtExploitable, self).__init__(
"abrt-exploitable",
gdb.COMMAND_SUPPORT, # command class
gdb.COMPLETE_NONE, # completion method
False # => it's not a prefix command
)
- # Called when the command is invoked from GDB
- def invoke(self, args, from_tty):
si = Signal_and_insn()
if not si.get_signal():
sys.stderr.write("Can't get signal no and do exploitability analysis\n")
return
si.get_instruction()
min_rating = 0
if args:
args = args.split(None, 1)
min_rating = int(args[0])
si.is_exploitable()
if si.exploitable_desc:
if si.exploitable_rating >= min_rating:
f = sys.stdout
if args and len(args) > 1:
f = open(args[1], 'w')
f.write("Likely crash reason: " + si.exploitable_desc + "\n")
f.write("Exploitable rating (0-9 scale): " + str(si.exploitable_rating) + "\n")
else:
sys.stderr.write("Exploitability analysis came up empty\n")
+AbrtExploitable() diff --git a/src/plugins/ccpp_event.conf b/src/plugins/ccpp_event.conf index dfc4908..aa8cdb3 100644 --- a/src/plugins/ccpp_event.conf +++ b/src/plugins/ccpp_event.conf @@ -15,10 +15,13 @@ EVENT=post-create analyzer=CCpp exit 1 fi # Try generating backtrace, if it fails we can still use
# the UUID generated by abrt-action-analyze-c
# the hash generated by abrt-action-analyze-c ##satyr migration: #satyr abrt-create-core-stacktrace "$DUMP_DIR" abrt-action-generate-core-backtrace
# Run GDB plugin to see if crash looks exploitable
abrt-action-analyze-vulnerability
# Generate hash abrt-action-analyze-c && abrt-action-list-dsos -m maps -o dso_list && (
diff --git a/tests/abrt-exploitable/.gitignore b/tests/abrt-exploitable/.gitignore new file mode 100644 index 0000000..b0c3b29 --- /dev/null +++ b/tests/abrt-exploitable/.gitignore @@ -0,0 +1,10 @@ +testDivideByZero +testExecuteInvalid +testFloatingPointException +testReadNull +testReadRandom +testSignalAbort +testSignalIll +testStackBufferOverflow +testStackRecursion +testWriteRandom diff --git a/tests/abrt-exploitable/Makefile b/tests/abrt-exploitable/Makefile new file mode 100644 index 0000000..6672fe3 --- /dev/null +++ b/tests/abrt-exploitable/Makefile @@ -0,0 +1,67 @@ +# Copyright (C) 2010, 2011 Red Hat, Inc. +# +# This file is part of ABRT. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/.
+CFLAGS=-g
+TESTS=\
- testDivideByZero\
- testReadNull\
- testStackRecursion\
- testReadRandom\
- testSignalAbort\
- testSignalIll\
- testWriteRandom\
- testExecuteInvalid\
- testStackBufferOverflow\
- testFloatingPointException\
+all: $(TESTS)
+clean:
- rm -f $(TESTS)
+testlive:
- for t in $(TESTS); do \
echo "====="; \
echo "Test: $$t"; \
gdb --batch \
-ex 'python execfile("../../src/plugins/abrt-gdb-exploitable")' \
-ex 'run' \
-ex 'disas $$pc-16,$$pc+16' \
-ex 'abrt-exploitable' \
-ex 'cont' \
-ex 'quit' \
./$$t; \
- done 2>&1 | tee testlive.log
+testcore:
- rm ./core* 2>/dev/null; \
- ulimit -c unlimited; \
- for t in $(TESTS); do \
echo "====="; \
echo "Test: $$t"; \
./$$t && { echo "No crash???"; continue; }; \
mv core* core || { echo "No corefile???"; continue; }; \
gdb --batch \
-ex 'python execfile("../../src/plugins/abrt-gdb-exploitable")' \
-ex 'core ./core' \
-ex 'disas $$pc-16,$$pc+16' \
-ex 'abrt-exploitable' \
-ex 'quit' \
; \
rm core; \
- done 2>&1 | tee testcore.log
diff --git a/tests/abrt-exploitable/testDivideByZero.c b/tests/abrt-exploitable/testDivideByZero.c new file mode 100644 index 0000000..06d1551 --- /dev/null +++ b/tests/abrt-exploitable/testDivideByZero.c @@ -0,0 +1,10 @@ +/*
- Test a divide by zero error
- This error is not exploitable
- */
+#include <stdio.h>
+int main(int argc, char *argv[]) {
- printf("%d\n", 7/0);
+} diff --git a/tests/abrt-exploitable/testExecuteInvalid.c b/tests/abrt-exploitable/testExecuteInvalid.c new file mode 100644 index 0000000..86ce515 --- /dev/null +++ b/tests/abrt-exploitable/testExecuteInvalid.c @@ -0,0 +1,17 @@ +/*
- Test a crash attempting to execute an invalid address
- This error is exploitable
- */
+#include <stdlib.h> +#include <string.h>
+int (*function_pointer)();
+int main(int argc, char *argv[]) {
- char *a;
- a = malloc(1024);
- a = (size_t)a * 1024; // This should put us well outside the valid memory range
- function_pointer = a;
- function_pointer();
+} diff --git a/tests/abrt-exploitable/testFloatingPointException.c b/tests/abrt-exploitable/testFloatingPointException.c new file mode 100644 index 0000000..7931914 --- /dev/null +++ b/tests/abrt-exploitable/testFloatingPointException.c @@ -0,0 +1,10 @@ +/*
- Test the floating point exception signal
- This error is exploitable
- */
+#include <signal.h>
+int main(int argc, char *argv[]) {
- raise(SIGFPE);
+} diff --git a/tests/abrt-exploitable/testReadNull.c b/tests/abrt-exploitable/testReadNull.c new file mode 100644 index 0000000..548cf73 --- /dev/null +++ b/tests/abrt-exploitable/testReadNull.c @@ -0,0 +1,12 @@ +/*
- Test a NULL read
- This error is not exploitable
- */
+#include <stdio.h>
+int main(int argc, char *argv[]) {
- char *a;
- a = 0x0;
- puts(a);
+} diff --git a/tests/abrt-exploitable/testReadRandom.c b/tests/abrt-exploitable/testReadRandom.c new file mode 100644 index 0000000..8493078 --- /dev/null +++ b/tests/abrt-exploitable/testReadRandom.c @@ -0,0 +1,14 @@ +/*
- Test a crash attempting to read invalid memory
- This error is not exploitable
- */
+#include <stdio.h> +#include <stdlib.h>
+int main(int argc, char *argv[]) {
- char *a;
- a = malloc(1024);
- a = (size_t)a * 1024; // This should put us well outside the valid memory range
- printf("%s\n", a);
+} diff --git a/tests/abrt-exploitable/testSignalAbort.c b/tests/abrt-exploitable/testSignalAbort.c new file mode 100644 index 0000000..fc41227 --- /dev/null +++ b/tests/abrt-exploitable/testSignalAbort.c @@ -0,0 +1,10 @@ +/*
- Test the abort signal
- This error is not exploitable
- */
+#include <signal.h>
+int main(int argc, char *argv[]) {
- raise(SIGABRT);
+} diff --git a/tests/abrt-exploitable/testSignalIll.c b/tests/abrt-exploitable/testSignalIll.c new file mode 100644 index 0000000..7783263 --- /dev/null +++ b/tests/abrt-exploitable/testSignalIll.c @@ -0,0 +1,10 @@ +/*
- Test the illegal instruction signal
- This error is exploitable
- */
+#include <signal.h>
+int main(int argc, char *argv[]) {
- raise(SIGILL);
+} diff --git a/tests/abrt-exploitable/testStackBufferOverflow.c b/tests/abrt-exploitable/testStackBufferOverflow.c new file mode 100644 index 0000000..fcbd95f --- /dev/null +++ b/tests/abrt-exploitable/testStackBufferOverflow.c @@ -0,0 +1,23 @@ +/*
- Test a stack buffer overflow
- This test could be exploitable (it needs further analysis)
- */
+#include <stdio.h>
+int i;
+int my_function() {
- char a[2];
- for (i = 0; i < 1024; i++) {
a[i] = 'A';
- }
- printf("%s\n", a);
+}
+int main(int argc, char *argv[]) {
- my_function();
- my_function();
- return 0;
+} diff --git a/tests/abrt-exploitable/testStackRecursion.c b/tests/abrt-exploitable/testStackRecursion.c new file mode 100644 index 0000000..dddf583 --- /dev/null +++ b/tests/abrt-exploitable/testStackRecursion.c @@ -0,0 +1,15 @@ +/*
- Test a crash from stack recursion
- This error is not exploitable
- */
+#include <stdio.h>
+void my_function() {
- char a[1024];
- my_function();
+}
+int main(int argc, char *argv[]) {
- my_function();
+} diff --git a/tests/abrt-exploitable/testWriteRandom.c b/tests/abrt-exploitable/testWriteRandom.c new file mode 100644 index 0000000..812f94c --- /dev/null +++ b/tests/abrt-exploitable/testWriteRandom.c @@ -0,0 +1,15 @@ +/*
- Test a crash attempting to write invalid memory
- This error is exploitable
- */
+#include <stdlib.h> +#include <string.h>
+int main(int argc, char *argv[]) {
- char *a;
- char b[] = "pwnt";
- a = malloc(1024);
- a = (size_t)a * 1024; // This should put us well outside the valid memory range
- strcpy(a, b);
+}
crash-catcher@lists.fedorahosted.org