diff -Nru nova-2014.1.5/debian/changelog nova-2014.1.5/debian/changelog --- nova-2014.1.5/debian/changelog 2016-09-09 09:41:48.000000000 +0000 +++ nova-2014.1.5/debian/changelog 2017-09-13 18:45:42.000000000 +0000 @@ -1,3 +1,76 @@ +nova (1:2014.1.5-0ubuntu1.7) trusty-security; urgency=medium + + * SECURITY UPDATE: DoS via instance deletion during migration + - debian/patches/CVE-2015-3241-1.patch: check for resize path on + libvirt instance delete in nova/tests/virt/libvirt/test_libvirt.py, + nova/virt/libvirt/driver.py. + - debian/patches/CVE-2015-3241-1.patch: sync process utils from oslo in + nova/openstack/common/processutils.py. + - debian/patches/CVE-2015-3241-1.patch: kill rsync/scp processes before + deleting instance in nova/tests/virt/libvirt/test_libvirt.py, + nova/tests/virt/libvirt/test_libvirt_utils.py, + nova/virt/libvirt/driver.py, nova/virt/libvirt/instancejobtracker.py, + nova/virt/libvirt/utils.py. + - CVE-2015-3241 + * SECURITY UPDATE: DoS via instance deletion during resize + - debian/patches/CVE-2015-3280.patch: delete orphaned instance files + from compute nodes in nova/compute/manager.py, + nova/tests/compute/test_compute_mgr.py. + - CVE-2015-3280 + * SECURITY UPDATE: DoS via crafted disk image + - debian/patches/CVE-2015-5162-1.patch: add prlimit parameter to + execute() in nova/openstack/common/prlimit.py, + nova/openstack/common/processutils.py, + nova/tests/openstack_common/test_processutils.py. + - debian/patches/CVE-2015-5162-2.patch: add support for missing process + limits in nova/openstack/common/prlimit.py, + nova/openstack/common/processutils.py, + nova/tests/openstack_common/test_processutils.py. + - debian/patches/CVE-2015-5162-3.patch: set address space & CPU time + limits when running qemu-img in nova/virt/images.py, + nova/tests/virt/libvirt/test_libvirt.py, + nova/tests/virt/libvirt/test_image_utils.py, + nova/tests/virt/libvirt/test_libvirt_utils.py. + - CVE-2015-5162 + * SECURITY UPDATE: arbitrary file read via snapshot + - debian/patches/CVE-2015-7548-1.patch: fix format detection in libvirt + snapshot in nova/tests/virt/libvirt/fake_libvirt_utils.py, + nova/tests/virt/libvirt/test_image_utils.py, + nova/tests/virt/libvirt/test_libvirt_utils.py, + nova/virt/libvirt/driver.py, nova/virt/libvirt/utils.py. + - debian/patches/CVE-2015-7548-2.patch: fix format conversion in + libvirt snapshot in nova/tests/virt/libvirt/test_libvirt.py, + nova/virt/images.py, nova/virt/libvirt/imagebackend.py. + - debian/patches/CVE-2015-7548-3.patch: fix backing file detection in + libvirt live snapshot in nova/tests/virt/libvirt/test_libvirt.py, + nova/tests/virt/libvirt/fake_libvirt_utils.py, nova/virt/images.py, + nova/virt/libvirt/driver.py, nova/virt/libvirt/utils.py. + - debian/patches/CVE-2015-7548-4.patch: disable live snapshot for + rbd-backed instances in nova/virt/libvirt/driver.py. + - CVE-2015-7548 + * SECURITY UPDATE: restriction bypass via security group changes + - debian/patches/CVE-2015-7713.patch: don't expect meta attributes in + object_compat that aren't in the db obj in nova/compute/manager.py, + nova/tests/compute/test_compute.py. + - CVE-2015-7713 + * SECURITY UPDATE: password disclosure via xen log files + - debian/patches/CVE-2015-8749.patch: mask passwords in volume + connection_data dict in nova/virt/xenapi/volume_utils.py. + - CVE-2015-8749 + * SECURITY UPDATE: arbitrary file read via crafted qcow2 header + - debian/patches/CVE-2016-2140-1.patch: always copy or recreate + disk.info during a migration in nova/virt/libvirt/driver.py, + nova/tests/virt/libvirt/test_libvirt.py. + - debian/patches/CVE-2016-2140-2.patch: fix processing of libvirt + disk.info in non-disk-image cases in nova/virt/libvirt/driver.py, + nova/tests/virt/libvirt/test_libvirt.py. + - debian/patches/CVE-2016-2140-3.patch: decode disk_info before use in + nova/tests/virt/libvirt/test_libvirt.py, nova/virt/libvirt/driver.py. + - CVE-2016-2140 + * Thanks to Red Hat for the backports many of these patches are based on. + + -- Marc Deslauriers Wed, 13 Sep 2017 14:30:17 -0400 + nova (1:2014.1.5-0ubuntu1.6) trusty; urgency=medium * Allow evacuate for an instance in the Error state (LP: #1298061) diff -Nru nova-2014.1.5/debian/patches/CVE-2015-3241-1.patch nova-2014.1.5/debian/patches/CVE-2015-3241-1.patch --- nova-2014.1.5/debian/patches/CVE-2015-3241-1.patch 1970-01-01 00:00:00.000000000 +0000 +++ nova-2014.1.5/debian/patches/CVE-2015-3241-1.patch 2017-09-12 15:48:00.000000000 +0000 @@ -0,0 +1,335 @@ +From 8232a7c6d58fe24e74259557986b5af9655bfd31 Mon Sep 17 00:00:00 2001 +From: John Warren +Date: Wed, 11 Jun 2014 20:29:28 +0000 +Subject: [PATCH] Check for resize path on libvirt instance delete + +If an instance is deleted after the instance's disk image path has +been renamed by adding the "_resize" suffix to it but before the +resize operation completes, the libvirt driver will not delete the +orphaned files and manual intervention is needed to get them deleted. + +This fix addresses the issue by attempting to rename the instance path +by adding a "_del" suffix and if that fails, renaming the instance path +with the "_resize" suffix by replacing the "_resize" suffix with the +"_del" suffix. If both renaming operations fail, the sequence is +repeated, in case the the disk image path initially had the "_resize" +suffix and another thread removed it before the second rename operation +was attempted. These rename operations are used in favor of checking +for the existence of paths and deleting if found, because rename +operations are atomic whereas another thread could rename the path +between the exist check and the deleting. + +Regardless of the outcome of the renaming operations, the existence of +the instance path with the "_del" suffix is verified and if it exists, +it is deleted. This is done in case a prior delete operation that +managed to create the "_del" path was subsequently interrupted before +all instance files could be deleted. + +Note that the LibvirtConnTestCase.test_delete_instance_files test case +was removed in order to eliminate redundancy. + +Closes-Bug: #1308565 + +(cherry picked from commit 98e6891dfd4408c56644f55fe3cff88703beb4bf) + +Upstream-Juno: https://review.openstack.org/#/c/99472/ +Related: rhbz 1257789 +Related: CVE-2015-3241 + +Change-Id: Ifcb2e18211347ccf3e5472779c5917a729a6eced +Reviewed-on: https://code.engineering.redhat.com/gerrit/57492 +Tested-by: RHOS Jenkins +Reviewed-by: Padraig Brady +--- + nova/tests/virt/libvirt/test_libvirt.py | 192 +++++++++++++++++++++++++------- + nova/virt/libvirt/driver.py | 50 +++++++-- + 2 files changed, 194 insertions(+), 48 deletions(-) + +Index: nova-2014.1.5/nova/tests/virt/libvirt/test_libvirt.py +=================================================================== +--- nova-2014.1.5.orig/nova/tests/virt/libvirt/test_libvirt.py 2017-09-12 11:47:57.420575903 -0400 ++++ nova-2014.1.5/nova/tests/virt/libvirt/test_libvirt.py 2017-09-12 11:47:57.412575798 -0400 +@@ -5246,9 +5246,10 @@ class LibvirtConnTestCase(test.TestCase) + else: + libvirt_driver.LibvirtDriver.volume_driver_method( + mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg()) +- self.mox.StubOutWithMock(shutil, "rmtree") +- shutil.rmtree(os.path.join(CONF.instances_path, +- 'instance-%08x' % int(instance['id']))) ++ self.mox.StubOutWithMock(libvirt_driver.LibvirtDriver, ++ 'delete_instance_files') ++ (libvirt_driver.LibvirtDriver.delete_instance_files(mox.IgnoreArg()). ++ AndReturn(True)) + self.mox.StubOutWithMock(libvirt_driver.LibvirtDriver, '_cleanup_lvm') + libvirt_driver.LibvirtDriver._cleanup_lvm(instance) + +@@ -5327,44 +5328,6 @@ class LibvirtConnTestCase(test.TestCase) + self.stubs.Set(os.path, 'exists', fake_os_path_exists) + conn.destroy(self.context, instance, [], None, False) + +- def test_delete_instance_files(self): +- instance = {"name": "instancename", "id": "42", +- "uuid": "875a8070-d0b9-4949-8b31-104d125c9a64", +- "cleaned": 0, 'info_cache': None, 'security_groups': []} +- +- self.mox.StubOutWithMock(db, 'instance_get_by_uuid') +- self.mox.StubOutWithMock(os.path, 'exists') +- self.mox.StubOutWithMock(shutil, "rmtree") +- +- db.instance_get_by_uuid(mox.IgnoreArg(), mox.IgnoreArg(), +- columns_to_join=['info_cache', +- 'security_groups'], +- use_slave=False +- ).AndReturn(instance) +- os.path.exists(mox.IgnoreArg()).AndReturn(False) +- os.path.exists(mox.IgnoreArg()).AndReturn(True) +- shutil.rmtree(os.path.join(CONF.instances_path, instance['uuid'])) +- os.path.exists(mox.IgnoreArg()).AndReturn(True) +- os.path.exists(mox.IgnoreArg()).AndReturn(False) +- os.path.exists(mox.IgnoreArg()).AndReturn(True) +- shutil.rmtree(os.path.join(CONF.instances_path, instance['uuid'])) +- os.path.exists(mox.IgnoreArg()).AndReturn(False) +- self.mox.ReplayAll() +- +- def fake_obj_load_attr(self, attrname): +- if not hasattr(self, attrname): +- self[attrname] = {} +- +- conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) +- self.stubs.Set(instance_obj.Instance, 'fields', +- {'id': int, 'uuid': str, 'cleaned': int}) +- self.stubs.Set(instance_obj.Instance, 'obj_load_attr', +- fake_obj_load_attr) +- +- inst_obj = instance_obj.Instance.get_by_uuid(None, instance['uuid']) +- self.assertFalse(conn.delete_instance_files(inst_obj)) +- self.assertTrue(conn.delete_instance_files(inst_obj)) +- + def test_reboot_different_ids(self): + class FakeLoopingCall: + def start(self, *a, **k): +@@ -9310,6 +9273,153 @@ class LibvirtDriverTestCase(test.TestCas + instance = self._create_instance() + self.assertTrue(conn.instance_on_disk(instance)) + ++ @mock.patch('shutil.rmtree') ++ @mock.patch('nova.utils.execute') ++ @mock.patch('os.path.exists') ++ @mock.patch('nova.virt.libvirt.utils.get_instance_path') ++ def test_delete_instance_files(self, get_instance_path, exists, exe, ++ shutil): ++ lv = self.libvirtconnection ++ get_instance_path.return_value = '/path' ++ params = dict(uuid='fake-uuid', id=1) ++ instance = self._create_instance(params) ++ ++ exists.side_effect = [False, False, True, False] ++ ++ result = lv.delete_instance_files(instance) ++ get_instance_path.assert_called_with(instance) ++ exe.assert_called_with('mv', '/path', '/path_del') ++ shutil.assert_called_with('/path_del') ++ self.assertTrue(result) ++ ++ @mock.patch('shutil.rmtree') ++ @mock.patch('nova.utils.execute') ++ @mock.patch('os.path.exists') ++ @mock.patch('nova.virt.libvirt.utils.get_instance_path') ++ def test_delete_instance_files_resize(self, get_instance_path, exists, ++ exe, shutil): ++ lv = self.libvirtconnection ++ get_instance_path.return_value = '/path' ++ params = dict(uuid='fake-uuid', id=1) ++ instance = self._create_instance(params) ++ ++ nova.utils.execute.side_effect = [Exception(), None] ++ exists.side_effect = [False, False, True, False] ++ ++ result = lv.delete_instance_files(instance) ++ get_instance_path.assert_called_with(instance) ++ expected = [mock.call('mv', '/path', '/path_del'), ++ mock.call('mv', '/path_resize', '/path_del')] ++ self.assertEqual(expected, exe.mock_calls) ++ shutil.assert_called_with('/path_del') ++ self.assertTrue(result) ++ ++ @mock.patch('shutil.rmtree') ++ @mock.patch('nova.utils.execute') ++ @mock.patch('os.path.exists') ++ @mock.patch('nova.virt.libvirt.utils.get_instance_path') ++ def test_delete_instance_files_failed(self, get_instance_path, exists, exe, ++ shutil): ++ lv = self.libvirtconnection ++ get_instance_path.return_value = '/path' ++ params = dict(uuid='fake-uuid', id=1) ++ instance = self._create_instance(params) ++ ++ exists.side_effect = [False, False, True, True] ++ ++ result = lv.delete_instance_files(instance) ++ get_instance_path.assert_called_with(instance) ++ exe.assert_called_with('mv', '/path', '/path_del') ++ shutil.assert_called_with('/path_del') ++ self.assertFalse(result) ++ ++ @mock.patch('shutil.rmtree') ++ @mock.patch('nova.utils.execute') ++ @mock.patch('os.path.exists') ++ @mock.patch('nova.virt.libvirt.utils.get_instance_path') ++ def test_delete_instance_files_mv_failed(self, get_instance_path, exists, ++ exe, shutil): ++ lv = self.libvirtconnection ++ get_instance_path.return_value = '/path' ++ params = dict(uuid='fake-uuid', id=1) ++ instance = self._create_instance(params) ++ ++ nova.utils.execute.side_effect = Exception() ++ exists.side_effect = [True, True] ++ ++ result = lv.delete_instance_files(instance) ++ get_instance_path.assert_called_with(instance) ++ expected = [mock.call('mv', '/path', '/path_del'), ++ mock.call('mv', '/path_resize', '/path_del')] * 2 ++ self.assertEqual(expected, exe.mock_calls) ++ self.assertFalse(result) ++ ++ @mock.patch('shutil.rmtree') ++ @mock.patch('nova.utils.execute') ++ @mock.patch('os.path.exists') ++ @mock.patch('nova.virt.libvirt.utils.get_instance_path') ++ def test_delete_instance_files_resume(self, get_instance_path, exists, ++ exe, shutil): ++ lv = self.libvirtconnection ++ get_instance_path.return_value = '/path' ++ params = dict(uuid='fake-uuid', id=1) ++ instance = self._create_instance(params) ++ ++ nova.utils.execute.side_effect = Exception() ++ exists.side_effect = [False, False, True, False] ++ ++ result = lv.delete_instance_files(instance) ++ get_instance_path.assert_called_with(instance) ++ expected = [mock.call('mv', '/path', '/path_del'), ++ mock.call('mv', '/path_resize', '/path_del')] * 2 ++ self.assertEqual(expected, exe.mock_calls) ++ self.assertTrue(result) ++ ++ @mock.patch('shutil.rmtree') ++ @mock.patch('nova.utils.execute') ++ @mock.patch('os.path.exists') ++ @mock.patch('nova.virt.libvirt.utils.get_instance_path') ++ def test_delete_instance_files_none(self, get_instance_path, exists, ++ exe, shutil): ++ lv = self.libvirtconnection ++ get_instance_path.return_value = '/path' ++ params = dict(uuid='fake-uuid', id=1) ++ instance = self._create_instance(params) ++ ++ nova.utils.execute.side_effect = Exception() ++ exists.side_effect = [False, False, False, False] ++ ++ result = lv.delete_instance_files(instance) ++ get_instance_path.assert_called_with(instance) ++ expected = [mock.call('mv', '/path', '/path_del'), ++ mock.call('mv', '/path_resize', '/path_del')] * 2 ++ self.assertEqual(expected, exe.mock_calls) ++ self.assertEqual(0, len(shutil.mock_calls)) ++ self.assertTrue(result) ++ ++ @mock.patch('shutil.rmtree') ++ @mock.patch('nova.utils.execute') ++ @mock.patch('os.path.exists') ++ @mock.patch('nova.virt.libvirt.utils.get_instance_path') ++ def test_delete_instance_files_concurrent(self, get_instance_path, exists, ++ exe, shutil): ++ lv = self.libvirtconnection ++ get_instance_path.return_value = '/path' ++ params = dict(uuid='fake-uuid', id=1) ++ instance = self._create_instance(params) ++ ++ nova.utils.execute.side_effect = [Exception(), Exception(), None] ++ exists.side_effect = [False, False, True, False] ++ ++ result = lv.delete_instance_files(instance) ++ get_instance_path.assert_called_with(instance) ++ expected = [mock.call('mv', '/path', '/path_del'), ++ mock.call('mv', '/path_resize', '/path_del')] ++ expected.append(expected[0]) ++ self.assertEqual(expected, exe.mock_calls) ++ shutil.assert_called_with('/path_del') ++ self.assertTrue(result) ++ + + class LibvirtVolumeUsageTestCase(test.TestCase): + """Test for LibvirtDriver.get_all_volume_usage.""" +Index: nova-2014.1.5/nova/virt/libvirt/driver.py +=================================================================== +--- nova-2014.1.5.orig/nova/virt/libvirt/driver.py 2017-09-12 11:47:57.420575903 -0400 ++++ nova-2014.1.5/nova/virt/libvirt/driver.py 2017-09-12 11:47:57.416575850 -0400 +@@ -5342,23 +5342,59 @@ class LibvirtDriver(driver.ComputeDriver + + def delete_instance_files(self, instance): + target = libvirt_utils.get_instance_path(instance) +- if os.path.exists(target): +- LOG.info(_('Deleting instance files %s'), target, ++ # A resize may be in progress ++ target_resize = target + '_resize' ++ # Other threads may attempt to rename the path, so renaming the path ++ # to target + '_del' (because it is atomic) and iterating through ++ # twice in the unlikely event that a concurrent rename occurs between ++ # the two rename attempts in this method. In general this method ++ # should be fairly thread-safe without these additional checks, since ++ # other operations involving renames are not permitted when the task ++ # state is not None and the task state should be set to something ++ # other than None by the time this method is invoked. ++ target_del = target + '_del' ++ for i in six.moves.range(2): ++ try: ++ utils.execute('mv', target, target_del) ++ break ++ except Exception: ++ pass ++ try: ++ utils.execute('mv', target_resize, target_del) ++ break ++ except Exception: ++ pass ++ # Either the target or target_resize path may still exist if all ++ # rename attempts failed. ++ remaining_path = None ++ for p in (target, target_resize): ++ if os.path.exists(p): ++ remaining_path = p ++ break ++ ++ # A previous delete attempt may have been interrupted, so target_del ++ # may exist even if all rename attempts during the present method ++ # invocation failed due to the absence of both target and ++ # target_resize. ++ if not remaining_path and os.path.exists(target_del): ++ LOG.info(_('Deleting instance files %s'), target_del, + instance=instance) ++ remaining_path = target_del + try: +- shutil.rmtree(target) ++ shutil.rmtree(target_del) + except OSError as e: + LOG.error(_('Failed to cleanup directory %(target)s: ' +- '%(e)s'), {'target': target, 'e': e}, ++ '%(e)s'), {'target': target_del, 'e': e}, + instance=instance) + + # It is possible that the delete failed, if so don't mark the instance + # as cleaned. +- if os.path.exists(target): +- LOG.info(_('Deletion of %s failed'), target, instance=instance) ++ if remaining_path and os.path.exists(remaining_path): ++ LOG.info(_('Deletion of %s failed'), remaining_path, ++ instance=instance) + return False + +- LOG.info(_('Deletion of %s complete'), target, instance=instance) ++ LOG.info(_('Deletion of %s complete'), target_del, instance=instance) + return True + + @property diff -Nru nova-2014.1.5/debian/patches/CVE-2015-3241-2.patch nova-2014.1.5/debian/patches/CVE-2015-3241-2.patch --- nova-2014.1.5/debian/patches/CVE-2015-3241-2.patch 1970-01-01 00:00:00.000000000 +0000 +++ nova-2014.1.5/debian/patches/CVE-2015-3241-2.patch 2017-09-12 12:09:00.000000000 +0000 @@ -0,0 +1,126 @@ +From 203d8803b786a2eaf73389f6c1209f720e1533dd Mon Sep 17 00:00:00 2001 +From: abhishekkekane +Date: Sat, 8 Aug 2015 02:28:50 -0700 +Subject: [PATCH] Sync process utils from oslo for execute callbacks + +------------------------------------------------ +The sync pulls in the following changes: + +Ifc23325 Add 2 callbacks to processutils.execute() +I22b2d7b processutils: ensure on_completion callback is always called +I59d5799 Let oslotest manage the six.move setting for mox +I245750f Remove `processutils` dependency on `log` +Ia5bb418 Fix exception message in openstack.common.processutils.execute +----------------------------------------------- + +Related-Bug: 1387543 +(cherry picked from commit bf23643e36c8764b4bd532546a2cc04385fe0cff) + +Upstream patch removes the six move from +nova/openstack/common/__init__.py. This backport leaves it there as +it doesn't seem to be related, and it upsets python 2.6. + +Upstream-Juno: https://review.openstack.org/#/c/208876/ +Related: rhbz 1257789 +Related: CVE-2015-3241 + +Change-Id: I22b2d7bde8797276f7670bc289d915dab5122481 +Reviewed-on: https://code.engineering.redhat.com/gerrit/57493 +Reviewed-by: Vladik Romanovsky +Tested-by: RHOS Jenkins +Reviewed-by: Matthew Booth +--- + nova/openstack/common/processutils.py | 59 ++++++++++++++++++++++++----------- + 1 file changed, 40 insertions(+), 19 deletions(-) + +diff --git a/nova/openstack/common/processutils.py b/nova/openstack/common/processutils.py +index cb787e2..4ad0a96 100644 +--- a/nova/openstack/common/processutils.py ++++ b/nova/openstack/common/processutils.py +@@ -112,6 +112,17 @@ def execute(*cmd, **kwargs): + :type shell: boolean + :param loglevel: log level for execute commands. + :type loglevel: int. (Should be logging.DEBUG or logging.INFO) ++ :param on_execute: This function will be called upon process creation ++ with the object as a argument. The Purpose of this ++ is to allow the caller of `processutils.execute` to ++ track process creation asynchronously. ++ :type on_execute: function(:class:`subprocess.Popen`) ++ :param on_completion: This function will be called upon process ++ completion with the object as a argument. The ++ Purpose of this is to allow the caller of ++ `processutils.execute` to track process completion ++ asynchronously. ++ :type on_completion: function(:class:`subprocess.Popen`) + :returns: (stdout, stderr) from process execution + :raises: :class:`UnknownArgumentError` on + receiving unknown arguments +@@ -127,6 +138,8 @@ def execute(*cmd, **kwargs): + root_helper = kwargs.pop('root_helper', '') + shell = kwargs.pop('shell', False) + loglevel = kwargs.pop('loglevel', logging.DEBUG) ++ on_execute = kwargs.pop('on_execute', None) ++ on_completion = kwargs.pop('on_completion', None) + + if isinstance(check_exit_code, bool): + ignore_exit_code = not check_exit_code +@@ -135,8 +148,7 @@ def execute(*cmd, **kwargs): + check_exit_code = [check_exit_code] + + if kwargs: +- raise UnknownArgumentError(_('Got unknown keyword args ' +- 'to utils.execute: %r') % kwargs) ++ raise UnknownArgumentError(_('Got unknown keyword args: %r') % kwargs) + + if run_as_root and hasattr(os, 'geteuid') and os.geteuid() != 0: + if not root_helper: +@@ -168,23 +180,32 @@ def execute(*cmd, **kwargs): + close_fds=close_fds, + preexec_fn=preexec_fn, + shell=shell) +- result = None +- for _i in six.moves.range(20): +- # NOTE(russellb) 20 is an arbitrary number of retries to +- # prevent any chance of looping forever here. +- try: +- if process_input is not None: +- result = obj.communicate(process_input) +- else: +- result = obj.communicate() +- except OSError as e: +- if e.errno in (errno.EAGAIN, errno.EINTR): +- continue +- raise +- break +- obj.stdin.close() # pylint: disable=E1101 +- _returncode = obj.returncode # pylint: disable=E1101 +- LOG.log(loglevel, _('Result was %s') % _returncode) ++ ++ if on_execute: ++ on_execute(obj) ++ ++ try: ++ result = None ++ for _i in six.moves.range(20): ++ # NOTE(russellb) 20 is an arbitrary number of retries to ++ # prevent any chance of looping forever here. ++ try: ++ if process_input is not None: ++ result = obj.communicate(process_input) ++ else: ++ result = obj.communicate() ++ except OSError as e: ++ if e.errno in (errno.EAGAIN, errno.EINTR): ++ continue ++ raise ++ break ++ obj.stdin.close() # pylint: disable=E1101 ++ _returncode = obj.returncode # pylint: disable=E1101 ++ LOG.log(loglevel, _('Result was %s') % _returncode) ++ finally: ++ if on_completion: ++ on_completion(obj) ++ + if not ignore_exit_code and _returncode not in check_exit_code: + (stdout, stderr) = result + sanitized_stdout = strutils.mask_password(stdout) diff -Nru nova-2014.1.5/debian/patches/CVE-2015-3241-3.patch nova-2014.1.5/debian/patches/CVE-2015-3241-3.patch --- nova-2014.1.5/debian/patches/CVE-2015-3241-3.patch 1970-01-01 00:00:00.000000000 +0000 +++ nova-2014.1.5/debian/patches/CVE-2015-3241-3.patch 2017-09-12 15:51:05.000000000 +0000 @@ -0,0 +1,362 @@ +Backport of: + +From 70d2a051b057054676df663291885defb84a6dd6 Mon Sep 17 00:00:00 2001 +From: abhishekkekane +Date: Mon, 6 Jul 2015 01:51:26 -0700 +Subject: [PATCH] libvirt: Kill rsync/scp processes before deleting instance + +In the resize operation, during copying files from source to +destination compute node scp/rsync processes are not aborted after +the instance is deleted because linux kernel doesn't delete instance +files physically until all processes using the file handle is closed +completely. Hence rsync/scp process keeps on running until it +transfers 100% of file data. + +Added new module instancejobtracker to libvirt driver which will add, +remove or terminate the processes running against particular instances. +Added callback methods to execute call which will store the pid of +scp/rsync process in cache as a key: value pair and to remove the +pid from the cache after process completion. Process id will be used to +kill the process if it is running while deleting the instance. Instance +uuid is used as a key in the cache and pid will be the value. + +Conflicts: + nova/tests/unit/virt/libvirt/test_driver.py + nova/tests/unit/virt/libvirt/test_utils.py + nova/virt/libvirt/driver.py + nova/virt/libvirt/utils.py + +Note: The required unit-tests are manually added to the below path, +as new path for unit-tests is not present in stable/juno release. +nova/tests/virt/libvirt/test_driver.py +nova/tests/virt/libvirt/test_utils.py + +SecurityImpact + +Closes-bug: #1387543 +(cherry picked from commit 7ab75d5b0b75fc3426323bef19bf436a258b9707) +(cherry picked from commit b5020a047fc487f35b76fc05f31e52665a1afda1) +(cherry picked from commit 539693e40388c4729c99a2c133b573896296df2a) + +Upstream-Juno: https://review.openstack.org/#/c/214528/ +Resolves: rhbz 1257789 +Resolves: CVE-2015-3241 + +Change-Id: Ie03acc00a7c904aec13c90ae6a53938d08e5e0c9 +Reviewed-on: https://code.engineering.redhat.com/gerrit/57494 +Reviewed-by: Vladik Romanovsky +Tested-by: RHOS Jenkins +Reviewed-by: Matthew Booth +--- + nova/tests/virt/libvirt/test_image_utils.py | 6 +- + nova/tests/virt/libvirt/test_libvirt.py | 40 +++++++++++ + nova/tests/virt/libvirt/test_libvirt_utils.py | 6 +- + nova/virt/libvirt/driver.py | 19 +++++- + nova/virt/libvirt/instancejobtracker.py | 97 +++++++++++++++++++++++++++ + nova/virt/libvirt/utils.py | 14 ++-- + 6 files changed, 172 insertions(+), 10 deletions(-) + create mode 100644 nova/virt/libvirt/instancejobtracker.py + +Index: nova-2014.1.5/nova/tests/virt/libvirt/test_libvirt.py +=================================================================== +--- nova-2014.1.5.orig/nova/tests/virt/libvirt/test_libvirt.py 2017-09-12 11:48:41.737154733 -0400 ++++ nova-2014.1.5/nova/tests/virt/libvirt/test_libvirt.py 2017-09-12 11:48:41.729154628 -0400 +@@ -23,6 +23,7 @@ import mox + import os + import re + import shutil ++import signal + import tempfile + import uuid + +@@ -6709,6 +6710,15 @@ class LibvirtConnTestCase(test.TestCase) + self.mox.ReplayAll() + self.assertTrue(conn._is_storage_shared_with('foo', '/path')) + ++ def test_store_pid_remove_pid(self): ++ instance = self.create_instance_obj(self.context) ++ drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) ++ popen = mock.Mock(pid=3) ++ drvr.job_tracker.add_job(instance, popen.pid) ++ self.assertIn(3, drvr.job_tracker.jobs[instance.uuid]) ++ drvr.job_tracker.remove_job(instance, popen.pid) ++ self.assertNotIn(instance.uuid, drvr.job_tracker.jobs) ++ + def test_create_domain_define_xml_fails(self): + """Tests that the xml is logged when defining the domain fails.""" + fake_xml = "this is a test" +@@ -8687,12 +8697,18 @@ class LibvirtDriverTestCase(test.TestCas + def fake_execute(*args, **kwargs): + pass + ++ def fake_copy_image(src, dest, host=None, receive=False, ++ on_execute=None, on_completion=None): ++ self.assertIsNotNone(on_execute) ++ self.assertIsNotNone(on_completion) ++ + self.stubs.Set(self.libvirtconnection, 'get_instance_disk_info', + fake_get_instance_disk_info) + self.stubs.Set(self.libvirtconnection, '_destroy', fake_destroy) + self.stubs.Set(self.libvirtconnection, 'get_host_ip_addr', + fake_get_host_ip_addr) + self.stubs.Set(utils, 'execute', fake_execute) ++ self.stubs.Set(libvirt_utils, 'copy_image', fake_copy_image) + + ins_ref = self._create_instance() + flavor = {'root_gb': 10, 'ephemeral_gb': 20} +@@ -9294,6 +9310,30 @@ class LibvirtDriverTestCase(test.TestCas + + @mock.patch('shutil.rmtree') + @mock.patch('nova.utils.execute') ++ @mock.patch('os.path.exists') ++ @mock.patch('os.kill') ++ @mock.patch('nova.virt.libvirt.utils.get_instance_path') ++ def test_delete_instance_files_kill_running( ++ self, get_instance_path, kill, exists, exe, shutil): ++ lv = self.libvirtconnection ++ get_instance_path.return_value = '/path' ++ params = dict(uuid='fake-uuid', id=1) ++ instance = self._create_instance(params) ++ lv.job_tracker.jobs[instance.uuid] = [3, 4] ++ ++ exists.side_effect = [False, False, True, False] ++ ++ result = lv.delete_instance_files(instance) ++ get_instance_path.assert_called_with(instance) ++ exe.assert_called_with('mv', '/path', '/path_del') ++ kill.assert_has_calls([mock.call(3, signal.SIGKILL), mock.call(3, 0), ++ mock.call(4, signal.SIGKILL), mock.call(4, 0)]) ++ shutil.assert_called_with('/path_del') ++ self.assertTrue(result) ++ self.assertNotIn(instance.uuid, lv.job_tracker.jobs) ++ ++ @mock.patch('shutil.rmtree') ++ @mock.patch('nova.utils.execute') + @mock.patch('os.path.exists') + @mock.patch('nova.virt.libvirt.utils.get_instance_path') + def test_delete_instance_files_resize(self, get_instance_path, exists, +Index: nova-2014.1.5/nova/tests/virt/libvirt/test_libvirt_utils.py +=================================================================== +--- nova-2014.1.5.orig/nova/tests/virt/libvirt/test_libvirt_utils.py 2017-09-12 11:48:41.737154733 -0400 ++++ nova-2014.1.5/nova/tests/virt/libvirt/test_libvirt_utils.py 2017-09-12 11:48:41.729154628 -0400 +@@ -218,7 +218,8 @@ blah BLAH: bb + mock_execute.assert_called_once_with('cp', 'src', 'dest') + + _rsync_call = functools.partial(mock.call, +- 'rsync', '--sparse', '--compress') ++ 'rsync', '--sparse', '--compress', ++ on_execute=None, on_completion=None) + + @mock.patch('nova.utils.execute') + def test_copy_image_rsync(self, mock_execute): +@@ -241,6 +242,7 @@ blah BLAH: bb + + mock_execute.assert_has_calls([ + self._rsync_call('--dry-run', 'src', 'host:dest'), +- mock.call('scp', 'src', 'host:dest'), ++ mock.call('scp', 'src', 'host:dest', ++ on_execute=None, on_completion=None), + ]) + self.assertEqual(2, mock_execute.call_count) +Index: nova-2014.1.5/nova/virt/libvirt/driver.py +=================================================================== +--- nova-2014.1.5.orig/nova/virt/libvirt/driver.py 2017-09-12 11:48:41.737154733 -0400 ++++ nova-2014.1.5/nova/virt/libvirt/driver.py 2017-09-12 11:50:03.642222778 -0400 +@@ -104,6 +104,7 @@ from nova.virt.libvirt import config as + from nova.virt.libvirt import firewall as libvirt_firewall + from nova.virt.libvirt import imagebackend + from nova.virt.libvirt import imagecache ++from nova.virt.libvirt import instancejobtracker + from nova.virt.libvirt import utils as libvirt_utils + from nova.virt import netutils + from nova.virt import watchdog_actions +@@ -414,6 +415,8 @@ class LibvirtDriver(driver.ComputeDriver + + self._volume_api = volume.API() + ++ self.job_tracker = instancejobtracker.InstanceJobTracker() ++ + @property + def disk_cachemode(self): + if self._disk_cachemode is None: +@@ -5102,6 +5105,12 @@ class LibvirtDriver(driver.ComputeDriver + img_path = info['path'] + fname = os.path.basename(img_path) + from_path = os.path.join(inst_base_resize, fname) ++ ++ on_execute = lambda process: self.job_tracker.add_job( ++ instance, process.pid) ++ on_completion = lambda process: self.job_tracker.remove_job( ++ instance, process.pid) ++ + if info['type'] == 'qcow2' and info['backing_file']: + tmp_path = from_path + "_rbase" + # merge backing file +@@ -5111,11 +5120,15 @@ class LibvirtDriver(driver.ComputeDriver + if shared_storage: + utils.execute('mv', tmp_path, img_path) + else: +- libvirt_utils.copy_image(tmp_path, img_path, host=dest) ++ libvirt_utils.copy_image(tmp_path, img_path, host=dest, ++ on_execute=on_execute, ++ on_completion=on_completion) + utils.execute('rm', '-f', tmp_path) + + else: # raw or qcow2 with no backing file +- libvirt_utils.copy_image(from_path, img_path, host=dest) ++ libvirt_utils.copy_image(from_path, img_path, host=dest, ++ on_execute=on_execute, ++ on_completion=on_completion) + except Exception: + with excutils.save_and_reraise_exception(): + self._cleanup_remote_migration(dest, inst_base, +@@ -5377,6 +5390,8 @@ class LibvirtDriver(driver.ComputeDriver + # invocation failed due to the absence of both target and + # target_resize. + if not remaining_path and os.path.exists(target_del): ++ self.job_tracker.terminate_jobs(instance) ++ + LOG.info(_('Deleting instance files %s'), target_del, + instance=instance) + remaining_path = target_del +Index: nova-2014.1.5/nova/virt/libvirt/instancejobtracker.py +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ nova-2014.1.5/nova/virt/libvirt/instancejobtracker.py 2017-09-12 11:48:41.733154681 -0400 +@@ -0,0 +1,97 @@ ++# Copyright 2015 NTT corp. ++# All Rights Reserved. ++# Licensed under the Apache License, Version 2.0 (the "License"); you may ++# not use this file except in compliance with the License. You may obtain ++# a copy of the License at ++# ++# http://www.apache.org/licenses/LICENSE-2.0 ++# ++# Unless required by applicable law or agreed to in writing, software ++# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT ++# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the ++# License for the specific language governing permissions and limitations ++# under the License. ++ ++ ++import collections ++import errno ++import os ++import signal ++ ++from nova.openstack.common.gettextutils import _LE ++from nova.openstack.common.gettextutils import _LW ++from nova.openstack.common import log as logging ++ ++ ++LOG = logging.getLogger(__name__) ++ ++ ++class InstanceJobTracker(object): ++ def __init__(self): ++ self.jobs = collections.defaultdict(list) ++ ++ def add_job(self, instance, pid): ++ """Appends process_id of instance to cache. ++ ++ This method will store the pid of a process in cache as ++ a key: value pair which will be used to kill the process if it ++ is running while deleting the instance. Instance uuid is used as ++ a key in the cache and pid will be the value. ++ ++ :param instance: Object of instance ++ :param pid: Id of the process ++ """ ++ self.jobs[instance.uuid].append(pid) ++ ++ def remove_job(self, instance, pid): ++ """Removes pid of process from cache. ++ ++ This method will remove the pid of a process from the cache. ++ ++ :param instance: Object of instance ++ :param pid: Id of the process ++ """ ++ uuid = instance.uuid ++ if uuid in self.jobs and pid in self.jobs[uuid]: ++ self.jobs[uuid].remove(pid) ++ ++ # remove instance.uuid if no pid's remaining ++ if not self.jobs[uuid]: ++ self.jobs.pop(uuid, None) ++ ++ def terminate_jobs(self, instance): ++ """Kills the running processes for given instance. ++ ++ This method is used to kill all running processes of the instance if ++ it is deleted in between. ++ ++ :param instance: Object of instance ++ """ ++ pids_to_remove = list(self.jobs.get(instance.uuid, [])) ++ for pid in pids_to_remove: ++ try: ++ # Try to kill the process ++ os.kill(pid, signal.SIGKILL) ++ except OSError as exc: ++ if exc.errno != errno.ESRCH: ++ LOG.error(_LE('Failed to kill process %(pid)s ' ++ 'due to %(reason)s, while deleting the ' ++ 'instance.'), {'pid': pid, 'reason': exc}, ++ instance=instance) ++ ++ try: ++ # Check if the process is still alive. ++ os.kill(pid, 0) ++ except OSError as exc: ++ if exc.errno != errno.ESRCH: ++ LOG.error(_LE('Unexpected error while checking process ' ++ '%(pid)s.'), {'pid': pid}, ++ instance=instance) ++ else: ++ # The process is still around ++ LOG.warn(_LW("Failed to kill a long running process " ++ "%(pid)s related to the instance when " ++ "deleting it."), {'pid': pid}, ++ instance=instance) ++ ++ self.remove_job(instance, pid) +Index: nova-2014.1.5/nova/virt/libvirt/utils.py +=================================================================== +--- nova-2014.1.5.orig/nova/virt/libvirt/utils.py 2017-09-12 11:48:41.737154733 -0400 ++++ nova-2014.1.5/nova/virt/libvirt/utils.py 2017-09-12 11:50:28.938552498 -0400 +@@ -481,12 +481,15 @@ def get_disk_backing_file(path, basename + return backing_file + + +-def copy_image(src, dest, host=None): ++def copy_image(src, dest, host=None, on_execute=None, ++ on_completion=None): + """Copy a disk image to an existing directory + + :param src: Source image + :param dest: Destination path + :param host: Remote host ++ :param on_execute: Callback method to store pid of process in cache ++ :param on_completion: Callback method to remove pid of process from cache + """ + + if not host: +@@ -505,11 +508,14 @@ def copy_image(src, dest, host=None): + # Do a relatively light weight test first, so that we + # can fall back to scp, without having run out of space + # on the destination for example. +- execute('rsync', '--sparse', '--compress', '--dry-run', src, dest) ++ execute('rsync', '--sparse', '--compress', '--dry-run', src, dest, ++ on_execute=on_execute, on_completion=on_completion) + except processutils.ProcessExecutionError: +- execute('scp', src, dest) ++ execute('scp', src, dest, on_execute=on_execute, ++ on_completion=on_completion) + else: +- execute('rsync', '--sparse', '--compress', src, dest) ++ execute('rsync', '--sparse', '--compress', src, dest, ++ on_execute=on_execute, on_completion=on_completion) + + + def write_to_file(path, contents, umask=None): diff -Nru nova-2014.1.5/debian/patches/CVE-2015-3280.patch nova-2014.1.5/debian/patches/CVE-2015-3280.patch --- nova-2014.1.5/debian/patches/CVE-2015-3280.patch 1970-01-01 00:00:00.000000000 +0000 +++ nova-2014.1.5/debian/patches/CVE-2015-3280.patch 2017-09-12 12:20:55.000000000 +0000 @@ -0,0 +1,218 @@ +From 38efa64f487ed644068b28ea050bb43f2e291208 Mon Sep 17 00:00:00 2001 +From: Rajesh Tailor +Date: Wed, 4 Mar 2015 05:05:19 -0800 +Subject: [PATCH] Delete orphaned instance files from compute nodes + +While resizing/revert-resizing instance, if instance gets deleted +in between, then instance files remains either on the source or +destination compute node. + +To address this issue, added a new periodic task +'_cleanup_incomplete_migrations' which takes care of deleting +instance files from source/destination compute nodes and then +mark migration record as failed so that it doesn't appear again +in the next periodic task run. + +SecurityImpact + +(cherry picked from commit 18d6b5cc79973fc553daf7a92f22cce4dc0ca013) + +Conflicts: + nova/compute/manager.py + nova/tests/unit/compute/test_compute_mgr.py + +(cherry picked from commit fa72fb8b51d59e04913c871539cee98a3da79058) + +Conflicts: + nova/tests/compute/test_compute_mgr.py + nova/compute/manager.py + +Closes-Bug: 1392527 +Resolves: rhbz 1264278 +Resolves: rhbz 1264279 +Upstream-Juno: https://review.openstack.org/#/c/219301/ +Change-Id: I9866d8e32e99b9f907921f4b226edf7b62bd83a7 +Reviewed-on: https://code.engineering.redhat.com/gerrit/58740 +Tested-by: RHOS Jenkins +Reviewed-by: Nikola Dipanov +Reviewed-by: Jon Schlueter +--- + nova/compute/manager.py | 60 ++++++++++++++++++++++++++-- + nova/tests/compute/test_compute_mgr.py | 73 ++++++++++++++++++++++++++++++++++ + 2 files changed, 129 insertions(+), 4 deletions(-) + +Index: nova-2014.1.5/nova/compute/manager.py +=================================================================== +--- nova-2014.1.5.orig/nova/compute/manager.py 2017-09-12 08:20:52.683037454 -0400 ++++ nova-2014.1.5/nova/compute/manager.py 2017-09-12 08:20:52.675037354 -0400 +@@ -245,12 +245,18 @@ def errors_out_migration(function): + def decorated_function(self, context, *args, **kwargs): + try: + return function(self, context, *args, **kwargs) +- except Exception: ++ except Exception as ex: + with excutils.save_and_reraise_exception(): + migration = kwargs['migration'] +- status = migration.status +- if status not in ['migrating', 'post-migrating']: +- return ++ ++ # NOTE(rajesht): If InstanceNotFound error is thrown from ++ # decorated function, migration status should be set to ++ # 'error', without checking current migration status. ++ if not isinstance(ex, exception.InstanceNotFound): ++ status = migration.status ++ if status not in ['migrating', 'post-migrating']: ++ return ++ + migration.status = 'error' + try: + migration.save(context.elevated()) +@@ -3279,6 +3285,7 @@ class ComputeManager(manager.Manager): + @wrap_exception() + @reverts_task_state + @wrap_instance_event ++ @errors_out_migration + @wrap_instance_fault + def revert_resize(self, context, instance, migration, reservations): + """Destroys the new instance on the destination machine. +@@ -3333,6 +3340,7 @@ class ComputeManager(manager.Manager): + @wrap_exception() + @reverts_task_state + @wrap_instance_event ++ @errors_out_migration + @wrap_instance_fault + def finish_revert_resize(self, context, instance, reservations, migration): + """Finishes the second half of reverting a resize. +@@ -5834,3 +5842,47 @@ class ComputeManager(manager.Manager): + instance.cleaned = True + with utils.temporary_mutation(context, read_deleted='yes'): + instance.save(context) ++ ++ @periodic_task.periodic_task(spacing=CONF.instance_delete_interval) ++ def _cleanup_incomplete_migrations(self, context): ++ """Delete instance files on failed resize/revert-resize operation ++ ++ During resize/revert-resize operation, if that instance gets deleted ++ in-between then instance files might remain either on source or ++ destination compute node because of race condition. ++ """ ++ LOG.debug('Cleaning up deleted instances with incomplete migration ') ++ migration_filters = {'host': CONF.host, ++ 'status': 'error'} ++ migrations = migration_obj.MigrationList.get_by_filters(context, ++ migration_filters) ++ ++ if not migrations: ++ return ++ ++ inst_uuid_from_migrations = set([migration.instance_uuid for migration ++ in migrations]) ++ ++ inst_filters = {'deleted': True, 'soft_deleted': False, ++ 'uuid': inst_uuid_from_migrations} ++ attrs = ['info_cache', 'security_groups', 'system_metadata'] ++ with utils.temporary_mutation(context, read_deleted='yes'): ++ instances = instance_obj.InstanceList.get_by_filters( ++ context, inst_filters, expected_attrs=attrs, use_slave=True) ++ ++ for instance in instances: ++ if instance.host != CONF.host: ++ for migration in migrations: ++ if instance.uuid == migration.instance_uuid: ++ # Delete instance files if not cleanup properly either ++ # from the source or destination compute nodes when ++ # the instance is deleted during resizing. ++ self.driver.delete_instance_files(instance) ++ try: ++ migration.status = 'failed' ++ migration.save(context.elevated()) ++ except exception.MigrationNotFound: ++ LOG.warning(_LW("Migration %s is not found."), ++ migration.id, context=context, ++ instance=instance) ++ break +Index: nova-2014.1.5/nova/tests/compute/test_compute_mgr.py +=================================================================== +--- nova-2014.1.5.orig/nova/tests/compute/test_compute_mgr.py 2017-09-12 08:20:52.683037454 -0400 ++++ nova-2014.1.5/nova/tests/compute/test_compute_mgr.py 2017-09-12 08:20:52.675037354 -0400 +@@ -870,6 +870,79 @@ class ComputeManagerUnitTestCase(test.No + self.assertFalse(c.cleaned) + self.assertEqual('1', c.system_metadata['clean_attempts']) + ++ @mock.patch.object(migration_obj.Migration, 'save') ++ @mock.patch.object(migration_obj.MigrationList, 'get_by_filters') ++ @mock.patch.object(instance_obj.InstanceList, 'get_by_filters') ++ def _test_cleanup_incomplete_migrations(self, inst_host, ++ mock_inst_get_by_filters, ++ mock_migration_get_by_filters, ++ mock_save): ++ def fake_inst(context, uuid, host): ++ inst = instance_obj.Instance(context) ++ inst.uuid = uuid ++ inst.host = host ++ return inst ++ ++ def fake_migration(uuid, status, inst_uuid, src_host, dest_host): ++ migration = migration_obj.Migration() ++ migration.uuid = uuid ++ migration.status = status ++ migration.instance_uuid = inst_uuid ++ migration.source_compute = src_host ++ migration.dest_compute = dest_host ++ return migration ++ ++ fake_instances = [fake_inst(self.context, '111', inst_host), ++ fake_inst(self.context, '222', inst_host)] ++ ++ fake_migrations = [fake_migration('123', 'error', '111', ++ 'fake-host', 'fake-mini'), ++ fake_migration('456', 'error', '222', ++ 'fake-host', 'fake-mini')] ++ ++ mock_migration_get_by_filters.return_value = fake_migrations ++ mock_inst_get_by_filters.return_value = fake_instances ++ ++ with mock.patch.object(self.compute.driver, 'delete_instance_files'): ++ self.compute._cleanup_incomplete_migrations(self.context) ++ ++ # Ensure that migration status is set to 'failed' after instance ++ # files deletion for those instances whose instance.host is not ++ # same as compute host where periodic task is running. ++ for inst in fake_instances: ++ if inst.host != CONF.host: ++ for mig in fake_migrations: ++ if inst.uuid == mig.instance_uuid: ++ self.assertEqual('failed', mig.status) ++ ++ def test_cleanup_incomplete_migrations_dest_node(self): ++ """Test to ensure instance files are deleted from destination node. ++ ++ If an instance gets deleted during resizing/revert-resizing ++ operation, in that case instance files gets deleted from ++ instance.host (source host here), but there is possibility that ++ instance files could be present on destination node. ++ ++ This test ensures that `_cleanup_incomplete_migration` periodic ++ task deletes orphaned instance files from destination compute node. ++ """ ++ self.flags(host='fake-mini') ++ self._test_cleanup_incomplete_migrations('fake-host') ++ ++ def test_cleanup_incomplete_migrations_source_node(self): ++ """Test to ensure instance files are deleted from source node. ++ ++ If instance gets deleted during resizing/revert-resizing operation, ++ in that case instance files gets deleted from instance.host (dest ++ host here), but there is possibility that instance files could be ++ present on source node. ++ ++ This test ensures that `_cleanup_incomplete_migration` periodic ++ task deletes orphaned instance files from source compute node. ++ """ ++ self.flags(host='fake-host') ++ self._test_cleanup_incomplete_migrations('fake-mini') ++ + def test_swap_volume_volume_api_usage(self): + # This test ensures that volume_id arguments are passed to volume_api + # and that volume states are OK diff -Nru nova-2014.1.5/debian/patches/CVE-2015-5162-1.patch nova-2014.1.5/debian/patches/CVE-2015-5162-1.patch --- nova-2014.1.5/debian/patches/CVE-2015-5162-1.patch 1970-01-01 00:00:00.000000000 +0000 +++ nova-2014.1.5/debian/patches/CVE-2015-5162-1.patch 2017-09-13 12:31:39.000000000 +0000 @@ -0,0 +1,370 @@ +From 994da57713461bb5524e641a93efc0ecd94ef329 Mon Sep 17 00:00:00 2001 +From: Victor Stinner +Date: Fri, 14 Oct 2016 16:17:58 +0200 +Subject: [PATCH] Add prlimit parameter to execute() + +Add a new oslo_concurrency.prlimit module which is written to be used +on the command line: + + python -m oslo_concurrency.prlimit --rss=RSS -- program arg1 ... + +This module calls setrlimit() to restrict the resources and then +executes the program. Its command line is written to be the same than +the Linux prlimit system program. + +Add a new ProcessLimits class processutils: resource limits on a +process. + +Add an optional prlimit parameter to process_utils.execute(). If the +parameter is used, wrap the command through the new oslo_concurrency +prlimit wrapper. + +Linux provides a prlimit command line tool which implements the same +feature (and even more), but it requires util-linux v2.21, and +OpenStack targets other operating systems like Solaris and FreeBSD. + +Upstream-Liberty: https://review.openstack.org/#/c/327630/ +Resolves: rhbz#1382549 + +NOTE(vstinner): The backport comes from oslo.concurrency of OSP 6, I +edited the patch manually to adapt it to the old +nova/openstack/common/ hierarchy and I created a new unit test file. +The test_relative_path() unit test was not backported because +execute() the env_variables parameter required by the test. + +Change-Id: Ib40aa62958ab9c157a2bd51d7ff3edb445556285 +Related-Bug: 1449062 +(cherry-pick from b2e78569c5cabc9582c02aacff1ce2a5e186c3ab) +(cherry picked from commit e33f64fc7920bc4c7051f35042237403fddf1f02) +Reviewed-on: https://code.engineering.redhat.com/gerrit/87169 +Tested-by: RHOS Jenkins +Reviewed-by: Kashyap Chamarthy +Tested-by: Victor Stinner +--- + nova/openstack/common/prlimit.py | 89 +++++++++++++++++ + nova/openstack/common/processutils.py | 49 +++++++++ + nova/tests/openstack_common/__init__.py | 0 + nova/tests/openstack_common/test_processutils.py | 122 +++++++++++++++++++++++ + 4 files changed, 260 insertions(+) + create mode 100644 nova/openstack/common/prlimit.py + create mode 100644 nova/tests/openstack_common/__init__.py + create mode 100644 nova/tests/openstack_common/test_processutils.py + +diff --git a/nova/openstack/common/prlimit.py b/nova/openstack/common/prlimit.py +new file mode 100644 +index 0000000..fa1ef68 +--- /dev/null ++++ b/nova/openstack/common/prlimit.py +@@ -0,0 +1,89 @@ ++# Copyright 2016 Red Hat. ++# All Rights Reserved. ++# ++# Licensed under the Apache License, Version 2.0 (the "License"); you may ++# not use this file except in compliance with the License. You may obtain ++# a copy of the License at ++# ++# http://www.apache.org/licenses/LICENSE-2.0 ++# ++# Unless required by applicable law or agreed to in writing, software ++# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT ++# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the ++# License for the specific language governing permissions and limitations ++# under the License. ++ ++from __future__ import print_function ++ ++import argparse ++import os ++import resource ++import sys ++ ++USAGE_PROGRAM = ('%s -m nova.openstack.common.prlimit' ++ % os.path.basename(sys.executable)) ++ ++RESOURCES = ( ++ # argparse argument => resource ++ ('as', resource.RLIMIT_AS), ++ ('nofile', resource.RLIMIT_NOFILE), ++ ('rss', resource.RLIMIT_RSS), ++) ++ ++ ++def parse_args(): ++ parser = argparse.ArgumentParser(description='prlimit', prog=USAGE_PROGRAM) ++ parser.add_argument('--as', type=int, ++ help='Address space limit in bytes') ++ parser.add_argument('--nofile', type=int, ++ help='Maximum number of open files') ++ parser.add_argument('--rss', type=int, ++ help='Maximum Resident Set Size (RSS) in bytes') ++ parser.add_argument('program', ++ help='Program (absolute path)') ++ parser.add_argument('program_args', metavar="arg", nargs='...', ++ help='Program parameters') ++ ++ args = parser.parse_args() ++ return args ++ ++ ++def main(): ++ args = parse_args() ++ ++ program = args.program ++ if not os.path.isabs(program): ++ # program uses a relative path: try to find the absolute path ++ # to the executable ++ if sys.version_info >= (3, 3): ++ import shutil ++ program_abs = shutil.which(program) ++ else: ++ import distutils.spawn ++ program_abs = distutils.spawn.find_executable(program) ++ if program_abs: ++ program = program_abs ++ ++ for arg_name, rlimit in RESOURCES: ++ value = getattr(args, arg_name) ++ if value is None: ++ continue ++ try: ++ resource.setrlimit(rlimit, (value, value)) ++ except ValueError as exc: ++ print("%s: failed to set the %s resource limit: %s" ++ % (USAGE_PROGRAM, arg_name.upper(), exc), ++ file=sys.stderr) ++ sys.exit(1) ++ ++ try: ++ os.execv(program, [program] + args.program_args) ++ except Exception as exc: ++ print("%s: failed to execute %s: %s" ++ % (USAGE_PROGRAM, program, exc), ++ file=sys.stderr) ++ sys.exit(1) ++ ++ ++if __name__ == "__main__": ++ main() +diff --git a/nova/openstack/common/processutils.py b/nova/openstack/common/processutils.py +index 4ad0a96..4a31171 100644 +--- a/nova/openstack/common/processutils.py ++++ b/nova/openstack/common/processutils.py +@@ -23,6 +23,7 @@ import os + import random + import shlex + import signal ++import sys + + from eventlet.green import subprocess + from eventlet import greenthread +@@ -81,6 +82,38 @@ def _subprocess_setup(): + signal.signal(signal.SIGPIPE, signal.SIG_DFL) + + ++class ProcessLimits(object): ++ """Resource limits on a process. ++ ++ Attributes: ++ ++ * address_space: Address space limit in bytes ++ * number_files: Maximum number of open files. ++ * resident_set_size: Maximum Resident Set Size (RSS) in bytes ++ ++ This object can be used for the *prlimit* parameter of :func:`execute`. ++ """ ++ ++ def __init__(self, **kw): ++ self.address_space = kw.pop('address_space', None) ++ self.number_files = kw.pop('number_files', None) ++ self.resident_set_size = kw.pop('resident_set_size', None) ++ if kw: ++ raise ValueError("invalid limits: %s" ++ % ', '.join(sorted(kw.keys()))) ++ ++ def prlimit_args(self): ++ """Create a list of arguments for the prlimit command line.""" ++ args = [] ++ if self.address_space: ++ args.append('--as=%s' % self.address_space) ++ if self.number_files: ++ args.append('--nofile=%s' % self.number_files) ++ if self.resident_set_size: ++ args.append('--rss=%s' % self.resident_set_size) ++ return args ++ ++ + def execute(*cmd, **kwargs): + """Helper method to shell out and execute a command through subprocess. + +@@ -123,10 +156,17 @@ def execute(*cmd, **kwargs): + `processutils.execute` to track process completion + asynchronously. + :type on_completion: function(:class:`subprocess.Popen`) ++ :param prlimit: Set resource limits on the child process. See ++ below for a detailed description. ++ :type prlimit: :class:`ProcessLimits` + :returns: (stdout, stderr) from process execution + :raises: :class:`UnknownArgumentError` on + receiving unknown arguments + :raises: :class:`ProcessExecutionError` ++ ++ The *prlimit* parameter can be used to set resource limits on the child ++ process. If this parameter is used, the child process will be spawned by a ++ wrapper process which will set limits before spawning the command. + """ + + process_input = kwargs.pop('process_input', None) +@@ -140,6 +180,7 @@ def execute(*cmd, **kwargs): + loglevel = kwargs.pop('loglevel', logging.DEBUG) + on_execute = kwargs.pop('on_execute', None) + on_completion = kwargs.pop('on_completion', None) ++ prlimit = kwargs.pop('prlimit', None) + + if isinstance(check_exit_code, bool): + ignore_exit_code = not check_exit_code +@@ -158,6 +199,14 @@ def execute(*cmd, **kwargs): + cmd = shlex.split(root_helper) + list(cmd) + + cmd = map(str, cmd) ++ ++ if prlimit: ++ args = [sys.executable, '-m', 'nova.openstack.common.prlimit'] ++ args.extend(prlimit.prlimit_args()) ++ args.append('--') ++ args.extend(cmd) ++ cmd = args ++ + sanitized_cmd = strutils.mask_password(' '.join(cmd)) + + while attempts > 0: +diff --git a/nova/tests/openstack_common/__init__.py b/nova/tests/openstack_common/__init__.py +new file mode 100644 +index 0000000..e69de29 +diff --git a/nova/tests/openstack_common/test_processutils.py b/nova/tests/openstack_common/test_processutils.py +new file mode 100644 +index 0000000..4822539 +--- /dev/null ++++ b/nova/tests/openstack_common/test_processutils.py +@@ -0,0 +1,122 @@ ++# Copyright 2011 OpenStack Foundation. ++# All Rights Reserved. ++# ++# Licensed under the Apache License, Version 2.0 (the "License"); you may ++# not use this file except in compliance with the License. You may obtain ++# a copy of the License at ++# ++# http://www.apache.org/licenses/LICENSE-2.0 ++# ++# Unless required by applicable law or agreed to in writing, software ++# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT ++# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the ++# License for the specific language governing permissions and limitations ++# under the License. ++ ++from __future__ import print_function ++ ++import os ++import resource ++import sys ++ ++from nova.openstack.common import processutils ++from nova import test ++ ++ ++class PrlimitTestCase(test.TestCase): ++ # Simply program that does nothing and returns an exit code 0. ++ # Use Python to be portable. ++ SIMPLE_PROGRAM = [sys.executable, '-c', 'pass'] ++ ++ def soft_limit(self, res, substract, default_limit): ++ # Create a new soft limit for a resource, lower than the current ++ # soft limit. ++ soft_limit, hard_limit = resource.getrlimit(res) ++ if soft_limit < 0: ++ soft_limit = default_limit ++ else: ++ soft_limit -= substract ++ return soft_limit ++ ++ def memory_limit(self, res): ++ # Substract 1 kB just to get a different limit. Don't substract too ++ # much to avoid memory allocation issues. ++ # ++ # Use 1 GB by default. Limit high enough to be able to load shared ++ # libraries. Limit low enough to be work on 32-bit platforms. ++ return self.soft_limit(res, 1024, 1024 ** 3) ++ ++ def limit_address_space(self): ++ max_memory = self.memory_limit(resource.RLIMIT_AS) ++ return processutils.ProcessLimits(address_space=max_memory) ++ ++ def test_simple(self): ++ # Simple test running a program (/bin/true) with no parameter ++ prlimit = self.limit_address_space() ++ stdout, stderr = processutils.execute(*self.SIMPLE_PROGRAM, ++ prlimit=prlimit) ++ self.assertEqual(stdout.rstrip(), '') ++ self.assertEqual(stderr.rstrip(), '') ++ ++ def check_limit(self, prlimit, resource, value): ++ code = ';'.join(('import resource', ++ 'print(resource.getrlimit(resource.%s))' % resource)) ++ args = [sys.executable, '-c', code] ++ stdout, stderr = processutils.execute(*args, prlimit=prlimit) ++ expected = (value, value) ++ self.assertEqual(stdout.rstrip(), str(expected)) ++ ++ def test_address_space(self): ++ prlimit = self.limit_address_space() ++ self.check_limit(prlimit, 'RLIMIT_AS', prlimit.address_space) ++ ++ def test_resident_set_size(self): ++ max_memory = self.memory_limit(resource.RLIMIT_RSS) ++ prlimit = processutils.ProcessLimits(resident_set_size=max_memory) ++ self.check_limit(prlimit, 'RLIMIT_RSS', max_memory) ++ ++ def test_number_files(self): ++ nfiles = self.soft_limit(resource.RLIMIT_NOFILE, 1, 1024) ++ prlimit = processutils.ProcessLimits(number_files=nfiles) ++ self.check_limit(prlimit, 'RLIMIT_NOFILE', nfiles) ++ ++ def test_unsupported_prlimit(self): ++ self.assertRaises(ValueError, processutils.ProcessLimits, xxx=33) ++ ++ def test_execv_error(self): ++ prlimit = self.limit_address_space() ++ args = ['/missing_path/dont_exist/program'] ++ try: ++ processutils.execute(*args, prlimit=prlimit) ++ except processutils.ProcessExecutionError as exc: ++ self.assertEqual(exc.exit_code, 1) ++ self.assertEqual(exc.stdout, '') ++ expected = ('%s -m nova.openstack.common.prlimit: ' ++ 'failed to execute /missing_path/dont_exist/program: ' ++ % os.path.basename(sys.executable)) ++ self.assertIn(expected, exc.stderr) ++ else: ++ self.fail("ProcessExecutionError not raised") ++ ++ def test_setrlimit_error(self): ++ prlimit = self.limit_address_space() ++ ++ # trying to set a limit higher than the current hard limit ++ # with setrlimit() should fail. ++ higher_limit = prlimit.address_space + 1024 ++ ++ args = [sys.executable, '-m', 'nova.openstack.common.prlimit', ++ '--as=%s' % higher_limit, ++ '--'] ++ args.extend(self.SIMPLE_PROGRAM) ++ try: ++ processutils.execute(*args, prlimit=prlimit) ++ except processutils.ProcessExecutionError as exc: ++ self.assertEqual(exc.exit_code, 1) ++ self.assertEqual(exc.stdout, '') ++ expected = ('%s -m nova.openstack.common.prlimit: ' ++ 'failed to set the AS resource limit: ' ++ % os.path.basename(sys.executable)) ++ self.assertIn(expected, exc.stderr) ++ else: ++ self.fail("ProcessExecutionError not raised") diff -Nru nova-2014.1.5/debian/patches/CVE-2015-5162-2.patch nova-2014.1.5/debian/patches/CVE-2015-5162-2.patch --- nova-2014.1.5/debian/patches/CVE-2015-5162-2.patch 1970-01-01 00:00:00.000000000 +0000 +++ nova-2014.1.5/debian/patches/CVE-2015-5162-2.patch 2017-09-13 12:33:08.000000000 +0000 @@ -0,0 +1,201 @@ +From b64a7d38673b48c2c12f9fcfd249a3f57c02e8f4 Mon Sep 17 00:00:00 2001 +From: Victor Stinner +Date: Fri, 14 Oct 2016 16:38:36 +0200 +Subject: [PATCH] processutils: add support for missing process limits + +The original commit adding support for process limits only wired +up address space, max files and resident set size limits. This +is not sufficient to enable nova to protect qemu-img commands +against malicious images. + +This commit adds support for the remaining limits supported +by python: core file size, cpu time, data size, file size, +locked memory size, max processes and stack size. + +Upstream-Liberty: https://review.openstack.org/#/c/332222/ +Resolves: rhbz#1382549 + +Related-bug: #1449062 +Change-Id: I164c4b35e1357a0f80ed7fe00a7ae8f49df92e31 +(cherry picked from commit 8af826953d1ad2cab2ecf360e0c794de70a367c3) +(cherry picked from commit 5f417f8e9656e097070036daced26d8b0f3728c3) +(cherry picked from commit d65d931da8490576f0abf30f124ca3a032b481c7) +Reviewed-on: https://code.engineering.redhat.com/gerrit/87170 +Tested-by: RHOS Jenkins +Reviewed-by: Kashyap Chamarthy +Tested-by: Victor Stinner +--- + nova/openstack/common/prlimit.py | 21 +++++++++++++ + nova/openstack/common/processutils.py | 38 +++++++++++++++++------- + nova/tests/openstack_common/test_processutils.py | 37 ++++++++++++++++++++++- + 3 files changed, 85 insertions(+), 11 deletions(-) + +diff --git a/nova/openstack/common/prlimit.py b/nova/openstack/common/prlimit.py +index fa1ef68..a3dc8a7 100644 +--- a/nova/openstack/common/prlimit.py ++++ b/nova/openstack/common/prlimit.py +@@ -26,8 +26,15 @@ USAGE_PROGRAM = ('%s -m nova.openstack.common.prlimit' + RESOURCES = ( + # argparse argument => resource + ('as', resource.RLIMIT_AS), ++ ('core', resource.RLIMIT_CORE), ++ ('cpu', resource.RLIMIT_CPU), ++ ('data', resource.RLIMIT_DATA), ++ ('fsize', resource.RLIMIT_FSIZE), ++ ('memlock', resource.RLIMIT_MEMLOCK), + ('nofile', resource.RLIMIT_NOFILE), ++ ('nproc', resource.RLIMIT_NPROC), + ('rss', resource.RLIMIT_RSS), ++ ('stack', resource.RLIMIT_STACK), + ) + + +@@ -35,10 +42,24 @@ def parse_args(): + parser = argparse.ArgumentParser(description='prlimit', prog=USAGE_PROGRAM) + parser.add_argument('--as', type=int, + help='Address space limit in bytes') ++ parser.add_argument('--core', type=int, ++ help='Core file size limit in bytes') ++ parser.add_argument('--cpu', type=int, ++ help='CPU time limit in seconds') ++ parser.add_argument('--data', type=int, ++ help='Data size limit in bytes') ++ parser.add_argument('--fsize', type=int, ++ help='File size limit in bytes') ++ parser.add_argument('--memlock', type=int, ++ help='Locked memory limit in bytes') + parser.add_argument('--nofile', type=int, + help='Maximum number of open files') ++ parser.add_argument('--nproc', type=int, ++ help='Maximum number of processes') + parser.add_argument('--rss', type=int, + help='Maximum Resident Set Size (RSS) in bytes') ++ parser.add_argument('--stack', type=int, ++ help='Stack size limit in bytes') + parser.add_argument('program', + help='Program (absolute path)') + parser.add_argument('program_args', metavar="arg", nargs='...', +diff --git a/nova/openstack/common/processutils.py b/nova/openstack/common/processutils.py +index 4a31171..17508d7 100644 +--- a/nova/openstack/common/processutils.py ++++ b/nova/openstack/common/processutils.py +@@ -88,16 +88,36 @@ class ProcessLimits(object): + Attributes: + + * address_space: Address space limit in bytes +- * number_files: Maximum number of open files. ++ * core_file_size: Core file size limit in bytes ++ * cpu_time: CPU time limit in seconds ++ * data_size: Data size limit in bytes ++ * file_size: File size limit in bytes ++ * memory_locked: Locked memory limit in bytes ++ * number_files: Maximum number of open files ++ * number_processes: Maximum number of processes + * resident_set_size: Maximum Resident Set Size (RSS) in bytes ++ * stack_size: Stack size limit in bytes + + This object can be used for the *prlimit* parameter of :func:`execute`. + """ + ++ _LIMITS = { ++ "address_space": "--as", ++ "core_file_size": "--core", ++ "cpu_time": "--cpu", ++ "data_size": "--data", ++ "file_size": "--fsize", ++ "memory_locked": "--memlock", ++ "number_files": "--nofile", ++ "number_processes": "--nproc", ++ "resident_set_size": "--rss", ++ "stack_size": "--stack", ++ } ++ + def __init__(self, **kw): +- self.address_space = kw.pop('address_space', None) +- self.number_files = kw.pop('number_files', None) +- self.resident_set_size = kw.pop('resident_set_size', None) ++ for limit in self._LIMITS.keys(): ++ setattr(self, limit, kw.pop(limit, None)) ++ + if kw: + raise ValueError("invalid limits: %s" + % ', '.join(sorted(kw.keys()))) +@@ -105,12 +125,10 @@ class ProcessLimits(object): + def prlimit_args(self): + """Create a list of arguments for the prlimit command line.""" + args = [] +- if self.address_space: +- args.append('--as=%s' % self.address_space) +- if self.number_files: +- args.append('--nofile=%s' % self.number_files) +- if self.resident_set_size: +- args.append('--rss=%s' % self.resident_set_size) ++ for limit in self._LIMITS.keys(): ++ val = getattr(self, limit) ++ if val is not None: ++ args.append("%s=%s" % (self._LIMITS[limit], val)) + return args + + +diff --git a/nova/tests/openstack_common/test_processutils.py b/nova/tests/openstack_common/test_processutils.py +index 4822539..a10f68c 100644 +--- a/nova/tests/openstack_common/test_processutils.py ++++ b/nova/tests/openstack_common/test_processutils.py +@@ -32,7 +32,7 @@ class PrlimitTestCase(test.TestCase): + # Create a new soft limit for a resource, lower than the current + # soft limit. + soft_limit, hard_limit = resource.getrlimit(res) +- if soft_limit < 0: ++ if soft_limit <= 0: + soft_limit = default_limit + else: + soft_limit -= substract +@@ -70,6 +70,31 @@ class PrlimitTestCase(test.TestCase): + prlimit = self.limit_address_space() + self.check_limit(prlimit, 'RLIMIT_AS', prlimit.address_space) + ++ def test_core_size(self): ++ size = self.soft_limit(resource.RLIMIT_CORE, 1, 1024) ++ prlimit = processutils.ProcessLimits(core_file_size=size) ++ self.check_limit(prlimit, 'RLIMIT_CORE', prlimit.core_file_size) ++ ++ def test_cpu_time(self): ++ time = self.soft_limit(resource.RLIMIT_CPU, 1, 1024) ++ prlimit = processutils.ProcessLimits(cpu_time=time) ++ self.check_limit(prlimit, 'RLIMIT_CPU', prlimit.cpu_time) ++ ++ def test_data_size(self): ++ max_memory = self.memory_limit(resource.RLIMIT_DATA) ++ prlimit = processutils.ProcessLimits(data_size=max_memory) ++ self.check_limit(prlimit, 'RLIMIT_DATA', max_memory) ++ ++ def test_file_size(self): ++ size = self.soft_limit(resource.RLIMIT_FSIZE, 1, 1024) ++ prlimit = processutils.ProcessLimits(file_size=size) ++ self.check_limit(prlimit, 'RLIMIT_FSIZE', prlimit.file_size) ++ ++ def test_memory_locked(self): ++ max_memory = self.memory_limit(resource.RLIMIT_MEMLOCK) ++ prlimit = processutils.ProcessLimits(memory_locked=max_memory) ++ self.check_limit(prlimit, 'RLIMIT_MEMLOCK', max_memory) ++ + def test_resident_set_size(self): + max_memory = self.memory_limit(resource.RLIMIT_RSS) + prlimit = processutils.ProcessLimits(resident_set_size=max_memory) +@@ -80,6 +105,16 @@ class PrlimitTestCase(test.TestCase): + prlimit = processutils.ProcessLimits(number_files=nfiles) + self.check_limit(prlimit, 'RLIMIT_NOFILE', nfiles) + ++ def test_number_processes(self): ++ nprocs = self.soft_limit(resource.RLIMIT_NPROC, 1, 65535) ++ prlimit = processutils.ProcessLimits(number_processes=nprocs) ++ self.check_limit(prlimit, 'RLIMIT_NPROC', nprocs) ++ ++ def test_stack_size(self): ++ max_memory = self.memory_limit(resource.RLIMIT_STACK) ++ prlimit = processutils.ProcessLimits(stack_size=max_memory) ++ self.check_limit(prlimit, 'RLIMIT_STACK', max_memory) ++ + def test_unsupported_prlimit(self): + self.assertRaises(ValueError, processutils.ProcessLimits, xxx=33) + diff -Nru nova-2014.1.5/debian/patches/CVE-2015-5162-3.patch nova-2014.1.5/debian/patches/CVE-2015-5162-3.patch --- nova-2014.1.5/debian/patches/CVE-2015-5162-3.patch 1970-01-01 00:00:00.000000000 +0000 +++ nova-2014.1.5/debian/patches/CVE-2015-5162-3.patch 2017-09-13 15:46:01.000000000 +0000 @@ -0,0 +1,245 @@ +Backport of: + +From 6bc37dcceca823998068167b49aec6def3112397 Mon Sep 17 00:00:00 2001 +From: "Daniel P. Berrange" +Date: Mon, 18 Apr 2016 16:32:19 +0000 +Subject: [PATCH] virt: set address space & CPU time limits when running + qemu-img + +This uses the new 'prlimit' parameter for oslo.concurrency execute +method, to set an address space limit of 1GB and CPU time limit +of 2 seconds, when running qemu-img. + +This is a re-implementation of the previously reverted commit + +commit da217205f53f9a38a573fb151898fbbeae41021d +Author: Tristan Cacqueray +Date: Wed Aug 5 17:17:04 2015 +0000 + + virt: Use preexec_fn to ulimit qemu-img info call + +NOTE (kchamart) [stable/liberty]: Add a check for the presence of +'ProcessLimits' attribute (which is only present in +oslo.concurrency>=2.6.1; and a conditional check for 'prlimit' parameter +in qemu_img_info() method. + +Upstream discussion[1][2] that led to merging this patch to +stable/liberty branch. + +[1] http://lists.openstack.org/pipermail/openstack-dev/2016-September/104091.html +[2] http://lists.openstack.org/pipermail/openstack-dev/2016-September/104303.html + +Closes-Bug: #1449062 +Change-Id: I135b5242af1bfdcb0ea09a6fcda21fc03a6fbe7d +(cherry picked from commit 068d851561addfefb2b812d91dc2011077cb6e1d) +--- + nova/tests/unit/virt/libvirt/test_driver.py | 7 ++++-- + nova/tests/unit/virt/libvirt/test_utils.py | 27 ++++++++++++++-------- + nova/virt/images.py | 16 ++++++++++++- + .../apply-limits-to-qemu-img-8813f7a333ebdf69.yaml | 8 +++++++ + 4 files changed, 46 insertions(+), 12 deletions(-) + create mode 100644 releasenotes/notes/apply-limits-to-qemu-img-8813f7a333ebdf69.yaml + +Index: nova-2014.1.5/nova/tests/virt/libvirt/test_libvirt.py +=================================================================== +--- nova-2014.1.5.orig/nova/tests/virt/libvirt/test_libvirt.py 2017-09-13 11:45:23.529581583 -0400 ++++ nova-2014.1.5/nova/tests/virt/libvirt/test_libvirt.py 2017-09-13 11:45:23.529581583 -0400 +@@ -4476,7 +4476,8 @@ class LibvirtConnTestCase(test.TestCase) + + self.mox.StubOutWithMock(utils, "execute") + utils.execute('env', 'LC_ALL=C', 'LANG=C', 'qemu-img', 'info', +- '/test/disk.local').AndReturn((ret, '')) ++ '/test/disk.local', prlimit=images.QEMU_IMG_LIMITS, ++ ).AndReturn((ret, '')) + + self.mox.ReplayAll() + conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) +@@ -4588,7 +4589,8 @@ class LibvirtConnTestCase(test.TestCase) + + self.mox.StubOutWithMock(utils, "execute") + utils.execute('env', 'LC_ALL=C', 'LANG=C', 'qemu-img', 'info', +- '/test/disk.local').AndReturn((ret, '')) ++ '/test/disk.local', prlimit=images.QEMU_IMG_LIMITS, ++ ).AndReturn((ret, '')) + + self.mox.ReplayAll() + conn_info = {'driver_volume_type': 'fake'} +@@ -8283,7 +8285,8 @@ class LibvirtUtilsTestCase(test.TestCase + rval = ('', '') + os.path.exists('/some/path').AndReturn(True) + utils.execute('env', 'LC_ALL=C', 'LANG=C', +- 'qemu-img', 'info', '/some/path').AndReturn(rval) ++ 'qemu-img', 'info', '/some/path', ++ prlimit=images.QEMU_IMG_LIMITS).AndReturn(rval) + utils.execute('qemu-img', 'create', '-f', 'qcow2', + '-o', 'backing_file=/some/path', + '/the/new/cow') +@@ -8320,7 +8323,7 @@ class LibvirtUtilsTestCase(test.TestCase + self.mox.StubOutWithMock(utils, 'execute') + os.path.exists('/some/path').AndReturn(True) + utils.execute('env', 'LC_ALL=C', 'LANG=C', 'qemu-img', 'info', +- '/some/path').AndReturn(('''image: 00000001 ++ '/some/path', prlimit=images.QEMU_IMG_LIMITS).AndReturn(('''image: 00000001 + file format: raw + virtual size: 4.4M (4592640 bytes) + disk size: 4.4M''', '')) +Index: nova-2014.1.5/nova/virt/images.py +=================================================================== +--- nova-2014.1.5.orig/nova/virt/images.py 2017-09-13 11:45:23.529581583 -0400 ++++ nova-2014.1.5/nova/virt/images.py 2017-09-13 11:45:23.529581583 -0400 +@@ -29,6 +29,8 @@ from nova.openstack.common import fileut + from nova.openstack.common.gettextutils import _ + from nova.openstack.common import imageutils + from nova.openstack.common import log as logging ++from nova.openstack.common import units ++from nova.openstack.common import processutils + from nova import utils + + LOG = logging.getLogger(__name__) +@@ -41,6 +43,16 @@ image_opts = [ + + CONF = cfg.CONF + CONF.register_opts(image_opts) ++QEMU_IMG_LIMITS = None ++ ++try: ++ QEMU_IMG_LIMITS = processutils.ProcessLimits( ++ cpu_time=2, ++ address_space=1 * units.Gi) ++except Exception: ++ LOG.error('Please upgrade to oslo.concurrency version ' ++ '2.6.1 -- this version has fixes for the ' ++ 'vulnerability CVE-2015-5162.') + + + def qemu_img_info(path): +@@ -50,8 +62,11 @@ def qemu_img_info(path): + if not os.path.exists(path) and CONF.libvirt.images_type != 'rbd': + return imageutils.QemuImgInfo() + +- out, err = utils.execute('env', 'LC_ALL=C', 'LANG=C', +- 'qemu-img', 'info', path) ++ cmd = ('env', 'LC_ALL=C', 'LANG=C', 'qemu-img', 'info', path) ++ if QEMU_IMG_LIMITS is not None: ++ out, err = utils.execute(*cmd, prlimit=QEMU_IMG_LIMITS) ++ else: ++ out, err = utils.execute(*cmd) + return imageutils.QemuImgInfo(out) + + +Index: nova-2014.1.5/nova/tests/virt/libvirt/test_image_utils.py +=================================================================== +--- nova-2014.1.5.orig/nova/tests/virt/libvirt/test_image_utils.py 2017-09-13 11:45:23.529581583 -0400 ++++ nova-2014.1.5/nova/tests/virt/libvirt/test_image_utils.py 2017-09-13 11:45:23.529581583 -0400 +@@ -50,7 +50,8 @@ disk size: 96K + self.mox.StubOutWithMock(utils, 'execute') + os.path.exists(path).AndReturn(True) + utils.execute('env', 'LC_ALL=C', 'LANG=C', +- 'qemu-img', 'info', path).AndReturn((output, '')) ++ 'qemu-img', 'info', path, ++ prlimit=images.QEMU_IMG_LIMITS).AndReturn((output, '')) + self.mox.ReplayAll() + d_type = libvirt_utils.get_disk_type(path) + self.assertEqual(f, d_type) +@@ -71,7 +72,8 @@ disk size: 96K + self.mox.StubOutWithMock(utils, 'execute') + os.path.exists(path).AndReturn(True) + utils.execute('env', 'LC_ALL=C', 'LANG=C', +- 'qemu-img', 'info', path).AndReturn((output, '')) ++ 'qemu-img', 'info', path, ++ prlimit=images.QEMU_IMG_LIMITS).AndReturn((output, '')) + self.mox.ReplayAll() + d_backing = libvirt_utils.get_disk_backing_file(path) + self.assertIsNone(d_backing) +@@ -97,7 +99,8 @@ disk size: 96K + self.mox.StubOutWithMock(utils, 'execute') + os.path.exists(path).AndReturn(True) + utils.execute('env', 'LC_ALL=C', 'LANG=C', +- 'qemu-img', 'info', path).AndReturn((output, '')) ++ 'qemu-img', 'info', path, ++ prlimit=images.QEMU_IMG_LIMITS).AndReturn((output, '')) + self.mox.ReplayAll() + d_size = libvirt_utils.get_disk_size(path) + self.assertEqual(i, d_size) +@@ -111,7 +114,8 @@ disk size: 96K + self.mox.StubOutWithMock(utils, 'execute') + os.path.exists(path).AndReturn(True) + utils.execute('env', 'LC_ALL=C', 'LANG=C', +- 'qemu-img', 'info', path).AndReturn((output, '')) ++ 'qemu-img', 'info', path, ++ prlimit=images.QEMU_IMG_LIMITS).AndReturn((output, '')) + self.mox.ReplayAll() + d_size = libvirt_utils.get_disk_size(path) + self.assertEqual(i, d_size) +@@ -130,7 +134,8 @@ blah BLAH: bb + self.mox.StubOutWithMock(utils, 'execute') + os.path.exists(path).AndReturn(True) + utils.execute('env', 'LC_ALL=C', 'LANG=C', +- 'qemu-img', 'info', path).AndReturn((example_output, '')) ++ 'qemu-img', 'info', path, ++ prlimit=images.QEMU_IMG_LIMITS).AndReturn((example_output, '')) + self.mox.ReplayAll() + image_info = images.qemu_img_info(path) + self.assertEqual('disk.config', image_info.image) +@@ -152,7 +157,8 @@ backing file: /var/lib/nova/a328c7998805 + self.mox.StubOutWithMock(utils, 'execute') + os.path.exists(path).AndReturn(True) + utils.execute('env', 'LC_ALL=C', 'LANG=C', +- 'qemu-img', 'info', path).AndReturn((example_output, '')) ++ 'qemu-img', 'info', path, ++ prlimit=images.QEMU_IMG_LIMITS).AndReturn((example_output, '')) + self.mox.ReplayAll() + image_info = images.qemu_img_info(path) + self.assertEqual('disk.config', image_info.image) +@@ -179,7 +185,8 @@ backing file: /var/lib/nova/a328c7998805 + self.mox.StubOutWithMock(utils, 'execute') + os.path.exists(path).AndReturn(True) + utils.execute('env', 'LC_ALL=C', 'LANG=C', +- 'qemu-img', 'info', path).AndReturn((example_output, '')) ++ 'qemu-img', 'info', path, ++ prlimit=images.QEMU_IMG_LIMITS).AndReturn((example_output, '')) + self.mox.ReplayAll() + image_info = images.qemu_img_info(path) + self.assertEqual('disk.config', image_info.image) +@@ -207,7 +214,8 @@ junk stuff: bbb + self.mox.StubOutWithMock(utils, 'execute') + os.path.exists(path).AndReturn(True) + utils.execute('env', 'LC_ALL=C', 'LANG=C', +- 'qemu-img', 'info', path).AndReturn((example_output, '')) ++ 'qemu-img', 'info', path, ++ prlimit=images.QEMU_IMG_LIMITS).AndReturn((example_output, '')) + self.mox.ReplayAll() + image_info = images.qemu_img_info(path) + self.assertEqual('disk.config', image_info.image) +@@ -231,7 +239,8 @@ ID TAG VM SIZE + self.mox.StubOutWithMock(utils, 'execute') + os.path.exists(path).AndReturn(True) + utils.execute('env', 'LC_ALL=C', 'LANG=C', +- 'qemu-img', 'info', path).AndReturn((example_output, '')) ++ 'qemu-img', 'info', path, ++ prlimit=images.QEMU_IMG_LIMITS).AndReturn((example_output, '')) + self.mox.ReplayAll() + image_info = images.qemu_img_info(path) + self.assertEqual('disk.config', image_info.image) +Index: nova-2014.1.5/nova/tests/virt/libvirt/test_libvirt_utils.py +=================================================================== +--- nova-2014.1.5.orig/nova/tests/virt/libvirt/test_libvirt_utils.py 2017-09-13 11:45:23.529581583 -0400 ++++ nova-2014.1.5/nova/tests/virt/libvirt/test_libvirt_utils.py 2017-09-13 11:45:54.838008056 -0400 +@@ -22,6 +22,7 @@ from oslo.config import cfg + from nova.openstack.common import processutils + from nova import test + from nova import utils ++from nova.virt import images + from nova.virt.libvirt import utils as libvirt_utils + + CONF = cfg.CONF +@@ -41,7 +42,8 @@ blah BLAH: bb + self.mox.StubOutWithMock(utils, 'execute') + os.path.exists(path).AndReturn(True) + utils.execute('env', 'LC_ALL=C', 'LANG=C', +- 'qemu-img', 'info', path).AndReturn((example_output, '')) ++ 'qemu-img', 'info', path, ++ prlimit=images.QEMU_IMG_LIMITS).AndReturn((example_output, '')) + self.mox.ReplayAll() + disk_type = libvirt_utils.get_disk_type(path) + self.assertEqual(disk_type, 'raw') diff -Nru nova-2014.1.5/debian/patches/CVE-2015-7548-1.patch nova-2014.1.5/debian/patches/CVE-2015-7548-1.patch --- nova-2014.1.5/debian/patches/CVE-2015-7548-1.patch 1970-01-01 00:00:00.000000000 +0000 +++ nova-2014.1.5/debian/patches/CVE-2015-7548-1.patch 2017-09-13 17:07:44.000000000 +0000 @@ -0,0 +1,277 @@ +Backport of: + +From 2cd7f611200021c1089f3258a16014be18eb7da9 Mon Sep 17 00:00:00 2001 +From: Matthew Booth +Date: Wed, 9 Dec 2015 15:36:32 +0000 +Subject: [PATCH] Fix format detection in libvirt snapshot + +The libvirt driver was using automatic format detection during +snapshot for disks stored on the local filesystem. This opened an +exploit if nova was configured to use local file storage, and +additionally to store those files in raw format by specifying +use_cow_images = False in nova.conf. An authenticated user could write +a qcow2 header to their guest image with a backing file on the host. +libvirt.utils.get_disk_type() would then misdetect the type of this +image as qcow2 and pass this to the Qcow2 image backend, whose +snapshot_extract method interprets the image as qcow2 and writes the +backing file to glance. The authenticated user can then download the +host file from glance. + +This patch makes 2 principal changes. libvirt.utils.get_disk_type, +which ought to be removed entirely as soon as possible, is updated to +no longer do format detection if the format can't be determined from +the path. Its name is changed to get_disk_type_from_path to reflect +its actual function. + +libvirt.utils.find_disk is updated to return both the path and format +of the root disk, rather than just the path. This is the most reliable +source of this information, as it reflects the actual format in use. +The previous format detection function of get_disk_type is replaced by +the format taken from libvirt. + +We replace a call to get_disk_type in _rebase_with_qemu_img with an +explicit call to qemu_img_info, as the other behaviour of +get_disk_type was not relevant in this context. qemu_img_info is safe +from the backing file exploit when called on a file known to be a +qcow2 image. As the file in this context is a volume snapshot, this is +a safe use. + +(cherry picked from commit f228834204fd8bdcf62f67e00c49edf63662a7dd) + + Conflicts: + nova/tests/virt/libvirt/fake_libvirt_utils.py + nova/tests/virt/libvirt/test_image_utils.py + nova/virt/libvirt/driver.py + +Resolves: rhbz 1295730 +Resolves: rhbz 1295729 +Partial-Bug: #1524274 +Change-Id: I94c1c0d26215c061f71c3f95e1a6bf3a58fa19ea +Reviewed-on: https://code.engineering.redhat.com/gerrit/64913 +Tested-by: RHOS Jenkins +Reviewed-by: Matthew Booth +--- + nova/tests/virt/libvirt/fake_libvirt_utils.py | 11 ++++++++-- + nova/tests/virt/libvirt/test_image_utils.py | 29 ++++++--------------------- + nova/tests/virt/libvirt/test_libvirt_utils.py | 19 +++--------------- + nova/virt/libvirt/driver.py | 25 +++++++++++++++++------ + nova/virt/libvirt/utils.py | 26 +++++++++++++++++++----- + 5 files changed, 58 insertions(+), 52 deletions(-) + +Index: nova-2014.1.5/nova/tests/virt/libvirt/fake_libvirt_utils.py +=================================================================== +--- nova-2014.1.5.orig/nova/tests/virt/libvirt/fake_libvirt_utils.py 2017-09-13 13:05:46.310514860 -0400 ++++ nova-2014.1.5/nova/tests/virt/libvirt/fake_libvirt_utils.py 2017-09-13 13:05:46.306514807 -0400 +@@ -94,7 +94,9 @@ def get_disk_backing_file(path): + return disk_backing_files.get(path, None) + + +-def get_disk_type(path): ++def get_disk_type_from_path(path): ++ if disk_type in ('raw', 'qcow2'): ++ return None + return disk_type + + +@@ -156,7 +158,12 @@ def file_open(path, mode=None): + + + def find_disk(virt_dom): +- return "filename" ++ if disk_type == 'lvm': ++ return ("/dev/nova-vg/lv", "raw") ++ elif disk_type in ['raw', 'qcow2']: ++ return ("filename", disk_type) ++ else: ++ return ("unknown_type_disk", None) + + + def load_file(path): +Index: nova-2014.1.5/nova/tests/virt/libvirt/test_image_utils.py +=================================================================== +--- nova-2014.1.5.orig/nova/tests/virt/libvirt/test_image_utils.py 2017-09-13 13:05:46.310514860 -0400 ++++ nova-2014.1.5/nova/tests/virt/libvirt/test_image_utils.py 2017-09-13 13:06:58.099472963 -0400 +@@ -22,40 +22,21 @@ from nova.virt.libvirt import utils as l + + + class ImageUtilsTestCase(test.NoDBTestCase): +- def test_disk_type(self): ++ def test_disk_type_from_path(self): + # Seems like lvm detection + # if its in /dev ?? + for p in ['/dev/b', '/dev/blah/blah']: +- d_type = libvirt_utils.get_disk_type(p) ++ d_type = libvirt_utils.get_disk_type_from_path(p) + self.assertEqual('lvm', d_type) + + # Try rbd detection +- d_type = libvirt_utils.get_disk_type('rbd:pool/instance') ++ d_type = libvirt_utils.get_disk_type_from_path('rbd:pool/instance') + self.assertEqual('rbd', d_type) + + # Try the other types +- template_output = """image: %(path)s +-file format: %(format)s +-virtual size: 64M (67108864 bytes) +-cluster_size: 65536 +-disk size: 96K +-""" + path = '/myhome/disk.config' +- for f in ['raw', 'qcow2']: +- output = template_output % ({ +- 'format': f, +- 'path': path, +- }) +- self.mox.StubOutWithMock(os.path, 'exists') +- self.mox.StubOutWithMock(utils, 'execute') +- os.path.exists(path).AndReturn(True) +- utils.execute('env', 'LC_ALL=C', 'LANG=C', +- 'qemu-img', 'info', path, +- prlimit=images.QEMU_IMG_LIMITS).AndReturn((output, '')) +- self.mox.ReplayAll() +- d_type = libvirt_utils.get_disk_type(path) +- self.assertEqual(f, d_type) +- self.mox.UnsetStubs() ++ d_type = libvirt_utils.get_disk_type_from_path(path) ++ self.assertIsNone(d_type) + + def test_disk_backing(self): + path = '/myhome/disk.config' +Index: nova-2014.1.5/nova/tests/virt/libvirt/test_libvirt_utils.py +=================================================================== +--- nova-2014.1.5.orig/nova/tests/virt/libvirt/test_libvirt_utils.py 2017-09-13 13:05:46.310514860 -0400 ++++ nova-2014.1.5/nova/tests/virt/libvirt/test_libvirt_utils.py 2017-09-13 13:07:24.239821827 -0400 +@@ -29,24 +29,10 @@ CONF = cfg.CONF + + + class LibvirtUtilsTestCase(test.NoDBTestCase): +- def test_get_disk_type(self): ++ def test_get_disk_type_from_path(self): + path = "disk.config" +- example_output = """image: disk.config +-file format: raw +-virtual size: 64M (67108864 bytes) +-cluster_size: 65536 +-disk size: 96K +-blah BLAH: bb +-""" +- self.mox.StubOutWithMock(os.path, 'exists') +- self.mox.StubOutWithMock(utils, 'execute') +- os.path.exists(path).AndReturn(True) +- utils.execute('env', 'LC_ALL=C', 'LANG=C', +- 'qemu-img', 'info', path, +- prlimit=images.QEMU_IMG_LIMITS).AndReturn((example_output, '')) +- self.mox.ReplayAll() +- disk_type = libvirt_utils.get_disk_type(path) +- self.assertEqual(disk_type, 'raw') ++ disk_type = libvirt_utils.get_disk_type_from_path(path) ++ self.assertIsNone(disk_type) + + def test_logical_volume_size(self): + executes = [] +Index: nova-2014.1.5/nova/virt/libvirt/driver.py +=================================================================== +--- nova-2014.1.5.orig/nova/virt/libvirt/driver.py 2017-09-13 13:05:46.310514860 -0400 ++++ nova-2014.1.5/nova/virt/libvirt/driver.py 2017-09-13 13:05:46.306514807 -0400 +@@ -1505,10 +1505,23 @@ class LibvirtDriver(driver.ComputeDriver + snapshot_image_service, snapshot_image_id = _image_service + snapshot = snapshot_image_service.show(context, snapshot_image_id) + +- disk_path = libvirt_utils.find_disk(virt_dom) +- source_format = libvirt_utils.get_disk_type(disk_path) ++ # source_format is an on-disk format ++ # source_type is a backend type ++ disk_path, source_format = libvirt_utils.find_disk(virt_dom) ++ source_type = libvirt_utils.get_disk_type_from_path(disk_path) ++ ++ # We won't have source_type for raw or qcow2 disks, because we can't ++ # determine that from the path. We should have it from the libvirt ++ # xml, though. ++ if source_type is None: ++ source_type = source_format ++ # For lxc instances we won't have it either from libvirt xml ++ # (because we just gave libvirt the mounted filesystem), or the path, ++ # so source_type is still going to be None. In this case, ++ # snapshot_backend is going to default to CONF.libvirt.images_type ++ # below, which is still safe. + +- image_format = CONF.libvirt.snapshot_image_format or source_format ++ image_format = CONF.libvirt.snapshot_image_format or source_type + + # NOTE(bfilippov): save lvm and rbd as raw + if image_format == 'lvm' or image_format == 'rbd': +@@ -1530,7 +1543,7 @@ class LibvirtDriver(driver.ComputeDriver + if self.has_min_version(MIN_LIBVIRT_LIVESNAPSHOT_VERSION, + MIN_QEMU_LIVESNAPSHOT_VERSION, + REQ_HYPERVISOR_LIVESNAPSHOT) \ +- and not source_format == "lvm" and not source_format == 'rbd': ++ and not source_type == "lvm" and not source_format == 'rbd': + live_snapshot = True + # Abort is an idempotent operation, so make sure any block + # jobs which may have failed are ended. This operation also +@@ -1561,7 +1574,7 @@ class LibvirtDriver(driver.ComputeDriver + virt_dom.managedSave(0) + + snapshot_backend = self.image_backend.snapshot(disk_path, +- image_type=source_format) ++ image_type=source_type) + + if live_snapshot: + LOG.info(_("Beginning live snapshot process"), +Index: nova-2014.1.5/nova/virt/libvirt/utils.py +=================================================================== +--- nova-2014.1.5.orig/nova/virt/libvirt/utils.py 2017-09-13 13:05:46.310514860 -0400 ++++ nova-2014.1.5/nova/virt/libvirt/utils.py 2017-09-13 13:05:46.310514860 -0400 +@@ -604,13 +604,20 @@ def find_disk(virt_dom): + """ + xml_desc = virt_dom.XMLDesc(0) + domain = etree.fromstring(xml_desc) ++ driver = None + if CONF.libvirt.virt_type == 'lxc': +- source = domain.find('devices/filesystem/source') ++ filesystem = domain.find('devices/filesystem') ++ driver = filesystem.find('driver') ++ ++ source = filesystem.find('source') + disk_path = source.get('dir') + disk_path = disk_path[0:disk_path.rfind('rootfs')] + disk_path = os.path.join(disk_path, 'disk') + else: +- source = domain.find('devices/disk/source') ++ disk = domain.find('devices/disk') ++ driver = disk.find('driver') ++ ++ source = disk.find('source') + disk_path = source.get('file') or source.get('dev') + if not disk_path and CONF.libvirt.images_type == 'rbd': + disk_path = source.get('name') +@@ -621,17 +628,26 @@ def find_disk(virt_dom): + raise RuntimeError(_("Can't retrieve root device path " + "from instance libvirt configuration")) + +- return disk_path ++ if driver is not None: ++ format = driver.get('type') ++ # This is a legacy quirk of libvirt/xen. Everything else should ++ # report the on-disk format in type. ++ if format == 'aio': ++ format = 'raw' ++ else: ++ format = None ++ return (disk_path, format) + + +-def get_disk_type(path): ++def get_disk_type_from_path(path): + """Retrieve disk type (raw, qcow2, lvm) for given file.""" + if path.startswith('/dev'): + return 'lvm' + elif path.startswith('rbd:'): + return 'rbd' + +- return images.qemu_img_info(path).file_format ++ # We can't reliably determine the type from this path ++ return None + + + def get_fs_info(path): diff -Nru nova-2014.1.5/debian/patches/CVE-2015-7548-2.patch nova-2014.1.5/debian/patches/CVE-2015-7548-2.patch --- nova-2014.1.5/debian/patches/CVE-2015-7548-2.patch 1970-01-01 00:00:00.000000000 +0000 +++ nova-2014.1.5/debian/patches/CVE-2015-7548-2.patch 2017-09-13 17:09:42.000000000 +0000 @@ -0,0 +1,170 @@ +Backport of: + +From 26138c7a1e5c14b6084a83d563d4aa8883843726 Mon Sep 17 00:00:00 2001 +From: Matthew Booth +Date: Thu, 10 Dec 2015 16:34:19 +0000 +Subject: [PATCH] Fix format conversion in libvirt snapshot + +The libvirt driver was calling images.convert_image during snapshot to +convert snapshots to the intended output format. However, this +function does not take the input format as an argument, meaning it +implicitly does format detection. This opened an exploit for setups +using raw storage on the backend, including raw on filesystem, LVM, +and RBD (Ceph). An authenticated user could write a qcow2 header to +their instance's disk which specified an arbitrary backing file on the +host. When convert_image ran during snapshot, this would then write +the contents of the backing file to glance, which is then available to +the user. If the setup uses an LVM backend this conversion runs as +root, meaning the user can exfiltrate any file on the host, including +raw disks. + +This change adds an input format to convert_image. + +(cherry picked from commit 6e0b5d760afd86d439aaf6f34d6f031afdaf208c) + + Conflicts: + nova/tests/virt/libvirt/test_libvirt.py + nova/virt/libvirt/imagebackend.py + +Resolves: rhbz 1295729 +Resolves: rhbz 1295730 +Partial-Bug: #1524274 +Change-Id: If73e73718ecd5db262ed9904091024238f98dbc0 +Reviewed-on: https://code.engineering.redhat.com/gerrit/64914 +Tested-by: RHOS Jenkins +Reviewed-by: Matthew Booth +--- + nova/tests/virt/libvirt/test_libvirt.py | 7 ++++--- + nova/virt/images.py | 26 ++++++++++++++++++++++++-- + nova/virt/libvirt/imagebackend.py | 19 ++++++++++++++----- + 3 files changed, 42 insertions(+), 10 deletions(-) + +Index: nova-2014.1.5/nova/tests/virt/libvirt/test_libvirt.py +=================================================================== +--- nova-2014.1.5.orig/nova/tests/virt/libvirt/test_libvirt.py 2017-09-13 13:09:38.757617032 -0400 ++++ nova-2014.1.5/nova/tests/virt/libvirt/test_libvirt.py 2017-09-13 13:09:38.753616979 -0400 +@@ -2829,7 +2829,7 @@ class LibvirtConnTestCase(test.TestCase) + libvirt_driver.utils.execute = self.fake_execute + self.stubs.Set(libvirt_driver.libvirt_utils, 'disk_type', 'raw') + +- def convert_image(source, dest, out_format): ++ def convert_image(source, dest, in_format, out_format): + libvirt_driver.libvirt_utils.files[dest] = '' + + self.stubs.Set(images, 'convert_image', convert_image) +@@ -2882,7 +2882,7 @@ class LibvirtConnTestCase(test.TestCase) + libvirt_driver.utils.execute = self.fake_execute + self.stubs.Set(libvirt_driver.libvirt_utils, 'disk_type', 'raw') + +- def convert_image(source, dest, out_format): ++ def convert_image(source, dest, in_format, out_format): + libvirt_driver.libvirt_utils.files[dest] = '' + + self.stubs.Set(images, 'convert_image', convert_image) +@@ -8531,7 +8531,8 @@ disk size: 4.4M''', '')) + target = 't.qcow2' + self.executes = [] + expected_commands = [('qemu-img', 'convert', '-O', 'raw', +- 't.qcow2.part', 't.qcow2.converted'), ++ 't.qcow2.part', 't.qcow2.converted', ++ '-f', 'qcow2'), + ('rm', 't.qcow2.part'), + ('mv', 't.qcow2.converted', 't.qcow2')] + images.fetch_to_raw(context, image_id, target, user_id, project_id, +Index: nova-2014.1.5/nova/virt/images.py +=================================================================== +--- nova-2014.1.5.orig/nova/virt/images.py 2017-09-13 13:09:38.757617032 -0400 ++++ nova-2014.1.5/nova/virt/images.py 2017-09-13 13:09:38.757617032 -0400 +@@ -70,9 +70,31 @@ def qemu_img_info(path): + return imageutils.QemuImgInfo(out) + + +-def convert_image(source, dest, out_format, run_as_root=False): ++def convert_image(source, dest, in_format, out_format, run_as_root=False): + """Convert image to other format.""" ++ if in_format is None: ++ raise RuntimeError("convert_image without input format is a security" ++ "risk") ++ _convert_image(source, dest, in_format, out_format, run_as_root) ++ ++ ++def convert_image_unsafe(source, dest, out_format, run_as_root=False): ++ """Convert image to other format, doing unsafe automatic input format ++ detection. Do not call this function. ++ """ ++ ++ # NOTE: there is only 1 caller of this function: ++ # imagebackend.Lvm.create_image. It is not easy to fix that without a ++ # larger refactor, so for the moment it has been manually audited and ++ # allowed to continue. Remove this function when Lvm.create_image has ++ # been fixed. ++ _convert_image(source, dest, None, out_format, run_as_root) ++ ++ ++def _convert_image(source, dest, in_format, out_format, run_as_root): + cmd = ('qemu-img', 'convert', '-O', out_format, source, dest) ++ if in_format is not None: ++ cmd = cmd + ('-f', in_format) + utils.execute(*cmd, run_as_root=run_as_root) + + +@@ -128,7 +150,7 @@ def fetch_to_raw(context, image_href, pa + staged = "%s.converted" % path + LOG.debug("%s was %s, converting to raw" % (image_href, fmt)) + with fileutils.remove_path_on_error(staged): +- convert_image(path_tmp, staged, 'raw') ++ convert_image(path_tmp, staged, fmt, 'raw') + os.unlink(path_tmp) + + data = qemu_img_info(staged) +Index: nova-2014.1.5/nova/virt/libvirt/imagebackend.py +=================================================================== +--- nova-2014.1.5.orig/nova/virt/libvirt/imagebackend.py 2017-09-13 13:09:38.757617032 -0400 ++++ nova-2014.1.5/nova/virt/libvirt/imagebackend.py 2017-09-13 13:09:38.757617032 -0400 +@@ -356,7 +356,7 @@ class Raw(Image): + self.correct_format() + + def snapshot_extract(self, target, out_format): +- images.convert_image(self.path, target, out_format) ++ images.convert_image(self.path, target, self.driver_format, out_format) + + + class Qcow2(Image): +@@ -465,7 +465,16 @@ class Lvm(Image): + size = size if resize else base_size + libvirt_utils.create_lvm_image(self.vg, self.lv, + size, sparse=self.sparse) +- images.convert_image(base, self.path, 'raw', run_as_root=True) ++ # NOTE: by calling convert_image_unsafe here we're ++ # telling qemu-img convert to do format detection on the input, ++ # because we don't know what the format is. For example, ++ # we might have downloaded a qcow2 image, or created an ++ # ephemeral filesystem locally, we just don't know here. Having ++ # audited this, all current sources have been sanity checked, ++ # either because they're locally generated, or because they have ++ # come from images.fetch_to_raw. However, this is major code smell. ++ images.convert_image_unsafe(base, self.path, self.driver_format, ++ run_as_root=True) + if resize: + disk.resize2fs(self.path, run_as_root=True) + +@@ -492,8 +501,8 @@ class Lvm(Image): + libvirt_utils.remove_logical_volumes(path) + + def snapshot_extract(self, target, out_format): +- images.convert_image(self.path, target, out_format, +- run_as_root=True) ++ images.convert_image(self.path, target, self.driver_format, ++ out_format, run_as_root=True) + + + class RBDVolumeProxy(object): +@@ -686,7 +695,7 @@ class Rbd(Image): + self._resize(self.rbd_name, size) + + def snapshot_extract(self, target, out_format): +- images.convert_image(self.path, target, out_format) ++ images.convert_image(self.path, target, 'raw', out_format) + + @staticmethod + def is_shared_block_storage(): diff -Nru nova-2014.1.5/debian/patches/CVE-2015-7548-3.patch nova-2014.1.5/debian/patches/CVE-2015-7548-3.patch --- nova-2014.1.5/debian/patches/CVE-2015-7548-3.patch 1970-01-01 00:00:00.000000000 +0000 +++ nova-2014.1.5/debian/patches/CVE-2015-7548-3.patch 2017-09-13 17:09:47.000000000 +0000 @@ -0,0 +1,165 @@ +Backport of: + +From d3573cca0764e853c1b0cead26cb65710919ee43 Mon Sep 17 00:00:00 2001 +From: Matthew Booth +Date: Fri, 11 Dec 2015 13:40:54 +0000 +Subject: [PATCH] Fix backing file detection in libvirt live snapshot + +When doing a live snapshot, the libvirt driver creates an intermediate +qcow2 file with the same backing file as the original disk. However, +it calls qemu-img info without specifying the input format explicitly. +An authenticated user can write data to a raw disk which will cause +this code to misinterpret the disk as a qcow2 file with a +user-specified backing file on the host, and return an arbitrary host +file as the backing file. + +This bug does not appear to result in a data leak in this case, but +this is hard to verify. It certainly results in corrupt output. + +(cherry picked from commit fec5b15911f7d4a927633875d042c6a94171b8ae) + + Conflicts: + nova/tests/virt/libvirt/fake_libvirt_utils.py + nova/tests/virt/libvirt/test_libvirt.py + nova/virt/images.py + nova/virt/libvirt/driver.py + +Resolves: rhbz 1295730 +Resolves: rhbz 1295729 +Closes-Bug: #1524274 +Change-Id: I11485f077d28f4e97529a691e55e3e3c0bea8872 +Reviewed-on: https://code.engineering.redhat.com/gerrit/64915 +Tested-by: RHOS Jenkins +Reviewed-by: Matthew Booth +--- + nova/tests/virt/libvirt/fake_libvirt_utils.py | 6 +++++- + nova/tests/virt/libvirt/test_libvirt.py | 3 +-- + nova/virt/images.py | 8 +++++--- + nova/virt/libvirt/driver.py | 16 ++++++++++------ + nova/virt/libvirt/utils.py | 9 +++++---- + 5 files changed, 26 insertions(+), 16 deletions(-) + +Index: nova-2014.1.5/nova/tests/virt/libvirt/fake_libvirt_utils.py +=================================================================== +--- nova-2014.1.5.orig/nova/tests/virt/libvirt/fake_libvirt_utils.py 2017-09-13 13:09:44.429692727 -0400 ++++ nova-2014.1.5/nova/tests/virt/libvirt/fake_libvirt_utils.py 2017-09-13 13:09:44.417692566 -0400 +@@ -90,7 +90,11 @@ def create_cow_image(backing_file, path) + pass + + +-def get_disk_backing_file(path): ++def get_disk_size(path, format=None): ++ return 0 ++ ++ ++def get_disk_backing_file(path, format=None): + return disk_backing_files.get(path, None) + + +Index: nova-2014.1.5/nova/tests/virt/libvirt/test_libvirt.py +=================================================================== +--- nova-2014.1.5.orig/nova/tests/virt/libvirt/test_libvirt.py 2017-09-13 13:09:44.429692727 -0400 ++++ nova-2014.1.5/nova/tests/virt/libvirt/test_libvirt.py 2017-09-13 13:09:44.421692620 -0400 +@@ -7451,8 +7451,7 @@ class LibvirtConnTestCase(test.TestCase) + unplug.side_effect = test.TestingException + self.assertRaises(test.TestingException, + conn.cleanup, 'ctxt', fake_inst, 'netinfo') +- unplug.assert_called_once_with(fake_inst, 'netinfo', +- ignore_errors=True) ++ unplug.assert_called_once_with(fake_inst, 'netinfo', ignore_errors=True) + + @mock.patch('os.path.exists', return_value=True) + @mock.patch('tempfile.mkstemp') +Index: nova-2014.1.5/nova/virt/images.py +=================================================================== +--- nova-2014.1.5.orig/nova/virt/images.py 2017-09-13 13:09:44.429692727 -0400 ++++ nova-2014.1.5/nova/virt/images.py 2017-09-13 13:09:44.421692620 -0400 +@@ -55,7 +55,7 @@ except Exception: + 'vulnerability CVE-2015-5162.') + + +-def qemu_img_info(path): ++def qemu_img_info(path, format=None): + """Return an object containing the parsed output from qemu-img info.""" + # TODO(mikal): this code should not be referring to a libvirt specific + # flag. +@@ -63,6 +63,8 @@ def qemu_img_info(path): + return imageutils.QemuImgInfo() + + cmd = ('env', 'LC_ALL=C', 'LANG=C', 'qemu-img', 'info', path) ++ if format is not None: ++ cmd = cmd + ('-f', format) + if QEMU_IMG_LIMITS is not None: + out, err = utils.execute(*cmd, prlimit=QEMU_IMG_LIMITS) + else: +Index: nova-2014.1.5/nova/virt/libvirt/driver.py +=================================================================== +--- nova-2014.1.5.orig/nova/virt/libvirt/driver.py 2017-09-13 13:09:44.429692727 -0400 ++++ nova-2014.1.5/nova/virt/libvirt/driver.py 2017-09-13 13:09:44.425692673 -0400 +@@ -1593,7 +1593,7 @@ class LibvirtDriver(driver.ComputeDriver + # NOTE(xqueralt): libvirt needs o+x in the temp directory + os.chmod(tmpdir, 0o701) + self._live_snapshot(virt_dom, disk_path, out_path, +- image_format) ++ source_format, image_format, metadata) + else: + snapshot_backend.snapshot_extract(out_path, image_format) + finally: +@@ -1651,7 +1651,8 @@ class LibvirtDriver(driver.ComputeDriver + else: + return True + +- def _live_snapshot(self, domain, disk_path, out_path, image_format): ++ def _live_snapshot(self, domain, disk_path, out_path, ++ source_format, image_format, image_meta): + """Snapshot an instance without downtime.""" + # Save a copy of the domain's running XML file + xml = domain.XMLDesc(0) +@@ -1667,9 +1668,11 @@ class LibvirtDriver(driver.ComputeDriver + # in QEMU 1.3. In order to do this, we need to create + # a destination image with the original backing file + # and matching size of the instance root disk. +- src_disk_size = libvirt_utils.get_disk_size(disk_path) ++ src_disk_size = libvirt_utils.get_disk_size(disk_path, ++ format=source_format) + src_back_path = libvirt_utils.get_disk_backing_file(disk_path, +- basename=False) ++ format=source_format, ++ basename=False) + disk_delta = out_path + '.delta' + libvirt_utils.create_cow_image(src_back_path, disk_delta, + src_disk_size) +Index: nova-2014.1.5/nova/virt/libvirt/utils.py +=================================================================== +--- nova-2014.1.5.orig/nova/virt/libvirt/utils.py 2017-09-13 13:09:44.429692727 -0400 ++++ nova-2014.1.5/nova/virt/libvirt/utils.py 2017-09-13 13:09:44.425692673 -0400 +@@ -457,24 +457,25 @@ def pick_disk_driver_name(hypervisor_ver + return None + + +-def get_disk_size(path): ++def get_disk_size(path, format=None): + """Get the (virtual) size of a disk image + + :param path: Path to the disk image ++ :param format: the on-disk format of path + :returns: Size (in bytes) of the given disk image as it would be seen + by a virtual machine. + """ +- size = images.qemu_img_info(path).virtual_size ++ size = images.qemu_img_info(path, format).virtual_size + return int(size) + + +-def get_disk_backing_file(path, basename=True): ++def get_disk_backing_file(path, basename=True, format=None): + """Get the backing file of a disk image + + :param path: Path to the disk image + :returns: a path to the image's backing store + """ +- backing_file = images.qemu_img_info(path).backing_file ++ backing_file = images.qemu_img_info(path, format).backing_file + if backing_file and basename: + backing_file = os.path.basename(backing_file) + diff -Nru nova-2014.1.5/debian/patches/CVE-2015-7548-4.patch nova-2014.1.5/debian/patches/CVE-2015-7548-4.patch --- nova-2014.1.5/debian/patches/CVE-2015-7548-4.patch 1970-01-01 00:00:00.000000000 +0000 +++ nova-2014.1.5/debian/patches/CVE-2015-7548-4.patch 2017-09-13 17:09:50.000000000 +0000 @@ -0,0 +1,41 @@ +From d28d73214f07d8577747d2b2e70dc11f370e4465 Mon Sep 17 00:00:00 2001 +From: Matthew Booth +Date: Thu, 14 Apr 2016 17:13:37 +0100 +Subject: [PATCH] Disable live snapshot for rbd-backed instances + +The backport of change I11485f077d28f4e97529a691e55e3e3c0bea8872 +missed a use of source_format. After this change source_format +strictly contains a file format, and source_type contains the name of +the backend. Therefore, for rbd source_format is 'raw', and +source_type is 'rbd'. The test to enable live migration still expected +source_format for rbd to be 'rbd', which caused the exclusion to be +missed. + +This change is a fixup to the backport. The new line is in line with +upstream. + +Downstream-Only +Resolves: rhbz#1326489 + +Change-Id: I6dcbceb39a97b5fbe7bf42d367596afc4ea061e0 +Reviewed-on: https://code.engineering.redhat.com/gerrit/72226 +Reviewed-by: Lee Yarwood +Tested-by: RHOS Jenkins +Tested-by: Matthew Booth +--- + nova/virt/libvirt/driver.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +Index: nova-2014.1.5/nova/virt/libvirt/driver.py +=================================================================== +--- nova-2014.1.5.orig/nova/virt/libvirt/driver.py 2017-09-13 13:09:48.401745734 -0400 ++++ nova-2014.1.5/nova/virt/libvirt/driver.py 2017-09-13 13:09:48.397745682 -0400 +@@ -1543,7 +1543,7 @@ class LibvirtDriver(driver.ComputeDriver + if self.has_min_version(MIN_LIBVIRT_LIVESNAPSHOT_VERSION, + MIN_QEMU_LIVESNAPSHOT_VERSION, + REQ_HYPERVISOR_LIVESNAPSHOT) \ +- and not source_type == "lvm" and not source_format == 'rbd': ++ and source_type not in ('lvm', 'rbd'): + live_snapshot = True + # Abort is an idempotent operation, so make sure any block + # jobs which may have failed are ended. This operation also diff -Nru nova-2014.1.5/debian/patches/CVE-2015-7713.patch nova-2014.1.5/debian/patches/CVE-2015-7713.patch --- nova-2014.1.5/debian/patches/CVE-2015-7713.patch 1970-01-01 00:00:00.000000000 +0000 +++ nova-2014.1.5/debian/patches/CVE-2015-7713.patch 2017-09-12 12:47:11.000000000 +0000 @@ -0,0 +1,117 @@ +From 6dfb9690b1c1d2a0836db48a735953a23a098470 Mon Sep 17 00:00:00 2001 +From: Matt Riedemann +Date: Wed, 9 Sep 2015 20:29:09 -0700 +Subject: [PATCH] Don't expect meta attributes in object_compat that aren't in + the db obj + +The object_compat decorator expects to get the Instance object with +'metadata' and 'system_metadata' attributes but if those aren't in the +db instance dict object, Instance._from_db_object will fail with a +KeyError. + +In Kilo this happens per refresh_instance_security_rules because in the +compute API code, the instance passed to refresh_instance_security_rules +comes from the call to get the security group(s) which joins on the +instances column, but that doesn't join on the metadata/system_metadata +fields for the instances. So when the instances get to object_compat in +the compute manager and the db instance dict is converted to the +Instance object, it expects fields that aren't in the dict and we get +the KeyError. + +The refresh_instance_security_rules case is fixed in Liberty per commit +12fbe6f082ef9b70b89302e15daa12e851e507a7 - in that case the compute API +passes Instance objects to the compute manager so object_compat doesn't +have anything to do, _load_instance just sees instance_or_dict isn't a +dict and ignores it. + +We're making this change since (1) it's an obviously wrong assumption in +object_compat and should be fixed and (2) we need to backport this fix +to stable/kilo since it's an upgrade impact for users there. + +Closes-Bug: #1484738 +Resolves: rhbz#1272864 +Upstream-Liberty: https://review.openstack.org/222022 +Upstream-Kilo: https://review.openstack.org/222023 +Upstream-Juno: https://review.openstack.org/222026 + +Conflicts: + nova/tests/unit/compute/test_compute.py + +NOTE(mriedem): The conflict is due to the unit tests being moved in +kilo, otherwise this is unchanged. + +Change-Id: I36a954c095a9aa35879200784dc18e35edf689e6 +(cherry picked from commit 9369aab04e37b7818d49b00e65857be8b3564e9e) +(cherry picked from commit 08d1153d3be9f8d59aa0acc03eedd45a1697ed7b) +Reviewed-on: https://code.engineering.redhat.com/gerrit/61173 +Reviewed-by: RHOS Jenkins +Tested-by: RHOS Jenkins +Reviewed-by: Lee Yarwood +--- + nova/compute/manager.py | 6 +++++- + nova/tests/compute/test_compute.py | 21 +++++++++++++++++++++ + 2 files changed, 26 insertions(+), 1 deletion(-) + +Index: nova-2014.1.5/nova/compute/manager.py +=================================================================== +--- nova-2014.1.5.orig/nova/compute/manager.py 2017-09-12 08:47:08.890464733 -0400 ++++ nova-2014.1.5/nova/compute/manager.py 2017-09-12 08:47:08.882464635 -0400 +@@ -397,6 +397,11 @@ def object_compat(function): + def decorated_function(self, context, *args, **kwargs): + def _load_instance(instance_or_dict): + if isinstance(instance_or_dict, dict): ++ # try to get metadata and system_metadata for most cases but ++ # only attempt to load those if the db instance already has ++ # those fields joined ++ metas = [meta for meta in ('metadata', 'system_metadata') ++ if meta in instance_or_dict] + instance = instance_obj.Instance._from_db_object( + context, instance_obj.Instance(), instance_or_dict, + expected_attrs=metas) +@@ -404,7 +409,6 @@ def object_compat(function): + return instance + return instance_or_dict + +- metas = ['metadata', 'system_metadata'] + try: + kwargs['instance'] = _load_instance(kwargs['instance']) + except KeyError: +Index: nova-2014.1.5/nova/tests/compute/test_compute.py +=================================================================== +--- nova-2014.1.5.orig/nova/tests/compute/test_compute.py 2017-09-12 08:47:08.890464733 -0400 ++++ nova-2014.1.5/nova/tests/compute/test_compute.py 2017-09-12 08:47:08.882464635 -0400 +@@ -1203,6 +1203,24 @@ class ComputeTestCase(BaseTestCase): + def test_fn(_self, context, instance): + self.assertIsInstance(instance, instance_obj.Instance) + self.assertEqual(instance.uuid, db_inst['uuid']) ++ self.assertEqual(instance.metadata, db_inst['metadata']) ++ self.assertEqual(instance.system_metadata, ++ db_inst['system_metadata']) ++ test_fn(None, self.context, instance=db_inst) ++ ++ def test_object_compat_no_metas(self): ++ # Tests that we don't try to set metadata/system_metadata on the ++ # instance object using fields that aren't in the db object. ++ db_inst = fake_instance.fake_db_instance() ++ db_inst.pop('metadata', None) ++ db_inst.pop('system_metadata', None) ++ ++ @compute_manager.object_compat ++ def test_fn(_self, context, instance): ++ self.assertIsInstance(instance, instance_obj.Instance) ++ self.assertEqual(instance.uuid, db_inst['uuid']) ++ self.assertNotIn('metadata', instance) ++ self.assertNotIn('system_metadata', instance) + test_fn(None, self.context, instance=db_inst) + + def test_object_compat_more_positional_args(self): +@@ -1212,6 +1230,9 @@ class ComputeTestCase(BaseTestCase): + def test_fn(_self, context, instance, pos_arg_1, pos_arg_2): + self.assertIsInstance(instance, instance_obj.Instance) + self.assertEqual(instance.uuid, db_inst['uuid']) ++ self.assertEqual(instance.metadata, db_inst['metadata']) ++ self.assertEqual(instance.system_metadata, ++ db_inst['system_metadata']) + self.assertEqual(pos_arg_1, 'fake_pos_arg1') + self.assertEqual(pos_arg_2, 'fake_pos_arg2') + diff -Nru nova-2014.1.5/debian/patches/CVE-2015-8749.patch nova-2014.1.5/debian/patches/CVE-2015-8749.patch --- nova-2014.1.5/debian/patches/CVE-2015-8749.patch 1970-01-01 00:00:00.000000000 +0000 +++ nova-2014.1.5/debian/patches/CVE-2015-8749.patch 2017-09-12 13:33:41.000000000 +0000 @@ -0,0 +1,48 @@ +Backport of: + +From ef1ccdaca9512b88878155f7d8c2c77853d91252 Mon Sep 17 00:00:00 2001 +From: Matt Riedemann +Date: Mon, 16 Nov 2015 13:11:09 -0800 +Subject: [PATCH] xen: mask passwords in volume connection_data dict + +The connection_data dict can have credentials in it, so we need to scrub +those before putting the stringified dict into the StorageError message +and raising that up and when logging the dict. + +Note that strutils.mask_password converts the dict to a string using +six.text_type so we don't have to do that conversion first. + +SecurityImpact + +Change-Id: Ic5f4d4c26794550a92481bf2b725ef5eafa581b2 +Closes-Bug: #1516765 +(cherry picked from commit 8b289237ed6d53738c22878decf0c429301cf3d0) +(cherry picked from commit cf197ec2d682fb4da777df2291ca7ef101f73b77) +--- + nova/tests/unit/virt/xenapi/test_volume_utils.py | 17 +++++++++++++++-- + nova/tests/unit/virt/xenapi/test_volumeops.py | 16 ++++++++++++++++ + nova/virt/xenapi/volume_utils.py | 3 ++- + nova/virt/xenapi/volumeops.py | 6 +++++- + 4 files changed, 38 insertions(+), 4 deletions(-) + +Index: nova-2014.1.5/nova/virt/xenapi/volume_utils.py +=================================================================== +--- nova-2014.1.5.orig/nova/virt/xenapi/volume_utils.py 2017-09-12 09:32:54.906602924 -0400 ++++ nova-2014.1.5/nova/virt/xenapi/volume_utils.py 2017-09-12 09:33:35.547153637 -0400 +@@ -26,6 +26,7 @@ from oslo.config import cfg + + from nova.openstack.common.gettextutils import _ + from nova.openstack.common import log as logging ++from nova.openstack.common import strutils + + xenapi_volume_utils_opts = [ + cfg.IntOpt('introduce_vdi_retry_wait', +@@ -267,7 +268,7 @@ def parse_volume_info(connection_data): + target_host is None or + target_iqn is None): + raise StorageError(_('Unable to obtain target information' +- ' %s') % connection_data) ++ ' %s') % strutils.mask_password(connection_data)) + volume_info = {} + volume_info['id'] = volume_id + volume_info['target'] = target_host diff -Nru nova-2014.1.5/debian/patches/CVE-2016-2140-1.patch nova-2014.1.5/debian/patches/CVE-2016-2140-1.patch --- nova-2014.1.5/debian/patches/CVE-2016-2140-1.patch 1970-01-01 00:00:00.000000000 +0000 +++ nova-2014.1.5/debian/patches/CVE-2016-2140-1.patch 2017-09-13 17:52:14.000000000 +0000 @@ -0,0 +1,178 @@ +Backport of: + +From 48e30ff15efdf167ce5782b57ee3cf287c5b9049 Mon Sep 17 00:00:00 2001 +From: Lee Yarwood +Date: Wed, 24 Feb 2016 11:23:22 +0000 +Subject: [PATCH] libvirt: Always copy or recreate disk.info during a migration + +The disk.info file contains the path and format of any image, config or +ephermal disk associated with an instance. When using RAW images and migrating +an instance this file should always be copied or recreated. This avoids the Raw +imagebackend reinspecting the format of these disks when spawning the instance +on the destination host. + +By not copying or recreating this disk.info file, a malicious image written to +an instance disk on the source host will cause Nova to reinspect and record a +different format for the disk on the destination. This format then being used +incorrectly when finally spawning the instance on the destination. + +Conflicts: + nova/tests/unit/virt/libvirt/test_driver.py + nova/virt/libvirt/driver.py + +Resolves:rhbz #1313655 +Resolves:rhbz #1313656 +SecurityImpact +Closes-bug: #1548450 +Change-Id: Idfc16f54049aaeab31ac1c1d8d79a129acc9fb87 +Reviewed-on: https://code.engineering.redhat.com/gerrit/69013 +Reviewed-by: Matthew Booth +Tested-by: RHOS Jenkins +--- + nova/tests/virt/libvirt/test_libvirt.py | 81 +++++++++++++++++++++++++++++++++ + nova/virt/libvirt/driver.py | 26 +++++++++++ + 2 files changed, 107 insertions(+) + +Index: nova-2014.1.5/nova/tests/virt/libvirt/test_libvirt.py +=================================================================== +--- nova-2014.1.5.orig/nova/tests/virt/libvirt/test_libvirt.py 2017-09-13 13:51:13.969596813 -0400 ++++ nova-2014.1.5/nova/tests/virt/libvirt/test_libvirt.py 2017-09-13 13:51:59.014183154 -0400 +@@ -4433,6 +4433,43 @@ class LibvirtConnTestCase(test.TestCase) + conn.pre_live_migration(self.context, instance, block_device_info=None, + network_info=[], disk_info={}) + ++ def test_pre_live_migration_recreate_disk_info(self): ++ ++ migrate_data = {'is_shared_storage': False, ++ 'is_volume_backed': False, ++ 'block_migration': True, ++ 'instance_relative_path': '/some/path/'} ++ disk_info = [{'disk_size': 5368709120, 'type': 'raw', ++ 'virt_disk_size': 5368709120, ++ 'path': '/some/path/disk', ++ 'backing_file': '', 'over_committed_disk_size': 0}, ++ {'disk_size': 1073741824, 'type': 'raw', ++ 'virt_disk_size': 1073741824, ++ 'path': '/some/path/disk.eph0', ++ 'backing_file': '', 'over_committed_disk_size': 0}] ++ image_disk_info = {'/some/path/disk': 'raw', ++ '/some/path/disk.eph0': 'raw'} ++ ++ drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) ++ instance = db.instance_create(self.context, self.test_instance) ++ instance_path = os.path.dirname(disk_info[0]['path']) ++ disk_info_path = os.path.join(instance_path, 'disk.info') ++ ++ with contextlib.nested( ++ mock.patch.object(os, 'mkdir'), ++ mock.patch.object(fake_libvirt_utils, 'write_to_file'), ++ mock.patch.object(drvr, '_create_images_and_backing') ++ ) as ( ++ mkdir, write_to_file, create_images_and_backing ++ ): ++ drvr.pre_live_migration(self.context, instance, ++ block_device_info=None, ++ network_info=[], ++ disk_info=disk_info, ++ migrate_data=migrate_data) ++ write_to_file.assert_called_with(disk_info_path, ++ jsonutils.dumps(image_disk_info)) ++ + def test_get_instance_disk_info_works_correctly(self): + # Test data + instance_ref = db.instance_create(self.context, self.test_instance) +@@ -8734,6 +8771,50 @@ class LibvirtDriverTestCase(test.TestCas + self.libvirtconnection.migrate_disk_and_power_off, + 'ctx', instance, '10.0.0.1', flavor, None) + ++ @mock.patch('nova.utils.execute') ++ @mock.patch('nova.virt.libvirt.utils.copy_image') ++ @mock.patch('nova.virt.libvirt.driver.LibvirtDriver._destroy') ++ @mock.patch('nova.virt.libvirt.utils.get_instance_path') ++ @mock.patch('nova.virt.libvirt.driver.LibvirtDriver' ++ '._is_storage_shared_with') ++ @mock.patch('nova.virt.libvirt.driver.LibvirtDriver' ++ '.get_instance_disk_info') ++ def test_migrate_disk_and_power_off_resize_copy_disk_info(self, ++ mock_disk_info, ++ mock_shared, ++ mock_path, ++ mock_destroy, ++ mock_copy, ++ mock_execuate): ++ ++ instance = self._create_instance() ++ disk_info = jsonutils.dumps([{'disk_size': 1, 'type': 'qcow2', ++ 'virt_disk_size': 10737418240, 'path': '/test/disk', ++ 'backing_file': '/base/disk'}, ++ {'disk_size': 1, 'type': 'qcow2', ++ 'virt_disk_size': 536870912, 'path': '/test/disk.swap', ++ 'backing_file': '/base/swap_512'}]) ++ disk_info_text = jsonutils.loads(disk_info) ++ instance_base = os.path.dirname(disk_info_text[0]['path']) ++ flavor = {'root_gb': 10, 'ephemeral_gb': 25} ++ flavor_object = flavor_obj.Flavor(**flavor) ++ ++ mock_disk_info.return_value = disk_info ++ mock_path.return_value = instance_base ++ mock_shared.return_value = False ++ ++ admin_ctx = context.get_admin_context() ++ self.libvirtconnection.migrate_disk_and_power_off(admin_ctx, instance, ++ mock.sentinel, ++ flavor_object, None) ++ ++ src_disk_info_path = os.path.join(instance_base + '_resize', ++ 'disk.info') ++ dst_disk_info_path = os.path.join(instance_base, 'disk.info') ++ mock_copy.assert_any_call(src_disk_info_path, dst_disk_info_path, ++ host=mock.sentinel, on_execute=mock.ANY, ++ on_completion=mock.ANY) ++ + def test_wait_for_running(self): + def fake_get_info(instance): + if instance['name'] == "not_found": +Index: nova-2014.1.5/nova/virt/libvirt/driver.py +=================================================================== +--- nova-2014.1.5.orig/nova/virt/libvirt/driver.py 2017-09-13 13:51:13.969596813 -0400 ++++ nova-2014.1.5/nova/virt/libvirt/driver.py 2017-09-13 13:51:13.969596813 -0400 +@@ -4723,6 +4723,24 @@ class LibvirtDriver(driver.ComputeDriver + raise exception.DestinationDiskExists(path=instance_dir) + os.mkdir(instance_dir) + ++ # Recreate the disk.info file and in doing so stop the ++ # imagebackend from recreating it incorrectly by inspecting the ++ # contents of each file when using the Raw backend. ++ if disk_info: ++ image_disk_info = {} ++ for info in disk_info: ++ image_file = os.path.basename(info['path']) ++ image_path = os.path.join(instance_dir, image_file) ++ image_disk_info[image_path] = info['type'] ++ ++ LOG.debug('Creating disk.info with the contents: %s', ++ image_disk_info, instance=instance) ++ ++ image_disk_info_path = os.path.join(instance_dir, ++ 'disk.info') ++ libvirt_utils.write_to_file(image_disk_info_path, ++ jsonutils.dumps(image_disk_info)) ++ + # Ensure images and backing files are present. + self._create_images_and_backing(context, instance, instance_dir, + disk_info) +@@ -5145,6 +5163,14 @@ class LibvirtDriver(driver.ComputeDriver + libvirt_utils.copy_image(from_path, img_path, host=dest, + on_execute=on_execute, + on_completion=on_completion) ++ ++ # Ensure disk.info is written to the new path to avoid disks being ++ # reinspected and potentially changing format. ++ src_disk_info_path = os.path.join(inst_base_resize, 'disk.info') ++ dst_disk_info_path = os.path.join(inst_base, 'disk.info') ++ libvirt_utils.copy_image(src_disk_info_path, dst_disk_info_path, ++ host=dest, on_execute=on_execute, ++ on_completion=on_completion) + except Exception: + with excutils.save_and_reraise_exception(): + self._cleanup_remote_migration(dest, inst_base, diff -Nru nova-2014.1.5/debian/patches/CVE-2016-2140-2.patch nova-2014.1.5/debian/patches/CVE-2016-2140-2.patch --- nova-2014.1.5/debian/patches/CVE-2016-2140-2.patch 1970-01-01 00:00:00.000000000 +0000 +++ nova-2014.1.5/debian/patches/CVE-2016-2140-2.patch 2017-09-13 17:10:03.000000000 +0000 @@ -0,0 +1,98 @@ +From e9ee2bd26b5f863099cda5f4aa89c4d567984d27 Mon Sep 17 00:00:00 2001 +From: Matthew Booth +Date: Wed, 9 Mar 2016 17:27:03 +0000 +Subject: [PATCH] Fix processing of libvirt disk.info in non-disk-image cases + +In Idfc16f54049aaeab31ac1c1d8d79a129acc9fb87 a change was made +that caused non-disk-image backends to fall over because of an +undefined variable because they skipped processing of the disk.info +file. This adds a check for that case to make sure we don't run +that path in the non-disk-image backend case. + +Conflicts: + nova/tests/virt/libvirt/test_libvirt.py + +Upstream-Kilo: https://review.openstack.org/#/c/290847 +Closes-Bug: #1555287 +Change-Id: I02f8a5f0e29816336e500a8fe8dcc9ece15968e9 +Reviewed-on: https://code.engineering.redhat.com/gerrit/69570 +Tested-by: RHOS Jenkins +Reviewed-by: Matthew Booth +--- + nova/tests/virt/libvirt/test_libvirt.py | 14 +++++++++++--- + nova/virt/libvirt/driver.py | 21 ++++++++++++--------- + 2 files changed, 23 insertions(+), 12 deletions(-) + +Index: nova-2014.1.5/nova/tests/virt/libvirt/test_libvirt.py +=================================================================== +--- nova-2014.1.5.orig/nova/tests/virt/libvirt/test_libvirt.py 2017-09-13 13:10:01.537921037 -0400 ++++ nova-2014.1.5/nova/tests/virt/libvirt/test_libvirt.py 2017-09-13 13:10:01.533920984 -0400 +@@ -8804,12 +8804,20 @@ class LibvirtDriverTestCase(test.TestCas + mock_shared.return_value = False + + admin_ctx = context.get_admin_context() +- self.libvirtconnection.migrate_disk_and_power_off(admin_ctx, instance, +- mock.sentinel, +- flavor_object, None) + + src_disk_info_path = os.path.join(instance_base + '_resize', + 'disk.info') ++ ++ with mock.patch.object(os.path, 'exists', autospec=True) \ ++ as mock_exists: ++ # disk.info exists on the source ++ mock_exists.side_effect = \ ++ lambda path: path == src_disk_info_path ++ self.libvirtconnection.migrate_disk_and_power_off(admin_ctx, ++ instance, mock.sentinel, ++ flavor_object, None) ++ self.assertTrue(mock_exists.called) ++ + dst_disk_info_path = os.path.join(instance_base, 'disk.info') + mock_copy.assert_any_call(src_disk_info_path, dst_disk_info_path, + host=mock.sentinel, on_execute=mock.ANY, +Index: nova-2014.1.5/nova/virt/libvirt/driver.py +=================================================================== +--- nova-2014.1.5.orig/nova/virt/libvirt/driver.py 2017-09-13 13:10:01.537921037 -0400 ++++ nova-2014.1.5/nova/virt/libvirt/driver.py 2017-09-13 13:10:01.533920984 -0400 +@@ -5134,17 +5134,18 @@ class LibvirtDriver(driver.ComputeDriver + if shared_storage: + dest = None + utils.execute('mkdir', '-p', inst_base) ++ ++ on_execute = lambda process: \ ++ self.job_tracker.add_job(instance, process.pid) ++ on_completion = lambda process: \ ++ self.job_tracker.remove_job(instance, process.pid) ++ + for info in disk_info: + # assume inst_base == dirname(info['path']) + img_path = info['path'] + fname = os.path.basename(img_path) + from_path = os.path.join(inst_base_resize, fname) + +- on_execute = lambda process: self.job_tracker.add_job( +- instance, process.pid) +- on_completion = lambda process: self.job_tracker.remove_job( +- instance, process.pid) +- + if info['type'] == 'qcow2' and info['backing_file']: + tmp_path = from_path + "_rbase" + # merge backing file +@@ -5167,10 +5168,12 @@ class LibvirtDriver(driver.ComputeDriver + # Ensure disk.info is written to the new path to avoid disks being + # reinspected and potentially changing format. + src_disk_info_path = os.path.join(inst_base_resize, 'disk.info') +- dst_disk_info_path = os.path.join(inst_base, 'disk.info') +- libvirt_utils.copy_image(src_disk_info_path, dst_disk_info_path, +- host=dest, on_execute=on_execute, +- on_completion=on_completion) ++ if os.path.exists(src_disk_info_path): ++ dst_disk_info_path = os.path.join(inst_base, 'disk.info') ++ libvirt_utils.copy_image(src_disk_info_path, ++ dst_disk_info_path, ++ host=dest, on_execute=on_execute, ++ on_completion=on_completion) + except Exception: + with excutils.save_and_reraise_exception(): + self._cleanup_remote_migration(dest, inst_base, diff -Nru nova-2014.1.5/debian/patches/CVE-2016-2140-3.patch nova-2014.1.5/debian/patches/CVE-2016-2140-3.patch --- nova-2014.1.5/debian/patches/CVE-2016-2140-3.patch 1970-01-01 00:00:00.000000000 +0000 +++ nova-2014.1.5/debian/patches/CVE-2016-2140-3.patch 2017-09-13 17:10:08.000000000 +0000 @@ -0,0 +1,59 @@ +From a20a0e46b7841bb64e6bc17b9f0d255541739ea9 Mon Sep 17 00:00:00 2001 +From: Lee Yarwood +Date: Thu, 17 Mar 2016 16:36:08 +0000 +Subject: [PATCH] libvirt: Decode disk_info before use + +The fix for OSSA 2016-007 / CVE-2016-2140 in f302bf04 assumed that +disk_info is always a plain, decoded list. However prior to Liberty +when preforming a live block migration the compute manager populates +disk_info with an encoded JSON string when calling +self.driver.get_instance_disk_info. In the live migration case without +block migration disk_info is None. + +As a result we should always decode disk_info when a block migration +is called for to ensure that we can iterate over the disks and rebuild +the disk.info file. + +The following change removed the JSON encoding from +get_instance_disk_info and other methods within the libvirt driver for +Liberty. + +libvirt: Remove unnecessary JSON conversions +https://review.openstack.org/#/c/177437/6 + +Closes-Bug: #1558697 +Change-Id: Icfe1f23cc3af2d0166dac82109111e341623fc4a +Reviewed-on: https://code.engineering.redhat.com/gerrit/70141 +Tested-by: RHOS Jenkins +Reviewed-by: Matthew Booth +--- + nova/tests/virt/libvirt/test_libvirt.py | 2 +- + nova/virt/libvirt/driver.py | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +Index: nova-2014.1.5/nova/tests/virt/libvirt/test_libvirt.py +=================================================================== +--- nova-2014.1.5.orig/nova/tests/virt/libvirt/test_libvirt.py 2017-09-13 13:10:06.361985415 -0400 ++++ nova-2014.1.5/nova/tests/virt/libvirt/test_libvirt.py 2017-09-13 13:10:06.357985362 -0400 +@@ -4465,7 +4465,7 @@ class LibvirtConnTestCase(test.TestCase) + drvr.pre_live_migration(self.context, instance, + block_device_info=None, + network_info=[], +- disk_info=disk_info, ++ disk_info=jsonutils.dumps(disk_info), + migrate_data=migrate_data) + write_to_file.assert_called_with(disk_info_path, + jsonutils.dumps(image_disk_info)) +Index: nova-2014.1.5/nova/virt/libvirt/driver.py +=================================================================== +--- nova-2014.1.5.orig/nova/virt/libvirt/driver.py 2017-09-13 13:10:06.361985415 -0400 ++++ nova-2014.1.5/nova/virt/libvirt/driver.py 2017-09-13 13:10:06.357985362 -0400 +@@ -4728,7 +4728,7 @@ class LibvirtDriver(driver.ComputeDriver + # contents of each file when using the Raw backend. + if disk_info: + image_disk_info = {} +- for info in disk_info: ++ for info in jsonutils.loads(disk_info): + image_file = os.path.basename(info['path']) + image_path = os.path.join(instance_dir, image_file) + image_disk_info[image_path] = info['type'] diff -Nru nova-2014.1.5/debian/patches/series nova-2014.1.5/debian/patches/series --- nova-2014.1.5/debian/patches/series 2016-09-09 09:41:48.000000000 +0000 +++ nova-2014.1.5/debian/patches/series 2017-09-13 17:09:12.000000000 +0000 @@ -14,3 +14,19 @@ Detach-iSCSI-latest-path-for-latest-disk.patch remove_useless_state_check.patch evacuate_error_vm.patch +CVE-2015-3241-1.patch +CVE-2015-3241-2.patch +CVE-2015-3241-3.patch +CVE-2015-3280.patch +CVE-2015-5162-1.patch +CVE-2015-5162-2.patch +CVE-2015-5162-3.patch +CVE-2015-7548-1.patch +CVE-2015-7548-2.patch +CVE-2015-7548-3.patch +CVE-2015-7548-4.patch +CVE-2015-7713.patch +CVE-2015-8749.patch +CVE-2016-2140-1.patch +CVE-2016-2140-2.patch +CVE-2016-2140-3.patch