{"id":"CVE-2026-103262","aliases":["ALSA-2026:82040","ALSA-2026:82041","AZL-105378","BELL-CVE-2026-103262","CLSA-2026-1790836708","CLSA-2026-1790846524","DEBIAN-CVE-2026-103262","EUVD-2026-90656","GHSA-chx6-46f5-w4vp","GHSA-h36x-fm6v-f9vx","RHSA-2026:82040","RHSA-2026:82041","RLSA-2026:82040","RLSA-2026:82041","UBUNTU-CVE-2026-103262","USN-8952-1"],"status":"published","title":"Tornado before 6.5.9 Denial of Service via CurlAsyncHTTPClient","description":"Tornado versions before 6.5.9 contain an unbounded memory accumulation vulnerability in CurlAsyncHTTPClient that allows remote attackers to cause denial of service by sending a compressed response. Attackers can send a gzip-encoded decompression bomb that accumulates in memory without size limits, causing the application process to be killed by out-of-memory conditions.","severity":{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-10","score":0.00628,"percentile":0.50437,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-400","CWE-409"],"published":"2026-09-30T23:49:13Z","modified":"2026-11-06T20:00:46Z","fixes":[{"ecosystem":"PyPI","package":"tornado","fixed_in":["6.5.9"],"sources":["ghsa","osv"]},{"ecosystem":"AlmaLinux:9","package":"python3-tornado","fixed_in":["6.5.10-1.el9_8.1"],"sources":["osv"]},{"ecosystem":"AlmaLinux:10","package":"python3-tornado","fixed_in":["6.5.10-1.el10_2.1"],"sources":["osv"]},{"ecosystem":"Alpaquita:25","package":"py3-tornado","fixed_in":["6.5.10-r0"],"sources":["osv"]},{"ecosystem":"Alpaquita:stream","package":"py3-tornado","fixed_in":["6.5.10-r0"],"sources":["osv"]},{"ecosystem":"TuxCare:PyPI","package":"tornado","fixed_in":["6.1.0.post10+tuxcare","5.1.1.post6+tuxcare"],"sources":["osv"]},{"ecosystem":"Red Hat:enterprise_linux:9::appstream","package":"python-tornado","fixed_in":["0:6.5.10-1.el9_8.1"],"sources":["osv"]},{"ecosystem":"Red Hat:enterprise_linux:9::appstream","package":"python-tornado-debugsource","fixed_in":["0:6.5.10-1.el9_8.1"],"sources":["osv"]},{"ecosystem":"Red Hat:enterprise_linux:9::appstream","package":"python3-tornado","fixed_in":["0:6.5.10-1.el9_8.1"],"sources":["osv"]},{"ecosystem":"Red Hat:enterprise_linux:9::appstream","package":"python3-tornado-debuginfo","fixed_in":["0:6.5.10-1.el9_8.1"],"sources":["osv"]},{"ecosystem":"Red Hat:enterprise_linux:10.2","package":"python-tornado","fixed_in":["0:6.5.10-1.el10_2.1"],"sources":["osv"]},{"ecosystem":"Red Hat:enterprise_linux:10.2","package":"python-tornado-debugsource","fixed_in":["0:6.5.10-1.el10_2.1"],"sources":["osv"]},{"ecosystem":"Red Hat:enterprise_linux:10.2","package":"python3-tornado","fixed_in":["0:6.5.10-1.el10_2.1"],"sources":["osv"]},{"ecosystem":"Red Hat:enterprise_linux:10.2","package":"python3-tornado-debuginfo","fixed_in":["0:6.5.10-1.el10_2.1"],"sources":["osv"]},{"ecosystem":"Rocky Linux:9","package":"python-tornado","fixed_in":["0:6.5.10-1.el9_8.1"],"sources":["osv"]},{"ecosystem":"Rocky Linux:10","package":"python-tornado","fixed_in":["0:6.5.10-1.el10_2.1"],"sources":["osv"]},{"ecosystem":"Ubuntu:Pro:16.04:LTS","package":"python-tornado","fixed_in":["4.2.1-1ubuntu3.1+esm4"],"sources":["osv"]},{"ecosystem":"Ubuntu:Pro:18.04:LTS","package":"python-tornado","fixed_in":["4.5.3-1ubuntu0.2+esm4"],"sources":["osv"]},{"ecosystem":"Ubuntu:24.04:LTS","package":"python-tornado","fixed_in":["6.4.0-1ubuntu0.6"],"sources":["osv"]},{"ecosystem":"Ubuntu:26.04:LTS","package":"python-tornado","fixed_in":["6.5.4-0.1ubuntu0.2"],"sources":["osv"]}],"affected":[{"source":"ghsa","ecosystem":"PyPI","package":"tornado","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"6.5.8"},{"fixed":"6.5.9"}]}]},{"source":"osv","ecosystem":"AlmaLinux:9","package":"python3-tornado","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"6.5.10-1.el9_8.1"}]}]},{"source":"osv","ecosystem":"AlmaLinux:10","package":"python3-tornado","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"6.5.10-1.el10_2.1"}]}]},{"source":"osv","ecosystem":"Azure Linux:3","package":"python-tornado","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"6.3.3-11"}]}]},{"source":"osv","ecosystem":"Alpaquita:25","package":"py3-tornado","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"6.5.1-r0"},{"fixed":"6.5.10-r0"}]}]},{"source":"osv","ecosystem":"Alpaquita:stream","package":"py3-tornado","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"6.3.3-r0"},{"fixed":"6.5.10-r0"}]}]},{"source":"osv","ecosystem":"TuxCare:PyPI","package":"tornado","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"6.1"},{"fixed":"6.1.0.post10+tuxcare"}]}],"versions":["6.1","6.1.0.post1+tuxcare","6.1.0.post2+tuxcare","6.1.0.post3+tuxcare","6.1.0.post4+tuxcare","6.1.0.post5+tuxcare","6.1.0.post6+tuxcare","6.1.0.post7+tuxcare","6.1.0.post8+tuxcare","6.1.0.post9+tuxcare"]},{"source":"osv","ecosystem":"TuxCare:PyPI","package":"tornado","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"5.1.1"},{"fixed":"5.1.1.post6+tuxcare"}]}],"versions":["5.1.1","5.1.1.post1+tuxcare","5.1.1.post2+tuxcare","5.1.1.post3+tuxcare","5.1.1.post4+tuxcare","5.1.1.post5+tuxcare"]},{"source":"osv","ecosystem":"Debian:12","package":"python-tornado","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["6.2.0-3","6.2.0-3+deb12u1","6.2.0-3+deb12u2","6.2.0-3+deb12u3","6.2.0-3+deb12u4","6.3.2-1","6.4.0-1","6.4.0-2","6.4.1-1","6.4.1-2","6.4.1-3","6.4.2-1","6.4.2-2","6.4.2-3","6.5.2-1","6.5.2-2","6.5.2-3","6.5.4-0.1","6.5.4-1","6.5.5-1","6.5.5-2","6.5.5-3","6.5.5-4","6.5.5-5"]},{"source":"osv","ecosystem":"Debian:13","package":"python-tornado","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["6.4.2-3","6.4.2-3+deb13u1","6.4.2-3+deb13u2","6.5.2-1","6.5.2-2","6.5.2-3","6.5.4-0.1","6.5.4-1","6.5.5-1","6.5.5-2","6.5.5-3","6.5.5-4","6.5.5-5"]},{"source":"osv","ecosystem":"Debian:14","package":"python-tornado","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["6.4.2-3","6.5.2-1","6.5.2-2","6.5.2-3","6.5.4-0.1","6.5.4-1","6.5.5-1","6.5.5-2","6.5.5-3","6.5.5-4","6.5.5-5"]},{"source":"osv","ecosystem":"PyPI","package":"tornado","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"6.5.9"}]}],"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.5.8","6.5b1"]},{"source":"osv","ecosystem":"Red Hat:enterprise_linux:9::appstream","package":"python-tornado","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:6.5.10-1.el9_8.1"}]}]},{"source":"osv","ecosystem":"Red Hat:enterprise_linux:9::appstream","package":"python-tornado-debugsource","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:6.5.10-1.el9_8.1"}]}]},{"source":"osv","ecosystem":"Red Hat:enterprise_linux:9::appstream","package":"python3-tornado","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:6.5.10-1.el9_8.1"}]}]},{"source":"osv","ecosystem":"Red Hat:enterprise_linux:9::appstream","package":"python3-tornado-debuginfo","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:6.5.10-1.el9_8.1"}]}]},{"source":"osv","ecosystem":"Red Hat:enterprise_linux:10.2","package":"python-tornado","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:6.5.10-1.el10_2.1"}]}]},{"source":"osv","ecosystem":"Red Hat:enterprise_linux:10.2","package":"python-tornado-debugsource","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:6.5.10-1.el10_2.1"}]}]},{"source":"osv","ecosystem":"Red Hat:enterprise_linux:10.2","package":"python3-tornado","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:6.5.10-1.el10_2.1"}]}]},{"source":"osv","ecosystem":"Red Hat:enterprise_linux:10.2","package":"python3-tornado-debuginfo","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:6.5.10-1.el10_2.1"}]}]},{"source":"osv","ecosystem":"Rocky Linux:9","package":"python-tornado","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:6.5.10-1.el9_8.1"}]}]},{"source":"osv","ecosystem":"Rocky Linux:10","package":"python-tornado","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:6.5.10-1.el10_2.1"}]}]},{"source":"osv","ecosystem":"Ubuntu:Pro:16.04:LTS","package":"python-tornado","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"4.2.1-1ubuntu3.1+esm4"}]}],"versions":["4.2.1-1ubuntu2","4.2.1-1ubuntu3","4.2.1-1ubuntu3.1","4.2.1-1ubuntu3.1+esm1","4.2.1-1ubuntu3.1+esm2","4.2.1-1ubuntu3.1+esm3"]},{"source":"osv","ecosystem":"Ubuntu:Pro:18.04:LTS","package":"python-tornado","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"4.5.3-1ubuntu0.2+esm4"}]}],"versions":["4.5.1-2.1~build2","4.5.2-1","4.5.3-1","4.5.3-1ubuntu0.1","4.5.3-1ubuntu0.2","4.5.3-1ubuntu0.2+esm1","4.5.3-1ubuntu0.2+esm2","4.5.3-1ubuntu0.2+esm3"]},{"source":"osv","ecosystem":"Ubuntu:Pro:22.04:LTS","package":"python-tornado","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["6.1.0-1build1","6.1.0-2","6.1.0-3","6.1.0-3build1","6.1.0-3ubuntu0.1~esm1","6.1.0-3ubuntu0.1~esm2","6.1.0-3ubuntu0.1~esm4","6.1.0-3ubuntu0.1~esm5","6.1.0-3ubuntu0.1~esm6","6.1.0-3ubuntu0.1~esm7"]},{"source":"osv","ecosystem":"Ubuntu:24.04:LTS","package":"python-tornado","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"6.4.0-1ubuntu0.6"}]}],"versions":["6.3.2-1","6.4.0-0ubuntu1","6.4.0-1","6.4.0-1build1","6.4.0-1ubuntu0.1","6.4.0-1ubuntu0.2","6.4.0-1ubuntu0.4","6.4.0-1ubuntu0.5"]},{"source":"osv","ecosystem":"Ubuntu:26.04:LTS","package":"python-tornado","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"6.5.4-0.1ubuntu0.2"}]}],"versions":["6.4.2-3","6.5.2-3","6.5.4-0.1","6.5.4-0.1ubuntu0.1"]},{"source":"osv","ecosystem":"Ubuntu:Pro:16.04:LTS","package":"python-tornado","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"4.2.1-1ubuntu3.1+esm4"}]}],"versions":["4.2.1-1ubuntu2","4.2.1-1ubuntu3","4.2.1-1ubuntu3.1","4.2.1-1ubuntu3.1+esm1","4.2.1-1ubuntu3.1+esm2","4.2.1-1ubuntu3.1+esm3"],"bundled":6},{"source":"osv","ecosystem":"Ubuntu:Pro:18.04:LTS","package":"python-tornado","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"4.5.3-1ubuntu0.2+esm4"}]}],"versions":["4.5.1-2.1~build2","4.5.2-1","4.5.3-1","4.5.3-1ubuntu0.1","4.5.3-1ubuntu0.2","4.5.3-1ubuntu0.2+esm1","4.5.3-1ubuntu0.2+esm2","4.5.3-1ubuntu0.2+esm3"],"bundled":6},{"source":"osv","ecosystem":"Ubuntu:Pro:20.04:LTS","package":"python-tornado","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"6.0.3+really5.1.1-3ubuntu0.1~esm7"}]}],"versions":["5.1.1-4ubuntu1","5.1.1-4ubuntu5","6.0.3+really5.1.1-2","6.0.3+really5.1.1-2build1","6.0.3+really5.1.1-2build2","6.0.3+really5.1.1-3","6.0.3+really5.1.1-3ubuntu0.1~esm1","6.0.3+really5.1.1-3ubuntu0.1~esm3","6.0.3+really5.1.1-3ubuntu0.1~esm5"],"bundled":6},{"source":"osv","ecosystem":"Ubuntu:Pro:22.04:LTS","package":"python-tornado","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"6.1.0-3ubuntu0.1~esm6"}]}],"versions":["6.1.0-1build1","6.1.0-2","6.1.0-3","6.1.0-3build1","6.1.0-3ubuntu0.1~esm1","6.1.0-3ubuntu0.1~esm2","6.1.0-3ubuntu0.1~esm4","6.1.0-3ubuntu0.1~esm5"],"bundled":6},{"source":"osv","ecosystem":"Ubuntu:24.04:LTS","package":"python-tornado","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"6.4.0-1ubuntu0.6"}]}],"versions":["6.3.2-1","6.4.0-0ubuntu1","6.4.0-1","6.4.0-1build1","6.4.0-1ubuntu0.1","6.4.0-1ubuntu0.2","6.4.0-1ubuntu0.4","6.4.0-1ubuntu0.5"],"bundled":6},{"source":"osv","ecosystem":"Ubuntu:26.10","package":"python-tornado","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"6.5.5-5ubuntu1.1"}]}],"versions":["6.5.4-0.1","6.5.5-2","6.5.5-4","6.5.5-5"],"bundled":6},{"source":"osv","ecosystem":"Ubuntu:26.04:LTS","package":"python-tornado","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"6.5.4-0.1ubuntu0.2"}]}],"versions":["6.4.2-3","6.5.2-3","6.5.4-0.1","6.5.4-0.1ubuntu0.1"],"bundled":6}],"products":[{"vendor":"tornadoweb","product":"tornado"}],"references":[{"url":"https://github.com/tornadoweb/tornado/security/advisories/GHSA-chx6-46f5-w4vp","tags":["vendor-advisory","exploit","advisory","web","article","report"]},{"url":"https://github.com/tornadoweb/tornado/commit/e412435febba4569c552c5c9054f1bf92bd171a9","tags":["patch","fix","web","article","report"]},{"url":"https://www.vulncheck.com/advisories/tornado-before-6.5.9-denial-of-service-via-curlasynchttpclient","tags":["third-party-advisory","advisory","article","report"]},{"url":"https://github.com/tornadoweb/tornado/pull/3719","tags":["web"]},{"url":"https://github.com/tornadoweb/tornado/commit/15f056080d8e456f92cca8ad0c33e5a5480414ac","tags":["web"]},{"url":"https://github.com/tornadoweb/tornado/commit/6564e0a0922c16f239d3a18adccd04736e380c89","tags":["web"]},{"url":"https://github.com/tornadoweb/tornado/commit/aa2eb0d989716ac00fe8d7a9919b81c1eccd6ec4","tags":["web"]},{"url":"https://github.com/tornadoweb/tornado/releases/tag/v6.5.9","tags":["web"]},{"url":"https://github.com/advisories/GHSA-chx6-46f5-w4vp"},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-103262","tags":["web","advisory"]},{"url":"https://github.com/advisories/GHSA-h36x-fm6v-f9vx"},{"url":"https://access.redhat.com/errata/RHSA-2026:82040","tags":["advisory"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-103262","tags":["report"]},{"url":"https://bugzilla.redhat.com/2544534","tags":["report"]},{"url":"https://errata.almalinux.org/9/ALSA-2026-82040.html","tags":["advisory"]},{"url":"https://access.redhat.com/errata/RHSA-2026:82041","tags":["advisory"]},{"url":"https://errata.almalinux.org/10/ALSA-2026-82041.html","tags":["advisory"]},{"url":"https://docs.bell-sw.com/security/cves/CVE-2026-103262","tags":["advisory"]},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/103xxx/CVE-2026-103262.json","tags":["advisory"]},{"url":"https://security-tracker.debian.org/tracker/CVE-2026-103262","tags":["advisory"]},{"url":"https://github.com/tornadoweb/tornado","tags":["package"]},{"url":"https://access.redhat.com/security/updates/classification/#important","tags":["article"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2544534","tags":["report"]},{"url":"https://security.access.redhat.com/data/csaf/v2/advisories/2026/rhsa-2026_82040.json","tags":["advisory"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-103262","tags":["advisory","report"]},{"url":"https://security.access.redhat.com/data/csaf/v2/advisories/2026/rhsa-2026_82041.json","tags":["advisory"]},{"url":"https://errata.rockylinux.org/RLSA-2026:82040","tags":["advisory"]},{"url":"https://errata.rockylinux.org/RLSA-2026:82041","tags":["advisory"]},{"url":"https://ubuntu.com/security/CVE-2026-103262","tags":["report"]},{"url":"https://ubuntu.com/security/notices/USN-8952-1","tags":["advisory"]},{"url":"https://ubuntu.com/security/CVE-2023-54397","tags":["report"]},{"url":"https://ubuntu.com/security/CVE-2024-14029","tags":["report"]},{"url":"https://ubuntu.com/security/CVE-2026-91990","tags":["report"]},{"url":"https://ubuntu.com/security/CVE-2026-91992","tags":["report"]},{"url":"https://ubuntu.com/security/CVE-2026-103263","tags":["report"]}],"sources":{"cve":[{"id":"CVE-2026-103262","title":"Tornado before 6.5.9 Denial of Service via CurlAsyncHTTPClient","description":"Tornado versions before 6.5.9 contain an unbounded memory accumulation vulnerability in CurlAsyncHTTPClient that allows remote attackers to cause denial of service by sending a compressed response. Attackers can send a gzip-encoded decompression bomb that accumulates in memory without size limits, causing the application process to be killed by out-of-memory conditions.","status":"published","published":"2026-10-01T10:42:04.913Z","modified":"2026-10-06T00:08:58.078Z","cvss":[{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high","assessor":"cna"},{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high","assessor":"cna"}],"cwes":["CWE-409"],"updated_at":"2026-11-02T13:34:37.420074Z"}],"euvd":[{"id":"EUVD-2026-90656","description":"Tornado versions before 6.5.9 contain an unbounded memory accumulation vulnerability in CurlAsyncHTTPClient that allows remote attackers to cause denial of service by sending a compressed response. Attackers can send a gzip-encoded decompression bomb that accumulates in memory without size limits, causing the application process to be killed by out-of-memory conditions.","status":"published","published":"2026-10-01T10:42:04Z","modified":"2026-10-06T00:08:58Z","cvss":[{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high"}],"updated_at":"2026-11-02T17:27:39.05741Z"}],"ghsa":[{"id":"GHSA-chx6-46f5-w4vp","title":"tornado: CurlAsyncHTTPClient enforces no response-size limit — decompression bomb drives unbounded memory accumulation to OOM","description":"An unbounded memory accumulation (decompression bomb) in `tornado.curl_httpclient.CurlAsyncHTTPClient` — the client-side sibling gap of CVE-2026-49855 — verified end-to-end on the 2026-08-15 master snapshot (`6.6.dev1`) and present unchanged in the latest release tag `v6.5.8` and on master (checked 2026-08-17). When a Tornado application configures the curl client (the documented deployment for proxy support / advanced TLS options) and `fetch()`es an attacker-chosen or attacker-compromised URL with default `decompress_response=True`, a malicious server replying `Content-Encoding: gzip` with a ~2.8 MB wire bomb drove the client's RSS from 30,884 kB to **1,032,100 kB (~1008 MB) in 3.18 s** (~350 MB/s, monotonic, no plateau) until the kernel OOM-killed the process (exit 137, cgroup `OOMKilled=true`) — with the transfer only 67% complete and **no client-side size check ever intervening**: `curl_httpclient.py` contains zero occurrences of `max_body_size`/`MAXFILESIZE`. The identical bomb against the default `SimpleAsyncHTTPClient` fails cleanly at ~65 MB, because every size gate CVE-2026-49855 added (compressed CL, chunked total, cumulative decompressed size — `http1connection.py:620,676,742`) lives in code the curl client never executes. This is a distinct component from the published advisory (which fixed `_GzipMessageDelegate`/`SimpleAsyncHTTPClient` only) and from the other curl-client advisories (credential handle-reuse GHSA-pw6j-qg29-8w7f, header CRLF GHSA-w235-7p84-xx57); the file's full commit history (latest 2026-06-17) shows no response-size work.\n\n## Details\n\n`tornado/curl_httpclient.py` (line numbers identical on master `6.6.dev1`, `v6.5.8`, and the audited snapshot):\n\n```python\n\"buffer\": BytesIO(),                                  # :202 — plain BytesIO, no accounting\n...\nelse:\n    write_function = buffer.write                     # :359 — every decompressed byte lands here\ncurl.setopt(pycurl.WRITEFUNCTION, write_function)     # :360\n...\nif request.decompress_response:                       # default True (HTTPRequest)\n    curl.setopt(pycurl.ENCODING, \"gzip,deflate\")      # :373-374 — libcurl advertises + auto-decodes\n```\n\nlibcurl decompresses the response **before** invoking `WRITEFUNCTION`, so the callback receives decompressed bytes, which are appended to an unbounded `BytesIO` until the transfer ends or the process dies. The only ceiling is `request_timeout` (default 20 s) — at zlib's hundreds of MB/s that still permits many GB of accumulation; the PoC raised it to 300 s and the 1 GiB cgroup cap was hit in 3.2 s regardless. There is no `pycurl.MAXFILESIZE`, no `max_buffer_size`/`max_body_size` plumbing (the constructor accepts no body-size option), and `streaming_callback` users fare no better (the callback variant at `:353-356` also performs zero accounting).\n\nContrast — `SimpleAsyncHTTPClient` path (`tornado/http1connection.py`), all absent from the curl path:\n\n```python\nif cast(int, content_length) \u003e self._max_body_size:        # :620  Content-Length gate\nif total_size \u003e self._max_body_size:                       # :676  chunked total gate\nif self._decompressed_body_size \u003e self._max_body_size:     # :742  CVE-2026-49855 decompressed gate\n```\n\n`SimpleAsyncHTTPClient.initialize()` defaults `max_buffer_size = 104857600` (100 MiB) with `max_body_size` defaulting to it (`simple_httpclient.py:117-121`); `CurlAsyncHTTPClient.initialize()` has no corresponding parameter at all.\n\nAttack chain (attacker = malicious HTTP server; victim = any Tornado app doing `fetch()` on attacker-influenced URLs — URL fetchers, webhook processors, link previewers, RSS/probe pollers):\n\n1. App configures `AsyncHTTPClient.configure(\"tornado.curl_httpclient.CurlAsyncHTTPClient\")` (documented for proxy support; proxies are only supported with the curl client).\n2. App calls `fetch(\"http://attacker/...\")` with defaults → request advertises `Accept-Encoding: gzip,deflate`.\n3. Attacker replies `200`, `Content-Encoding: gzip`, `Transfer-Encoding: chunked`, body = a gzip stream of zeros (4,174,525 wire bytes expanding to 4 GiB, 1029:1, sent in 64 KB chunks).\n4. libcurl auto-decodes at ~350 MB/s into `buffer.write` with no size accounting → process RSS climbs linearly until OOM. The response need not complete: the client died with 2,818,048/4,174,525 wire bytes delivered (67%).\n\nVariant without compression: `decompress_response=False` plus an endless streaming body (no Content-Length, no final chunk) feeds the same unaccounted `buffer.write` — this client never enforces any cap on any path.\n\n## PoC\n\nVerified end-to-end 2026-08-15 in a single container (cgroup `--memory 1g --memory-swap 1g` so the exhaustion endpoint is safe and fast to observe), two processes over a real TCP socket on `127.0.0.1:8081`: a raw-socket malicious server (`evil_server.py`, builds the 4 GiB-of-zeros gzip bomb once, serves it with chunked framing) and the Tornado victim (`victim_curl.py`, configures `CurlAsyncHTTPClient`, `fetch(..., request_timeout=300)`, samples `/proc/self/status` VmRSS every 0.2 s from a monitor thread so the curve survives the OOM kill).\n\n### Build \u0026 run the victim\n\n```bash\ngit clone https://github.com/tornadoweb/tornado\ncd tornado\npip install pycurl\npython evil_server.py \u0026     # 127.0.0.1:8081; prints BOMB_BUILT wire_bytes=... ratio=... EVIL_LISTENING\npython victim_curl.py       # victim: CurlAsyncHTTPClient fetch -\u003e expect linear RSS rise -\u003e OOM\npython victim_simple.py     # control: default SimpleAsyncHTTPClient on the same bomb\n```\n\nVerified environment: debian:bookworm-slim, Python 3.11.2, python3-pycurl 7.45.2 (libcurl 7.88.1, zlib 1.2.13), tornado master snapshot `6.6.dev1` of 2026-08-15 run from the source tree (`sys.path`), container memory capped at 1 GiB. `curl_httpclient.py` verified byte-equivalent (still zero size-limit references) in tag `v6.5.8` and on master as of 2026-08-17.\n\n### Reproduction steps\n\n1. **Precondition — the documented curl-client deployment fetching a remote URL.** The vulnerability requires the application to use `CurlAsyncHTTPClient` (the standard configuration when proxy support or advanced TLS options are needed) with default `decompress_response=True`, and to fetch a URL whose server the attacker controls or has compromised. `victim_curl.py` implements exactly that (`AsyncHTTPClient.configure(\"tornado.curl_httpclient.CurlAsyncHTTPClient\")` → `fetch()`), and the server log confirms the ENCODING path engaged — the victim's request arrived with `User-Agent: Mozilla/5.0 (compatible; pycurl)` and `Accept-Encoding: gzip,deflate`.\n\n2. **Attack:** start `evil_server.py`, wait for `EVIL_LISTENING`, then run `victim_curl.py` (the fetch itself is the attack; no further interaction).\n\n3. **Expected:** `victim_curl_rss.log` shows VmRSS 30,884 → 1,032,100 kB over 3.18 s, rising ~350 MB/s with no plateau; the container kills the victim (`exit 137`, docker `OOMKilled=true`); the server logs `CLIENT_DIED_MID_TRANSFER wire_sent=2818048/4174525` — the accumulation is bounded only by available memory, never by a client-side check, and the 4 GiB payload was never fully delivered.\n\n4. **Variant:** with `decompress_response=False` and an unterminated chunked body (server keeps sending forever), the same `buffer.write` path accumulates unbounded plain bytes — no compression needed; `request_timeout` only extends the ceiling.\n\n5. **Control:** `victim_simple.py` fetches the identical bomb with the default `SimpleAsyncHTTPClient` → clean `FETCH_FAILED: HTTP 599: Connection closed`, RSS peak ~65 MB (24,984 → 66,712 kB), script exit 0 — the CVE-2026-49855 accounting in `http1connection.py` aborts the transfer, proving the gap is specific to the curl client.\n\n### PoC source\n\nFull PoC source (**victim_curl.py**, stdlib only, no dependencies): [victim_curl.py](https://gist.github.com/afldl/211c0e7af8175c2eabab53ed037c435d) (secret gist, unlisted).\n\n\nThe gist carries `evil_server.py` (bomb server), `victim_curl.py` (victim), `victim_simple.py` (control), and `REPRODUCE.md`.\n\n### Captured output (2026-08-15 run, verbatim excerpts)\n\n```\nevil_server.log:\nBOMB_BUILT wire_bytes=4174525 decompressed_bytes=4294967296 ratio=1029:1\nEVIL_LISTENING 127.0.0.1:8081\nREQUEST_FROM 127.0.0.1:57544 -\u003e GET /bomb HTTP/1.1\nREQUEST_HEADERS:\nGET /bomb HTTP/1.1\nHost: 127.0.0.1:8081\nUser-Agent: Mozilla/5.0 (compatible; pycurl)\nAccept: */*\nAccept-Encoding: gzip,deflate\nWIRE_SENT 65536/4174525\nWIRE_SENT 2162688/4174525\nCLIENT_DIED_MID_TRANSFER wire_sent=2818048/4174525 err=ConnectionResetError(104, 'Connection reset by peer')\n\nvictim_curl_rss.log (VmRSS kB, every 0.2 s):\n0.00  30884\n0.61  231848\n1.41  514720\n2.22  794480\n2.82 1000756\n3.18 1032100        \u003c- last sample before kill\n\ndriver_f1.log:\ntimeout 240 python3 /e2e/F1/victim_curl.py ...  606 Killed\nVICTIM_CURL_EXIT=137         # docker inspect -\u003e OomKilled: true\n\nvictim_simple.log (control, same bomb):\nFETCH_FAILED: HTTP 599: Connection closed\nSCRIPT_FINISHED_NORMALLY     # exit 0, VmRSS peak 66712 kB\n```\n\nHonest framing of the endpoint: the OOM kill at ~1008 MB was forced by the test's 1 GiB cgroup cap as the observation instrument; the \"unbounded\" claim rests on the linear no-plateau RSS curve, death at 67% wire delivery, and the absence of any size accounting in the code path — with more memory the transfer would have continued to the full 4 GiB payload.\n\n## Impact\n\nDenial of service (memory exhaustion) of any Tornado application that uses the curl HTTP client and fetches attacker-influenced URLs. A single ~4 MB response kills a 1 GiB process in ~3 s; wire cost scales as `available_memory / 1000`. Because the accumulation happens on the shared event loop's client, one malicious response takes down the entire application (all concurrently served users), and a slow endless-body variant drains memory gradually below detection thresholds. The attack requires no privileges, no user interaction, and only that the victim's configured client visits the attacker's origin.\n\n### Suggested fix\n\nEnforce a byte budget in the write path of `CurlAsyncHTTPClient`: wrap the `WRITEFUNCTION` (both the `buffer.write` branch and the `streaming_callback` branch) in a counter that aborts the transfer (`curl.setopt(pycurl.FAILONERROR)`-style cancellation or raising from the callback) once the received total exceeds `max_body_size`, plumbed from `initialize()` with the same 100 MiB default as `SimpleAsyncHTTPClient`. Because libcurl decompresses before the write callback, the counter naturally measures *decompressed* bytes — the same semantics as the CVE-2026-49855 fix. (`pycurl.MAXFILESIZE` alone is insufficient: it applies to the compressed transfer size.)\n\n### Affected versions\n\n- **\u003c= 6.5.8** (latest tag; the curl client has never had a response-size limit) and master (`6.6.dev1`, verified 2026-08-15/17).\n- 6.5.6's CVE-2026-49855 fix covered `SimpleAsyncHTTPClient`/`http1connection.py` only; `curl_httpclient.py` was not touched.\n\n## Credit\n\nReported by the diff/ambidiff security research effort (afldl).","status":"published","published":"2026-09-30T23:49:13Z","modified":"2026-09-30T23:49:14Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high"}],"cwes":["CWE-400","CWE-409"],"updated_at":"2026-11-02T14:41:23.783773Z"},{"id":"GHSA-h36x-fm6v-f9vx","title":"Tornado versions before 6.5.9 contain an unbounded memory accumulation vulnerability in...","description":"Tornado versions before 6.5.9 contain an unbounded memory accumulation vulnerability in CurlAsyncHTTPClient that allows remote attackers to cause denial of service by sending a compressed response. Attackers can send a gzip-encoded decompression bomb that accumulates in memory without size limits, causing the application process to be killed by out-of-memory conditions.","status":"published","published":"2026-10-01T12:31:16Z","modified":"2026-10-01T12:31:23Z","cvss":[{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"high"},{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high"}],"cwes":["CWE-409"],"updated_at":"2026-11-02T15:12:39.998127Z"}],"nvd":[{"id":"CVE-2026-103262","description":"Tornado versions before 6.5.9 contain an unbounded memory accumulation vulnerability in CurlAsyncHTTPClient that allows remote attackers to cause denial of service by sending a compressed response. Attackers can send a gzip-encoded decompression bomb that accumulates in memory without size limits, causing the application process to be killed by out-of-memory conditions.","status":"published","published":"2026-10-01T11:17:21.05Z","modified":"2026-10-06T01:16:32.767Z","cvss":[{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"high","assessor":"secondary"},{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high","assessor":"secondary"}],"cwes":["CWE-409"],"updated_at":"2026-11-02T14:12:53.097633Z"}],"osv":[{"id":"BELL-CVE-2026-103262","status":"published","published":"2026-10-02T06:05:04.892048Z","modified":"2026-10-07T07:15:15.780197257Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high"}],"updated_at":"2026-11-02T15:28:23.037038Z"},{"id":"CVE-2026-103262","title":"Tornado before 6.5.9 Denial of Service via CurlAsyncHTTPClient","description":"Tornado versions before 6.5.9 contain an unbounded memory accumulation vulnerability in CurlAsyncHTTPClient that allows remote attackers to cause denial of service by sending a compressed response. Attackers can send a gzip-encoded decompression bomb that accumulates in memory without size limits, causing the application process to be killed by out-of-memory conditions.","status":"published","published":"2026-10-01T10:42:04.913Z","modified":"2026-10-28T02:31:01.254670575Z","cvss":[{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high"}],"cwes":["CWE-409"],"updated_at":"2026-11-02T15:44:54.236592Z"},{"id":"DEBIAN-CVE-2026-103262","description":"Tornado versions before 6.5.9 contain an unbounded memory accumulation vulnerability in CurlAsyncHTTPClient that allows remote attackers to cause denial of service by sending a compressed response. Attackers can send a gzip-encoded decompression bomb that accumulates in memory without size limits, causing the application process to be killed by out-of-memory conditions.","status":"published","published":"2026-10-01T11:17:21.05Z","modified":"2026-10-06T10:00:14.511488187Z","cvss":[{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"high"}],"updated_at":"2026-11-02T15:45:48.903138Z"},{"id":"GHSA-chx6-46f5-w4vp","title":"tornado: CurlAsyncHTTPClient enforces no response-size limit — decompression bomb drives unbounded memory accumulation to OOM","description":"An unbounded memory accumulation (decompression bomb) in `tornado.curl_httpclient.CurlAsyncHTTPClient` — the client-side sibling gap of CVE-2026-49855 — verified end-to-end on the 2026-08-15 master snapshot (`6.6.dev1`) and present unchanged in the latest release tag `v6.5.8` and on master (checked 2026-08-17). When a Tornado application configures the curl client (the documented deployment for proxy support / advanced TLS options) and `fetch()`es an attacker-chosen or attacker-compromised URL with default `decompress_response=True`, a malicious server replying `Content-Encoding: gzip` with a ~2.8 MB wire bomb drove the client's RSS from 30,884 kB to **1,032,100 kB (~1008 MB) in 3.18 s** (~350 MB/s, monotonic, no plateau) until the kernel OOM-killed the process (exit 137, cgroup `OOMKilled=true`) — with the transfer only 67% complete and **no client-side size check ever intervening**: `curl_httpclient.py` contains zero occurrences of `max_body_size`/`MAXFILESIZE`. The identical bomb against the default `SimpleAsyncHTTPClient` fails cleanly at ~65 MB, because every size gate CVE-2026-49855 added (compressed CL, chunked total, cumulative decompressed size — `http1connection.py:620,676,742`) lives in code the curl client never executes. This is a distinct component from the published advisory (which fixed `_GzipMessageDelegate`/`SimpleAsyncHTTPClient` only) and from the other curl-client advisories (credential handle-reuse GHSA-pw6j-qg29-8w7f, header CRLF GHSA-w235-7p84-xx57); the file's full commit history (latest 2026-06-17) shows no response-size work.\n\n## Details\n\n`tornado/curl_httpclient.py` (line numbers identical on master `6.6.dev1`, `v6.5.8`, and the audited snapshot):\n\n```python\n\"buffer\": BytesIO(),                                  # :202 — plain BytesIO, no accounting\n...\nelse:\n    write_function = buffer.write                     # :359 — every decompressed byte lands here\ncurl.setopt(pycurl.WRITEFUNCTION, write_function)     # :360\n...\nif request.decompress_response:                       # default True (HTTPRequest)\n    curl.setopt(pycurl.ENCODING, \"gzip,deflate\")      # :373-374 — libcurl advertises + auto-decodes\n```\n\nlibcurl decompresses the response **before** invoking `WRITEFUNCTION`, so the callback receives decompressed bytes, which are appended to an unbounded `BytesIO` until the transfer ends or the process dies. The only ceiling is `request_timeout` (default 20 s) — at zlib's hundreds of MB/s that still permits many GB of accumulation; the PoC raised it to 300 s and the 1 GiB cgroup cap was hit in 3.2 s regardless. There is no `pycurl.MAXFILESIZE`, no `max_buffer_size`/`max_body_size` plumbing (the constructor accepts no body-size option), and `streaming_callback` users fare no better (the callback variant at `:353-356` also performs zero accounting).\n\nContrast — `SimpleAsyncHTTPClient` path (`tornado/http1connection.py`), all absent from the curl path:\n\n```python\nif cast(int, content_length) \u003e self._max_body_size:        # :620  Content-Length gate\nif total_size \u003e self._max_body_size:                       # :676  chunked total gate\nif self._decompressed_body_size \u003e self._max_body_size:     # :742  CVE-2026-49855 decompressed gate\n```\n\n`SimpleAsyncHTTPClient.initialize()` defaults `max_buffer_size = 104857600` (100 MiB) with `max_body_size` defaulting to it (`simple_httpclient.py:117-121`); `CurlAsyncHTTPClient.initialize()` has no corresponding parameter at all.\n\nAttack chain (attacker = malicious HTTP server; victim = any Tornado app doing `fetch()` on attacker-influenced URLs — URL fetchers, webhook processors, link previewers, RSS/probe pollers):\n\n1. App configures `AsyncHTTPClient.configure(\"tornado.curl_httpclient.CurlAsyncHTTPClient\")` (documented for proxy support; proxies are only supported with the curl client).\n2. App calls `fetch(\"http://attacker/...\")` with defaults → request advertises `Accept-Encoding: gzip,deflate`.\n3. Attacker replies `200`, `Content-Encoding: gzip`, `Transfer-Encoding: chunked`, body = a gzip stream of zeros (4,174,525 wire bytes expanding to 4 GiB, 1029:1, sent in 64 KB chunks).\n4. libcurl auto-decodes at ~350 MB/s into `buffer.write` with no size accounting → process RSS climbs linearly until OOM. The response need not complete: the client died with 2,818,048/4,174,525 wire bytes delivered (67%).\n\nVariant without compression: `decompress_response=False` plus an endless streaming body (no Content-Length, no final chunk) feeds the same unaccounted `buffer.write` — this client never enforces any cap on any path.\n\n## PoC\n\nVerified end-to-end 2026-08-15 in a single container (cgroup `--memory 1g --memory-swap 1g` so the exhaustion endpoint is safe and fast to observe), two processes over a real TCP socket on `127.0.0.1:8081`: a raw-socket malicious server (`evil_server.py`, builds the 4 GiB-of-zeros gzip bomb once, serves it with chunked framing) and the Tornado victim (`victim_curl.py`, configures `CurlAsyncHTTPClient`, `fetch(..., request_timeout=300)`, samples `/proc/self/status` VmRSS every 0.2 s from a monitor thread so the curve survives the OOM kill).\n\n### Build \u0026 run the victim\n\n```bash\ngit clone https://github.com/tornadoweb/tornado\ncd tornado\npip install pycurl\npython evil_server.py \u0026     # 127.0.0.1:8081; prints BOMB_BUILT wire_bytes=... ratio=... EVIL_LISTENING\npython victim_curl.py       # victim: CurlAsyncHTTPClient fetch -\u003e expect linear RSS rise -\u003e OOM\npython victim_simple.py     # control: default SimpleAsyncHTTPClient on the same bomb\n```\n\nVerified environment: debian:bookworm-slim, Python 3.11.2, python3-pycurl 7.45.2 (libcurl 7.88.1, zlib 1.2.13), tornado master snapshot `6.6.dev1` of 2026-08-15 run from the source tree (`sys.path`), container memory capped at 1 GiB. `curl_httpclient.py` verified byte-equivalent (still zero size-limit references) in tag `v6.5.8` and on master as of 2026-08-17.\n\n### Reproduction steps\n\n1. **Precondition — the documented curl-client deployment fetching a remote URL.** The vulnerability requires the application to use `CurlAsyncHTTPClient` (the standard configuration when proxy support or advanced TLS options are needed) with default `decompress_response=True`, and to fetch a URL whose server the attacker controls or has compromised. `victim_curl.py` implements exactly that (`AsyncHTTPClient.configure(\"tornado.curl_httpclient.CurlAsyncHTTPClient\")` → `fetch()`), and the server log confirms the ENCODING path engaged — the victim's request arrived with `User-Agent: Mozilla/5.0 (compatible; pycurl)` and `Accept-Encoding: gzip,deflate`.\n\n2. **Attack:** start `evil_server.py`, wait for `EVIL_LISTENING`, then run `victim_curl.py` (the fetch itself is the attack; no further interaction).\n\n3. **Expected:** `victim_curl_rss.log` shows VmRSS 30,884 → 1,032,100 kB over 3.18 s, rising ~350 MB/s with no plateau; the container kills the victim (`exit 137`, docker `OOMKilled=true`); the server logs `CLIENT_DIED_MID_TRANSFER wire_sent=2818048/4174525` — the accumulation is bounded only by available memory, never by a client-side check, and the 4 GiB payload was never fully delivered.\n\n4. **Variant:** with `decompress_response=False` and an unterminated chunked body (server keeps sending forever), the same `buffer.write` path accumulates unbounded plain bytes — no compression needed; `request_timeout` only extends the ceiling.\n\n5. **Control:** `victim_simple.py` fetches the identical bomb with the default `SimpleAsyncHTTPClient` → clean `FETCH_FAILED: HTTP 599: Connection closed`, RSS peak ~65 MB (24,984 → 66,712 kB), script exit 0 — the CVE-2026-49855 accounting in `http1connection.py` aborts the transfer, proving the gap is specific to the curl client.\n\n### PoC source\n\nFull PoC source (**victim_curl.py**, stdlib only, no dependencies): [victim_curl.py](https://gist.github.com/afldl/211c0e7af8175c2eabab53ed037c435d) (secret gist, unlisted).\n\n\nThe gist carries `evil_server.py` (bomb server), `victim_curl.py` (victim), `victim_simple.py` (control), and `REPRODUCE.md`.\n\n### Captured output (2026-08-15 run, verbatim excerpts)\n\n```\nevil_server.log:\nBOMB_BUILT wire_bytes=4174525 decompressed_bytes=4294967296 ratio=1029:1\nEVIL_LISTENING 127.0.0.1:8081\nREQUEST_FROM 127.0.0.1:57544 -\u003e GET /bomb HTTP/1.1\nREQUEST_HEADERS:\nGET /bomb HTTP/1.1\nHost: 127.0.0.1:8081\nUser-Agent: Mozilla/5.0 (compatible; pycurl)\nAccept: */*\nAccept-Encoding: gzip,deflate\nWIRE_SENT 65536/4174525\nWIRE_SENT 2162688/4174525\nCLIENT_DIED_MID_TRANSFER wire_sent=2818048/4174525 err=ConnectionResetError(104, 'Connection reset by peer')\n\nvictim_curl_rss.log (VmRSS kB, every 0.2 s):\n0.00  30884\n0.61  231848\n1.41  514720\n2.22  794480\n2.82 1000756\n3.18 1032100        \u003c- last sample before kill\n\ndriver_f1.log:\ntimeout 240 python3 /e2e/F1/victim_curl.py ...  606 Killed\nVICTIM_CURL_EXIT=137         # docker inspect -\u003e OomKilled: true\n\nvictim_simple.log (control, same bomb):\nFETCH_FAILED: HTTP 599: Connection closed\nSCRIPT_FINISHED_NORMALLY     # exit 0, VmRSS peak 66712 kB\n```\n\nHonest framing of the endpoint: the OOM kill at ~1008 MB was forced by the test's 1 GiB cgroup cap as the observation instrument; the \"unbounded\" claim rests on the linear no-plateau RSS curve, death at 67% wire delivery, and the absence of any size accounting in the code path — with more memory the transfer would have continued to the full 4 GiB payload.\n\n## Impact\n\nDenial of service (memory exhaustion) of any Tornado application that uses the curl HTTP client and fetches attacker-influenced URLs. A single ~4 MB response kills a 1 GiB process in ~3 s; wire cost scales as `available_memory / 1000`. Because the accumulation happens on the shared event loop's client, one malicious response takes down the entire application (all concurrently served users), and a slow endless-body variant drains memory gradually below detection thresholds. The attack requires no privileges, no user interaction, and only that the victim's configured client visits the attacker's origin.\n\n### Suggested fix\n\nEnforce a byte budget in the write path of `CurlAsyncHTTPClient`: wrap the `WRITEFUNCTION` (both the `buffer.write` branch and the `streaming_callback` branch) in a counter that aborts the transfer (`curl.setopt(pycurl.FAILONERROR)`-style cancellation or raising from the callback) once the received total exceeds `max_body_size`, plumbed from `initialize()` with the same 100 MiB default as `SimpleAsyncHTTPClient`. Because libcurl decompresses before the write callback, the counter naturally measures *decompressed* bytes — the same semantics as the CVE-2026-49855 fix. (`pycurl.MAXFILESIZE` alone is insufficient: it applies to the compressed transfer size.)\n\n### Affected versions\n\n- **\u003c= 6.5.8** (latest tag; the curl client has never had a response-size limit) and master (`6.6.dev1`, verified 2026-08-15/17).\n- 6.5.6's CVE-2026-49855 fix covered `SimpleAsyncHTTPClient`/`http1connection.py` only; `curl_httpclient.py` was not touched.\n\n## Credit\n\nReported by the diff/ambidiff security research effort (afldl).","status":"published","published":"2026-09-30T23:49:13Z","modified":"2026-10-02T03:56:11.819291984Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high"}],"cwes":["CWE-400","CWE-409"],"updated_at":"2026-11-02T15:46:45.28161Z"},{"id":"RHSA-2026:82040","title":"Red Hat Security Advisory: python-tornado security update","status":"published","published":"2026-10-14T10:32:39Z","modified":"2026-10-20T10:16:57Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high"}],"updated_at":"2026-11-02T15:53:23.547034Z"},{"id":"RHSA-2026:82041","title":"Red Hat Security Advisory: python-tornado security update","status":"published","published":"2026-10-14T10:32:40Z","modified":"2026-10-20T10:16:58Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high"}],"updated_at":"2026-11-02T15:53:23.547034Z"},{"id":"RLSA-2026:82040","title":"Important: python-tornado security update","description":"Python is an interpreted, interactive, object-oriented programming language, which includes modules, classes, exceptions, very high level dynamic data types and dynamic typing. Python supports interfaces to many system calls and libraries, as well as to various windowing systems.\n\nSecurity Fix(es):\n\n* tornado: Tornado: Denial of Service via decompression bomb in CurlAsyncHTTPClient (CVE-2026-103262)\n\nFor more details about the security issue(s), including the impact, a CVSS score, acknowledgments, and other related information, refer to the CVE page(s) listed in the References section.","status":"published","published":"2026-10-14T06:30:05.473043Z","modified":"2026-10-14T07:00:03.771623977Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high"}],"updated_at":"2026-11-02T15:53:26.449929Z"},{"id":"RLSA-2026:82041","title":"Important: python-tornado security update","description":"Python is an interpreted, interactive, object-oriented programming language, which includes modules, classes, exceptions, very high level dynamic data types and dynamic typing. Python supports interfaces to many system calls and libraries, as well as to various windowing systems.\n\nSecurity Fix(es):\n\n* tornado: Tornado: Denial of Service via decompression bomb in CurlAsyncHTTPClient (CVE-2026-103262)\n\nFor more details about the security issue(s), including the impact, a CVSS score, acknowledgments, and other related information, refer to the CVE page(s) listed in the References section.","status":"published","published":"2026-10-14T12:08:56.68559Z","modified":"2026-10-14T12:30:02.660550679Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high"}],"updated_at":"2026-11-02T15:53:26.449929Z"},{"id":"UBUNTU-CVE-2026-103262","description":"Tornado versions before 6.5.9 contain an unbounded memory accumulation vulnerability in CurlAsyncHTTPClient that allows remote attackers to cause denial of service by sending a compressed response. Attackers can send a gzip-encoded decompression bomb that accumulates in memory without size limits, causing the application process to be killed by out-of-memory conditions.","status":"published","published":"2026-10-01T11:17:00Z","modified":"2026-11-06T20:00:37.443118167Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high"},{"version":"4.0","score":8.7,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high"}],"updated_at":"2026-11-06T20:07:08.647364Z"},{"id":"ALSA-2026:82040","title":"Important: python-tornado security update","description":"Python is an interpreted, interactive, object-oriented programming language, which includes modules, classes, exceptions, very high level dynamic data types and dynamic typing. Python supports interfaces to many system calls and libraries, as well as to various windowing systems.  \n\nSecurity Fix(es):  \n\n  * tornado: Tornado: Denial of Service via decompression bomb in CurlAsyncHTTPClient (CVE-2026-103262)\n\n\nFor more details about the security issue(s), including the impact, a CVSS score, acknowledgments, and other related information, refer to the CVE page(s) listed in the References section.\n","status":"published","published":"2026-10-13T00:00:00Z","modified":"2026-10-29T11:32:38.209913302Z","updated_at":"2026-11-02T15:27:55.200553Z"},{"id":"ALSA-2026:82041","title":"Important: python-tornado security update","description":"Python is an interpreted, interactive, object-oriented programming language, which includes modules, classes, exceptions, very high level dynamic data types and dynamic typing. Python supports interfaces to many system calls and libraries, as well as to various windowing systems.  \n\nSecurity Fix(es):  \n\n  * tornado: Tornado: Denial of Service via decompression bomb in CurlAsyncHTTPClient (CVE-2026-103262)\n\n\nFor more details about the security issue(s), including the impact, a CVSS score, acknowledgments, and other related information, refer to the CVE page(s) listed in the References section.\n","status":"published","published":"2026-10-13T00:00:00Z","modified":"2026-10-29T11:31:01.000178003Z","updated_at":"2026-11-02T15:27:55.200553Z"},{"id":"AZL-105378","title":"CVE-2026-103262 affecting package python-tornado 6.3.3-11","description":"Tornado versions before 6.5.9 contain an unbounded memory accumulation vulnerability in CurlAsyncHTTPClient that allows remote attackers to cause denial of service by sending a compressed response. Attackers can send a gzip-encoded decompression bomb that accumulates in memory without size limits, causing the application process to be killed by out-of-memory conditions.","status":"published","published":"2026-10-01T11:17:21Z","modified":"2026-11-05T05:36:30Z","updated_at":"2026-11-05T16:41:17.719431Z"},{"id":"CLSA-2026-1790836708","title":"TuxCare security update for tornado (1 CVE)","description":"TuxCare rebuilt tornado to address 1 CVE. Fixed in 6.1.0.post10+tuxcare.","status":"published","published":"2026-10-01T06:38:28Z","modified":"2026-10-15T09:48:02.405224454Z","updated_at":"2026-11-02T15:43:48.65893Z"},{"id":"CLSA-2026-1790846524","title":"TuxCare security update for tornado (1 CVE)","description":"TuxCare rebuilt tornado to address 1 CVE. Fixed in 5.1.1.post6+tuxcare.","status":"published","published":"2026-10-01T09:22:04Z","modified":"2026-10-15T09:48:02.365563798Z","updated_at":"2026-11-02T15:43:48.65893Z"},{"id":"USN-8952-1","title":"python-tornado vulnerabilities","description":"It was discovered that Tornado did not properly handle certain HTTP requests\nand network inputs. An attacker could possibly use these issues to cause a\ndenial of service, expose sensitive information, or bypass access controls.\n\n(CVE-2023-54397, CVE-2024-14029, CVE-2026-91990, CVE-2026-91992,\nCVE-2026-103262, CVE-2026-103263)","status":"published","published":"2026-10-22T12:07:23Z","modified":"2026-11-06T20:00:46.992536703Z","updated_at":"2026-11-06T20:06:20.037234Z"}]}}
