summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app-admin/glance/files/cve-2016-0757-stable-liberty.patch')
-rw-r--r--app-admin/glance/files/cve-2016-0757-stable-liberty.patch332
1 files changed, 332 insertions, 0 deletions
diff --git a/app-admin/glance/files/cve-2016-0757-stable-liberty.patch b/app-admin/glance/files/cve-2016-0757-stable-liberty.patch
new file mode 100644
index 000000000000..19c8365eb018
--- /dev/null
+++ b/app-admin/glance/files/cve-2016-0757-stable-liberty.patch
@@ -0,0 +1,332 @@
+From c6021e9b3642340036347026a3f251e066e53094 Mon Sep 17 00:00:00 2001
+From: Erno Kuvaja <jokke@usr.fi>
+Date: Tue, 19 Jan 2016 13:37:05 +0000
+Subject: [PATCH] Prevent user to remove last location of the image
+
+If the last location of the image is removed, image transitions back to queued.
+This allows user to upload new data into the existing image record. By
+preventing removal of the last location we prevent the image transition back to
+queued.
+
+This change also prevents doing the same operation via replacing the locations
+with empty list.
+
+SecurityImpact
+DocImpact
+APIImpact
+
+Conflicts:
+ glance/tests/unit/v2/test_images_resource.py
+
+Change-Id: Ieb03aaba887492819f9c58aa67f7acfcea81720e
+Closes-Bug: #1525915
+(cherry picked from commit 2f4504da2149697bcdb93ed855e15025d2a08f8c)
+---
+ glance/api/v2/images.py | 19 +++-
+ glance/tests/functional/v2/test_images.py | 14 ---
+ glance/tests/unit/v2/test_images_resource.py | 122 ++++-----------------
+ ...oving-last-image-location-d5ee3e00efe14f34.yaml | 10 ++
+ 4 files changed, 44 insertions(+), 121 deletions(-)
+ create mode 100644 releasenotes/notes/Prevent-removing-last-image-location-d5ee3e00efe14f34.yaml
+
+diff --git a/glance/api/v2/images.py b/glance/api/v2/images.py
+index 17678f2..cf667bf 100644
+--- a/glance/api/v2/images.py
++++ b/glance/api/v2/images.py
+@@ -181,7 +181,10 @@ class ImagesController(object):
+ path = change['path']
+ path_root = path[0]
+ value = change['value']
+- if path_root == 'locations':
++ if path_root == 'locations' and value == []:
++ msg = _("Cannot set locations to empty list.")
++ raise webob.exc.HTTPForbidden(message=msg)
++ elif path_root == 'locations' and value != []:
+ self._do_replace_locations(image, value)
+ elif path_root == 'owner' and req.context.is_admin == False:
+ msg = _("Owner can't be updated by non admin.")
+@@ -217,7 +220,10 @@ class ImagesController(object):
+ path = change['path']
+ path_root = path[0]
+ if path_root == 'locations':
+- self._do_remove_locations(image, path[1])
++ try:
++ self._do_remove_locations(image, path[1])
++ except exception.Forbidden as e:
++ raise webob.exc.HTTPForbidden(e.msg)
+ else:
+ if hasattr(image, path_root):
+ msg = _("Property %s may not be removed.")
+@@ -306,6 +312,11 @@ class ImagesController(object):
+ explanation=encodeutils.exception_to_unicode(ve))
+
+ def _do_remove_locations(self, image, path_pos):
++ if len(image.locations) == 1:
++ LOG.debug("User forbidden to remove last location of image %s",
++ image.image_id)
++ msg = _("Cannot remove last location in the image.")
++ raise exception.Forbidden(message=msg)
+ pos = self._get_locations_op_pos(path_pos,
+ len(image.locations), False)
+ if pos is None:
+@@ -315,11 +326,11 @@ class ImagesController(object):
+ # NOTE(zhiyan): this actually deletes the location
+ # from the backend store.
+ image.locations.pop(pos)
++ # TODO(jokke): Fix this, we should catch what store throws and
++ # provide definitely something else than IternalServerError to user.
+ except Exception as e:
+ raise webob.exc.HTTPInternalServerError(
+ explanation=encodeutils.exception_to_unicode(e))
+- if len(image.locations) == 0 and image.status == 'active':
+- image.status = 'queued'
+
+
+ class RequestDeserializer(wsgi.JSONRequestDeserializer):
+diff --git a/glance/tests/functional/v2/test_images.py b/glance/tests/functional/v2/test_images.py
+index aabc567..f199787 100644
+--- a/glance/tests/functional/v2/test_images.py
++++ b/glance/tests/functional/v2/test_images.py
+@@ -522,20 +522,6 @@ class TestImages(functional.FunctionalTest):
+ response = requests.patch(path, headers=headers, data=data)
+ self.assertEqual(200, response.status_code, response.text)
+
+- # Remove all locations of the image then the image size shouldn't be
+- # able to access
+- path = self._url('/v2/images/%s' % image2_id)
+- media_type = 'application/openstack-images-v2.1-json-patch'
+- headers = self._headers({'content-type': media_type})
+- doc = [{'op': 'replace', 'path': '/locations', 'value': []}]
+- data = jsonutils.dumps(doc)
+- response = requests.patch(path, headers=headers, data=data)
+- self.assertEqual(200, response.status_code, response.text)
+- image = jsonutils.loads(response.text)
+- self.assertIsNone(image['size'])
+- self.assertIsNone(image['virtual_size'])
+- self.assertEqual('queued', image['status'])
+-
+ # Deletion should work. Deleting image-1
+ path = self._url('/v2/images/%s' % image_id)
+ response = requests.delete(path, headers=self._headers())
+diff --git a/glance/tests/unit/v2/test_images_resource.py b/glance/tests/unit/v2/test_images_resource.py
+index 698c284..ee09ee7 100644
+--- a/glance/tests/unit/v2/test_images_resource.py
++++ b/glance/tests/unit/v2/test_images_resource.py
+@@ -1417,26 +1417,6 @@ class TestImagesController(base.IsolatedUnitTest):
+ self.assertRaises(webob.exc.HTTPConflict, self.controller.update,
+ another_request, created_image.image_id, changes)
+
+- def test_update_replace_locations(self):
+- self.stubs.Set(store, 'get_size_from_backend',
+- unit_test_utils.fake_get_size_from_backend)
+- request = unit_test_utils.get_fake_request()
+- changes = [{'op': 'replace', 'path': ['locations'], 'value': []}]
+- output = self.controller.update(request, UUID1, changes)
+- self.assertEqual(UUID1, output.image_id)
+- self.assertEqual(0, len(output.locations))
+- self.assertEqual('queued', output.status)
+- self.assertIsNone(output.size)
+-
+- new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
+- changes = [{'op': 'replace', 'path': ['locations'],
+- 'value': [new_location]}]
+- output = self.controller.update(request, UUID1, changes)
+- self.assertEqual(UUID1, output.image_id)
+- self.assertEqual(1, len(output.locations))
+- self.assertEqual(new_location, output.locations[0])
+- self.assertEqual('active', output.status)
+-
+ def test_update_replace_locations_non_empty(self):
+ new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
+ request = unit_test_utils.get_fake_request()
+@@ -1448,35 +1428,9 @@ class TestImagesController(base.IsolatedUnitTest):
+ def test_update_replace_locations_invalid(self):
+ request = unit_test_utils.get_fake_request()
+ changes = [{'op': 'replace', 'path': ['locations'], 'value': []}]
+- output = self.controller.update(request, UUID1, changes)
+- self.assertEqual(UUID1, output.image_id)
+- self.assertEqual(0, len(output.locations))
+- self.assertEqual('queued', output.status)
+-
+- request = unit_test_utils.get_fake_request()
+- changes = [{'op': 'replace', 'path': ['locations'],
+- 'value': [{'url': 'unknow://foo', 'metadata': {}}]}]
+- self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
++ self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
+ request, UUID1, changes)
+
+- def test_update_replace_locations_status_exception(self):
+- self.stubs.Set(store, 'get_size_from_backend',
+- unit_test_utils.fake_get_size_from_backend)
+- request = unit_test_utils.get_fake_request()
+- changes = [{'op': 'replace', 'path': ['locations'], 'value': []}]
+- output = self.controller.update(request, UUID2, changes)
+- self.assertEqual(UUID2, output.image_id)
+- self.assertEqual(0, len(output.locations))
+- self.assertEqual('queued', output.status)
+-
+- self.db.image_update(None, UUID2, {'disk_format': None})
+-
+- new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
+- changes = [{'op': 'replace', 'path': ['locations'],
+- 'value': [new_location]}]
+- self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
+- request, UUID2, changes)
+-
+ def test_update_add_property(self):
+ request = unit_test_utils.get_fake_request()
+
+@@ -1600,24 +1554,6 @@ class TestImagesController(base.IsolatedUnitTest):
+ self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
+ request, UUID1, changes)
+
+- def test_update_add_locations_status_exception(self):
+- self.stubs.Set(store, 'get_size_from_backend',
+- unit_test_utils.fake_get_size_from_backend)
+- request = unit_test_utils.get_fake_request()
+- changes = [{'op': 'replace', 'path': ['locations'], 'value': []}]
+- output = self.controller.update(request, UUID2, changes)
+- self.assertEqual(UUID2, output.image_id)
+- self.assertEqual(0, len(output.locations))
+- self.assertEqual('queued', output.status)
+-
+- self.db.image_update(None, UUID2, {'disk_format': None})
+-
+- new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
+- changes = [{'op': 'add', 'path': ['locations', '-'],
+- 'value': new_location}]
+- self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
+- request, UUID2, changes)
+-
+ def test_update_add_duplicate_locations(self):
+ new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
+ request = unit_test_utils.get_fake_request()
+@@ -1631,23 +1567,6 @@ class TestImagesController(base.IsolatedUnitTest):
+ self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
+ request, UUID1, changes)
+
+- def test_update_replace_duplicate_locations(self):
+- self.stubs.Set(store, 'get_size_from_backend',
+- unit_test_utils.fake_get_size_from_backend)
+- request = unit_test_utils.get_fake_request()
+- changes = [{'op': 'replace', 'path': ['locations'], 'value': []}]
+- output = self.controller.update(request, UUID1, changes)
+- self.assertEqual(UUID1, output.image_id)
+- self.assertEqual(0, len(output.locations))
+- self.assertEqual('queued', output.status)
+-
+- new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
+- changes = [{'op': 'replace', 'path': ['locations'],
+- 'value': [new_location, new_location]}]
+-
+- self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
+- request, UUID1, changes)
+-
+ def test_update_add_too_many_locations(self):
+ self.config(image_location_quota=1)
+ request = unit_test_utils.get_fake_request()
+@@ -1748,9 +1667,12 @@ class TestImagesController(base.IsolatedUnitTest):
+ {'op': 'add', 'path': ['locations', '-'],
+ 'value': {'url': '%s/fake_location_1' % BASE_URI,
+ 'metadata': {}}},
++ {'op': 'add', 'path': ['locations', '-'],
++ 'value': {'url': '%s/fake_location_2' % BASE_URI,
++ 'metadata': {}}},
+ ]
+ self.controller.update(request, UUID1, changes)
+- self.config(image_location_quota=1)
++ self.config(image_location_quota=2)
+
+ # We must remove two properties to avoid being
+ # over the limit of 1 property
+@@ -1763,8 +1685,8 @@ class TestImagesController(base.IsolatedUnitTest):
+ ]
+ output = self.controller.update(request, UUID1, changes)
+ self.assertEqual(UUID1, output.image_id)
+- self.assertEqual(1, len(output.locations))
+- self.assertIn('fake_location_3', output.locations[0]['url'])
++ self.assertEqual(2, len(output.locations))
++ self.assertIn('fake_location_3', output.locations[1]['url'])
+ self.assertNotEqual(output.created_at, output.updated_at)
+
+ def test_update_remove_base_property(self):
+@@ -1805,24 +1727,23 @@ class TestImagesController(base.IsolatedUnitTest):
+ unit_test_utils.fake_get_size_from_backend)
+
+ request = unit_test_utils.get_fake_request()
+- changes = [{'op': 'remove', 'path': ['locations', '0']}]
+- output = self.controller.update(request, UUID1, changes)
+- self.assertEqual(output.image_id, UUID1)
+- self.assertEqual(0, len(output.locations))
+- self.assertEqual('queued', output.status)
+- self.assertIsNone(output.size)
+-
+ new_location = {'url': '%s/fake_location' % BASE_URI, 'metadata': {}}
+ changes = [{'op': 'add', 'path': ['locations', '-'],
+ 'value': new_location}]
++ self.controller.update(request, UUID1, changes)
++ changes = [{'op': 'remove', 'path': ['locations', '0']}]
+ output = self.controller.update(request, UUID1, changes)
+ self.assertEqual(UUID1, output.image_id)
+ self.assertEqual(1, len(output.locations))
+- self.assertEqual(new_location, output.locations[0])
+ self.assertEqual('active', output.status)
+
+ def test_update_remove_location_invalid_pos(self):
+ request = unit_test_utils.get_fake_request()
++ changes = [
++ {'op': 'add', 'path': ['locations', '-'],
++ 'value': {'url': '%s/fake_location' % BASE_URI,
++ 'metadata': {}}}]
++ self.controller.update(request, UUID1, changes)
+ changes = [{'op': 'remove', 'path': ['locations', None]}]
+ self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
+ request, UUID1, changes)
+@@ -1844,6 +1765,11 @@ class TestImagesController(base.IsolatedUnitTest):
+ fake_delete_image_location_from_backend)
+
+ request = unit_test_utils.get_fake_request()
++ changes = [
++ {'op': 'add', 'path': ['locations', '-'],
++ 'value': {'url': '%s/fake_location' % BASE_URI,
++ 'metadata': {}}}]
++ self.controller.update(request, UUID1, changes)
+ changes = [{'op': 'remove', 'path': ['locations', '0']}]
+ self.assertRaises(webob.exc.HTTPInternalServerError,
+ self.controller.update, request, UUID1, changes)
+@@ -2137,16 +2063,6 @@ class TestImagesControllerPolicies(base.IsolatedUnitTest):
+ self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
+ request, UUID1, changes)
+
+- self.stubs.Set(self.store_utils, 'delete_image_location_from_backend',
+- fake_delete_image_location_from_backend)
+-
+- changes = [{'op': 'replace', 'path': ['locations'], 'value': []}]
+- self.controller.update(request, UUID1, changes)
+- changes = [{'op': 'replace', 'path': ['locations'],
+- 'value': [new_location]}]
+- self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
+- request, UUID1, changes)
+-
+ def test_update_delete_image_location_unauthorized(self):
+ rules = {"delete_image_location": False}
+ self.policy.set_rules(rules)
+diff --git a/releasenotes/notes/Prevent-removing-last-image-location-d5ee3e00efe14f34.yaml b/releasenotes/notes/Prevent-removing-last-image-location-d5ee3e00efe14f34.yaml
+new file mode 100644
+index 0000000..344e6e5
+--- /dev/null
++++ b/releasenotes/notes/Prevent-removing-last-image-location-d5ee3e00efe14f34.yaml
+@@ -0,0 +1,10 @@
++---
++security:
++ - Fixing bug 1525915; image might be transitioning
++ from active to queued by regular user by removing
++ last location of image (or replacing locations
++ with empty list). This allows user to re-upload
++ data to the image breaking Glance's promise of
++ image data immutability. From now on, last
++ location cannot be removed and locations cannot
++ be replaced with empty list.
+--
+1.9.1
+