Skip to content

Out-of-memory when reading a HTTP response with large Content-Length #119451

Closed

Description

@serhiy-storchaka

http.client.HTTPResponse.read() (without argument) consumes the amount of memory specified by the value of the Content-Lenght header in the response before starting receiving the data from the socket. Normally, if the server does not send enough data, the client gets IncompleteRead error, but if Content-Lenght is too large, it can consume a large amount of memory and CPU time and cause swapping. Therefore, a maleficent server can cause a DOS attack on client by sending a small response.

Linked PRs

Activity

  1. added a commit that references this issue on Dec 1, 2025
  2. added 5 commits that reference this issue on Dec 1, 2025
  3. changed the title [-]Out-of-memory when reading a HTTP response with large Content-Lenght[/-] [+]Out-of-memory when reading a HTTP response with large Content-Length[/+] on Dec 1, 2025
  4. added a commit that references this issue on Dec 1, 2025
  5. z00z00z00 commented on Dec 3, 2025

    @z00z00z00

    The CVE assigned is CVE-2025-13836

  6. added a commit that references this issue on Dec 5, 2025
  7. added a commit that references this issue on Dec 6, 2025
  8. added a commit that references this issue on Dec 22, 2025
  9. frenzymadness commented on Jan 13, 2026

    @frenzymadness
    Contributor

    I know those versions are no longer supported, but AFAICT, releases older than 3.10.0a1 are not vulnerable. It seems that this vulnerability is the result of this performance improvement: 152f0b8

    It would be great if somebody could confirm this and reflect that fact in vulnerability databases.

  10. serhiy-storchaka commented on Jan 13, 2026

    @serhiy-storchaka
    MemberAuthor

    They are vulnerable. bytearray(amt) allocates a large buffer and fills it with zeros before reading even started.

  11. frenzymadness commented on Jan 13, 2026

    @frenzymadness
    Contributor

    Thanks for the quick response. What you've said is true but amt is the amount of data requested by the user/client, not the amount of data in content-length header coming from server.

    The following code is from 3.9.25:

        def read(self, amt=None):
            if self.fp is None:
                return b""
    
            if self._method == "HEAD":
                self._close_conn()
                return b""
    
            if amt is not None:
                # Amount is given, implement using readinto
                b = bytearray(amt)
                n = self.readinto(b)
                return memoryview(b)[:n].tobytes()
            else:
                # Amount is not given (unbounded read) so we must check self.length
                # and self.chunked
    
                if self.chunked:
                    return self._readall_chunked()
    
                if self.length is None:
                    s = self.fp.read()
                else:
                    try:
                        s = self._safe_read(self.length)
                    except IncompleteRead:
                        self._close_conn()
                        raise
                    self.length = 0
                self._close_conn()        # we read everything
                return s

    If user requests a huge amount of data, bytearray(amt) allocates memory for them. But this vulnerability is about an unreasonable huge number in content-length response headers coming from server.
    If amt is not specified and self.length coming from content-length header is, read function calls _safe_read and that reads the response chunk by chunk with a limit:

        def _safe_read(self, amt):
            """Read the number of bytes requested, compensating for partial reads.
    
            Normally, we have a blocking socket, but a read() can be interrupted
            by a signal (resulting in a partial read).
    
            Note that we cannot distinguish between EOF and an interrupt when zero
            bytes have been read. IncompleteRead() will be raised in this
            situation.
    
            This function should be used when <amt> bytes "should" be present for
            reading. If the bytes are truly not available (due to EOF), then the
            IncompleteRead exception can be used to detect the problem.
            """
            s = []
            while amt > 0:
                chunk = self.fp.read(min(amt, MAXAMOUNT))
                if not chunk:
                    raise IncompleteRead(b''.join(s), amt)
                s.append(chunk)
                amt -= len(chunk)
            return b"".join(s)

    Am I wrong here?

  12. serhiy-storchaka commented on Jan 13, 2026

    @serhiy-storchaka
    MemberAuthor

    You are right, but the _safe_read() code was changed not in 152f0b8 (bpo-41002), but in d6bf6f2 (bpo-36050). This is 3.8.0a4.

  13. frenzymadness commented on Jan 14, 2026

    @frenzymadness
    Contributor

    Hmm, it seems to be more complex. AFAICT, 3.6 is not vulnerable, 3.8 is vulnerable, 3.9 is not, and 3.10+ are vulnerable again.

  14. antdking commented on Jan 14, 2026

    @antdking

    It may have been missed, but NIST have set a base score of 9.1 and flagged as a Critical vulnerability: https://nvd.nist.gov/vuln/detail/CVE-2025-13836

  15. added 2 commits that reference this issue on Jan 25, 2026
  16. added a commit that references this issue on May 14, 2026
  17. added 2 commits that reference this issue on Sep 18, 2026
  18. added 2 commits that reference this issue on Oct 1, 2026
  19. added a commit that references this issue on Oct 17, 2026
  20. added a commit that references this issue on Oct 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.10 (EOL)end of life3.11only security fixes3.12only security fixes3.13only security fixes3.14bugs and security fixes3.15bugs and security fixesstdlibStandard Library Python modules in the Lib/ directorytopic-IOtype-securityA security issue

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions