{"id":"PYSEC-2026-3928","summary":" Tornado: Urlencoded body parsing omits max_num_fields, so one request can stall the event loop","details":"## Summary\n\nTornado parses `application/x-www-form-urlencoded` bodies with `urllib.parse.parse_qs` and does not pass `max_num_fields`. A body made almost entirely of separators produces tens of millions of fields, and the parse happens on the event loop before the handler runs, so a single request stalls the whole server.\n\n## Where it is\n\n`tornado/escape.py`, at HEAD `e530031405e2154654dedc4c84d5656b557ea310`:\n\n```python\nresult = urllib.parse.parse_qs(\n    qs, keep_blank_values, strict_parsing, encoding=\"latin1\", errors=\"strict\"\n)\n```\n\n`max_num_fields` is the parameter CPython added for exactly this, and it is absent.\n\nThe path to it is entirely server-side and pre-dispatch. `RequestHandler._execute` parses the body at `tornado/web.py:1821`, which reaches `HTTPServerRequest._parse_body` at `tornado/httputil.py:636`, and the urlencoded branch of `parse_body_arguments` calls `parse_qs_bytes` at `tornado/httputil.py:1030`.\n\nThe size that reaches it is bounded only by the body cap, which defaults to the stream's `max_buffer_size` of 104857600 at `tornado/iostream.py:239`, applied as the request body default at `tornado/http1connection.py:136-140`. A 100 MB body of separators is around fifty million fields.\n\n## Impact\n\nDenial of service against the whole process, not one request. Tornado is single-threaded and the parse is synchronous on the event loop, so every other connection waits. No authentication is needed if any route accepts a form post, which is the normal case.\n\n## Suggested fix\n\nPass a bound:\n\n```python\nresult = urllib.parse.parse_qs(\n    qs, keep_blank_values, strict_parsing, encoding=\"latin1\", errors=\"strict\",\n    max_num_fields=max_num_fields,\n)\n```\n\nwith a conservative default and a way for applications to raise it. CPython raises `ValueError` when the limit is exceeded, which maps cleanly onto a 400.\n\nLowering the default body cap for urlencoded specifically would help too, since 100 MB of form fields is not a shape any real client sends.\n\n## Why I do not think this is a duplicate\n\nThe published tornado advisories cover out-of-bounds access in the C extension, unbounded accumulation of decompressed chunks in `AsyncHTTPClient`, the Authorization header surviving cross-origin redirects, credential leakage on curl handle reuse, and cookie attribute validation. The decompression one is the nearest in spirit and is on the client side; this is the server parsing a request body. The call is unchanged at HEAD.","aliases":["CVE-2026-82397","GHSA-mpf4-983q-p7j4"],"modified":"2026-09-10T12:15:15.559565535Z","published":"2026-09-10T09:44:59.496635Z","references":[{"type":"WEB","url":"https://github.com/tornadoweb/tornado/security/advisories/GHSA-mpf4-983q-p7j4"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-82397"},{"type":"WEB","url":"https://github.com/tornadoweb/tornado/pull/3704"},{"type":"WEB","url":"https://github.com/tornadoweb/tornado/commit/8d6363ed7b69d5f0da806efe34d256627a2191de"},{"type":"PACKAGE","url":"https://github.com/tornadoweb/tornado"},{"type":"WEB","url":"https://github.com/tornadoweb/tornado/releases/tag/v6.5.8"},{"type":"PACKAGE","url":"https://pypi.org/project/tornado"},{"type":"ADVISORY","url":"https://github.com/advisories/GHSA-mpf4-983q-p7j4"}],"affected":[{"package":{"name":"tornado","ecosystem":"PyPI","purl":"pkg:pypi/tornado"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"6.5.8"}]}],"versions":["0.2","1.0","1.1","1.1.1","1.2","1.2.1","2.0","2.1","2.1.1","2.2","2.2.1","2.3","2.4","2.4.1","3.0","3.0.1","3.0.2","3.1","3.1.1","3.2","3.2.1","3.2.2","4.0","4.0.1","4.0.2","4.1","4.1b2","4.2","4.2.1","4.2b1","4.3","4.3b1","4.3b2","4.4","4.4.1","4.4.2","4.4.3","4.4b1","4.5","4.5.1","4.5.2","4.5.3","4.5b1","4.5b2","5.0","5.0.1","5.0.2","5.0a1","5.0b1","5.1","5.1.1","5.1b1","6.0","6.0.1","6.0.2","6.0.3","6.0.4","6.0a1","6.0b1","6.1","6.1b1","6.1b2","6.2","6.2b1","6.2b2","6.3","6.3.1","6.3.2","6.3.3","6.3b1","6.4","6.4.1","6.4.2","6.4b1","6.5","6.5.1","6.5.2","6.5.3","6.5.4","6.5.5","6.5.6","6.5.7","6.5b1"],"database_specific":{"source":"https://github.com/pypa/advisory-database/blob/main/vulns/tornado/PYSEC-2026-3928.yaml"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"}]}