CVE-2025-13836 Python http.client Read DoS and OOM via Content-Length

  • Thread Author
A newly recorded weakness in Python’s standard HTTP client lets a malicious server force a client process to allocate huge amounts of memory by abusing the Content-Length handling, creating a remote Denial‑of‑Service (DoS) and out‑of‑memory (OOM) risk for applications that use the library without additional safeguards. The issue is tracked as CVE‑2025‑13836 and was fixed upstream by a code change that changes how http.client reads response bodies so memory usage grows only with the data actually received rather than with the Content‑Length header value.

Background​

Python’s http.client is the low‑level HTTP client used by the standard library and by many higher‑level modules and third‑party libraries. Historically, HTTP client code exposes two different reading styles: callers can request a specific number of bytes, or they can call read with no argument to obtain the entire remaining response body. In many implementations the latter mode trusts either the Content‑Length header or the chunked encoding state to determine how many bytes to allocate and return.
CVE‑2025‑13836 stems from the behavior where a call to http.client.HTTPResponse.read without an explicit amount will allocate a buffer equal to the Content‑Length header before data has arrived. If a malicious or misconfigured server supplies an extremely large Content‑Length, the client will attempt to allocate that much memory even if the server sends far less actual payload, allowing remote actors to provoke OOM conditions and process death in an easy, unauthenticated network call. The vulnerability was reported and triaged through CPython’s issue tracker and fixed via a pull request that was merged on December 1, 2025.

What the vulnerability is (plain language)​

  • The core bug: http.client’s read (when called with no size) used the Content‑Length header value to decide how much memory to allocate before reading the socket, rather than streaming in bounded chunks.
  • The consequence: a malicious server can send a small amount of data or deliberately truncate the response but advertise an enormous Content‑Length. The client will still pre‑allocate that large buffer and likely trigger MemoryError, heavy swapping, or complete process termination — classic DoS behavior.
  • Attack surface: any Python program that uses http.client (directly or indirectly), calls response.read without supplying a safe amount, and relies on untrusted servers. This includes scripts, microservices, CLI tools, and embedded Python runtimes used in larger products.
The CPython maintainers summarized the technical problem and merged a patch that changes the read behavior to process large bodies in incremental chunks so memory usage is proportional to actually received bytes rather than trusting the Content‑Length header upfront. Tests were added to validate behavior with very large Content‑Length values.

Technical analysis — how the fix works​

The upstream change addresses the root cause by altering HTTPResponse.read semantics in the implementation:
  • For responses larger than a small threshold, the code now reads the body in chunks and grows an internal buffer incrementally (using a BytesIO-like strategy), rather than pre‑allocating a single large bytes object sized to Content‑Length.
  • The patch introduces a threshold limit and uses geometrically increasing chunk sizes for efficient reads: this keeps memory overhead minimal while preserving high throughput for legitimately large bodies.
  • New unit tests were added that simulate servers advertising Content‑Length values from small numbers up to extremely large bit shifts; the tests ensure that truncated responses raise IncompleteRead and that proper length results return the expected byte counts.
Put simply: the fix converts an eager allocation model into a streamed, bounded model for large responses — a standard defensive programming approach for network parsers.

Affected versions and patch status​

  • The Python Security team and CVE record identify CPython as the affected product. The vulnerability applies to the active branches that had the old read semantics; the pull request was merged into the main branch and backports were prepared for supported maintenance branches.
  • Distribution trackers show that packaged Python versions remain in various states of remediation; for example, Debian and other vendor feeds listed multiple Python package builds as vulnerable and working toward packaging the backports. Administrators should confirm their distro’s package advisory and apply the vendor update when available.
  • For runtime evidence: the fix landed in the mainline CPython tree on Dec 1, 2025 and maintainers are actively backporting the change to supported branches (3.10–3.14) and bundling the fix into maintenance releases and distro packages. Operators should look for patched releases or vendor advisories that explicitly reference GH‑119454 (the merged PR) or the corresponding commit IDs.
Note: distribution packaging schedules vary. Even after upstream fixes are merged, downstream package maintainers need time to ship updates — that’s why distro trackers may show “vulnerable / pending fix” entries for a while. Confirm package versions in your environment instead of relying on generic release dates.

Severity and exploitability​

  • The Python Software Foundation’s CNA entry and NVD summarize the impact as availability (DoS). The CVSS assessments published at disclosure time put the issue in the Medium range (CVSS v4.0 / v3.x contributions vary—NVD and the CNA listed a 6.3-ish assessment; some vendors show a close 6.5 value). This reflects that the attack is network‑accessible and requires low complexity, but the outcome is limited to denial of service rather than code execution or data exposure.
  • Exploitability is straightforward when an attacker controls the server that a vulnerable client will contact (for example, a benign remote URL that an app follows or a maligned mirror). No authentication or special privileges are required. That low precondition makes this a practical risk for client‑side tooling and automated crawlers that fetch remote URLs without content‑size checks.
  • As of the last public records at disclosure there were no confirmed reports of active exploitation in the wild, but the class of problem (unbounded allocations from malformed headers) is well understood and easy to automate in attacks, which keeps the operational risk meaningful. Treat “no current exploitation” as transitory; attackers commonly weaponize simple DoS faults quickly once a public explanation and PoC patterns circulate.

Which deployments are at real risk​

  • Client applications that fetch content from untrusted or semi‑trusted servers and that call response.read without size limits.
  • Automation and scraping tools that process external URLs (CI runners, bots, monitoring agents).
  • Libraries or products that embed Python and include code that uses http.client directly (some internal or vendor components do).
  • Containerized services and serverless functions with limited memory budgets: an attacker can crash multiple containers quickly by hitting an endpoint that triggers outbound HTTP calls to a malicious host.
  • Note for Windows admins: Python is widely used in cross‑platform tooling, DevOps automation, and management scripts. Any environment that executes unvetted Python code or uses Python to pull remote resources (update checks, telemetry, package mirrors) should be considered in scope. Similar memory‑allocation/streaming bugs have appeared in many languages and libraries and often cause surprising outages in mixed Windows/Linux estates.

Immediate mitigations (before you can patch)​

  • Upgrade: prioritize installing patched Python releases as soon as your OS/distribution or vendor publishes them. The upstream fix is already merged; vendor packages should follow quickly.
  • Defensive coding (for maintainers and app teams):
  • Avoid response.read with no limit. Prefer streaming reads with a bounded chunk size:
  • Example pattern: read in a loop with response.read(8192) until no more bytes are returned, assembling into a stream or writing to disk as data arrives.
  • Validate Content‑Length header values before trusting them (e.g., clamp to a sane maximum and reject or log anomalous headers).
  • Use timeouts and connection limits to avoid long‑running or resource‑stalled connections.
  • Use a hardened HTTP proxy or gateway (NGINX, HAProxy, commercial WAFs) that enforces upstream header limits and body size caps. Layering a proxy in front of your services makes it easier to impose central limits without changing every client.
  • Memory limits and process isolation:
  • Run vulnerable Python processes in memory‑constrained containers or under cgroups so the impact is scoped to a containerized process rather than the entire host.
  • Use process supervisors that restart workers gracefully if they OOM, preserving service availability.
  • Monitoring and detection:
  • Add alerts for sudden MemoryError exceptions, increased swap, or OOM kills from python processes.
  • Monitor for repeated outbound requests to the same remote host from multiple processes — a sign that an attacker is abusing a call pattern.
  • Inventory:
  • Use SBOMs, package managers, and environment scans to find where http.client is used directly. Search your repositories for “import http.client” and for common wrappers that may call read without limits. Treat embedded Python runtimes in third‑party appliances as distinct inventory items to validate with vendors.

How to test if you’re vulnerable​

  • Reproduce locally in a controlled lab: run a small TCP server that responds with an HTTP reply whose Content‑Length header is an extremely large number but only sends a small amount of payload. A vulnerable client that calls response.read without bounds will either allocate the advertised size or will hang/throw MemoryError. The CPython test added to the fix simulates these conditions; consult the repo test file for example test harness code. Do not run such tests on production hosts or public networks — keep them in a lab.

Detection and incident response guidance​

  • If you observe Python processes repeatedly crashing with MemoryError or the kernel OOM killer invoked around Python worker PIDs, prioritize a forensic capture of recent outbound HTTP endpoints and a stack trace of the crashing process.
  • Correlate process crashes with recent outbound DNS resolutions or IP addresses — a malicious host can be probed and blocked.
  • If a crash is due to third‑party code you cannot patch immediately, isolate that workload behind a proxy that enforces header/body limits and consider replacing the offending client call with a safe wrapper until a full upgrade can be scheduled.

Broader implications and why this class of bug keeps recurring​

Parsing inputs and honoring remote metadata (like Content‑Length) without defensive bounds is a long‑standing hazard in network stacks. Similar issues — where a header or protocol field is trusted to size an allocation — have produced OOMs, swapping storms, and process crashes across languages and ecosystems. The fix in CPython mirrors defensive approaches used elsewhere: stream large payloads by chunks, cap ambiguous header values, and enforce strict per‑connection limits.
For Windows‑heavy teams, this is a reminder that cross‑platform open‑source runtimes (Python, Node, Go, etc. are integral to modern toolchains and can affect Windows endpoints indirectly: any management tools, scripts, or orchestrators that call external URLs are potential vectors. Inventorying those runtimes and keeping them updated is as important as patching Windows‑native services.

Recommended timeline for administrators and developers​

  • Within 24–72 hours:
  • Identify systems that run Python workloads which fetch external HTTP content automatically or parse third‑party URLs.
  • Apply immediate mitigations (proxies, clamping Content‑Length, instrumenting memory alerts).
  • Within 1 week:
  • Apply upstream vendor patches or distro updates as they become available; confirm packages explicitly reference the CPython PR/commit or CVE when possible.
  • Replace unbounded response.read usages with safe, bounded streaming reads in code under your control.
  • Within 1 month:
  • Audit vendor appliances and third‑party applications that embed Python; ask vendors for their patch timelines if they bundle vulnerable runtimes.
  • Perform a risk assessment for public‑facing clients and automation that contact untrusted servers.
  • Longer term:
  • Adopt secure coding checklists for network I/O: enforce limits, validate headers, and use streaming APIs.
  • Add SBOM generation and runtime SBOM verification into CI to detect embedded, unpatched runtimes sooner.

Strengths of the upstream response — and remaining risks​

Strengths:
  • The CPython team produced a small, clearly scoped fix that preserves API semantics for normal cases while eliminating the eager allocation failure. The patch includes tests and was merged and backported rapidly, which reduces long‑term exposure.
  • The change is defensive rather than invasive: it reads in chunks and thus minimizes regressions or functional surprises for legitimate clients.
Remaining risks:
  • Distribution lag: downstream OS and packaging ecosystems must ship the backports and rebuild packages. Until those packages are installed, many environments remain exposed. Confirm your exact package versions.
  • Transitive exposure: third‑party products that embed older Python releases (appliances, OEM binaries, vendor toolchains) can lag behind OS packages by months. These often require vendor coordination.
  • Incomplete mitigation: code that still calls unbounded read without safeguards — and runs long‑lived or memory‑constrained workloads — remains at risk if the underlying runtime is not updated. Static scanning and code review are necessary complements to installing patches.

Final assessment​

CVE‑2025‑13836 is a pragmatic, medium‑severity memory‑exhaustion vulnerability in a widely used standard library component. The engineering fix is small and effective — switching to streamed, chunked reads — and upstream maintainers merged the change and prepared backports quickly. The operational risk is real because the exploit is trivial when an attacker controls the server a client will contact, but the impact is availability‑only rather than data compromise or remote code execution. That combination means the issue deserves prompt remediation but does not require the same emergency posture as an active RCE in a privilege‑bearing component.
Windows administrators, developers, and package maintainers should:
  • Inventory Python usage and pay special attention to tools that fetch remote content automatically.
  • Apply vendor and distro patches as they arrive and update embedded runtimes in vendor appliances.
  • Temporarily clamp Content‑Length handling, enforce proxies with header/body limits, and convert any unbounded response.read calls to bounded streaming reads.
CVE‑2025‑13836 is a textbook case: small, focused engineering fixes at the library level neutralize a large class of trivial attacks — but the real challenge for most organizations is quickly finding and updating every runtime that might call into http.client in the wild.
Acknowledgement: the technical fix and details described here are drawn from the CPython issue and pull request discussion and the published CVE records; administrators should confirm remediation status against their distribution and vendor advisories before declaring systems fully remediated.
Source: MSRC Security Update Guide - Microsoft Security Response Center