diff mbox series

[meta-python,wrynose,1/3] python3-aiohttp: fix CVE-2026-59881

Message ID 20261016064219.3910025-2-dkelaiya@cisco.com
State New
Headers show
Series python3-aiohttp: fix multiple CVEs | expand

Commit Message

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 mbox series

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"