VYPR
High severityNVD Advisory· Published Aug 19, 2015· Updated May 6, 2026

CVE-2015-5163

CVE-2015-5163

Description

The import task action in OpenStack Image Service (Glance) 2015.1.x before 2015.1.2 (kilo), when using the V2 API, allows remote authenticated users to read arbitrary files via a crafted backing file for a qcow2 image.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
glancePyPI
>= 2015.1.0, < 2015.1.22015.1.2

Affected products

2
  • OpenStack/Glance2 versions
    cpe:2.3:a:openstack:glance:2015.1.0:*:*:*:*:*:*:*+ 1 more
    • cpe:2.3:a:openstack:glance:2015.1.0:*:*:*:*:*:*:*
    • cpe:2.3:a:openstack:glance:2015.1.1:*:*:*:*:*:*:*

Patches

1
eb99e45829a1

Don't import files with backed files

https://github.com/openstack/glanceFlavio PercocoJul 9, 2015via ghsa
2 files changed · +132 32
  • glance/async/flows/base_import.py+26 0 modified
    @@ -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 @@ def execute(self, image_id):
             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):
    
  • glance/tests/unit/async/flows/test_import.py+106 32 modified
    @@ -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 @@ def create_image(*args, **kwargs):
     
             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 @@ def create_image(*args, **kwargs):
                     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 @@ def create_image(*args, **kwargs):
             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 @@ def test_import_to_fs(self):
             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,
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

12

News mentions

0

No linked articles in our index yet.