This is an RFC, please do not commit.
Usage: Run abrt-action-analyze-vulnerability in a directory which contains ./coredump file. If crash looks exploitable, the tool creates ./exploitable file. Example 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: * improve error detection (e.g. what to do if gdb failed to run?) * suppress stray gdb output * set $SIGNO_OF_THE_COREDUMP to work around non-working $_siginfo.si_signo * decide whether to push for $_signo support in gdb (I have a tested gdb patch) * instruction analyzer is x86 specific now, make it per-arch (how to get arch name???)
v2: improved instruction analyzer, added more exploibable cases to detect, added tests
Signed-off-by: Denys Vlasenko dvlasenk@redhat.com --- abrt.spec.in | 2 + src/plugins/Makefile.am | 10 +- src/plugins/abrt-action-analyze-vulnerability | 9 + src/plugins/abrt-gdb-exploitable | 502 +++++++++++++++++++++ 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 + 17 files changed, 738 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/abrt.spec.in b/abrt.spec.in index fec5b56..ce5f944 100644 --- a/abrt.spec.in +++ b/abrt.spec.in @@ -670,6 +670,7 @@ gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || : %{_initrddir}/abrt-ccpp %endif %{_libexecdir}/abrt-hook-ccpp +%{_libexecdir}/abrt-gdb-exploitable
# attr(6755) ~= SETUID|SETGID %attr(6755, abrt, abrt) %{_libexecdir}/abrt-action-install-debuginfo-to-abrt-cache @@ -677,6 +678,7 @@ gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || : %{_bindir}/abrt-action-analyze-c %{_bindir}/abrt-action-trim-files %{_bindir}/abrt-action-analyze-core +%{_bindir}/abrt-action-analyze-vulnerability %{_bindir}/abrt-action-install-debuginfo %{_bindir}/abrt-action-generate-backtrace %{_bindir}/abrt-action-generate-core-backtrace 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..f2a410e --- /dev/null +++ b/src/plugins/abrt-action-analyze-vulnerability @@ -0,0 +1,9 @@ +#!/bin/sh + +if type gdb >/dev/null 2>&1; then + # gdb is avaliable + gdb --batch \ + -ex 'python execfile("/usr/libexec/abrt-gdb-exploitable")' \ + -ex 'core-file ./coredump' \ + -ex 'abrt-exploitable 5 ./exploitable' +fi diff --git a/src/plugins/abrt-gdb-exploitable b/src/plugins/abrt-gdb-exploitable new file mode 100755 index 0000000..4f73e0b --- /dev/null +++ b/src/plugins/abrt-gdb-exploitable @@ -0,0 +1,502 @@ +#!/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 gdb +import os +import signal + +_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 -2 + "inc":-2, + "dec":-2, + "neg":-2, + "not":-2, + "pop":-2, + # "Set byte on condition". One-operand insns. + "seta":-2, + "setae":-2, + "setb":-2, + "setbe":-2, + "setc":-2, + "sete":-2, + "setg":-2, + "setge":-2, + "setl":-2, + "setle":-2, + "setna":-2, + "setnae":-2, + "setnb":-2, + "setnbe":-2, + "setnc":-2, + "setne":-2, + "setng":-2, + "setnge":-2, + "setnl":-2, + "setnle":-2, + "setno":-2, + "setnp":-2, + "setns":-2, + "setnz":-2, + "seto":-2, + "setp":-2, + "setpe":-2, + "setpo":-2, + "sets":-2, + "setz":-2, + # 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 -2. + "shl":-2, + "shr":-2, + "sal":-2, + "sar":-2, + "rol":-2, + "ror":-2, + "rcl":-2, + "rcr":-2, + "shld":-2, + "shrd":-2, + # Bit tests. Any memory operand is a destination, can use -2. + "bts":-2, + "btr":-2, + "btc":-2, + # One-operand (register pair is another, implicit operand). + "cmpxchg8b":-2, + "cmpxchg16b":-2, + + # Either mem operand indicates write to mem. + "xchg":-2, + + # String store insns. + # Look similar to widening signed move "movs[bwl][wlq]", + # but aliasing doesn't happen since widening move has two siffixes + "movs":-1, + "stos":-1, + # 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":-2, + "sgdt":-2, + "sidt":-2, + "sldt":-2, + "str":-2, + + # FPU/SIMD madness follows. + + # FPU store insns. One-operand. + "fsts":-2, + "fstl":-2, + #"fstt" doesn't exist + "fstps":-2, + "fstpl":-2, + "fstpt":-2, + # Saving state. One-operand insns. + "fstcw":-2, + "fnstcw":-2, + "fstsw":-2, + "fnstsw":-2, + "fstenv":-2, + "fnstenv":-2, + "fsave":-2, + "fnsave":-2, + "fxsave":-2, + "xsave":-2, + "xsaveopt":-2, + "fsave64":-2, + "fnsave64":-2, + "fxsave64":-2, + "xsave64":-2, + "xsaveopt64":-2, + "stmxcsr":-2, + "vstmxcsr":-2, + # SIMD store insns. + # Three-operand insns. Any memory operand is a destination. + "vcvtps2ph":-2, + "extractps":-2, + "vextractps":-2, + #[v]extractpd does not exist + "vextractf128":-2, + "vextracti128":-2, + "pextr":-2, # covers pextr[bwq] + "pextrd":-2, + "vpextr":-2, + "vpextrd":-2, + "vmaskmovpd":-2, + "vmaskmovps":-2, + "vpmaskmovd":-2, + "vpmaskmovq":-2, + # These insns have implicit (%edi) dest operand: + "maskmovq":-1, # mmx version + "maskmovdqu":-1, + "vmaskmovdqu":-1, + + # check binutils/gas/testsuite/gas/i386/* for more weird insns + # Instruction Set Reference, A-M and N-Z: + # http://download.intel.com/products/processor/manual/253666.pdf + # http://download.intel.com/products/processor/manual/253667.pdf + # SSE4: + # http://software.intel.com/sites/default/files/m/0/3/c/d/4/18187-d9156103.pdf + # Instruction Set Extensions: + # http://download-software.intel.com/sites/default/files/319433-014.pdf + # Xeon Phi: + # http://download-software.intel.com/sites/default/files/forum/278102/32736400... + + #"vmovXXX" - special-cased in the code + "mov":2 + + # Note: stack-writing instructions are omitted +} + +_pushing_instr = { + "push":-1, + "pusha":-1, + "pushf":-1, + "enter":-1, + "call":-1, + "lcall":-1 +} + +_intdiv_instr = { + "div":-1, + "idiv":-1 +} + +_jumping_instr = { + "jmp":-1, # indirect jumps/calls with garbage data + "call":-1, # call: also possible that stack is exhausted (infinite recursion) + "ljmp":-1, + "lcall":-1, + # 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":-1, + "lret":-1, + "ret":-1 +} + +_return_instr = { + "iret":-1, + "lret":-1, + "ret":-1 # stack smashed if we crash on this +} + +def _fetch_insn_from_table(ins, table): + if not ins: + return None + if ins in table.keys(): + return table[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.keys(): + return table[ins] + return None + +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 == -1: # 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 == -2: # 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 + +class _Signal_and_Insn: + pass + +def _get_signal_and_instruction(): + si = _Signal_and_Insn() + si.signo = None + try: + sig = gdb.parse_and_eval("$_signo") # ("$_siginfo.si_signo") + # type(sig) = <type 'gdb.Value'> + # sig is 8 (for SIGFPE) + si.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. + # + # HACK_ALERT: kernels before 3.?.? do not record siginfo in coredumps, + # so $_siginfo isn't present. + # Lets see whether we are running from the abrt and it provided us with signal number + # + try: + si.signo = int(os.environ["SIGNO_OF_THE_COREDUMP"]) + except KeyError: + return None + + si.current_instruction = None + si.mnemonic = None + si.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) + # type(instructions) = <type 'str'> + except gdb.error: + # For example, if tracee already exited normally. + # Another observed case is if $pc points to unmapped area. + # Python Exception <class 'gdb.error'> No registers.: + return si + + raw_instructions = instructions + #print 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 si + + si.current_instruction = instructions[current] + # TODO: too simplistic. + # consider this example: + # "data32 data32 data32 nopw %cs:0x0(%rax,%rax,1)" + t = si.current_instruction.split(None,2) + si.mnemonic = t[0] + if len(t) > 1: + si.operands = t[1] + return si + +#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 occurred" + if _instruction_is_division(self): + self.exploitable_rating = 0 + self.exploitable_desc = "Division by zero occurred" + 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 _instruction_is_pushing(self): + self.exploitable_rating = 4 + self.exploitable_desc = "Stack overflow" + elif _instruction_is_writing(self): + self.exploitable_rating = 6 + self.exploitable_desc = "Write to an invalid address" + elif _instruction_is_return(self): + 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 _instruction_is_jumping(self): + 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 = _get_signal_and_instruction() + #print "w", _instruction_is_writing(self) + f = sys.stdout + min_rating = 0 + if args: + args = args.split(None, 1) + min_rating = int(args[0]) + if len(args) > 1: + f = open(args[1], 'w') + if not si: + sys.stderr.write("Can't get signal no and do exploitability analysis\n") + return + _is_exploitable(si) + if si.exploitable_desc: + if si.exploitable_rating >= min_rating: + 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 on this crash 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..fe4078d --- /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..53e8762 --- /dev/null +++ b/tests/abrt-exploitable/testFloatingPointException.c @@ -0,0 +1,10 @@ +/* + * Test the illegal instruction 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); +}
Would be great if you push it into a separate branch, so we don't have to manually apply every change you do. In your current tree run:
$ git checkout -b exploitcheck # this will create the branch $ git push origin exploitcheck # this will push it to the server
others can just:
$ git clone .. && git checkout exploitcheck
Whenever you do some changes others will just git pull instead of the annoying mail saving&applying ...
Thanks, Jirka
On 06/13/2013 02:01 PM, Denys Vlasenko wrote:
This is an RFC, please do not commit.
Usage: Run abrt-action-analyze-vulnerability in a directory which contains ./coredump file. If crash looks exploitable, the tool creates ./exploitable file. Example 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:
- improve error detection (e.g. what to do if gdb failed to run?)
- suppress stray gdb output
- set $SIGNO_OF_THE_COREDUMP to work around non-working $_siginfo.si_signo
- decide whether to push for $_signo support in gdb (I have a tested gdb patch)
- instruction analyzer is x86 specific now, make it per-arch (how to get arch name???)
v2: improved instruction analyzer, added more exploibable cases to detect, added tests
Signed-off-by: Denys Vlasenko dvlasenk@redhat.com
abrt.spec.in | 2 + src/plugins/Makefile.am | 10 +- src/plugins/abrt-action-analyze-vulnerability | 9 + src/plugins/abrt-gdb-exploitable | 502 +++++++++++++++++++++ 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 + 17 files changed, 738 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/abrt.spec.in b/abrt.spec.in index fec5b56..ce5f944 100644 --- a/abrt.spec.in +++ b/abrt.spec.in @@ -670,6 +670,7 @@ gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || : %{_initrddir}/abrt-ccpp %endif %{_libexecdir}/abrt-hook-ccpp +%{_libexecdir}/abrt-gdb-exploitable
# attr(6755) ~= SETUID|SETGID %attr(6755, abrt, abrt) %{_libexecdir}/abrt-action-install-debuginfo-to-abrt-cache @@ -677,6 +678,7 @@ gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || : %{_bindir}/abrt-action-analyze-c %{_bindir}/abrt-action-trim-files %{_bindir}/abrt-action-analyze-core +%{_bindir}/abrt-action-analyze-vulnerability %{_bindir}/abrt-action-install-debuginfo %{_bindir}/abrt-action-generate-backtrace %{_bindir}/abrt-action-generate-core-backtrace 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..f2a410e --- /dev/null +++ b/src/plugins/abrt-action-analyze-vulnerability @@ -0,0 +1,9 @@ +#!/bin/sh
+if type gdb >/dev/null 2>&1; then
- # gdb is avaliable
- gdb --batch \
-ex 'python execfile("/usr/libexec/abrt-gdb-exploitable")' \
-ex 'core-file ./coredump' \
-ex 'abrt-exploitable 5 ./exploitable'
+fi diff --git a/src/plugins/abrt-gdb-exploitable b/src/plugins/abrt-gdb-exploitable new file mode 100755 index 0000000..4f73e0b --- /dev/null +++ b/src/plugins/abrt-gdb-exploitable @@ -0,0 +1,502 @@ +#!/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 gdb +import os +import signal
+_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 -2
- "inc":-2,
- "dec":-2,
- "neg":-2,
- "not":-2,
- "pop":-2,
- # "Set byte on condition". One-operand insns.
- "seta":-2,
- "setae":-2,
- "setb":-2,
- "setbe":-2,
- "setc":-2,
- "sete":-2,
- "setg":-2,
- "setge":-2,
- "setl":-2,
- "setle":-2,
- "setna":-2,
- "setnae":-2,
- "setnb":-2,
- "setnbe":-2,
- "setnc":-2,
- "setne":-2,
- "setng":-2,
- "setnge":-2,
- "setnl":-2,
- "setnle":-2,
- "setno":-2,
- "setnp":-2,
- "setns":-2,
- "setnz":-2,
- "seto":-2,
- "setp":-2,
- "setpe":-2,
- "setpo":-2,
- "sets":-2,
- "setz":-2,
- # 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 -2.
- "shl":-2,
- "shr":-2,
- "sal":-2,
- "sar":-2,
- "rol":-2,
- "ror":-2,
- "rcl":-2,
- "rcr":-2,
- "shld":-2,
- "shrd":-2,
- # Bit tests. Any memory operand is a destination, can use -2.
- "bts":-2,
- "btr":-2,
- "btc":-2,
- # One-operand (register pair is another, implicit operand).
- "cmpxchg8b":-2,
- "cmpxchg16b":-2,
- # Either mem operand indicates write to mem.
- "xchg":-2,
- # String store insns.
- # Look similar to widening signed move "movs[bwl][wlq]",
- # but aliasing doesn't happen since widening move has two siffixes
- "movs":-1,
- "stos":-1,
- # 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":-2,
- "sgdt":-2,
- "sidt":-2,
- "sldt":-2,
- "str":-2,
- # FPU/SIMD madness follows.
- # FPU store insns. One-operand.
- "fsts":-2,
- "fstl":-2,
- #"fstt" doesn't exist
- "fstps":-2,
- "fstpl":-2,
- "fstpt":-2,
- # Saving state. One-operand insns.
- "fstcw":-2,
- "fnstcw":-2,
- "fstsw":-2,
- "fnstsw":-2,
- "fstenv":-2,
- "fnstenv":-2,
- "fsave":-2,
- "fnsave":-2,
- "fxsave":-2,
- "xsave":-2,
- "xsaveopt":-2,
- "fsave64":-2,
- "fnsave64":-2,
- "fxsave64":-2,
- "xsave64":-2,
- "xsaveopt64":-2,
- "stmxcsr":-2,
- "vstmxcsr":-2,
- # SIMD store insns.
- # Three-operand insns. Any memory operand is a destination.
- "vcvtps2ph":-2,
- "extractps":-2,
- "vextractps":-2,
- #[v]extractpd does not exist
- "vextractf128":-2,
- "vextracti128":-2,
- "pextr":-2, # covers pextr[bwq]
- "pextrd":-2,
- "vpextr":-2,
- "vpextrd":-2,
- "vmaskmovpd":-2,
- "vmaskmovps":-2,
- "vpmaskmovd":-2,
- "vpmaskmovq":-2,
- # These insns have implicit (%edi) dest operand:
- "maskmovq":-1, # mmx version
- "maskmovdqu":-1,
- "vmaskmovdqu":-1,
- # check binutils/gas/testsuite/gas/i386/* for more weird insns
- # Instruction Set Reference, A-M and N-Z:
- # http://download.intel.com/products/processor/manual/253666.pdf
- # http://download.intel.com/products/processor/manual/253667.pdf
- # SSE4:
- # http://software.intel.com/sites/default/files/m/0/3/c/d/4/18187-d9156103.pdf
- # Instruction Set Extensions:
- # http://download-software.intel.com/sites/default/files/319433-014.pdf
- # Xeon Phi:
- # http://download-software.intel.com/sites/default/files/forum/278102/32736400...
- #"vmovXXX" - special-cased in the code
- "mov":2
- # Note: stack-writing instructions are omitted
+}
+_pushing_instr = {
- "push":-1,
- "pusha":-1,
- "pushf":-1,
- "enter":-1,
- "call":-1,
- "lcall":-1
+}
+_intdiv_instr = {
- "div":-1,
- "idiv":-1
+}
+_jumping_instr = {
- "jmp":-1, # indirect jumps/calls with garbage data
- "call":-1, # call: also possible that stack is exhausted (infinite recursion)
- "ljmp":-1,
- "lcall":-1,
- # 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":-1,
- "lret":-1,
- "ret":-1
+}
+_return_instr = {
- "iret":-1,
- "lret":-1,
- "ret":-1 # stack smashed if we crash on this
+}
+def _fetch_insn_from_table(ins, table):
- if not ins:
return None
- if ins in table.keys():
return table[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.keys():
return table[ins]
- return None
+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 == -1: # 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 == -2: # 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
+class _Signal_and_Insn:
- pass
+def _get_signal_and_instruction():
- si = _Signal_and_Insn()
- si.signo = None
- try:
sig = gdb.parse_and_eval("$_signo") # ("$_siginfo.si_signo")
# type(sig) = <type 'gdb.Value'>
# sig is 8 (for SIGFPE)
si.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.
#
# HACK_ALERT: kernels before 3.?.? do not record siginfo in coredumps,
# so $_siginfo isn't present.
# Lets see whether we are running from the abrt and it provided us with signal number
#
try:
si.signo = int(os.environ["SIGNO_OF_THE_COREDUMP"])
except KeyError:
return None
- si.current_instruction = None
- si.mnemonic = None
- si.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)
# type(instructions) = <type 'str'>
- except gdb.error:
# For example, if tracee already exited normally.
# Another observed case is if $pc points to unmapped area.
# Python Exception <class 'gdb.error'> No registers.:
return si
- raw_instructions = instructions
- #print 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 si
- si.current_instruction = instructions[current]
- # TODO: too simplistic.
- # consider this example:
- # "data32 data32 data32 nopw %cs:0x0(%rax,%rax,1)"
- t = si.current_instruction.split(None,2)
- si.mnemonic = t[0]
- if len(t) > 1:
si.operands = t[1]
- return si
+#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 occurred"
if _instruction_is_division(self):
self.exploitable_rating = 0
self.exploitable_desc = "Division by zero occurred"
- 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 _instruction_is_pushing(self):
self.exploitable_rating = 4
self.exploitable_desc = "Stack overflow"
- elif _instruction_is_writing(self):
self.exploitable_rating = 6
self.exploitable_desc = "Write to an invalid address"
- elif _instruction_is_return(self):
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 _instruction_is_jumping(self):
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 = _get_signal_and_instruction()
#print "w", _instruction_is_writing(self)
f = sys.stdout
min_rating = 0
if args:
args = args.split(None, 1)
min_rating = int(args[0])
if len(args) > 1:
f = open(args[1], 'w')
if not si:
sys.stderr.write("Can't get signal no and do exploitability analysis\n")
return
_is_exploitable(si)
if si.exploitable_desc:
if si.exploitable_rating >= min_rating:
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 on this crash 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..fe4078d --- /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..53e8762 --- /dev/null +++ b/tests/abrt-exploitable/testFloatingPointException.c @@ -0,0 +1,10 @@ +/*
- Test the illegal instruction 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);
+}
On 06/13/2013 03:42 PM, Jiri Moskovcak wrote:
Would be great if you push it into a separate branch, so we don't have to manually apply every change you do. In your current tree run:
$ git checkout -b exploitcheck # this will create the branch $ git push origin exploitcheck # this will push it to the server
others can just:
$ git clone .. && git checkout exploitcheck
Whenever you do some changes others will just git pull instead of the annoying mail saving&applying ...
Done it, with the new version of the change.
Martin, can you try it? It should be working with stock gdb and kernel.
On Fri, Jun 14, 2013 at 13:56:51 +0200, Denys Vlasenko wrote:
Martin, can you try it? It should be working with stock gdb and kernel.
One small problem: this happens when the tool is run on coredumps with multiple threads:
abrtd: Missing separate debuginfo for the main executable file abrtd: Try: yum --disablerepo='*' --enablerepo='*-debug*' install /usr/lib/debug/.build-id/8d/e70b65a7828735a2b9eeaffdfbc1da44a36245 abrtd: [New LWP 847] abrtd: [New LWP 871] abrtd: [New LWP 873] abrtd: [New LWP 869] abrtd: [New LWP 874] abrtd: [New LWP 870] abrtd: [New LWP 875] abrtd: [New LWP 877] abrtd: [New LWP 878] abrtd: [New LWP 876] abrtd: [New LWP 872] abrtd: Core was generated by `/usr/sbin/libvirtd'. abrtd: Program terminated with signal 11, Segmentation fault. abrtd: #0 0x0000003739ae8bdf in ?? () abrtd: Python Exception <type 'exceptions.ValueError'> invalid literal for int() with base 10: '11\n11\n11\n11\n11\n11\n11\n11\n11\n11\n11': abrtd: Error occurred in Python command: invalid literal for int() with base 10: '11\n11\n11\n11\n11\n11\n11\n11\n11\n11\n11' abrtd: Error while running gdb:
On 06/17/2013 06:03 PM, Martin Milata wrote:
On Fri, Jun 14, 2013 at 13:56:51 +0200, Denys Vlasenko wrote:
Martin, can you try it? It should be working with stock gdb and kernel.
One small problem: this happens when the tool is run on coredumps with multiple threads:
abrtd: Missing separate debuginfo for the main executable file abrtd: Try: yum --disablerepo='*' --enablerepo='*-debug*' install /usr/lib/debug/.build-id/8d/e70b65a7828735a2b9eeaffdfbc1da44a36245 abrtd: [New LWP 847] abrtd: [New LWP 871] abrtd: [New LWP 873] abrtd: [New LWP 869] abrtd: [New LWP 874] abrtd: [New LWP 870] abrtd: [New LWP 875] abrtd: [New LWP 877] abrtd: [New LWP 878] abrtd: [New LWP 876] abrtd: [New LWP 872] abrtd: Core was generated by `/usr/sbin/libvirtd'. abrtd: Program terminated with signal 11, Segmentation fault. abrtd: #0 0x0000003739ae8bdf in ?? () abrtd: Python Exception <type 'exceptions.ValueError'> invalid literal for int() with base 10: '11\n11\n11\n11\n11\n11\n11\n11\n11\n11\n11': abrtd: Error occurred in Python command: invalid literal for int() with base 10: '11\n11\n11\n11\n11\n11\n11\n11\n11\n11\n11' abrtd: Error while running gdb:
Thanks! Committed this fix:
# Find "cursig: N" and extract N. # This gets used by abrt-exploitable as a fallback # if gdb and/or kernel is uncooperative. -SIGNO_OF_THE_COREDUMP=$(eu-readelf -n coredump | grep -o 'cursig: *[0-9]*' | sed 's/[^0-9]//g') +# "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
After a quick glance at the code:
- all the functions taking instance as parameter called "self" and then working with it should be methods
--Jirka
On 06/13/2013 02:01 PM, Denys Vlasenko wrote:
This is an RFC, please do not commit.
Usage: Run abrt-action-analyze-vulnerability in a directory which contains ./coredump file. If crash looks exploitable, the tool creates ./exploitable file. Example 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:
- improve error detection (e.g. what to do if gdb failed to run?)
- suppress stray gdb output
- set $SIGNO_OF_THE_COREDUMP to work around non-working $_siginfo.si_signo
- decide whether to push for $_signo support in gdb (I have a tested gdb patch)
- instruction analyzer is x86 specific now, make it per-arch (how to get arch name???)
v2: improved instruction analyzer, added more exploibable cases to detect, added tests
Signed-off-by: Denys Vlasenko dvlasenk@redhat.com
abrt.spec.in | 2 + src/plugins/Makefile.am | 10 +- src/plugins/abrt-action-analyze-vulnerability | 9 + src/plugins/abrt-gdb-exploitable | 502 +++++++++++++++++++++ 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 + 17 files changed, 738 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/abrt.spec.in b/abrt.spec.in index fec5b56..ce5f944 100644 --- a/abrt.spec.in +++ b/abrt.spec.in @@ -670,6 +670,7 @@ gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || : %{_initrddir}/abrt-ccpp %endif %{_libexecdir}/abrt-hook-ccpp +%{_libexecdir}/abrt-gdb-exploitable
# attr(6755) ~= SETUID|SETGID %attr(6755, abrt, abrt) %{_libexecdir}/abrt-action-install-debuginfo-to-abrt-cache @@ -677,6 +678,7 @@ gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || : %{_bindir}/abrt-action-analyze-c %{_bindir}/abrt-action-trim-files %{_bindir}/abrt-action-analyze-core +%{_bindir}/abrt-action-analyze-vulnerability %{_bindir}/abrt-action-install-debuginfo %{_bindir}/abrt-action-generate-backtrace %{_bindir}/abrt-action-generate-core-backtrace 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..f2a410e --- /dev/null +++ b/src/plugins/abrt-action-analyze-vulnerability @@ -0,0 +1,9 @@ +#!/bin/sh
+if type gdb >/dev/null 2>&1; then
- # gdb is avaliable
- gdb --batch \
-ex 'python execfile("/usr/libexec/abrt-gdb-exploitable")' \
-ex 'core-file ./coredump' \
-ex 'abrt-exploitable 5 ./exploitable'
+fi diff --git a/src/plugins/abrt-gdb-exploitable b/src/plugins/abrt-gdb-exploitable new file mode 100755 index 0000000..4f73e0b --- /dev/null +++ b/src/plugins/abrt-gdb-exploitable @@ -0,0 +1,502 @@ +#!/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 gdb +import os +import signal
+_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 -2
- "inc":-2,
- "dec":-2,
- "neg":-2,
- "not":-2,
- "pop":-2,
- # "Set byte on condition". One-operand insns.
- "seta":-2,
- "setae":-2,
- "setb":-2,
- "setbe":-2,
- "setc":-2,
- "sete":-2,
- "setg":-2,
- "setge":-2,
- "setl":-2,
- "setle":-2,
- "setna":-2,
- "setnae":-2,
- "setnb":-2,
- "setnbe":-2,
- "setnc":-2,
- "setne":-2,
- "setng":-2,
- "setnge":-2,
- "setnl":-2,
- "setnle":-2,
- "setno":-2,
- "setnp":-2,
- "setns":-2,
- "setnz":-2,
- "seto":-2,
- "setp":-2,
- "setpe":-2,
- "setpo":-2,
- "sets":-2,
- "setz":-2,
- # 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 -2.
- "shl":-2,
- "shr":-2,
- "sal":-2,
- "sar":-2,
- "rol":-2,
- "ror":-2,
- "rcl":-2,
- "rcr":-2,
- "shld":-2,
- "shrd":-2,
- # Bit tests. Any memory operand is a destination, can use -2.
- "bts":-2,
- "btr":-2,
- "btc":-2,
- # One-operand (register pair is another, implicit operand).
- "cmpxchg8b":-2,
- "cmpxchg16b":-2,
- # Either mem operand indicates write to mem.
- "xchg":-2,
- # String store insns.
- # Look similar to widening signed move "movs[bwl][wlq]",
- # but aliasing doesn't happen since widening move has two siffixes
- "movs":-1,
- "stos":-1,
- # 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":-2,
- "sgdt":-2,
- "sidt":-2,
- "sldt":-2,
- "str":-2,
- # FPU/SIMD madness follows.
- # FPU store insns. One-operand.
- "fsts":-2,
- "fstl":-2,
- #"fstt" doesn't exist
- "fstps":-2,
- "fstpl":-2,
- "fstpt":-2,
- # Saving state. One-operand insns.
- "fstcw":-2,
- "fnstcw":-2,
- "fstsw":-2,
- "fnstsw":-2,
- "fstenv":-2,
- "fnstenv":-2,
- "fsave":-2,
- "fnsave":-2,
- "fxsave":-2,
- "xsave":-2,
- "xsaveopt":-2,
- "fsave64":-2,
- "fnsave64":-2,
- "fxsave64":-2,
- "xsave64":-2,
- "xsaveopt64":-2,
- "stmxcsr":-2,
- "vstmxcsr":-2,
- # SIMD store insns.
- # Three-operand insns. Any memory operand is a destination.
- "vcvtps2ph":-2,
- "extractps":-2,
- "vextractps":-2,
- #[v]extractpd does not exist
- "vextractf128":-2,
- "vextracti128":-2,
- "pextr":-2, # covers pextr[bwq]
- "pextrd":-2,
- "vpextr":-2,
- "vpextrd":-2,
- "vmaskmovpd":-2,
- "vmaskmovps":-2,
- "vpmaskmovd":-2,
- "vpmaskmovq":-2,
- # These insns have implicit (%edi) dest operand:
- "maskmovq":-1, # mmx version
- "maskmovdqu":-1,
- "vmaskmovdqu":-1,
- # check binutils/gas/testsuite/gas/i386/* for more weird insns
- # Instruction Set Reference, A-M and N-Z:
- # http://download.intel.com/products/processor/manual/253666.pdf
- # http://download.intel.com/products/processor/manual/253667.pdf
- # SSE4:
- # http://software.intel.com/sites/default/files/m/0/3/c/d/4/18187-d9156103.pdf
- # Instruction Set Extensions:
- # http://download-software.intel.com/sites/default/files/319433-014.pdf
- # Xeon Phi:
- # http://download-software.intel.com/sites/default/files/forum/278102/32736400...
- #"vmovXXX" - special-cased in the code
- "mov":2
- # Note: stack-writing instructions are omitted
+}
+_pushing_instr = {
- "push":-1,
- "pusha":-1,
- "pushf":-1,
- "enter":-1,
- "call":-1,
- "lcall":-1
+}
+_intdiv_instr = {
- "div":-1,
- "idiv":-1
+}
+_jumping_instr = {
- "jmp":-1, # indirect jumps/calls with garbage data
- "call":-1, # call: also possible that stack is exhausted (infinite recursion)
- "ljmp":-1,
- "lcall":-1,
- # 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":-1,
- "lret":-1,
- "ret":-1
+}
+_return_instr = {
- "iret":-1,
- "lret":-1,
- "ret":-1 # stack smashed if we crash on this
+}
+def _fetch_insn_from_table(ins, table):
- if not ins:
return None
- if ins in table.keys():
return table[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.keys():
return table[ins]
- return None
+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 == -1: # 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 == -2: # 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
+class _Signal_and_Insn:
- pass
+def _get_signal_and_instruction():
- si = _Signal_and_Insn()
- si.signo = None
- try:
sig = gdb.parse_and_eval("$_signo") # ("$_siginfo.si_signo")
# type(sig) = <type 'gdb.Value'>
# sig is 8 (for SIGFPE)
si.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.
#
# HACK_ALERT: kernels before 3.?.? do not record siginfo in coredumps,
# so $_siginfo isn't present.
# Lets see whether we are running from the abrt and it provided us with signal number
#
try:
si.signo = int(os.environ["SIGNO_OF_THE_COREDUMP"])
except KeyError:
return None
- si.current_instruction = None
- si.mnemonic = None
- si.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)
# type(instructions) = <type 'str'>
- except gdb.error:
# For example, if tracee already exited normally.
# Another observed case is if $pc points to unmapped area.
# Python Exception <class 'gdb.error'> No registers.:
return si
- raw_instructions = instructions
- #print 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 si
- si.current_instruction = instructions[current]
- # TODO: too simplistic.
- # consider this example:
- # "data32 data32 data32 nopw %cs:0x0(%rax,%rax,1)"
- t = si.current_instruction.split(None,2)
- si.mnemonic = t[0]
- if len(t) > 1:
si.operands = t[1]
- return si
+#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 occurred"
if _instruction_is_division(self):
self.exploitable_rating = 0
self.exploitable_desc = "Division by zero occurred"
- 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 _instruction_is_pushing(self):
self.exploitable_rating = 4
self.exploitable_desc = "Stack overflow"
- elif _instruction_is_writing(self):
self.exploitable_rating = 6
self.exploitable_desc = "Write to an invalid address"
- elif _instruction_is_return(self):
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 _instruction_is_jumping(self):
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 = _get_signal_and_instruction()
#print "w", _instruction_is_writing(self)
f = sys.stdout
min_rating = 0
if args:
args = args.split(None, 1)
min_rating = int(args[0])
if len(args) > 1:
f = open(args[1], 'w')
if not si:
sys.stderr.write("Can't get signal no and do exploitability analysis\n")
return
_is_exploitable(si)
if si.exploitable_desc:
if si.exploitable_rating >= min_rating:
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 on this crash 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..fe4078d --- /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..53e8762 --- /dev/null +++ b/tests/abrt-exploitable/testFloatingPointException.c @@ -0,0 +1,10 @@ +/*
- Test the illegal instruction 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);
+}
On 06/13/2013 03:48 PM, Jiri Moskovcak wrote:
After a quick glance at the code:
- all the functions taking instance as parameter called "self" and then
working with it should be methods
- after a bit detailed reading on _is_exploitable() seems to change the state of the instance and should be fixed to not do so, since it's not a method - the other functions which takes the instance and just read it should have the parameter renamed - "self" is really misleading
--Jirka
--Jirka
On 06/13/2013 02:01 PM, Denys Vlasenko wrote:
This is an RFC, please do not commit.
Usage: Run abrt-action-analyze-vulnerability in a directory which contains ./coredump file. If crash looks exploitable, the tool creates ./exploitable file. Example 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:
- improve error detection (e.g. what to do if gdb failed to run?)
- suppress stray gdb output
- set $SIGNO_OF_THE_COREDUMP to work around non-working
$_siginfo.si_signo
- decide whether to push for $_signo support in gdb (I have a tested
gdb patch)
- instruction analyzer is x86 specific now, make it per-arch (how to get arch name???)
v2: improved instruction analyzer, added more exploibable cases to detect, added tests
Signed-off-by: Denys Vlasenko dvlasenk@redhat.com
abrt.spec.in | 2 + src/plugins/Makefile.am | 10 +- src/plugins/abrt-action-analyze-vulnerability | 9 + src/plugins/abrt-gdb-exploitable | 502 +++++++++++++++++++++ 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 + 17 files changed, 738 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/abrt.spec.in b/abrt.spec.in index fec5b56..ce5f944 100644 --- a/abrt.spec.in +++ b/abrt.spec.in @@ -670,6 +670,7 @@ gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || : %{_initrddir}/abrt-ccpp %endif %{_libexecdir}/abrt-hook-ccpp +%{_libexecdir}/abrt-gdb-exploitable
# attr(6755) ~= SETUID|SETGID %attr(6755, abrt, abrt) %{_libexecdir}/abrt-action-install-debuginfo-to-abrt-cache @@ -677,6 +678,7 @@ gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || : %{_bindir}/abrt-action-analyze-c %{_bindir}/abrt-action-trim-files %{_bindir}/abrt-action-analyze-core +%{_bindir}/abrt-action-analyze-vulnerability %{_bindir}/abrt-action-install-debuginfo %{_bindir}/abrt-action-generate-backtrace %{_bindir}/abrt-action-generate-core-backtrace 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..f2a410e --- /dev/null +++ b/src/plugins/abrt-action-analyze-vulnerability @@ -0,0 +1,9 @@ +#!/bin/sh
+if type gdb >/dev/null 2>&1; then
- # gdb is avaliable
- gdb --batch \
-ex 'python execfile("/usr/libexec/abrt-gdb-exploitable")' \
-ex 'core-file ./coredump' \
-ex 'abrt-exploitable 5 ./exploitable'
+fi diff --git a/src/plugins/abrt-gdb-exploitable b/src/plugins/abrt-gdb-exploitable new file mode 100755 index 0000000..4f73e0b --- /dev/null +++ b/src/plugins/abrt-gdb-exploitable @@ -0,0 +1,502 @@ +#!/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 gdb +import os +import signal
+_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 -2
- "inc":-2,
- "dec":-2,
- "neg":-2,
- "not":-2,
- "pop":-2,
- # "Set byte on condition". One-operand insns.
- "seta":-2,
- "setae":-2,
- "setb":-2,
- "setbe":-2,
- "setc":-2,
- "sete":-2,
- "setg":-2,
- "setge":-2,
- "setl":-2,
- "setle":-2,
- "setna":-2,
- "setnae":-2,
- "setnb":-2,
- "setnbe":-2,
- "setnc":-2,
- "setne":-2,
- "setng":-2,
- "setnge":-2,
- "setnl":-2,
- "setnle":-2,
- "setno":-2,
- "setnp":-2,
- "setns":-2,
- "setnz":-2,
- "seto":-2,
- "setp":-2,
- "setpe":-2,
- "setpo":-2,
- "sets":-2,
- "setz":-2,
- # 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 -2.
- "shl":-2,
- "shr":-2,
- "sal":-2,
- "sar":-2,
- "rol":-2,
- "ror":-2,
- "rcl":-2,
- "rcr":-2,
- "shld":-2,
- "shrd":-2,
- # Bit tests. Any memory operand is a destination, can use -2.
- "bts":-2,
- "btr":-2,
- "btc":-2,
- # One-operand (register pair is another, implicit operand).
- "cmpxchg8b":-2,
- "cmpxchg16b":-2,
- # Either mem operand indicates write to mem.
- "xchg":-2,
- # String store insns.
- # Look similar to widening signed move "movs[bwl][wlq]",
- # but aliasing doesn't happen since widening move has two siffixes
- "movs":-1,
- "stos":-1,
- # 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":-2,
- "sgdt":-2,
- "sidt":-2,
- "sldt":-2,
- "str":-2,
- # FPU/SIMD madness follows.
- # FPU store insns. One-operand.
- "fsts":-2,
- "fstl":-2,
- #"fstt" doesn't exist
- "fstps":-2,
- "fstpl":-2,
- "fstpt":-2,
- # Saving state. One-operand insns.
- "fstcw":-2,
- "fnstcw":-2,
- "fstsw":-2,
- "fnstsw":-2,
- "fstenv":-2,
- "fnstenv":-2,
- "fsave":-2,
- "fnsave":-2,
- "fxsave":-2,
- "xsave":-2,
- "xsaveopt":-2,
- "fsave64":-2,
- "fnsave64":-2,
- "fxsave64":-2,
- "xsave64":-2,
- "xsaveopt64":-2,
- "stmxcsr":-2,
- "vstmxcsr":-2,
- # SIMD store insns.
- # Three-operand insns. Any memory operand is a destination.
- "vcvtps2ph":-2,
- "extractps":-2,
- "vextractps":-2,
- #[v]extractpd does not exist
- "vextractf128":-2,
- "vextracti128":-2,
- "pextr":-2, # covers pextr[bwq]
- "pextrd":-2,
- "vpextr":-2,
- "vpextrd":-2,
- "vmaskmovpd":-2,
- "vmaskmovps":-2,
- "vpmaskmovd":-2,
- "vpmaskmovq":-2,
- # These insns have implicit (%edi) dest operand:
- "maskmovq":-1, # mmx version
- "maskmovdqu":-1,
- "vmaskmovdqu":-1,
- # check binutils/gas/testsuite/gas/i386/* for more weird insns
- # Instruction Set Reference, A-M and N-Z:
- # http://download.intel.com/products/processor/manual/253666.pdf
- # http://download.intel.com/products/processor/manual/253667.pdf
- # SSE4:
- #
http://software.intel.com/sites/default/files/m/0/3/c/d/4/18187-d9156103.pdf
- # Instruction Set Extensions:
- #
http://download-software.intel.com/sites/default/files/319433-014.pdf
- # Xeon Phi:
- #
http://download-software.intel.com/sites/default/files/forum/278102/32736400...
- #"vmovXXX" - special-cased in the code
- "mov":2
- # Note: stack-writing instructions are omitted
+}
+_pushing_instr = {
- "push":-1,
- "pusha":-1,
- "pushf":-1,
- "enter":-1,
- "call":-1,
- "lcall":-1
+}
+_intdiv_instr = {
- "div":-1,
- "idiv":-1
+}
+_jumping_instr = {
- "jmp":-1, # indirect jumps/calls with garbage data
- "call":-1, # call: also possible that stack is exhausted
(infinite recursion)
- "ljmp":-1,
- "lcall":-1,
- # 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":-1,
- "lret":-1,
- "ret":-1
+}
+_return_instr = {
- "iret":-1,
- "lret":-1,
- "ret":-1 # stack smashed if we crash on this
+}
+def _fetch_insn_from_table(ins, table):
- if not ins:
return None
- if ins in table.keys():
return table[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.keys():
return table[ins]
- return None
+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 == -1: # 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 == -2: # 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
+class _Signal_and_Insn:
- pass
+def _get_signal_and_instruction():
- si = _Signal_and_Insn()
- si.signo = None
- try:
sig = gdb.parse_and_eval("$_signo") # ("$_siginfo.si_signo")
# type(sig) = <type 'gdb.Value'>
# sig is 8 (for SIGFPE)
si.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.
#
# HACK_ALERT: kernels before 3.?.? do not record siginfo in
coredumps,
# so $_siginfo isn't present.
# Lets see whether we are running from the abrt and it
provided us with signal number
#
try:
si.signo = int(os.environ["SIGNO_OF_THE_COREDUMP"])
except KeyError:
return None
- si.current_instruction = None
- si.mnemonic = None
- si.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)
# type(instructions) = <type 'str'>
- except gdb.error:
# For example, if tracee already exited normally.
# Another observed case is if $pc points to unmapped area.
# Python Exception <class 'gdb.error'> No registers.:
return si
- raw_instructions = instructions
- #print 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 si
- si.current_instruction = instructions[current]
- # TODO: too simplistic.
- # consider this example:
- # "data32 data32 data32 nopw %cs:0x0(%rax,%rax,1)"
- t = si.current_instruction.split(None,2)
- si.mnemonic = t[0]
- if len(t) > 1:
si.operands = t[1]
- return si
+#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 occurred"
if _instruction_is_division(self):
self.exploitable_rating = 0
self.exploitable_desc = "Division by zero occurred"
- 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 _instruction_is_pushing(self):
self.exploitable_rating = 4
self.exploitable_desc = "Stack overflow"
- elif _instruction_is_writing(self):
self.exploitable_rating = 6
self.exploitable_desc = "Write to an invalid address"
- elif _instruction_is_return(self):
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 _instruction_is_jumping(self):
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 = _get_signal_and_instruction()
#print "w", _instruction_is_writing(self)
f = sys.stdout
min_rating = 0
if args:
args = args.split(None, 1)
min_rating = int(args[0])
if len(args) > 1:
f = open(args[1], 'w')
if not si:
sys.stderr.write("Can't get signal no and do
exploitability analysis\n")
return
_is_exploitable(si)
if si.exploitable_desc:
if si.exploitable_rating >= min_rating:
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 on this crash
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..fe4078d --- /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..53e8762 --- /dev/null +++ b/tests/abrt-exploitable/testFloatingPointException.c @@ -0,0 +1,10 @@ +/*
- Test the illegal instruction 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);
+}
Hi, I don't have much to say regarding what the script does -- I think it's a good start and you probably have better knowledge of low-level details than I do anyway. Perhaps someone from security or tools department might give us some feedback or hint how to go about this? There's also similar script for the ubuntu error reporting tool, you might want to check it out for some ideas: http://bazaar.launchpad.net/~apport-hackers/apport/trunk/view/head:/data/gen...
As the script only uses the instruction pointer and signal number from the coredump, we could run it on the server side in the future, as both values should be available in a uReport. The major advantages of this approach would be that we don't have to depend on GDB (apparently a problem on RHEL) and that we can run improved versions of the script on crashes received when this version was not available. The plugin doesn't seem to be integrated with GDB very tightly, so this should be easy if we keep this in mind.
Some remarks regarding the code below:
On Thu, Jun 13, 2013 at 14:01:11 +0200, Denys Vlasenko wrote:
+_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,
I suggest using something more descriptive, like assigning the numbers to a variable, e.g. WRITES_ALWAYS = -1, and then using those.
+def _fetch_insn_from_table(ins, table):
- if not ins:
return None
- if ins in table.keys():
return table[ins]
You can use just "ins in table".
- 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
Can you use a regular expression for this, possibly with splitting the operands into list first? http://docs.python.org/2.7/library/re.html
+class _Signal_and_Insn:
- pass
As Jiri said, I'd make most of the functions method of this class. At least you'll get rid of the underscores before their names;)
+def _get_signal_and_instruction():
This one should probably be the constructor. As mentioned, I'd probably move calls to the gdb module outside the class (but that can be done when we actually need it).
# HACK_ALERT: kernels before 3.?.? do not record siginfo in coredumps,
# so $_siginfo isn't present.
By the way, do you have any reference for this? The prstatus structure which contains a signal number seems to be present in coredumps at least on 2.6.32. Or am I misunderstanding the problem?
- 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)
What about using regular expressions here?
- elif self.signo == signal.SIGQUIT:
self.exploitable_rating = 0
self.exploitable_desc = "QUIT signal (Ctrl-\ pressed?)"
The backslash should probably be doubled or the whole string literal preceded by the letter r. This was found by pylint, I can recommend running it yourself, it can be useful sometimes.
- 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:
The giant if-else block looks ugly, but I can't think of a less ugly way to replace it ...
f = sys.stdout
The sys module is not imported (says pylint).
Cheers, Martin
CC'ing Jan
Martin Milata mmilata@redhat.com writes:
Hi, I don't have much to say regarding what the script does -- I think it's a good start and you probably have better knowledge of low-level details than I do anyway. Perhaps someone from security or tools department might give us some feedback or hint how to go about this? There's also similar script for the ubuntu error reporting tool, you might want to check it out for some ideas: http://bazaar.launchpad.net/~apport-hackers/apport/trunk/view/head:/data/gen...
As the script only uses the instruction pointer and signal number from the coredump, we could run it on the server side in the future, as both values should be available in a uReport. The major advantages of this approach would be that we don't have to depend on GDB (apparently a problem on RHEL) and that we can run improved versions of the script on crashes received when this version was not available. The plugin doesn't seem to be integrated with GDB very tightly, so this should be easy if we keep this in mind.
Some remarks regarding the code below:
On Thu, Jun 13, 2013 at 14:01:11 +0200, Denys Vlasenko wrote:
+_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,
I suggest using something more descriptive, like assigning the numbers to a variable, e.g. WRITES_ALWAYS = -1, and then using those.
+def _fetch_insn_from_table(ins, table):
- if not ins:
return None
- if ins in table.keys():
return table[ins]
You can use just "ins in table".
- 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
Can you use a regular expression for this, possibly with splitting the operands into list first? http://docs.python.org/2.7/library/re.html
+class _Signal_and_Insn:
- pass
As Jiri said, I'd make most of the functions method of this class. At least you'll get rid of the underscores before their names;)
+def _get_signal_and_instruction():
This one should probably be the constructor. As mentioned, I'd probably move calls to the gdb module outside the class (but that can be done when we actually need it).
# HACK_ALERT: kernels before 3.?.? do not record siginfo in coredumps,
# so $_siginfo isn't present.
By the way, do you have any reference for this? The prstatus structure which contains a signal number seems to be present in coredumps at least on 2.6.32. Or am I misunderstanding the problem?
- 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)
What about using regular expressions here?
- elif self.signo == signal.SIGQUIT:
self.exploitable_rating = 0
self.exploitable_desc = "QUIT signal (Ctrl-\ pressed?)"
The backslash should probably be doubled or the whole string literal preceded by the letter r. This was found by pylint, I can recommend running it yourself, it can be useful sometimes.
- 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:
The giant if-else block looks ugly, but I can't think of a less ugly way to replace it ...
f = sys.stdout
The sys module is not imported (says pylint).
Cheers, Martin
On Thu, 13 Jun 2013 17:08:44 +0200, Nikola Pajkovsky wrote:
CC'ing Jan
I cannot agree with this approach, doing static analysis from the binary is too late, this is why RH David Malcolm is developing an interface into GCC: https://fedorahosted.org/gcc-python-plugin/
It may be better to ask him personally more about the plugin, I do not know it.
# HACK_ALERT: kernels before 3.?.? do not record siginfo in coredumps,
# so $_siginfo isn't present.
By the way, do you have any reference for this? The prstatus structure which contains a signal number seems to be present in coredumps at least on 2.6.32. Or am I misunderstanding the problem?
GDB cannot decode it now just from prstatus, assigned to Sergio: Add $_signo convenience variable https://bugzilla.redhat.com/show_bug.cgi?id=971849
Regards, Jan
On 06/13/2013 06:42 PM, Jan Kratochvil wrote:
On Thu, 13 Jun 2013 17:08:44 +0200, Nikola Pajkovsky wrote:
CC'ing Jan
I cannot agree with this approach, doing static analysis from the binary is too late, this is why RH David Malcolm is developing an interface into GCC: https://fedorahosted.org/gcc-python-plugin/
It may be better to ask him personally more about the plugin, I do not know it.
- well that works only if you have the code, our project should be able to help users identify possible threats even in the apps which doesn't come with the source code - besides, the page says: "The project is at an "alpha" level of quality: it works, and ​has uncovered numerous coding errors - but the APIs aren't set in stone yet." - when it's ready it can be used as a first level of defence and ABRT as the last, but from what I understand it's totally unrelated to ABRT
--Jirka
On 06/13/2013 04:55 PM, Martin Milata wrote:
Some remarks regarding the code below:
- 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
Can you use a regular expression for this, possibly with splitting the operands into list first? http://docs.python.org/2.7/library/re.html
I think "import re", compiling of the regexp and matching will be slower than searching for a string. It is also less understandable:
regex = re.compile('^\s+(0x[0-9a-f]+)\s+(<+\d+>):\s+(.*)$')
I can't easily tell what this regexp is doing.
The only benefit is that using regex may take a few lines less than existing code.
# HACK_ALERT: kernels before 3.?.? do not record siginfo in coredumps,
# so $_siginfo isn't present.
By the way, do you have any reference for this? The prstatus structure which contains a signal number seems to be present in coredumps at least on 2.6.32. Or am I misunderstanding the problem?
The point is, gdb needs _complete_ siginfo to be able to show $_siginfo. A few bits of signal info in prstatus is a complete siginfo. (Of course, $_siginfo.si_signo doesn't need complete siginfo, but gdb doesn't know that...)
- 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)
What about using regular expressions here?
re.compile('^\s+(0x[0-9a-f]+)\s+(<+\d+>):\s+(.*)$')
The rest of your comments make sense, I'll update the branch.
On Mon, 2013-06-17 at 15:56 +0200, Denys Vlasenko wrote:
On 06/13/2013 04:55 PM, Martin Milata wrote:
Some remarks regarding the code below:
- 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
Can you use a regular expression for this, possibly with splitting the operands into list first? http://docs.python.org/2.7/library/re.html
I think "import re", compiling of the regexp and matching will be slower than searching for a string. It is also less understandable:
regex is more understandable for me
regex = re.compile('^\s+(0x[0-9a-f]+)\s+(<+\d+>):\s+(.*)$')
I can't easily tell what this regexp is doing.
I can tell what this regexp is doing but I can't easily tell what the lines above are doing.
The only benefit is that using regex may take a few lines less than existing code.
# HACK_ALERT: kernels before 3.?.? do not record siginfo in coredumps,
# so $_siginfo isn't present.
By the way, do you have any reference for this? The prstatus structure which contains a signal number seems to be present in coredumps at least on 2.6.32. Or am I misunderstanding the problem?
The point is, gdb needs _complete_ siginfo to be able to show $_siginfo. A few bits of signal info in prstatus is a complete siginfo. (Of course, $_siginfo.si_signo doesn't need complete siginfo, but gdb doesn't know that...)
- 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)
What about using regular expressions here?
re.compile('^\s+(0x[0-9a-f]+)\s+(<+\d+>):\s+(.*)$')
The rest of your comments make sense, I'll update the branch.
On 06/17/2013 03:56 PM, Denys Vlasenko wrote:
On 06/13/2013 04:55 PM, Martin Milata wrote:
Some remarks regarding the code below:
- 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
Can you use a regular expression for this, possibly with splitting the operands into list first? http://docs.python.org/2.7/library/re.html
I think "import re", compiling of the regexp and matching will be slower than searching for a string.
- even if the part about the speed would be true (which I doubt) we're not chasing for milliseconds...
It is also less understandable:
regex = re.compile('^\s+(0x[0-9a-f]+)\s+(<+\d+>):\s+(.*)$')
I can't easily tell what this regexp is doing.
- you can use named groups to make it more clear:
('^\s+(?P<memop>0x[0-9a-f]+)\s+(?P<somethingelse><+\d+>):\s+(?P<rest>.*)$')
- with the groups it's quite clear what are you looking for..
The only benefit is that using regex may take a few lines less than existing code.
- no, the benefit is that the regexp is more readable for 3 out of 4 people who saw that code so far..
Jirka
# HACK_ALERT: kernels before 3.?.? do not record siginfo in coredumps,
# so $_siginfo isn't present.
By the way, do you have any reference for this? The prstatus structure which contains a signal number seems to be present in coredumps at least on 2.6.32. Or am I misunderstanding the problem?
The point is, gdb needs _complete_ siginfo to be able to show $_siginfo. A few bits of signal info in prstatus is a complete siginfo. (Of course, $_siginfo.si_signo doesn't need complete siginfo, but gdb doesn't know that...)
- 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)
What about using regular expressions here?
re.compile('^\s+(0x[0-9a-f]+)\s+(<+\d+>):\s+(.*)$')
The rest of your comments make sense, I'll update the branch.
crash-catcher@lists.fedorahosted.org