[oe][meta-python][wrynose][PATCH 04/10] python3-aiohttp: fix CVE-2026-54274

"Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)" <[email protected]>
Newsgroups org.openembedded.lists.openembedded-devel
Message-ID <[email protected]>
From: Darsh Kelaiya <[email protected]>

This patch applies the upstream fix as referenced in [2], using
the commit shown in [1].

[1] https://github.com/aio-libs/aiohttp/commit/14b6ee851fb16ec199acb950de0c82d476799e7d
[2] https://github.com/advisories/GHSA-xcgm-r5h9-7989

Signed-off-by: Darsh Kelaiya <[email protected]>
---
 .../python3-aiohttp/CVE-2026-54274.patch      | 182 ++++++++++++++++++
 .../python/python3-aiohttp_3.13.5.bb          |   1 +
 2 files changed, 183 insertions(+)
 create mode 100644 meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-54274.patch

diff --git a/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-54274.patch b/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-54274.patch
new file mode 100644
index 0000000000..27a2438cc1
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-54274.patch
@@ -0,0 +1,182 @@
+From f971aaedd8dc9cab84f09b97a412744084115113 Mon Sep 17 00:00:00 2001
+From: "patchback[bot]" <45432694+patchback[bot]@users.noreply.github.com>
+Date: Sat, 6 Jun 2026 00:05:19 +0100
+Subject: [PATCH] [PR #12817/69344c6e backport][3.14] Improve websocket checks
+ (#12818)
+
+**This is a backport of PR #12817 as merged into master
+(69344c6efa3e5dd80b1c88079fa06d4e902a3b83).**
+
+CVE: CVE-2026-54274
+Upstream-Status: Backport [https://github.com/aio-libs/aiohttp/commit/14b6ee851fb16ec199acb950de0c82d476799e7d]
+
+Co-authored-by: Sam Bull <[email protected]>
+(cherry picked from commit 14b6ee851fb16ec199acb950de0c82d476799e7d)
+Signed-off-by: Darsh Kelaiya <[email protected]>
+---
+ CHANGES/12817.bugfix.rst        |  1 +
+ aiohttp/_websocket/reader_py.py | 42 +++++++++++++-------
+ tests/test_websocket_parser.py  | 68 +++++++++++++++++++++++++++++++++
+ 3 files changed, 98 insertions(+), 13 deletions(-)
+ create mode 100644 CHANGES/12817.bugfix.rst
+
+diff --git a/CHANGES/12817.bugfix.rst b/CHANGES/12817.bugfix.rst
+new file mode 100644
+index 000000000..c8a35e309
+--- /dev/null
++++ b/CHANGES/12817.bugfix.rst
+@@ -0,0 +1 @@
++Tightened up some websocket parser checks -- by :user:`Dreamsorcerer`.
+diff --git a/aiohttp/_websocket/reader_py.py b/aiohttp/_websocket/reader_py.py
+index 5166d7ec2..16c5f3be6 100644
+--- a/aiohttp/_websocket/reader_py.py
++++ b/aiohttp/_websocket/reader_py.py
+@@ -202,12 +202,6 @@ class WebSocketReader:
+                 if opcode != OP_CODE_CONTINUATION:
+                     self._opcode = opcode
+                 self._partial += payload
+-                if self._max_msg_size and len(self._partial) >= self._max_msg_size:
+-                    raise WebSocketError(
+-                        WSCloseCode.MESSAGE_TOO_BIG,
+-                        f"Message size {len(self._partial)} "
+-                        f"exceeds limit {self._max_msg_size}",
+-                    )
+                 return
+ 
+             has_partial = bool(self._partial)
+@@ -230,13 +224,6 @@ class WebSocketReader:
+             else:
+                 assembled_payload = payload
+ 
+-            if self._max_msg_size and len(assembled_payload) >= self._max_msg_size:
+-                raise WebSocketError(
+-                    WSCloseCode.MESSAGE_TOO_BIG,
+-                    f"Message size {len(assembled_payload)} "
+-                    f"exceeds limit {self._max_msg_size}",
+-                )
+-
+             # Decompress process must to be done after all packets
+             # received.
+             if compressed:
+@@ -361,6 +348,19 @@ class WebSocketReader:
+                         "Received frame with non-zero reserved bits",
+                     )
+ 
++                if opcode not in {
++                    OP_CODE_CONTINUATION,
++                    OP_CODE_TEXT,
++                    OP_CODE_BINARY,
++                    OP_CODE_CLOSE,
++                    OP_CODE_PING,
++                    OP_CODE_PONG,
++                }:
++                    raise WebSocketError(
++                        WSCloseCode.PROTOCOL_ERROR,
++                        f"Unexpected opcode={opcode!r}",
++                    )
++
+                 if opcode > 0x7 and fin == 0:
+                     raise WebSocketError(
+                         WSCloseCode.PROTOCOL_ERROR,
+@@ -413,6 +413,22 @@ class WebSocketReader:
+                 else:
+                     self._payload_bytes_to_read = len_flag
+ 
++                # Reject oversized data frames before buffering any payload
++                # bytes. Control frames are capped at 125 bytes (checked in
++                # READ_HEADER) so only text/binary/continuation need this.
++                if self._max_msg_size and self._frame_opcode in {
++                    OP_CODE_TEXT,
++                    OP_CODE_BINARY,
++                    OP_CODE_CONTINUATION,
++                }:
++                    projected_size = self._payload_bytes_to_read + len(self._partial)
++                    if projected_size >= self._max_msg_size:
++                        raise WebSocketError(
++                            WSCloseCode.MESSAGE_TOO_BIG,
++                            f"Message size {projected_size} "
++                            f"exceeds limit {self._max_msg_size}",
++                        )
++
+                 self._state = READ_PAYLOAD_MASK if self._has_mask else READ_PAYLOAD
+ 
+             # read payload mask
+diff --git a/tests/test_websocket_parser.py b/tests/test_websocket_parser.py
+index 603310958..3a283e0a3 100644
+--- a/tests/test_websocket_parser.py
++++ b/tests/test_websocket_parser.py
+@@ -643,6 +643,74 @@ def test_compressed_msg_too_large(out) -> None:
+     assert ctx.value.code == WSCloseCode.MESSAGE_TOO_BIG
+ 
+ 
[email protected]("fin", (0x80, 0x00), ids=("fin", "non-fin"))
++def test_msg_too_large_at_header(out: WebSocketDataQueue, fin: int) -> None:
++    max_msg_size = 256
++    parser = WebSocketReader(out, max_msg_size, compress=False)
++
++    # Header alone: TEXT, 64-bit length, declares 1 MiB of payload.
++    header = PACK_LEN3(fin | WSMsgType.TEXT, 127, 1024 * 1024)
++    with pytest.raises(
++        WebSocketError, match=r"^Message size 1048576 exceeds limit 256$"
++    ) as ctx:
++        parser._feed_data(header)
++    assert ctx.value.code == WSCloseCode.MESSAGE_TOO_BIG
++
++
++def test_msg_too_large_across_fragments(out: WebSocketDataQueue) -> None:
++    # Individual fragments fit under max_msg_size but accumulate past it.
++    max_msg_size = 256
++    parser = WebSocketReader(out, max_msg_size, compress=False)
++
++    first = build_frame(b"a" * 100, WSMsgType.TEXT, is_fin=False)
++    parser._feed_data(first)
++    middle = build_frame(b"b" * 100, WSMsgType.CONTINUATION, is_fin=False)
++    parser._feed_data(middle)
++
++    # Third 100-byte fragment would push the accumulated total to 300.
++    last = build_frame(b"c" * 100, WSMsgType.CONTINUATION, is_fin=False)
++    with pytest.raises(
++        WebSocketError, match=r"^Message size 300 exceeds limit 256$"
++    ) as ctx:
++        parser._feed_data(last)
++    assert ctx.value.code == WSCloseCode.MESSAGE_TOO_BIG
++
++
++def test_msg_too_large_text_after_non_fin_text(out: WebSocketDataQueue) -> None:
++    # Protocol-violating sequence: a fresh TEXT arrives while a fragmented
++    # message is still open.
++    max_msg_size = 256
++    parser = WebSocketReader(out, max_msg_size, compress=False)
++
++    first = build_frame(b"a" * 200, WSMsgType.TEXT, is_fin=False)
++    parser._feed_data(first)
++
++    # Second TEXT header alone announces 100 bytes; 100 + 200 partial = 300.
++    second_header = PACK_LEN1(WSMsgType.TEXT, 100)
++    with pytest.raises(
++        WebSocketError, match=r"^Message size 300 exceeds limit 256$"
++    ) as ctx:
++        parser._feed_data(second_header)
++    assert ctx.value.code == WSCloseCode.MESSAGE_TOO_BIG
++
++
[email protected](
++    "opcode",
++    (0x3, 0x4, 0x5, 0x6, 0x7, 0xB, 0xC, 0xD, 0xE, 0xF),
++    ids=lambda v: f"0x{v:x}",
++)
++def test_reserved_opcode_rejected_at_header(
++    out: WebSocketDataQueue, opcode: int
++) -> None:
++    # RFC 6455 reserves opcodes 0x3-0x7 (non-control) and 0xB-0xF (control).
++    parser = WebSocketReader(out, max_msg_size=256, compress=False)
++
++    header = PACK_LEN3(0x80 | opcode, 127, 1024 * 1024)
++    with pytest.raises(WebSocketError, match=rf"^Unexpected opcode={opcode}$") as ctx:
++        parser._feed_data(header)
++    assert ctx.value.code == WSCloseCode.PROTOCOL_ERROR
++
++
+ class TestWebSocketError:
+     def test_ctor(self) -> None:
+         err = WebSocketError(WSCloseCode.PROTOCOL_ERROR, "Something invalid")
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 33ef64a59b..a40f522e4d 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
@@ -10,6 +10,7 @@ SRC_URI += " \
     file://CVE-2026-34993.patch \
     file://CVE-2026-47265.patch \
     file://CVE-2026-50269.patch \
+    file://CVE-2026-54274.patch \
 "
 
 CVE_PRODUCT = "aiohttp"
-- 
2.35.6
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.