diff options
author | Matthew Thode <prometheanfire@gentoo.org> | 2015-08-14 00:22:26 -0500 |
---|---|---|
committer | Matthew Thode <prometheanfire@gentoo.org> | 2015-08-14 00:22:26 -0500 |
commit | 2d4637cbfa732ff2cb94f440debc1ec813cafe58 (patch) | |
tree | 5ad8d26ef812f5277949de524457d6298de33175 /app-admin/glance | |
parent | ec6b413fb9250619c0989149c70f716121ac2ddd (diff) | |
download | gentoo-2d4637cbfa732ff2cb94f440debc1ec813cafe58.tar.gz gentoo-2d4637cbfa732ff2cb94f440debc1ec813cafe58.tar.xz |
app-admin/glance: fixing CVE-2015-5163
CVE: CVE-2015-5163
Signed-off-by: Matthew Thode <prometheanfire@gentoo.org>
Package-Manager: portage-2.2.20.1
Diffstat (limited to 'app-admin/glance')
-rw-r--r-- | app-admin/glance/files/cve-2015-5163-stable-kilo.patch | 260 | ||||
-rw-r--r-- | app-admin/glance/glance-2015.1.1-r1.ebuild (renamed from app-admin/glance/glance-2015.1.1.ebuild) | 1 |
2 files changed, 261 insertions, 0 deletions
diff --git a/app-admin/glance/files/cve-2015-5163-stable-kilo.patch b/app-admin/glance/files/cve-2015-5163-stable-kilo.patch new file mode 100644 index 00000000000..91507c964f4 --- /dev/null +++ b/app-admin/glance/files/cve-2015-5163-stable-kilo.patch @@ -0,0 +1,260 @@ +From eb99e45829a1b4c93db5692bdbf636a86faa56c4 Mon Sep 17 00:00:00 2001 +From: Flavio Percoco <flaper87@gmail.com> +Date: Thu, 9 Jul 2015 14:44:04 +0200 +Subject: Don't import files with backed files + +There's a security issue where it'd be possible to import images with +backed files using the task engine and then use/convert those to access +system files or any other file in the system. An example of an attack +would be to import an image with a backing file pointing to +`/etc/passwd`, then convert it to raw and download the generated image. + +This patch forbids importing files with baking files entirely. It does +that in the `_ImportToFS` task, which is the one that imports the image +locally to then execute other tasks on it. It's not necessary for the +`_ImportToStore` task because other tasks won't be executed when the +image is imported in the final store. + +Change-Id: I35f43c3b3f326942fb53b7dadb94700ac4513494 +Closes-bug: #1471912 +(cherry picked from commit d529863a1e8d2307526bdb395b4aebe97f81603d) + +diff --git a/glance/async/flows/base_import.py b/glance/async/flows/base_import.py +index 7656bde..d216aa8 100644 +--- a/glance/async/flows/base_import.py ++++ b/glance/async/flows/base_import.py +@@ -13,12 +13,15 @@ + # License for the specific language governing permissions and limitations + # under the License. + ++import json + import logging + import os + + import glance_store as store_api + from glance_store import backend ++from oslo_concurrency import processutils as putils + from oslo_config import cfg ++from oslo_utils import excutils + import six + from stevedore import named + from taskflow.patterns import linear_flow as lf +@@ -146,6 +149,29 @@ class _ImportToFS(task.Task): + data = script_utils.get_image_data_iter(self.uri) + + path = self.store.add(image_id, data, 0, context=None)[0] ++ ++ try: ++ # NOTE(flaper87): Consider moving this code to a common ++ # place that other tasks can consume as well. ++ stdout, stderr = putils.trycmd('qemu-img', 'info', ++ '--output=json', path, ++ log_errors=putils.LOG_ALL_ERRORS) ++ except OSError as exc: ++ with excutils.save_and_reraise_exception(): ++ msg = (_LE('Failed to execute security checks on the image ' ++ '%(task_id)s: %(exc)s') % ++ {'task_id': self.task_id, 'exc': exc.message}) ++ LOG.error(msg) ++ ++ metadata = json.loads(stdout) ++ ++ backing_file = metadata.get('backing-filename') ++ if backing_file is not None: ++ msg = _("File %(path)s has invalid backing file " ++ "%(bfile)s, aborting.") % {'path': path, ++ 'bfile': backing_file} ++ raise RuntimeError(msg) ++ + return path + + def revert(self, image_id, result=None, **kwargs): +diff --git a/glance/tests/unit/async/flows/test_import.py b/glance/tests/unit/async/flows/test_import.py +index 70f790c..4cf3d13 100644 +--- a/glance/tests/unit/async/flows/test_import.py ++++ b/glance/tests/unit/async/flows/test_import.py +@@ -13,14 +13,17 @@ + # License for the specific language governing permissions and limitations + # under the License. + ++import json + import mock + import os + import urllib2 + + import glance_store ++from oslo_concurrency import processutils as putils + from oslo_config import cfg + from six.moves import cStringIO + from taskflow import task ++from taskflow.types import failure + + import glance.async.flows.base_import as import_flow + from glance.async import taskflow_executor +@@ -106,16 +109,23 @@ class TestImportTask(test_utils.BaseTestCase): + + with mock.patch.object(script_utils, 'get_image_data_iter') as dmock: + dmock.return_value = cStringIO("TEST_IMAGE") +- executor.begin_processing(self.task.task_id) +- image_path = os.path.join(self.test_dir, self.image.image_id) +- tmp_image_path = os.path.join(self.work_dir, +- "%s.tasks_import" % image_path) +- self.assertFalse(os.path.exists(tmp_image_path)) +- self.assertTrue(os.path.exists(image_path)) +- self.assertEqual(1, len(list(self.image.locations))) +- self.assertEqual("file://%s/%s" % (self.test_dir, +- self.image.image_id), +- self.image.locations[0]['url']) ++ ++ with mock.patch.object(putils, 'trycmd') as tmock: ++ tmock.return_value = (json.dumps({ ++ 'format': 'qcow2', ++ }), None) ++ ++ executor.begin_processing(self.task.task_id) ++ image_path = os.path.join(self.test_dir, self.image.image_id) ++ tmp_image_path = os.path.join(self.work_dir, ++ "%s.tasks_import" % image_path) ++ ++ self.assertFalse(os.path.exists(tmp_image_path)) ++ self.assertTrue(os.path.exists(image_path)) ++ self.assertEqual(1, len(list(self.image.locations))) ++ self.assertEqual("file://%s/%s" % (self.test_dir, ++ self.image.image_id), ++ self.image.locations[0]['url']) + + def test_import_flow_missing_work_dir(self): + self.config(engine_mode='serial', group='taskflow_executor') +@@ -151,6 +161,54 @@ class TestImportTask(test_utils.BaseTestCase): + self.assertFalse(os.path.exists(tmp_image_path)) + self.assertTrue(os.path.exists(image_path)) + ++ def test_import_flow_backed_file_import_to_fs(self): ++ self.config(engine_mode='serial', group='taskflow_executor') ++ ++ img_factory = mock.MagicMock() ++ ++ executor = taskflow_executor.TaskExecutor( ++ self.context, ++ self.task_repo, ++ self.img_repo, ++ img_factory) ++ ++ self.task_repo.get.return_value = self.task ++ ++ def create_image(*args, **kwargs): ++ kwargs['image_id'] = UUID1 ++ return self.img_factory.new_image(*args, **kwargs) ++ ++ self.img_repo.get.return_value = self.image ++ img_factory.new_image.side_effect = create_image ++ ++ with mock.patch.object(script_utils, 'get_image_data_iter') as dmock: ++ dmock.return_value = cStringIO("TEST_IMAGE") ++ ++ with mock.patch.object(putils, 'trycmd') as tmock: ++ tmock.return_value = (json.dumps({ ++ 'backing-filename': '/etc/password' ++ }), None) ++ ++ with mock.patch.object(import_flow._ImportToFS, ++ 'revert') as rmock: ++ self.assertRaises(RuntimeError, ++ executor.begin_processing, ++ self.task.task_id) ++ self.assertTrue(rmock.called) ++ self.assertIsInstance(rmock.call_args[1]['result'], ++ failure.Failure) ++ ++ image_path = os.path.join(self.test_dir, ++ self.image.image_id) ++ ++ fname = "%s.tasks_import" % image_path ++ tmp_image_path = os.path.join(self.work_dir, fname) ++ ++ self.assertFalse(os.path.exists(tmp_image_path)) ++ # Note(sabari): The image should not have been uploaded to ++ # the store as the flow failed before ImportToStore Task. ++ self.assertFalse(os.path.exists(image_path)) ++ + def test_import_flow_revert(self): + self.config(engine_mode='serial', + group='taskflow_executor') +@@ -175,20 +233,31 @@ class TestImportTask(test_utils.BaseTestCase): + with mock.patch.object(script_utils, 'get_image_data_iter') as dmock: + dmock.return_value = cStringIO("TEST_IMAGE") + +- with mock.patch.object(import_flow, "_get_import_flows") as imock: +- imock.return_value = (x for x in [_ErrorTask()]) +- self.assertRaises(RuntimeError, +- executor.begin_processing, self.task.task_id) +- image_path = os.path.join(self.test_dir, self.image.image_id) +- tmp_image_path = os.path.join(self.work_dir, +- "%s.tasks_import" % image_path) +- self.assertFalse(os.path.exists(tmp_image_path)) +- +- # NOTE(flaper87): Eventually, we want this to be assertTrue. +- # The current issue is there's no way to tell taskflow to +- # continue on failures. That is, revert the subflow but keep +- # executing the parent flow. Under discussion/development. +- self.assertFalse(os.path.exists(image_path)) ++ with mock.patch.object(putils, 'trycmd') as tmock: ++ tmock.return_value = (json.dumps({ ++ 'format': 'qcow2', ++ }), None) ++ ++ with mock.patch.object(import_flow, ++ "_get_import_flows") as imock: ++ imock.return_value = (x for x in [_ErrorTask()]) ++ self.assertRaises(RuntimeError, ++ executor.begin_processing, ++ self.task.task_id) ++ ++ image_path = os.path.join(self.test_dir, ++ self.image.image_id) ++ tmp_image_path = os.path.join(self.work_dir, ++ ("%s.tasks_import" % ++ image_path)) ++ self.assertFalse(os.path.exists(tmp_image_path)) ++ ++ # NOTE(flaper87): Eventually, we want this to be assertTrue ++ # The current issue is there's no way to tell taskflow to ++ # continue on failures. That is, revert the subflow but ++ # keep executing the parent flow. Under ++ # discussion/development. ++ self.assertFalse(os.path.exists(image_path)) + + def test_import_flow_no_import_flows(self): + self.config(engine_mode='serial', +@@ -271,15 +340,20 @@ class TestImportTask(test_utils.BaseTestCase): + with mock.patch.object(script_utils, 'get_image_data_iter') as dmock: + dmock.return_value = "test" + +- image_id = UUID1 +- path = import_fs.execute(image_id) +- reader, size = glance_store.get_from_backend(path) +- self.assertEqual(4, size) +- self.assertEqual(dmock.return_value, "".join(reader)) ++ with mock.patch.object(putils, 'trycmd') as tmock: ++ tmock.return_value = (json.dumps({ ++ 'format': 'qcow2', ++ }), None) ++ ++ image_id = UUID1 ++ path = import_fs.execute(image_id) ++ reader, size = glance_store.get_from_backend(path) ++ self.assertEqual(4, size) ++ self.assertEqual(dmock.return_value, "".join(reader)) + +- image_path = os.path.join(self.work_dir, image_id) +- tmp_image_path = os.path.join(self.work_dir, image_path) +- self.assertTrue(os.path.exists(tmp_image_path)) ++ image_path = os.path.join(self.work_dir, image_id) ++ tmp_image_path = os.path.join(self.work_dir, image_path) ++ self.assertTrue(os.path.exists(tmp_image_path)) + + def test_delete_from_fs(self): + delete_fs = import_flow._DeleteFromFS(self.task.task_id, +-- +cgit v0.10.2 + diff --git a/app-admin/glance/glance-2015.1.1.ebuild b/app-admin/glance/glance-2015.1.1-r1.ebuild index b22016621f1..e7df249fe71 100644 --- a/app-admin/glance/glance-2015.1.1.ebuild +++ b/app-admin/glance/glance-2015.1.1-r1.ebuild @@ -137,6 +137,7 @@ RDEPEND=" " PATCHES=( + "${FILESDIR}/cve-2015-5163-stable-kilo.patch" ) pkg_setup() { |