diff -Nru apport-2.20.9/apport/fileutils.py apport-2.20.9/apport/fileutils.py --- apport-2.20.9/apport/fileutils.py 2020-11-10 15:03:57.000000000 -0500 +++ apport-2.20.9/apport/fileutils.py 2021-01-26 07:21:46.000000000 -0500 @@ -376,6 +376,34 @@ get_config.config = None +def get_starttime(contents): + '''Extracts the starttime from the contents of a stat file''' + + # 22nd field in a stat file is the time the process started after + # system boot in clock ticks. In order to prevent filename + # manipulations including spaces or extra parentheses, skip all the way + # to the very last closing parentheses, then start counting. + stripped = contents[contents.rfind(")") + 2:] + # We've skipped over the PID and the filename, so index is now 19. + return int(stripped.split()[19]) + + +def get_uid_and_gid(contents): + '''Extracts the uid and gid from the contents of a status file''' + + real_uid = None + real_gid = None + for line in contents.splitlines(): + # Iterate through the whole contents to make sure we're getting + # the last Uid and Gid lines in the file and not a manipulated + # process name with embedded newlines. + if line.startswith('Uid:') and len(line.split()) > 1: + real_uid = int(line.split()[1]) + elif line.startswith('Gid:') and len(line.split()) > 1: + real_gid = int(line.split()[1]) + return (real_uid, real_gid) + + def shared_libraries(path): '''Get libraries with which the specified binary is linked. diff -Nru apport-2.20.9/data/apport apport-2.20.9/data/apport --- apport-2.20.9/data/apport 2020-12-03 12:39:32.000000000 -0500 +++ apport-2.20.9/data/apport 2021-01-26 07:21:46.000000000 -0500 @@ -15,7 +15,7 @@ import sys, os, os.path, subprocess, time, traceback, pwd, io import signal, inspect, grp, fcntl, socket, atexit, array, struct -import errno, argparse +import errno, argparse, stat import apport, apport.fileutils @@ -76,33 +76,22 @@ # 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('status', opener=proc_pid_opener) 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 + with open('status', opener=proc_pid_opener) as status_file: + contents = status_file.read() + (real_uid, real_gid) = apport.fileutils.get_uid_and_gid(contents) + assert real_uid is not None, 'failed to parse Uid' assert real_gid is not None, 'failed to parse Gid' cwd = os.open('cwd', os.O_RDONLY | os.O_PATH | os.O_DIRECTORY, dir_fd=proc_pid_fd) -def get_starttime(contents): - '''Extracts the starttime from the contents of a stat file''' - - # 22nd field in a stat file is the time the process started after - # system boot in clock ticks. - return int(contents.split()[21]) - - def get_process_starttime(): '''Get the starttime of the process using proc_pid_fd''' with open("stat", opener=proc_pid_opener) as stat_file: contents = stat_file.read() - return get_starttime(contents) + return apport.fileutils.get_starttime(contents) def get_apport_starttime(): @@ -110,7 +99,7 @@ with open("/proc/%s/stat" % os.getpid()) as stat_file: contents = stat_file.read() - return get_starttime(contents) + return apport.fileutils.get_starttime(contents) def drop_privileges(real_only=False): @@ -727,14 +716,18 @@ if os.path.exists(report): if apport.fileutils.seen_report(report): # do not flood the logs and the user with repeated crashes - with open(report, 'rb') as f: - crash_counter = apport.fileutils.get_recent_crashes(f) - crash_counter += 1 - if crash_counter > 1: - drop_privileges() - write_user_coredump(pid, cwd, core_ulimit) - error_log('this executable already crashed %i times, ignoring' % crash_counter) - sys.exit(0) + # and make sure the file isn't a FIFO or symlink + fd = os.open(report, os.O_NOFOLLOW | os.O_RDONLY) + st = os.fstat(fd) + if stat.S_ISREG(st.st_mode): + with os.fdopen(fd, 'rb') as f: + crash_counter = apport.fileutils.get_recent_crashes(f) + crash_counter += 1 + if crash_counter > 1: + drop_privileges() + write_user_coredump(pid, cwd, core_ulimit) + error_log('this executable already crashed %i times, ignoring' % crash_counter) + sys.exit(0) # remove the old file, so that we can create the new one with # os.O_CREAT|os.O_EXCL os.unlink(report) diff -Nru apport-2.20.9/test/test_fileutils.py apport-2.20.9/test/test_fileutils.py --- apport-2.20.9/test/test_fileutils.py 2019-11-11 16:57:56.000000000 -0500 +++ apport-2.20.9/test/test_fileutils.py 2021-01-26 07:21:46.000000000 -0500 @@ -396,6 +396,98 @@ self.assertFalse(apport.fileutils.links_with_shared_library('/etc', 'libc')) self.assertFalse(apport.fileutils.links_with_shared_library('/etc/passwd', 'libc')) + def test_get_starttime(self): + '''get_starttime()''' + + template = ('2022799 (%s) S 1834041 2022799 2022799 34820 ' + '2022806 4194304 692 479 0 0 2 0 0 0 20 0 1 0 ' + '34034895 13750272 1270 18446744073709551615 ' + '94876723015680 94876723738373 140731174338544 ' + '0 0 0 65536 3670020 1266777851 1 0 0 17 7 0 0 0 0 0 ' + '94876723969264 94876724016644 94876748173312 ' + '140731174345133 140731174345138 140731174345138 ' + '140731174346734 0\n') + + for name in ['goodname', 'bad name', '(badname', 'badname)', + '(badname)', '((badname))', 'badname(', ')badname', + 'bad) name', 'bad) name\\']: + starttime = apport.fileutils.get_starttime(template % name) + self.assertEqual(starttime, 34034895) + + def test_get_uid_and_gid(self): + '''get_uid_and_gid()''' + + # Python 3's open uses universal newlines, which means all + # line endings get replaced with \n, so we don't need to test + # different line ending combinations + template = ('Name:\t%s\n' + 'Umask:\t0002\n' + 'State:\tS (sleeping)\n' + 'Tgid:\t2288405\n' + 'Ngid:\t0\n' + 'Pid:\t2288405\n' + 'PPid:\t2167353\n' + 'TracerPid:\t0\n' + 'Uid:\t1000\t1000\t1000\t1000\n' + 'Gid:\t2000\t2000\t2000\t2000\n' + 'FDSize:\t256\n' + 'Groups:\t4 20 24 27 30 46 108 120 131 132 135 137 1000 \n' + 'NStgid:\t2288405\n' + 'NSpid:\t2288405\n' + 'NSpgid:\t2288405\n' + 'NSsid:\t2288405\n' + 'VmPeak:\t 13428 kB\n' + 'VmSize:\t 13428 kB\n' + 'VmLck:\t 0 kB\n' + 'VmPin:\t 0 kB\n' + 'VmHWM:\t 5200 kB\n' + 'VmRSS:\t 5200 kB\n' + 'RssAnon:\t 1700 kB\n' + 'RssFile:\t 3500 kB\n' + 'RssShmem:\t 0 kB\n' + 'VmData:\t 1600 kB\n' + 'VmStk:\t 132 kB\n' + 'VmExe:\t 708 kB\n' + 'VmLib:\t 1748 kB\n' + 'VmPTE:\t 68 kB\n' + 'VmSwap:\t 0 kB\n' + 'HugetlbPages:\t 0 kB\n' + 'CoreDumping:\t0\n' + 'THP_enabled:\t1\n' + 'Threads:\t1\n' + 'SigQ:\t0/62442\n' + 'SigPnd:\t0000000000000000\n' + 'ShdPnd:\t0000000000000000\n' + 'SigBlk:\t0000000000010000\n' + 'SigIgn:\t0000000000380004\n' + 'SigCgt:\t000000004b817efb\n' + 'CapInh:\t0000000000000000\n' + 'CapPrm:\t0000000000000000\n' + 'CapEff:\t0000000000000000\n' + 'CapBnd:\t000000ffffffffff\n' + 'CapAmb:\t0000000000000000\n' + 'NoNewPrivs:\t0\n' + 'Seccomp:\t0\n' + 'Speculation_Store_Bypass:\tthread vulnerable\n' + 'Cpus_allowed:\tff\n' + 'Cpus_allowed_list:\t0-7\n' + 'Mems_allowed:\t00000000,00000000,00000000,00000000,' + '00000000,00000000,00000000,00000000,00000000,' + '00000000,00000000,00000000,00000000,00000000,' + '00000000,00000000,00000000,00000000,00000000,' + '00000000,00000000,00000000,00000000,00000000,' + '00000000,00000000,00000000,00000000,00000000,' + '00000000,00000000,00000001\n' + 'Mems_allowed_list:\t0\n' + 'voluntary_ctxt_switches:\t133\n' + 'nonvoluntary_ctxt_switches:\t0\n') + + for name in ['goodname', 'Uid:', '\nUid:', 'Uid: 1000 1000 1000\n', + 'a\nUid: 0\nGid: 0', 'a\nUid: 0\nGid:']: + (uid, gid) = apport.fileutils.get_uid_and_gid(template % name) + self.assertEqual(uid, 1000) + self.assertEqual(gid, 2000) + if __name__ == '__main__': unittest.main()