In many cases, we can detect signals which are masquerading
as a segfault, but are really sent via kill(2) et al.
Signed-off-by: Denys Vlasenko <dvlasenk(a)redhat.com>
---
src/plugins/abrt-gdb-exploitable | 152 ++++++++++++++++++++++++++++++++++-----
1 file changed, 133 insertions(+), 19 deletions(-)
diff --git a/src/plugins/abrt-gdb-exploitable b/src/plugins/abrt-gdb-exploitable
index 51257e9..3bb80b9 100755
--- a/src/plugins/abrt-gdb-exploitable
+++ b/src/plugins/abrt-gdb-exploitable
@@ -451,16 +451,19 @@ class SignalAndInsn:
def get_signal(self):
self.signo = None
+ self.si_code = None
try:
# Requires new kernels which record complete siginfo
# in coredumps (Linux 3.9 still don't have it),
# and new gdb:
sig = gdb.parse_and_eval("$_siginfo.si_signo")
+ code = gdb.parse_and_eval("$_siginfo.si_code")
# Requires patched gdb:
#sig = gdb.parse_and_eval("$_signo")
#
# type(sig) = <type 'gdb.Value'>, convert to plain int:
self.signo = int(sig)
+ self.si_code = int(code)
except gdb.error:
# "Python Exception <class 'gdb.error'>
# Attempt to extract a component of a value that is not a structure"
@@ -495,8 +498,100 @@ class SignalAndInsn:
self.exploitable_rating = 3
self.exploitable_desc = ""
- if 0:
- pass
+ # siginfo.si_code:
+ # If <= 0, then it's not a crash:
+ # SI_ASYNCNL = -60 /* asynch name lookup completion */
+ # SI_TKILL = -6 /* tkill (and tgkill?) */
+ # SI_SIGIO = -5 /* queued SIGIO */
+ # SI_ASYNCIO = -4 /* AIO completion */
+ # SI_MESGQ = -3 /* real time mesq state change */
+ # SI_TIMER = -2 /* timer expiration (timer_create() with SIGEV_SIGNAL) */
+ # SI_QUEUE = -1 /* sigqueue */
+ # SI_USER = 0 /* kill, sigsend */
+ # Crashes have si_code > 0:
+ # testDivideByZero: SIGFPE si_code=FPE_INTDIV(1), si_addr=0x40054a
+ # x86-64 opcode 0x62: SIGILL si_code=ILL_ILLOPN(2), si_addr=0x40053f
+ # x86-64 priv.insn.: SIGSEGV si_code=SI_KERNEL(128), si_addr=0
+ # testExecuteInvalid: SIGSEGV si_code=SEGV_MAPERR(1), si_addr=0x1c2404000
+ # testStackBufferOverflow ("ret" to bad address):
+ # SIGSEGV si_code=SI_KERNEL(128), si_addr=0
+ # testStackRecursion: SIGSEGV si_code=SEGV_MAPERR(1), si_addr=0x7fff4c216d28
+ # testWriteRandom: SIGSEGV si_code=SEGV_MAPERR(1), si_addr=0x1eb004004
+ # However:
+ # Keyboard signals (^C INT, ^\ QUIT, ^Z TSTP) also have si_code=SI_KERNEL.
+ # SIGWINCH has si_code=SI_KERNEL.
+ # SIGALRM from alarm(N) has si_code=SI_KERNEL.
+ # Surprisingly, SIGPIPE has si_code=SI_USER!
+ if self.si_code is not None:
+ # Filter out user-generated signals:
+ if self.si_code == -6 or self.si_code == -1: # SI_TKILL/SI_QUEUE
+ self.exploitable_rating = 0
+ self.exploitable_desc = _("Signal sent by userspace code")
+ return
+ if self.si_code < 0:
+ self.exploitable_rating = 0
+ self.exploitable_desc = _("Signal sent by timer/IO/async
event")
+ return
+ # Unfortunately, this isn't reliable to flag user-sent signals:
+ # not only SIGPIPE, but some other kernel signals have SI_USER
+ # (grep kernel sources for "send_sig(sig, ..., 0)").
+ # At least we know it's not a crash.
+ if self.si_code == 0: # SI_USER
+ self.exploitable_rating = 0
+ self.exploitable_desc = _("Signal has siginfo.si_code =
SI_USER")
+ # Special case (a kernel buglet?)
+ if self.signo == signal.SIGPIPE:
+ self.exploitable_desc = _("Signal due to write to closed
pipe")
+ return
+ # And kernel-generated ones:
+ if self.si_code == 0x80: # SI_KERNEL
+ if self.signo in (signal.SIGINT, signal.SIGQUIT, signal.SIGTSTP):
+ self.exploitable_rating = 0
+ self.exploitable_desc = _("Signal sent by keyboard")
+ return
+ if self.signo in (signal.SIGTTIN, signal.SIGTTOU, signal.SIGHUP):
+ self.exploitable_rating = 0
+ self.exploitable_desc = _("Job control signal sent by
kernel")
+ return
+ if self.signo == signal.SIGWINCH:
+ self.exploitable_rating = 0
+ self.exploitable_desc = _("Signal sent by window resize")
+ return
+ if self.signo == signal.SIGALRM:
+ self.exploitable_rating = 0
+ self.exploitable_desc = _("Signal sent by alarm(N)
expiration")
+ return
+ # else: Can't rule out "crash" signal: may be
FPE/ILL/BUS/SEGV.
+ # Fall through into signo/insn analysis.
+ # else: We are here if signal was not from user and not SI_KERNEL.
+ # Fall through into signo/insn analysis.
+ # else: We are here if si_code isn't known.
+ # Fall through into signo/insn analysis.
+
+ # Guessing here... it might be kill(2) as well.
+ # Should I add "Likely ..." to the descriptions?
+ if self.signo in (signal.SIGINT, signal.SIGQUIT, signal.SIGTSTP):
+ self.exploitable_rating = 0
+ self.exploitable_desc = _("Signal sent by keyboard")
+ return
+ if self.signo in (signal.SIGTTIN, signal.SIGTTOU, signal.SIGHUP):
+ self.exploitable_rating = 0
+ self.exploitable_desc = _("Job control signal sent by kernel")
+ return
+ if self.signo == signal.SIGPIPE:
+ self.exploitable_rating = 0
+ self.exploitable_desc = _("Signal due to write to broken pipe")
+ return
+ if self.signo == signal.SIGWINCH:
+ self.exploitable_rating = 0
+ self.exploitable_desc = _("Signal sent by window resize")
+ return
+ if self.signo == signal.SIGALRM:
+ self.exploitable_rating = 0
+ self.exploitable_desc = _("Signal sent by alarm(N) expiration")
+ return
+
+ # Which signals can coredump?
# SIGABRT Abort signal from abort(3)
# SIGQUIT Quit from keyboard
# SIGXCPU CPU time limit exceeded
@@ -507,58 +602,77 @@ class SignalAndInsn:
# SIGILL Illegal Instruction
# SIGSEGV Invalid memory reference
# SIGBUS Bus error (bad memory access)
- elif self.signo == signal.SIGABRT:
+ if self.signo == signal.SIGABRT:
self.exploitable_rating = 0
self.exploitable_desc = _("ABRT signal (abort() was called?)")
- elif self.signo == signal.SIGQUIT:
+ return
+ if self.signo == signal.SIGQUIT:
self.exploitable_rating = 0
self.exploitable_desc = _("QUIT signal (Ctrl-\\ pressed?)")
- elif self.signo == signal.SIGXCPU:
+ return
+ if self.signo == signal.SIGXCPU:
self.exploitable_rating = 0
self.exploitable_desc = _("XCPU signal (over CPU time limit)")
- elif self.signo == signal.SIGXFSZ:
+ return
+ if self.signo == signal.SIGXFSZ:
self.exploitable_rating = 0
self.exploitable_desc = _("XFSZ signal (over file size limit)")
- elif self.signo == signal.SIGTRAP:
+ return
+ if self.signo == signal.SIGTRAP:
self.exploitable_rating = 0
self.exploitable_desc = _("TRAP signal (can be a bug in a
debugger/tracer)")
+ return
elif self.signo == signal.SIGSYS:
self.exploitable_rating = 1
- self.exploitable_desc = _("SYS signal (unknown/masked syscall was
called?)")
+ self.exploitable_desc = _("SYS signal (unknown syscall was
called?)")
+ return
- elif self.signo == signal.SIGFPE:
+ if self.signo == signal.SIGFPE:
self.exploitable_rating = 1
self.exploitable_desc = _("Arithmetic exception")
- if self.instruction_is_division:
+ # 1 is FPE_INTDIV
+ if self.si_code == 1 or self.instruction_is_division:
self.exploitable_rating = 0
self.exploitable_desc = _("Division by zero")
- elif self.signo == signal.SIGILL:
+ return
+ if self.signo == signal.SIGILL:
self.exploitable_rating = 5
self.exploitable_desc = _("Illegal instruction (jump to a random
address?)")
+ return
- # TODO: check that sig is SIGSEGV/SIGBUS?
+ if self.signo != signal.SIGSEGV and self.signo != signal.SIGBUS:
+ self.exploitable_rating = 2
+ # Pity that we can't give a more descriptive explanation
+ self.exploitable_desc = _("Non-crash related signal")
+ return
- elif self.instruction_is_pushing:
+ if self.instruction_is_pushing:
self.exploitable_rating = 4
self.exploitable_desc = _("Stack overflow")
- elif self.instruction_is_store:
+ return
+ if self.instruction_is_store:
self.exploitable_rating = 6
self.exploitable_desc = _("Write to an invalid address")
- elif self.instruction_is_return:
+ return
+ if self.instruction_is_return:
self.exploitable_rating = 7
self.exploitable_desc = _("Subroutine return to an invalid address
(corrupted stack?)")
+ return
# Note: we check "ret" first, _then_ jumps.
# Corrupted stack is different from corrupted data.
- elif self.instruction_is_branch:
+ if self.instruction_is_branch:
self.exploitable_rating = 6
self.exploitable_desc = _("Jump to an invalid address")
- elif not self.current_instruction:
+ return
+ if not self.current_instruction:
self.exploitable_rating = 6
self.exploitable_desc = _("Jump to an invalid address")
- elif self.signo == signal.SIGBUS:
+ return
+ if 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:
+ return
+ # default values remain (e.g. description is "")
class AbrtExploitable(gdb.Command):
"Analyze a crash to determine exploitability"
--
1.8.1.4
Show replies by date
Pushed, thanks! I changed the one remaining "elif:" to "if:".