CVE-2012-4573: Permissions, Privileges, and Access Controls (CWE-264)
value: The v1 API in OpenStack Glance Grizzly, Folsom (2012.2), and Essex (2012.1) allows remote authenticated users to delete arbitrary non-protected images via an image deletion request, a different vulnerability than CVE-2012-5482.
CWE description: Weaknesses in this category are related to the management of permissions, privileges, and other security features that are used to perform access control.
Published: 2012-11-11T13:00Z
Repository: https://github.com/openstack/glance
Language: Python
Files changed: glance/api/v1/images.py, glance/tests/stubs.py, glance/tests/unit/v1/test_api.py, glance/tests/utils.py
Commit date: 2012-11-07 10:01:44 -0500
Commit message: Delete from store after registry delete.

Because we rely on the registry to determine authorization in the glance
v1 api, we must attempt a registry delete before deleting an image from
the image store.

This patch includes the test for the bug, which was posted separately
on the bug.

Fixes bug 1065187.

Change-Id: I1a06b7c7421524066c684539e2f3516c4ed2c475

Diff: diff --git a/glance/api/v1/images.py b/glance/api/v1/images.py
index bc2f37e7..cd3cf35c 100644
--- a/glance/api/v1/images.py
+++ b/glance/api/v1/images.py
@@ -825,26 +825,32 @@ class Controller(controller.BaseController):
             msg = _("Forbidden to delete a deleted image.")
             LOG.debug(msg)
             raise HTTPForbidden(explanation=msg, request=req,
                                 content_type="text/plain")
 
-        status = 'deleted'
+        if image['location'] and CONF.delayed_delete:
+            status = 'pending_delete'
+        else:
+            status = 'deleted'
+
         try:
+            # Delete the image from the registry first, since we rely on it
+            # for authorization checks.
+            # See https://bugs.launchpad.net/glance/+bug/1065187
+            registry.update_image_metadata(req.context, id, {'status': status})
+            registry.delete_image_metadata(req.context, id)
+
             # The image's location field may be None in the case
             # of a saving or queued image, therefore don't ask a backend
             # to delete the image if the backend doesn't yet store it.
             # See https://bugs.launchpad.net/glance/+bug/747799
             if image['location']:
                 if CONF.delayed_delete:
-                    status = 'pending_delete'
                     schedule_delayed_delete_from_backend(image['location'], id)
                 else:
                     safe_delete_from_backend(image['location'],
                                              req.context, id)
-
-            registry.update_image_metadata(req.context, id, {'status': status})
-            registry.delete_image_metadata(req.context, id)
         except exception.NotFound, e:
             msg = ("Failed to find image to delete: %(e)s" % locals())
             for line in msg.split('\n'):
                 LOG.info(line)
             raise HTTPNotFound(explanation=msg,
diff --git a/glance/tests/stubs.py b/glance/tests/stubs.py
index fecea11e..92dcf90a 100644
--- a/glance/tests/stubs.py
+++ b/glance/tests/stubs.py
@@ -58,11 +58,17 @@ class FakeRegistryConnection(object):
         if body:
             self.req.body = body
 
     def getresponse(self):
         mapper = routes.Mapper()
-        api = context.UnauthenticatedContextMiddleware(rserver.API(mapper))
+        server = rserver.API(mapper)
+        # NOTE(markwash): we need to pass through context auth information if
+        # we have it.
+        if 'X-Auth-Token' in self.req.headers:
+            api = utils.FakeAuthMiddleware(server)
+        else:
+            api = context.UnauthenticatedContextMiddleware(server)
         webob_res = self.req.get_response(api)
 
         return utils.FakeHTTPResponse(status=webob_res.status_int,
                                       headers=webob_res.headers,
                                       data=webob_res.body)
diff --git a/glance/tests/unit/v1/test_api.py b/glance/tests/unit/v1/test_api.py
index 9a088e3b..2d3e8ee2 100644
--- a/glance/tests/unit/v1/test_api.py
+++ b/glance/tests/unit/v1/test_api.py
@@ -3013,10 +3013,30 @@ class TestGlanceAPI(base.IsolatedUnitTest):
         req = webob.Request.blank("/images/%s" % _gen_uuid())
         req.method = 'DELETE'
         res = req.get_response(self.api)
         self.assertEquals(res.status_int, webob.exc.HTTPNotFound.code)
 
+    def test_delete_not_allowed(self):
+        # Verify we can get the image data
+        req = webob.Request.blank("/images/%s" % UUID2)
+        req.method = 'GET'
+        req.headers['X-Auth-Token'] = 'user:tenant:'
+        res = req.get_response(self.api)
+        self.assertEqual(res.status_int, 200)
+        self.assertEqual(len(res.body), 19)
+
+        # Verify we cannot delete the image
+        req.method = 'DELETE'
+        res = req.get_response(self.api)
+        self.assertEqual(res.status_int, 403)
+
+        # Verify the image data is still there
+        req.method = 'GET'
+        res = req.get_response(self.api)
+        self.assertEqual(res.status_int, 200)
+        self.assertEqual(len(res.body), 19)
+
     def test_delete_queued_image(self):
         """Delete an image in a queued state
 
         Bug #747799 demonstrated that trying to DELETE an image
         that had had its save process killed manually results in failure
diff --git a/glance/tests/utils.py b/glance/tests/utils.py
index 26257817..0751d318 100644
--- a/glance/tests/utils.py
+++ b/glance/tests/utils.py
@@ -370,10 +370,11 @@ class FakeAuthMiddleware(wsgi.Middleware):
         kwargs = {
             'user': user,
             'tenant': tenant,
             'roles': roles,
             'is_admin': self.is_admin,
+            'auth_tok': auth_tok,
         }
 
         req.context = context.RequestContext(**kwargs)
 
 

Vulnerable code:         status = 'deleted'
                    status = 'pending_delete'

            registry.update_image_metadata(req.context, id, {'status': status})
            registry.delete_image_metadata(req.context, id)
        api = context.UnauthenticatedContextMiddleware(rserver.API(mapper))
Fixed code:         if image['location'] and CONF.delayed_delete:
            status = 'pending_delete'
        else:
            status = 'deleted'

            # Delete the image from the registry first, since we rely on it
            # for authorization checks.
            # See https://bugs.launchpad.net/glance/+bug/1065187
            registry.update_image_metadata(req.context, id, {'status': status})
            registry.delete_image_metadata(req.context, id)

        server = rserver.API(mapper)
        # NOTE(markwash): we need to pass through context auth information if
        # we have it.
        if 'X-Auth-Token' in self.req.headers:
            api = utils.FakeAuthMiddleware(server)
        else:
            api = context.UnauthenticatedContextMiddleware(server)
    def test_delete_not_allowed(self):
        # Verify we can get the image data
        req = webob.Request.blank("/images/%s" % UUID2)
        req.method = 'GET'
        req.headers['X-Auth-Token'] = 'user:tenant:'
        res = req.get_response(self.api)
        self.assertEqual(res.status_int, 200)
        self.assertEqual(len(res.body), 19)

        # Verify we cannot delete the image
        req.method = 'DELETE'
        res = req.get_response(self.api)
        self.assertEqual(res.status_int, 403)

        # Verify the image data is still there
        req.method = 'GET'
        res = req.get_response(self.api)
        self.assertEqual(res.status_int, 200)
        self.assertEqual(len(res.body), 19)

            'auth_tok': auth_tok,