[ABRT PATCH 1/2 v2] Add gdb python plugin which analyzes coredump for vulnerability

Denys Vlasenko dvlasenk at redhat.com
Mon Jun 24 12:40:13 UTC 2013


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".

V2: switched to regexps; i18n of messages

TODO:
* instruction analyzer is x86 specific now, make it per-arch

Signed-off-by: Denys Vlasenko <dvlasenk at redhat.com>
---
 src/plugins/Makefile.am                            |  10 +-
 src/plugins/abrt-action-analyze-vulnerability      |  41 ++
 src/plugins/abrt-gdb-exploitable                   | 517 +++++++++++++++++++++
 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, 783 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..474c0e4
--- /dev/null
+++ b/src/plugins/abrt-gdb-exploitable
@@ -0,0 +1,517 @@
+#!/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 re
+import gettext
+import gdb
+
+GETTEXT_PROGNAME = "abrt"
+_ = gettext.lgettext
+
+_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/327364001en.pdf
+
+    #"[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
+
+    mem_op1_regex = re.compile("^((-?0x)|[(])")
+    mem_op2_regex = re.compile("[,:]((-?0x)|[(])")
+
+    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 Signal_and_insn.mem_op1_regex.search(self.operands):
+            mem_op_pos = 0
+        else:
+            match = Signal_and_insn.mem_op2_regex.search(self.operands)
+            if not match:
+                return False # no memory operands
+            mem_op_pos = match.start() + 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);
+}
-- 
1.8.1.4



More information about the Crash-catcher mailing list