diff -Nru sosreport-3.2/debian/changelog sosreport-3.2/debian/changelog --- sosreport-3.2/debian/changelog 2015-08-13 11:37:35.000000000 +0200 +++ sosreport-3.2/debian/changelog 2015-12-14 16:38:37.000000000 +0100 @@ -1,3 +1,10 @@ +sosreport (3.2-2ubuntu2) wily; urgency=medium + + * Fix CVE-2015-7529 - predictable tmp files usage + (LP: #1525271) + + -- Louis Bouchard Mon, 14 Dec 2015 15:12:11 +0100 + sosreport (3.2-2ubuntu1) wily; urgency=medium * Fix [Landscape] Move landscape logs from logs plugin to landscape plugin, diff -Nru sosreport-3.2/debian/patches/0005-CVE-2015-7529.patch sosreport-3.2/debian/patches/0005-CVE-2015-7529.patch --- sosreport-3.2/debian/patches/0005-CVE-2015-7529.patch 1970-01-01 01:00:00.000000000 +0100 +++ sosreport-3.2/debian/patches/0005-CVE-2015-7529.patch 2015-12-14 15:22:52.000000000 +0100 @@ -0,0 +1,326 @@ +Description: CVE-2015-7529 + Avoid predictable tmp files usage + Upstream commits : 4a9b919, 19e2bbc, 7f27277, 6038fdf +Author: "Bryn M. Reeves" +Origin: Upstream +Bug: https://github.com/sosreport/sos/issues/696 +--- +Index: sosreport-3.2/sos/policies/__init__.py +=================================================================== +--- sosreport-3.2.orig/sos/policies/__init__.py 2015-12-14 15:10:58.919021981 +0100 ++++ sosreport-3.2/sos/policies/__init__.py 2015-12-14 15:10:58.919021981 +0100 +@@ -10,11 +10,9 @@ + + from sos.utilities import (ImporterHelper, + import_module, +- get_hash_name, + shell_out) + from sos.plugins import IndependentPlugin + from sos import _sos as _ +-import hashlib + from textwrap import fill + from six import print_ + from six.moves import input +@@ -264,43 +262,29 @@ + considered to be a superuser""" + return (os.getuid() == 0) + +- def _create_checksum(self, final_filename=None): +- if not final_filename: +- return False +- +- archive_fp = open(final_filename, 'rb') +- digest = hashlib.new(get_hash_name()) +- digest.update(archive_fp.read()) +- archive_fp.close() +- return digest.hexdigest() +- +- def get_preferred_hash_algorithm(self): ++ def get_preferred_hash_name(self): + """Returns the string name of the hashlib-supported checksum algorithm + to use""" + return "md5" + +- def display_results(self, final_filename=None, build=False): ++ def display_results(self, archive, directory, checksum): ++ # Display results is called from the tail of SoSReport.final_work() ++ # ++ # Logging is already shutdown and all terminal output must use the ++ # print() call. + + # make sure a report exists +- if not final_filename: ++ if not archive and not directory: + return False + + self._print() + +- if not build: +- # store checksum into file +- fp = open(final_filename + "." + get_hash_name(), "w") +- checksum = self._create_checksum(final_filename) +- if checksum: +- fp.write(checksum + "\n") +- fp.close() +- ++ if archive: + self._print(_("Your sosreport has been generated and saved " +- "in:\n %s") % final_filename) ++ "in:\n %s") % archive) + else: +- checksum = None + self._print(_("sosreport build tree is located at : %s" % +- final_filename)) ++ directory)) + + self._print() + if checksum: +@@ -351,20 +335,28 @@ + vendor = "None" + PATH = "/bin:/sbin:/usr/bin:/usr/sbin" + ++ _preferred_hash_name = None ++ + def __init__(self): + super(LinuxPolicy, self).__init__() + +- def get_preferred_hash_algorithm(self): ++ def get_preferred_hash_name(self): ++ ++ if self._preferred_hash_name: ++ return self._preferred_hash_name ++ + checksum = "md5" + try: + fp = open("/proc/sys/crypto/fips_enabled", "r") + except: ++ self._preferred_hash_name = checksum + return checksum + + fips_enabled = fp.read() + if fips_enabled.find("1") >= 0: + checksum = "sha256" + fp.close() ++ self._preferred_hash_name = checksum + return checksum + + def default_runlevel(self): +Index: sosreport-3.2/sos/sosreport.py +=================================================================== +--- sosreport-3.2.orig/sos/sosreport.py 2015-12-14 15:10:58.919021981 +0100 ++++ sosreport-3.2/sos/sosreport.py 2015-12-14 15:22:50.592337858 +0100 +@@ -32,7 +32,9 @@ + from stat import ST_UID, ST_GID, ST_MODE, ST_CTIME, ST_ATIME, ST_MTIME, S_IMODE + from time import strftime, localtime + from collections import deque ++from shutil import rmtree + import tempfile ++import hashlib + + from sos import _sos as _ + from sos import __version__ +@@ -618,6 +620,7 @@ + self.archive = None + self.tempfile_util = None + self._args = args ++ self.sys_tmp = None + + try: + import signal +@@ -636,15 +639,22 @@ + + self._is_root = self.policy.is_root() + +- self.tmpdir = os.path.abspath( +- self.policy.get_tmp_dir(self.opts.tmp_dir)) +- if not os.path.isdir(self.tmpdir) \ +- or not os.access(self.tmpdir, os.W_OK): ++ # system temporary directory to use ++ tmp = os.path.abspath(self.policy.get_tmp_dir(self.opts.tmp_dir)) ++ ++ if not os.path.isdir(tmp) \ ++ or not os.access(tmp, os.W_OK): + # write directly to stderr as logging is not initialised yet +- sys.stderr.write("temporary directory %s " % self.tmpdir +- + "does not exist or is not writable\n") ++ sys.stderr.write("temporary directory %s " % tmp ++ + "does not exist or is not writable\n") + self._exit(1) ++ ++ self.sys_tmp = tmp ++ ++ # our (private) temporary directory ++ self.tmpdir = tempfile.mkdtemp(prefix="sos.", dir=self.sys_tmp) + self.tempfile_util = TempFileUtil(self.tmpdir) ++ + self._set_directories() + + def print_header(self): +@@ -1321,25 +1331,47 @@ + if self.raise_plugins: + raise + ++ def _create_checksum(self, archive, hash_name): ++ if not archive: ++ return False ++ ++ archive_fp = open(archive, 'rb') ++ digest = hashlib.new(hash_name) ++ digest.update(archive_fp.read()) ++ archive_fp.close() ++ return digest.hexdigest() ++ ++ def _write_checksum(self, archive, hash_name, checksum): ++ # store checksum into file ++ fp = open(archive + "." + hash_name, "w") ++ if checksum: ++ fp.write(checksum + "\n") ++ fp.close() ++ + def final_work(self): +- # this must come before archive creation to ensure that log ++ # This must come before archive creation to ensure that log + # files are closed and cleaned up at exit. ++ # ++ # All subsequent terminal output must use print(). + self._finish_logging() +- # package up the results for the support organization ++ ++ archive = None # archive path ++ directory = None # report directory path (--build) ++ ++ # package up and compress the results + if not self.opts.build: + old_umask = os.umask(0o077) + if not self.opts.quiet: + print(_("Creating compressed archive...")) + # compression could fail for a number of reasons + try: +- final_filename = self.archive.finalize( ++ archive = self.archive.finalize( + self.opts.compression_type) + except (OSError, IOError) as e: + if e.errno in fatal_fs_errors: +- self.ui_log.error("") +- self.ui_log.error(" %s while finalizing archive" +- % e.strerror) +- self.ui_log.error("") ++ print("") ++ print(_(" %s while finalizing archive" % e.strerror)) ++ print("") + self._exit(1) + except: + if self.opts.debug: +@@ -1349,9 +1381,64 @@ + finally: + os.umask(old_umask) + else: +- final_filename = self.archive.get_archive_path() +- self.policy.display_results(final_filename, build=self.opts.build) +- self.tempfile_util.clean() ++ # move the archive root out of the private tmp directory. ++ directory = self.archive.get_archive_path() ++ dir_name = os.path.basename(directory) ++ try: ++ os.rename(directory, os.path.join(self.sys_tmp, dir_name)) ++ except (OSError, IOError): ++ print(_("Error moving directory: %s" % directory)) ++ return False ++ ++ checksum = None ++ ++ if not self.opts.build: ++ # compute and store the archive checksum ++ hash_name = self.policy.get_preferred_hash_name() ++ checksum = self._create_checksum(archive, hash_name) ++ self._write_checksum(archive, hash_name, checksum) ++ ++ # output filename is in the private tmpdir - move it to the ++ # containing directory. ++ final_name = os.path.join(self.sys_tmp, os.path.basename(archive)) ++ ++ archive_hash = archive + "." + hash_name ++ final_hash = final_name + "." + hash_name ++ ++ # move the archive and checksum file ++ try: ++ os.rename(archive, final_name) ++ archive = final_name ++ except (OSError, IOError): ++ print(_("Error moving archive file: %s" % archive)) ++ return False ++ ++ # There is a race in the creation of the final checksum file: ++ # since the archive has already been published and the checksum ++ # file name is predictable once the archive name is known a ++ # malicious user could attempt to create a symbolic link in order ++ # to misdirect writes to a file of the attacker's choosing. ++ # ++ # To mitigate this we write the checksum inside the private tmp ++ # directory and use an atomic rename that is guaranteed to either ++ # succeed or fail: at worst the move will fail and be reported to ++ # the user. The correct checksum value is still written to the ++ # terminal and nothing is written to a location under the control ++ # of the user creating the link. ++ try: ++ os.rename(archive_hash, final_hash) ++ except (OSError, IOError): ++ print(_("Error moving checksum file: %s" % archive_hash)) ++ return False ++ ++ self.policy.display_results(archive, directory, checksum) ++ ++ # clean up ++ if self.tempfile_util: ++ self.tempfile_util.clean() ++ if self.tmpdir: ++ rmtree(self.tmpdir) ++ + return True + + def verify_plugins(self): +@@ -1402,6 +1489,8 @@ + self.archive.cleanup() + if self.tempfile_util: + self.tempfile_util.clean() ++ if self.tmpdir: ++ rmtree(self.tmpdir) + return False + + +Index: sosreport-3.2/sos/utilities.py +=================================================================== +--- sosreport-3.2.orig/sos/utilities.py 2015-12-14 15:10:58.919021981 +0100 ++++ sosreport-3.2/sos/utilities.py 2015-12-14 15:10:58.919021981 +0100 +@@ -53,18 +53,6 @@ + return closing(path_or_file) + + +-def get_hash_name(): +- """Returns the algorithm used when computing a hash""" +- import sos.policies +- policy = sos.policies.load() +- try: +- name = policy.get_preferred_hash_algorithm() +- hashlib.new(name) +- return name +- except: +- return 'sha256' +- +- + def convert_bytes(bytes_, K=1 << 10, M=1 << 20, G=1 << 30, T=1 << 40): + """Converts a number of bytes to a shorter, more human friendly format""" + fn = float(bytes_) +Index: sosreport-3.2/tests/utilities_tests.py +=================================================================== +--- sosreport-3.2.orig/tests/utilities_tests.py 2015-12-14 15:10:58.919021981 +0100 ++++ sosreport-3.2/tests/utilities_tests.py 2015-12-14 15:10:58.919021981 +0100 +@@ -5,7 +5,7 @@ + import six + from six import StringIO + +-from sos.utilities import grep, get_hash_name, is_executable, sos_get_command_output, find, tail, shell_out ++from sos.utilities import grep, is_executable, sos_get_command_output, find, tail, shell_out + import sos + + TEST_DIR = os.path.dirname(__file__) diff -Nru sosreport-3.2/debian/patches/series sosreport-3.2/debian/patches/series --- sosreport-3.2/debian/patches/series 2015-08-13 11:28:15.000000000 +0200 +++ sosreport-3.2/debian/patches/series 2015-12-14 14:48:37.000000000 +0100 @@ -2,3 +2,4 @@ 0002-sosreport-fix-archive-permissions-regression.patch 0003-move-landscape-log-collection-from-logs-to-landscape-plugin.patch 0004-landscape-15.01-move-logs-file-to-landscape-server.patch +0005-CVE-2015-7529.patch