aiohttp memory leak when middleware is enabled when requesting a resource with a non-allowed method
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.
| Package | Affected versions | Patched versions |
|---|---|---|
aiohttpPyPI | >= 3.10.6, < 3.10.11 | 3.10.11 |
Affected products
1Patches
1bc15db616150[PR #9852/249855a backport][3.10] Fix system routes polluting the middleware cache (#9855)
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- github.com/advisories/GHSA-27mf-ghqm-j3j8ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-52303ghsaADVISORY
- github.com/aio-libs/aiohttp/commit/bc15db61615079d1b6327ba42c682f758fa96936ghsax_refsource_MISCWEB
- github.com/aio-libs/aiohttp/security/advisories/GHSA-27mf-ghqm-j3j8ghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.