diff mbox series

[meta-python,scarthgap,09/15] python3-aiohttp: patch CVE-2025-69226

Message ID 20260207103359.4177243-9-skandigraun@gmail.com
State New
Headers show
Series [meta-oe,scarthgap,01/15] imagemagick: patch CVE-2025-66628 | expand

Commit Message

Gyorgy Sarvari Feb. 7, 2026, 10:33 a.m. UTC
Details: https://nvd.nist.gov/vuln/detail/CVE-2025-69226

Backport the patch that is referenced by the NVD advisory.

Signed-off-by: Gyorgy Sarvari <skandigraun@gmail.com>
---
 .../python3-aiohttp/CVE-2025-69226.patch      | 134 ++++++++++++++++++
 .../python/python3-aiohttp_3.9.5.bb           |   1 +
 2 files changed, 135 insertions(+)
 create mode 100644 meta-python/recipes-devtools/python/python3-aiohttp/CVE-2025-69226.patch
diff mbox series

Patch

diff --git a/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2025-69226.patch b/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2025-69226.patch
new file mode 100644
index 0000000000..77dd89a805
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2025-69226.patch
@@ -0,0 +1,134 @@ 
+From 8d718d1fb8ee7a923c0e42cf100908ecead4f564 Mon Sep 17 00:00:00 2001
+From: Gyorgy Sarvari <skandigraun@gmail.com>
+Date: Sat, 3 Jan 2026 01:55:05 +0000
+Subject: [PATCH] Reject static URLs that traverse outside static root (#11888)
+ (#11906)
+
+From: Sam Bull <git@sambull.org>
+
+(cherry picked from commit 63961fa77fa2443109f25c3d8ab94772d3878626)
+
+Co-authored-by: J. Nick Koston <nick@koston.org>
+
+CVE: CVE-2025-69226
+Upstream-Status: Backport [https://github.com/aio-libs/aiohttp/commit/f2a86fd5ac0383000d1715afddfa704413f0711e]
+Signed-off-by: Gyorgy Sarvari <skandigraun@gmail.com>
+---
+ aiohttp/web_urldispatcher.py          | 17 +++++++++--------
+ tests/test_urldispatch.py             | 18 +++++++++++++++++-
+ tests/test_web_sendfile_functional.py |  2 +-
+ tests/test_web_urldispatcher.py       |  4 ++--
+ 4 files changed, 29 insertions(+), 12 deletions(-)
+
+diff --git a/aiohttp/web_urldispatcher.py b/aiohttp/web_urldispatcher.py
+index 954291f..864f8dd 100644
+--- a/aiohttp/web_urldispatcher.py
++++ b/aiohttp/web_urldispatcher.py
+@@ -7,6 +7,7 @@ import html
+ import inspect
+ import keyword
+ import os
++import platform
+ import re
+ import warnings
+ from contextlib import contextmanager
+@@ -88,6 +89,7 @@ ROUTE_RE: Final[Pattern[str]] = re.compile(
+ )
+ PATH_SEP: Final[str] = re.escape("/")
+ 
++IS_WINDOWS: Final[bool] = platform.system() == "Windows"
+ 
+ _ExpectHandler = Callable[[Request], Awaitable[Optional[StreamResponse]]]
+ _Resolve = Tuple[Optional["UrlMappingMatchInfo"], Set[str]]
+@@ -647,7 +649,12 @@ class StaticResource(PrefixResource):
+         path = request.rel_url.raw_path
+         method = request.method
+         allowed_methods = set(self._routes)
+-        if not path.startswith(self._prefix2) and path != self._prefix:
++        # We normalise here to avoid matches that traverse below the static root.
++        # e.g. /static/../../../../home/user/webapp/static/
++        norm_path = os.path.normpath(path)
++        if IS_WINDOWS:
++            norm_path = norm_path.replace("\\", "/")
++        if not norm_path.startswith(self._prefix2) and norm_path != self._prefix:
+             return None, set()
+ 
+         if method not in allowed_methods:
+@@ -663,14 +670,8 @@ class StaticResource(PrefixResource):
+         return iter(self._routes.values())
+ 
+     async def _handle(self, request: Request) -> StreamResponse:
+-        rel_url = request.match_info["filename"]
+         try:
+-            filename = Path(rel_url)
+-            if filename.anchor:
+-                # rel_url is an absolute name like
+-                # /static/\\machine_name\c$ or /static/D:\path
+-                # where the static dir is totally different
+-                raise HTTPForbidden()
++            filename = request.match_info["filename"]
+             unresolved_path = self._directory.joinpath(filename)
+             if self._follow_symlinks:
+                 normalized_path = Path(os.path.normpath(unresolved_path))
+diff --git a/tests/test_urldispatch.py b/tests/test_urldispatch.py
+index 4f3abb8..cec4cd0 100644
+--- a/tests/test_urldispatch.py
++++ b/tests/test_urldispatch.py
+@@ -1,4 +1,5 @@
+ import pathlib
++import platform
+ import re
+ from collections.abc import Container, Iterable, Mapping, MutableMapping, Sized
+ from urllib.parse import unquote
+@@ -967,7 +968,22 @@ async def test_405_for_resource_adapter(router) -> None:
+     assert (None, {"HEAD", "GET"}) == ret
+ 
+ 
+-async def test_check_allowed_method_for_found_resource(router) -> None:
++@pytest.mark.skipif(platform.system() == "Windows", reason="Different path formats")
++async def test_static_resource_outside_traversal(router: web.UrlDispatcher) -> None:
++    """Test relative path traversing outside root does not resolve."""
++    static_file = pathlib.Path(aiohttp.__file__)
++    request_path = "/st" + "/.." * (len(static_file.parts) - 2) + str(static_file)
++    assert pathlib.Path(request_path).resolve() == static_file
++
++    resource = router.add_static("/st", static_file.parent)
++    ret = await resource.resolve(make_mocked_request("GET", request_path))
++    # Should not resolve, otherwise filesystem information may be leaked.
++    assert (None, set()) == ret
++
++
++async def test_check_allowed_method_for_found_resource(
++    router: web.UrlDispatcher,
++) -> None:
+     handler = make_handler()
+     resource = router.add_resource("/")
+     resource.add_route("GET", handler)
+diff --git a/tests/test_web_sendfile_functional.py b/tests/test_web_sendfile_functional.py
+index 57ac084..aa53726 100644
+--- a/tests/test_web_sendfile_functional.py
++++ b/tests/test_web_sendfile_functional.py
+@@ -565,7 +565,7 @@ async def test_static_file_directory_traversal_attack(aiohttp_client) -> None:
+ 
+     url_abspath = "/static/" + str(full_path.resolve())
+     resp = await client.get(url_abspath)
+-    assert 403 == resp.status
++    assert resp.status == 404
+     await resp.release()
+ 
+     await client.close()
+diff --git a/tests/test_web_urldispatcher.py b/tests/test_web_urldispatcher.py
+index 0441890..4164677 100644
+--- a/tests/test_web_urldispatcher.py
++++ b/tests/test_web_urldispatcher.py
+@@ -701,8 +701,8 @@ async def test_static_absolute_url(
+     here = pathlib.Path(__file__).parent
+     app.router.add_static("/static", here)
+     client = await aiohttp_client(app)
+-    resp = await client.get("/static/" + str(file_path.resolve()))
+-    assert resp.status == 403
++    async with client.get("/static/" + str(file_path.resolve())) as resp:
++        assert resp.status == 404
+ 
+ 
+ async def test_for_issue_5250(
diff --git a/meta-python/recipes-devtools/python/python3-aiohttp_3.9.5.bb b/meta-python/recipes-devtools/python/python3-aiohttp_3.9.5.bb
index 43482db392..f2332065ea 100644
--- a/meta-python/recipes-devtools/python/python3-aiohttp_3.9.5.bb
+++ b/meta-python/recipes-devtools/python/python3-aiohttp_3.9.5.bb
@@ -9,6 +9,7 @@  SRC_URI[sha256sum] = "edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d
 SRC_URI += "file://CVE-2024-52304.patch \
            file://CVE-2025-53643.patch \
            file://CVE-2025-69225.patch \
+           file://CVE-2025-69226.patch \
            "
 
 PYPI_PACKAGE = "aiohttp"