diff -u apport-2.0.1/apport/fileutils.py apport-2.0.1/apport/fileutils.py --- apport-2.0.1/apport/fileutils.py +++ apport-2.0.1/apport/fileutils.py @@ -221,10 +221,14 @@ except (ValueError, KeyError): return 0 -def make_report_path(report, uid=None): - '''Construct a canonical pathname for the given report. +def make_report_file(report, uid=None): + '''Construct a canonical pathname for a report and open it for writing If uid is not given, it defaults to the uid of the current process. + The report file must not exist already, to prevent losing previous reports + or symlink attacks. + + Return an open file object for binary writing. ''' if 'ExecutablePath' in report: subject = report['ExecutablePath'].replace('/', '_') @@ -236,7 +240,8 @@ if not uid: uid = os.getuid() - return os.path.join(report_dir, '%s.%s.crash' % (subject, str(uid))) + path = os.path.join(report_dir, '%s.%s.crash' % (subject, str(uid))) + return os.fdopen(os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o640), 'wb') def check_files_md5(sumfile): '''Check file integrity against md5 sum file. diff -u apport-2.0.1/debian/changelog apport-2.0.1/debian/changelog --- apport-2.0.1/debian/changelog +++ apport-2.0.1/debian/changelog @@ -1,3 +1,23 @@ +apport (2.0.1-0ubuntu17.10) precise-security; urgency=medium + + * SECURITY FIX: kernel_crashdump: Enforce that the log/dmesg files are not a + symlink. + This prevents normal users from pre-creating a symlink to the predictable + .crash file, and thus triggering a "fill up disk" DoS attack when the + .crash report tries to include itself. Thanks to halfdog for discovering + this! (CVE-2015-1338, part of LP #1492570) + * SECURITY FIX: Fix all writers of report files to open the report file + exclusively. + Fix package_hook, kernel_crashdump, and similar hooks to fail if the + report already exists. This prevents privilege escalation through symlink + attacks. Note that this will also prevent overwriting previous reports + with the same same. Thanks to halfdog for discovering this! + (CVE-2015-1338, LP: #1492570) + * debian/tests/upstream-system: Change directory to /tmp, so that tests + actually run against the installed package. + + -- Martin Pitt Mon, 21 Sep 2015 11:58:45 +0200 + apport (2.0.1-0ubuntu17.9) precise-security; urgency=medium * SECURITY UPDATE: When /proc/sys/fs/suid_dumpable is enabled, crashing a diff -u apport-2.0.1/debian/tests/upstream-system apport-2.0.1/debian/tests/upstream-system --- apport-2.0.1/debian/tests/upstream-system +++ apport-2.0.1/debian/tests/upstream-system @@ -5,2 +5,3 @@ # work around LP #972324 +cd /tmp/ env -u TMPDIR /usr/share/apport/testsuite/run 2>&1 diff -u apport-2.0.1/test/test_fileutils.py apport-2.0.1/test/test_fileutils.py --- apport-2.0.1/test/test_fileutils.py +++ apport-2.0.1/test/test_fileutils.py @@ -208,16 +208,30 @@ CrashCounter: 3''' % time.ctime(time.mktime(time.localtime())-3600)) self.assertEqual(apport.fileutils.get_recent_crashes(r), 3) - def test_make_report_path(self): - '''make_report_path()''' + def test_make_report_file(self): + '''make_report_file()''' pr = problem_report.ProblemReport() - self.assertRaises(ValueError, apport.fileutils.make_report_path, pr) + self.assertRaises(ValueError, apport.fileutils.make_report_file, pr) pr['Package'] = 'bash 1' - self.assertTrue(apport.fileutils.make_report_path(pr).startswith('%s/bash' % apport.fileutils.report_dir)) - pr['ExecutablePath'] = '/bin/bash'; - self.assertTrue(apport.fileutils.make_report_path(pr).startswith('%s/_bin_bash' % apport.fileutils.report_dir)) + with apport.fileutils.make_report_file(pr) as f: + path = os.path.join(apport.fileutils.report_dir, os.listdir(apport.fileutils.report_dir)[0]) + self.assertTrue(path.startswith('%s/bash' % apport.fileutils.report_dir), path) + os.unlink(path) + + pr['ExecutablePath'] = '/bin/bash' + with apport.fileutils.make_report_file(pr) as f: + path = os.path.join(apport.fileutils.report_dir, os.listdir(apport.fileutils.report_dir)[0]) + self.assertTrue(path.startswith('%s/_bin_bash' % apport.fileutils.report_dir), path) + + # file exists already, should fail now + self.assertRaises(OSError, apport.fileutils.make_report_file, pr) + + # should still fail if it's a dangling symlink + os.unlink(path) + os.symlink(os.path.join(apport.fileutils.report_dir, 'pwned'), path) + self.assertRaises(OSError, apport.fileutils.make_report_file, pr) def test_check_files_md5(self): '''check_files_md5()''' only in patch2: unchanged: --- apport-2.0.1.orig/data/gcc_ice_hook +++ apport-2.0.1/data/gcc_ice_hook @@ -31,4 +31,5 @@ pr['PreprocessedSource'] = (open(sourcefile), False) # write report -pr.write(open(apport.fileutils.make_report_path(pr), 'w')) +with apport.fileutils.make_report_file(pr) as f: + pr.write(f) only in patch2: unchanged: --- apport-2.0.1.orig/data/java_uncaught_exception +++ apport-2.0.1/data/java_uncaught_exception @@ -80,7 +80,8 @@ report['Title'] = make_title(report) - report.write(open(apport.fileutils.make_report_path(report), 'w')) + with apport.fileutils.make_report_file(report) as f: + report.write(f) if __name__ == '__main__': main() only in patch2: unchanged: --- apport-2.0.1.orig/data/kernel_crashdump +++ apport-2.0.1/data/kernel_crashdump @@ -22,14 +22,28 @@ pr = apport.Report('KernelCrash') pr['Package'] = apport.packaging.get_kernel_package() -pr['VmCore'] = (vmcore_path,) +try: + core_fd = os.open(vmcore_path, os.O_RDONLY | os.O_NOFOLLOW) + pr['VmCore'] = (os.fdopen(core_fd, 'rb'),) +except (IOError, OSError) as e: + apport.fatal('Cannot create report: ' + str(e)) + pr.add_os_info() + +# only accept plain files here, not symlinks; otherwise we might recursively +# include the report, or similar DoS attacks if os.path.exists(vmcore_path + '.log'): - pr['VmCoreLog'] = (vmcore_path + '.log',) + try: + log_fd = os.open(vmcore_path + '.log', os.O_RDONLY | os.O_NOFOLLOW) + pr['VmCoreLog'] = (os.fdopen(log_fd, 'rb'),) + os.unlink(vmcore_path + '.log') + except OSError as e: + apport.fatal('Cannot open vmcore log: ' + str(e)) # write report try: - pr.write(open(apport.fileutils.make_report_path(pr), 'w')) + with apport.fileutils.make_report_file(pr) as f: + pr.write(f) except IOError, e: apport.fatal('Cannot create report: ' + str(e)) only in patch2: unchanged: --- apport-2.0.1.orig/data/kernel_oops +++ apport-2.0.1/data/kernel_oops @@ -33,6 +33,5 @@ pr['OopsText'] = oops # write report -report_path = apport.fileutils.make_report_path(pr, uid=checksum) -if not os.path.exists(report_path): - pr.write(open(report_path, 'w')) +with apport.fileutils.make_report_file(pr, uid=checksum) as f: + pr.write(f) only in patch2: unchanged: --- apport-2.0.1.orig/data/package_hook +++ apport-2.0.1/data/package_hook @@ -56,4 +56,5 @@ pr[mkattrname(path)] = (path,) # write report -pr.write(open(apport.fileutils.make_report_path(pr), 'w')) +with apport.fileutils.make_report_file(pr) as f: + pr.write(f) only in patch2: unchanged: --- apport-2.0.1.orig/data/unkillable_shutdown +++ apport-2.0.1/data/unkillable_shutdown @@ -92,9 +92,8 @@ if blacklist: r['OmitPids'] = ' '.join(blacklist) - f = open(apport.fileutils.make_report_path(r), 'w') - r.write(f) - f.close() + with apport.fileutils.make_report_file(r) as f: + r.write(f) # # main only in patch2: unchanged: --- apport-2.0.1.orig/test/test_hooks.py +++ apport-2.0.1/test/test_hooks.py @@ -148,6 +148,23 @@ r.add_package_info(r['Package']) self.assertTrue(' ' in r['Package']) # appended version number + def test_kernel_crashdump_log_symlink(self): + '''attempted DoS with vmcore.log symlink + + We must only accept plain files, otherwise vmcore.log might be a + symlink to the .crash file, which would recursively fill itself. + ''' + f = open(os.path.join(apport.fileutils.report_dir, 'vmcore'), 'wb') + f.write(b'\x01' * 100) + f.close() + os.symlink('vmcore', os.path.join(apport.fileutils.report_dir, 'vmcore.log')) + + self.assertNotEqual(subprocess.call('%s/kernel_crashdump' % datadir, + stderr=subprocess.PIPE), + 0, 'kernel_crashdump unexpectedly succeeded') + + self.assertEqual(apport.fileutils.get_new_reports(), []) + @classmethod def _gcc_version_path(klass): '''Determine a valid version and executable path of gcc and return it