[RFC: ABRT PATCH] Add gdb python plugin which analyzes vulnerability
Denys Vlasenko
dvlasenk at redhat.com
Tue Jun 11 12:32:29 UTC 2013
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:
"""
Program tried to write to an invalid address
Exploitable rating (1-10 scale):
6
"""
This patch adds abrt-action-analyze-vulnerability invocation to
"EVENT=post-create analyzer=CCpp".
TODO:
* decide what to do if we _dont_ see particularly suspicious stuff -
do nothing? Or still create a file?
* 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???)
Signed-off-by: Denys Vlasenko <dvlasenk at redhat.com>
---
abrt.spec.in | 2 +
src/plugins/Makefile.am | 10 +-
src/plugins/abrt-action-analyze-vulnerability | 9 +
src/plugins/abrt-gdb-exploitable | 268 ++++++++++++++++++++++++++
src/plugins/ccpp_event.conf | 5 +-
5 files changed, 291 insertions(+), 3 deletions(-)
create mode 100755 src/plugins/abrt-action-analyze-vulnerability
create mode 100755 src/plugins/abrt-gdb-exploitable
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..47d2ce4
--- /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 ./exploitable'
+fi
diff --git a/src/plugins/abrt-gdb-exploitable b/src/plugins/abrt-gdb-exploitable
new file mode 100755
index 0000000..116d9db
--- /dev/null
+++ b/src/plugins/abrt-gdb-exploitable
@@ -0,0 +1,268 @@
+#!/usr/bin/python
+# This is a GDB plugin.
+# Usage:
+# gdb --batch -ex "source THIS_FILE" -ex run -ex abrt-exploitable PROG
+# or
+# gdb --batch -ex "source THIS_FILE" -ex "core COREDUMP" -ex abrt-exploitable
+
+import gdb
+import os
+import signal
+
+_writing_instr = {
+ # "insn:which operand to check for mem ref"
+ "add":2,
+ "adc":2,
+ "sub":2,
+ "sbb":2,
+ "and":2,
+ "xor":2,
+ "or":2,
+ "inc":1,
+ "dec":1,
+ "neg":1,
+ "not":1,
+ # sarl $2,(%rcx), BUT:
+ # FIXME: shifts by 1 have one operand!!!- sarb (%rax)
+ "shl":2,
+ "shr":2,
+ "sal":2,
+ "sar":2,
+ "rol":2,
+ "ror":2,
+ "rcl":2,
+ "rcr":2,
+ # shld $0xc6,%ecx,(%rdi)
+ #"shld":3,
+ #"shrd":3,
+ "bts":1,
+ "btr":1,
+ "btc":1,
+ "pop":1,
+
+ "xchg":-2, # either mem operand indicates write to mem
+
+ # FIXME: aliased to widening move "movs[bwl][wlq]" which never stores to mem
+ "movs":-1, # don't check operands, this insn always writes to mem
+ "stos":-1,
+ # these always write to stack:
+ #"push":-1,
+ #"pusha":-1,
+ #"pushf":-1,
+ #"enter":-1,
+
+ "cmpxchg":2,
+ "xadd":2,
+ # with binutils-like disasm, implicit register operands aren't shown
+ "cmpxchg8b":1,
+ "cmpxchg16b":1,
+
+ #"f[x]save":1,
+
+ # check binutils/gas/testsuite/gas/i386/* for more weird insns
+
+ "mov":2
+}
+
+_jumping_instr = {
+ "jmp":-1, # indirect jumps/calls with garbage data
+ "call":-1, # call: also possible that stack is exhausted (infinite recursion)
+ #"push":-1, ?
+ #"pusha":-1,
+ #"enter":-1,
+
+ "ret":-1 # stack smashed
+}
+
+#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 _get_signal_and_instruction(self):
+ self.signo = None
+ try:
+ sig = gdb.parse_and_eval("$_signo") # ("$_siginfo.si_signo")
+ # type(sig) = <type 'gdb.Value'>
+ # sig is 8 (for SIGFPE)
+ self.signo = int(sig)
+ except gdb.error:
+ # Python Exception <class 'gdb.error'> Attempt to extract a component of a value that is not a structure.:
+ # Possible reasons why $_siginfo doesn't exist:
+ # program is still running, program exited normally,
+ # we work with a coredump from an old kernel.
+ #
+ # 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:
+ self.signo = int(os.environ["SIGNO_OF_THE_COREDUMP"])
+ except KeyError:
+ return False
+
+ self.current_instruction = None
+ self.mnemonic = None
+ self.operands = ""
+ try:
+ # just "disassemble $pc" won't work if $pc doesn't point
+ # inside a known function
+ instructions = gdb.execute("disassemble $pc,$pc+32", to_string=True)
+ # type(instructions) = <type 'str'>
+ except gdb.error:
+ # For example, if tracee already exited normally:
+ # Python Exception <class 'gdb.error'> No registers.:
+ return False
+
+ 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 True
+
+ self.current_instruction = instructions[current]
+ # TODO: too simplistic.
+ # consider this example:
+ # "data32 data32 data32 nopw %cs:0x0(%rax,%rax,1)"
+ t = self.current_instruction.split(None,2)
+ self.mnemonic = t[0]
+ if len(t) > 1:
+ self.operands = t[1]
+ return True
+
+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 1
+
+ paren = self.operands.find("(")
+ if paren < 0:
+ return False # no memory operands
+
+ if operand == -2: # any mem operand indicates write
+ return 1
+
+ comma = self.operands.find(",")
+ if paren < 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:
+ # paren 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 False
+ return True
+
+def _instruction_is_jump(self):
+ if _fetch_insn_from_table(self.mnemonic, _jumping_instr):
+ return True
+ return False
+
+
+def _is_exploitable(self):
+ self.exploitable_rating = 3
+ self.exploitable_desc = ""
+ if 0:
+ pass
+ elif self.signo == signal.SIGFPE:
+ self.exploitable_rating = 1
+ self.exploitable_desc = "Arithmetic exceptions (such as division by zero) are rarely exploitable"
+ # TODO? look at instruction, if it is a division, lower rating to 0?
+ # Or at least give a better desc ("Division by zero" and "(Other) arithmetic exception" is more informative)
+ elif self.signo == signal.SIGILL:
+ self.exploitable_rating = 5
+ self.exploitable_desc = "SIGILL may be an indication that program jumped to a random address"
+ elif not self.current_instruction: # TODO: and SIGSEGV?
+ self.exploitable_rating = 6
+ self.exploitable_desc = "Program jumped to an invalid address"
+ elif _instruction_is_writing(self):
+ self.exploitable_rating = 6
+ self.exploitable_desc = "Program tried to write to an invalid address"
+ #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, arg, from_tty):
+ if not _get_signal_and_instruction(self):
+ return
+ #print "w", _instruction_is_writing(self)
+ _is_exploitable(self)
+ if self.exploitable_desc and self.exploitable_rating > 3:
+ f = sys.stdout
+ if arg:
+ f = open(arg, 'w')
+ f.write(self.exploitable_desc + "\n")
+ f.write("Exploitable rating (1-10 scale):\n" + str(self.exploitable_rating) + "\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 &&
(
--
1.8.1.4
More information about the Crash-catcher
mailing list