All of lore.kernel.org
 help / color / mirror / Atom feed
* [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.