VYPR
High severity7.5GHSA Advisory· Published Jun 15, 2026· Updated Jun 15, 2026

tornado AsyncHTTPClient accumulates decompressed chunks without size limit (gzip bomb)

CVE-2026-49855

Description

Tornado's gzip decompression in SimpleAsyncHTTPClient lacks a cumulative size limit, allowing a malicious server to exhaust memory via a gzip bomb; fixed in 6.5.6.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Tornado's gzip decompression in SimpleAsyncHTTPClient lacks a cumulative size limit, allowing a malicious server to exhaust memory via a gzip bomb; fixed in 6.5.6.

Vulnerability

Tornado's gzip decompression routines in SimpleAsyncHTTPClient (default configuration) process data in chunks but do not enforce an overall limit on the total decompressed size, only on the compressed size. This allows a malicious server to send a small compressed payload that decompresses to a very large size (gzip bomb). Affected versions are prior to Tornado 6.5.6. HTTPServer is not affected unless decompress_request=True is set. [1][2]

Exploitation

An attacker controlling a server that the client connects to can send a response with a small compressed body that decompresses to an extremely large size. The client's SimpleAsyncHTTPClient will accumulate the decompressed chunks in memory without a cumulative limit. No authentication or special privileges are required; the attacker only needs to serve a malicious response. [1][2]

Impact

Successful exploitation leads to uncontrolled memory consumption on the client side, potentially causing denial of service (DoS) due to memory exhaustion. The attacker does not gain code execution or data access, but can disrupt the client's operation. [1][2]

Mitigation

The fix is in Tornado 6.5.6, where max_body_size is now checked against both the compressed and cumulative decompressed size. Prior to upgrading, users can set decompress_response=False or switch to CurlAsyncHTTPClient as a workaround. [1][2]

AI Insight generated on Jun 15, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

3

Patches

1
ff808b33adc5

http1connection: Enforce max_body_size in _GzipMessageDelegate

https://github.com/tornadoweb/tornadoBen DarnellMay 26, 2026Fixed in 6.5.6via llm-release-walk
2 files changed · +19 3
  • tornado/http1connection.py+14 2 modified
    @@ -182,7 +182,9 @@ def read_response(self, delegate: httputil.HTTPMessageDelegate) -> Awaitable[boo
             been read. The result is true if the stream is still open.
             """
             if self.params.decompress:
    -            delegate = _GzipMessageDelegate(delegate, self.params.chunk_size)
    +            delegate = _GzipMessageDelegate(
    +                delegate, self.params.chunk_size, self._max_body_size
    +            )
             return self._read_message(delegate)
     
         async def _read_message(self, delegate: httputil.HTTPMessageDelegate) -> bool:
    @@ -705,9 +707,16 @@ async def _read_body_until_close(
     class _GzipMessageDelegate(httputil.HTTPMessageDelegate):
         """Wraps an `HTTPMessageDelegate` to decode ``Content-Encoding: gzip``."""
     
    -    def __init__(self, delegate: httputil.HTTPMessageDelegate, chunk_size: int) -> None:
    +    def __init__(
    +        self,
    +        delegate: httputil.HTTPMessageDelegate,
    +        chunk_size: int,
    +        max_body_size: int,
    +    ) -> None:
             self._delegate = delegate
             self._chunk_size = chunk_size
    +        self._max_body_size = max_body_size
    +        self._decompressed_body_size = 0
             self._decompressor = None  # type: Optional[GzipDecompressor]
     
         def headers_received(
    @@ -732,6 +741,9 @@ async def data_received(self, chunk: bytes) -> None:
                         compressed_data, self._chunk_size
                     )
                     if decompressed:
    +                    self._decompressed_body_size += len(decompressed)
    +                    if self._decompressed_body_size > self._max_body_size:
    +                        raise httputil.HTTPInputError("decompressed body too large")
                         ret = self._delegate.data_received(decompressed)
                         if ret is not None:
                             await ret
    
  • tornado/test/httpserver_test.py+5 1 modified
    @@ -1133,7 +1133,7 @@ def test_uncompressed(self):
     
     class GzipTest(GzipBaseTest, AsyncHTTPTestCase):
         def get_httpserver_options(self):
    -        return dict(decompress_request=True)
    +        return dict(decompress_request=True, max_body_size=100)
     
         def test_gzip(self):
             response = self.post_gzip("foo=bar")
    @@ -1154,6 +1154,10 @@ def test_gzip_case_insensitive(self):
             )
             self.assertEqual(json_decode(response.body), {"foo": ["bar"]})
     
    +    def test_size_limit(self):
    +        with ExpectLog(gen_log, ".*decompressed body too large", level=logging.INFO):
    +            self.post_gzip("x" * 101)
    +
     
     class GzipUnsupportedTest(GzipBaseTest, AsyncHTTPTestCase):
         def test_gzip_unsupported(self):
    

Vulnerability mechanics

Root cause

"Missing cumulative size limit on decompressed gzip data allows unbounded memory allocation."

Attack vector

A malicious HTTP server sends a small gzip-compressed response that decompresses to a very large payload (a decompression bomb). When `SimpleAsyncHTTPClient` (default configuration) or `HTTPServer` with `decompress_request=True` processes the response, the `_GzipMessageDelegate` decompresses each chunk but never enforces an upper bound on the total decompressed size [CWE-409]. The attacker does not need authentication; they only need to serve a crafted response to a client that uses gzip decompression.

Affected code

The vulnerability is in `tornado/http1connection.py` in the `_GzipMessageDelegate` class. The `data_received` method decompresses gzip chunks without tracking the cumulative decompressed size, allowing unlimited memory consumption. The patch adds `_decompressed_body_size` tracking and a check against `max_body_size`.

What the fix does

The patch adds a `_decompressed_body_size` counter and a `_max_body_size` parameter to `_GzipMessageDelegate`. In `data_received`, after each decompression step the cumulative size is compared against `max_body_size`; if exceeded, an `HTTPInputError` is raised. This ensures the limit applies to the post-decompression size, not just the compressed Content-Length. The test `test_size_limit` verifies that a 101-byte uncompressed payload triggers the error.

Preconditions

  • configThe client must use SimpleAsyncHTTPClient (default) or HTTPServer with decompress_request=True.
  • networkThe attacker controls an HTTP server that sends a gzip-compressed response with a high compression ratio.

Generated on Jun 15, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

2

News mentions

0

No linked articles in our index yet.