diff -u apport-2.20.1/apport/report.py apport-2.20.1/apport/report.py --- apport-2.20.1/apport/report.py +++ apport-2.20.1/apport/report.py @@ -848,8 +848,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: diff -u apport-2.20.1/data/general-hooks/ubuntu-gnome.py apport-2.20.1/data/general-hooks/ubuntu-gnome.py --- apport-2.20.1/data/general-hooks/ubuntu-gnome.py +++ apport-2.20.1/data/general-hooks/ubuntu-gnome.py @@ -12,6 +12,9 @@ def add_info(report, ui): + release = report.get('DistroRelease', '') + + msg = 'The GNOME3 PPA you are using is no longer supported for this Ubuntu release. Please ' # redirect reports against PPA packages to ubuntu-gnome project if '[origin: LP-PPA-gnome3-team-gnome3' in report.get('Package', ''): report['CrashDB'] = '''{ @@ -25,12 +28,22 @@ - if 'LP-PPA-gnome3-team-gnome3-staging' in report['Package']: + if 'LP-PPA-gnome3-team-gnome3-staging' in report.get('Package', ''): report.setdefault('Tags', '') report['Tags'] += ' gnome3-staging' + if release in ('Ubuntu 14.04', 'Ubuntu 16.04'): + report['UnreportableReason'] = '%s run "ppa-purge ppa:gnome3-team/gnome3-staging".' % msg # using the next PPA? - if 'LP-PPA-gnome3-team-gnome3-next' in report['Package']: + elif 'LP-PPA-gnome3-team-gnome3-next' in report.get('Package', ''): report.setdefault('Tags', '') report['Tags'] += ' gnome3-next' + if release in ('Ubuntu 14.04', 'Ubuntu 16.04'): + report['UnreportableReason'] = '%s run "ppa-purge ppa:gnome3-team/gnome3-next".' % msg + + else: + if release in ('Ubuntu 14.04', 'Ubuntu 16.04'): + report['UnreportableReason'] = '%s run "ppa-purge ppa:gnome3-team/gnome3".' % msg if '[origin: LP-PPA-gnome3-team-gnome3' in report.get('Dependencies', ''): report.setdefault('Tags', '') report['Tags'] += ' gnome3-ppa' + if release in ('Ubuntu 14.04', 'Ubuntu 16.04') and 'UnreportableReason' not in report: + report['UnreportableReason'] = '%s use ppa-purge to remove the PPA.' % msg diff -u apport-2.20.1/data/general-hooks/ubuntu.py apport-2.20.1/data/general-hooks/ubuntu.py --- apport-2.20.1/data/general-hooks/ubuntu.py +++ apport-2.20.1/data/general-hooks/ubuntu.py @@ -37,6 +37,18 @@ add_proposed_info(report) + # collect a condensed version of /proc/cpuinfo + apport.hookutils.attach_file(report, '/proc/cpuinfo', + 'ProcCpuinfo') + short_cpuinfo = [] + for item in reversed(report.get('ProcCpuinfo', '').split('\n')): + short_cpuinfo.append(item) + if item.startswith('processor\t:'): + break + short_cpuinfo = reversed(short_cpuinfo) + report['ProcCpuinfoMinimal'] = '\n'.join(short_cpuinfo) + report.pop('ProcCpuinfo') + try: report['ApportVersion'] = apport.packaging.get_version('apport') except ValueError: @@ -126,13 +138,26 @@ if report['SourcePackage'] in UPDATE_BOOT: apport.hookutils.attach_default_grub(report, 'EtcDefaultGrub') dupe_sig = '' - PKG_MSGS = ('Authenticating', 'De-configuring', 'Examining', - 'Installing ', 'Preparing', 'Processing triggers', 'Purging', - 'Removing', 'Replaced', 'Replacing', 'Setting up', - 'Unpacking', 'Would remove') + dupe_sig_created = False + # messages we expect to see from a package manager (LP: #1692127) + pkg_mngr_msgs = re.compile(r"""^(Authenticating| + De-configuring| + Examining| + Installing| + Preparing| + Processing\ triggers| + Purging| + Removing| + Replaced| + Replacing| + Setting\ up| + Unpacking| + Would remove).* + \.\.\.\s*$""", re.X) for line in termlog.split('\n'): - if line.startswith(PKG_MSGS): + if pkg_mngr_msgs.search(line): dupe_sig = '%s\n' % line + dupe_sig_created = True continue dupe_sig += '%s\n' % line if 'dpkg: error' in dupe_sig and line.startswith(' '): @@ -141,7 +166,7 @@ if conflict_pkg and not apport.packaging.is_distro_package(conflict_pkg.group(1)): report['UnreportableReason'] = _('An Ubuntu package has a file conflict with a package that is not a genuine Ubuntu package.') add_tag(report, 'package-conflict') - if [d for d in PKG_MSGS if d in dupe_sig]: + if dupe_sig_created: # the duplicate signature should be the first failure report['DuplicateSignature'] = 'package:%s:%s\n%s' % (package, version, dupe_sig) break diff -u apport-2.20.1/debian/apport.install apport-2.20.1/debian/apport.install --- apport-2.20.1/debian/apport.install +++ apport-2.20.1/debian/apport.install @@ -19,7 +19,6 @@ usr/share/doc/apport usr/share/locale usr/share/icons -usr/share/mime usr/share/polkit-1 usr/share/apport/package-hooks usr/share/apport/general-hooks diff -u apport-2.20.1/debian/changelog apport-2.20.1/debian/changelog --- apport-2.20.1/debian/changelog +++ apport-2.20.1/debian/changelog @@ -1,3 +1,56 @@ +apport (2.20.1-0ubuntu2.10) xenial-security; urgency=medium + + * SECURITY UPDATE: code execution through path traversial in + .crash files (LP: #1700573) + - apport/report.py, test/test_ui.py: fix traversal issue + and add a test for that. + - debian/apport.install, setup.py, xdg-mime/apport.xml: removes + apport as a file handler for .crash files. Thanks to Brian + Murray for the patch and Felix Wilhelm for discovering this. + - CVE-2017-10708 + + -- Leonidas S. Barbosa Mon, 17 Jul 2017 08:43:18 -0300 + +apport (2.20.1-0ubuntu2.9) xenial; urgency=medium + + * test/test_signal_crashes.py: delete the test which uses an arbitrary + unpredictable core file size. + + -- Brian Murray Thu, 29 Jun 2017 13:33:50 -0700 + +apport (2.20.1-0ubuntu2.8) xenial; urgency=medium + + * test/test_signal_crashes.py: a ulimit of 1M bytes isn't enough to produce + a core file anymore so bump it to 10M. + + -- Brian Murray Mon, 26 Jun 2017 14:58:24 -0700 + +apport (2.20.1-0ubuntu2.7) xenial; urgency=medium + + * data/general-hooks/ubuntu.py: Modify how a duplicate signature is created + for package installation failures. (LP: #1692127) + + -- Brian Murray Mon, 19 Jun 2017 17:01:15 -0700 + +apport (2.20.1-0ubuntu2.6) xenial; urgency=medium + + * data/general/ubuntu.py: Collect a minimal version of /proc/cpuinfo in + every report. (LP: #1673557) + * data/general/ubuntu-gnome.py: The GNOME3 PPAs are no longer supported for + 14.04 or 16.04 so set an UnreportableReason in those reports. + (LP: #1689093) + * test_backend_apt_dpkg.py: Move tests from Ubuntu 15.10 "wily" (which is + EoL now) to 16.04 LTS "xenial". (LP: #1690437) + + -- Brian Murray Fri, 12 May 2017 11:39:04 -0700 + +apport (2.20.1-0ubuntu2.5) xenial; urgency=medium + + * apport-gtk: Specify module version with GI imports to avoid warnings. + Thanks Anatoly Techtonik. (LP: #1502173) + + -- Brian Murray Tue, 03 Jan 2017 15:31:33 -0800 + apport (2.20.1-0ubuntu2.4) xenial-security; urgency=medium [ Marc Deslauriers ] diff -u apport-2.20.1/gtk/apport-gtk apport-2.20.1/gtk/apport-gtk --- apport-2.20.1/gtk/apport-gtk +++ apport-2.20.1/gtk/apport-gtk @@ -13,6 +13,9 @@ import os.path, sys, subprocess, os, re +import gi +gi.require_version('Wnck', '3.0') +gi.require_version('GdkX11', '3.0') from gi.repository import GLib, Wnck, GdkX11, Gdk Gdk # pyflakes; needed for GdkX11 try: diff -u apport-2.20.1/test/test_backend_apt_dpkg.py apport-2.20.1/test/test_backend_apt_dpkg.py --- apport-2.20.1/test/test_backend_apt_dpkg.py +++ apport-2.20.1/test/test_backend_apt_dpkg.py @@ -488,9 +488,9 @@ def test_install_packages_versioned(self): '''install_packages() with versions and with cache''' - self._setup_foonux_config(release='wily', updates=True) - v_coreutils = '8.23-4ubuntu2' - v_libc = '2.21-0ubuntu4' + self._setup_foonux_config(release='xenial', updates=True) + v_coreutils = '8.25-2ubuntu2' + v_libc = '2.23-0ubuntu3' obsolete = impl.install_packages(self.rootdir, self.configdir, 'Foonux 1.2', [('coreutils', v_coreutils), # should not come from updates @@ -511,7 +511,7 @@ self.assert_elf_arch(os.path.join(self.rootdir, 'usr/bin/stat'), impl.get_system_architecture()) self.assertTrue(os.path.exists(os.path.join(self.rootdir, - 'usr/lib/debug/usr/bin/stat'))) + 'usr/lib/debug/.build-id'))) self.assertTrue(os.path.exists(os.path.join(self.rootdir, 'usr/share/zoneinfo/zone.tab'))) self.assertTrue(os.path.exists(os.path.join(self.rootdir, @@ -521,7 +521,7 @@ self.assertEqual(sandbox_ver('coreutils'), v_coreutils) self.assertEqual(sandbox_ver('libc6'), v_libc) self.assertEqual(sandbox_ver('libc6-dbg'), v_libc) - self.assertGreater(sandbox_ver('tzdata'), '2015') + self.assertGreater(sandbox_ver('tzdata'), '2016') with open(os.path.join(self.rootdir, 'packages.txt')) as f: pkglist = f.read().splitlines() @@ -568,14 +568,12 @@ # complains about obsolete packages result = impl.install_packages(self.rootdir, self.configdir, - 'Foonux 1.2', [('gnome-common', '1.1')]) - self.assertEqual(len(result.splitlines()), 1) - self.assertTrue('gnome-common' in result) - self.assertTrue('1.1' in result) + 'Foonux 1.2', [('aspell-doc', '1.1')]) + self.assertIn(result, 'aspell-doc version 1.1 required, but 0.60.7~20110707-3build1 is available\n') # ... but installs the current version anyway self.assertTrue(os.path.exists( - os.path.join(self.rootdir, 'usr/bin/gnome-autogen.sh'))) - self.assertGreaterEqual(sandbox_ver('gnome-common'), '3.1.0-0ubuntu1') + os.path.join(self.rootdir, 'usr/share/info/aspell.info.gz'))) + self.assertGreaterEqual(sandbox_ver('aspell-doc'), '0.60.7~2011') # does not crash on nonexisting packages result = impl.install_packages(self.rootdir, self.configdir, @@ -609,7 +607,7 @@ def test_install_packages_unversioned(self): '''install_packages() without versions and no cache''' - self._setup_foonux_config(release='wily') + self._setup_foonux_config(release='xenial') obsolete = impl.install_packages(self.rootdir, self.configdir, 'Foonux 1.2', [('coreutils', None), @@ -622,7 +620,7 @@ self.assert_elf_arch(os.path.join(self.rootdir, 'usr/bin/stat'), impl.get_system_architecture()) self.assertTrue(os.path.exists(os.path.join(self.rootdir, - 'usr/lib/debug/usr/bin/stat'))) + 'usr/lib/debug/.build-id'))) self.assertTrue(os.path.exists(os.path.join(self.rootdir, 'usr/share/zoneinfo/zone.tab'))) @@ -639,9 +637,9 @@ # keeps track of package versions with open(os.path.join(self.rootdir, 'packages.txt')) as f: pkglist = f.read().splitlines() - self.assertIn('coreutils 8.23-4ubuntu2', pkglist) - self.assertIn('coreutils-dbgsym 8.23-4ubuntu2', pkglist) - self.assertIn('tzdata 2015g-1', pkglist) + self.assertIn('coreutils 8.25-2ubuntu2', pkglist) + self.assertIn('coreutils-dbgsym 8.25-2ubuntu2', pkglist) + self.assertIn('tzdata 2016d-0ubuntu0.16.04', pkglist) self.assertEqual(len(pkglist), 3, str(pkglist)) @unittest.skipUnless(_has_internet(), 'online test') @@ -815,14 +813,14 @@ def test_install_packages_armhf(self): '''install_packages() for foreign architecture armhf''' - self._setup_foonux_config(release='wily') + self._setup_foonux_config(release='xenial') obsolete = impl.install_packages(self.rootdir, self.configdir, 'Foonux 1.2', [('coreutils', None), - ('libc6', '2.21-0ubuntu0'), + ('libc6', '2.23-0ubuntu0'), ], False, self.cachedir, architecture='armhf') - self.assertEqual(obsolete, 'libc6 version 2.21-0ubuntu0 required, but 2.21-0ubuntu4 is available\n') + self.assertEqual(obsolete, 'libc6 version 2.23-0ubuntu0 required, but 2.23-0ubuntu3 is available\n') self.assertTrue(os.path.exists(os.path.join(self.rootdir, 'usr/bin/stat'))) @@ -833,8 +831,8 @@ # caches packages cache = os.listdir(os.path.join(self.cachedir, 'Foonux 1.2', 'apt', 'var', 'cache', 'apt', 'archives')) - self.assertTrue('coreutils_8.23-4ubuntu2_armhf.deb' in cache, cache) - self.assertTrue('libc6_2.21-0ubuntu4_armhf.deb' in cache, cache) + self.assertTrue('coreutils_8.25-2ubuntu2_armhf.deb' in cache, cache) + self.assertTrue('libc6_2.23-0ubuntu3_armhf.deb' in cache, cache) @unittest.skipUnless(_has_internet(), 'online test') def test_install_packages_from_launchpad(self): diff -u apport-2.20.1/test/test_ui.py apport-2.20.1/test/test_ui.py --- apport-2.20.1/test/test_ui.py +++ apport-2.20.1/test/test_ui.py @@ -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' only in patch2: unchanged: --- apport-2.20.1.orig/setup.py +++ apport-2.20.1/setup.py @@ -129,10 +129,9 @@ description='intercept, process, and report crashes and bug reports', version=__version__, - data_files=[('share/mime/packages', glob('xdg-mime/*')), + data_files=[('share/doc/apport/', glob('doc/*.txt')), # these are not supposed to be called directly, use apport-bug instead ('share/apport', ['gtk/apport-gtk', 'kde/apport-kde']), - ('share/doc/apport/', glob('doc/*.txt')), ('lib/pm-utils/sleep.d/', glob('pm-utils/sleep.d/*')), ('/lib/udev/rules.d', glob('udev/*.rules')), (systemd_unit_dir, glob('data/systemd/*')), only in patch2: unchanged: --- apport-2.20.1.orig/test/test_report.py +++ apport-2.20.1/test/test_report.py @@ -818,7 +818,7 @@ self._validate_gdb_fields(pr) self.assertFalse('AssertionMessage' in pr, pr.get('AssertionMessage')) - def test_add_gdb_info_abort_glib(self): + def disabled_test_add_gdb_info_abort_glib(self): '''add_gdb_info() with glib assertion''' (fd, script) = tempfile.mkstemp() assert not os.path.exists('core') only in patch2: unchanged: --- apport-2.20.1.orig/test/test_signal_crashes.py +++ apport-2.20.1/test/test_signal_crashes.py @@ -18,7 +18,6 @@ # (core ulimit (bytes), expect core signal, expect core file, expect report) core_ulimit_table = [(1, False, False, False), (1000, True, False, True), - (1000000, True, True, True), (-1, True, True, True)] required_fields = ['ProblemType', 'CoreDump', 'Date', 'ExecutablePath',