diff -u apport-2.0.1/kde/apport-kde apport-2.0.1/kde/apport-kde --- apport-2.0.1/kde/apport-kde +++ apport-2.0.1/kde/apport-kde @@ -198,7 +198,7 @@ 'unexpectedly.') % desktop_file.getName()) self.text.hide() - if 'ProcCmdline' in report: + if 'ProcCmdline' in report and self.ui.offer_restart: self.closed_button.show() self.closed_button.setText(_('Leave Closed')) self.continue_button.setText(_('Relaunch')) @@ -338,7 +338,7 @@ return return_value text = self.dialog.continue_button.text().remove('&') - if response == 1 and text == _('Relaunch'): + if response == 1 and text == _('Relaunch') and self.offer_restart: return_value['restart'] = True if self.dialog.send_error_report.isChecked(): return_value['report'] = True diff -u apport-2.0.1/apport/ui.py apport-2.0.1/apport/ui.py --- apport-2.0.1/apport/ui.py +++ apport-2.0.1/apport/ui.py @@ -143,6 +143,7 @@ self.report = None self.report_file = None self.cur_package = None + self.offer_restart = False try: self.crashdb = get_crashdb(None) @@ -168,6 +169,9 @@ otherwise. ''' result = False + # for iterating over /var/crash (as opposed to running on or clicking + # on a particular .crash file) we offer restarting + self.offer_restart = True if os.geteuid() == 0: reports = apport.fileutils.get_new_system_reports() diff -u apport-2.0.1/apport/report.py apport-2.0.1/apport/report.py --- apport-2.0.1/apport/report.py +++ apport-2.0.1/apport/report.py @@ -758,7 +758,11 @@ if not package: package = self.get('Package') if package: - hook = '%s/%s.py' % (_hook_dir, package.split()[0]) + package = package.split()[0] + if '/' in package: + self['UnreportableReason'] = 'invalid Package: %s' % package + return + hook = '%s/%s.py' % (_hook_dir, package) if os.path.exists(hook): try: with open(hook) as fd: @@ -783,7 +787,11 @@ if not srcpackage: srcpackage = self.get('SourcePackage') if srcpackage: - hook = '%s/source_%s.py' % (_hook_dir, srcpackage.split()[0]) + srcpackage = srcpackage.split()[0] + if '/' in srcpackage: + self['UnreportableReason'] = 'invalid SourcePackage: %s' % package + return + hook = '%s/source_%s.py' % (_hook_dir, srcpackage) if os.path.exists(hook): try: with open(hook) as fd: 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,22 @@ +apport (2.0.1-0ubuntu17.15) precise-security; urgency=medium + + [ Marc Deslauriers ] + * SECURITY UPDATE: path traversal vulnerability with hooks execution + - Clean path in apport/report.py, added test to test/test_ui.py. + - No CVE number + - LP: #1648806 + + [ Steve Beattie ] + * SECURITY UPDATE: code execution via malicious crash files + - Only offer restarting the application when processing a + crash file in /var/crash in apport/ui.py, gtk/apport-gtk, + and kde/apport-kde. Add testcases to test/test_ui.py, + test/test_ui_gtk.py, and test_ui_kde.py. + - No CVE number + - LP: #1648806 + + -- Marc Deslauriers Mon, 12 Dec 2016 07:34:52 -0500 + apport (2.0.1-0ubuntu17.13) precise-security; urgency=medium * SECURITY FIX: When determining the path of a Python module for a program diff -u apport-2.0.1/test/test_ui_gtk.py apport-2.0.1/test/test_ui_gtk.py --- apport-2.0.1/test/test_ui_gtk.py +++ apport-2.0.1/test/test_ui_gtk.py @@ -198,6 +198,8 @@ | [ Show Details ] [ Leave Closed ] [ Relaunch ] | +-----------------------------------------------------------------+ ''' + # pretend we got called through run_crashes() which sets offer_restart + self.app.offer_restart = True self.app.report['ProblemType'] = 'Crash' self.app.report['CrashCounter'] = '1' self.app.report['ProcCmdline'] = 'apport-bug apport' @@ -228,6 +230,39 @@ self.assertTrue(self.app.w('ignore_future_problems').get_label().endswith( 'of this program version')) + def test_regular_crash_layout_norestart(self): + ''' + +-----------------------------------------------------------------+ + | [ apport ] The application Apport has closed unexpectedly. | + | | + | [x] Send an error report to help fix this problem. | + | [ ] Ignore future problems of this program version. | + | | + | [ Show Details ] [ Continue ] | + +-----------------------------------------------------------------+ + ''' + # pretend we did not get called through run_crashes(), thus no offer_restart + self.app.report['ProblemType'] = 'Crash' + self.app.report['CrashCounter'] = '1' + self.app.report['ProcCmdline'] = 'apport-bug apport' + self.app.report['Package'] = 'apport 1.2.3~0ubuntu1' + with tempfile.NamedTemporaryFile() as fp: + fp.write(b'''[Desktop Entry] +Version=1.0 +Name=Apport +Type=Application''') + fp.flush() + self.app.report['DesktopFile'] = fp.name + GLib.idle_add(Gtk.main_quit) + self.app.ui_present_report_details(True) + self.assertEqual(self.app.w('dialog_crash_new').get_title(), self.distro) + self.assertEqual(self.app.w('title_label').get_text(), + _('The application Apport has closed unexpectedly.')) + self.assertTrue(self.app.w('continue_button').get_property('visible')) + self.assertEqual(self.app.w('continue_button').get_label(), + _('Continue')) + self.assertFalse(self.app.w('closed_button').get_property('visible')) + def test_system_crash_layout(self): ''' +---------------------------------------------------------------+ diff -u apport-2.0.1/test/test_ui_kde.py apport-2.0.1/test/test_ui_kde.py --- apport-2.0.1/test/test_ui_kde.py +++ apport-2.0.1/test/test_ui_kde.py @@ -188,6 +188,8 @@ | [ Show Details ] [ Leave Closed ] [ Relaunch ] | +-----------------------------------------------------------------+ ''' + # pretend we got called through run_crashes() which sets offer_restart + self.app.offer_restart = True self.app.report['ProblemType'] = 'Crash' self.app.report['CrashCounter'] = '1' self.app.report['ProcCmdline'] = 'apport-bug apport' @@ -217,6 +219,40 @@ self.assertTrue(str(self.app.dialog.ignore_future_problems.text()).endswith( 'of this program version')) + def test_regular_crash_layout_norestart(self): + ''' + +-----------------------------------------------------------------+ + | [ apport ] The application Apport has closed unexpectedly. | + | | + | [x] Send an error report to help fix this problem. | + | [ ] Ignore future problems of this program version. | + | | + | [ Show Details ] [ Continue ] | + +-----------------------------------------------------------------+ + ''' + # pretend we did not get called through run_crashes(), thus no offer_restart + self.app.report['ProblemType'] = 'Crash' + self.app.report['CrashCounter'] = '1' + self.app.report['ProcCmdline'] = 'apport-bug apport' + self.app.report['Package'] = 'apport 1.2.3~0ubuntu1' + with tempfile.NamedTemporaryFile() as fp: + fp.write(b'''[Desktop Entry] +Version=1.0 +Name=Apport +Type=Application''') + fp.flush() + self.app.report['DesktopFile'] = fp.name + QTimer.singleShot(0, QCoreApplication.quit) + self.app.ui_present_report_details(True) + self.assertEqual(self.app.dialog.heading.text(), + _('The application Apport has closed unexpectedly.')) + self.assertTrue(self.app.dialog.send_error_report.isVisible()) + self.assertTrue(self.app.dialog.send_error_report.isChecked()) + self.assertTrue(self.app.dialog.details.isVisible()) + self.assertTrue(self.app.dialog.continue_button.isVisible()) + self.assertEqual(self.app.dialog.continue_button.text(), _('Continue')) + self.assertFalse(self.app.dialog.closed_button.isVisible()) + def test_system_crash_layout(self): ''' +-----------------------------------------------------------------+ diff -u apport-2.0.1/test/test_ui.py apport-2.0.1/test/test_ui.py --- apport-2.0.1/test/test_ui.py +++ apport-2.0.1/test/test_ui.py @@ -395,6 +395,22 @@ self.ui = TestSuiteUserInterface() self.assertEqual(self.ui.run_argv(), False) + def test_run_restart(self): + '''running the frontend with pending reports offers restart''' + + r = self._gen_test_crash() + report_file = os.path.join(apport.fileutils.report_dir, 'test.crash') + with open(report_file, 'wb') as f: + r.write(f) + sys.argv = [] + self.ui = TestSuiteUserInterface() + self.ui.present_details_response = {'report': False, + 'blacklist': False, + 'examine': False, + 'restart': False} + self.ui.run_argv() + self.assertEqual(self.ui.offer_restart, True) + def test_run_report_bug_noargs(self): '''run_report_bug() without specifying arguments''' @@ -672,6 +688,7 @@ self.assertEqual(self.ui.msg_title, None) self.assertEqual(self.ui.opened_url, None) self.assertEqual(self.ui.ic_progress_pulses, 0) + self.assertEqual(self.ui.offer_restart, False) # report in crash notification dialog, send full report r.write(open(report_file, 'w')) @@ -715,6 +732,7 @@ self.assertEqual(self.ui.ic_progress_pulses, 0) self.assertTrue(self.ui.report.check_ignored()) + self.assertEqual(self.ui.offer_restart, False) def test_run_crash_abort(self): '''run_crash() for an abort() without assertion message''' @@ -829,6 +847,26 @@ (self.ui.msg_title, self.ui.msg_text)) self.assertEqual(self.ui.msg_severity, 'info') + def test_run_crash_malicious_package(self): + '''Package: path traversal''' + + bad_hook = tempfile.NamedTemporaryFile(suffix='.py') + bad_hook.write(b"def add_info(r, u):\n open('/tmp/pwned', 'w').close()") + bad_hook.flush() + + self.report['ExecutablePath'] = '/bin/bash' + self.report['Package'] = '../' * 20 + os.path.splitext(bad_hook.name)[0] + self.update_report_file() + self.ui.present_details_response = {'report': True, + 'blacklist': False, + 'examine': False, + 'restart': False} + + self.ui.run_crash(self.report_file.name) + + self.assertFalse(os.path.exists('/tmp/pwned')) + self.assertIn('invalid Package:', self.ui.msg_text) + def test_run_crash_ignore(self): '''run_crash() on a crash with the Ignore field''' self.report['Ignore'] = 'True' diff -u apport-2.0.1/gtk/apport-gtk apport-2.0.1/gtk/apport-gtk --- apport-2.0.1/gtk/apport-gtk +++ apport-2.0.1/gtk/apport-gtk @@ -216,7 +216,7 @@ _('The application %s has closed unexpectedly.') % n) self.w('subtitle_label').hide() - if 'ProcCmdline' in self.report: + if 'ProcCmdline' in self.report and self.offer_restart: self.w('closed_button').show() self.w('closed_button').set_label(_('Leave Closed')) self.w('continue_button').set_label(_('Relaunch')) @@ -290,7 +290,7 @@ return_value['report'] = True if self.w('ignore_future_problems').get_active(): return_value['blacklist'] = True - if widget == self.w('continue_button') and self.desktop_file: + if widget == self.w('continue_button') and self.desktop_file and self.offer_restart: return_value['restart'] = True Gtk.main_quit()