diff -Nru sosreport-3.1/debian/changelog sosreport-3.1/debian/changelog --- sosreport-3.1/debian/changelog 2014-10-13 14:51:03.000000000 +0200 +++ sosreport-3.1/debian/changelog 2015-12-14 16:40:03.000000000 +0100 @@ -1,3 +1,10 @@ +sosreport (3.1-1ubuntu2.2) trusty; urgency=medium + + * Fix CVE-2015-7529 - predictable tmp files usage + (LP: #1525271) + + -- Louis Bouchard Mon, 14 Dec 2015 16:22:41 +0100 + sosreport (3.1-1ubuntu2.1) trusty-proposed; urgency=medium * debian/patches/add-rabbitmq-plugin.patch diff -Nru sosreport-3.1/debian/patches/0010-CVE-2015-7529.patch sosreport-3.1/debian/patches/0010-CVE-2015-7529.patch --- sosreport-3.1/debian/patches/0010-CVE-2015-7529.patch 1970-01-01 01:00:00.000000000 +0100 +++ sosreport-3.1/debian/patches/0010-CVE-2015-7529.patch 2015-12-14 16:15:44.000000000 +0100 @@ -0,0 +1,266 @@ +Index: sosreport-3.1/sos/policies/__init__.py +=================================================================== +--- sosreport-3.1.orig/sos/policies/__init__.py 2015-12-14 14:46:46.000000000 +0100 ++++ sosreport-3.1/sos/policies/__init__.py 2015-12-14 15:52:35.345462524 +0100 +@@ -11,11 +11,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 +@@ -244,41 +242,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() +- +- self._print(_("Your sosreport has been generated and saved in:\n %s") % final_filename) ++ if archive: ++ self._print(_("Your sosreport has been generated and saved " ++ "in:\n %s") % archive) + else: +- checksum = None +- self._print(_("sosreport build tree is located at : %s" % final_filename)) ++ self._print(_("sosreport build tree is located at : %s" % ++ directory)) + + self._print() + if checksum: +@@ -330,20 +316,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.1/sos/sosreport.py +=================================================================== +--- sosreport-3.1.orig/sos/sosreport.py 2015-12-14 14:46:46.000000000 +0100 ++++ sosreport-3.1/sos/sosreport.py 2015-12-14 16:15:12.317101634 +0100 +@@ -42,7 +42,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 textwrap ++import hashlib + import tempfile + + from sos import _sos as _ +@@ -528,6 +530,7 @@ + self.global_plugin_options = {} + self.archive = None + self.tempfile_util = None ++ self.sys_tmp = None + + try: + import signal +@@ -542,14 +545,20 @@ + self._read_config() + self.policy = sos.policies.load() + 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 \ ++ 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() + +@@ -1103,16 +1112,38 @@ + 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: + print (_("Creating compressed archive...")) + # compression could fail for a number of reasons + try: +- final_filename = self.archive.finalize(self.opts.compression_type) ++ archive = self.archive.finalize(self.opts.compression_type) + except: + if self.opts.debug: + raise +@@ -1120,10 +1151,63 @@ + return False + + else: +- final_filename = self.archive.get_archive_path() ++ # 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) + +- self.policy.display_results(final_filename, build = self.opts.build) +- self.tempfile_util.clean() ++ # clean up ++ if self.tempfile_util: ++ self.tempfile_util.clean() ++ if self.tmpdir: ++ rmtree(self.tmpdir) + + return True + +@@ -1175,6 +1259,8 @@ + self.archive.cleanup() + if self.tempfile_util: + self.tempfile_util.clean() ++ if self.tmpdir: ++ rmtree(self.tmpdir) + return False + + def main(args): diff -Nru sosreport-3.1/debian/patches/series sosreport-3.1/debian/patches/series --- sosreport-3.1/debian/patches/series 2014-09-24 09:48:32.000000000 +0200 +++ sosreport-3.1/debian/patches/series 2015-12-14 15:44:46.000000000 +0100 @@ -7,3 +7,4 @@ 0007-Collect-upstart-logs.patch 0008-Collect-squid-deb-proxy-info.patch 0009-fix-ceilometer-stack.patch +0010-CVE-2015-7529.patch