summaryrefslogtreecommitdiff
path: root/app-admin/glance
diff options
context:
space:
mode:
authorMatthew Thode <prometheanfire@gentoo.org>2015-08-14 00:22:26 -0500
committerMatthew Thode <prometheanfire@gentoo.org>2015-08-14 00:22:26 -0500
commit2d4637cbfa732ff2cb94f440debc1ec813cafe58 (patch)
tree5ad8d26ef812f5277949de524457d6298de33175 /app-admin/glance
parentec6b413fb9250619c0989149c70f716121ac2ddd (diff)
downloadgentoo-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.patch260
-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() {