mirror of
https://github.com/openembedded/meta-openembedded.git
synced 2026-11-13 03:37:15 +00:00
python3-aiohttp: fix CVE-2026-69244
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> Signed-off-by: Anuj Mittal <anuj.mittal@oss.qualcomm.com>
This commit is contained in:
1 parent
25f14104c2
commit
aa6d99fed0
2 files changed
+160
No files matched your search
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in new issue
Block a user