| Server IP : 61.19.30.66 / Your IP : 216.73.216.15 Web Server : Apache/2.2.22 (Ubuntu) System : Linux klw 3.11.0-15-generic #25~precise1-Ubuntu SMP Thu Jan 30 17:39:31 UTC 2014 x86_64 User : www-data ( 33) PHP Version : 5.3.10-1ubuntu3.48 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority, MySQL : ON | cURL : OFF | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : OFF Directory : /usr/share/apport/ |
Upload File : |
#!/usr/bin/python
# Collect information about a crash and create a report in the directory
# specified by apport.fileutils.report_dir.
# See https://wiki.ubuntu.com/Apport for details.
#
# Copyright (c) 2006 - 2011 Canonical Ltd.
# Author: Martin Pitt <martin.pitt@ubuntu.com>
#
# 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. See http://www.gnu.org/copyleft/gpl.html for
# the full text of the license.
import sys, os, os.path, subprocess, time, traceback, tempfile, glob, pwd
import signal, inspect, grp, fcntl
import apport, apport.fileutils
#################################################################
#
# functions
#
#################################################################
def check_lock():
'''Abort if another instance of apport is already running.
This avoids bringing down the system to its knees if there is a series of
crashes.'''
# create a lock file
lockfile = os.path.join(apport.fileutils.report_dir, '.lock')
try:
fd = os.open(lockfile, os.O_WRONLY|os.O_CREAT|os.O_NOFOLLOW)
except OSError as e:
error_log('cannot create lock file (uid %i): %s' % (os.getuid(), str(e)))
sys.exit(1)
try:
fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
except IOError:
error_log('another apport instance is already running, aborting')
sys.exit(1)
(pidstat, real_uid, real_gid, cwd) = (None, None, None, None)
def get_pid_info(pid):
'''Read /proc information about pid'''
global pidstat, real_uid, real_gid, cwd
# unhandled exceptions on missing or invalidly formatted files are okay
# here -- we want to know in the log file
pidstat = os.stat('/proc/%s/stat' % pid)
# determine real UID of the target process; do *not* use the owner of
# /proc/pid/stat, as that will be root for setuid or unreadable programs!
# (this matters when suid_dumpable is enabled)
with open('/proc/%s/status' % pid) as f:
for line in f:
if line.startswith('Uid:'):
real_uid = int(line.split()[1])
elif line.startswith('Gid:'):
real_gid = int(line.split()[1])
break
assert real_uid is not None, 'failed to parse Uid'
assert real_gid is not None, 'failed to parse Gid'
cwd = os.readlink('/proc/' + pid + '/cwd')
def drop_privileges(pid, real_only=False):
'''Change user and group to match the given target process
Normally that irrevocably drops privileges to the real user/group of the
target process. With real_only=True only the real IDs are changed, but
the effective IDs remain.
'''
if real_only:
os.setregid(real_gid, -1)
os.setreuid(real_uid, -1)
else:
os.setgid(real_gid)
os.setuid(real_uid)
assert os.getegid() == real_gid
assert os.geteuid() == real_uid
assert os.getgid() == real_gid
assert os.getuid() == real_uid
def init_error_log():
'''Open a suitable error log if sys.stderr is not a tty.'''
if not os.isatty(sys.stderr.fileno()):
log = os.environ.get('APPORT_LOG_FILE', '/var/log/apport.log')
try:
f = os.open(log, os.O_WRONLY|os.O_CREAT|os.O_APPEND, 0o600)
try:
admgid = grp.getgrnam('adm')[2]
os.chown(log, -1, admgid)
os.chmod(log, 0o640)
except KeyError:
pass # if group adm doesn't exist, just leave it as root
except OSError: # on a permission error, don't touch stderr
return
os.dup2(f, sys.stderr.fileno())
os.dup2(f, sys.stdout.fileno())
os.close(f)
def error_log(msg):
'''Output something to the error log.'''
apport.error('apport (pid %s) %s: %s', os.getpid(), time.asctime(), msg)
def _log_signal_handler(sgn, frame):
'''Internal apport signal handler. Just log the signal handler and exit.'''
# reset handler so that we do not get stuck in loops
signal.signal(sgn, signal.SIG_IGN)
try:
error_log('Got signal %i, aborting; frame:' % sgn)
for s in inspect.stack():
error_log(str(s))
except:
pass
sys.exit(1)
def setup_signals():
'''Install a signal handler for all crash-like signals, so that apport is
not called on itself when apport crashed.'''
signal.signal(signal.SIGILL, _log_signal_handler)
signal.signal(signal.SIGABRT, _log_signal_handler)
signal.signal(signal.SIGFPE, _log_signal_handler)
signal.signal(signal.SIGSEGV, _log_signal_handler)
signal.signal(signal.SIGPIPE, _log_signal_handler)
signal.signal(signal.SIGBUS, _log_signal_handler)
def write_user_coredump(pid, cwd, limit):
'''Write the core into the current directory if ulimit requests it.'''
# three cases:
# limit == 0: do not write anything
# limit == None: unlimited or large enough, write out everything
# limit nonzero: crashed process' core size ulimit in bytes
if limit == '0':
return
if limit:
limit = int(limit)
else:
assert limit is None
# limit -1 means 'unlimited'
if limit < 0:
limit = None
else:
# ulimit specifies kB
limit *= 1024
# don't write a core dump for suid/sgid/unreadable or otherwise
# protected executables, in accordance with core(5)
# (suid_dumpable==2 and core_pattern restrictions); when this happens,
# /proc/pid/stat is owned by root (or the user suid'ed to), but we already
# changed to the crashed process' real uid
assert pidstat, 'pidstat not initialized'
if pidstat.st_uid != os.getuid() or pidstat.st_gid != os.getgid():
error_log('disabling core dump for suid/sgid/unreadable executable')
return
core_path = os.path.join(cwd, 'core')
try:
if open('/proc/sys/kernel/core_uses_pid').read().strip() != '0':
core_path += '.' + str(pid)
core_file = os.open(core_path, os.O_WRONLY|os.O_CREAT|os.O_EXCL, 0o600)
except (OSError, IOError):
return
error_log('writing core dump to %s (limit: %s)' % (core_path, str(limit)))
written = 0
# Priming read
block = sys.stdin.read(1048576)
while True:
size = len(block)
if size == 0:
break
written += size
if limit and written > limit:
error_log('aborting core dump writing, size exceeds current limit %i' % limit)
os.close(core_file)
os.unlink(core_path)
return
if os.write(core_file, block) != size:
error_log('aborting core dump writing, could not write')
os.close(core_file)
os.unlink(core_path)
return
block = sys.stdin.read(1048576)
os.close(core_file)
return core_path
def usable_ram():
'''Return how many bytes of RAM is currently available that can be
allocated without causing major thrashing.'''
# abuse our excellent RFC822 parser to parse /proc/meminfo
r = apport.Report()
r.load(open('/proc/meminfo'))
memfree = long(r['MemFree'].split()[0])
cached = long(r['Cached'].split()[0])
writeback = long(r['Writeback'].split()[0])
return (memfree+cached-writeback) * 1024
def is_closing_session(pid, uid):
'''Check if pid is in a closing user session.
During that, crashes are common as the session D-BUS and X.org are going
away, etc. These crash reports are mostly noise, so should be ignored.
'''
with open('/proc/%s/environ' % pid) as e:
env = e.read().split('\0')
for e in env:
if e.startswith('DBUS_SESSION_BUS_ADDRESS='):
dbus_addr = e.split('=', 1)[1]
break
else:
error_log('is_closing_session(): no DBUS_SESSION_BUS_ADDRESS in environment')
return False
orig_uid = os.geteuid()
os.setresuid(-1, os.getuid(), -1)
try:
gdbus = subprocess.Popen(['/usr/bin/gdbus', 'call', '-e', '-d',
'org.gnome.SessionManager', '-o', '/org/gnome/SessionManager', '-m',
'org.gnome.SessionManager.IsSessionRunning'], stdout=subprocess.PIPE,
stderr=subprocess.PIPE, env={'DBUS_SESSION_BUS_ADDRESS': dbus_addr})
(out, err) = gdbus.communicate()
if err:
error_log('gdbus call error: ' + err.decode('UTF-8'))
except OSError as e:
error_log('gdbus call failed, cannot determine running session: ' + str(e))
return False
finally:
os.setresuid(-1, orig_uid, -1)
error_log('debug: session gdbus call: ' + out.decode('UTF-8'))
if out.startswith(b'(false,'):
return True
return False
#################################################################
#
# main
#
#################################################################
if len(sys.argv) != 4:
try:
print('Usage: %s <pid> <signal number> <core file ulimit>' % sys.argv[0])
print('The core dump is read from stdin.')
except IOError:
# sys.stderr might not actually exist, expecially not when being called
# from the kernel
pass
sys.exit(1)
init_error_log()
check_lock()
try:
setup_signals()
(pid, signum, core_ulimit) = sys.argv[1:]
# drop our process priority level to not disturb userspace so much
try:
os.nice(10)
except OSError:
pass # *shrug*, we tried
get_pid_info(pid)
# Partially drop privs to gain proper os.access() checks
drop_privileges(pid, True)
# ignore SIGQUIT (it's usually deliberately generated by users)
if signum == str(signal.SIGQUIT):
sys.exit(0)
error_log('called for pid %s, signal %s' % (pid, signum))
# check if the executable was modified after the process started (e. g.
# package got upgraded in between)
exe_mtime = os.stat('/proc/%s/exe' % pid).st_mtime
process_start = os.lstat('/proc/%s/cmdline' % pid).st_mtime
if not os.path.exists(os.readlink('/proc/%s/exe' % pid)) or exe_mtime > process_start:
error_log('executable was modified after program start, ignoring')
sys.exit(1)
info = apport.Report('Crash')
info['Signal'] = signum
info['CoreDump'] = (sys.stdin, True, usable_ram()*3/4, True)
# We already need this here to figure out the ExecutableName (for scripts,
# etc).
info.add_proc_info(pid)
if 'ExecutablePath' not in info:
error_log('could not determine ExecutablePath, aborting')
sys.exit(1)
if 'InterpreterPath' in info:
error_log('script: %s, interpreted by %s (command line "%s")' %
(info['ExecutablePath'], info['InterpreterPath'],
info['ProcCmdline']))
else:
error_log('executable: %s (command line "%s")' %
(info['ExecutablePath'], info['ProcCmdline']))
# ignore non-package binaries (unless configured otherwise)
if not apport.fileutils.likely_packaged(info['ExecutablePath']):
if not apport.fileutils.get_config('main', 'unpackaged', False, bool=True):
error_log('executable does not belong to a package, ignoring')
# check if the user wants a core dump
drop_privileges(pid)
write_user_coredump(pid, cwd, core_ulimit)
sys.exit(1)
# ignore SIGXCPU and SIGXFSZ since this indicates some external
# influence changing soft RLIMIT values when running programs.
if signum in [str(signal.SIGXCPU),str(signal.SIGXFSZ)]:
error_log('Ignoring signal %s (caused by exceeding soft RLIMIT)' % signum)
drop_privileges(pid)
write_user_coredump(pid, cwd, core_ulimit)
sys.exit(0)
# ignore blacklisted binaries
if info.check_ignored():
error_log('executable version is blacklisted, ignoring')
sys.exit(1)
if is_closing_session(pid, pidstat.st_uid):
error_log('happens for shutting down session, ignoring')
sys.exit(1)
crash_counter = 0
# Create crash report file descriptor for writing the report into
# report_dir
try:
report = '%s/%s.%i.crash' % (apport.fileutils.report_dir, info['ExecutablePath'].replace('/', '_'), pidstat.st_uid)
if os.path.exists(report):
if apport.fileutils.seen_report(report):
# do not flood the logs and the user with repeated crashes
crash_counter = apport.fileutils.get_recent_crashes(open(report))
crash_counter += 1
if crash_counter > 1:
drop_privileges(pid)
write_user_coredump(pid, cwd, core_ulimit)
error_log('this executable already crashed %i times, ignoring' % crash_counter)
sys.exit(1)
# remove the old file, so that we can create the new one with
# os.O_CREAT|os.O_EXCL
os.unlink(report)
else:
error_log('apport: report %s already exists and unseen, doing nothing to avoid disk usage DoS' % report)
drop_privileges(pid)
write_user_coredump(pid, cwd, core_ulimit)
sys.exit(1)
# we prefer having a file mode of 0 while writing; this doesn't work
# for suid binaries as we completely drop privs and thus can't chmod
# them later on
if pidstat.st_uid != os.getuid():
mode = 0o640
else:
mode = 0
reportfile = os.fdopen(os.open(report, os.O_WRONLY | os.O_CREAT | os.O_EXCL, mode), 'wb')
assert reportfile.fileno() > sys.stderr.fileno()
# Make sure the crash reporting daemon can read this report
try:
gid = pwd.getpwnam('whoopsie').pw_gid
os.chown(report, pidstat.st_uid, gid)
except (OSError, KeyError):
os.chown(report, pidstat.st_uid, pidstat.st_gid)
except (OSError, IOError) as e:
error_log('Could not create report file: %s' % str(e))
sys.exit(1)
# Totally drop privs before writing out the reportfile.
drop_privileges(pid)
# check if the user wants a core dump
coredump = write_user_coredump(pid, cwd, core_ulimit)
# Now that we've written it, it's no longer on stdin, so we need
# to change the CoreDump info if we want it in the crash report.
if coredump:
info['CoreDump'] = (open(coredump), True, usable_ram()*3/4)
info.add_user_info()
info.add_os_info()
if crash_counter > 0:
info['CrashCounter'] = '%i' % crash_counter
try:
info.write(reportfile)
except IOError:
if reportfile != sys.stderr:
os.unlink(report)
raise
if report and mode == 0:
# for non-suid programs, make the report writable now, when it's
# completely written
os.chmod(report, 0o640)
if reportfile != sys.stderr:
error_log('wrote report %s' % report)
except (SystemExit, KeyboardInterrupt):
raise
except Exception as e:
error_log('Unhandled exception:')
traceback.print_exc()
error_log('pid: %i, uid: %i, gid: %i, euid: %i, egid: %i' % (
os.getpid(), os.getuid(), os.getgid(), os.geteuid(), os.getegid()))
error_log('environment: %s' % str(os.environ))