=== modified file 'apport/report.py' --- apport/report.py 2017-06-19 20:24:46 +0000 +++ apport/report.py 2017-07-10 22:31:49 +0000 @@ -862,8 +862,9 @@ hook_dirs = [_hook_dir] # also search hooks in /opt, when program is from there opt_path = None - if self.get('ExecutablePath', '').startswith(_opt_dir): - opt_path = self.get('ExecutablePath', '') + exec_path = os.path.realpath(self.get('ExecutablePath', '')) + if exec_path.startswith(_opt_dir): + opt_path = exec_path elif package: # check package contents try: === modified file 'test/test_ui.py' --- test/test_ui.py 2017-05-12 21:17:36 +0000 +++ test/test_ui.py 2017-07-10 22:24:15 +0000 @@ -989,6 +989,27 @@ self.assertFalse(os.path.exists('/tmp/pwned')) self.assertIn('invalid Package:', self.ui.msg_text) + def test_run_crash_malicious_exec_path(self): + '''ExecutablePath: path traversal''' + + hook_dir = '/tmp/share/apport/package-hooks' + os.makedirs(hook_dir, exist_ok=True) + bad_hook = tempfile.NamedTemporaryFile(dir=hook_dir, suffix='.py') + bad_hook.write(b"def add_info(r, u):\n open('/tmp/pwned', 'w').close()") + bad_hook.flush() + + self.report['ExecutablePath'] = '/opt/../' + hook_dir + self.report['Package'] = os.path.splitext(bad_hook.name)[0].replace(hook_dir, '') + 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')) + def test_run_crash_ignore(self): '''run_crash() on a crash with the Ignore field''' self.report['Ignore'] = 'True'