python3-tornado: Fix CVE-2026-103261

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>
Signed-off-by: Anuj Mittal <anuj.mittal@oss.qualcomm.com>
This commit is contained in:
Yogita Urade authored and Anuj Mittal committed 2026-11-09 08:44:40 +05:30
1 parent 131bcf4881
commit 30603fc2bc
2 files changed
+99

No files matched your search

@@ -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
@@ -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