diff mbox series

[meta-python,wrynose,6/11] python3-tornado: patch CVE-2026-82397

Message ID 20260927003921.746786-6-ankur.tyagi85@gmail.com
State New
Headers show
Series [meta-networking,wrynose,1/11] unbound: patch CVE-2026-80225 | expand

Commit Message

Ankur Tyagi Sept. 27, 2026, 12:39 a.m. UTC
From: Ankur Tyagi <ankur.tyagi85@gmail.com>

Details:
https://nvd.nist.gov/vuln/detail/cve-2026-82397

Signed-off-by: Ankur Tyagi <ankur.tyagi85@gmail.com>
---
 .../python3-tornado/CVE-2026-82397.patch      | 149 ++++++++++++++++++
 .../python/python3-tornado_6.5.7.bb           |   1 +
 2 files changed, 150 insertions(+)
 create mode 100644 meta-python/recipes-devtools/python/python3-tornado/CVE-2026-82397.patch
diff mbox series

Patch

diff --git a/meta-python/recipes-devtools/python/python3-tornado/CVE-2026-82397.patch b/meta-python/recipes-devtools/python/python3-tornado/CVE-2026-82397.patch
new file mode 100644
index 0000000000..71278d5d23
--- /dev/null
+++ b/meta-python/recipes-devtools/python/python3-tornado/CVE-2026-82397.patch
@@ -0,0 +1,149 @@ 
+From eba9e5652af3edbf9d8f2375194b5c63362d0c91 Mon Sep 17 00:00:00 2001
+From: Ben Darnell <ben@bendarnell.com>
+Date: Wed, 5 Aug 2026 21:20:58 -0400
+Subject: [PATCH] httputil: Enforce a new limit on the number of arguments in a
+ request
+
+Large POST bodies can be very expensive to parse in the worst case,
+so use the (new in Python 3.8) max_num_fields argument to limit the
+cost. A new field in ParseBodyConfig allows users to configure this
+limit. The default is 1000, which is the same as that used in php and
+node.js.
+
+(cherry picked from commit 8d6363ed7b69d5f0da806efe34d256627a2191de)
+
+CVE: CVE-2026-82397
+Upstream-Status: Backport [https://github.com/tornadoweb/tornado/commit/8d6363ed7b69d5f0da806efe34d256627a2191de]
+
+Signed-off-by: Ankur Tyagi <ankur.tyagi85@gmail.com>
+---
+ tornado/escape.py             | 16 ++++++++++++++--
+ tornado/httputil.py           | 26 +++++++++++++++++++++++++-
+ tornado/test/httputil_test.py | 18 ++++++++++++++++++
+ 3 files changed, 57 insertions(+), 3 deletions(-)
+
+diff --git a/tornado/escape.py b/tornado/escape.py
+index 8515bf58..a1c16b36 100644
+--- a/tornado/escape.py
++++ b/tornado/escape.py
+@@ -171,7 +171,11 @@ def url_unescape(
+ 
+ 
+ def parse_qs_bytes(
+-    qs: Union[str, bytes], keep_blank_values: bool = False, strict_parsing: bool = False
++    qs: Union[str, bytes],
++    keep_blank_values: bool = False,
++    strict_parsing: bool = False,
++    *,
++    max_num_fields: Optional[int] = None,
+ ) -> Dict[str, List[bytes]]:
+     """Parses a query string like urlparse.parse_qs,
+     but takes bytes and returns the values as byte strings.
+@@ -179,13 +183,21 @@ def parse_qs_bytes(
+     Keys still become type str (interpreted as latin1 in python3!)
+     because it's too painful to keep them as byte strings in
+     python3 and in practice they're nearly always ascii anyway.
++
++    .. versionadded:: 6.5.8
++       The ``max_num_fields`` argument. ValueError is raised if this limit is exceeded.
+     """
+     # This is gross, but python3 doesn't give us another way.
+     # Latin1 is the universal donor of character encodings.
+     if isinstance(qs, bytes):
+         qs = qs.decode("latin1")
+     result = urllib.parse.parse_qs(
+-        qs, keep_blank_values, strict_parsing, encoding="latin1", errors="strict"
++        qs,
++        keep_blank_values,
++        strict_parsing,
++        encoding="latin1",
++        errors="strict",
++        max_num_fields=max_num_fields,
+     )
+     encoded = {}
+     for k, v in result.items():
+diff --git a/tornado/httputil.py b/tornado/httputil.py
+index f698db21..b80c796c 100644
+--- a/tornado/httputil.py
++++ b/tornado/httputil.py
+@@ -947,6 +947,23 @@ class ParseMultipartConfig:
+     """
+ 
+ 
++@dataclasses.dataclass
++class ParseUrlEncodedConfig:
++    """This class configures the parsing of ``application/x-www-form-urlencoded`` request bodies.
++
++    Its primary purpose is to place limits on the size and complexity of request messages
++    to avoid potential denial-of-service attacks.
++
++    .. versionadded:: 6.5.8
++    """
++
++    max_arguments: int = 1000
++    """The maximum number of arguments accepted in a urlencoded request.
++
++    Each ``<input>`` element in an HTML form corresponds to at least one argument.
++    """
++
++
+ @dataclasses.dataclass
+ class ParseBodyConfig:
+     """This class configures the parsing of request bodies.
+@@ -957,6 +974,9 @@ class ParseBodyConfig:
+     multipart: ParseMultipartConfig = dataclasses.field(
+         default_factory=ParseMultipartConfig
+     )
++    urlencoded: ParseUrlEncodedConfig = dataclasses.field(
++        default_factory=ParseUrlEncodedConfig
++    )
+     """Configuration for ``multipart/form-data`` request bodies."""
+ 
+ 
+@@ -1015,7 +1035,11 @@ def parse_body_arguments(
+             )
+         try:
+             # real charset decoding will happen in RequestHandler.decode_argument()
+-            uri_arguments = parse_qs_bytes(body, keep_blank_values=True)
++            uri_arguments = parse_qs_bytes(
++                body,
++                keep_blank_values=True,
++                max_num_fields=config.urlencoded.max_arguments,
++            )
+         except Exception as e:
+             raise HTTPInputError("Invalid x-www-form-urlencoded body: %s" % e) from e
+         for name, values in uri_arguments.items():
+diff --git a/tornado/test/httputil_test.py b/tornado/test/httputil_test.py
+index 92683ae9..afb15879 100644
+--- a/tornado/test/httputil_test.py
++++ b/tornado/test/httputil_test.py
+@@ -1,4 +1,5 @@
+ from tornado.httputil import (
++    parse_body_arguments,
+     url_concat,
+     parse_multipart_form_data,
+     HTTPHeaders,
+@@ -95,6 +96,23 @@ class QsParseTest(unittest.TestCase):
+         self.assertIn(("b", "2"), qsl)
+ 
+ 
++class UrlEncodedDataTest(unittest.TestCase):
++    def test_urlencoded_data(self):
++        data = b"a=1&b=2&a=3"
++        args, files = form_data_args()
++        parse_body_arguments("application/x-www-form-urlencoded", data, args, files)
++        self.assertEqual(args["a"], [b"1", b"3"])
++        self.assertEqual(args["b"], [b"2"])
++        self.assertEqual(files, {})
++
++    def test_max_arguments(self):
++        data = b"".join(b"a=1&" for _ in range(1001))
++        args, files = form_data_args()
++        with self.assertRaises(HTTPInputError) as cm:
++            parse_body_arguments("application/x-www-form-urlencoded", data, args, files)
++        self.assertIn("Max number of fields exceeded", str(cm.exception))
++
++
+ class MultipartFormDataTest(unittest.TestCase):
+     def test_file_upload(self):
+         data = b"""\
diff --git a/meta-python/recipes-devtools/python/python3-tornado_6.5.7.bb b/meta-python/recipes-devtools/python/python3-tornado_6.5.7.bb
index 5d3db11b6b..ce7b903ef7 100644
--- a/meta-python/recipes-devtools/python/python3-tornado_6.5.7.bb
+++ b/meta-python/recipes-devtools/python/python3-tornado_6.5.7.bb
@@ -9,6 +9,7 @@  LIC_FILES_CHKSUM = "file://LICENSE;md5=3b83ef96387f14655fc854ddc3c6bd57"
 SRC_URI[sha256sum] = "66c513a76cda70d53907bc27cf1447557699c2e95aa48ba27a442ff61c3ddfc2"
 
 SRC_URI += "file://CVE-2026-91990.patch \
+            file://CVE-2026-82397.patch \
 "
 
 inherit pypi python_setuptools_build_meta