diff options
| author | Gyorgy Sarvari <skandigraun@gmail.com> | 2026-02-04 17:29:19 +0100 |
|---|---|---|
| committer | Anuj Mittal <anuj.mittal@oss.qualcomm.com> | 2026-02-05 06:59:40 +0530 |
| commit | 0ea6c04dde4902016de86120de99c16ea7d91df6 (patch) | |
| tree | 23f2a9b26a4de1e80e67a265093c4128a423222f /meta-python/recipes-devtools/python/python3-aiohttp | |
| parent | 4ac10b5dbb564b6032ebfea1ec845809acc8b91a (diff) | |
| download | meta-openembedded-0ea6c04dde4902016de86120de99c16ea7d91df6.tar.gz | |
python3-aiohttp: patch CVE-2025-69227
Details: https://nvd.nist.gov/vuln/detail/CVE-2025-69227
Backport the patch that is referenced by teh NVD advisory.
Signed-off-by: Gyorgy Sarvari <skandigraun@gmail.com>
Signed-off-by: Anuj Mittal <anuj.mittal@oss.qualcomm.com>
Diffstat (limited to 'meta-python/recipes-devtools/python/python3-aiohttp')
| -rw-r--r-- | meta-python/recipes-devtools/python/python3-aiohttp/CVE-2025-69227.patch | 148 |
1 files changed, 148 insertions, 0 deletions
diff --git a/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2025-69227.patch b/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2025-69227.patch new file mode 100644 index 0000000000..65dae1707b --- /dev/null +++ b/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2025-69227.patch | |||
| @@ -0,0 +1,148 @@ | |||
| 1 | From 635c8c03b609b1099d93fb8ea8c8691624237b0f Mon Sep 17 00:00:00 2001 | ||
| 2 | From: Gyorgy Sarvari <skandigraun@gmail.com> | ||
| 3 | Date: Sat, 3 Jan 2026 04:53:29 +0000 | ||
| 4 | Subject: [PATCH] Replace asserts with exceptions (#11897) (#11914) | ||
| 5 | |||
| 6 | From: Sam Bull <git@sambull.org> | ||
| 7 | |||
| 8 | (cherry picked from commit d5bf65f15c0c718b6b95e9bc9d0914a92c51e60f) | ||
| 9 | |||
| 10 | Co-authored-by: J. Nick Koston <nick@home-assistant.io> | ||
| 11 | |||
| 12 | CVE: CVE-2025-69227 | ||
| 13 | Upstream-Status: Backport [https://github.com/aio-libs/aiohttp/commit/bc1319ec3cbff9438a758951a30907b072561259] | ||
| 14 | Signed-off-by: Gyorgy Sarvari <skandigraun@gmail.com> | ||
| 15 | --- | ||
| 16 | aiohttp/multipart.py | 10 ++++------ | ||
| 17 | aiohttp/web_request.py | 8 +++----- | ||
| 18 | tests/test_multipart.py | 12 +++++++++++- | ||
| 19 | tests/test_web_request.py | 24 +++++++++++++++++++++++- | ||
| 20 | 4 files changed, 41 insertions(+), 13 deletions(-) | ||
| 21 | |||
| 22 | diff --git a/aiohttp/multipart.py b/aiohttp/multipart.py | ||
| 23 | index 54dfd48..7783ac5 100644 | ||
| 24 | --- a/aiohttp/multipart.py | ||
| 25 | +++ b/aiohttp/multipart.py | ||
| 26 | @@ -357,11 +357,8 @@ class BodyPartReader: | ||
| 27 | self._read_bytes += len(chunk) | ||
| 28 | if self._read_bytes == self._length: | ||
| 29 | self._at_eof = True | ||
| 30 | - if self._at_eof: | ||
| 31 | - clrf = await self._content.readline() | ||
| 32 | - assert ( | ||
| 33 | - b"\r\n" == clrf | ||
| 34 | - ), "reader did not read all the data or it is malformed" | ||
| 35 | + if self._at_eof and await self._content.readline() != b"\r\n": | ||
| 36 | + raise ValueError("Reader did not read all the data or it is malformed") | ||
| 37 | return chunk | ||
| 38 | |||
| 39 | async def _read_chunk_from_length(self, size: int) -> bytes: | ||
| 40 | @@ -390,7 +387,8 @@ class BodyPartReader: | ||
| 41 | while len(chunk) < self._boundary_len: | ||
| 42 | chunk += await self._content.read(size) | ||
| 43 | self._content_eof += int(self._content.at_eof()) | ||
| 44 | - assert self._content_eof < 3, "Reading after EOF" | ||
| 45 | + if self._content_eof > 2: | ||
| 46 | + raise ValueError("Reading after EOF") | ||
| 47 | if self._content_eof: | ||
| 48 | break | ||
| 49 | if len(chunk) > size: | ||
| 50 | diff --git a/aiohttp/web_request.py b/aiohttp/web_request.py | ||
| 51 | index 6e09027..96222b0 100644 | ||
| 52 | --- a/aiohttp/web_request.py | ||
| 53 | +++ b/aiohttp/web_request.py | ||
| 54 | @@ -721,13 +721,13 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin): | ||
| 55 | multipart = await self.multipart() | ||
| 56 | max_size = self._client_max_size | ||
| 57 | |||
| 58 | - field = await multipart.next() | ||
| 59 | - while field is not None: | ||
| 60 | + while (field := await multipart.next()) is not None: | ||
| 61 | size = 0 | ||
| 62 | field_ct = field.headers.get(hdrs.CONTENT_TYPE) | ||
| 63 | |||
| 64 | if isinstance(field, BodyPartReader): | ||
| 65 | - assert field.name is not None | ||
| 66 | + if field.name is None: | ||
| 67 | + raise ValueError("Multipart field missing name.") | ||
| 68 | |||
| 69 | # Note that according to RFC 7578, the Content-Type header | ||
| 70 | # is optional, even for files, so we can't assume it's | ||
| 71 | @@ -779,8 +779,6 @@ class BaseRequest(MutableMapping[str, Any], HeadersMixin): | ||
| 72 | raise ValueError( | ||
| 73 | "To decode nested multipart you need to use custom reader", | ||
| 74 | ) | ||
| 75 | - | ||
| 76 | - field = await multipart.next() | ||
| 77 | else: | ||
| 78 | data = await self.read() | ||
| 79 | if data: | ||
| 80 | diff --git a/tests/test_multipart.py b/tests/test_multipart.py | ||
| 81 | index 75b73a7..5351945 100644 | ||
| 82 | --- a/tests/test_multipart.py | ||
| 83 | +++ b/tests/test_multipart.py | ||
| 84 | @@ -221,11 +221,21 @@ class TestPartReader: | ||
| 85 | with Stream(data) as stream: | ||
| 86 | obj = aiohttp.BodyPartReader(BOUNDARY, {}, stream) | ||
| 87 | result = b"" | ||
| 88 | - with pytest.raises(AssertionError): | ||
| 89 | + with pytest.raises(ValueError): | ||
| 90 | for _ in range(4): | ||
| 91 | result += await obj.read_chunk(7) | ||
| 92 | assert data == result | ||
| 93 | |||
| 94 | + async def test_read_with_content_length_malformed_crlf(self) -> None: | ||
| 95 | + # Content-Length is correct but data after content is not \r\n | ||
| 96 | + content = b"Hello" | ||
| 97 | + h = CIMultiDictProxy(CIMultiDict({"CONTENT-LENGTH": str(len(content))})) | ||
| 98 | + # Malformed: "XX" instead of "\r\n" after content | ||
| 99 | + with Stream(content + b"XX--:--") as stream: | ||
| 100 | + obj = aiohttp.BodyPartReader(BOUNDARY, h, stream) | ||
| 101 | + with pytest.raises(ValueError, match="malformed"): | ||
| 102 | + await obj.read() | ||
| 103 | + | ||
| 104 | async def test_read_boundary_with_incomplete_chunk(self) -> None: | ||
| 105 | with Stream(b"") as stream: | ||
| 106 | |||
| 107 | diff --git a/tests/test_web_request.py b/tests/test_web_request.py | ||
| 108 | index da80ca9..125b95e 100644 | ||
| 109 | --- a/tests/test_web_request.py | ||
| 110 | +++ b/tests/test_web_request.py | ||
| 111 | @@ -10,6 +10,7 @@ from multidict import CIMultiDict, CIMultiDictProxy, MultiDict | ||
| 112 | from yarl import URL | ||
| 113 | |||
| 114 | from aiohttp import HttpVersion | ||
| 115 | +from aiohttp.base_protocol import BaseProtocol | ||
| 116 | from aiohttp.http_parser import RawRequestMessage | ||
| 117 | from aiohttp.streams import StreamReader | ||
| 118 | from aiohttp.test_utils import make_mocked_request | ||
| 119 | @@ -815,7 +816,28 @@ async def test_multipart_formdata(protocol) -> None: | ||
| 120 | assert dict(result) == {"a": "b", "c": "d"} | ||
| 121 | |||
| 122 | |||
| 123 | -async def test_multipart_formdata_file(protocol) -> None: | ||
| 124 | +async def test_multipart_formdata_field_missing_name(protocol: BaseProtocol) -> None: | ||
| 125 | + # Ensure ValueError is raised when Content-Disposition has no name | ||
| 126 | + payload = StreamReader(protocol, 2**16, loop=asyncio.get_event_loop()) | ||
| 127 | + payload.feed_data( | ||
| 128 | + b"-----------------------------326931944431359\r\n" | ||
| 129 | + b"Content-Disposition: form-data\r\n" # Missing name! | ||
| 130 | + b"\r\n" | ||
| 131 | + b"value\r\n" | ||
| 132 | + b"-----------------------------326931944431359--\r\n" | ||
| 133 | + ) | ||
| 134 | + content_type = ( | ||
| 135 | + "multipart/form-data; boundary=---------------------------326931944431359" | ||
| 136 | + ) | ||
| 137 | + payload.feed_eof() | ||
| 138 | + req = make_mocked_request( | ||
| 139 | + "POST", "/", headers={"CONTENT-TYPE": content_type}, payload=payload | ||
| 140 | + ) | ||
| 141 | + with pytest.raises(ValueError, match="Multipart field missing name"): | ||
| 142 | + await req.post() | ||
| 143 | + | ||
| 144 | + | ||
| 145 | +async def test_multipart_formdata_file(protocol: BaseProtocol) -> None: | ||
| 146 | # Make sure file uploads work, even without a content type | ||
| 147 | payload = StreamReader(protocol, 2**16, loop=asyncio.get_event_loop()) | ||
| 148 | payload.feed_data( | ||
