Moderate severityNVD Advisory· Published Jan 21, 2015· Updated May 6, 2026
CVE-2015-1195
CVE-2015-1195
Description
The V2 API in OpenStack Image Registry and Delivery Service (Glance) before 2014.1.4 and 2014.2.x before 2014.2.2 allows remote authenticated users to read or delete arbitrary files via a full pathname in a filesystem: URL in the image location property. NOTE: this vulnerability exists because of an incomplete fix for CVE-2014-9493.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
glancePyPI | < 11.0.0a0 | 11.0.0a0 |
Affected products
1- cpe:2.3:a:openstack:image_registry_and_delivery_service_\(glance\):*:*:*:*:*:*:*:*Range: >=2014.1,<2014.1.4
Patches
37d3a1db33ccbPrevent file, swift+config and filesystem schemes
3 files changed · +22 −24
glance/store/__init__.py+7 −4 modified@@ -76,6 +76,8 @@ 'glance.store.vmware_datastore.Store' ] +RESTRICTED_URI_SCHEMAS = frozenset(['file', 'filesystem', 'swift+config']) + class BackendException(Exception): pass @@ -434,10 +436,11 @@ def validate_external_location(uri): :param uri: The URI of external image location. :return: Whether given URI of external image location are OK. """ - pieces = urlparse.urlparse(uri) - valid_schemes = [scheme for scheme in get_known_schemes() - if scheme != 'file' and scheme != 'swift+config'] - return pieces.scheme in valid_schemes + + # TODO(gm): Use a whitelist of allowed schemes + scheme = urlparse.urlparse(uri).scheme + return (scheme in get_known_schemes() and + scheme not in RESTRICTED_URI_SCHEMAS) class ImageRepoProxy(glance.domain.proxy.Repo):
glance/tests/unit/test_store_location.py+3 −0 modified@@ -524,12 +524,15 @@ def test_add_location_with_restricted_sources(self): loc1 = {'url': 'file:///fake1.img.tar.gz', 'metadata': {}} loc2 = {'url': 'swift+config:///xxx', 'metadata': {}} + loc3 = {'url': 'filesystem:///foo.img.tar.gz', 'metadata': {}} # Test for insert location image1 = TestStoreLocation.FakeImageProxy(utils.FakeStoreAPI()) locations = glance.store.StoreLocations(image1, []) self.assertRaises(exception.BadStoreUri, locations.insert, 0, loc1) + self.assertRaises(exception.BadStoreUri, locations.insert, 0, loc3) self.assertNotIn(loc1, locations) + self.assertNotIn(loc3, locations) # Test for set_attr of _locations_proxy image2 = TestStoreLocation.FakeImageProxy(utils.FakeStoreAPI())
glance/tests/unit/v1/test_api.py+12 −20 modified@@ -1010,31 +1010,23 @@ def test_add_copy_from_with_location(self): def test_add_copy_from_with_restricted_sources(self): """Tests creates an image from copy-from with restricted sources""" - fixture_headers = {'x-image-meta-store': 'file', + header_template = {'x-image-meta-store': 'file', 'x-image-meta-disk-format': 'vhd', - 'x-glance-api-copy-from': 'file:///etc/passwd', 'x-image-meta-container-format': 'ovf', 'x-image-meta-name': 'fake image #F'} - req = webob.Request.blank("/images") - req.method = 'POST' - for k, v in six.iteritems(fixture_headers): - req.headers[k] = v - res = req.get_response(self.api) - self.assertEqual(400, res.status_int) - - fixture_headers = {'x-image-meta-store': 'file', - 'x-image-meta-disk-format': 'vhd', - 'x-glance-api-copy-from': 'swift+config://xxx', - 'x-image-meta-container-format': 'ovf', - 'x-image-meta-name': 'fake image #F'} + schemas = ["file:///etc/passwd", + "swift+config:///xxx", + "filesystem:///etc/passwd"] - req = webob.Request.blank("/images") - req.method = 'POST' - for k, v in six.iteritems(fixture_headers): - req.headers[k] = v - res = req.get_response(self.api) - self.assertEqual(400, res.status_int) + for schema in schemas: + req = webob.Request.blank("/images") + req.method = 'POST' + for k, v in six.iteritems(header_template): + req.headers[k] = v + req.headers['x-glance-api-copy-from'] = schema + res = req.get_response(self.api) + self.assertEqual(400, res.status_int) def test_add_copy_from_upload_image_unauthorized_with_body(self): rules = {"upload_image": '!', "modify_image": '@',
a2d986b976e9Prevent file, swift+config and filesystem schemes
3 files changed · +21 −25
glance/common/store_utils.py+6 −5 modified@@ -38,6 +38,8 @@ CONF = cfg.CONF CONF.register_opts(store_utils_opts) +RESTRICTED_URI_SCHEMAS = frozenset(['file', 'filesystem', 'swift+config']) + def safe_delete_from_backend(context, image_id, location): """ @@ -136,8 +138,7 @@ def validate_external_location(uri): """ # TODO(zhiyan): This function could be moved to glance_store. - - pieces = urlparse.urlparse(uri) - valid_schemes = [scheme for scheme in store_api.get_known_schemes() - if scheme != 'file' and scheme != 'swift+config'] - return pieces.scheme in valid_schemes + # TODO(gm): Use a whitelist of allowed schemes + scheme = urlparse.urlparse(uri).scheme + return (scheme in store_api.get_known_schemes() and + scheme not in RESTRICTED_URI_SCHEMAS)
glance/tests/unit/test_store_location.py+3 −0 modified@@ -67,12 +67,15 @@ def test_add_location_with_restricted_sources(self): loc1 = {'url': 'file:///fake1.img.tar.gz', 'metadata': {}} loc2 = {'url': 'swift+config:///xxx', 'metadata': {}} + loc3 = {'url': 'filesystem:///foo.img.tar.gz', 'metadata': {}} # Test for insert location image1 = TestStoreLocation.FakeImageProxy() locations = glance.location.StoreLocations(image1, []) self.assertRaises(exception.BadStoreUri, locations.insert, 0, loc1) + self.assertRaises(exception.BadStoreUri, locations.insert, 0, loc3) self.assertNotIn(loc1, locations) + self.assertNotIn(loc3, locations) # Test for set_attr of _locations_proxy image2 = TestStoreLocation.FakeImageProxy()
glance/tests/unit/v1/test_api.py+12 −20 modified@@ -1070,31 +1070,23 @@ def test_add_copy_from_with_location(self): def test_add_copy_from_with_restricted_sources(self): """Tests creates an image from copy-from with restricted sources""" - fixture_headers = {'x-image-meta-store': 'file', + header_template = {'x-image-meta-store': 'file', 'x-image-meta-disk-format': 'vhd', - 'x-glance-api-copy-from': 'file:///etc/passwd', 'x-image-meta-container-format': 'ovf', 'x-image-meta-name': 'fake image #F'} - req = webob.Request.blank("/images") - req.method = 'POST' - for k, v in six.iteritems(fixture_headers): - req.headers[k] = v - res = req.get_response(self.api) - self.assertEqual(400, res.status_int) + schemas = ["file:///etc/passwd", + "swift+config:///xxx", + "filesystem:///etc/passwd"] - fixture_headers = {'x-image-meta-store': 'file', - 'x-image-meta-disk-format': 'vhd', - 'x-glance-api-copy-from': 'swift+config://xxx', - 'x-image-meta-container-format': 'ovf', - 'x-image-meta-name': 'fake image #F'} - - req = webob.Request.blank("/images") - req.method = 'POST' - for k, v in six.iteritems(fixture_headers): - req.headers[k] = v - res = req.get_response(self.api) - self.assertEqual(400, res.status_int) + for schema in schemas: + req = webob.Request.blank("/images") + req.method = 'POST' + for k, v in six.iteritems(header_template): + req.headers[k] = v + req.headers['x-glance-api-copy-from'] = schema + res = req.get_response(self.api) + self.assertEqual(400, res.status_int) def test_add_copy_from_upload_image_unauthorized_with_body(self): rules = {"upload_image": '!', "modify_image": '@',
5191ed1879c5Prevent file, swift+config and filesystem schemes
3 files changed · +21 −25
glance/common/store_utils.py+6 −5 modified@@ -38,6 +38,8 @@ CONF = cfg.CONF CONF.register_opts(store_utils_opts) +RESTRICTED_URI_SCHEMAS = frozenset(['file', 'filesystem', 'swift+config']) + def safe_delete_from_backend(context, image_id, location): """ @@ -136,8 +138,7 @@ def validate_external_location(uri): """ # TODO(zhiyan): This function could be moved to glance_store. - - pieces = urlparse.urlparse(uri) - valid_schemes = [scheme for scheme in store_api.get_known_schemes() - if scheme != 'file' and scheme != 'swift+config'] - return pieces.scheme in valid_schemes + # TODO(gm): Use a whitelist of allowed schemes + scheme = urlparse.urlparse(uri).scheme + return (scheme in store_api.get_known_schemes() and + scheme not in RESTRICTED_URI_SCHEMAS)
glance/tests/unit/test_store_location.py+3 −0 modified@@ -69,12 +69,15 @@ def test_add_location_with_restricted_sources(self): loc1 = {'url': 'file:///fake1.img.tar.gz', 'metadata': {}} loc2 = {'url': 'swift+config:///xxx', 'metadata': {}} + loc3 = {'url': 'filesystem:///foo.img.tar.gz', 'metadata': {}} # Test for insert location image1 = TestStoreLocation.FakeImageProxy() locations = glance.location.StoreLocations(image1, []) self.assertRaises(exception.BadStoreUri, locations.insert, 0, loc1) + self.assertRaises(exception.BadStoreUri, locations.insert, 0, loc3) self.assertNotIn(loc1, locations) + self.assertNotIn(loc3, locations) # Test for set_attr of _locations_proxy image2 = TestStoreLocation.FakeImageProxy()
glance/tests/unit/v1/test_api.py+12 −20 modified@@ -1071,31 +1071,23 @@ def test_add_copy_from_with_location(self): def test_add_copy_from_with_restricted_sources(self): """Tests creates an image from copy-from with restricted sources""" - fixture_headers = {'x-image-meta-store': 'file', + header_template = {'x-image-meta-store': 'file', 'x-image-meta-disk-format': 'vhd', - 'x-glance-api-copy-from': 'file:///etc/passwd', 'x-image-meta-container-format': 'ovf', 'x-image-meta-name': 'fake image #F'} - req = webob.Request.blank("/images") - req.method = 'POST' - for k, v in six.iteritems(fixture_headers): - req.headers[k] = v - res = req.get_response(self.api) - self.assertEqual(400, res.status_int) + schemas = ["file:///etc/passwd", + "swift+config:///xxx", + "filesystem:///etc/passwd"] - fixture_headers = {'x-image-meta-store': 'file', - 'x-image-meta-disk-format': 'vhd', - 'x-glance-api-copy-from': 'swift+config://xxx', - 'x-image-meta-container-format': 'ovf', - 'x-image-meta-name': 'fake image #F'} - - req = webob.Request.blank("/images") - req.method = 'POST' - for k, v in six.iteritems(fixture_headers): - req.headers[k] = v - res = req.get_response(self.api) - self.assertEqual(400, res.status_int) + for schema in schemas: + req = webob.Request.blank("/images") + req.method = 'POST' + for k, v in six.iteritems(header_template): + req.headers[k] = v + req.headers['x-glance-api-copy-from'] = schema + res = req.get_response(self.api) + self.assertEqual(400, res.status_int) def test_add_copy_from_upload_image_unauthorized_with_body(self): rules = {"upload_image": '!', "modify_image": '@',
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- lists.openstack.org/pipermail/openstack-announce/2015-January/000325.htmlnvdVendor AdvisoryWEB
- secunia.com/advisories/62169nvdThird Party AdvisoryWEB
- www.openwall.com/lists/oss-security/2015/01/15/2nvdMailing ListThird Party AdvisoryWEB
- www.openwall.com/lists/oss-security/2015/01/18/5nvdMailing ListThird Party AdvisoryWEB
- www.oracle.com/technetwork/topics/security/bulletinapr2015-2511959.htmlnvdThird Party AdvisoryWEB
- www.securityfocus.com/bid/71976nvdThird Party AdvisoryVDB EntryWEB
- bugs.launchpad.net/ossa/+bug/1408663nvdThird Party AdvisoryWEB
- github.com/advisories/GHSA-pwrj-f53c-f89jghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2015-1195ghsaADVISORY
- github.com/openstack/glance/commit/5191ed1879c5fd5b2694f922bcedec232f461088ghsaWEB
- github.com/openstack/glance/commit/7d3a1db33ccbd25b9fc7326ce3468eabd2a41a99ghsaWEB
- github.com/openstack/glance/commit/a2d986b976e9325a272e2d422465165315d19fe6ghsaWEB
News mentions
0No linked articles in our index yet.