[ABRT PATCH 1/2] Add gdb python plugin which analyzes coredump for vulnerability. Closes #596

Jiri Moskovcak jmoskovc at redhat.com
Mon Jun 24 08:17:02 UTC 2013


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 at 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/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
> +
> +    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);
> +}
>



More information about the Crash-catcher mailing list