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 +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 +--- + 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 ```` 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 d879042d5b..bb85d8e8a2 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