GHSA-8423-8fgw-73vq: Pip/tornado vulnerability
Description
Summary
parsemultipartformdata (httputil.py:34) calls data.split(b"--"+boundary+b"\r\n") before the maxparts check (:35). A 600KB body with 100k parts creates a 100k-element transient list first, then rejects transient memory amplification (each split element is a copy). Pre-auth HTTP DoS.
Root cause
python parts = data[:finalboundaryindex].split(b"--" + boundary + b"\r\n") # :34 huge list first if len(parts) > config.maxparts: # :35 check after raise HTTPInputError("multipart/form-data has too many parts")
PoC
gist: https://gist.github.com/afldl/649861f25d39b53b7edbe0298e171617 poc.py + output.txt (100k parts from 600KB transient list).
Fix
Count separators without materializing the list (e.g. data.count(b"--"+boundary) first).
Credit
Reported by afldl, 2026-07.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
pip/tornadoto a version that resolves this vulnerability.Fixed in 6.5.8
Event History
Frequently Asked Questions
What does an attacker need to exploit this issue?
An attacker only needs to send a crafted multipart/form-data HTTP request containing a very large number of boundary-separated parts. The issue is pre-authentication, so no valid account or prior access is required.
Which deployments are exposed?
Deployments that process attacker-controlled multipart/form-data requests through the affected parser are exposed. The provided data does not identify a configuration that disables the vulnerable behavior by default.
What can be done if a code fix cannot be deployed immediately?
Limit or reject multipart/form-data requests with excessive numbers of parts before they reach the parser, where possible. The underlying fix is to count boundary separators without first materializing the full list of parts.
How can we recognize exploitation attempts or impact?
Look for small-to-moderate multipart request bodies containing unusually many repeated multipart boundaries, such as 100,000 parts in a roughly 600 KB body. Such requests may be rejected for exceeding max_parts, but only after transient memory amplification has already occurred.