apport-{gtk,kde}: Only offer "Relaunch" for recent /var/crash crashes It only makes sense to offer relaunching for crashes that just happened and the apport UI got triggered on those. When opening a .crash file copied from somewhere else or after the crash happened, this is even actively dangerous as a malicious crash file can specify any arbitrary command to run. Note that apport-cli has never offered restarting (as that's not a reasonable thing to do in a shell), thus does not need to be changed. Thanks to Donncha O'Cearbhaill for discovering this! CVE-2016-XXXX LP: #1648806 === modified file 'apport/ui.py' --- old/apport/ui.py 2016-12-10 14:05:18 +0000 +++ new/apport/ui.py 2016-12-13 16:06:33 +0000 @@ -189,6 +189,7 @@ self.report = None self.report_file = None self.cur_package = None + self.offer_restart = False try: self.crashdb = apport.crashdb.get_crashdb(None) @@ -219,6 +220,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() === modified file 'gtk/apport-gtk' --- old/gtk/apport-gtk 2016-12-10 11:28:27 +0000 +++ new/gtk/apport-gtk 2016-12-13 16:06:33 +0000 @@ -224,7 +224,7 @@ if xid: self.set_modal_for(xid) - if report_type == 'Hang': + if report_type == 'Hang' and self.offer_restart: self.w('ignore_future_problems').set_active(False) self.w('ignore_future_problems').hide() self.w('closed_button').show() @@ -282,7 +282,7 @@ pid = apport.ui.get_pid(self.report) still_running = pid and apport.ui.still_running(pid) - if 'ProcCmdline' not in self.report or still_running: + if 'ProcCmdline' not in self.report or still_running or not self.offer_restart: self.w('closed_button').hide() self.w('continue_button').set_label(_('Continue')) else: @@ -370,7 +370,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_info: + if widget == self.w('continue_button') and self.desktop_info and self.offer_restart: return_value['restart'] = True Gtk.main_quit() === modified file 'kde/apport-kde' --- old/kde/apport-kde 2016-12-10 11:28:27 +0000 +++ new/kde/apport-kde 2016-12-13 16:06:33 +0000 @@ -226,7 +226,7 @@ pid = apport.ui.get_pid(report) still_running = pid and apport.ui.still_running(pid) - if 'ProcCmdline' not in report or still_running: + if 'ProcCmdline' not in report or still_running or not self.ui.offer_restart: self.closed_button.hide() self.continue_button.setText(_('Continue')) else: @@ -378,7 +378,7 @@ return return_value text = self.dialog.continue_button.text().replace('&', '') - 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 === modified file 'test/test_ui.py' --- old/test/test_ui.py 2016-12-10 16:47:06 +0000 +++ new/test/test_ui.py 2016-12-13 16:06:33 +0000 @@ -485,6 +485,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''' @@ -773,6 +789,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 with open(report_file, 'wb') as f: @@ -818,6 +835,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''' === modified file 'test/test_ui_gtk.py' --- old/test/test_ui_gtk.py 2016-12-10 11:28:27 +0000 +++ new/test/test_ui_gtk.py 2016-12-13 16:06:33 +0000 @@ -211,6 +211,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' @@ -240,6 +242,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_hang_layout(self): ''' +-----------------------------------------------------------------+ @@ -250,6 +285,8 @@ | [ Show Details ] [ Force Closed ] [ Relaunch ] | +-----------------------------------------------------------------+ ''' + # pretend we got called through run_crashes() which sets offer_restart + self.app.offer_restart = True self.app.report['ProblemType'] = 'Hang' self.app.report['ProcCmdline'] = 'apport-bug apport' self.app.report['Package'] = 'apport 1.2.3~0ubuntu1' === modified file 'test/test_ui_kde.py' --- old/test/test_ui_kde.py 2016-12-10 11:28:27 +0000 +++ new/test/test_ui_kde.py 2016-12-13 16:06:33 +0000 @@ -202,6 +202,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' @@ -231,6 +233,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): ''' +-----------------------------------------------------------------+