CVE-2013-0270 (Python, published 2013-04-12T22:55Z)
Description: value: OpenStack Keystone Grizzly before 2013.1, Folsom, and possibly earlier allows remote attackers to cause a denial of service (CPU and memory consumption) via a large HTTP request, as demonstrated by a long tenant_name when requesting a token.
Severity: MEDIUM (CVSS v3 base score: )
Weakness: CWE-119 \u2014 Improper Restriction of Operations within the Bounds of a Memory Buffer
The product performs operations on a memory buffer, but it reads from or writes to a memory location outside the buffer's intended boundary. This may result in read or write operations on unexpected memory locations that could be linked to other variables, data structures, or internal program data.
Repository: https://github.com/openstack/keystone
Files changed:
etc/keystone.conf.sample
keystone/common/utils.py
keystone/config.py
keystone/exception.py
keystone/middleware/core.py
tests/test_sizelimit.py
Commit message: Limit the size of HTTP requests.

Adds a new RequestBodySizeLimiter middleware to guard against
really large HTTP requests. The default max request size is 112k
although this limit is configurable via the 'max_request_body_size'
config parameter.

Fixes LP Bug #1099025.

Change-Id: Id51be3d9a0d829d63d55a92dca61a39a17629785


Vulnerable code:
pipeline = stats_monitoring url_normalize token_auth admin_token_auth xml_body json_body debug ec2_extension user_crud_extension public_service
pipeline = stats_monitoring url_normalize token_auth admin_token_auth xml_body json_body debug stats_reporting ec2_extension s3_extension crud_extension admin_service
pipeline = stats_monitoring url_normalize token_auth admin_token_auth xml_body json_body debug stats_reporting ec2_extension s3_extension service_v3
pipeline = stats_monitoring url_normalize xml_body public_version_service
pipeline = stats_monitoring url_normalize xml_body admin_version_service

Fixed code:
[filter:sizelimit]
paste.filter_factory = keystone.middleware:RequestBodySizeLimiter.factory

pipeline = sizelimit stats_monitoring url_normalize token_auth admin_token_auth xml_body json_body debug ec2_extension user_crud_extension public_service
pipeline = sizelimit stats_monitoring url_normalize token_auth admin_token_auth xml_body json_body debug stats_reporting ec2_extension s3_extension crud_extension admin_service
pipeline = sizelimit stats_monitoring url_normalize token_auth admin_token_auth xml_body json_body debug stats_reporting ec2_extension s3_extension service_v3
pipeline = sizelimit stats_monitoring url_normalize xml_body public_version_service
pipeline = sizelimit stats_monitoring url_normalize xml_body admin_version_service


class LimitingReader(object):
    """Reader to limit the size of an incoming request."""
    def __init__(self, data, limit):
        """
        :param data: Underlying data object
        :param limit: maximum number of bytes the reader should allow
        """
        self.data = data
        self.limit = limit
        self.bytes_read = 0

    def __iter__(self):
        for chunk in self.data:
            self.bytes_read += len(chunk)
            if self.bytes_read > self.limit:
                raise exception.RequestTooLarge()
            else:
                yield chunk

    def read(self, i):
        result = self.data.read(i)
        self.bytes_read += len(result)
        if self.bytes_read > self.limit:
            raise exception.RequestTooLarge()
        return result

    def read(self):
        result = self.data.read()
        self.bytes_read += len(result)
        if self.bytes_read > self.limit:
            raise exception.RequestTooLarge()
        return result
#default max request size is 112k
register_int('max_request_body_size', default=114688)
class RequestTooLarge(Error):
    """Request is too large."""
    code = 413
    title = 'Request is too large.'


import webob.dec

from keystone.common import utils


class RequestBodySizeLimiter(wsgi.Middleware):
    """Limit the size of an incoming request."""

    def __init__(self, *args, **kwargs):
        super(RequestBodySizeLimiter, self).__init__(*args, **kwargs)

    @webob.dec.wsgify(RequestClass=wsgi.Request)
    def __call__(self, req):

        if req.content_length > CONF.max_request_body_size:
            raise exception.RequestTooLarge()
        if req.content_length is None and req.is_body_readable:
            limiter = utils.LimitingReader(req.body_file,
                                           CONF.max_request_body_size)
            req.body_file = limiter
        return self.application
# Copyright (c) 2013 OpenStack, LLC
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

import webob

from keystone import config
from keystone import exception
from keystone import middleware
from keystone import test

CONF = config.CONF
MAX_REQUEST_BODY_SIZE = CONF.max_request_body_size


class TestRequestBodySizeLimiter(test.TestCase):

    def setUp(self):
        super(TestRequestBodySizeLimiter, self).setUp()

        @webob.dec.wsgify()
        def fake_app(req):
            return webob.Response(req.body)

        self.middleware = middleware.RequestBodySizeLimiter(fake_app)
        self.request = webob.Request.blank('/', method='POST')

    def test_content_length_acceptable(self):
        self.request.headers['Content-Length'] = MAX_REQUEST_BODY_SIZE
        self.request.body = "0" * MAX_REQUEST_BODY_SIZE
        response = self.request.get_response(self.middleware)
        self.assertEqual(response.status_int, 200)

    def test_content_length_too_large(self):
        self.request.headers['Content-Length'] = MAX_REQUEST_BODY_SIZE + 1
        self.request.body = "0" * (MAX_REQUEST_BODY_SIZE + 1)
        self.assertRaises(exception.RequestTooLarge,
                          self.request.get_response,
                          self.middleware)

    def test_request_too_large_no_content_length(self):
        self.request.body = "0" * (MAX_REQUEST_BODY_SIZE + 1)
        self.request.headers['Content-Length'] = None
        self.assertRaises(exception.RequestTooLarge,
                          self.request.get_response,
                          self.middleware)