From patchwork Fri Oct 16 06:42:17 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)" X-Patchwork-Id: 101051 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 88917CCA462 for ; Fri, 16 Oct 2026 06:42:56 +0000 (UTC) Received: from rcdn-iport-1.cisco.com (rcdn-iport-1.cisco.com [173.37.86.72]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.16161.1792132971502053406 for ; Thu, 15 Oct 2026 23:42:51 -0700 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: message contains an insecure body length tag" header.i=@cisco.com header.s=iport01 header.b=DG2X7PGF; spf=pass (domain: cisco.com, ip: 173.37.86.72, mailfrom: dkelaiya@cisco.com) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cisco.com; i=@cisco.com; l=5377; q=dns/txt; s=iport01; t=1792132971; x=1793342571; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=7ejFU6x/2M+XRUoJTvI2bsdjZRzcyUytouBPqciDliY=; b=DG2X7PGFqHJsrabWqly1BMIRgqTKd/3xvM1xMQbFeUtyW4ngyDr3rl8V kwCSpXQ2ZuhxXxKyfnPVvWFvqqa6bs+f4nMQGhYil1VcIDqxRlymCUmKO cwQjbJoldnDlJRVb0ZgV+0vig5/Ggwug9mk2FE8VLzwG/81msbpIs/OrP /lEsGoYFbUi365OOZNQuQVqkp1By11uMlb6msvW9ExZDNqFD9IEWbRtaw C2tUUbPAU/vzbfnfljfa45MdmbbxA3ML12/QNIas0RMJ0/tCAxCVVxtJw +msWliIFYThYr1puU7Sgao34WGo42KdCdKJS6u4NQkQt4oz5qKxSMAway w==; X-CSE-ConnectionGUID: OaipThxtSam7AlU1MDfHsQ== X-CSE-MsgGUID: fxm9g1mSTBmWzy5eU0lnyg== X-IPAS-Result: A0BJAgDLxdFq/5T/Ja1aglmCV3VgQ0kDlkcDnhyBfg8BAQEPRA0EAQGEP0YCjhUCJjQJDgECBAMCAwEBAQEBAQEBAQEBAQoBAQUBAQECAQcFgQ4Thk8NkBIBAgEDJwsBGAEtEBwDAQIvKyMIGYMCAYJ0AxG8eoF5M4EBgykBgVTbMgELFAGBOIVAiCNdGAGEfCcbG4FygRWDaYEFgVwCgUiGXQSCIoEMgVqBA5RESIEeA1ksAVUTDQoLBwWBaAM1EioVbjIdgSM+F4EHGwYFgR2BIIIWIxk2eoEJXoErKWABEBeBCYIHAoJUggACAUlBDgdFUwklQxJHJiIIEgkBExowC4EbODwJKEEXDHMMHQVyCAINCQcpGA1IESw3FRkEPm4HkhQegmV7EwEqAYEgSEROpSehDwoog3aMIpU6GjOFW6UUC5h9jgqVf1CEaYFoPIFZcBU7gmcJShkPji4LC4NghWTIdicyAgkDLwEBBwIHDgMLgWiRfgEB IronPort-Data: A9a23:DS/pPK7KGZjk7G/h+S41dgxRtGjGchMFZxGqfqrLsTDasY5as4F+v mEdWmrXP/jeNGH3KYogbI+3oUJTucPXn4I2SwA++XpjZn8b8sCt6fZ1gavT04J+CuWZESqLO u1HMoGowPgcFyGa+1H1dOex9RGQ7InQLpLkEunIJyttcgFtTSYlmHpLlvUw6mJSqYDR7zil5 5Wo/KUzBHf/g2QqazpNtfrawP9SlK2aVA0w7wRWic9j5Dcyp1FNZLoDKKe4KWfPQ4U8NoaSW +bZwbilyXjS9hErB8nNuu6TnpoiG+O60aCm0xK6aoD66vRwjnVaPpUTaJLwXXxqZwChxLid/ jniWauYEm/FNoWU8AgUvoIx/ytWZcWq85efSZSzXFD6I0DuKxPRL/tS4E4eGoQZ4tp3EX11/ PlHATQwNzWvicKt+efuIgVsrpxLwMjDJogTvDRkiDreF/tjGMmFSKTR7tge1zA17ixMNa+BP IxCNnw1MUmGOkEfUrsUIMpWcOOAinrydzRZuVu9rqss6G+Vxwt0uFToGIeOJITWGZoNwi50o ErI72GoDUgfD+C1zAWX4y+hu7fLmzL0Ddd6+LqQs6QCbEeo7msLBRsbUFG2rfW0hguyVsxSL 2QQ+zEytu417EGtQ9z3UhG0rXLCuQQTM+e8CMUg4w2Lj66R6AGDCy1dF3hKaccts4k9QjlCO kK1ou4FzAdH6NW9IU9xPJ/Nxd9uEUD59VM/WBI= IronPort-HdrOrdr: A9a23:jzxzQ6wHOxPksucRLNByKrPwDr1zdoMgy1knxilNoNJuHfBw8P re+8jzuiWUtN98YhwdcJW7Scu9qBDnhPpICPcqXYtKNTOO0ADDEGgh1/qG/9SKIUPDH4BmuZ uIC5IOa+HYPBxdkdvw5hW+HpILxdmK973tuMLlpk0dKz2Dr8pbnn9E4sHxKDwOeDV7 X-Talos-CUID: 9a23:rPiYmGvxSCfT93bP3rPE1xCN6IsCe3zF8HfAP3SxDDlATIDMbHOe2Lhdxp8= X-Talos-MUID: 9a23:fy6ejwbnfafMBuBTuS/p1T47MfxUyaGRGlFQnKhev8+VOnkl X-IronPort-Anti-Spam-Filtered: true X-IronPort-AV: E=Sophos;i="6.27,161,1787011200"; d="scan'208";a="536044704" Received: from rcdn-l-core-11.cisco.com ([173.37.255.148]) by rcdn-iport-1.cisco.com with ESMTP/TLS/TLS_AES_256_GCM_SHA384; 16 Oct 2026 06:42:50 +0000 Received: from sjc-ads-5675.cisco.com (sjc-ads-5675.cisco.com [10.28.88.189]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by rcdn-l-core-11.cisco.com (Postfix) with ESMTPS id 8B4A7180002EC; Fri, 16 Oct 2026 06:42:50 +0000 (GMT) Received: by sjc-ads-5675.cisco.com (Postfix, from userid 1887444) id B9E22CBF201; Thu, 15 Oct 2026 23:42:34 -0700 (PDT) From: "Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)" To: openembedded-devel@lists.openembedded.org Cc: Darsh Kelaiya Subject: [oe][meta-python][wrynose][PATCH 1/3] python3-aiohttp: fix CVE-2026-59881 Date: Thu, 15 Oct 2026 23:42:17 -0700 Message-Id: <20261016064219.3910025-2-dkelaiya@cisco.com> X-Mailer: git-send-email 2.35.6 In-Reply-To: <20261016064219.3910025-1-dkelaiya@cisco.com> References: <20261016064219.3910025-1-dkelaiya@cisco.com> MIME-Version: 1.0 X-Outbound-Client-TLS: ANONYMOUS;sjc-ads-5675.cisco.com [10.28.88.189];TLSv1.3;TLS_AES_256_GCM_SHA384;256 X-Outbound-SMTP-Client: 10.28.88.189, sjc-ads-5675.cisco.com X-Outbound-Node: rcdn-l-core-11.cisco.com List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Fri, 16 Oct 2026 06:42:56 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-devel/message/131068 From: Darsh Kelaiya 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 --- .../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 +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 +--- + 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" From patchwork Fri Oct 16 06:42:18 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)" X-Patchwork-Id: 101050 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id AD910CCA46D for ; Fri, 16 Oct 2026 06:42:46 +0000 (UTC) Received: from rcdn-iport-5.cisco.com (rcdn-iport-5.cisco.com [173.37.86.76]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.16159.1792132956216575615 for ; Thu, 15 Oct 2026 23:42:36 -0700 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: message contains an insecure body length tag" header.i=@cisco.com header.s=iport01 header.b=MyVDmUT4; spf=pass (domain: cisco.com, ip: 173.37.86.76, mailfrom: dkelaiya@cisco.com) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cisco.com; i=@cisco.com; l=12744; q=dns/txt; s=iport01; t=1792132956; x=1793342556; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=OjZ1Lp5IwLTA1mTTAzvLXE1FMvXoaTxxveRjkJuRH2E=; b=MyVDmUT4PHfNeGn/9qYy1xZPGpmXjgbDD0Lps79anAgDJs6FRiAlXRcy fYP2YXROQboNKSTgrpVpBk8EqIAveHg/cxEerz4l97NhaHvIDXTUtay1T Lxa5MRzs9rjgFevOQEE9BKI7Pujk21NpfGYCmnHRReFXdKpGKMyCQ5KLy EVjNJ8tXmTnevoWB7QL/zCFDe5fYUz5t0vAtA2bfDgM7ZzCMwsLTv+qiS VZiJLyEBD6JbKRyA7FvT29bTP8WEzvJ31vypp3YEmw8i92yJsmP6xK+FJ lQM1DBrydb31IT/f4Evez0bWMVAQkTY/atxYct/BMwAOzbhy3RS4xUge8 w==; X-CSE-ConnectionGUID: 4UfxRhQZTmCmlE9T4YPEbA== X-CSE-MsgGUID: EeeOYEKKToGZCaOjq4N9Ww== X-IPAS-Result: A0BLAgDLxdFq/5P/Ja1aglmCGD91YENJA5ZHA4ETkDeMUoF+DwEBAQ9EDQQBAYQ/RgKOFQImNAkOAQIEAwIDAQEBAQEBAQEBAQEBCgEBBQEBAQIBBwWBDhOGTw2QEgECAQMnCwEYAS0QHAMBAi8rIwgQCYMCAYJ0AxG8eoF5M4EBgykBMQKBIdsyAQsUAYE4hUCII10YAYR8JxsbgXKBFYE7gTh2gQWBXAICgTYQCYZUBIIigQyBWoEDlERIgR4DWSwBVRMNCgsHBYFoAzUSKhVuMh2BIz4XgQcbBgWBHYEgghYjGTZ6gQlegSspYAEQF4EJggcCglSCAAIBSUEOB0VTCSVDEkcmIggSCQETGjALgRs4PAkoQRcMcwwdBXIIAg0JBykYDUgRLDcVGQQ+bgeSFB6CZRYXRw0NASoBf4EtEQUBBQIwkmAHAjkKkXuBNZ9aCiiDdowilToaM4QEgVeSQJJUC5h9jgqVZxgBT4RpgWg8gVlwFTuCZwlKGQ+OKw4LgRQBAoJJhRPJRycyAgkDLwEBBwIHDgMLgWiQAQEmgVYBAQ IronPort-Data: A9a23:5IrZsa/9ZHUy3ERWIGS9DrUD0H+TJUtcMsCJ2f8bNWPcYEJGY0x3z GYYWWuPbvvYMGfzfNlxa4vj9R4AsMXRnIU2HAtp+X9EQiMRo6IpJzg2wmQcns+2BpeeJK6yx 5xGMrEsFOhtEDmB4E/rbei5xZVF/fngbqLmD+LZMTxGSwZhSSMw4TpugOdRbrRA2bBVOCvT/ 4mtyyHjEAX9gWAtajtKs/vrRC5H5ZwehhtJ5jTSWtgT1LPuvyF9JI4SI6i3M0z5TuF8dsamR /zOxa2O5WjQ+REgELuNyt4XpWVTH9Y+lSDX4pZnc/DKbipq/0Te4Y5nXBYoUnq7vh3S9zxHJ HqhgrTrIeshFvWkdO3wyHC0GQkmVUFN0OevzXRSLaV/wmWeG0YAzcmCA2lqAIMIwOZOM11L3 vkcBWExMiG7quiflefTpulE3qzPLeHxN48Z/3UlxjbDALN/GdbIQr7B4plT2zJYasJmRKmFI ZFGL2AyMVKZP0In1lQ/UPrSmM+khXT7ejxJoXqepLE85C7YywkZPL3FbYGLJo3RH5kM9qqej k/L8kPILzI5CNuw+xGM1XC12smeojyuDer+E5X9rJaGmma7wXQeDhATX1a3rfS1z0KzRd9bA 0gV4TY1668q+UqmS9PwUxG1rDiDpBF0ZjZLO/cx5AfIzu/f5ByUQzFcCDVAc9ch8sQxQFTGy 2O0oj8gPhQ32JX9dJ5X3uz8Qe+aUcTNEVI/WA== IronPort-HdrOrdr: A9a23:TzRA6KqxHVrV6LpNT2H6ouMaV5oJeYIsimQD101hICG9vPb2qy nIpoV96faaslcssR0b9OxofZPwI080lqQFhbX5Q43DYOCOggLBR+tfBMnZsljd8kbFmNK1u5 0NT0EHMqySMXFKyeDn/QK/D9EshPOD8KyumKPi6k0Fd3ANV0mlhD0Jcjpy1SZNNXB7OaY= X-Talos-CUID: 9a23:+9Zvbmshgpgo9rFoLBEEDUN36It5T3PN9nfpEXO9ImNtZeypFW6o1alNxp8= X-Talos-MUID: 9a23:ESbUdAUQ+5W3EfDq/BLhiRZTGeRL36OBOmsEtpcf5+/VCjMlbg== X-IronPort-Anti-Spam-Filtered: true X-IronPort-AV: E=Sophos;i="6.27,161,1787011200"; d="scan'208";a="536493241" Received: from rcdn-l-core-10.cisco.com ([173.37.255.147]) by rcdn-iport-5.cisco.com with ESMTP/TLS/TLS_AES_256_GCM_SHA384; 16 Oct 2026 06:42:35 +0000 Received: from sjc-ads-5675.cisco.com (sjc-ads-5675.cisco.com [10.28.88.189]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by rcdn-l-core-10.cisco.com (Postfix) with ESMTPS id 200C51800025B; Fri, 16 Oct 2026 06:42:35 +0000 (GMT) Received: by sjc-ads-5675.cisco.com (Postfix, from userid 1887444) id BE6FBCBF202; Thu, 15 Oct 2026 23:42:34 -0700 (PDT) From: "Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)" To: openembedded-devel@lists.openembedded.org Cc: Darsh Kelaiya Subject: [oe][meta-python][wrynose][PATCH 2/3] python3-aiohttp: fix CVE-2026-69243 Date: Thu, 15 Oct 2026 23:42:18 -0700 Message-Id: <20261016064219.3910025-3-dkelaiya@cisco.com> X-Mailer: git-send-email 2.35.6 In-Reply-To: <20261016064219.3910025-1-dkelaiya@cisco.com> References: <20261016064219.3910025-1-dkelaiya@cisco.com> MIME-Version: 1.0 X-Outbound-Client-TLS: ANONYMOUS;sjc-ads-5675.cisco.com [10.28.88.189];TLSv1.3;TLS_AES_256_GCM_SHA384;256 X-Outbound-SMTP-Client: 10.28.88.189, sjc-ads-5675.cisco.com X-Outbound-Node: rcdn-l-core-10.cisco.com List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Fri, 16 Oct 2026 06:42:46 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-devel/message/131067 From: Darsh Kelaiya 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 --- .../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 +(cherry picked from commit 6ae358f0983c3f4d6f67692b2f8e65dc8e091c98) +Signed-off-by: Darsh Kelaiya +--- + 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" From patchwork Fri Oct 16 06:42:19 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)" X-Patchwork-Id: 101049 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 857A6CC6B38 for ; Fri, 16 Oct 2026 06:42:46 +0000 (UTC) Received: from rcdn-iport-1.cisco.com (rcdn-iport-1.cisco.com [173.37.86.72]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.15844.1792132956136156653 for ; Thu, 15 Oct 2026 23:42:36 -0700 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: message contains an insecure body length tag" header.i=@cisco.com header.s=iport01 header.b=hwSgad+9; spf=pass (domain: cisco.com, ip: 173.37.86.72, mailfrom: dkelaiya@cisco.com) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cisco.com; i=@cisco.com; l=8326; q=dns/txt; s=iport01; t=1792132956; x=1793342556; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=fgKtzwkmrwrsxKjAqYtqXQYXCyeiiRrw62tdMspC14o=; b=hwSgad+9iWAURqiVa/T3TlF6ta3439dBB7PQ9Mh7CxcfkOmGus4/yP9Y lkMOI4IkecZOruhS/3HwCwLUE9x8GZUqNbjEhl4r1TwBUmsaTJ57yAScS Dbex+oqPH/mSNDP+BPUCahxuCMfbE9rnWzi0RfyzCLoTkM4QitYZKedo7 fA03NhhSXUWjdAspXmxtICZ5HADVf2z3DizAIC5lkVps3KV7hPsl1e1/l fIlcGGi9TlEfNDf94fZ9Sgtrtv/djTiEK5pnFOyj+WGqljO8Fim/oJze0 WeWsXkHk7bX0mLL0JpdkmW0f5S0TED52zBxMAm6Pcg4K32b9/YDKuuopo Q==; X-CSE-ConnectionGUID: DxOenbbSSMSKUiE40gv60g== X-CSE-MsgGUID: xUgEQ2K/RnyD7DqHT1l0nw== X-IPAS-Result: A0BJAgDLxdFq/5X/Ja1aglmCV3VgQ0kDlkcDnhyBfg8BAQEPRA0EAQGEP0YCjhUCJjQJDgECBAMCAwEBAQEBAQEBAQEBAQoBAQUBAQECAQcFgQ4Thk8NkBIBAgEDJwsBGAEtEBwDAQIvKyMIGYMCAYJ0AxG8eoF5M4EBgykBMQWBHtsyAQsUAYE4hUCII10YAYR8JxsbgXKBFYE7gi6BBYFcAoglBIIigQyBWoFJk35IgR4DWSwBVRMNCgsHBYFoAzUSKhVuMh2BIz4XgQcbBgWBHYEgghYjGTZ6gQlegSspYAEQF4EJggcCglSCAAIBSUEOB0VTCSVDEkcmIggSCQETGjALgRs4PAkoQRcMcwwdBXIIAg0JBykYDUgRLDcVGQQ+bgeSFB6CZQF6EwEqAReBHXgck16Re6EPCiiDdowilToaM4QElBeSVAuYfY4KlV9whGmBaDyBWXAVgyIJShkPjisOC4NgzlonMgIJAy8BAQcCBw4DC4FokX4BAQ IronPort-Data: A9a23:l+kNvqxyi2HBelhQujt6t+dnxyrEfRIJ4+MujC+fZmUNrF6WrkUAm zYaXWCFO/2MZGqnc94ja4608EpT7cOBndJrSQRqqFhgHilAwSbn6Xt1DatR0we6dJCroJdPt p1GAjX4BJlqCCKa/lHyYuCJQUBUjcmgXqD7BPPPJhd/TAplTDZJoR94kobVuKYw6TSCK13L4 4yaT/H3Ygf/hWcsazxMsspvlTs21BjMkGJA1rABTagjUG/2zxE9EJ8ZLKetGHr0KqE8NvK6X evK0Iai9Wrf+Ro3Yvv9+losWhRXKlJ6FVHmZkt+A8BOsDAbzsAB+vpT2M4nVKtio27hc+adZ zl6ncfYpQ8BZsUgkQmGOvVSO3kW0aZuoNcrLZUj2CCe5xWuTpfi/xlhJG0mGoAe8OxMOkR11 sEZBWxVYQ7d3+3jldpXSsE07igiBNPgMIVavjRryivUSK98B5vCWK7No9Rf2V/chOgXQq2YP JVfM2cyKk2cMnWjOX9PYH46tOuri332cixRgFmUvqEwpWPUyWSd1ZCwa4SNJoDUGJg9ckCwt l/J0l2jIVYgMs2zlhac0k71grfJgnauMG4VPPjinhJwu3WU3mEVBRgcWFe3rPX8hkOgVtZ3L 00P5jFoqrA/8kGuRNTxUxC05nmesXYht8F4CeY27kSJj6HT+QvcXzJCRT9aY9tgv8gzLdA36 mK0cxrSLWQHmNWopbi1r994cRva1fApEFI/ IronPort-HdrOrdr: A9a23:H9sPkK1UI89kw0RCGuBMUwqjBJAkLtp133Aq2lEZdPWaSKOlfq eV7ZMmPHDP6Qr5NEtMpTnEAtjjfZq+z+8Q3WBuB9eftWDd0QPCRr2Kr7GSpgEIcBeRygcy78 tdmtBFeb7NJGk/q9rm6w+lFNtl6tyG/Ke0wdr69R5WPGdXg2UK1XYANu5deXcGPTV7OQ== X-Talos-CUID: 9a23:rI6y32u3pjimIa/+q74CjquU6IsJYlrQyn3aGHSdMkVUSZDJEVCOpvtrxp8= X-Talos-MUID: 9a23:xAWNbg4Ltu0su/Eq/GO97NqSxox43quDMRou1q8AquXbGTVQB26Z1DKoF9o= X-IronPort-Anti-Spam-Filtered: true X-IronPort-AV: E=Sophos;i="6.27,161,1787011200"; d="scan'208";a="536044655" Received: from rcdn-l-core-12.cisco.com ([173.37.255.149]) by rcdn-iport-1.cisco.com with ESMTP/TLS/TLS_AES_256_GCM_SHA384; 16 Oct 2026 06:42:35 +0000 Received: from sjc-ads-5675.cisco.com (sjc-ads-5675.cisco.com [10.28.88.189]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by rcdn-l-core-12.cisco.com (Postfix) with ESMTPS id 24E9F180001DD; Fri, 16 Oct 2026 06:42:35 +0000 (GMT) Received: by sjc-ads-5675.cisco.com (Postfix, from userid 1887444) id C4768CBF203; Thu, 15 Oct 2026 23:42:34 -0700 (PDT) From: "Darsh Kelaiya -X (dkelaiya - E INFOCHIPS PRIVATE LIMITED at Cisco)" To: openembedded-devel@lists.openembedded.org Cc: Darsh Kelaiya Subject: [oe][meta-python][wrynose][PATCH 3/3] python3-aiohttp: fix CVE-2026-69244 Date: Thu, 15 Oct 2026 23:42:19 -0700 Message-Id: <20261016064219.3910025-4-dkelaiya@cisco.com> X-Mailer: git-send-email 2.35.6 In-Reply-To: <20261016064219.3910025-1-dkelaiya@cisco.com> References: <20261016064219.3910025-1-dkelaiya@cisco.com> MIME-Version: 1.0 X-Outbound-Client-TLS: ANONYMOUS;sjc-ads-5675.cisco.com [10.28.88.189];TLSv1.3;TLS_AES_256_GCM_SHA384;256 X-Outbound-SMTP-Client: 10.28.88.189, sjc-ads-5675.cisco.com X-Outbound-Node: rcdn-l-core-12.cisco.com List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Fri, 16 Oct 2026 06:42:46 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-devel/message/131066 From: Darsh Kelaiya 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 --- .../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" +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 +--- + 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"