diff mbox series

[meta-python,scarthgap,1/9] python3-aiohttp: fix CVE-2025-69224

Message ID 20260831045744.3321483-2-dkelaiya@cisco.com
State New
Headers show
Series python3-aiohttp: fix multiple CVEs | expand

Commit Message

From: Darsh Kelaiya <dkelaiya@cisco.com>

This patch applies the reviewed upstream fix commits shown in
[1] and [2]. The advisory identifying the fix is referenced in
[3].

The generated aiohttp/_http_parser.c changes are omitted. Add
python3-cython-native and regenerate the C source from the patched
_http_parser.pyx during do_configure.

[1] https://github.com/aio-libs/aiohttp/commit/32677f2adfd907420c078dda6b79225c6f4ebce0
[2] https://github.com/aio-libs/aiohttp/commit/5affd64f86d28a16a8f8e6fea2d217c99bf7831f
[3] https://nvd.nist.gov/vuln/detail/CVE-2025-69224

Signed-off-by: Darsh Kelaiya <dkelaiya@cisco.com>
---
 .../python3-aiohttp/CVE-2025-69224.patch      | 161 ++++++++++++++++++
 .../python/python3-aiohttp_3.9.5.bb           |  10 ++
 2 files changed, 171 insertions(+)
 create mode 100644 meta-python/recipes-devtools/python/python3-aiohttp/CVE-2025-69224.patch
diff mbox series

Patch

diff --git a/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2025-69224.patch b/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2025-69224.patch
new file mode 100644
index 0000000000..d1a830e077
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2025-69224.patch
@@ -0,0 +1,161 @@ 
+From 4c27b675ef3d1c5b3b0f379525be575ec0d8d15e Mon Sep 17 00:00:00 2001
+From: Sam Bull <git@sambull.org>
+Date: Sat, 3 Jan 2026 00:02:45 +0000
+Subject: [PATCH] Reject non-ascii characters in some headers (#11886) (#11902)
+
+CVE: CVE-2025-69224
+Upstream-Status: Backport [https://github.com/aio-libs/aiohttp/commit/32677f2adfd907420c078dda6b79225c6f4ebce0]
+
+Backport Changes:
+- Adapted the pure-Python `Transfer-Encoding` validation inline because
+  aiohttp 3.9.5 lacks the upstream `_is_chunked_te()` call path.
+- Backported the request/response upgrade distinction needed to apply the
+  non-ASCII request-header check while preserving CONNECT and HTTP 101
+  response handling.
+- Added the `ALLOWED_UPGRADES` definition from upstream prerequisite commit
+  c99a1e27375285149ea82cbdcc2f2c40e57596dc because aiohttp 3.9.5
+  predates it and the Cython parser otherwise fails to compile.
+- Retained aiohttp 3.9.5's supported `gzip`, `deflate`, and `br` content
+  encodings; omitted upstream zstd-specific code and test context because
+  this version lacks zstd decompression support.
+- Retained target-compatible `Any` test annotations because the older test
+  module does not import `HttpRequestParser`.
+
+(cherry picked from commit 5affd64f86d28a16a8f8e6fea2d217c99bf7831f)
+(cherry picked from commit 32677f2adfd907420c078dda6b79225c6f4ebce0)
+Signed-off-by: Darsh Kelaiya <dkelaiya@cisco.com>
+---
+ aiohttp/_http_parser.pyx  | 18 +++++++++++++-----
+ aiohttp/http_parser.py    |  8 +++++---
+ tests/test_http_parser.py | 32 ++++++++++++++++++++++++++++++--
+ 3 files changed, 48 insertions(+), 10 deletions(-)
+
+diff --git a/aiohttp/_http_parser.pyx b/aiohttp/_http_parser.pyx
+index 7ea9b32ca..f1c130395 100644
+--- a/aiohttp/_http_parser.pyx
++++ b/aiohttp/_http_parser.pyx
+@@ -47,6 +47,7 @@ include "_headers.pxi"
+ 
+ from aiohttp cimport _find_header
+ 
++ALLOWED_UPGRADES = frozenset({"websocket"})
+ DEF DEFAULT_FREELIST_SIZE = 250
+ 
+ cdef extern from "Python.h":
+@@ -425,8 +426,14 @@ cdef class HttpParser:
+         raw_headers = tuple(self._raw_headers)
+         headers = CIMultiDictProxy(self._headers)
+ 
+-        if upgrade or self._cparser.method == cparser.HTTP_CONNECT:
+-            self._upgraded = True
++        if self._cparser.type == cparser.HTTP_REQUEST:
++            h_upg = headers.get("upgrade", "")
++            allowed = upgrade and h_upg.isascii() and h_upg.lower() in ALLOWED_UPGRADES
++            if allowed or self._cparser.method == cparser.HTTP_CONNECT:
++                self._upgraded = True
++        else:
++            if upgrade and self._cparser.status_code == 101:
++                self._upgraded = True
+ 
+         # do not support old websocket spec
+         if SEC_WEBSOCKET_KEY1 in headers:
+@@ -436,9 +443,10 @@ cdef class HttpParser:
+         enc = self._content_encoding
+         if enc is not None:
+             self._content_encoding = None
+-            enc = enc.lower()
+-            if enc in ('gzip', 'deflate', 'br'):
+-                encoding = enc
++            if enc.isascii():
++                enc = enc.lower()
++                if enc in ('gzip', 'deflate', 'br'):
++                    encoding = enc
+ 
+         if self._cparser.type == cparser.HTTP_REQUEST:
+             msg = _new_request_message(
+diff --git a/aiohttp/http_parser.py b/aiohttp/http_parser.py
+index 0a80c5c6d..5768bd623 100644
+--- a/aiohttp/http_parser.py
++++ b/aiohttp/http_parser.py
+@@ -232,7 +232,9 @@ class HeadersParser:
+ 
+ def _is_supported_upgrade(headers: CIMultiDictProxy[str]) -> bool:
+     """Check if the upgrade header is supported."""
+-    return headers.get(hdrs.UPGRADE, "").lower() in {"tcp", "websocket"}
++    u = headers.get(hdrs.UPGRADE, "")
++    # .lower() can transform non-ascii characters.
++    return u.isascii() and u.lower() in {"tcp", "websocket"}
+ 
+ 
+ class HttpParser(abc.ABC, Generic[_MsgT]):
+@@ -542,7 +544,7 @@ class HttpParser(abc.ABC, Generic[_MsgT]):
+ 
+         # encoding
+         enc = headers.get(hdrs.CONTENT_ENCODING)
+-        if enc:
++        if enc and enc.isascii():
+             enc = enc.lower()
+             if enc in ("gzip", "deflate", "br"):
+                 encoding = enc
+@@ -550,7 +552,7 @@ class HttpParser(abc.ABC, Generic[_MsgT]):
+         # chunking
+         te = headers.get(hdrs.TRANSFER_ENCODING)
+         if te is not None:
+-            if "chunked" == te.lower():
++            if te.isascii() and "chunked" == te.lower():
+                 chunked = True
+             else:
+                 raise BadHttpMessage("Request has invalid `Transfer-Encoding`")
+diff --git a/tests/test_http_parser.py b/tests/test_http_parser.py
+index 2f34f0bc0..021b6e4ae 100644
+--- a/tests/test_http_parser.py
++++ b/tests/test_http_parser.py
+@@ -482,6 +482,20 @@ def test_request_chunked(parser) -> None:
+     assert isinstance(payload, streams.StreamReader)
+ 
+ 
++def test_te_header_non_ascii(parser: Any) -> None:
++    # K = Kelvin sign, not valid ascii.
++    text = "GET /test HTTP/1.1\r\nTransfer-Encoding: chunKed\r\n\r\n"
++    with pytest.raises(http_exceptions.BadHttpMessage):
++        parser.feed_data(text.encode())
++
++
++def test_upgrade_header_non_ascii(parser: Any) -> None:
++    # K = Kelvin sign, not valid ascii.
++    text = "GET /test HTTP/1.1\r\nUpgrade: websocKet\r\n\r\n"
++    messages, upgrade, tail = parser.feed_data(text.encode())
++    assert not upgrade
++
++
+ def test_request_te_chunked_with_content_length(parser: Any) -> None:
+     text = (
+         b"GET /test HTTP/1.1\r\n"
+@@ -555,8 +569,22 @@ def test_compression_brotli(parser) -> None:
+     assert msg.compression == "br"
+ 
+ 
+-def test_compression_unknown(parser) -> None:
+-    text = b"GET /test HTTP/1.1\r\n" b"content-encoding: compress\r\n\r\n"
++@pytest.mark.parametrize(
++    "enc",
++    (
++        "deflate".encode(),  # "fl".upper() == "FL"
++    ),
++)
++def test_compression_non_ascii(parser: Any, enc: bytes) -> None:
++    text = b"GET /test HTTP/1.1\r\ncontent-encoding: " + enc + b"\r\n\r\n"
++    messages, upgrade, tail = parser.feed_data(text)
++    msg = messages[0][0]
++    # Non-ascii input should not evaluate to a valid encoding scheme.
++    assert msg.compression is None
++
++
++def test_compression_unknown(parser: Any) -> None:
++    text = b"GET /test HTTP/1.1\r\ncontent-encoding: compress\r\n\r\n"
+     messages, upgrade, tail = parser.feed_data(text)
+     msg = messages[0][0]
+     assert msg.compression is None
+-- 
+2.35.6
+
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 84a9f2e668..ffdc25791b 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
@@ -8,6 +8,7 @@  SRC_URI[sha256sum] = "edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d
 
 SRC_URI += "file://CVE-2024-52304.patch \
            file://CVE-2025-53643.patch \
+           file://CVE-2025-69224.patch \
            file://CVE-2025-69225.patch \
            file://CVE-2025-69226.patch \
            file://CVE-2025-69228.patch \
@@ -16,6 +17,15 @@  SRC_URI += "file://CVE-2024-52304.patch \
 PYPI_PACKAGE = "aiohttp"
 inherit python_setuptools_build_meta pypi
 
+DEPENDS += "python3-cython-native"
+
+do_configure:prepend() {
+    cython3 -3 -Werror \
+        -I ${S}/aiohttp \
+        -o ${S}/aiohttp/_http_parser.c \
+        ${S}/aiohttp/_http_parser.pyx
+}
+
 RDEPENDS:${PN} = "\
     python3-aiohappyeyeballs \
     python3-aiosignal \