* [oe][meta-python][wrynose][PATCH 0/3] python3-aiohttp: fix multiple CVEs
@ 2026-10-16 6:42 Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-10-16 6:42 ` [oe][meta-python][wrynose][PATCH 1/3] python3-aiohttp: fix CVE-2026-59881 Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)
` (3 more replies)
0 siblings, 4 replies; 8+ messages in thread
From: Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco) @ 2026-10-16 6:42 UTC (permalink / raw)
To: openembedded-devel; +Cc: Darsh Kelaiya
From: Darsh Kelaiya <dkelaiya@cisco.com>
Backport three upstream aiohttp security fixes to the 3.13.5 recipe
on wrynose: CVE-2026-59881, CVE-2026-69243, and CVE-2026-69244.
The series fixes WebSocket compression handling, rejected WebSocket
upgrade request-body parsing, and bounded error messages in the C
HTTP parser.
The individual commits retain upstream fix and advisory references.
This series depends on the previously submitted Wrynose fix for
CVE-2026-54273, which has not merged yet. Apply that patch [1] before
this series. It provides the HTTP parser queue-limit changes and
recipe-time Cython regeneration used by these backports.
[1] https://patchwork.yoctoproject.org/project/oe/patch/20260928174806.1818191-1-dkelaiya@cisco.com/
Darsh Kelaiya (3):
python3-aiohttp: fix CVE-2026-59881
python3-aiohttp: fix CVE-2026-69243
python3-aiohttp: fix CVE-2026-69244
.../python3-aiohttp/CVE-2026-59881.patch | 103 ++++++
.../python3-aiohttp/CVE-2026-69243.patch | 307 ++++++++++++++++++
.../python3-aiohttp/CVE-2026-69244.patch | 159 +++++++++
.../python/python3-aiohttp_3.13.5.bb | 3 +
4 files changed, 572 insertions(+)
create mode 100644 meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-59881.patch
create mode 100644 meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-69243.patch
create mode 100644 meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-69244.patch
--
2.35.6
^ permalink raw reply [flat|nested] 8+ messages in thread
* [oe][meta-python][wrynose][PATCH 1/3] python3-aiohttp: fix CVE-2026-59881
2026-10-16 6:42 [oe][meta-python][wrynose][PATCH 0/3] python3-aiohttp: fix multiple CVEs Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)
@ 2026-10-16 6:42 ` Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-10-16 6:42 ` [oe][meta-python][wrynose][PATCH 2/3] python3-aiohttp: fix CVE-2026-69243 Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)
` (2 subsequent siblings)
3 siblings, 0 replies; 8+ messages in thread
From: Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco) @ 2026-10-16 6:42 UTC (permalink / raw)
To: openembedded-devel; +Cc: Darsh Kelaiya
From: Darsh Kelaiya <dkelaiya@cisco.com>
This patch applies the upstream fix as referenced in [2], using
the commit shown in [1]. The 3.13.5 backport passes the negotiated
compression setting to the client WebSocket reader.
[1] https://github.com/aio-libs/aiohttp/commit/47fb6ae354d4fa22048f4dbe7dbf82b625f0a2f6
[2] https://github.com/aio-libs/aiohttp/security/advisories/GHSA-mq44-7p77-q5h7
Signed-off-by: Darsh Kelaiya <dkelaiya@cisco.com>
---
.../python3-aiohttp/CVE-2026-59881.patch | 103 ++++++++++++++++++
.../python/python3-aiohttp_3.13.5.bb | 1 +
2 files changed, 104 insertions(+)
create mode 100644 meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-59881.patch
diff --git a/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-59881.patch b/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-59881.patch
new file mode 100644
index 0000000000..14b05cd2fa
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-59881.patch
@@ -0,0 +1,103 @@
+From b052f95658821f726388cb86cfefad1cec264842 Mon Sep 17 00:00:00 2001
+From: Sam Bull <git@sambull.org>
+Date: Thu, 1 Oct 2026 02:37:30 -0700
+Subject: [PATCH] Fix decompressing frames without negotiation (#12976)
+ (#12978)
+
+CVE: CVE-2026-59881
+Upstream-Status: Backport [https://github.com/aio-libs/aiohttp/commit/47fb6ae354d4fa22048f4dbe7dbf82b625f0a2f6]
+
+Backport Changes:
+- Passed the negotiated compression flag to the 3.13.5 client reader.
+- Kept the reader's compress=True default: both production call
+ sites now pass the negotiated setting (the server already did).
+- Adapted the upstream client regression test to the 3.13.5 API.
+- Omitted changes to newer WebSocket APIs and unrelated tests.
+
+(cherry picked from commit 47fb6ae354d4fa22048f4dbe7dbf82b625f0a2f6)
+Signed-off-by: Darsh Kelaiya <dkelaiya@cisco.com>
+---
+ aiohttp/client.py | 4 ++-
+ tests/test_client_ws_functional.py | 40 +++++++++++++++++++++++++++++-
+ 2 files changed, 42 insertions(+), 2 deletions(-)
+
+diff --git a/aiohttp/client.py b/aiohttp/client.py
+index 0637eff6b..8cd9add5f 100644
+--- a/aiohttp/client.py
++++ b/aiohttp/client.py
+@@ -1190,7 +1190,9 @@ class ClientSession:
+ transport = conn.transport
+ assert transport is not None
+ reader = WebSocketDataQueue(conn_proto, 2**16, loop=self._loop)
+- conn_proto.set_parser(WebSocketReader(reader, max_msg_size), reader)
++ conn_proto.set_parser(
++ WebSocketReader(reader, max_msg_size, compress=bool(compress)), reader
++ )
+ writer = WebSocketWriter(
+ conn_proto,
+ transport,
+diff --git a/tests/test_client_ws_functional.py b/tests/test_client_ws_functional.py
+index 7b6bd0322..a67769c1c 100644
+--- a/tests/test_client_ws_functional.py
++++ b/tests/test_client_ws_functional.py
+@@ -1,5 +1,7 @@
+ import asyncio
++import struct
+ import sys
++import zlib
+ from typing import Any, List, NoReturn, Optional
+ from unittest import mock
+
+@@ -15,8 +17,9 @@ from aiohttp import (
+ web,
+ )
+ from aiohttp._websocket.reader import WebSocketDataQueue
++from aiohttp._websocket.models import WS_DEFLATE_TRAILING
+ from aiohttp.client_ws import ClientWSTimeout
+-from aiohttp.http import WSCloseCode
++from aiohttp.http import WebSocketError, WSCloseCode
+ from aiohttp.pytest_plugin import AiohttpClient
+
+ if sys.version_info >= (3, 11):
+@@ -1276,3 +1279,38 @@ async def test_websocket_connection_cancellation(
+ # Cleanup properly
+ websocket._response = mock.Mock()
+ await websocket.close()
++
++
++async def test_client_rejects_compressed_frame_without_negotiation(
++ aiohttp_client: AiohttpClient,
++) -> None:
++ """Reject RSV1 frames when permessage-deflate was not negotiated."""
++ payload = b"this frame should never be decompressed by the client"
++
++ async def handler(request: web.Request) -> web.WebSocketResponse:
++ ws = web.WebSocketResponse()
++ await ws.prepare(request)
++ transport = request.transport
++ assert transport is not None
++ compressor = zlib.compressobj(
++ zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -zlib.MAX_WBITS
++ )
++ compressed = compressor.compress(payload)
++ compressed += compressor.flush(zlib.Z_SYNC_FLUSH)
++ compressed = compressed.removesuffix(WS_DEFLATE_TRAILING)
++ frame = struct.pack("!BB", 0x80 | 0x40 | WSMsgType.TEXT.value,
++ len(compressed)) + compressed
++ transport.write(frame)
++ await ws.receive()
++ return ws
++
++ app = web.Application()
++ app.router.add_get("/", handler)
++ client = await aiohttp_client(app)
++ async with client.ws_connect("/") as ws:
++ assert ws.compress == 0
++ msg = await ws.receive()
++
++ assert msg.type is WSMsgType.ERROR, msg
++ assert isinstance(msg.data, WebSocketError)
++ assert msg.data.code == WSCloseCode.PROTOCOL_ERROR
+--
+2.35.6
+
diff --git a/meta-python/recipes-devtools/python/python3-aiohttp_3.13.5.bb b/meta-python/recipes-devtools/python/python3-aiohttp_3.13.5.bb
index 0faf2a8d74..b34a0fa3eb 100644
--- a/meta-python/recipes-devtools/python/python3-aiohttp_3.13.5.bb
+++ b/meta-python/recipes-devtools/python/python3-aiohttp_3.13.5.bb
@@ -19,6 +19,7 @@ SRC_URI += " \
file://CVE-2026-54280.patch \
file://CVE-2026-54273_p1.patch \
file://CVE-2026-54273_p2.patch \
+ file://CVE-2026-59881.patch \
"
CVE_PRODUCT = "aiohttp:aiohttp"
--
2.35.6
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [oe][meta-python][wrynose][PATCH 2/3] python3-aiohttp: fix CVE-2026-69243
2026-10-16 6:42 [oe][meta-python][wrynose][PATCH 0/3] python3-aiohttp: fix multiple CVEs Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-10-16 6:42 ` [oe][meta-python][wrynose][PATCH 1/3] python3-aiohttp: fix CVE-2026-59881 Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)
@ 2026-10-16 6:42 ` Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-10-28 5:29 ` Anuj Mittal
2026-10-16 6:42 ` [oe][meta-python][wrynose][PATCH 3/3] python3-aiohttp: fix CVE-2026-69244 Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-10-27 9:28 ` [meta-python][wrynose][PATCH 0/3] python3-aiohttp: fix multiple CVEs Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)
3 siblings, 1 reply; 8+ messages in thread
From: Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco) @ 2026-10-16 6:42 UTC (permalink / raw)
To: openembedded-devel; +Cc: Darsh Kelaiya
From: Darsh Kelaiya <dkelaiya@cisco.com>
This patch applies the upstream fix as referenced in [2], using
the commit shown in [1]. It adapts the Python and C HTTP parser
paths to read a rejected WebSocket upgrade request body before
switching protocols.
[1] https://github.com/aio-libs/aiohttp/commit/6ae358f0983c3f4d6f67692b2f8e65dc8e091c98
[2] https://github.com/aio-libs/aiohttp/security/advisories/GHSA-mfx4-hv73-q22v
Signed-off-by: Darsh Kelaiya <dkelaiya@cisco.com>
---
.../python3-aiohttp/CVE-2026-69243.patch | 307 ++++++++++++++++++
.../python/python3-aiohttp_3.13.5.bb | 1 +
2 files changed, 308 insertions(+)
create mode 100644 meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-69243.patch
diff --git a/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-69243.patch b/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-69243.patch
new file mode 100644
index 0000000000..090a82f2f2
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-69243.patch
@@ -0,0 +1,307 @@
+From 3267a26ebb0c28cc70b0caf5836d20ff1b7c2f39 Mon Sep 17 00:00:00 2001
+From: "patchback[bot]" <45432694+patchback[bot]@users.noreply.github.com>
+Date: Tue, 30 Jun 2026 20:21:57 +0100
+Subject: [PATCH] [PR #13016/8ef76ab8 backport][3.14] Fix body reads after
+ failed WS upgrade (#13017)
+
+This is a backport of PR #13016 as merged into master
+(8ef76ab8029d0f2d88449fe115feb42ded646c7c).
+
+CVE: CVE-2026-69243
+Upstream-Status: Backport [https://github.com/aio-libs/aiohttp/commit/6ae358f0983c3f4d6f67692b2f8e65dc8e091c98]
+
+Backport Changes:
+- Adapted deferred upgrades to the 3.13.5 llhttp pause and tail
+ buffering path, including the CVE-2026-54273 queue limit.
+- Adapted the pure Python parser to its boolean payload result.
+- Used the 3.13.5 buffer size in the earlier 54273 test.
+- Kept the existing pipelined-upgrade regression test and added
+ the upstream body-read regression test.
+- Omitted generated _http_parser.c because the Wrynose recipe
+ regenerates it from _http_parser.pyx during do_configure.
+
+Co-authored-by: Sam Bull <git@sambull.org>
+(cherry picked from commit 6ae358f0983c3f4d6f67692b2f8e65dc8e091c98)
+Signed-off-by: Darsh Kelaiya <dkelaiya@cisco.com>
+---
+ CHANGES/13016.bugfix.rst | 1 +
+ aiohttp/_http_parser.pyx | 20 +++++++++----
+ aiohttp/http_parser.py | 21 ++++++++++----
+ tests/test_http_parser.py | 48 ++++++++++++++++++++++++++++++++
+ tests/test_web_functional.py | 54 ++++++++++++++++++++++++++++++++++--
+ 5 files changed, 131 insertions(+), 13 deletions(-)
+ create mode 100644 CHANGES/13016.bugfix.rst
+
+diff --git a/CHANGES/13016.bugfix.rst b/CHANGES/13016.bugfix.rst
+new file mode 100644
+index 000000000..a984f64e3
+--- /dev/null
++++ b/CHANGES/13016.bugfix.rst
+@@ -0,0 +1 @@
++Fixed request body not being read on rejected WebSocket upgrades -- by :user:`Dreamsorcerer`.
+diff --git a/aiohttp/_http_parser.pyx b/aiohttp/_http_parser.pyx
+index 0d0762748..478d82ecb 100644
+--- a/aiohttp/_http_parser.pyx
++++ b/aiohttp/_http_parser.pyx
+@@ -318,6 +318,7 @@ cdef class HttpParser:
+ set _seen_singletons
+ list _raw_headers
+ bint _upgraded
++ bint _pending_upgrade
+ list _messages
+ Py_ssize_t _msg_in_flight
+ Py_ssize_t _max_msg_queue_size
+@@ -387,6 +388,7 @@ cdef class HttpParser:
+ self._response_with_body = response_with_body
+ self._read_until_eof = read_until_eof
+ self._upgraded = False
++ self._pending_upgrade = False
+ self._auto_decompress = auto_decompress
+ self._content_encoding = None
+ self._lax = False
+@@ -466,10 +468,13 @@ cdef class HttpParser:
+ 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
++ # Wait until the complete request body has been parsed.
++ self._pending_upgrade = True
+ else:
+ if upgrade and self._cparser.status_code == 101:
+- self._upgraded = True
++ # llhttp pauses for a 101 on its own; just mark the pending
++ # switch so feed_data returns the upgraded-protocol tail.
++ self._pending_upgrade = True
+
+ # do not support old websocket spec
+ if SEC_WEBSOCKET_KEY1 in headers:
+@@ -597,6 +602,10 @@ cdef class HttpParser:
+ if errno is cparser.HPE_PAUSED_UPGRADE:
+ cparser.llhttp_resume_after_upgrade(self._cparser)
+ nb = cparser.llhttp_get_error_pos(self._cparser) - base
++ if self._pending_upgrade:
++ # A supported upgrade whose request body has now been fully read.
++ self._upgraded = True
++ self._pending_upgrade = False
+ elif errno is cparser.HPE_PAUSED:
+ cparser.llhttp_resume(self._cparser)
+ pos = cparser.llhttp_get_error_pos(self._cparser) - base
+@@ -818,10 +827,9 @@ cdef int cb_on_headers_complete(cparser.llhttp_t* parser) except -1:
+ pyparser._last_error = exc
+ return -1
+ else:
+- if pyparser._upgraded or pyparser._cparser.method == cparser.HTTP_CONNECT:
+- return 2
+- else:
+- return 0
++ if not pyparser._response_with_body:
++ return 1
++ return 0
+
+
+ cdef int cb_on_body(cparser.llhttp_t* parser,
+diff --git a/aiohttp/http_parser.py b/aiohttp/http_parser.py
+index 32375f83a..f4458dc9d 100644
+--- a/aiohttp/http_parser.py
++++ b/aiohttp/http_parser.py
+@@ -289,6 +289,7 @@ class HttpParser(abc.ABC, Generic[_MsgT]):
+ self._lines: List[bytes] = []
+ self._tail = b""
+ self._upgraded = False
++ self._pending_upgrade = False
+ self._payload = None
+ self._payload_parser: Optional[HttpPayloadParser] = None
+ self._auto_decompress = auto_decompress
+@@ -417,9 +418,7 @@ class HttpParser(abc.ABC, Generic[_MsgT]):
+ if SEC_WEBSOCKET_KEY1 in msg.headers:
+ raise InvalidHeader(SEC_WEBSOCKET_KEY1)
+
+- self._upgraded = msg.upgrade and _is_supported_upgrade(
+- msg.headers
+- )
++ upgraded = msg.upgrade and _is_supported_upgrade(msg.headers)
+
+ method = getattr(msg, "method", self.method)
+ # code is only present on responses
+@@ -431,8 +430,7 @@ class HttpParser(abc.ABC, Generic[_MsgT]):
+ method and method in EMPTY_BODY_METHODS
+ )
+ if not empty_body and (
+- ((length is not None and length > 0) or msg.chunked)
+- and not self._upgraded
++ (length is not None and length > 0) or msg.chunked
+ ):
+ payload = StreamReader(
+ self.protocol,
+@@ -457,6 +455,10 @@ class HttpParser(abc.ABC, Generic[_MsgT]):
+ )
+ if not payload_parser.done:
+ self._payload_parser = payload_parser
++ # https://www.rfc-editor.org/info/rfc9110/#section-7.8-15
++ # Defer any requested upgrade until the
++ # complete request has been read.
++ self._pending_upgrade = upgraded
+ elif method == METH_CONNECT:
+ assert isinstance(msg, RawRequestMessage)
+ payload = StreamReader(
+@@ -501,6 +503,11 @@ class HttpParser(abc.ABC, Generic[_MsgT]):
+ )
+ if not payload_parser.done:
+ self._payload_parser = payload_parser
++ elif upgraded:
++ # No body to read, so the connection switches to
++ # the upgraded protocol immediately.
++ self._upgraded = True
++ payload = EMPTY_PAYLOAD
+ else:
+ payload = EMPTY_PAYLOAD
+
+@@ -548,6 +555,10 @@ class HttpParser(abc.ABC, Generic[_MsgT]):
+ start_pos = 0
+ data_len = len(data)
+ self._payload_parser = None
++ if self._pending_upgrade:
++ # The body is complete; the protocol switch may begin.
++ self._upgraded = True
++ self._pending_upgrade = False
+ continue
+ else:
+ break
+diff --git a/tests/test_http_parser.py b/tests/test_http_parser.py
+index a4e6bc056..92e59af1c 100644
+--- a/tests/test_http_parser.py
++++ b/tests/test_http_parser.py
+@@ -1217,6 +1217,54 @@ async def test_http_request_upgrade_unknown(parser: Any) -> None:
+ assert await messages[0][-1].read() == b"{}"
+
+
++@pytest.mark.parametrize("chunked", (False, True), ids=("content-length", "chunked"))
++async def test_http_request_upgrade_with_body_read(
++ parser: HttpRequestParser, chunked: bool
++) -> None:
++ body_request = b"foobarbaz\r\n\r\n"
++ if chunked:
++ framing = b"Transfer-Encoding: chunked\r\n"
++ body = b"%x\r\n%s\r\n0\r\n\r\n" % (len(body_request), body_request)
++ else:
++ framing = b"Content-Length: %d\r\n" % len(body_request)
++ body = body_request
++ after = b"GET /after HTTP/1.1\r\nHost: a\r\n\r\n"
++ text = (
++ b"GET /ws HTTP/1.1\r\nHost: a\r\n"
++ b"Connection: Upgrade\r\nUpgrade: websocket\r\n"
++ + framing
++ + b"\r\n"
++ + body
++ + after
++ )
++ messages, upgrade, tail = parser.feed_data(text)
++ assert len(messages) == 1
++ msg, payload = messages[0]
++ assert msg.method == "GET"
++ assert msg.path == "/ws"
++ assert msg.upgrade
++ assert await payload.read() == body_request
++ # The connection switches protocols only after the body is fully read.
++ assert upgrade
++ assert tail == after
++
++
++def test_http_request_upgrade_empty_body_allowed(parser: HttpRequestParser) -> None:
++ text = (
++ b"GET /ws HTTP/1.1\r\n"
++ b"Host: a\r\n"
++ b"Connection: Upgrade\r\n"
++ b"Upgrade: websocket\r\n"
++ b"Content-Length: 0\r\n\r\n"
++ b"some raw data"
++ )
++ messages, upgrade, tail = parser.feed_data(text)
++ msg = messages[0][0]
++ assert msg.upgrade
++ assert upgrade
++ assert tail == b"some raw data"
++
++
+ @pytest.fixture
+ def xfail_c_parser_url(request) -> None:
+ if isinstance(request.getfixturevalue("parser"), HttpRequestParserPy):
+diff --git a/tests/test_web_functional.py b/tests/test_web_functional.py
+index b958bef9d..0d2824a9c 100644
+--- a/tests/test_web_functional.py
++++ b/tests/test_web_functional.py
+@@ -25,7 +25,6 @@ from aiohttp import (
+ )
+ from aiohttp.compression_utils import ZLibBackend, ZLibCompressObjProtocol
+ from aiohttp.hdrs import CONTENT_LENGTH, CONTENT_TYPE, TRANSFER_ENCODING
+-from aiohttp.helpers import DEFAULT_CHUNK_SIZE
+ from aiohttp.pytest_plugin import AiohttpClient, AiohttpServer
+ from aiohttp.streams import StreamReader
+ from aiohttp.typedefs import Handler
+@@ -1923,7 +1922,7 @@ async def test_unread_compressed_body_drain_is_bounded(
+ await writer.wait_closed()
+
+ # Bounded by the buffer, not the decompressed size.
+- assert max(drain_reads) <= 3 * DEFAULT_CHUNK_SIZE
++ assert max(drain_reads) <= 3 * 2**16
+ assert max(drain_reads) < decompressed_size
+
+
+@@ -2624,3 +2623,54 @@ async def test_keepalive_expires_on_time(aiohttp_client: AiohttpClient) -> None:
+
+ # Ensure the keep alive handle expires
+ assert request_handler._keepalive_handle is None
++
++
++async def test_declined_websocket_upgrade_reads_body(
++ aiohttp_server: AiohttpServer,
++) -> None:
++ body_read = b""
++
++ async def ws_handler(request: web.Request) -> web.Response:
++ nonlocal body_read
++ # Decline the upgrade; read the body as a normal handler may.
++ body_read = await request.read()
++ return web.Response(text="declined")
++
++ async def after_handler(request: web.Request) -> web.Response:
++ return web.Response(text="after")
++
++ app = web.Application()
++ app.router.add_get("/ws", ws_handler)
++ app.router.add_get("/after", after_handler)
++ server = await aiohttp_server(app)
++
++ body = b"FooBarBaz\r\n\r\n"
++ # Use raw connection in order to pipeline requests.
++ pipeline = (
++ (
++ b"GET /ws HTTP/1.1\r\n"
++ b"Host: localhost\r\n"
++ b"Connection: Upgrade\r\n"
++ b"Upgrade: websocket\r\n"
++ b"Content-Length: %d\r\n\r\n" % len(body)
++ )
++ + body
++ + b"GET /after HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n"
++ )
++
++ reader, writer = await asyncio.open_connection(server.host, server.port)
++ try:
++ writer.write(pipeline)
++ await writer.drain()
++ # The trailing request sends Connection: close, so the server closes
++ # once it has answered it -- reading to EOF gathers both responses.
++ response = await asyncio.wait_for(reader.read(), 5)
++ finally:
++ writer.close()
++ with suppress(ConnectionResetError, BrokenPipeError):
++ await writer.wait_closed()
++
++ assert body_read == body
++ # Both the upgrade request and the pipelined request were served.
++ assert response.count(b"HTTP/1.") == 2, response
++ assert response.count(b" 200 ") == 2, response
+--
+2.35.6
+
diff --git a/meta-python/recipes-devtools/python/python3-aiohttp_3.13.5.bb b/meta-python/recipes-devtools/python/python3-aiohttp_3.13.5.bb
index b34a0fa3eb..417760c9ca 100644
--- a/meta-python/recipes-devtools/python/python3-aiohttp_3.13.5.bb
+++ b/meta-python/recipes-devtools/python/python3-aiohttp_3.13.5.bb
@@ -20,6 +20,7 @@ SRC_URI += " \
file://CVE-2026-54273_p1.patch \
file://CVE-2026-54273_p2.patch \
file://CVE-2026-59881.patch \
+ file://CVE-2026-69243.patch \
"
CVE_PRODUCT = "aiohttp:aiohttp"
--
2.35.6
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [oe][meta-python][wrynose][PATCH 3/3] python3-aiohttp: fix CVE-2026-69244
2026-10-16 6:42 [oe][meta-python][wrynose][PATCH 0/3] python3-aiohttp: fix multiple CVEs Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-10-16 6:42 ` [oe][meta-python][wrynose][PATCH 1/3] python3-aiohttp: fix CVE-2026-59881 Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-10-16 6:42 ` [oe][meta-python][wrynose][PATCH 2/3] python3-aiohttp: fix CVE-2026-69243 Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)
@ 2026-10-16 6:42 ` Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-10-27 9:28 ` [meta-python][wrynose][PATCH 0/3] python3-aiohttp: fix multiple CVEs Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)
3 siblings, 0 replies; 8+ messages in thread
From: Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco) @ 2026-10-16 6:42 UTC (permalink / raw)
To: openembedded-devel; +Cc: Darsh Kelaiya
From: Darsh Kelaiya <dkelaiya@cisco.com>
This patch applies the upstream fix as referenced in [2], using
the commit shown in [1]. The recipe regenerates the C HTTP parser
from the corrected Cython source during configuration.
[1] https://github.com/aio-libs/aiohttp/commit/49f65d54150397892f7bcc4aae887767d51c322d
[2] https://github.com/aio-libs/aiohttp/security/advisories/GHSA-cq5v-8q36-5273
Signed-off-by: Darsh Kelaiya <dkelaiya@cisco.com>
---
.../python3-aiohttp/CVE-2026-69244.patch | 159 ++++++++++++++++++
.../python/python3-aiohttp_3.13.5.bb | 1 +
2 files changed, 160 insertions(+)
create mode 100644 meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-69244.patch
diff --git a/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-69244.patch b/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-69244.patch
new file mode 100644
index 0000000000..6fc982356a
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-69244.patch
@@ -0,0 +1,159 @@
+From bfa3f83ad150bf6bc660dd0e3178375942b9528a Mon Sep 17 00:00:00 2001
+From: "J. Nick Koston" <nick@koston.org>
+Date: Wed, 22 Jul 2026 11:14:50 -1000
+Subject: [PATCH] [PR #13222/f4866933 backport][3.14] Build C parser error
+ message from bounded buffer slice (#13223)
+
+CVE: CVE-2026-69244
+Upstream-Status: Backport [https://github.com/aio-libs/aiohttp/commit/49f65d54150397892f7bcc4aae887767d51c322d]
+
+Backport Changes:
+- Used mock protocols in three parser-error regression tests:
+ the 3.13.5 test setup differs from upstream, and these tests
+ need no transport behavior to check the bounded error snippet.
+- Omitted generated _http_parser.c because the Wrynose recipe
+ regenerates it from _http_parser.pyx during do_configure.
+
+(cherry picked from commit 49f65d54150397892f7bcc4aae887767d51c322d)
+Signed-off-by: Darsh Kelaiya <dkelaiya@cisco.com>
+---
+ CHANGES/13222.bugfix.rst | 1 +
+ aiohttp/_http_parser.pyx | 9 ++--
+ tests/test_http_parser.py | 92 +++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 98 insertions(+), 4 deletions(-)
+ create mode 100644 CHANGES/13222.bugfix.rst
+
+diff --git a/CHANGES/13222.bugfix.rst b/CHANGES/13222.bugfix.rst
+new file mode 100644
+index 000000000..dcdda1705
+--- /dev/null
++++ b/CHANGES/13222.bugfix.rst
+@@ -0,0 +1 @@
++Fixed error message construction in the C HTTP parser -- by :user:`bdraco`.
+diff --git a/aiohttp/_http_parser.pyx b/aiohttp/_http_parser.pyx
+index 478d82ecb..ffbd53fde 100644
+--- a/aiohttp/_http_parser.pyx
++++ b/aiohttp/_http_parser.pyx
+@@ -623,11 +623,12 @@ cdef class HttpParser:
+ ex = self._last_error
+ self._last_error = None
+ else:
+- after = cparser.llhttp_get_error_pos(self._cparser)
+- before = data[:after - base]
+- after_b = after.split(b"\r\n", 1)[0]
++ error_pos = cparser.llhttp_get_error_pos(self._cparser)
++ error_off = error_pos - base
++ before = data[:error_off]
++ after = data[error_off:].split(b"\r\n", 1)[0]
+ before = before.rsplit(b"\r\n", 1)[-1]
+- data = before + after_b
++ data = before + after
+ pointer = " " * (len(repr(before))-1) + "^"
+ ex = parser_error_from_errno(self._cparser, data, pointer)
+ self._payload = None
+diff --git a/tests/test_http_parser.py b/tests/test_http_parser.py
+index 92e59af1c..4cdd94cb6 100644
+--- a/tests/test_http_parser.py
++++ b/tests/test_http_parser.py
+@@ -1607,6 +1607,98 @@ async def test_http_response_parser_bad_chunked_strict_c(loop, protocol) -> None
+ response.feed_data(text)
+
+
++# The C parser builds its error message from a snippet of the fed buffer. That
++# snippet must be sliced out of the bounded ``data`` bytes object; materializing the
++# raw llhttp error-position pointer instead runs strlen past the end of the buffer.
++# These tests pin the snippet to bytes that lie inside the fed input, and cover error
++# positions right at the buffer end where the over-read used to happen.
++
++_CHUNKED_RESP_HDR = b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"
++
++# (malformed body appended to a chunked response header, expected error snippet,
++# llhttp error reason)
++_BAD_CHUNKED_RESPONSES = [
++ (b"0\rX\r\n\r\n", b"0\rX", "Expected LF after chunk size"),
++ (b"5\r\nhello\rX\r\n0\r\n\r\n", b"hello\rX", "Expected LF after chunk data"),
++ (b"5\r\nhelloXY\r\n0\r\n\r\n", b"helloXY", "Expected LF after chunk data"),
++ (b"1\r\nA\rB\r\n0\r\n\r\n", b"A\rB", "Expected LF after chunk data"),
++ (b"0_2e\r\n\r\n", b"0_2e", "Invalid character in chunk size"),
++]
++
++# Same, but the buffer ends exactly at the offending byte so the error position sits
++# at the end of the input with no trailing CRLF to bound the old strlen-based slice.
++_BAD_CHUNKED_RESPONSES_AT_END = [
++ (b"0\rX", b"0\rX", "Expected LF after chunk size"),
++ (b"5\r\nhello\rX", b"hello\rX", "Expected LF after chunk data"),
++ (b"0_", b"0_", "Invalid character in chunk size"),
++]
++
++_BAD_REQUESTS_AT_END = [
++ (b"GET / HTTP/1.1\r\nHost: x\n", b"Host: x\n"),
++ (b"POST / HTTP/1.1\r\nHost: x\r\nA: b\x01", b"A: b\x01"),
++]
++
++
++@pytest.mark.skipif(NO_EXTENSIONS, reason="Only tests C parser.")
++@pytest.mark.parametrize(
++ ("body", "snippet", "reason"),
++ _BAD_CHUNKED_RESPONSES + _BAD_CHUNKED_RESPONSES_AT_END,
++)
++async def test_c_parser_error_snippet_bounded_response(
++ body: bytes, snippet: bytes, reason: str
++) -> None:
++ loop = asyncio.get_running_loop()
++ protocol = mock.Mock()
++ response = HttpResponseParserC(
++ protocol, loop, 2**16, max_line_size=8190, max_field_size=8190
++ )
++ protocol._parser = response
++ text = _CHUNKED_RESP_HDR + body
++ assert snippet in text
++ match = f"{re.escape(reason)}[\\s\\S]*{re.escape(repr(snippet))}"
++ with pytest.raises(http_exceptions.BadHttpMessage, match=match):
++ response.feed_data(text)
++
++
++@pytest.mark.skipif(NO_EXTENSIONS, reason="Only tests C parser.")
++@pytest.mark.parametrize(("text", "snippet"), _BAD_REQUESTS_AT_END)
++def test_c_parser_error_snippet_at_buffer_end_request(
++ loop: asyncio.AbstractEventLoop,
++ text: bytes,
++ snippet: bytes,
++) -> None:
++ protocol = mock.Mock()
++ parser = HttpRequestParserC(
++ protocol, loop, 2**16, max_line_size=8190, max_field_size=8190
++ )
++ protocol._parser = parser
++ assert snippet in text
++ with pytest.raises(http_exceptions.BadHttpMessage, match=re.escape(repr(snippet))):
++ parser.feed_data(text)
++
++
++@pytest.mark.skipif(NO_EXTENSIONS, reason="Only tests C parser.")
++@pytest.mark.parametrize("split", [1, 2, 3, 5])
++@pytest.mark.parametrize(("body", "snippet", "reason"), _BAD_CHUNKED_RESPONSES)
++async def test_c_parser_error_snippet_fragmented_response(
++ body: bytes, snippet: bytes, reason: str, split: int
++) -> None:
++ # Feed the malformed body in two pieces so the second feed runs through the
++ # tail-prepend path before the error is reported; the parser must report a clean
++ # error rather than reading past the buffer. The snippet offset shifts with the
++ # split, so pin the stable llhttp reason instead.
++ loop = asyncio.get_running_loop()
++ protocol = mock.Mock()
++ response = HttpResponseParserC(
++ protocol, loop, 2**16, max_line_size=8190, max_field_size=8190
++ )
++ protocol._parser = response
++ response.feed_data(_CHUNKED_RESP_HDR)
++ with pytest.raises(http_exceptions.BadHttpMessage, match=re.escape(reason)):
++ response.feed_data(body[:split])
++ response.feed_data(body[split:])
++
++
+ async def test_http_response_parser_notchunked(response) -> None:
+ text = b"HTTP/1.1 200 OK\r\nTransfer-Encoding: notchunked\r\n\r\n1\r\nT\r\n3\r\nest\r\n0\r\n\r\n"
+ messages, upgrade, tail = response.feed_data(text)
+--
+2.35.6
+
diff --git a/meta-python/recipes-devtools/python/python3-aiohttp_3.13.5.bb b/meta-python/recipes-devtools/python/python3-aiohttp_3.13.5.bb
index 417760c9ca..bd5b5fe4d9 100644
--- a/meta-python/recipes-devtools/python/python3-aiohttp_3.13.5.bb
+++ b/meta-python/recipes-devtools/python/python3-aiohttp_3.13.5.bb
@@ -21,6 +21,7 @@ SRC_URI += " \
file://CVE-2026-54273_p2.patch \
file://CVE-2026-59881.patch \
file://CVE-2026-69243.patch \
+ file://CVE-2026-69244.patch \
"
CVE_PRODUCT = "aiohttp:aiohttp"
--
2.35.6
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [meta-python][wrynose][PATCH 0/3] python3-aiohttp: fix multiple CVEs
2026-10-16 6:42 [oe][meta-python][wrynose][PATCH 0/3] python3-aiohttp: fix multiple CVEs Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)
` (2 preceding siblings ...)
2026-10-16 6:42 ` [oe][meta-python][wrynose][PATCH 3/3] python3-aiohttp: fix CVE-2026-69244 Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)
@ 2026-10-27 9:28 ` Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-10-28 5:54 ` [oe] " Anuj Mittal
3 siblings, 1 reply; 8+ messages in thread
From: Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco) @ 2026-10-27 9:28 UTC (permalink / raw)
To: openembedded-devel
[-- Attachment #1: Type: text/plain, Size: 2261 bytes --]
Hello Anuj,
I noticed that the aiohttp series has been picked up in your
anujm/wrynose contrib branch, but the prerequisite fix for
CVE-2026-54273 is missing.
As noted in the cover letter below, the series depends on that patch
for the HTTP parser queue-limit changes and recipe-time Cython
regeneration.
Could you please check the prerequisite patch [1] ( https://patchwork.yoctoproject.org/project/oe/patch/20260928174806.1818191-1-dkelaiya@cisco.com/ ) before this series?
[1] https://patchwork.yoctoproject.org/project/oe/patch/20260928174806.1818191-1-dkelaiya@cisco.com/
Thanks,
Darsh
On Fri, Oct 16, 2026 at 12:12 PM, Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco) wrote:
>
> From: Darsh Kelaiya <dkelaiya@cisco.com>
>
> Backport three upstream aiohttp security fixes to the 3.13.5 recipe
> on wrynose: CVE-2026-59881, CVE-2026-69243, and CVE-2026-69244.
>
> The series fixes WebSocket compression handling, rejected WebSocket
> upgrade request-body parsing, and bounded error messages in the C
> HTTP parser.
> The individual commits retain upstream fix and advisory references.
>
> This series depends on the previously submitted Wrynose fix for
> CVE-2026-54273, which has not merged yet. Apply that patch [1] before
> this series. It provides the HTTP parser queue-limit changes and
> recipe-time Cython regeneration used by these backports.
>
> [1] https://patchwork.yoctoproject.org/project/oe/patch/20260928174806.1818191-1-dkelaiya@cisco.com/
>
>
> Darsh Kelaiya (3):
> python3-aiohttp: fix CVE-2026-59881
> python3-aiohttp: fix CVE-2026-69243
> python3-aiohttp: fix CVE-2026-69244
>
> .../python3-aiohttp/CVE-2026-59881.patch | 103 ++++++
> .../python3-aiohttp/CVE-2026-69243.patch | 307 ++++++++++++++++++
> .../python3-aiohttp/CVE-2026-69244.patch | 159 +++++++++
> .../python/python3-aiohttp_3.13.5.bb | 3 +
> 4 files changed, 572 insertions(+)
> create mode 100644
> meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-59881.patch
> create mode 100644
> meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-69243.patch
> create mode 100644
> meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-69244.patch
>
> --
> 2.35.6
[-- Attachment #2: Type: text/html, Size: 2918 bytes --]
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [oe][meta-python][wrynose][PATCH 2/3] python3-aiohttp: fix CVE-2026-69243
2026-10-16 6:42 ` [oe][meta-python][wrynose][PATCH 2/3] python3-aiohttp: fix CVE-2026-69243 Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)
@ 2026-10-28 5:29 ` Anuj Mittal
2026-10-28 5:39 ` Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)
0 siblings, 1 reply; 8+ messages in thread
From: Anuj Mittal @ 2026-10-28 5:29 UTC (permalink / raw)
To: dkelaiya; +Cc: openembedded-devel
This does not apply:
https://autobuilder.yoctoproject.org/valkyrie/#/builders/81/builds/1906
On Fri, Oct 16, 2026 at 2:42 PM Darsh Kelaiya -X (dkelaiya - E
INFOCHIPS PRIVATE LIMITED at Cisco) via lists.openembedded.org
<dkelaiya=cisco.com@lists.openembedded.org> wrote:
>
> From: Darsh Kelaiya <dkelaiya@cisco.com>
>
> This patch applies the upstream fix as referenced in [2], using
> the commit shown in [1]. It adapts the Python and C HTTP parser
> paths to read a rejected WebSocket upgrade request body before
> switching protocols.
>
> [1] https://github.com/aio-libs/aiohttp/commit/6ae358f0983c3f4d6f67692b2f8e65dc8e091c98
> [2] https://github.com/aio-libs/aiohttp/security/advisories/GHSA-mfx4-hv73-q22v
>
> Signed-off-by: Darsh Kelaiya <dkelaiya@cisco.com>
> ---
> .../python3-aiohttp/CVE-2026-69243.patch | 307 ++++++++++++++++++
> .../python/python3-aiohttp_3.13.5.bb | 1 +
> 2 files changed, 308 insertions(+)
> create mode 100644 meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-69243.patch
>
> diff --git a/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-69243.patch b/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-69243.patch
> new file mode 100644
> index 0000000000..090a82f2f2
> --- /dev/null
> +++ b/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-69243.patch
> @@ -0,0 +1,307 @@
> +From 3267a26ebb0c28cc70b0caf5836d20ff1b7c2f39 Mon Sep 17 00:00:00 2001
> +From: "patchback[bot]" <45432694+patchback[bot]@users.noreply.github.com>
> +Date: Tue, 30 Jun 2026 20:21:57 +0100
> +Subject: [PATCH] [PR #13016/8ef76ab8 backport][3.14] Fix body reads after
> + failed WS upgrade (#13017)
> +
> +This is a backport of PR #13016 as merged into master
> +(8ef76ab8029d0f2d88449fe115feb42ded646c7c).
> +
> +CVE: CVE-2026-69243
> +Upstream-Status: Backport [https://github.com/aio-libs/aiohttp/commit/6ae358f0983c3f4d6f67692b2f8e65dc8e091c98]
> +
> +Backport Changes:
> +- Adapted deferred upgrades to the 3.13.5 llhttp pause and tail
> + buffering path, including the CVE-2026-54273 queue limit.
> +- Adapted the pure Python parser to its boolean payload result.
> +- Used the 3.13.5 buffer size in the earlier 54273 test.
> +- Kept the existing pipelined-upgrade regression test and added
> + the upstream body-read regression test.
> +- Omitted generated _http_parser.c because the Wrynose recipe
> + regenerates it from _http_parser.pyx during do_configure.
> +
> +Co-authored-by: Sam Bull <git@sambull.org>
> +(cherry picked from commit 6ae358f0983c3f4d6f67692b2f8e65dc8e091c98)
> +Signed-off-by: Darsh Kelaiya <dkelaiya@cisco.com>
> +---
> + CHANGES/13016.bugfix.rst | 1 +
> + aiohttp/_http_parser.pyx | 20 +++++++++----
> + aiohttp/http_parser.py | 21 ++++++++++----
> + tests/test_http_parser.py | 48 ++++++++++++++++++++++++++++++++
> + tests/test_web_functional.py | 54 ++++++++++++++++++++++++++++++++++--
> + 5 files changed, 131 insertions(+), 13 deletions(-)
> + create mode 100644 CHANGES/13016.bugfix.rst
> +
> +diff --git a/CHANGES/13016.bugfix.rst b/CHANGES/13016.bugfix.rst
> +new file mode 100644
> +index 000000000..a984f64e3
> +--- /dev/null
> ++++ b/CHANGES/13016.bugfix.rst
> +@@ -0,0 +1 @@
> ++Fixed request body not being read on rejected WebSocket upgrades -- by :user:`Dreamsorcerer`.
> +diff --git a/aiohttp/_http_parser.pyx b/aiohttp/_http_parser.pyx
> +index 0d0762748..478d82ecb 100644
> +--- a/aiohttp/_http_parser.pyx
> ++++ b/aiohttp/_http_parser.pyx
> +@@ -318,6 +318,7 @@ cdef class HttpParser:
> + set _seen_singletons
> + list _raw_headers
> + bint _upgraded
> ++ bint _pending_upgrade
> + list _messages
> + Py_ssize_t _msg_in_flight
> + Py_ssize_t _max_msg_queue_size
> +@@ -387,6 +388,7 @@ cdef class HttpParser:
> + self._response_with_body = response_with_body
> + self._read_until_eof = read_until_eof
> + self._upgraded = False
> ++ self._pending_upgrade = False
> + self._auto_decompress = auto_decompress
> + self._content_encoding = None
> + self._lax = False
> +@@ -466,10 +468,13 @@ cdef class HttpParser:
> + 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
> ++ # Wait until the complete request body has been parsed.
> ++ self._pending_upgrade = True
> + else:
> + if upgrade and self._cparser.status_code == 101:
> +- self._upgraded = True
> ++ # llhttp pauses for a 101 on its own; just mark the pending
> ++ # switch so feed_data returns the upgraded-protocol tail.
> ++ self._pending_upgrade = True
> +
> + # do not support old websocket spec
> + if SEC_WEBSOCKET_KEY1 in headers:
> +@@ -597,6 +602,10 @@ cdef class HttpParser:
> + if errno is cparser.HPE_PAUSED_UPGRADE:
> + cparser.llhttp_resume_after_upgrade(self._cparser)
> + nb = cparser.llhttp_get_error_pos(self._cparser) - base
> ++ if self._pending_upgrade:
> ++ # A supported upgrade whose request body has now been fully read.
> ++ self._upgraded = True
> ++ self._pending_upgrade = False
> + elif errno is cparser.HPE_PAUSED:
> + cparser.llhttp_resume(self._cparser)
> + pos = cparser.llhttp_get_error_pos(self._cparser) - base
> +@@ -818,10 +827,9 @@ cdef int cb_on_headers_complete(cparser.llhttp_t* parser) except -1:
> + pyparser._last_error = exc
> + return -1
> + else:
> +- if pyparser._upgraded or pyparser._cparser.method == cparser.HTTP_CONNECT:
> +- return 2
> +- else:
> +- return 0
> ++ if not pyparser._response_with_body:
> ++ return 1
> ++ return 0
> +
> +
> + cdef int cb_on_body(cparser.llhttp_t* parser,
> +diff --git a/aiohttp/http_parser.py b/aiohttp/http_parser.py
> +index 32375f83a..f4458dc9d 100644
> +--- a/aiohttp/http_parser.py
> ++++ b/aiohttp/http_parser.py
> +@@ -289,6 +289,7 @@ class HttpParser(abc.ABC, Generic[_MsgT]):
> + self._lines: List[bytes] = []
> + self._tail = b""
> + self._upgraded = False
> ++ self._pending_upgrade = False
> + self._payload = None
> + self._payload_parser: Optional[HttpPayloadParser] = None
> + self._auto_decompress = auto_decompress
> +@@ -417,9 +418,7 @@ class HttpParser(abc.ABC, Generic[_MsgT]):
> + if SEC_WEBSOCKET_KEY1 in msg.headers:
> + raise InvalidHeader(SEC_WEBSOCKET_KEY1)
> +
> +- self._upgraded = msg.upgrade and _is_supported_upgrade(
> +- msg.headers
> +- )
> ++ upgraded = msg.upgrade and _is_supported_upgrade(msg.headers)
> +
> + method = getattr(msg, "method", self.method)
> + # code is only present on responses
> +@@ -431,8 +430,7 @@ class HttpParser(abc.ABC, Generic[_MsgT]):
> + method and method in EMPTY_BODY_METHODS
> + )
> + if not empty_body and (
> +- ((length is not None and length > 0) or msg.chunked)
> +- and not self._upgraded
> ++ (length is not None and length > 0) or msg.chunked
> + ):
> + payload = StreamReader(
> + self.protocol,
> +@@ -457,6 +455,10 @@ class HttpParser(abc.ABC, Generic[_MsgT]):
> + )
> + if not payload_parser.done:
> + self._payload_parser = payload_parser
> ++ # https://www.rfc-editor.org/info/rfc9110/#section-7.8-15
> ++ # Defer any requested upgrade until the
> ++ # complete request has been read.
> ++ self._pending_upgrade = upgraded
> + elif method == METH_CONNECT:
> + assert isinstance(msg, RawRequestMessage)
> + payload = StreamReader(
> +@@ -501,6 +503,11 @@ class HttpParser(abc.ABC, Generic[_MsgT]):
> + )
> + if not payload_parser.done:
> + self._payload_parser = payload_parser
> ++ elif upgraded:
> ++ # No body to read, so the connection switches to
> ++ # the upgraded protocol immediately.
> ++ self._upgraded = True
> ++ payload = EMPTY_PAYLOAD
> + else:
> + payload = EMPTY_PAYLOAD
> +
> +@@ -548,6 +555,10 @@ class HttpParser(abc.ABC, Generic[_MsgT]):
> + start_pos = 0
> + data_len = len(data)
> + self._payload_parser = None
> ++ if self._pending_upgrade:
> ++ # The body is complete; the protocol switch may begin.
> ++ self._upgraded = True
> ++ self._pending_upgrade = False
> + continue
> + else:
> + break
> +diff --git a/tests/test_http_parser.py b/tests/test_http_parser.py
> +index a4e6bc056..92e59af1c 100644
> +--- a/tests/test_http_parser.py
> ++++ b/tests/test_http_parser.py
> +@@ -1217,6 +1217,54 @@ async def test_http_request_upgrade_unknown(parser: Any) -> None:
> + assert await messages[0][-1].read() == b"{}"
> +
> +
> ++@pytest.mark.parametrize("chunked", (False, True), ids=("content-length", "chunked"))
> ++async def test_http_request_upgrade_with_body_read(
> ++ parser: HttpRequestParser, chunked: bool
> ++) -> None:
> ++ body_request = b"foobarbaz\r\n\r\n"
> ++ if chunked:
> ++ framing = b"Transfer-Encoding: chunked\r\n"
> ++ body = b"%x\r\n%s\r\n0\r\n\r\n" % (len(body_request), body_request)
> ++ else:
> ++ framing = b"Content-Length: %d\r\n" % len(body_request)
> ++ body = body_request
> ++ after = b"GET /after HTTP/1.1\r\nHost: a\r\n\r\n"
> ++ text = (
> ++ b"GET /ws HTTP/1.1\r\nHost: a\r\n"
> ++ b"Connection: Upgrade\r\nUpgrade: websocket\r\n"
> ++ + framing
> ++ + b"\r\n"
> ++ + body
> ++ + after
> ++ )
> ++ messages, upgrade, tail = parser.feed_data(text)
> ++ assert len(messages) == 1
> ++ msg, payload = messages[0]
> ++ assert msg.method == "GET"
> ++ assert msg.path == "/ws"
> ++ assert msg.upgrade
> ++ assert await payload.read() == body_request
> ++ # The connection switches protocols only after the body is fully read.
> ++ assert upgrade
> ++ assert tail == after
> ++
> ++
> ++def test_http_request_upgrade_empty_body_allowed(parser: HttpRequestParser) -> None:
> ++ text = (
> ++ b"GET /ws HTTP/1.1\r\n"
> ++ b"Host: a\r\n"
> ++ b"Connection: Upgrade\r\n"
> ++ b"Upgrade: websocket\r\n"
> ++ b"Content-Length: 0\r\n\r\n"
> ++ b"some raw data"
> ++ )
> ++ messages, upgrade, tail = parser.feed_data(text)
> ++ msg = messages[0][0]
> ++ assert msg.upgrade
> ++ assert upgrade
> ++ assert tail == b"some raw data"
> ++
> ++
> + @pytest.fixture
> + def xfail_c_parser_url(request) -> None:
> + if isinstance(request.getfixturevalue("parser"), HttpRequestParserPy):
> +diff --git a/tests/test_web_functional.py b/tests/test_web_functional.py
> +index b958bef9d..0d2824a9c 100644
> +--- a/tests/test_web_functional.py
> ++++ b/tests/test_web_functional.py
> +@@ -25,7 +25,6 @@ from aiohttp import (
> + )
> + from aiohttp.compression_utils import ZLibBackend, ZLibCompressObjProtocol
> + from aiohttp.hdrs import CONTENT_LENGTH, CONTENT_TYPE, TRANSFER_ENCODING
> +-from aiohttp.helpers import DEFAULT_CHUNK_SIZE
> + from aiohttp.pytest_plugin import AiohttpClient, AiohttpServer
> + from aiohttp.streams import StreamReader
> + from aiohttp.typedefs import Handler
> +@@ -1923,7 +1922,7 @@ async def test_unread_compressed_body_drain_is_bounded(
> + await writer.wait_closed()
> +
> + # Bounded by the buffer, not the decompressed size.
> +- assert max(drain_reads) <= 3 * DEFAULT_CHUNK_SIZE
> ++ assert max(drain_reads) <= 3 * 2**16
> + assert max(drain_reads) < decompressed_size
> +
> +
> +@@ -2624,3 +2623,54 @@ async def test_keepalive_expires_on_time(aiohttp_client: AiohttpClient) -> None:
> +
> + # Ensure the keep alive handle expires
> + assert request_handler._keepalive_handle is None
> ++
> ++
> ++async def test_declined_websocket_upgrade_reads_body(
> ++ aiohttp_server: AiohttpServer,
> ++) -> None:
> ++ body_read = b""
> ++
> ++ async def ws_handler(request: web.Request) -> web.Response:
> ++ nonlocal body_read
> ++ # Decline the upgrade; read the body as a normal handler may.
> ++ body_read = await request.read()
> ++ return web.Response(text="declined")
> ++
> ++ async def after_handler(request: web.Request) -> web.Response:
> ++ return web.Response(text="after")
> ++
> ++ app = web.Application()
> ++ app.router.add_get("/ws", ws_handler)
> ++ app.router.add_get("/after", after_handler)
> ++ server = await aiohttp_server(app)
> ++
> ++ body = b"FooBarBaz\r\n\r\n"
> ++ # Use raw connection in order to pipeline requests.
> ++ pipeline = (
> ++ (
> ++ b"GET /ws HTTP/1.1\r\n"
> ++ b"Host: localhost\r\n"
> ++ b"Connection: Upgrade\r\n"
> ++ b"Upgrade: websocket\r\n"
> ++ b"Content-Length: %d\r\n\r\n" % len(body)
> ++ )
> ++ + body
> ++ + b"GET /after HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n"
> ++ )
> ++
> ++ reader, writer = await asyncio.open_connection(server.host, server.port)
> ++ try:
> ++ writer.write(pipeline)
> ++ await writer.drain()
> ++ # The trailing request sends Connection: close, so the server closes
> ++ # once it has answered it -- reading to EOF gathers both responses.
> ++ response = await asyncio.wait_for(reader.read(), 5)
> ++ finally:
> ++ writer.close()
> ++ with suppress(ConnectionResetError, BrokenPipeError):
> ++ await writer.wait_closed()
> ++
> ++ assert body_read == body
> ++ # Both the upgrade request and the pipelined request were served.
> ++ assert response.count(b"HTTP/1.") == 2, response
> ++ assert response.count(b" 200 ") == 2, response
> +--
> +2.35.6
> +
> diff --git a/meta-python/recipes-devtools/python/python3-aiohttp_3.13.5.bb b/meta-python/recipes-devtools/python/python3-aiohttp_3.13.5.bb
> index b34a0fa3eb..417760c9ca 100644
> --- a/meta-python/recipes-devtools/python/python3-aiohttp_3.13.5.bb
> +++ b/meta-python/recipes-devtools/python/python3-aiohttp_3.13.5.bb
> @@ -20,6 +20,7 @@ SRC_URI += " \
> file://CVE-2026-54273_p1.patch \
> file://CVE-2026-54273_p2.patch \
> file://CVE-2026-59881.patch \
> + file://CVE-2026-69243.patch \
> "
>
> CVE_PRODUCT = "aiohttp:aiohttp"
> --
> 2.35.6
>
>
> -=-=-=-=-=-=-=-=-=-=-=-
> Links: You receive all messages sent to this group.
> View/Reply Online (#131067): https://lists.openembedded.org/g/openembedded-devel/message/131067
> Mute This Topic: https://lists.openembedded.org/mt/121783978/3616702
> Group Owner: openembedded-devel+owner@lists.openembedded.org
> Unsubscribe: https://lists.openembedded.org/g/openembedded-devel/unsub [anuj.mittal@oss.qualcomm.com]
> -=-=-=-=-=-=-=-=-=-=-=-
>
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [oe][meta-python][wrynose][PATCH 2/3] python3-aiohttp: fix CVE-2026-69243
2026-10-28 5:29 ` Anuj Mittal
@ 2026-10-28 5:39 ` Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)
0 siblings, 0 replies; 8+ messages in thread
From: Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco) @ 2026-10-28 5:39 UTC (permalink / raw)
To: anuj.mittal@oss.qualcomm.com; +Cc: openembedded-devel@lists.openembedded.org
[-- Attachment #1: Type: text/plain, Size: 16670 bytes --]
Hello Anuj,
Please check this:
https://lists.openembedded.org/g/openembedded-devel/message/131527
Thanks,
Darsh
________________________________
From: openembedded-devel@lists.openembedded.org <openembedded-devel@lists.openembedded.org> on behalf of Anuj Mittal via lists.openembedded.org <anuj.mittal=oss.qualcomm.com@lists.openembedded.org>
Sent: Wednesday, October 28, 2026 10:59
To: Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco) <dkelaiya@cisco.com>
Cc: openembedded-devel@lists.openembedded.org <openembedded-devel@lists.openembedded.org>
Subject: Re: [oe][meta-python][wrynose][PATCH 2/3] python3-aiohttp: fix CVE-2026-69243
This does not apply:
https://autobuilder.yoctoproject.org/valkyrie/#/builders/81/builds/1906
On Fri, Oct 16, 2026 at 2:42 PM Darsh Kelaiya -X (dkelaiya - E
INFOCHIPS PRIVATE LIMITED at Cisco) via lists.openembedded.org
<dkelaiya=cisco.com@lists.openembedded.org> wrote:
>
> From: Darsh Kelaiya <dkelaiya@cisco.com>
>
> This patch applies the upstream fix as referenced in [2], using
> the commit shown in [1]. It adapts the Python and C HTTP parser
> paths to read a rejected WebSocket upgrade request body before
> switching protocols.
>
> [1] https://github.com/aio-libs/aiohttp/commit/6ae358f0983c3f4d6f67692b2f8e65dc8e091c98
> [2] https://github.com/aio-libs/aiohttp/security/advisories/GHSA-mfx4-hv73-q22v
>
> Signed-off-by: Darsh Kelaiya <dkelaiya@cisco.com>
> ---
> .../python3-aiohttp/CVE-2026-69243.patch | 307 ++++++++++++++++++
> .../python/python3-aiohttp_3.13.5.bb | 1 +
> 2 files changed, 308 insertions(+)
> create mode 100644 meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-69243.patch
>
> diff --git a/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-69243.patch b/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-69243.patch
> new file mode 100644
> index 0000000000..090a82f2f2
> --- /dev/null
> +++ b/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-69243.patch
> @@ -0,0 +1,307 @@
> +From 3267a26ebb0c28cc70b0caf5836d20ff1b7c2f39 Mon Sep 17 00:00:00 2001
> +From: "patchback[bot]" <45432694+patchback[bot]@users.noreply.github.com>
> +Date: Tue, 30 Jun 2026 20:21:57 +0100
> +Subject: [PATCH] [PR #13016/8ef76ab8 backport][3.14] Fix body reads after
> + failed WS upgrade (#13017)
> +
> +This is a backport of PR #13016 as merged into master
> +(8ef76ab8029d0f2d88449fe115feb42ded646c7c).
> +
> +CVE: CVE-2026-69243
> +Upstream-Status: Backport [https://github.com/aio-libs/aiohttp/commit/6ae358f0983c3f4d6f67692b2f8e65dc8e091c98]
> +
> +Backport Changes:
> +- Adapted deferred upgrades to the 3.13.5 llhttp pause and tail
> + buffering path, including the CVE-2026-54273 queue limit.
> +- Adapted the pure Python parser to its boolean payload result.
> +- Used the 3.13.5 buffer size in the earlier 54273 test.
> +- Kept the existing pipelined-upgrade regression test and added
> + the upstream body-read regression test.
> +- Omitted generated _http_parser.c because the Wrynose recipe
> + regenerates it from _http_parser.pyx during do_configure.
> +
> +Co-authored-by: Sam Bull <git@sambull.org>
> +(cherry picked from commit 6ae358f0983c3f4d6f67692b2f8e65dc8e091c98)
> +Signed-off-by: Darsh Kelaiya <dkelaiya@cisco.com>
> +---
> + CHANGES/13016.bugfix.rst | 1 +
> + aiohttp/_http_parser.pyx | 20 +++++++++----
> + aiohttp/http_parser.py | 21 ++++++++++----
> + tests/test_http_parser.py | 48 ++++++++++++++++++++++++++++++++
> + tests/test_web_functional.py | 54 ++++++++++++++++++++++++++++++++++--
> + 5 files changed, 131 insertions(+), 13 deletions(-)
> + create mode 100644 CHANGES/13016.bugfix.rst
> +
> +diff --git a/CHANGES/13016.bugfix.rst b/CHANGES/13016.bugfix.rst
> +new file mode 100644
> +index 000000000..a984f64e3
> +--- /dev/null
> ++++ b/CHANGES/13016.bugfix.rst
> +@@ -0,0 +1 @@
> ++Fixed request body not being read on rejected WebSocket upgrades -- by :user:`Dreamsorcerer`.
> +diff --git a/aiohttp/_http_parser.pyx b/aiohttp/_http_parser.pyx
> +index 0d0762748..478d82ecb 100644
> +--- a/aiohttp/_http_parser.pyx
> ++++ b/aiohttp/_http_parser.pyx
> +@@ -318,6 +318,7 @@ cdef class HttpParser:
> + set _seen_singletons
> + list _raw_headers
> + bint _upgraded
> ++ bint _pending_upgrade
> + list _messages
> + Py_ssize_t _msg_in_flight
> + Py_ssize_t _max_msg_queue_size
> +@@ -387,6 +388,7 @@ cdef class HttpParser:
> + self._response_with_body = response_with_body
> + self._read_until_eof = read_until_eof
> + self._upgraded = False
> ++ self._pending_upgrade = False
> + self._auto_decompress = auto_decompress
> + self._content_encoding = None
> + self._lax = False
> +@@ -466,10 +468,13 @@ cdef class HttpParser:
> + 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
> ++ # Wait until the complete request body has been parsed.
> ++ self._pending_upgrade = True
> + else:
> + if upgrade and self._cparser.status_code == 101:
> +- self._upgraded = True
> ++ # llhttp pauses for a 101 on its own; just mark the pending
> ++ # switch so feed_data returns the upgraded-protocol tail.
> ++ self._pending_upgrade = True
> +
> + # do not support old websocket spec
> + if SEC_WEBSOCKET_KEY1 in headers:
> +@@ -597,6 +602,10 @@ cdef class HttpParser:
> + if errno is cparser.HPE_PAUSED_UPGRADE:
> + cparser.llhttp_resume_after_upgrade(self._cparser)
> + nb = cparser.llhttp_get_error_pos(self._cparser) - base
> ++ if self._pending_upgrade:
> ++ # A supported upgrade whose request body has now been fully read.
> ++ self._upgraded = True
> ++ self._pending_upgrade = False
> + elif errno is cparser.HPE_PAUSED:
> + cparser.llhttp_resume(self._cparser)
> + pos = cparser.llhttp_get_error_pos(self._cparser) - base
> +@@ -818,10 +827,9 @@ cdef int cb_on_headers_complete(cparser.llhttp_t* parser) except -1:
> + pyparser._last_error = exc
> + return -1
> + else:
> +- if pyparser._upgraded or pyparser._cparser.method == cparser.HTTP_CONNECT:
> +- return 2
> +- else:
> +- return 0
> ++ if not pyparser._response_with_body:
> ++ return 1
> ++ return 0
> +
> +
> + cdef int cb_on_body(cparser.llhttp_t* parser,
> +diff --git a/aiohttp/http_parser.py b/aiohttp/http_parser.py
> +index 32375f83a..f4458dc9d 100644
> +--- a/aiohttp/http_parser.py
> ++++ b/aiohttp/http_parser.py
> +@@ -289,6 +289,7 @@ class HttpParser(abc.ABC, Generic[_MsgT]):
> + self._lines: List[bytes] = []
> + self._tail = b""
> + self._upgraded = False
> ++ self._pending_upgrade = False
> + self._payload = None
> + self._payload_parser: Optional[HttpPayloadParser] = None
> + self._auto_decompress = auto_decompress
> +@@ -417,9 +418,7 @@ class HttpParser(abc.ABC, Generic[_MsgT]):
> + if SEC_WEBSOCKET_KEY1 in msg.headers:
> + raise InvalidHeader(SEC_WEBSOCKET_KEY1)
> +
> +- self._upgraded = msg.upgrade and _is_supported_upgrade(
> +- msg.headers
> +- )
> ++ upgraded = msg.upgrade and _is_supported_upgrade(msg.headers)
> +
> + method = getattr(msg, "method", self.method)
> + # code is only present on responses
> +@@ -431,8 +430,7 @@ class HttpParser(abc.ABC, Generic[_MsgT]):
> + method and method in EMPTY_BODY_METHODS
> + )
> + if not empty_body and (
> +- ((length is not None and length > 0) or msg.chunked)
> +- and not self._upgraded
> ++ (length is not None and length > 0) or msg.chunked
> + ):
> + payload = StreamReader(
> + self.protocol,
> +@@ -457,6 +455,10 @@ class HttpParser(abc.ABC, Generic[_MsgT]):
> + )
> + if not payload_parser.done:
> + self._payload_parser = payload_parser
> ++ # https://www.rfc-editor.org/info/rfc9110/#section-7.8-15
> ++ # Defer any requested upgrade until the
> ++ # complete request has been read.
> ++ self._pending_upgrade = upgraded
> + elif method == METH_CONNECT:
> + assert isinstance(msg, RawRequestMessage)
> + payload = StreamReader(
> +@@ -501,6 +503,11 @@ class HttpParser(abc.ABC, Generic[_MsgT]):
> + )
> + if not payload_parser.done:
> + self._payload_parser = payload_parser
> ++ elif upgraded:
> ++ # No body to read, so the connection switches to
> ++ # the upgraded protocol immediately.
> ++ self._upgraded = True
> ++ payload = EMPTY_PAYLOAD
> + else:
> + payload = EMPTY_PAYLOAD
> +
> +@@ -548,6 +555,10 @@ class HttpParser(abc.ABC, Generic[_MsgT]):
> + start_pos = 0
> + data_len = len(data)
> + self._payload_parser = None
> ++ if self._pending_upgrade:
> ++ # The body is complete; the protocol switch may begin.
> ++ self._upgraded = True
> ++ self._pending_upgrade = False
> + continue
> + else:
> + break
> +diff --git a/tests/test_http_parser.py b/tests/test_http_parser.py
> +index a4e6bc056..92e59af1c 100644
> +--- a/tests/test_http_parser.py
> ++++ b/tests/test_http_parser.py
> +@@ -1217,6 +1217,54 @@ async def test_http_request_upgrade_unknown(parser: Any) -> None:
> + assert await messages[0][-1].read() == b"{}"
> +
> +
> ++@pytest.mark.parametrize("chunked", (False, True), ids=("content-length", "chunked"))
> ++async def test_http_request_upgrade_with_body_read(
> ++ parser: HttpRequestParser, chunked: bool
> ++) -> None:
> ++ body_request = b"foobarbaz\r\n\r\n"
> ++ if chunked:
> ++ framing = b"Transfer-Encoding: chunked\r\n"
> ++ body = b"%x\r\n%s\r\n0\r\n\r\n" % (len(body_request), body_request)
> ++ else:
> ++ framing = b"Content-Length: %d\r\n" % len(body_request)
> ++ body = body_request
> ++ after = b"GET /after HTTP/1.1\r\nHost: a\r\n\r\n"
> ++ text = (
> ++ b"GET /ws HTTP/1.1\r\nHost: a\r\n"
> ++ b"Connection: Upgrade\r\nUpgrade: websocket\r\n"
> ++ + framing
> ++ + b"\r\n"
> ++ + body
> ++ + after
> ++ )
> ++ messages, upgrade, tail = parser.feed_data(text)
> ++ assert len(messages) == 1
> ++ msg, payload = messages[0]
> ++ assert msg.method == "GET"
> ++ assert msg.path == "/ws"
> ++ assert msg.upgrade
> ++ assert await payload.read() == body_request
> ++ # The connection switches protocols only after the body is fully read.
> ++ assert upgrade
> ++ assert tail == after
> ++
> ++
> ++def test_http_request_upgrade_empty_body_allowed(parser: HttpRequestParser) -> None:
> ++ text = (
> ++ b"GET /ws HTTP/1.1\r\n"
> ++ b"Host: a\r\n"
> ++ b"Connection: Upgrade\r\n"
> ++ b"Upgrade: websocket\r\n"
> ++ b"Content-Length: 0\r\n\r\n"
> ++ b"some raw data"
> ++ )
> ++ messages, upgrade, tail = parser.feed_data(text)
> ++ msg = messages[0][0]
> ++ assert msg.upgrade
> ++ assert upgrade
> ++ assert tail == b"some raw data"
> ++
> ++
> + @pytest.fixture
> + def xfail_c_parser_url(request) -> None:
> + if isinstance(request.getfixturevalue("parser"), HttpRequestParserPy):
> +diff --git a/tests/test_web_functional.py b/tests/test_web_functional.py
> +index b958bef9d..0d2824a9c 100644
> +--- a/tests/test_web_functional.py
> ++++ b/tests/test_web_functional.py
> +@@ -25,7 +25,6 @@ from aiohttp import (
> + )
> + from aiohttp.compression_utils import ZLibBackend, ZLibCompressObjProtocol
> + from aiohttp.hdrs import CONTENT_LENGTH, CONTENT_TYPE, TRANSFER_ENCODING
> +-from aiohttp.helpers import DEFAULT_CHUNK_SIZE
> + from aiohttp.pytest_plugin import AiohttpClient, AiohttpServer
> + from aiohttp.streams import StreamReader
> + from aiohttp.typedefs import Handler
> +@@ -1923,7 +1922,7 @@ async def test_unread_compressed_body_drain_is_bounded(
> + await writer.wait_closed()
> +
> + # Bounded by the buffer, not the decompressed size.
> +- assert max(drain_reads) <= 3 * DEFAULT_CHUNK_SIZE
> ++ assert max(drain_reads) <= 3 * 2**16
> + assert max(drain_reads) < decompressed_size
> +
> +
> +@@ -2624,3 +2623,54 @@ async def test_keepalive_expires_on_time(aiohttp_client: AiohttpClient) -> None:
> +
> + # Ensure the keep alive handle expires
> + assert request_handler._keepalive_handle is None
> ++
> ++
> ++async def test_declined_websocket_upgrade_reads_body(
> ++ aiohttp_server: AiohttpServer,
> ++) -> None:
> ++ body_read = b""
> ++
> ++ async def ws_handler(request: web.Request) -> web.Response:
> ++ nonlocal body_read
> ++ # Decline the upgrade; read the body as a normal handler may.
> ++ body_read = await request.read()
> ++ return web.Response(text="declined")
> ++
> ++ async def after_handler(request: web.Request) -> web.Response:
> ++ return web.Response(text="after")
> ++
> ++ app = web.Application()
> ++ app.router.add_get("/ws", ws_handler)
> ++ app.router.add_get("/after", after_handler)
> ++ server = await aiohttp_server(app)
> ++
> ++ body = b"FooBarBaz\r\n\r\n"
> ++ # Use raw connection in order to pipeline requests.
> ++ pipeline = (
> ++ (
> ++ b"GET /ws HTTP/1.1\r\n"
> ++ b"Host: localhost\r\n"
> ++ b"Connection: Upgrade\r\n"
> ++ b"Upgrade: websocket\r\n"
> ++ b"Content-Length: %d\r\n\r\n" % len(body)
> ++ )
> ++ + body
> ++ + b"GET /after HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n"
> ++ )
> ++
> ++ reader, writer = await asyncio.open_connection(server.host, server.port)
> ++ try:
> ++ writer.write(pipeline)
> ++ await writer.drain()
> ++ # The trailing request sends Connection: close, so the server closes
> ++ # once it has answered it -- reading to EOF gathers both responses.
> ++ response = await asyncio.wait_for(reader.read(), 5)
> ++ finally:
> ++ writer.close()
> ++ with suppress(ConnectionResetError, BrokenPipeError):
> ++ await writer.wait_closed()
> ++
> ++ assert body_read == body
> ++ # Both the upgrade request and the pipelined request were served.
> ++ assert response.count(b"HTTP/1.") == 2, response
> ++ assert response.count(b" 200 ") == 2, response
> +--
> +2.35.6
> +
> diff --git a/meta-python/recipes-devtools/python/python3-aiohttp_3.13.5.bb b/meta-python/recipes-devtools/python/python3-aiohttp_3.13.5.bb
> index b34a0fa3eb..417760c9ca 100644
> --- a/meta-python/recipes-devtools/python/python3-aiohttp_3.13.5.bb
> +++ b/meta-python/recipes-devtools/python/python3-aiohttp_3.13.5.bb
> @@ -20,6 +20,7 @@ SRC_URI += " \
> file://CVE-2026-54273_p1.patch \
> file://CVE-2026-54273_p2.patch \
> file://CVE-2026-59881.patch \
> + file://CVE-2026-69243.patch \
> "
>
> CVE_PRODUCT = "aiohttp:aiohttp"
> --
> 2.35.6
>
>
>
>
[-- Attachment #2: Type: text/html, Size: 34586 bytes --]
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [oe] [meta-python][wrynose][PATCH 0/3] python3-aiohttp: fix multiple CVEs
2026-10-27 9:28 ` [meta-python][wrynose][PATCH 0/3] python3-aiohttp: fix multiple CVEs Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)
@ 2026-10-28 5:54 ` Anuj Mittal
0 siblings, 0 replies; 8+ messages in thread
From: Anuj Mittal @ 2026-10-28 5:54 UTC (permalink / raw)
To: dkelaiya; +Cc: openembedded-devel
On Tue, Oct 27, 2026 at 5:28 PM Darsh Kelaiya -X (dkelaiya - E
INFOCHIPS PRIVATE LIMITED at Cisco) via lists.openembedded.org
<dkelaiya=cisco.com@lists.openembedded.org> wrote:
>
> Hello Anuj,
>
> I noticed that the aiohttp series has been picked up in your
> anujm/wrynose contrib branch, but the prerequisite fix for
> CVE-2026-54273 is missing.
>
> As noted in the cover letter below, the series depends on that patch
> for the HTTP parser queue-limit changes and recipe-time Cython
> regeneration.
>
> Could you please check the prerequisite patch [1] before this series?
Thank you. I was reviewing the patches and missed including the first
one. I will try this again.
Thanks,
Anuj
>
> [1] https://patchwork.yoctoproject.org/project/oe/patch/20260928174806.1818191-1-dkelaiya@cisco.com/
>
> Thanks,
> Darsh
>
> On Fri, Oct 16, 2026 at 12:12 PM, Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco) wrote:
>
> From: Darsh Kelaiya <dkelaiya@cisco.com>
>
> Backport three upstream aiohttp security fixes to the 3.13.5 recipe
> on wrynose: CVE-2026-59881, CVE-2026-69243, and CVE-2026-69244.
>
> The series fixes WebSocket compression handling, rejected WebSocket
> upgrade request-body parsing, and bounded error messages in the C
> HTTP parser.
> The individual commits retain upstream fix and advisory references.
>
> This series depends on the previously submitted Wrynose fix for
> CVE-2026-54273, which has not merged yet. Apply that patch [1] before
> this series. It provides the HTTP parser queue-limit changes and
> recipe-time Cython regeneration used by these backports.
>
> [1] https://patchwork.yoctoproject.org/project/oe/patch/20260928174806.1818191-1-dkelaiya@cisco.com/
>
> Darsh Kelaiya (3):
> python3-aiohttp: fix CVE-2026-59881
> python3-aiohttp: fix CVE-2026-69243
> python3-aiohttp: fix CVE-2026-69244
>
> .../python3-aiohttp/CVE-2026-59881.patch | 103 ++++++
> .../python3-aiohttp/CVE-2026-69243.patch | 307 ++++++++++++++++++
> .../python3-aiohttp/CVE-2026-69244.patch | 159 +++++++++
> .../python/python3-aiohttp_3.13.5.bb | 3 +
> 4 files changed, 572 insertions(+)
> create mode 100644 meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-59881.patch
> create mode 100644 meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-69243.patch
> create mode 100644 meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-69244.patch
>
> --
> 2.35.6
>
>
> -=-=-=-=-=-=-=-=-=-=-=-
> Links: You receive all messages sent to this group.
> View/Reply Online (#131527): https://lists.openembedded.org/g/openembedded-devel/message/131527
> Mute This Topic: https://lists.openembedded.org/mt/121783976/3616702
> Group Owner: openembedded-devel+owner@lists.openembedded.org
> Unsubscribe: https://lists.openembedded.org/g/openembedded-devel/unsub [anuj.mittal@oss.qualcomm.com]
> -=-=-=-=-=-=-=-=-=-=-=-
>
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-10-28 5:55 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-16 6:42 [oe][meta-python][wrynose][PATCH 0/3] python3-aiohttp: fix multiple CVEs Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-10-16 6:42 ` [oe][meta-python][wrynose][PATCH 1/3] python3-aiohttp: fix CVE-2026-59881 Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-10-16 6:42 ` [oe][meta-python][wrynose][PATCH 2/3] python3-aiohttp: fix CVE-2026-69243 Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-10-28 5:29 ` Anuj Mittal
2026-10-28 5:39 ` Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-10-16 6:42 ` [oe][meta-python][wrynose][PATCH 3/3] python3-aiohttp: fix CVE-2026-69244 Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-10-27 9:28 ` [meta-python][wrynose][PATCH 0/3] python3-aiohttp: fix multiple CVEs Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)
2026-10-28 5:54 ` [oe] " Anuj Mittal
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.