Files
meta-openembedded/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2025-69224.patch
T
Darsh Kelaiya f6e53ba11b python3-aiohttp: fix CVE-2025-69224
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>
Signed-off-by: Anuj Mittal <anuj.mittal@oss.qualcomm.com>
2026-09-01 10:18:02 +05:30

162 lines
6.2 KiB
Diff
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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:
+ # = Kelvin sign, not valid ascii.
+ text = "GET /test HTTP/1.1\r\nTransfer-Encoding: chuned\r\n\r\n"
+ with pytest.raises(http_exceptions.BadHttpMessage):
+ parser.feed_data(text.encode())
+
+
+def test_upgrade_header_non_ascii(parser: Any) -> None:
+ # = Kelvin sign, not valid ascii.
+ text = "GET /test HTTP/1.1\r\nUpgrade: websocet\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