CVE-2012-2101: Permissions, Privileges, and Access Controls (CWE-264)
value: Openstack Compute (Nova) Folsom, 2012.1, and 2011.3 does not limit the number of security group rules, which allows remote authenticated users with certain permissions to cause a denial of service (CPU and hard drive consumption) via a network request that triggers a large number of iptables rules.
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-06-07T19:55Z
Repository: https://github.com/openstack/nova
Language: Python
Files changed: nova/api/ec2/cloud.py, nova/api/openstack/compute/contrib/security_groups.py, nova/db/api.py, nova/db/sqlalchemy/api.py, nova/quota.py, nova/tests/api/ec2/test_cloud.py, nova/tests/api/openstack/compute/contrib/test_quota_classes.py, nova/tests/api/openstack/compute/contrib/test_quotas.py, nova/tests/api/openstack/compute/contrib/test_security_groups.py, nova/tests/test_quota.py
Commit date: 2012-04-19 01:18:14 -0700
Commit message: Implement security group quotas.

Fixes LP Bug #969545

Change-Id: I60d4300aa04597e2d8b0eea31ab0303b0b3c48f9

Diff: diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index 9346d107b..012afb9be 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -39,10 +39,11 @@ from nova import db
 from nova import exception
 from nova import flags
 from nova.image import s3
 from nova import log as logging
 from nova import network
+from nova import quota
 from nova import utils
 from nova import volume
 
 
 FLAGS = flags.FLAGS
@@ -723,10 +724,17 @@ class CloudController(object):
                                                     values_for_rule):
                     err = _('%s - This rule already exists in group')
                     raise exception.EC2APIError(err % values_for_rule)
                 postvalues.append(values_for_rule)
 
+        allowed = quota.allowed_security_group_rules(context,
+                                                   security_group['id'],
+                                                   1)
+        if allowed < 1:
+            msg = _("Quota exceeded, too many security group rules.")
+            raise exception.EC2APIError(msg)
+
         rule_ids = []
         for values_for_rule in postvalues:
             security_group_rule = db.security_group_rule_create(
                     context,
                     values_for_rule)
@@ -780,10 +788,14 @@ class CloudController(object):
         self.compute_api.ensure_default_security_group(context)
         if db.security_group_exists(context, context.project_id, group_name):
             msg = _('group %s already exists')
             raise exception.EC2APIError(msg % group_name)
 
+        if quota.allowed_security_groups(context, 1) < 1:
+            msg = _("Quota exceeded, too many security groups.")
+            raise exception.EC2APIError(msg)
+
         group = {'user_id': context.user_id,
                  'project_id': context.project_id,
                  'name': group_name,
                  'description': group_description}
         group_ref = db.security_group_create(context, group)
diff --git a/nova/api/openstack/compute/contrib/security_groups.py b/nova/api/openstack/compute/contrib/security_groups.py
index 0d85c7b87..281cc8c71 100644
--- a/nova/api/openstack/compute/contrib/security_groups.py
+++ b/nova/api/openstack/compute/contrib/security_groups.py
@@ -29,10 +29,11 @@ from nova.api.openstack import xmlutil
 from nova import compute
 from nova import db
 from nova import exception
 from nova import flags
 from nova import log as logging
+from nova import quota
 from nova import utils
 
 
 LOG = logging.getLogger(__name__)
 FLAGS = flags.FLAGS
@@ -287,10 +288,14 @@ class SecurityGroupController(SecurityGroupControllerBase):
         self._validate_security_group_property(group_description,
                                                "description")
         group_name = group_name.strip()
         group_description = group_description.strip()
 
+        if quota.allowed_security_groups(context, 1) < 1:
+            msg = _("Quota exceeded, too many security groups.")
+            raise exc.HTTPBadRequest(explanation=msg)
+
         LOG.audit(_("Create Security Group %s"), group_name, context=context)
         self.compute_api.ensure_default_security_group(context)
         if db.security_group_exists(context, context.project_id, group_name):
             msg = _('Security group %s already exists') % group_name
             raise exc.HTTPBadRequest(explanation=msg)
@@ -374,10 +379,17 @@ class SecurityGroupRulesController(SecurityGroupControllerBase):
 
         if self._security_group_rule_exists(security_group, values):
             msg = _('This rule already exists in group %s') % parent_group_id
             raise exc.HTTPBadRequest(explanation=msg)
 
+        allowed = quota.allowed_security_group_rules(context,
+                                                   parent_group_id,
+                                                   1)
+        if allowed < 1:
+            msg = _("Quota exceeded, too many security group rules.")
+            raise exc.HTTPBadRequest(explanation=msg)
+
         security_group_rule = db.security_group_rule_create(context, values)
         self.sgh.trigger_security_group_rule_create_refresh(
             context, [security_group_rule['id']])
         self.compute_api.trigger_security_group_rules_refresh(context,
                                     security_group_id=security_group['id'])
diff --git a/nova/db/api.py b/nova/db/api.py
index f217a9e56..9f3cf5fb1 100644
--- a/nova/db/api.py
+++ b/nova/db/api.py
@@ -1149,10 +1149,15 @@ def security_group_create(context, values):
 def security_group_destroy(context, security_group_id):
     """Deletes a security group."""
     return IMPL.security_group_destroy(context, security_group_id)
 
 
+def security_group_count_by_project(context, project_id):
+    """Count number of security groups in a project."""
+    return IMPL.security_group_count_by_project(context, project_id)
+
+
 ####################
 
 
 def security_group_rule_create(context, values):
     """Create a new security group."""
@@ -1180,10 +1185,15 @@ def security_group_rule_destroy(context, security_group_rule_id):
 def security_group_rule_get(context, security_group_rule_id):
     """Gets a security group rule."""
     return IMPL.security_group_rule_get(context, security_group_rule_id)
 
 
+def security_group_rule_count_by_group(context, security_group_id):
+    """Count rules in a given security group."""
+    return IMPL.security_group_rule_count_by_group(context, security_group_id)
+
+
 ###################
 
 
 def provider_fw_rule_create(context, rule):
     """Add a firewall rule at the provider level (all hosts & instances)."""
diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py
index ff1b5a3a4..2e0278bcc 100644
--- a/nova/db/sqlalchemy/api.py
+++ b/nova/db/sqlalchemy/api.py
@@ -2901,10 +2901,17 @@ def security_group_destroy(context, security_group_id):
                 update({'deleted': True,
                         'deleted_at': utils.utcnow(),
                         'updated_at': literal_column('updated_at')})
 
 
+@require_context
+def security_group_count_by_project(context, project_id):
+    authorize_project_context(context, project_id)
+    return model_query(context, models.SecurityGroup, read_deleted="no").\
+                   filter_by(project_id=project_id).\
+                   count()
+
 ###################
 
 
 def _security_group_rule_get_query(context, session=None):
     return model_query(context, models.SecurityGroupIngressRule,
@@ -2959,10 +2966,18 @@ def security_group_rule_destroy(context, security_group_rule_id):
                                                       security_group_rule_id,
                                                       session=session)
         security_group_rule.delete(session=session)
 
 
+@require_context
+def security_group_rule_count_by_group(context, security_group_id):
+    return model_query(context, models.SecurityGroupIngressRule,
+                   read_deleted="no").\
+                   filter_by(parent_group_id=security_group_id).\
+                   count()
+
+#
 ###################
 
 
 @require_admin_context
 def provider_fw_rule_create(context, rule):
diff --git a/nova/quota.py b/nova/quota.py
index a5ee5cdd0..9f43f722c 100644
--- a/nova/quota.py
+++ b/nova/quota.py
@@ -52,19 +52,25 @@ quota_opts = [
                default=10 * 1024,
                help='number of bytes allowed per injected file'),
     cfg.IntOpt('quota_injected_file_path_bytes',
                default=255,
                help='number of bytes allowed per injected file path'),
+    cfg.IntOpt('quota_security_groups',
+               default=10,
+               help='number of security groups per project'),
+    cfg.IntOpt('quota_security_group_rules',
+               default=20,
+               help='number of security rules per security group'),
     ]
 
 FLAGS = flags.FLAGS
 FLAGS.register_opts(quota_opts)
 
 
 quota_resources = ['metadata_items', 'injected_file_content_bytes',
         'volumes', 'gigabytes', 'ram', 'floating_ips', 'instances',
-        'injected_files', 'cores']
+        'injected_files', 'cores', 'security_groups', 'security_group_rules']
 
 
 def _get_default_quotas():
     defaults = {
         'instances': FLAGS.quota_instances,
@@ -75,10 +81,12 @@ def _get_default_quotas():
         'floating_ips': FLAGS.quota_floating_ips,
         'metadata_items': FLAGS.quota_metadata_items,
         'injected_files': FLAGS.quota_injected_files,
         'injected_file_content_bytes':
             FLAGS.quota_injected_file_content_bytes,
+        'security_groups': FLAGS.quota_security_groups,
+        'security_group_rules': FLAGS.quota_security_group_rules,
     }
     # -1 in the quota flags means unlimited
     return defaults
 
 
@@ -168,10 +176,36 @@ def allowed_floating_ips(context, requested_floating_ips):
                                                   used_floating_ips,
                                                   quota['floating_ips'])
     return min(requested_floating_ips, allowed_floating_ips)
 
 
+def allowed_security_groups(context, requested_security_groups):
+    """Check quota and return min(requested, allowed) security groups."""
+    project_id = context.project_id
+    context = context.elevated()
+    used_sec_groups = db.security_group_count_by_project(context, project_id)
+    quota = get_project_quotas(context, project_id)
+    allowed_sec_groups = _get_request_allotment(requested_security_groups,
+                                                  used_sec_groups,
+                                                  quota['security_groups'])
+    return min(requested_security_groups, allowed_sec_groups)
+
+
+def allowed_security_group_rules(context, security_group_id,
+        requested_rules):
+    """Check quota and return min(requested, allowed) sec group rules."""
+    project_id = context.project_id
+    context = context.elevated()
+    used_rules = db.security_group_rule_count_by_group(context,
+                                                            security_group_id)
+    quota = get_project_quotas(context, project_id)
+    allowed_rules = _get_request_allotment(requested_rules,
+                                              used_rules,
+                                              quota['security_group_rules'])
+    return min(requested_rules, allowed_rules)
+
+
 def _calculate_simple_quota(context, resource, requested):
     """Check quota for resource; return min(requested, allowed)."""
     quota = get_project_quotas(context, context.project_id)
     allowed = _get_request_allotment(requested, 0, quota[resource])
     return min(requested, allowed)
diff --git a/nova/tests/api/ec2/test_cloud.py b/nova/tests/api/ec2/test_cloud.py
index 6ac0ed07c..78ca7833c 100644
--- a/nova/tests/api/ec2/test_cloud.py
+++ b/nova/tests/api/ec2/test_cloud.py
@@ -269,10 +269,22 @@ class CloudTestCase(test.TestCase):
         group_descript = result['securityGroupSet'][0]['groupDescription']
         self.assertEqual(descript, group_descript)
         delete = self.cloud.delete_security_group
         self.assertTrue(delete(self.context, 'testgrp'))
 
+    def test_security_group_quota_limit(self):
+        self.flags(quota_security_groups=10)
+        for i in range(1, 10):
+            name = 'test name %i' % i
+            descript = 'test description %i' % i
+            create = self.cloud.create_security_group
+            result = create(self.context, name, descript)
+
+        # 11'th group should fail
+        self.assertRaises(exception.EC2APIError,
+                          create, self.context, 'foo', 'bar')
+
     def test_delete_security_group_by_id(self):
         sec = db.security_group_create(self.context,
                                        {'project_id': self.context.project_id,
                                         'name': 'test'})
         delete = self.cloud.delete_security_group
@@ -434,10 +446,23 @@ class CloudTestCase(test.TestCase):
         kwargs = {'to_port': '999', 'from_port': '999', 'ip_protocol': 'tcp'}
         authz(self.context, group_name=sec['name'], **kwargs)
         self.assertRaises(exception.EC2APIError, authz, self.context,
                           group_name=sec['name'], **kwargs)
 
+    def test_security_group_ingress_quota_limit(self):
+        self.flags(quota_security_group_rules=20)
+        kwargs = {'project_id': self.context.project_id, 'name': 'test'}
+        sec_group = db.security_group_create(self.context, kwargs)
+        authz = self.cloud.authorize_security_group_ingress
+        for i in range(100, 120):
+            kwargs = {'to_port': i, 'from_port': i, 'ip_protocol': 'tcp'}
+            authz(self.context, group_id=sec_group['id'], **kwargs)
+
+        kwargs = {'to_port': 121, 'from_port': 121, 'ip_protocol': 'tcp'}
+        self.assertRaises(exception.EC2APIError, authz, self.context,
+                              group_id=sec_group['id'], **kwargs)
+
     def _test_authorize_security_group_no_ports_with_source_group(self, proto):
         kwargs = {'project_id': self.context.project_id, 'name': 'test'}
         sec = db.security_group_create(self.context, kwargs)
 
         authz = self.cloud.authorize_security_group_ingress
diff --git a/nova/tests/api/openstack/compute/contrib/test_quota_classes.py b/nova/tests/api/openstack/compute/contrib/test_quota_classes.py
index e29d266e3..d922152e0 100644
--- a/nova/tests/api/openstack/compute/contrib/test_quota_classes.py
+++ b/nova/tests/api/openstack/compute/contrib/test_quota_classes.py
@@ -24,11 +24,12 @@ from nova.tests.api.openstack import fakes
 
 def quota_set(class_name):
     return {'quota_class_set': {'id': class_name, 'metadata_items': 128,
             'volumes': 10, 'gigabytes': 1000, 'ram': 51200,
             'floating_ips': 10, 'instances': 10, 'injected_files': 5,
-            'cores': 20, 'injected_file_content_bytes': 10240}}
+            'cores': 20, 'injected_file_content_bytes': 10240,
+            'security_groups': 10, 'security_group_rules': 20}}
 
 
 class QuotaClassSetsTest(test.TestCase):
 
     def setUp(self):
@@ -43,11 +44,14 @@ class QuotaClassSetsTest(test.TestCase):
             'volumes': 10,
             'floating_ips': 10,
             'metadata_items': 128,
             'gigabytes': 1000,
             'injected_files': 5,
-            'injected_file_content_bytes': 10240}
+            'injected_file_content_bytes': 10240,
+            'security_groups': 10,
+            'security_group_rules': 20,
+            }
 
         quota_set = self.controller._format_quota_set('test_class',
                                                       raw_quota_set)
         qs = quota_set['quota_class_set']
 
@@ -59,10 +63,12 @@ class QuotaClassSetsTest(test.TestCase):
         self.assertEqual(qs['gigabytes'], 1000)
         self.assertEqual(qs['floating_ips'], 10)
         self.assertEqual(qs['metadata_items'], 128)
         self.assertEqual(qs['injected_files'], 5)
         self.assertEqual(qs['injected_file_content_bytes'], 10240)
+        self.assertEqual(qs['security_groups'], 10)
+        self.assertEqual(qs['security_group_rules'], 20)
 
     def test_quotas_show_as_admin(self):
         req = fakes.HTTPRequest.blank(
             '/v2/fake4/os-quota-class-sets/test_class',
             use_admin_context=True)
@@ -79,11 +85,14 @@ class QuotaClassSetsTest(test.TestCase):
     def test_quotas_update_as_admin(self):
         body = {'quota_class_set': {'instances': 50, 'cores': 50,
                                     'ram': 51200, 'volumes': 10,
                                     'gigabytes': 1000, 'floating_ips': 10,
                                     'metadata_items': 128, 'injected_files': 5,
-                                    'injected_file_content_bytes': 10240}}
+                                    'injected_file_content_bytes': 10240,
+                                    'security_groups': 10,
+                                    'security_group_rules': 20,
+                                    }}
 
         req = fakes.HTTPRequest.blank(
             '/v2/fake4/os-quota-class-sets/test_class',
             use_admin_context=True)
         res_dict = self.controller.update(req, 'test_class', body)
@@ -93,11 +102,14 @@ class QuotaClassSetsTest(test.TestCase):
     def test_quotas_update_as_user(self):
         body = {'quota_class_set': {'instances': 50, 'cores': 50,
                                     'ram': 51200, 'volumes': 10,
                                     'gigabytes': 1000, 'floating_ips': 10,
                                     'metadata_items': 128, 'injected_files': 5,
-                                    'injected_file_content_bytes': 10240}}
+                                    'injected_file_content_bytes': 10240,
+                                    'security_groups': 10,
+                                    'security_group_rules': 20,
+                                    }}
 
         req = fakes.HTTPRequest.blank(
             '/v2/fake4/os-quota-class-sets/test_class')
         self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
                           req, 'test_class', body)
@@ -118,10 +130,12 @@ class QuotaTemplateXMLSerializerTest(test.TestCase):
                 gigabytes=40,
                 ram=50,
                 floating_ips=60,
                 instances=70,
                 injected_files=80,
+                security_groups=10,
+                security_group_rules=20,
                 cores=90))
         text = self.serializer.serialize(exemplar)
 
         print text
         tree = etree.fromstring(text)
@@ -142,10 +156,12 @@ class QuotaTemplateXMLSerializerTest(test.TestCase):
                 gigabytes='40',
                 ram='50',
                 floating_ips='60',
                 instances='70',
                 injected_files='80',
+                security_groups='10',
+                security_group_rules='20',
                 cores='90'))
         intext = ("<?xml version='1.0' encoding='UTF-8'?>\n"
                   '<quota_class_set>'
                   '<metadata_items>10</metadata_items>'
                   '<injected_file_content_bytes>20'
@@ -155,9 +171,11 @@ class QuotaTemplateXMLSerializerTest(test.TestCase):
                   '<ram>50</ram>'
                   '<floating_ips>60</floating_ips>'
                   '<instances>70</instances>'
                   '<injected_files>80</injected_files>'
                   '<cores>90</cores>'
+                  '<security_groups>10</security_groups>'
+                  '<security_group_rules>20</security_group_rules>'
                   '</quota_class_set>')
 
         result = self.deserializer.deserialize(intext)['body']
         self.assertEqual(result, exemplar)
diff --git a/nova/tests/api/openstack/compute/contrib/test_quotas.py b/nova/tests/api/openstack/compute/contrib/test_quotas.py
index ea34a4e86..b603ae684 100644
--- a/nova/tests/api/openstack/compute/contrib/test_quotas.py
+++ b/nova/tests/api/openstack/compute/contrib/test_quotas.py
@@ -26,11 +26,12 @@ from nova.tests.api.openstack import fakes
 
 def quota_set(id):
     return {'quota_set': {'id': id, 'metadata_items': 128, 'volumes': 10,
             'gigabytes': 1000, 'ram': 51200, 'floating_ips': 10,
             'instances': 10, 'injected_files': 5, 'cores': 20,
-            'injected_file_content_bytes': 10240}}
+            'injected_file_content_bytes': 10240,
+            'security_groups': 10, 'security_group_rules': 20}}
 
 
 class QuotaSetsTest(test.TestCase):
 
     def setUp(self):
@@ -45,11 +46,14 @@ class QuotaSetsTest(test.TestCase):
             'volumes': 10,
             'floating_ips': 10,
             'metadata_items': 128,
             'gigabytes': 1000,
             'injected_files': 5,
-            'injected_file_content_bytes': 10240}
+            'injected_file_content_bytes': 10240,
+            'security_groups': 10,
+            'security_group_rules': 20,
+            }
 
         quota_set = self.controller._format_quota_set('1234', raw_quota_set)
         qs = quota_set['quota_set']
 
         self.assertEqual(qs['id'], '1234')
@@ -60,10 +64,12 @@ class QuotaSetsTest(test.TestCase):
         self.assertEqual(qs['gigabytes'], 1000)
         self.assertEqual(qs['floating_ips'], 10)
         self.assertEqual(qs['metadata_items'], 128)
         self.assertEqual(qs['injected_files'], 5)
         self.assertEqual(qs['injected_file_content_bytes'], 10240)
+        self.assertEqual(qs['security_groups'], 10)
+        self.assertEqual(qs['security_group_rules'], 20)
 
     def test_quotas_defaults(self):
         uri = '/v2/fake_tenant/os-quota-sets/fake_tenant/defaults'
 
         req = fakes.HTTPRequest.blank(uri)
@@ -77,11 +83,14 @@ class QuotaSetsTest(test.TestCase):
                     'volumes': 10,
                     'gigabytes': 1000,
                     'floating_ips': 10,
                     'metadata_items': 128,
                     'injected_files': 5,
-                    'injected_file_content_bytes': 10240}}
+                    'injected_file_content_bytes': 10240,
+                    'security_groups': 10,
+                    'security_group_rules': 20,
+                    }}
 
         self.assertEqual(res_dict, expected)
 
     def test_quotas_show_as_admin(self):
         req = fakes.HTTPRequest.blank('/v2/fake4/os-quota-sets/1234',
@@ -98,11 +107,13 @@ class QuotaSetsTest(test.TestCase):
     def test_quotas_update_as_admin(self):
         body = {'quota_set': {'instances': 50, 'cores': 50,
                               'ram': 51200, 'volumes': 10,
                               'gigabytes': 1000, 'floating_ips': 10,
                               'metadata_items': 128, 'injected_files': 5,
-                              'injected_file_content_bytes': 10240}}
+                              'injected_file_content_bytes': 10240,
+                              'security_groups': 10,
+                              'security_group_rules': 20}}
 
         req = fakes.HTTPRequest.blank('/v2/fake4/os-quota-sets/update_me',
                                       use_admin_context=True)
         res_dict = self.controller.update(req, 'update_me', body)
 
@@ -111,11 +122,13 @@ class QuotaSetsTest(test.TestCase):
     def test_quotas_update_as_user(self):
         body = {'quota_set': {'instances': 50, 'cores': 50,
                               'ram': 51200, 'volumes': 10,
                               'gigabytes': 1000, 'floating_ips': 10,
                               'metadata_items': 128, 'injected_files': 5,
-                              'injected_file_content_bytes': 10240}}
+                              'injected_file_content_bytes': 10240,
+                              'security_groups': 10,
+                              'security_group_rules': 20}}
 
         req = fakes.HTTPRequest.blank('/v2/fake4/os-quota-sets/update_me')
         self.assertRaises(webob.exc.HTTPForbidden, self.controller.update,
                           req, 'update_me', body)
 
@@ -147,10 +160,12 @@ class QuotaXMLSerializerTest(test.TestCase):
                 gigabytes=40,
                 ram=50,
                 floating_ips=60,
                 instances=70,
                 injected_files=80,
+                security_groups=10,
+                security_group_rules=20,
                 cores=90))
         text = self.serializer.serialize(exemplar)
 
         print text
         tree = etree.fromstring(text)
@@ -170,10 +185,12 @@ class QuotaXMLSerializerTest(test.TestCase):
                 gigabytes='40',
                 ram='50',
                 floating_ips='60',
                 instances='70',
                 injected_files='80',
+                security_groups='10',
+                security_group_rules='20',
                 cores='90'))
         intext = ("<?xml version='1.0' encoding='UTF-8'?>\n"
                   '<quota_set>'
                   '<metadata_items>10</metadata_items>'
                   '<injected_file_content_bytes>20'
@@ -182,10 +199,12 @@ class QuotaXMLSerializerTest(test.TestCase):
                   '<gigabytes>40</gigabytes>'
                   '<ram>50</ram>'
                   '<floating_ips>60</floating_ips>'
                   '<instances>70</instances>'
                   '<injected_files>80</injected_files>'
+                  '<security_groups>10</security_groups>'
+                  '<security_group_rules>20</security_group_rules>'
                   '<cores>90</cores>'
                   '</quota_set>')
 
         result = self.deserializer.deserialize(intext)['body']
         self.assertEqual(result, exemplar)
diff --git a/nova/tests/api/openstack/compute/contrib/test_security_groups.py b/nova/tests/api/openstack/compute/contrib/test_security_groups.py
index 0cf66ec6e..8cc4cc672 100644
--- a/nova/tests/api/openstack/compute/contrib/test_security_groups.py
+++ b/nova/tests/api/openstack/compute/contrib/test_security_groups.py
@@ -23,16 +23,19 @@ import webob
 
 from nova.api.openstack.compute.contrib import security_groups
 from nova.api.openstack import wsgi
 import nova.db
 from nova import exception
+from nova import flags
 from nova import test
 from nova.tests.api.openstack import fakes
 
 
 FAKE_UUID = 'a47ae74e-ab08-447f-8eee-ffd43fc46c16'
 
+FLAGS = flags.FLAGS
+
 
 class AttrDict(dict):
     def __getattr__(self, k):
         return self[k]
 
@@ -217,10 +220,22 @@ class TestSecurityGroups(test.TestCase):
 
         req = fakes.HTTPRequest.blank('/v2/fake/os-security-groups')
         self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create,
                           req, {'security_group': sg})
 
+    def test_create_security_group_quota_limit(self):
+        req = fakes.HTTPRequest.blank('/v2/fake/os-security-groups')
+        for num in range(1, FLAGS.quota_security_groups):
+            name = 'test%s' % num
+            sg = security_group_template(name=name)
+            res_dict = self.controller.create(req, {'security_group': sg})
+            self.assertEqual(res_dict['security_group']['name'], name)
+
+        sg = security_group_template()
+        self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create,
+                          req, {'security_group': sg})
+
     def test_get_security_group_list(self):
         groups = []
         for i, name in enumerate(['default', 'test']):
             sg = security_group_template(id=i + 1,
                                          name=name,
@@ -892,10 +907,26 @@ class TestSecurityGroupRules(test.TestCase):
         req = fakes.HTTPRequest.blank('/v2/fake/os-security-group-rules' +
                                       '/22222222222222')
         self.assertRaises(webob.exc.HTTPNotFound, self.controller.delete,
                           req, '22222222222222')
 
+    def test_create_rule_quota_limit(self):
+        req = fakes.HTTPRequest.blank('/v2/fake/os-security-group-rules')
+        for num in range(100, 100 + FLAGS.quota_security_group_rules):
+            rule = {
+                'ip_protocol': 'tcp', 'from_port': num,
+                'to_port': num, 'parent_group_id': '2', 'group_id': '1'
+            }
+            self.controller.create(req, {'security_group_rule': rule})
+
+        rule = {
+            'ip_protocol': 'tcp', 'from_port': '121', 'to_port': '121',
+            'parent_group_id': '2', 'group_id': '1'
+        }
+        self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create,
+                          req, {'security_group_rule': rule})
+
 
 class TestSecurityGroupRulesXMLDeserializer(unittest.TestCase):
 
     def setUp(self):
         self.deserializer = security_groups.SecurityGroupRulesXMLDeserializer()
diff --git a/nova/tests/test_quota.py b/nova/tests/test_quota.py
index ae3232338..06aaa26db 100644
--- a/nova/tests/test_quota.py
+++ b/nova/tests/test_quota.py
@@ -39,10 +39,12 @@ class GetQuotaTestCase(test.TestCase):
                    quota_cores=20,
                    quota_ram=50 * 1024,
                    quota_volumes=10,
                    quota_gigabytes=1000,
                    quota_floating_ips=10,
+                   quota_security_groups=10,
+                   quota_security_group_rules=20,
                    quota_metadata_items=128,
                    quota_injected_files=5,
                    quota_injected_file_content_bytes=10 * 1024)
         self.context = context.RequestContext('admin', 'admin', is_admin=True)
 
@@ -55,10 +57,12 @@ class GetQuotaTestCase(test.TestCase):
                     cores=10,
                     ram=25 * 1024,
                     volumes=5,
                     gigabytes=500,
                     floating_ips=5,
+                    quota_security_groups=10,
+                    quota_security_group_rules=20,
                     metadata_items=64,
                     injected_files=2,
                     injected_file_content_bytes=5 * 1024,
                     invalid_quota=100,
                     )
@@ -76,10 +80,12 @@ class GetQuotaTestCase(test.TestCase):
                     cores=5,
                     ram=12 * 1024,
                     volumes=2,
                     gigabytes=250,
                     floating_ips=2,
+                    security_groups=5,
+                    security_group_rules=10,
                     metadata_items=32,
                     injected_files=1,
                     injected_file_content_bytes=2 * 1024,
                     invalid_quota=50,
                     )
@@ -95,10 +101,12 @@ class GetQuotaTestCase(test.TestCase):
                 cores=20,
                 ram=50 * 1024,
                 volumes=10,
                 gigabytes=1000,
                 floating_ips=10,
+                security_groups=10,
+                security_group_rules=20,
                 metadata_items=128,
                 injected_files=5,
                 injected_file_content_bytes=10 * 1024,
                 ))
 
@@ -107,10 +115,12 @@ class GetQuotaTestCase(test.TestCase):
                    quota_cores=-1,
                    quota_ram=-1,
                    quota_volumes=-1,
                    quota_gigabytes=-1,
                    quota_floating_ips=-1,
+                   quota_security_groups=-1,
+                   quota_security_group_rules=-1,
                    quota_metadata_items=-1,
                    quota_injected_files=-1,
                    quota_injected_file_content_bytes=-1)
         result = quota._get_default_quotas()
         self.assertEqual(result, dict(
@@ -118,10 +128,12 @@ class GetQuotaTestCase(test.TestCase):
                 cores=-1,
                 ram=-1,
                 volumes=-1,
                 gigabytes=-1,
                 floating_ips=-1,
+                security_groups=-1,
+                security_group_rules=-1,
                 metadata_items=-1,
                 injected_files=-1,
                 injected_file_content_bytes=-1,
                 ))
 
@@ -133,10 +145,12 @@ class GetQuotaTestCase(test.TestCase):
                 cores=20,
                 ram=50 * 1024,
                 volumes=10,
                 gigabytes=1000,
                 floating_ips=10,
+                security_groups=10,
+                security_group_rules=20,
                 metadata_items=128,
                 injected_files=5,
                 injected_file_content_bytes=10 * 1024,
                 ))
 
@@ -148,10 +162,12 @@ class GetQuotaTestCase(test.TestCase):
                 cores=10,
                 ram=25 * 1024,
                 volumes=5,
                 gigabytes=500,
                 floating_ips=5,
+                security_groups=10,
+                security_group_rules=20,
                 metadata_items=64,
                 injected_files=2,
                 injected_file_content_bytes=5 * 1024,
                 ))
 
@@ -164,10 +180,12 @@ class GetQuotaTestCase(test.TestCase):
                 cores=20,
                 ram=50 * 1024,
                 volumes=10,
                 gigabytes=1000,
                 floating_ips=10,
+                security_groups=10,
+                security_group_rules=20,
                 metadata_items=128,
                 injected_files=5,
                 injected_file_content_bytes=10 * 1024,
                 ))
 
@@ -180,10 +198,12 @@ class GetQuotaTestCase(test.TestCase):
                 cores=5,
                 ram=12 * 1024,
                 volumes=2,
                 gigabytes=250,
                 floating_ips=2,
+                security_groups=5,
+                security_group_rules=10,
                 metadata_items=32,
                 injected_files=1,
                 injected_file_content_bytes=2 * 1024,
                 ))
 
@@ -197,10 +217,12 @@ class GetQuotaTestCase(test.TestCase):
                 cores=10,
                 ram=25 * 1024,
                 volumes=5,
                 gigabytes=500,
                 floating_ips=5,
+                security_groups=10,
+                security_group_rules=20,
                 metadata_items=64,
                 injected_files=2,
                 injected_file_content_bytes=5 * 1024,
                 ))
 
@@ -214,10 +236,12 @@ class GetQuotaTestCase(test.TestCase):
                 cores=5,
                 ram=12 * 1024,
                 volumes=2,
                 gigabytes=250,
                 floating_ips=2,
+                security_groups=5,
+                security_group_rules=10,
                 metadata_items=32,
                 injected_files=1,
                 injected_file_content_bytes=2 * 1024,
                 ))
 
@@ -402,10 +426,38 @@ class QuotaTestCase(test.TestCase):
         floating_ips = quota.allowed_floating_ips(self.context, 100)
         self.assertEqual(floating_ips, 100)
         floating_ips = quota.allowed_floating_ips(self.context, 101)
         self.assertEqual(floating_ips, 101)
 
+    def test_unlimited_security_groups(self):
+        self.flags(quota_security_groups=10)
+        security_groups = quota.allowed_security_groups(self.context, 100)
+        self.assertEqual(security_groups, 10)
+        db.quota_create(self.context, self.project_id, 'security_groups', -1)
+        security_groups = quota.allowed_security_groups(self.context, 100)
+        self.assertEqual(security_groups, 100)
+        security_groups = quota.allowed_security_groups(self.context, 101)
+        self.assertEqual(security_groups, 101)
+
+    def test_unlimited_security_group_rules(self):
+
+        def fake_security_group_rule_count_by_group(context, sec_group_id):
+            return 0
+
+        self.stubs.Set(db, 'security_group_rule_count_by_group',
+                       fake_security_group_rule_count_by_group)
+
+        self.flags(quota_security_group_rules=20)
+        rules = quota.allowed_security_group_rules(self.context, 1234, 100)
+        self.assertEqual(rules, 20)
+        db.quota_create(self.context, self.project_id, 'security_group_rules',
+                        -1)
+        rules = quota.allowed_security_group_rules(self.context, 1234, 100)
+        self.assertEqual(rules, 100)
+        rules = quota.allowed_security_group_rules(self.context, 1234, 101)
+        self.assertEqual(rules, 101)
+
     def test_unlimited_metadata_items(self):
         self.flags(quota_metadata_items=10)
         items = quota.allowed_metadata_items(self.context, 100)
         self.assertEqual(items, 10)
         db.quota_create(self.context, self.project_id, 'metadata_items', -1)

Vulnerable code:         'injected_files', 'cores']
            'cores': 20, 'injected_file_content_bytes': 10240}}
            'injected_file_content_bytes': 10240}
                                    'injected_file_content_bytes': 10240}}
                                    'injected_file_content_bytes': 10240}}
            'injected_file_content_bytes': 10240}}
            'injected_file_content_bytes': 10240}
                    'injected_file_content_bytes': 10240}}
                              'injected_file_content_bytes': 10240}}
                              'injected_file_content_bytes': 10240}}
Fixed code: from nova import quota
        allowed = quota.allowed_security_group_rules(context,
                                                   security_group['id'],
                                                   1)
        if allowed < 1:
            msg = _("Quota exceeded, too many security group rules.")
            raise exception.EC2APIError(msg)

        if quota.allowed_security_groups(context, 1) < 1:
            msg = _("Quota exceeded, too many security groups.")
            raise exception.EC2APIError(msg)

from nova import quota
        if quota.allowed_security_groups(context, 1) < 1:
            msg = _("Quota exceeded, too many security groups.")
            raise exc.HTTPBadRequest(explanation=msg)

        allowed = quota.allowed_security_group_rules(context,
                                                   parent_group_id,
                                                   1)
        if allowed < 1:
            msg = _("Quota exceeded, too many security group rules.")
            raise exc.HTTPBadRequest(explanation=msg)

def security_group_count_by_project(context, project_id):
    """Count number of security groups in a project."""
    return IMPL.security_group_count_by_project(context, project_id)


def security_group_rule_count_by_group(context, security_group_id):
    """Count rules in a given security group."""
    return IMPL.security_group_rule_count_by_group(context, security_group_id)


@require_context
def security_group_count_by_project(context, project_id):
    authorize_project_context(context, project_id)
    return model_query(context, models.SecurityGroup, read_deleted="no").\
                   filter_by(project_id=project_id).\
                   count()

@require_context
def security_group_rule_count_by_group(context, security_group_id):
    return model_query(context, models.SecurityGroupIngressRule,
                   read_deleted="no").\
                   filter_by(parent_group_id=security_group_id).\
                   count()

#
    cfg.IntOpt('quota_security_groups',
               default=10,
               help='number of security groups per project'),
    cfg.IntOpt('quota_security_group_rules',
               default=20,
               help='number of security rules per security group'),
        'injected_files', 'cores', 'security_groups', 'security_group_rules']
        'security_groups': FLAGS.quota_security_groups,
        'security_group_rules': FLAGS.quota_security_group_rules,
def allowed_security_groups(context, requested_security_groups):
    """Check quota and return min(requested, allowed) security groups."""
    project_id = context.project_id
    context = context.elevated()
    used_sec_groups = db.security_group_count_by_project(context, project_id)
    quota = get_project_quotas(context, project_id)
    allowed_sec_groups = _get_request_allotment(requested_security_groups,
                                                  used_sec_groups,
                                                  quota['security_groups'])
    return min(requested_security_groups, allowed_sec_groups)


def allowed_security_group_rules(context, security_group_id,
        requested_rules):
    """Check quota and return min(requested, allowed) sec group rules."""
    project_id = context.project_id
    context = context.elevated()
    used_rules = db.security_group_rule_count_by_group(context,
                                                            security_group_id)
    quota = get_project_quotas(context, project_id)
    allowed_rules = _get_request_allotment(requested_rules,
                                              used_rules,
                                              quota['security_group_rules'])
    return min(requested_rules, allowed_rules)


    def test_security_group_quota_limit(self):
        self.flags(quota_security_groups=10)
        for i in range(1, 10):
            name = 'test name %i' % i
            descript = 'test description %i' % i
            create = self.cloud.create_security_group
            result = create(self.context, name, descript)

        # 11'th group should fail
        self.assertRaises(exception.EC2APIError,
                          create, self.context, 'foo', 'bar')

    def test_security_group_ingress_quota_limit(self):
        self.flags(quota_security_group_rules=20)
        kwargs = {'project_id': self.context.project_id, 'name': 'test'}
        sec_group = db.security_group_create(self.context, kwargs)
        authz = self.cloud.authorize_security_group_ingress
        for i in range(100, 120):
            kwargs = {'to_port': i, 'from_port': i, 'ip_protocol': 'tcp'}
            authz(self.context, group_id=sec_group['id'], **kwargs)

        kwargs = {'to_port': 121, 'from_port': 121, 'ip_protocol': 'tcp'}
        self.assertRaises(exception.EC2APIError, authz, self.context,
                              group_id=sec_group['id'], **kwargs)

            'cores': 20, 'injected_file_content_bytes': 10240,
            'security_groups': 10, 'security_group_rules': 20}}
            'injected_file_content_bytes': 10240,
            'security_groups': 10,
            'security_group_rules': 20,
            }
        self.assertEqual(qs['security_groups'], 10)
        self.assertEqual(qs['security_group_rules'], 20)
                                    'injected_file_content_bytes': 10240,
                                    'security_groups': 10,
                                    'security_group_rules': 20,
                                    }}
                                    'injected_file_content_bytes': 10240,
                                    'security_groups': 10,
                                    'security_group_rules': 20,
                                    }}
                security_groups=10,
                security_group_rules=20,
                security_groups='10',
                security_group_rules='20',
                  '<security_groups>10</security_groups>'
                  '<security_group_rules>20</security_group_rules>'
            'injected_file_content_bytes': 10240,
            'security_groups': 10, 'security_group_rules': 20}}
            'injected_file_content_bytes': 10240,
            'security_groups': 10,
            'security_group_rules': 20,
            }
        self.assertEqual(qs['security_groups'], 10)
        self.assertEqual(qs['security_group_rules'], 20)
                    'injected_file_content_bytes': 10240,
                    'security_groups': 10,
                    'security_group_rules': 20,
                    }}
                              'injected_file_content_bytes': 10240,
                              'security_groups': 10,
                              'security_group_rules': 20}}
                              'injected_file_content_bytes': 10240,
                              'security_groups': 10,
                              'security_group_rules': 20}}
                security_groups=10,
                security_group_rules=20,
                security_groups='10',
                security_group_rules='20',
                  '<security_groups>10</security_groups>'
                  '<security_group_rules>20</security_group_rules>'
from nova import flags
FLAGS = flags.FLAGS

    def test_create_security_group_quota_limit(self):
        req = fakes.HTTPRequest.blank('/v2/fake/os-security-groups')
        for num in range(1, FLAGS.quota_security_groups):
            name = 'test%s' % num
            sg = security_group_template(name=name)
            res_dict = self.controller.create(req, {'security_group': sg})
            self.assertEqual(res_dict['security_group']['name'], name)

        sg = security_group_template()
        self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create,
                          req, {'security_group': sg})

    def test_create_rule_quota_limit(self):
        req = fakes.HTTPRequest.blank('/v2/fake/os-security-group-rules')
        for num in range(100, 100 + FLAGS.quota_security_group_rules):
            rule = {
                'ip_protocol': 'tcp', 'from_port': num,
                'to_port': num, 'parent_group_id': '2', 'group_id': '1'
            }
            self.controller.create(req, {'security_group_rule': rule})

        rule = {
            'ip_protocol': 'tcp', 'from_port': '121', 'to_port': '121',
            'parent_group_id': '2', 'group_id': '1'
        }
        self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create,
                          req, {'security_group_rule': rule})

                   quota_security_groups=10,
                   quota_security_group_rules=20,
                    quota_security_groups=10,
                    quota_security_group_rules=20,
                    security_groups=5,
                    security_group_rules=10,
                security_groups=10,
                security_group_rules=20,
                   quota_security_groups=-1,
                   quota_security_group_rules=-1,
                security_groups=-1,
                security_group_rules=-1,
                security_groups=10,
                security_group_rules=20,
                security_groups=10,
                security_group_rules=20,
                security_groups=10,
                security_group_rules=20,
                security_groups=5,
                security_group_rules=10,
                security_groups=10,
                security_group_rules=20,
                security_groups=5,
                security_group_rules=10,
    def test_unlimited_security_groups(self):
        self.flags(quota_security_groups=10)
        security_groups = quota.allowed_security_groups(self.context, 100)
        self.assertEqual(security_groups, 10)
        db.quota_create(self.context, self.project_id, 'security_groups', -1)
        security_groups = quota.allowed_security_groups(self.context, 100)
        self.assertEqual(security_groups, 100)
        security_groups = quota.allowed_security_groups(self.context, 101)
        self.assertEqual(security_groups, 101)

    def test_unlimited_security_group_rules(self):

        def fake_security_group_rule_count_by_group(context, sec_group_id):
            return 0

        self.stubs.Set(db, 'security_group_rule_count_by_group',
                       fake_security_group_rule_count_by_group)

        self.flags(quota_security_group_rules=20)
        rules = quota.allowed_security_group_rules(self.context, 1234, 100)
        self.assertEqual(rules, 20)
        db.quota_create(self.context, self.project_id, 'security_group_rules',
                        -1)
        rules = quota.allowed_security_group_rules(self.context, 1234, 100)
        self.assertEqual(rules, 100)
        rules = quota.allowed_security_group_rules(self.context, 1234, 101)
        self.assertEqual(rules, 101)