diff mbox series

[meta-python,scarthgap,04/13] python3-aiohttp: fix CVE-2026-34520

Message ID 20260928174323.1810308-5-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 reviewed upstream fix shown in [1].
The CVE record is referenced in [2]. It also backports
the C-parser null-byte validation and adapts its
regression test from [3].

[1] https://github.com/aio-libs/aiohttp/commit/9370b9714a7a56003cacd31a9b4ae16eab109ba4
[2] https://nvd.nist.gov/vuln/detail/CVE-2026-34520
[3] https://github.com/aio-libs/aiohttp/commit/db560cfeab32aa35f3d06f7a24238bedc01ffe2b

Signed-off-by: Darsh Kelaiya <dkelaiya@cisco.com>
---
 .../python3-aiohttp/CVE-2026-34520.patch      | 121 ++++++++++++++++++
 .../python/python3-aiohttp_3.9.5.bb           |   1 +
 2 files changed, 122 insertions(+)
 create mode 100644 meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-34520.patch
diff mbox series

Patch

diff --git a/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-34520.patch b/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-34520.patch
new file mode 100644
index 0000000000..2492a23a3d
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-aiohttp/CVE-2026-34520.patch
@@ -0,0 +1,121 @@ 
+From 661f91935d061a3d7d554f2c808c8a14f3122991 Mon Sep 17 00:00:00 2001
+From: Rodrigo Nogueira <rodrigo.b.nogueira@gmail.com>
+Date: Wed, 11 Mar 2026 20:38:17 -0300
+Subject: [PATCH] [PR #12231/7043bc56 backport][3.13] Adjust header value
+ character checks to RFC 9110 (#12235)
+
+CVE: CVE-2026-34520
+Upstream-Status: Backport [https://github.com/aio-libs/aiohttp/commit/9370b9714a7a56003cacd31a9b4ae16eab109ba4]
+
+Backport Changes:
+- Included the C-parser null-byte validation from upstream prerequisite
+  commit db560cfeab32aa35f3d06f7a24238bedc01ffe2b because the Scarthgap
+  3.9.5 recipe patch stack does not contain that prerequisite.
+- Used the target's local `raw_value` bytes object when raising
+  `InvalidHeader`, since aiohttp 3.9.5 converts
+  `_raw_value` before checking.
+- Added a response-parser regression test for the C-parser validation.
+- Omitted the generated aiohttp/_http_parser.c changes because the
+  Scarthgap recipe regenerates this file from _http_parser.pyx with
+  Cython.
+
+Co-authored-by: rodrigo.nogueira <rodrigo.nogueira@prf.gov.br>
+(cherry picked from commit 9370b9714a7a56003cacd31a9b4ae16eab109ba4)
+Signed-off-by: Darsh Kelaiya <dkelaiya@cisco.com>
+---
+ CHANGES/12231.bugfix.rst  |  2 ++
+ aiohttp/_http_parser.pyx  |  7 +++++++
+ aiohttp/http_parser.py    |  9 ++++++++-
+ tests/test_http_parser.py | 17 +++++++++++++++++
+ 4 files changed, 34 insertions(+), 1 deletion(-)
+ create mode 100644 CHANGES/12231.bugfix.rst
+
+diff --git a/CHANGES/12231.bugfix.rst b/CHANGES/12231.bugfix.rst
+new file mode 100644
+index 000000000..cd74bd1e7
+--- /dev/null
++++ b/CHANGES/12231.bugfix.rst
+@@ -0,0 +1,2 @@
++Adjusted pure-Python request header value validation to align with RFC 9110 control-character handling, while preserving lax response parser behavior, and added regression tests for Host/header control-character cases.
++-- by :user:`rodrigobnogueira`.
+diff --git a/aiohttp/_http_parser.pyx b/aiohttp/_http_parser.pyx
+index cc8b13cfb..496498d6f 100644
+--- a/aiohttp/_http_parser.pyx
++++ b/aiohttp/_http_parser.pyx
+@@ -387,5 +387,12 @@ cdef class HttpParser:
+             value = raw_value.decode('utf-8', 'surrogateescape')
+ 
++            # reject null bytes in header values - matches the Python parser
++            # check at http_parser.py. llhttp in lenient mode doesn't reject
++            # these itself, so we need to catch them here.
++            # ref: RFC 9110 section 5.5 (CTL chars forbidden in field values)
++            if "\x00" in value:
++                raise InvalidHeader(raw_value)
++
+             self._headers.add(name, value)
+             if len(self._headers) > self._max_headers:
+                 raise BadHttpMessage("Too many headers received")
+diff --git a/aiohttp/http_parser.py b/aiohttp/http_parser.py
+index 95ac28252..a7fec7f01 100644
+--- a/aiohttp/http_parser.py
++++ b/aiohttp/http_parser.py
+@@ -73,6 +73,10 @@ ASCIISET: Final[Set[str]] = set(string.printable)
+ #     token = 1*tchar
+ _TCHAR_SPECIALS: Final[str] = re.escape("!#$%&'*+-.^_`|~")
+ TOKENRE: Final[Pattern[str]] = re.compile(f"[0-9A-Za-z{_TCHAR_SPECIALS}]+")
++# https://www.rfc-editor.org/rfc/rfc9110#section-5.5-5
++_FIELD_VALUE_FORBIDDEN_CTL_RE: Final[Pattern[str]] = re.compile(
++    r"[\x00-\x08\x0a-\x1f\x7f]"
++)
+ VERSRE: Final[Pattern[str]] = re.compile(r"HTTP/(\d)\.(\d)", re.ASCII)
+ DIGITS: Final[Pattern[str]] = re.compile(r"\d+", re.ASCII)
+ HEXDIGITS: Final[Pattern[bytes]] = re.compile(rb"[0-9a-fA-F]+")
+@@ -200,7 +204,10 @@ class HeadersParser:
+             value = bvalue.decode("utf-8", "surrogateescape")
+ 
+             # https://www.rfc-editor.org/rfc/rfc9110.html#section-5.5-5
+-            if "\n" in value or "\r" in value or "\x00" in value:
++            if self._lax:
++                if "\n" in value or "\r" in value or "\x00" in value:
++                    raise InvalidHeader(bvalue)
++            elif _FIELD_VALUE_FORBIDDEN_CTL_RE.search(value):
+                 raise InvalidHeader(bvalue)
+ 
+             headers.add(name, value)
+diff --git a/tests/test_http_parser.py b/tests/test_http_parser.py
+index cb562f598..be7446e0a 100644
+--- a/tests/test_http_parser.py
++++ b/tests/test_http_parser.py
+@@ -210,6 +210,9 @@ def test_bad_header_name(parser: Any, rfc9110_5_6_2_token_delim: str) -> None:
+         "Foo : bar",  # https://www.rfc-editor.org/rfc/rfc9112.html#section-5.1-2
+         "Foo\t: bar",
+         "\xffoo: bar",
++        "Foo: abc\x01def",  # CTL bytes forbidden per RFC 9110 ยง5.5
++        "Foo: abc\x7fdef",  # DEL is also a CTL byte
++        "Foo: abc\x1fdef",
+     ),
+ )
+ def test_bad_headers(parser: Any, hdr: str) -> None:
+@@ -218,6 +221,20 @@ def test_bad_headers(parser: Any, hdr: str) -> None:
+         parser.feed_data(text)
+ 
+ 
++def test_ctl_host_header_bad_characters(parser: HttpRequestParser) -> None:
++    """CTL byte in Host header must be rejected."""
++    text = b"GET /test HTTP/1.1\r\nHost: trusted.example\x01@bad.test\r\n\r\n"
++    with pytest.raises(http_exceptions.BadHttpMessage):
++        parser.feed_data(text)
++
++
++def test_null_byte_in_response_header_value(
++    response: HttpResponseParser,
++) -> None:
++    with pytest.raises(http_exceptions.InvalidHeader):
++        response.feed_data(b"HTTP/1.1 200 OK\r\nFoo: abc\x00def\r\n\r\n")
++
++
+ def test_unpaired_surrogate_in_header_py(loop: Any, protocol: Any) -> None:
+     parser = HttpRequestParserPy(
+         protocol,
+-- 
+2.35.6
diff --git a/meta-python/recipes-devtools/python/python3-aiohttp_3.9.5.bb b/meta-python/recipes-devtools/python/python3-aiohttp_3.9.5.bb
index fecf871d9f..b7741d5ed4 100644
--- a/meta-python/recipes-devtools/python/python3-aiohttp_3.9.5.bb
+++ b/meta-python/recipes-devtools/python/python3-aiohttp_3.9.5.bb
@@ -24,6 +24,7 @@  SRC_URI += "file://CVE-2024-52304.patch \
            file://CVE-2026-34519.patch \
            file://CVE-2026-34516.patch \
            file://CVE-2026-34517.patch \
+           file://CVE-2026-34520.patch \
            "
 
 CVE_STATUS[CVE-2026-34515] = "not-applicable-platform: Vulnerability only affects applications running on Windows"