Blame src/plugins/abrt-gdb-exploitable

Packit 8ea169
#!/usr/bin/python3
Packit 8ea169
# This is a GDB plugin.
Packit 8ea169
# Usage:
Packit 8ea169
# gdb --batch -ex 'python exec(open("THIS_FILE").read())' -ex run -ex abrt-exploitable PROG
Packit 8ea169
# or
Packit 8ea169
# gdb --batch -ex 'python exec(open("THIS_FILE").read())' -ex 'core COREDUMP' -ex abrt-exploitable
Packit 8ea169
Packit 8ea169
import sys
Packit 8ea169
import os
Packit 8ea169
import signal
Packit 8ea169
import re
Packit 8ea169
import gettext
Packit 8ea169
import locale
Packit 8ea169
import gdb
Packit 8ea169
Packit 8ea169
GETTEXT_PROGNAME = "abrt"
Packit 8ea169
_ = gettext.gettext
Packit 8ea169
Packit 8ea169
def init_gettext():
Packit 8ea169
    try:
Packit 8ea169
        locale.setlocale(locale.LC_ALL, "")
Packit 8ea169
    except locale.Error:
Packit 8ea169
        os.environ['LC_ALL'] = 'C'
Packit 8ea169
        locale.setlocale(locale.LC_ALL, "")
Packit 8ea169
    # Defeat "AttributeError: 'module' object has no attribute 'nl_langinfo'"
Packit 8ea169
    try:
Packit 8ea169
        gettext.bind_textdomain_codeset(GETTEXT_PROGNAME, locale.nl_langinfo(locale.CODESET))
Packit 8ea169
    except AttributeError:
Packit 8ea169
        pass
Packit 8ea169
    gettext.bindtextdomain(GETTEXT_PROGNAME, '/usr/share/locale')
Packit 8ea169
    gettext.textdomain(GETTEXT_PROGNAME)
Packit 8ea169
Packit 8ea169
_WRITES_ALWAYS = -1
Packit 8ea169
_WRITES_IF_MEMREF = -2
Packit 8ea169
Packit 8ea169
_x86_writing_instr = {
Packit 8ea169
    # insn:N, where N:
Packit 8ea169
    # -1: this insn always writes to memory
Packit 8ea169
    # -2: writes to memory if any operand is a memory operand
Packit 8ea169
    # 2:  writes to memory if 2nd (or later) operand is a memory operand
Packit 8ea169
    #
Packit 8ea169
    # Two-operand insns
Packit 8ea169
    "add": 2,
Packit 8ea169
    "adc": 2,
Packit 8ea169
    "sub": 2,
Packit 8ea169
    "sbb": 2,
Packit 8ea169
    "and": 2,
Packit 8ea169
    "xor": 2,
Packit 8ea169
    "or": 2,
Packit 8ea169
    "xadd": 2,
Packit 8ea169
    "cmpxchg": 2,
Packit 8ea169
    # One-operand insns. Can use 1 or _WRITES_IF_MEMREF
Packit 8ea169
    "inc": _WRITES_IF_MEMREF,
Packit 8ea169
    "dec": _WRITES_IF_MEMREF,
Packit 8ea169
    "neg": _WRITES_IF_MEMREF,
Packit 8ea169
    "not": _WRITES_IF_MEMREF,
Packit 8ea169
    "pop": _WRITES_IF_MEMREF,
Packit 8ea169
    # "Set byte on condition". One-operand insns.
Packit 8ea169
    "seta": _WRITES_IF_MEMREF,
Packit 8ea169
    "setae": _WRITES_IF_MEMREF,
Packit 8ea169
    "setb": _WRITES_IF_MEMREF,
Packit 8ea169
    "setbe": _WRITES_IF_MEMREF,
Packit 8ea169
    "setc": _WRITES_IF_MEMREF,
Packit 8ea169
    "sete": _WRITES_IF_MEMREF,
Packit 8ea169
    "setg": _WRITES_IF_MEMREF,
Packit 8ea169
    "setge": _WRITES_IF_MEMREF,
Packit 8ea169
    "setl": _WRITES_IF_MEMREF,
Packit 8ea169
    "setle": _WRITES_IF_MEMREF,
Packit 8ea169
    "setna": _WRITES_IF_MEMREF,
Packit 8ea169
    "setnae": _WRITES_IF_MEMREF,
Packit 8ea169
    "setnb": _WRITES_IF_MEMREF,
Packit 8ea169
    "setnbe": _WRITES_IF_MEMREF,
Packit 8ea169
    "setnc": _WRITES_IF_MEMREF,
Packit 8ea169
    "setne": _WRITES_IF_MEMREF,
Packit 8ea169
    "setng": _WRITES_IF_MEMREF,
Packit 8ea169
    "setnge": _WRITES_IF_MEMREF,
Packit 8ea169
    "setnl": _WRITES_IF_MEMREF,
Packit 8ea169
    "setnle": _WRITES_IF_MEMREF,
Packit 8ea169
    "setno": _WRITES_IF_MEMREF,
Packit 8ea169
    "setnp": _WRITES_IF_MEMREF,
Packit 8ea169
    "setns": _WRITES_IF_MEMREF,
Packit 8ea169
    "setnz": _WRITES_IF_MEMREF,
Packit 8ea169
    "seto": _WRITES_IF_MEMREF,
Packit 8ea169
    "setp": _WRITES_IF_MEMREF,
Packit 8ea169
    "setpe": _WRITES_IF_MEMREF,
Packit 8ea169
    "setpo": _WRITES_IF_MEMREF,
Packit 8ea169
    "sets": _WRITES_IF_MEMREF,
Packit 8ea169
    "setz": _WRITES_IF_MEMREF,
Packit 8ea169
    # Shifts.
Packit 8ea169
    # sarl $2,(%rcx)
Packit 8ea169
    # sarl (%rax) - *implicit* operand (shift count) 1.
Packit 8ea169
    # shld 11,%ecx,(%rdi) - *third* operand is r/m.
Packit 8ea169
    # Luckily, any memory operand is a destination, can use _WRITES_IF_MEMREF.
Packit 8ea169
    "shl": _WRITES_IF_MEMREF,
Packit 8ea169
    "shr": _WRITES_IF_MEMREF,
Packit 8ea169
    "sal": _WRITES_IF_MEMREF,
Packit 8ea169
    "sar": _WRITES_IF_MEMREF,
Packit 8ea169
    "rol": _WRITES_IF_MEMREF,
Packit 8ea169
    "ror": _WRITES_IF_MEMREF,
Packit 8ea169
    "rcl": _WRITES_IF_MEMREF,
Packit 8ea169
    "rcr": _WRITES_IF_MEMREF,
Packit 8ea169
    "shld": _WRITES_IF_MEMREF,
Packit 8ea169
    "shrd": _WRITES_IF_MEMREF,
Packit 8ea169
    # Bit tests. Any memory operand is a destination, can use _WRITES_IF_MEMREF.
Packit 8ea169
    "bts": _WRITES_IF_MEMREF,
Packit 8ea169
    "btr": _WRITES_IF_MEMREF,
Packit 8ea169
    "btc": _WRITES_IF_MEMREF,
Packit 8ea169
    # One-operand (register pair is another, implicit operand).
Packit 8ea169
    "cmpxchg8b": _WRITES_IF_MEMREF,
Packit 8ea169
    "cmpxchg16b": _WRITES_IF_MEMREF,
Packit 8ea169
Packit 8ea169
    # Either mem operand indicates write to mem.
Packit 8ea169
    "xchg": _WRITES_IF_MEMREF,
Packit 8ea169
Packit 8ea169
    # String store insns.
Packit 8ea169
    # Look similar to widening signed move "movs[bwl][wlq]",
Packit 8ea169
    # but aliasing doesn't happen since widening move has two siffixes
Packit 8ea169
    "movs": _WRITES_ALWAYS,
Packit 8ea169
    "stos": _WRITES_ALWAYS,
Packit 8ea169
    # Widening moves never store to mem.
Packit 8ea169
    # May look like we need to list them because otherwise they get caught
Packit 8ea169
    # by "movXXX", but thankfully their 2nd operand is never a memory reference,
Packit 8ea169
    # which "movXXX" wildcard checks.
Packit 8ea169
    #"mov[sz][bwl][wlq]":0,
Packit 8ea169
Packit 8ea169
    # One-operand insn.
Packit 8ea169
    # These are system insns, but they do NOT cause exception in userspace.
Packit 8ea169
    "smsw": _WRITES_IF_MEMREF,
Packit 8ea169
    "sgdt": _WRITES_IF_MEMREF,
Packit 8ea169
    "sidt": _WRITES_IF_MEMREF,
Packit 8ea169
    "sldt": _WRITES_IF_MEMREF,
Packit 8ea169
    "str": _WRITES_IF_MEMREF,
Packit 8ea169
Packit 8ea169
    # FPU/SIMD madness follows.
Packit 8ea169
Packit 8ea169
    # FPU store insns. One-operand.
Packit 8ea169
    "fsts": _WRITES_IF_MEMREF,
Packit 8ea169
    "fstl": _WRITES_IF_MEMREF,
Packit 8ea169
    #"fstt" doesn't exist
Packit 8ea169
    "fstps": _WRITES_IF_MEMREF,
Packit 8ea169
    "fstpl": _WRITES_IF_MEMREF,
Packit 8ea169
    "fstpt": _WRITES_IF_MEMREF,
Packit 8ea169
    # Saving state. One-operand insns.
Packit 8ea169
    "fstcw": _WRITES_IF_MEMREF,
Packit 8ea169
    "fnstcw": _WRITES_IF_MEMREF,
Packit 8ea169
    "fstsw": _WRITES_IF_MEMREF,
Packit 8ea169
    "fnstsw": _WRITES_IF_MEMREF,
Packit 8ea169
    "fstenv": _WRITES_IF_MEMREF,
Packit 8ea169
    "fnstenv": _WRITES_IF_MEMREF,
Packit 8ea169
    "fsave": _WRITES_IF_MEMREF,
Packit 8ea169
    "fnsave": _WRITES_IF_MEMREF,
Packit 8ea169
    "fxsave": _WRITES_IF_MEMREF,
Packit 8ea169
    "xsave": _WRITES_IF_MEMREF,
Packit 8ea169
    "xsaveopt": _WRITES_IF_MEMREF,
Packit 8ea169
    "fsave64": _WRITES_IF_MEMREF,
Packit 8ea169
    "fnsave64": _WRITES_IF_MEMREF,
Packit 8ea169
    "fxsave64": _WRITES_IF_MEMREF,
Packit 8ea169
    "xsave64": _WRITES_IF_MEMREF,
Packit 8ea169
    "xsaveopt64": _WRITES_IF_MEMREF,
Packit 8ea169
    "stmxcsr": _WRITES_IF_MEMREF,
Packit 8ea169
    "vstmxcsr": _WRITES_IF_MEMREF,
Packit 8ea169
    # SIMD store insns.
Packit 8ea169
    # Three-operand insns. Any memory operand is a destination.
Packit 8ea169
    "vcvtps2ph": _WRITES_IF_MEMREF,
Packit 8ea169
    "extractps": _WRITES_IF_MEMREF,
Packit 8ea169
    "vextractps": _WRITES_IF_MEMREF,
Packit 8ea169
    #[v]extractpd does not exist
Packit 8ea169
    "vextractf128": _WRITES_IF_MEMREF,
Packit 8ea169
    "vextracti128": _WRITES_IF_MEMREF,
Packit 8ea169
    "pextr": _WRITES_IF_MEMREF,       # covers pextr[bwq]
Packit 8ea169
    "pextrd": _WRITES_IF_MEMREF,
Packit 8ea169
    "vpextr": _WRITES_IF_MEMREF,
Packit 8ea169
    "vpextrd": _WRITES_IF_MEMREF,
Packit 8ea169
    "vmaskmovpd": _WRITES_IF_MEMREF,
Packit 8ea169
    "vmaskmovps": _WRITES_IF_MEMREF,
Packit 8ea169
    "vpmaskmovd": _WRITES_IF_MEMREF,
Packit 8ea169
    "vpmaskmovq": _WRITES_IF_MEMREF,
Packit 8ea169
    # These insns have implicit (%edi) dest operand:
Packit 8ea169
    "maskmovq": _WRITES_ALWAYS,    # mmx version
Packit 8ea169
    "maskmovdqu": _WRITES_ALWAYS,
Packit 8ea169
    "vmaskmovdqu": _WRITES_ALWAYS,
Packit 8ea169
Packit 8ea169
    # check binutils/gas/testsuite/gas/i386/* for more weird insns
Packit 8ea169
    # Instruction Set Reference, A-M and N-Z:
Packit 8ea169
    # http://download.intel.com/products/processor/manual/253666.pdf
Packit 8ea169
    # http://download.intel.com/products/processor/manual/253667.pdf
Packit 8ea169
    # SSE4:
Packit 8ea169
    # http://software.intel.com/sites/default/files/m/0/3/c/d/4/18187-d9156103.pdf
Packit 8ea169
    # Instruction Set Extensions:
Packit 8ea169
    # http://download-software.intel.com/sites/default/files/319433-014.pdf
Packit 8ea169
    # Xeon Phi:
Packit 8ea169
    # http://download-software.intel.com/sites/default/files/forum/278102/327364001en.pdf
Packit 8ea169
Packit 8ea169
    #"[v]movXXX" - special-cased in the code
Packit 8ea169
    "mov": 2
Packit 8ea169
Packit 8ea169
    # Note: stack-writing instructions are omitted
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
_x86_pushing_instr = (
Packit 8ea169
    "push",
Packit 8ea169
    "pusha",
Packit 8ea169
    "pushf",
Packit 8ea169
    "enter",
Packit 8ea169
    "call",
Packit 8ea169
    "lcall"
Packit 8ea169
)
Packit 8ea169
Packit 8ea169
_x86_intdiv_instr = ("div", "idiv")
Packit 8ea169
Packit 8ea169
_x86_jumping_instr = (
Packit 8ea169
    "jmp",  # indirect jumps/calls with garbage data
Packit 8ea169
    "call", # call: also possible that stack is exhausted (infinite recursion)
Packit 8ea169
    "ljmp",
Packit 8ea169
    "lcall",
Packit 8ea169
    # Yes, lret/iret isn't used in normal userspace code,
Packit 8ea169
    # but it does work (compile with "gcc -nostartfiles -nostdlib -m32"):
Packit 8ea169
    #
Packit 8ea169
    #_start: .globl  _start
Packit 8ea169
    #        pushf
Packit 8ea169
    #        push    %cs
Packit 8ea169
    #        push    $next
Packit 8ea169
    #        iret            # lret or ret would work too
Packit 8ea169
    #next:
Packit 8ea169
    #        movl    $42, %ebx
Packit 8ea169
    #        movl    $1, %eax
Packit 8ea169
    #        int     $0x80   # exit(42)
Packit 8ea169
    #
Packit 8ea169
    "iret",
Packit 8ea169
    "lret",
Packit 8ea169
    "ret"
Packit 8ea169
)
Packit 8ea169
Packit 8ea169
# stack was smashed if we crash on one of these
Packit 8ea169
_x86_return_instr = ("iret", "lret", "ret")
Packit 8ea169
Packit 8ea169
_x86_mem_op1_regex = re.compile("^((-?0x)|[(])")
Packit 8ea169
_x86_mem_op2_regex = re.compile("[,:]((-?0x)|[(])")
Packit 8ea169
Packit 8ea169
def _x86_fetch_insn_from_table(ins, table):
Packit 8ea169
    if not ins:
Packit 8ea169
        return None
Packit 8ea169
    if ins in table:
Packit 8ea169
        if type(table) == dict:
Packit 8ea169
            return table[ins]
Packit 8ea169
        return ins
Packit 8ea169
    # Drop common byte/word/long/quad suffix and try again
Packit 8ea169
    if ins[-1] in ("b", "w", "l", "q"):
Packit 8ea169
        ins = ins[:-1]
Packit 8ea169
        if ins in table:
Packit 8ea169
            if type(table) == dict:
Packit 8ea169
                return table[ins]
Packit 8ea169
            return ins
Packit 8ea169
    return None
Packit 8ea169
Packit 8ea169
Packit 8ea169
class SignalAndInsn:
Packit 8ea169
Packit 8ea169
    def x86_instruction_is_store(self):
Packit 8ea169
        operand = _x86_fetch_insn_from_table(self.mnemonic, _x86_writing_instr)
Packit 8ea169
        if not operand:
Packit 8ea169
            if not self.mnemonic:
Packit 8ea169
                return False
Packit 8ea169
            # There are far too many SSE store instructions,
Packit 8ea169
            # don't want to pollute the table with them.
Packit 8ea169
            # Special-case the check for MOVxxx
Packit 8ea169
            # and its SIMD cousins VMOVxxx:
Packit 8ea169
            if self.mnemonic[:3] != "mov" and self.mnemonic[:4] != "vmov":
Packit 8ea169
                return False
Packit 8ea169
            operand = 2
Packit 8ea169
Packit 8ea169
        if operand == _WRITES_ALWAYS:  # no need to check operands, it's a write
Packit 8ea169
            return True
Packit 8ea169
Packit 8ea169
        # Memory operands look like this: [%seg:][[-]0xHEXNUM][(%reg[,...])]
Packit 8ea169
        # Careful with immediate operands which are $0xHEXNUM
Packit 8ea169
        # and FPU register references which are st(N).
Packit 8ea169
        if _x86_mem_op1_regex.search(self.operands):
Packit 8ea169
            mem_op_pos = 0
Packit 8ea169
        else:
Packit 8ea169
            match = _x86_mem_op2_regex.search(self.operands)
Packit 8ea169
            if not match:
Packit 8ea169
                return False # no memory operands
Packit 8ea169
            mem_op_pos = match.start() + 1
Packit 8ea169
Packit 8ea169
        if operand == _WRITES_IF_MEMREF:  # any mem operand indicates write
Packit 8ea169
            return True
Packit 8ea169
Packit 8ea169
        comma = self.operands.find(",")
Packit 8ea169
        if mem_op_pos < comma:
Packit 8ea169
            # "%cs:0x0(%rax,%rax,1),foo" - 1st operand is memory
Packit 8ea169
            # "%cs:0x0(%rax),foo" - 1st operand is memory
Packit 8ea169
            memory_operand = 1
Packit 8ea169
        elif comma < 0:
Packit 8ea169
            # "%cs:0x0(%rax)" - 1st operand is memory
Packit 8ea169
            memory_operand = 1
Packit 8ea169
        else:
Packit 8ea169
            # mem_op_pos is after comma
Packit 8ea169
            # "foo,%cs:0x0(%rax,%rax,1)" - 2nd operand is memory
Packit 8ea169
            # (It also can be a third, fourth etc operand)
Packit 8ea169
            memory_operand = 2
Packit 8ea169
Packit 8ea169
        if operand == memory_operand:
Packit 8ea169
            return True
Packit 8ea169
        return False
Packit 8ea169
Packit 8ea169
    def x86_get_instruction(self):
Packit 8ea169
        try:
Packit 8ea169
            # just "disassemble $pc" won't work if $pc doesn't point
Packit 8ea169
            # inside a known function
Packit 8ea169
            raw_instructions = gdb.execute("disassemble $pc,$pc+32", to_string=True)
Packit 8ea169
        except gdb.error:
Packit 8ea169
            # For example, if tracee already exited normally.
Packit 8ea169
            # Another observed case is if $pc points to unmapped area.
Packit 8ea169
            # We get "Python Exception <class 'gdb.error'> No registers"
Packit 8ea169
            return
Packit 8ea169
Packit 8ea169
        instructions = []
Packit 8ea169
        current = None
Packit 8ea169
        for line in raw_instructions.split("\n"):
Packit 8ea169
            # line can be:
Packit 8ea169
            # "Dump of assembler code from 0xAAAA to 0xBBBB:"
Packit 8ea169
            # "[=>] 0x00000000004004dc[ <+0>]:  push   %rbp"
Packit 8ea169
            #   (" <+0>" part is present when we run on a live process,
Packit 8ea169
            #   on coredump it is absent)
Packit 8ea169
            # "End of assembler dump."
Packit 8ea169
            # "" (empty line)
Packit 8ea169
            if line.startswith("=>"):
Packit 8ea169
                line = line[2:]
Packit 8ea169
                current = len(instructions)
Packit 8ea169
            line = line.split(":", 1)
Packit 8ea169
            if len(line) < 2:        # no ":"?
Packit 8ea169
                continue
Packit 8ea169
            line = line[1]           # drop "foo:"
Packit 8ea169
            line = line.strip()      # drop leading/trailing whitespace
Packit 8ea169
            if line:
Packit 8ea169
                instructions.append(line)
Packit 8ea169
        if current == None:
Packit 8ea169
            # we determined that $pc points to a bad address,
Packit 8ea169
            # which is an interesting fact.
Packit 8ea169
            return
Packit 8ea169
Packit 8ea169
        # There can be a disasm comment: "insn op,op,op  # comment";
Packit 8ea169
        # strip it, and whitespace on both ends:
Packit 8ea169
        t = instructions[current].split("#", 1)[0].strip()
Packit 8ea169
        self.current_instruction = t
Packit 8ea169
        # Strip prefixes:
Packit 8ea169
        while True:
Packit 8ea169
            t = t.split(None, 1)
Packit 8ea169
            self.mnemonic = t[0]
Packit 8ea169
            if len(t) < 2:
Packit 8ea169
                break
Packit 8ea169
            if self.mnemonic.startswith("rex."):
Packit 8ea169
                t = t[1]
Packit 8ea169
                continue
Packit 8ea169
            if self.mnemonic in (
Packit 8ea169
                    "data32", "data16", "addr32", "addr16", "rex",
Packit 8ea169
                    "cs", "ds", "es", "ss", "fs", "gs",
Packit 8ea169
                    "lock", "rep", "repz", "repnz", "xacquire", "xrelease"
Packit 8ea169
            ):
Packit 8ea169
                t = t[1]
Packit 8ea169
                continue
Packit 8ea169
            # First word isn't a prefix -> we found the insn word
Packit 8ea169
            self.operands = t[1]
Packit 8ea169
            break
Packit 8ea169
Packit 8ea169
        self.instruction_is_pushing = (_x86_fetch_insn_from_table(self.mnemonic, _x86_pushing_instr) is not None)
Packit 8ea169
        self.instruction_is_division = (_x86_fetch_insn_from_table(self.mnemonic, _x86_intdiv_instr) is not None)
Packit 8ea169
        self.instruction_is_branch = (_x86_fetch_insn_from_table(self.mnemonic, _x86_jumping_instr) is not None)
Packit 8ea169
        self.instruction_is_return = (_x86_fetch_insn_from_table(self.mnemonic, _x86_return_instr) is not None)
Packit 8ea169
        self.instruction_is_store = self.x86_instruction_is_store()
Packit 8ea169
Packit 8ea169
Packit 8ea169
    def ppc_get_instruction(self):
Packit 8ea169
        try:
Packit 8ea169
            # just "disassemble $pc" won't work if $pc doesn't point
Packit 8ea169
            # inside a known function
Packit 8ea169
            raw_instructions = gdb.execute("disassemble $pc,$pc+32", to_string=True)
Packit 8ea169
        except gdb.error:
Packit 8ea169
            # For example, if tracee already exited normally.
Packit 8ea169
            # Another observed case is if $pc points to unmapped area.
Packit 8ea169
            # We get "Python Exception <class 'gdb.error'> No registers"
Packit 8ea169
            return
Packit 8ea169
Packit 8ea169
        instructions = []
Packit 8ea169
        current = None
Packit 8ea169
        for line in raw_instructions.split("\n"):
Packit 8ea169
            # line can be:
Packit 8ea169
            # "Dump of assembler code from 0xAAAA to 0xBBBB:"
Packit 8ea169
            # "[=>] 0x00000000004004dc[ <+0>]:  push   %rbp"
Packit 8ea169
            #   (" <+0>" part is present when we run on a live process,
Packit 8ea169
            #   on coredump it is absent)
Packit 8ea169
            # "End of assembler dump."
Packit 8ea169
            # "" (empty line)
Packit 8ea169
            if line.startswith("=>"):
Packit 8ea169
                line = line[2:]
Packit 8ea169
                current = len(instructions)
Packit 8ea169
            line = line.split(":", 1)
Packit 8ea169
            if len(line) < 2:        # no ":"?
Packit 8ea169
                continue
Packit 8ea169
            line = line[1]           # drop "foo:"
Packit 8ea169
            line = line.strip()      # drop leading/trailing whitespace
Packit 8ea169
            if line:
Packit 8ea169
                instructions.append(line)
Packit 8ea169
        if current is None:
Packit 8ea169
            # we determined that $pc points to a bad address,
Packit 8ea169
            # which is an interesting fact.
Packit 8ea169
            return
Packit 8ea169
Packit 8ea169
        # There can be a disasm comment: "insn op,op,op  # comment";
Packit 8ea169
        # strip it, and whitespace on both ends:
Packit 8ea169
        t = instructions[current].split("#", 1)[0].strip()
Packit 8ea169
        self.current_instruction = t
Packit 8ea169
        # Split it into mnemonic and operands
Packit 8ea169
        t = t.split(None, 1)
Packit 8ea169
        self.mnemonic = t[0]
Packit 8ea169
        if len(t) > 1:
Packit 8ea169
            self.operands = t[1]
Packit 8ea169
Packit 8ea169
        self.instruction_is_store = self.mnemonic.startswith("st")
Packit 8ea169
        self.instruction_is_branch = self.mnemonic.startswith("b")
Packit 8ea169
        self.instruction_is_pushing = (self.instruction_is_store and "(r1)" in self.operands)
Packit 8ea169
        # Looks like div[o] insns on ppc don't cause exceptions
Packit 8ea169
        # (need to check whether, and how, FPE is generated)
Packit 8ea169
        #self.instruction_is_division =
Packit 8ea169
        # On ppc, return insn is b[cond]lr. TODO: is cond form ever used by gcc?
Packit 8ea169
        self.instruction_is_return = (self.mnemonic == "blr")
Packit 8ea169
Packit 8ea169
Packit 8ea169
    def get_instruction(self):
Packit 8ea169
        self.current_instruction = None
Packit 8ea169
        self.mnemonic = None
Packit 8ea169
        self.operands = ""
Packit 8ea169
        self.instruction_is_division = None
Packit 8ea169
        self.instruction_is_store = None
Packit 8ea169
        self.instruction_is_pushing = None
Packit 8ea169
        self.instruction_is_return = None
Packit 8ea169
        self.instruction_is_branch = None
Packit 8ea169
        try:
Packit 8ea169
            arch = gdb.execute("show architecture", to_string=True)
Packit 8ea169
            # Examples of the string we get:
Packit 8ea169
            # The target architecture is set automatically (currently i386)
Packit 8ea169
            # The target architecture is set automatically (currently i386:x86-64)
Packit 8ea169
            # The target architecture is set automatically (currently powerpc:common64)
Packit 8ea169
            if " i386" in arch:
Packit 8ea169
                return self.x86_get_instruction()
Packit 8ea169
            if " powerpc" in arch:
Packit 8ea169
                return self.ppc_get_instruction()
Packit 8ea169
        except gdb.error:
Packit 8ea169
            return
Packit 8ea169
Packit 8ea169
    def get_signal(self):
Packit 8ea169
        self.signo = None
Packit 8ea169
        self.si_code = None
Packit 8ea169
        try:
Packit 8ea169
            # Requires new kernels which record complete siginfo
Packit 8ea169
            # in coredumps (Linux 3.9 still don't have it),
Packit 8ea169
            # and new gdb:
Packit 8ea169
            sig = gdb.parse_and_eval("$_siginfo.si_signo")
Packit 8ea169
            code = gdb.parse_and_eval("$_siginfo.si_code")
Packit 8ea169
            # Requires patched gdb:
Packit 8ea169
            #sig = gdb.parse_and_eval("$_signo")
Packit 8ea169
            #
Packit 8ea169
            # type(sig) = <type 'gdb.Value'>, convert to plain int:
Packit 8ea169
            self.signo = int(sig)
Packit 8ea169
            self.si_code = int(code)
Packit 8ea169
        except gdb.error:
Packit 8ea169
            # "Python Exception <class 'gdb.error'>
Packit 8ea169
            #  Attempt to extract a component of a value that is not a structure"
Packit 8ea169
            # Possible reasons why $_siginfo doesn't exist:
Packit 8ea169
            # program is still running, program exited normally,
Packit 8ea169
            # we work with a coredump from an old kernel.
Packit 8ea169
            #
Packit 8ea169
            # Lets see whether we are running from the abrt and it
Packit 8ea169
            # provided us with signal number. Horrible hack :(
Packit 8ea169
            #
Packit 8ea169
            try:
Packit 8ea169
                self.signo = int(os.environ["SIGNO_OF_THE_COREDUMP"])
Packit 8ea169
            except KeyError:
Packit 8ea169
                return False
Packit 8ea169
        return True
Packit 8ea169
Packit 8ea169
    #Our initial set of testing will use the list Apple included in their
Packit 8ea169
    #CrashWrangler announcement:
Packit 8ea169
    #
Packit 8ea169
    #Exploitable if:
Packit 8ea169
    #        Crash on write instruction
Packit 8ea169
    #        Crash executing invalid address
Packit 8ea169
    #        Crash calling an invalid address
Packit 8ea169
    #        Crash accessing an uninitialized or freed pointer as indicated by
Packit 8ea169
    #            using the MallocScribble environment variable
Packit 8ea169
    #        Illegal instruction exception
Packit 8ea169
    #        Abort due to -fstack-protector, _FORTIFY_SOURCE, heap corruption
Packit 8ea169
    #            detected
Packit 8ea169
    #        Stack trace of crashing thread contains certain functions such as
Packit 8ea169
    #            malloc, free, szone_error, objc_MsgSend, etc.
Packit 8ea169
    def is_exploitable(self):
Packit 8ea169
        self.exploitable_rating = 3
Packit 8ea169
        self.exploitable_desc = ""
Packit 8ea169
Packit 8ea169
        # siginfo.si_code:
Packit 8ea169
        # If <= 0, then it's not a crash:
Packit 8ea169
        #  SI_ASYNCNL = -60 /* asynch name lookup completion */
Packit 8ea169
        #  SI_TKILL   = -6  /* tkill (and tgkill?) */
Packit 8ea169
        #  SI_SIGIO   = -5  /* queued SIGIO */
Packit 8ea169
        #  SI_ASYNCIO = -4  /* AIO completion */
Packit 8ea169
        #  SI_MESGQ   = -3  /* real time mesq state change */
Packit 8ea169
        #  SI_TIMER   = -2  /* timer expiration (timer_create() with SIGEV_SIGNAL) */
Packit 8ea169
        #  SI_QUEUE   = -1  /* sigqueue */
Packit 8ea169
        #  SI_USER    = 0   /* kill, sigsend */
Packit 8ea169
        # Crashes have si_code > 0:
Packit 8ea169
        # testDivideByZero:   SIGFPE  si_code=FPE_INTDIV(1), si_addr=0x40054a
Packit 8ea169
        # x86-64 opcode 0x62: SIGILL  si_code=ILL_ILLOPN(2), si_addr=0x40053f
Packit 8ea169
        # x86-64 priv.insn.:  SIGSEGV si_code=SI_KERNEL(128), si_addr=0
Packit 8ea169
        # testExecuteInvalid: SIGSEGV si_code=SEGV_MAPERR(1), si_addr=0x1c2404000
Packit 8ea169
        # testStackBufferOverflow ("ret" to bad address):
Packit 8ea169
        #                     SIGSEGV si_code=SI_KERNEL(128), si_addr=0
Packit 8ea169
        # testStackRecursion: SIGSEGV si_code=SEGV_MAPERR(1), si_addr=0x7fff4c216d28
Packit 8ea169
        # testWriteRandom:    SIGSEGV si_code=SEGV_MAPERR(1), si_addr=0x1eb004004
Packit 8ea169
        # However:
Packit 8ea169
        # Keyboard signals (^C INT, ^\ QUIT, ^Z TSTP) also have si_code=SI_KERNEL.
Packit 8ea169
        # SIGWINCH has si_code=SI_KERNEL.
Packit 8ea169
        # SIGALRM from alarm(N) has si_code=SI_KERNEL.
Packit 8ea169
        # Surprisingly, SIGPIPE has si_code=SI_USER!
Packit 8ea169
        if self.si_code is not None:
Packit 8ea169
            # Filter out user-generated signals:
Packit 8ea169
            if self.si_code == -6 or self.si_code == -1:  # SI_TKILL/SI_QUEUE
Packit 8ea169
                self.exploitable_rating = 0
Packit 8ea169
                self.exploitable_desc = _("Signal sent by userspace code")
Packit 8ea169
                return
Packit 8ea169
            if self.si_code < 0:
Packit 8ea169
                self.exploitable_rating = 0
Packit 8ea169
                self.exploitable_desc = _("Signal sent by timer/IO/async event")
Packit 8ea169
                return
Packit 8ea169
            # Unfortunately, this isn't reliable to flag user-sent signals:
Packit 8ea169
            # not only SIGPIPE, but some other kernel signals have SI_USER
Packit 8ea169
            # (grep kernel sources for "send_sig(sig, ..., 0)").
Packit 8ea169
            # At least we know it's not a crash.
Packit 8ea169
            if self.si_code == 0:  # SI_USER
Packit 8ea169
                self.exploitable_rating = 0
Packit 8ea169
                self.exploitable_desc = _("Signal has siginfo.si_code = SI_USER")
Packit 8ea169
                # Special case (a kernel buglet?)
Packit 8ea169
                if self.signo == signal.SIGPIPE:
Packit 8ea169
                    self.exploitable_desc = _("Signal due to write to closed pipe")
Packit 8ea169
                return
Packit 8ea169
            # And kernel-generated ones:
Packit 8ea169
            if self.si_code == 0x80:  # SI_KERNEL
Packit 8ea169
                if self.signo in (signal.SIGINT, signal.SIGQUIT, signal.SIGTSTP):
Packit 8ea169
                    self.exploitable_rating = 0
Packit 8ea169
                    self.exploitable_desc = _("Signal sent by keyboard")
Packit 8ea169
                    return
Packit 8ea169
                if self.signo in (signal.SIGTTIN, signal.SIGTTOU, signal.SIGHUP):
Packit 8ea169
                    self.exploitable_rating = 0
Packit 8ea169
                    self.exploitable_desc = _("Job control signal sent by kernel")
Packit 8ea169
                    return
Packit 8ea169
                if self.signo == signal.SIGWINCH:
Packit 8ea169
                    self.exploitable_rating = 0
Packit 8ea169
                    self.exploitable_desc = _("Signal sent by window resize")
Packit 8ea169
                    return
Packit 8ea169
                if self.signo == signal.SIGALRM:
Packit 8ea169
                    self.exploitable_rating = 0
Packit 8ea169
                    self.exploitable_desc = _("Signal sent by alarm(N) expiration")
Packit 8ea169
                    return
Packit 8ea169
                # else: Can't rule out "crash" signal: may be FPE/ILL/BUS/SEGV.
Packit 8ea169
                # Fall through into signo/insn analysis.
Packit 8ea169
            # else: We are here if signal was not from user and not SI_KERNEL.
Packit 8ea169
            # Fall through into signo/insn analysis.
Packit 8ea169
        # else: We are here if si_code isn't known.
Packit 8ea169
        # Fall through into signo/insn analysis.
Packit 8ea169
Packit 8ea169
        # Guessing here... it might be kill(2) as well.
Packit 8ea169
        # Should I add "Likely ..." to the descriptions?
Packit 8ea169
        if self.signo in (signal.SIGINT, signal.SIGQUIT, signal.SIGTSTP):
Packit 8ea169
            self.exploitable_rating = 0
Packit 8ea169
            self.exploitable_desc = _("Signal sent by keyboard")
Packit 8ea169
            return
Packit 8ea169
        if self.signo in (signal.SIGTTIN, signal.SIGTTOU, signal.SIGHUP):
Packit 8ea169
            self.exploitable_rating = 0
Packit 8ea169
            self.exploitable_desc = _("Job control signal sent by kernel")
Packit 8ea169
            return
Packit 8ea169
        if self.signo == signal.SIGPIPE:
Packit 8ea169
            self.exploitable_rating = 0
Packit 8ea169
            self.exploitable_desc = _("Signal due to write to broken pipe")
Packit 8ea169
            return
Packit 8ea169
        if self.signo == signal.SIGWINCH:
Packit 8ea169
            self.exploitable_rating = 0
Packit 8ea169
            self.exploitable_desc = _("Signal sent by window resize")
Packit 8ea169
            return
Packit 8ea169
        if self.signo == signal.SIGALRM:
Packit 8ea169
            self.exploitable_rating = 0
Packit 8ea169
            self.exploitable_desc = _("Signal sent by alarm(N) expiration")
Packit 8ea169
            return
Packit 8ea169
Packit 8ea169
        # Which signals can coredump?
Packit 8ea169
        # SIGABRT Abort signal from abort(3)
Packit 8ea169
        # SIGQUIT Quit from keyboard
Packit 8ea169
        # SIGXCPU CPU time limit exceeded
Packit 8ea169
        # SIGXFSZ File size limit exceeded
Packit 8ea169
        # SIGTRAP Trace/breakpoint trap
Packit 8ea169
        # SIGSYS  Bad argument to routine (SVr4)
Packit 8ea169
        # SIGFPE  Floating point exception
Packit 8ea169
        # SIGILL  Illegal Instruction
Packit 8ea169
        # SIGSEGV Invalid memory reference
Packit 8ea169
        # SIGBUS  Bus error (bad memory access)
Packit 8ea169
        if self.signo == signal.SIGABRT:
Packit 8ea169
            self.exploitable_rating = 0
Packit 8ea169
            self.exploitable_desc = _("ABRT signal (abort() was called?)")
Packit 8ea169
            return
Packit 8ea169
        # Already handled above:
Packit 8ea169
        #if self.signo == signal.SIGQUIT:
Packit 8ea169
        #    self.exploitable_rating = 0
Packit 8ea169
        #    self.exploitable_desc = _("QUIT signal (Ctrl-\\ pressed?)")
Packit 8ea169
        #    return
Packit 8ea169
        if self.signo == signal.SIGXCPU:
Packit 8ea169
            self.exploitable_rating = 0
Packit 8ea169
            self.exploitable_desc = _("XCPU signal (over CPU time limit)")
Packit 8ea169
            return
Packit 8ea169
        if self.signo == signal.SIGXFSZ:
Packit 8ea169
            self.exploitable_rating = 0
Packit 8ea169
            self.exploitable_desc = _("XFSZ signal (over file size limit)")
Packit 8ea169
            return
Packit 8ea169
        if self.signo == signal.SIGTRAP:
Packit 8ea169
            self.exploitable_rating = 0
Packit 8ea169
            self.exploitable_desc = _("TRAP signal (can be a bug in a debugger/tracer)")
Packit 8ea169
            return
Packit 8ea169
        if self.signo == signal.SIGSYS:
Packit 8ea169
            self.exploitable_rating = 1
Packit 8ea169
            self.exploitable_desc = _("SYS signal (unknown syscall was called?)")
Packit 8ea169
            return
Packit 8ea169
Packit 8ea169
        if self.signo == signal.SIGFPE:
Packit 8ea169
            self.exploitable_rating = 1
Packit 8ea169
            self.exploitable_desc = _("Arithmetic exception")
Packit 8ea169
            # 1 is FPE_INTDIV
Packit 8ea169
            if self.si_code == 1 or self.instruction_is_division:
Packit 8ea169
                self.exploitable_rating = 0
Packit 8ea169
                self.exploitable_desc = _("Division by zero")
Packit 8ea169
            return
Packit 8ea169
        if self.signo == signal.SIGILL:
Packit 8ea169
            self.exploitable_rating = 5
Packit 8ea169
            self.exploitable_desc = _("Illegal instruction (jump to a random address?)")
Packit 8ea169
            return
Packit 8ea169
Packit 8ea169
        if self.signo != signal.SIGSEGV and self.signo != signal.SIGBUS:
Packit 8ea169
            self.exploitable_rating = 2
Packit 8ea169
            # Pity that we can't give a more descriptive explanation
Packit 8ea169
            self.exploitable_desc = _("Non-crash related signal")
Packit 8ea169
            return
Packit 8ea169
Packit 8ea169
        if self.instruction_is_pushing:
Packit 8ea169
            self.exploitable_rating = 4
Packit 8ea169
            self.exploitable_desc = _("Stack overflow")
Packit 8ea169
            return
Packit 8ea169
        if self.instruction_is_store:
Packit 8ea169
            self.exploitable_rating = 6
Packit 8ea169
            self.exploitable_desc = _("Write to an invalid address")
Packit 8ea169
            return
Packit 8ea169
        if self.instruction_is_return:
Packit 8ea169
            self.exploitable_rating = 7
Packit 8ea169
            self.exploitable_desc = _("Subroutine return to an invalid address (corrupted stack?)")
Packit 8ea169
            return
Packit 8ea169
        # Note: we check "ret" first, _then_ jumps.
Packit 8ea169
        # Corrupted stack is different from corrupted data.
Packit 8ea169
        if self.instruction_is_branch:
Packit 8ea169
            self.exploitable_rating = 6
Packit 8ea169
            self.exploitable_desc = _("Jump to an invalid address")
Packit 8ea169
            return
Packit 8ea169
        if not self.current_instruction:
Packit 8ea169
            self.exploitable_rating = 6
Packit 8ea169
            self.exploitable_desc = _("Jump to an invalid address")
Packit 8ea169
            return
Packit 8ea169
        if self.signo == signal.SIGBUS:
Packit 8ea169
            self.exploitable_rating = 5
Packit 8ea169
            self.exploitable_desc = _("Access past the end of mapped file, invalid address, unaligned access, etc")
Packit 8ea169
            return
Packit 8ea169
        # default values remain (e.g. description is "")
Packit 8ea169
Packit 8ea169
class AbrtExploitable(gdb.Command):
Packit 8ea169
    "Analyze a crash to determine exploitability"
Packit 8ea169
    def __init__(self):
Packit 8ea169
        super(AbrtExploitable, self).__init__(
Packit 8ea169
                "abrt-exploitable",
Packit 8ea169
                gdb.COMMAND_SUPPORT, # command class
Packit 8ea169
                gdb.COMPLETE_NONE,   # completion method
Packit 8ea169
                False  # => it's not a prefix command
Packit 8ea169
        )
Packit 8ea169
        init_gettext()
Packit 8ea169
Packit 8ea169
    # Called when the command is invoked from GDB
Packit 8ea169
    def invoke(self, args, from_tty):
Packit 8ea169
        si = SignalAndInsn()
Packit 8ea169
        if not si.get_signal():
Packit 8ea169
            sys.stderr.write(_("Can't get signal no and do exploitability analysis\n"))
Packit 8ea169
            return
Packit 8ea169
        si.get_instruction()
Packit 8ea169
        min_rating = 0
Packit 8ea169
        if args:
Packit 8ea169
            args = args.split(None, 1)
Packit 8ea169
            min_rating = int(args[0])
Packit 8ea169
        si.is_exploitable()
Packit 8ea169
        if si.exploitable_desc:
Packit 8ea169
            if si.exploitable_rating >= min_rating:
Packit 8ea169
                f = sys.stdout
Packit 8ea169
                if args and len(args) > 1:
Packit 8ea169
                    f = open(args[1], 'w')
Packit 8ea169
                f.write(_("Likely crash reason: ") + si.exploitable_desc + "\n")
Packit 8ea169
                f.write(_("Exploitable rating (0-9 scale): ") + str(si.exploitable_rating) + "\n")
Packit 8ea169
                if si.current_instruction:
Packit 8ea169
                    f.write(_("Current instruction: ") + si.current_instruction + "\n")
Packit 8ea169
        else:
Packit 8ea169
            sys.stderr.write(_("Exploitability analysis came up empty\n"))
Packit 8ea169
Packit 8ea169
AbrtExploitable()