diff --git a/meta-python/recipes-devtools/python/python3-tornado/CVE-2026-103261.patch b/meta-python/recipes-devtools/python/python3-tornado/CVE-2026-103261.patch new file mode 100644 index 0000000000..f603b4b24a --- /dev/null +++ b/meta-python/recipes-devtools/python/python3-tornado/CVE-2026-103261.patch @@ -0,0 +1,98 @@ +From 03945136ea9746eccf61caf88edae39642e59c93 Mon Sep 17 00:00:00 2001 +From: Claude +Date: Sun, 13 Sep 2026 03:32:19 +0000 +Subject: [PATCH] httputil: Apply the argument count limit to query strings + +parse_body_arguments limits the number of fields in a urlencoded body +(added in 6.5.8), but HTTPServerRequest.__init__ parsed the query string +with no such limit, so the same superlinear parsing cost was reachable +through the request line instead of the body. + +Measured on the unpatched code, a query string with 400k fields took +3.2s of CPU to parse. In the default configuration the request line is +bounded by max_header_size (64KB), which caps this at roughly 12,700 +arguments and ~47ms per request, but that is still an order of magnitude +past the limit the body path enforces, and applications are free to +raise max_header_size. + +Pass the same max_arguments limit here. Over-limit query strings now +raise HTTPInputError, which the server already turns into a 400. + +The limit is read from the global _DEFAULT_PARSE_BODY_CONFIG because +HTTPServerRequest has no access to any per-connection configuration. +This is the same limitation the body path already has in practice: +_parse_body calls parse_body_arguments without a config, so the body +limit is likewise only adjustable through set_parse_body_config. + +CVE: CVE-2026-103261 +Upstream-Status: Backport [https://github.com/tornadoweb/tornado/commit/03945136ea9746eccf61caf88edae39642e59c93] + +Co-Authored-By: Claude Opus 5 +Claude-Session: https://claude.ai/code/session_01JD7LUuFMdyPJB1Dwdq6fPg +Signed-off-by: Yogita Urade +--- + tornado/httputil.py | 14 +++++++++++++- + tornado/test/httputil_test.py | 15 +++++++++++++++ + 2 files changed, 28 insertions(+), 1 deletion(-) + +diff --git a/tornado/httputil.py b/tornado/httputil.py +index b80c796c..6a5db233 100644 +--- a/tornado/httputil.py ++++ b/tornado/httputil.py +@@ -550,7 +550,19 @@ class HTTPServerRequest: + + if uri is not None: + self.path, sep, self.query = uri.partition("?") +- self.arguments = parse_qs_bytes(self.query, keep_blank_values=True) ++ try: ++ self.arguments = parse_qs_bytes( ++ self.query, ++ keep_blank_values=True, ++ # The query string is bounded by max_header_size, but parsing ++ # is expensive enough per field to be worth limiting. Use the ++ # same limit as a urlencoded body. This reads the global ++ # config because HTTPServerRequest has no access to the ++ # per-connection configuration; see set_parse_body_config. ++ max_num_fields=_DEFAULT_PARSE_BODY_CONFIG.urlencoded.max_arguments, ++ ) ++ except Exception as e: ++ raise HTTPInputError("Invalid query string: %s" % e) from e + self.query_arguments = copy.deepcopy(self.arguments) + self.body_arguments = {} # type: Dict[str, List[bytes]] + +diff --git a/tornado/test/httputil_test.py b/tornado/test/httputil_test.py +index afb15879..0e9d5c41 100644 +--- a/tornado/test/httputil_test.py ++++ b/tornado/test/httputil_test.py +@@ -11,6 +11,7 @@ from tornado.httputil import ( + HTTPInputError, + HTTPFile, + ParseMultipartConfig, ++ _DEFAULT_PARSE_BODY_CONFIG, + ) + from tornado.escape import utf8, native_str + from tornado.log import gen_log +@@ -658,6 +659,20 @@ class HTTPServerRequestTest(unittest.TestCase): + ) + self.assertNotIn("Canary", repr(request)) + ++ def test_query_arguments_within_limit(self): ++ n = _DEFAULT_PARSE_BODY_CONFIG.urlencoded.max_arguments ++ request = HTTPServerRequest(uri="/?" + "&".join("a=%d" % i for i in range(n))) ++ self.assertEqual(len(request.arguments["a"]), n) ++ ++ def test_max_query_arguments(self): ++ # The query string is subject to the same limit on the number of ++ # arguments as a urlencoded body: parsing is superlinear enough in ++ # the number of fields to be worth bounding. ++ n = _DEFAULT_PARSE_BODY_CONFIG.urlencoded.max_arguments + 1 ++ with self.assertRaises(HTTPInputError) as cm: ++ HTTPServerRequest(uri="/?" + "&".join("a=%d" % i for i in range(n))) ++ self.assertIn("Max number of fields exceeded", str(cm.exception)) ++ + + class ParseRequestStartLineTest(unittest.TestCase): + METHOD = "GET" +-- +2.35.6 + 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 332d5875e6..a1b016d818 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 @@ -11,6 +11,7 @@ SRC_URI[sha256sum] = "66c513a76cda70d53907bc27cf1447557699c2e95aa48ba27a442ff61c SRC_URI += "file://CVE-2026-91990.patch \ file://CVE-2026-82397.patch \ file://CVE-2026-91991.patch \ + file://CVE-2026-103261.patch \ " inherit pypi python_setuptools_build_meta