VYPR
Moderate severityNVD Advisory· Published Nov 18, 2024· Updated Nov 19, 2024

aiohttp memory leak when middleware is enabled when requesting a resource with a non-allowed method

CVE-2024-52303

Description

aiohttp is an asynchronous HTTP client/server framework for asyncio and Python. In versions starting with 3.10.6 and prior to 3.10.11, a memory leak can occur when a request produces a MatchInfoError. This was caused by adding an entry to a cache on each request, due to the building of each MatchInfoError producing a unique cache entry. An attacker may be able to exhaust the memory resources of a server by sending a substantial number (100,000s to millions) of such requests. Those who use any middlewares with aiohttp.web should upgrade to version 3.10.11 to receive a patch.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
aiohttpPyPI
>= 3.10.6, < 3.10.113.10.11

Affected products

1

Patches

1
bc15db616150

[PR #9852/249855a backport][3.10] Fix system routes polluting the middleware cache (#9855)

https://github.com/aio-libs/aiohttpJ. Nick KostonNov 13, 2024via ghsa
3 files changed · +39 5
  • aiohttp/web_app.py+11 3 modified
    @@ -54,6 +54,7 @@
         MaskDomain,
         MatchedSubAppResource,
         PrefixedSubAppResource,
    +    SystemRoute,
         UrlDispatcher,
     )
     
    @@ -79,7 +80,6 @@
     _Resource = TypeVar("_Resource", bound=AbstractResource)
     
     
    -@lru_cache(None)
     def _build_middlewares(
         handler: Handler, apps: Tuple["Application", ...]
     ) -> Callable[[Request], Awaitable[StreamResponse]]:
    @@ -90,6 +90,9 @@ def _build_middlewares(
         return handler
     
     
    +_cached_build_middleware = lru_cache(maxsize=1024)(_build_middlewares)
    +
    +
     class Application(MutableMapping[Union[str, AppKey[Any]], Any]):
         ATTRS = frozenset(
             [
    @@ -544,8 +547,13 @@ async def _handle(self, request: Request) -> StreamResponse:
             handler = match_info.handler
     
             if self._run_middlewares:
    -            if not self._has_legacy_middlewares:
    -                handler = _build_middlewares(handler, match_info.apps)
    +            # If its a SystemRoute, don't cache building the middlewares since
    +            # they are constructed for every MatchInfoError as a new handler
    +            # is made each time.
    +            if not self._has_legacy_middlewares and not isinstance(
    +                match_info.route, SystemRoute
    +            ):
    +                handler = _cached_build_middleware(handler, match_info.apps)
                 else:
                     for app in match_info.apps[::-1]:
                         for m, new_style in app._middlewares_handlers:  # type: ignore[union-attr]
    
  • CHANGES/9852.bugfix.rst+1 0 added
    @@ -0,0 +1 @@
    +Fixed system routes polluting the middleware cache -- by :user:`bdraco`.
    
  • tests/test_web_middleware.py+27 2 modified
    @@ -1,10 +1,11 @@
     import re
    -from typing import Any
    +from typing import Any, NoReturn
     
     import pytest
     from yarl import URL
     
    -from aiohttp import web
    +from aiohttp import web, web_app
    +from aiohttp.pytest_plugin import AiohttpClient
     from aiohttp.typedefs import Handler
     
     
    @@ -520,3 +521,27 @@ async def call(self, request, handler: Handler):
         assert 201 == resp.status
         txt = await resp.text()
         assert "OK[new style middleware]" == txt
    +
    +
    +async def test_middleware_does_not_leak(aiohttp_client: AiohttpClient) -> None:
    +    async def any_handler(request: web.Request) -> NoReturn:
    +        assert False
    +
    +    class Middleware:
    +        @web.middleware
    +        async def call(
    +            self, request: web.Request, handler: Handler
    +        ) -> web.StreamResponse:
    +            return await handler(request)
    +
    +    app = web.Application()
    +    app.router.add_route("POST", "/any", any_handler)
    +    app.middlewares.append(Middleware().call)
    +
    +    client = await aiohttp_client(app)
    +
    +    web_app._cached_build_middleware.cache_clear()
    +    for _ in range(10):
    +        resp = await client.get("/any")
    +        assert resp.status == 405
    +    assert web_app._cached_build_middleware.cache_info().currsize < 10
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.