Fix recursion error in account_quota middleware

There is an infinite loop if multiple quota limits are set and exceeded,
eventually resulting in a 500 response due to a RecursionError ("maximum
recursion depth exceeded").

The issue is the delayed rejection, required to support container_acls.
If any quota is exceeded the middleware needs to return directly,
without proceeding to check other quota settings.

The fix is basically to add a "return self.app". However, there is quite
some redundant code, thus moving this into its own method.

Another test with multiple exceeded quotas has been added, which is
failing without the bugfix.

Closes-Bug: #2118758
Change-Id: I49ec4c5f6c83f36ce1d38f2f1687081c71488286
Signed-off-by: Christian Schwede <cschwede@redhat.com>
This commit is contained in:
Christian Schwede
2025-07-25 09:10:28 +00:00
parent 8af485775a
commit 06a6329793
2 changed files with 40 additions and 50 deletions

View File

@@ -102,6 +102,24 @@ class AccountQuotaMiddleware(object):
def __init__(self, app, *args, **kwargs):
self.app = app
def quota_exceeded(self, request, body):
# request.environ['swift.authorize'](req) is delayed and not called
# immediately to support container acls. However, the middleware should
# still return immediately if any quota is exceeded.
resp = HTTPRequestEntityTooLarge(body=body)
if 'swift.authorize' in request.environ:
orig_authorize = request.environ['swift.authorize']
def reject_authorize(*args, **kwargs):
aresp = orig_authorize(*args, **kwargs)
if aresp:
return aresp
return resp
request.environ['swift.authorize'] = reject_authorize
return self.app
else:
return resp
def validate_and_translate_quotas(self, request, quota_type):
new_quotas = {}
new_quotas[None] = request.headers.get(
@@ -209,18 +227,7 @@ class AccountQuotaMiddleware(object):
if quota >= 0:
new_size = int(account_info['bytes']) + content_length
if quota < new_size:
resp = HTTPRequestEntityTooLarge(body='Upload exceeds quota.')
if 'swift.authorize' in request.environ:
orig_authorize = request.environ['swift.authorize']
def reject_authorize(*args, **kwargs):
aresp = orig_authorize(*args, **kwargs)
if aresp:
return aresp
return resp
request.environ['swift.authorize'] = reject_authorize
else:
return resp
return self.quota_exceeded(request, "Upload exceeds quota.")
# Check for quota count violation
try:
@@ -230,18 +237,7 @@ class AccountQuotaMiddleware(object):
if quota >= 0:
new_count = int(account_info['total_object_count']) + 1
if quota < new_count:
resp = HTTPRequestEntityTooLarge(body='Upload exceeds quota.')
if 'swift.authorize' in request.environ:
orig_authorize = request.environ['swift.authorize']
def reject_authorize(*args, **kwargs):
aresp = orig_authorize(*args, **kwargs)
if aresp:
return aresp
return resp
request.environ['swift.authorize'] = reject_authorize
else:
return resp
return self.quota_exceeded(request, "Upload exceeds quota.")
container_info = get_container_info(request.environ, self.app,
swift_source='AQ')
@@ -259,19 +255,8 @@ class AccountQuotaMiddleware(object):
policy_stats = account_info['storage_policies'].get(policy_idx, {})
new_size = int(policy_stats.get('bytes', 0)) + content_length
if policy_quota < new_size:
resp = HTTPRequestEntityTooLarge(
body='Upload exceeds policy quota.')
if 'swift.authorize' in request.environ:
orig_authorize = request.environ['swift.authorize']
def reject_authorize(*args, **kwargs):
aresp = orig_authorize(*args, **kwargs)
if aresp:
return aresp
return resp
request.environ['swift.authorize'] = reject_authorize
else:
return resp
return self.quota_exceeded(
request, "Upload exceeds policy quota.")
# Check quota-count per policy
sysmeta_key = 'quota-count-policy-%s' % policy_idx
@@ -283,19 +268,8 @@ class AccountQuotaMiddleware(object):
policy_stats = account_info['storage_policies'].get(policy_idx, {})
new_size = int(policy_stats.get('object_count', 0)) + 1
if policy_quota < new_size:
resp = HTTPRequestEntityTooLarge(
body='Upload exceeds policy quota.')
if 'swift.authorize' in request.environ:
orig_authorize = request.environ['swift.authorize']
def reject_authorize(*args, **kwargs):
aresp = orig_authorize(*args, **kwargs)
if aresp:
return aresp
return resp
request.environ['swift.authorize'] = reject_authorize
else:
return resp
return self.quota_exceeded(
request, "Upload exceeds policy quota.")
return self.app

View File

@@ -318,6 +318,22 @@ class TestAccountQuota(unittest.TestCase):
self.assertEqual(res.status_int, 413)
self.assertEqual(res.body, b'Upload exceeds quota.')
def test_exceed_multiple_quota_authorized(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-bytes-used': '100',
'x-account-object-count': '10',
'x-account-sysmeta-quota-count': '10',
'x-account-sysmeta-quota-bytes': '10'})
app = FakeAuthFilter(account_quotas.AccountQuotaMiddleware(self.app))
cache = FakeCache(None)
req = Request.blank('/v1/a/c/o', method='PUT',
headers={'x-auth-token': 'secret',
'content-length': '901'},
environ={'swift.cache': cache})
res = req.get_response(app)
self.assertEqual(res.status_int, 413)
self.assertEqual(res.body, b'Upload exceeds quota.')
def test_over_quota_container_create_still_works(self):
self.app.register('HEAD', '/v1/a', HTTPOk, {
'x-account-bytes-used': '1001',