diff mbox series

[meta-oe,wrynose,1/6] python3-tornado: Fix CVE-2026-103261

Message ID 20261105070819.2086816-2-yurade@cisco.com
State New
Headers show
Series python3-tornado: Security fixes | expand

Commit Message

Yogita Urade Nov. 5, 2026, 7:08 a.m. UTC
This patch applies the upstream fix as referenced in [2],
using the commit shown in [1].

[1] https://github.com/tornadoweb/tornado/commit/03945136ea9746eccf61caf88edae39642e59c93
[2] https://nvd.nist.gov/vuln/detail/CVE-2026-103261

Signed-off-by: Yogita Urade <yurade@cisco.com>
---
 .../python3-tornado/CVE-2026-103261.patch     | 98 +++++++++++++++++++
 .../python/python3-tornado_6.5.7.bb           |  1 +
 2 files changed, 99 insertions(+)
 create mode 100644 meta-python/recipes-devtools/python/python3-tornado/CVE-2026-103261.patch
diff mbox series

Patch

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 <noreply@anthropic.com>
+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 <noreply@anthropic.com>
+Claude-Session: https://claude.ai/code/session_01JD7LUuFMdyPJB1Dwdq6fPg
+Signed-off-by: Yogita Urade <yurade@cisco.com>
+---
+ 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